From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 113462E1722 for ; Sun, 23 Aug 2026 17:19:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787505571; cv=none; b=eH8DVIqrDA/AeJDOr0QGBA63se5xkh/hKVzIUYOeJKDvqry3w91NKJ0ECFuiuDcpXGmKugTTanU+mli4+4efGYhCgpZi7RmFt7Q9o0KKJNgziOrYMHr4jG+5tH3HCf6QQwP2D069961ZNMHzg3zxu3MLGdNYuBqP9B/gJmd8/mw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787505571; c=relaxed/simple; bh=lrLOpiwUYqoSmyooRxxouvi5hmSyRdADKIxBQj8qXcs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mGAoJ+gmM13jRkt3y6iCYcT/hC0cL1Ta0KmpYLPQR+Wx+T6WyRw+1BG/0A64j98u5StNDMMTVt7REX2NzetDc1hgpy71M886g9cRP6atN4nyTXXCzEKJ0+J4rp3XnxcdHmrfud6blK4HZS8UAnae+blGMN/BHYWHSIM/OOP3NNQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=A1arLPNI; arc=none smtp.client-ip=209.85.214.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="A1arLPNI" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-2cc7e86e7aeso36389815ad.2 for ; Sun, 23 Aug 2026 10:19:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787505562; x=1788110362; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1SKbTg2fccCNv5y9oPCvlF0Yx4GhwXET3Wq2x3I80Uw=; b=A1arLPNIZzzv+yQsT1rJuwTvkiFCslATFCnMKsX+8C2I+blJrjm4GFM8fS6akZYn2Z +TPOFhH1/bM29MFNJjES+8mQZT7ZsfIXNadDFsZm4hYviH3NwXZwaJpEHLLJpCvtp80j eDiKvPerZRnzL3SpH/E40ysXkoSNsaBmR3HuWyTwYpneIwzxX4tKb8H4rziJe5Yvdts2 yuPV+N6OmQrqVnyc1g3GJaj+aR+UOgsjP3Cs15wv/0OxduCVHLhKF81Xx1a7ppJ1iXnN Jj4WryX2hmDgy20R6aSl257Nt1OAKTWrTk4GAullshvmdk3BRe9U6Bw1zi352A0fJJgB gbag== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787505562; x=1788110362; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=1SKbTg2fccCNv5y9oPCvlF0Yx4GhwXET3Wq2x3I80Uw=; b=otS9EnqUAXGZ3qt0dIodqqHfAe83/XCyJk+eaKS2pgzGSK5qPVg6gsmMieY34uetGd QKRX1YA77+QNqgSjQPI4DypnwGfDGdlKF27uO4B1C55deYiIKLBu0p0v9CSaxjm2WFH2 zW0MffMeX/hgJOm3/puQOImMnQOzbpsmjbESCX0G9FGmD6l7QPHFd91vLsplPSkiKHMp 8fMZNpY4h5MvW4XFDyLyBHEKu6x7rDIfZpaVEhVI6DP0JOyY90q6i0zJJhnf0MAZhKHB cGwXmmLeDcfz52HmeiYPCXv+JWnNwai8TbgzptVWhAb5AgrnKWolhgc3woGlM6TVY1/o NlvQ== X-Gm-Message-State: AFuF++nKr9u71jNTMtkZdlH8M+14rlukPPy8HsZZQy6NSwt9WxL8pJHm DhXRZ0G3I0FIa9rAWiwOX9Bxis1Jpqxsvr/8JOKYW67EOfYV6zA0zmUvMiQ92g== X-Gm-Gg: AR+sD11KAA4sE4LMRgrDEuWJsD1sYq7EE3q2K4et0BAujekbydqeAAnNoRTwEPkBnkp ZGBQXBrQPI5QlkGTFcPlWfo8GQ+Ku0o1imH5rgCyqw7He8afw4IFhRw6DZYlFinRYMBGgNOK8IA HMt8XdE6+DjvrbNCamlnMrBOgna/vIoS9NyWBp4MoGMz88Si2qgXCK01avqIGR2RY5d0m8trKYj V2WlH1SJSdFdKT+QZDB+wrctiB/j7nxFsSTlmPAjctN7zHBm93TjkfQ9WmwiJqAXJ9e8SH8jyXn 2u28yx+RRTu/RMokr56oGxg7PNwWKds41PEmrs0FyaPRRN8thX6WbFMd9kvTQLCeB9A2boo0pZS Q7nzP/cyyxtKoCJugierYAwX1j4A9SLnFjpQvWstd9ODWa6WOmxkcTecr6+HcO0WrUPKTQtwiA2 9xEUJne8k121ykQIRYVFx0Vvhzi1qx44dw/yhBJBF3nqsbNRWxdov9DF91jIEO/+CpGmC4q0guG KuEx30QPH6h51iH77TNdpWUPJtBr+mVH79kxKbqesxOApQOuBUMv3oKNfdkh9MbvMXvgY2SCGoA TtTicxxwNg8C/jb2va2CR9kEHiiw95XekNngO5k1+/OCww== X-Received: by 2002:a17:903:2c0c:b0:2d3:78c2:1f19 with SMTP id d9443c01a7336-2d64b0270b8mr388433355ad.9.1787505559793; Sun, 23 Aug 2026 10:19:19 -0700 (PDT) Received: from localhost (192-184-169-91.fiber.dynamic.sonic.net. [192.184.169.91]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-327f91d37cesm20926091eec.15.2026.08.23.10.19.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 23 Aug 2026 10:19:19 -0700 (PDT) From: Michael Montalbo To: git@vger.kernel.org Cc: Patrick Steinhardt Subject: [RFC PATCH 02/14] organize: add the labeler, organizer, and apply --labels-only Date: Sun, 23 Aug 2026 10:18:46 -0700 Message-ID: <20260823171915.2662373-3-mmontalbo@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260823171915.2662373-1-mmontalbo@gmail.com> References: <20260823171915.2662373-1-mmontalbo@gmail.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The core builtin reconciles a tree against a hand-written [labels] section, but nothing fills [labels] in and every move is a bare rename. A project needs to record where each file belongs and to repoint the references a move breaks. Add the two configured commands that supply that judgment, the way a merge driver's command is configured in git config: organize.labeler records a [labels] line per root file in scope organize.organizer returns edits for the files a move touches git organize apply --labels-only runs the labeler and writes [labels], preserving the lines of already placed files. Plain git organize apply, when an organizer is configured, hands it the standing moves over a pipe; the organizer returns a patch of the edits and a reason for any move it declines. The moves and the patch apply as one git apply transaction, so a failure leaves the tree untouched. A declined move keeps its file and its [labels] line. The organizer patch is validated before anything applies. It may edit a referring file, or rename a moved file as it edits it, but it must not add, delete, or copy files, and any rename must match a planned move. Add a reference labeler and organizer under contrib/organize for Git's own tree. The labeler places each source by the "area:" prefix its own commits carry most often, mined with git log --follow: the area its authors name need not be its filename, so ws.c files under whitespace and diffcore-pickaxe.c under pickaxe. git-layout.map groups these prefix tokens into components under [tokens]; odb owns object, object-file, blob, tag, and so on. A source whose prefix is a broad area name that also swept its neighbors, or that has too little history to name an area, falls back to its filename through the [names] section. A source that neither section places, but that changes chiefly alongside one component, is promoted there. A source that couples broadly stays at the root. Each record also carries the prefix, the #include coupling, and the co-change profile as advisory signals. The organizer repoints the moved build object in the Makefile and meson.build, and rewrites the #include of each moved header to its new path across the tree. Signed-off-by: Michael Montalbo --- Documentation/git-organize.adoc | 108 ++++++- Makefile | 2 + builtin/organize.c | 48 +++- contrib/organize/git-layout.map | 41 +++ contrib/organize/labeler | 333 ++++++++++++++++++++++ contrib/organize/organizer | 334 ++++++++++++++++++++++ meson.build | 2 + organize/gitorganize-format.c | 5 + organize/labeler-protocol.c | 52 ++++ organize/labeler-protocol.h | 15 + organize/organize.c | 165 ++++++++++- organize/organize.h | 50 +++- organize/organizer-protocol.c | 254 +++++++++++++++++ organize/organizer-protocol.h | 18 ++ t/t0096-organize.sh | 479 +++++++++++++++++++++++++++++--- 15 files changed, 1817 insertions(+), 89 deletions(-) create mode 100644 contrib/organize/git-layout.map create mode 100755 contrib/organize/labeler create mode 100755 contrib/organize/organizer create mode 100644 organize/labeler-protocol.c create mode 100644 organize/labeler-protocol.h create mode 100644 organize/organizer-protocol.c create mode 100644 organize/organizer-protocol.h diff --git a/Documentation/git-organize.adoc b/Documentation/git-organize.adoc index 4ff76f5c13..8b216146b6 100644 --- a/Documentation/git-organize.adoc +++ b/Documentation/git-organize.adoc @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git organize status' 'git organize apply' +'git organize apply' --labels-only [--reseed] DESCRIPTION @@ -31,18 +32,36 @@ rule it satisfies places it; a file matching no rule is the backlog. source in scope, ` = ...`, with every label the project defines. A placed file is listed too, so its `[labels]` line records its labels, independently of the directory name. Only a label named in a rule -places a file. +places a file; a label named in no rule places nothing and is recorded for a +reader. + +The labeler and organizer live in config: `organize.labeler` and +`organize.organizer`. A label is a key and value the labeler attaches to a +file. `git organize apply --labels-only` runs the labeler and records the +labels. A file is out of place when its matching rule names a directory it +is not in yet. `git organize status` reads `[labels]` and reports the out-of-place files, the backlog, a file in scope that `[labels]` does not record, and a -recorded path that no longer exists. It runs nothing and -changes nothing. +recorded path that no longer exists. status runs no +configured command and changes nothing. `git organize apply` reconciles the tree. It moves each out-of-place file -into its directory. A move is a content-identical rename, so `git log ---follow` and `git blame` track the file exactly. apply stages the result -and repoints each carved file's `[labels]` line to its new path, carrying -its labels. It commits nothing. apply requires a clean worktree. +into its directory. A move that git organize makes on its own is a +content-identical rename, so `git log --follow` and `git blame` track the +file exactly. apply stages the result and repoints each carved file's +`[labels]` line to its new path, carrying its labels. It commits nothing. + +A move can require an edit elsewhere, such as repointing a reference in +another file, or an edit to the moved file itself, such as repointing its +own references. A project supplies those edits with an organizer, its +`organize.organizer` command. apply hands the organizer its +moves. The organizer returns a patch of the edits and, for any move it +cannot complete, a reason to skip it. When the organizer edits a file as it +moves, git's rename detection matches it while its similarity stays above +the rename threshold. apply applies the moves and the patch as one +transaction. With no organizer configured, apply moves the files and makes +no other edit. COMMANDS @@ -52,13 +71,59 @@ status:: Report the files whose placement value names a directory they are not in (the moves), the backlog (recorded files with no matching rule), a file in scope that `[labels]` does not record, and a recorded - path that no longer exists. Changes nothing. + path that no longer exists. Runs no configured + command and changes nothing. apply:: Move each out-of-place file into its directory as a content-identical - rename, repoint each carved file's `[labels]` line to its new path, and - stage the result. apply requires a clean worktree, so the change can be - discarded as a whole. + rename, apply the organizer's edits, repoint each carved file's + `[labels]` line to its new path, and stage the result. apply requires a + clean worktree, so the change can be discarded as a whole. ++ +With `--labels-only`, apply instead records the `[labels]` line for every root +file in scope and stages the file. A file already recorded keeps its line, so a +placement chosen by hand or in an earlier run stands; the labeler only seeds a +file that has no line yet. With `--reseed`, re-derive every line from the +labeler, discarding the recorded placements. This is the only path that runs a +labeler; `git organize apply` without `--labels-only` and `git organize status` +never do. + + +OPTIONS +------- + +--labels-only:: + With apply, run the labeler and record the labels; move no file. A + recorded file keeps its line; the labeler only seeds a file that has no + line yet. + +--reseed:: + With apply `--labels-only`, re-derive every `[labels]` line from the + labeler, discarding the recorded placements. Use it to re-apply the + labeler after its map changes; without it a recorded line is kept. + + +CONFIGURATION +------------- +organize.labeler:: + The command that records the labels. `git organize apply + --labels-only` runs it over the root files in scope. It writes one + record per file on its standard output: the path, a NUL, its + space-separated `key=value` labels, a NUL. A file in scope with no + record is unrecorded, reported apart from the backlog. Use user or + system config for this setting; do + not take it from a repository file. + +organize.organizer:: + The command that returns move edits. apply runs it over the moves. It + reads the pending moves on its standard input and returns a patch and + any skip reasons; see PROTOCOL. Optional. Without it, apply performs the + moves and makes no other edit. Use user or system config for this + setting; do not take it from a repository file. + +The labeler and organizer are trusted, the way a clean or smudge filter or a +hook is trusted. Set them in user or system config, so a repository you clone +cannot supply its own. FILES @@ -71,10 +136,23 @@ FILES file takes the directory of the first rule its labels satisfy, and a file matching no rule is the backlog. `[labels]` holds the recorded labels, one ` = ...` line per source in scope, - including placed files. The project writes `[scope]` and `[layout]`; the - move apply repoints a carved file's line. A `#` line is a comment; git - organize rewrites the file whole, keeping the hand-authored `[scope]` - and `[layout]` verbatim. + including placed files. The project writes `[scope]` and `[layout]`; + `git organize apply --labels-only` writes `[labels]`, and the move apply + repoints a carved file's line. A `#` line is a comment; git organize + rewrites the file whole, keeping the hand-authored `[scope]` and + `[layout]` verbatim. + + +PROTOCOL +-------- +apply speaks a line protocol with the organizer over a pipe. It writes the +version line `git-organize 1 organize`, then a `move