From: Michael Montalbo Date: Sun, 23 Aug 2026 17:18:46 GMT Subject: [RFC PATCH 02/14] organize: add the labeler, organizer, and apply --labels-only Message-ID: <20260823171915.2662373-3-mmontalbo@gmail.com> In-Reply-To: <20260823171915.2662373-1-mmontalbo@gmail.com> 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