Git development
 help / color / mirror / Atom feed
From: Michael Montalbo <mmontalbo@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <pks@pks.im>
Subject: [RFC PATCH 03/14] organize: add status --exit-code
Date: Sun, 23 Aug 2026 10:18:47 -0700	[thread overview]
Message-ID: <20260823171915.2662373-4-mmontalbo@gmail.com> (raw)
In-Reply-To: <20260823171915.2662373-1-mmontalbo@gmail.com>

git organize status reports and always returns zero. A project that keeps
its tree reconciled needs status to fail when the tree drifts, so a check
can gate on the result.

Add --exit-code. status returns 1 when a file is out of place or a recorded
path no longer exists, and 0 otherwise. A standing backlog alone does not
fail it: a backlog file matches no rule, so it is not out of place.
--exit-code changes no output.

Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
 Documentation/git-organize.adoc | 15 ++++++++++++---
 builtin/organize.c              | 12 +++++++-----
 t/t0096-organize.sh             | 20 +++++++++++++++-----
 3 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/Documentation/git-organize.adoc b/Documentation/git-organize.adoc
index 8b216146b6..37ada38234 100644
--- a/Documentation/git-organize.adoc
+++ b/Documentation/git-organize.adoc
@@ -9,7 +9,7 @@ git-organize - Reconcile a source tree against a declared layout
 SYNOPSIS
 --------
 [verse]
-'git organize status'
+'git organize status' [--exit-code]
 'git organize apply'
 'git organize apply' --labels-only [--reseed]
 
@@ -44,7 +44,9 @@ 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. status runs no
-configured command and changes nothing.
+configured command and changes nothing. With `--exit-code` it exits
+non-zero when a file is out of place, a file in scope is unrecorded, or a
+recorded path is missing; a standing backlog alone does not fail it.
 
 `git organize apply` reconciles the tree. It moves each out-of-place file
 into its directory. A move that git organize makes on its own is a
@@ -72,7 +74,9 @@ status::
 	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. Runs no configured
-	command and changes nothing.
+	command and changes nothing. With `--exit-code`, exit non-zero when a
+	file is out of place, a file in scope is unrecorded, or a recorded path
+	is missing.
 
 apply::
 	Move each out-of-place file into its directory as a content-identical
@@ -92,6 +96,11 @@ never do.
 OPTIONS
 -------
 
+--exit-code::
+	Exit non-zero from status when a file is out of place, a file in scope
+	is unrecorded, or a recorded path is missing. A standing backlog alone
+	does not fail it. Changes no output.
+
 --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
diff --git a/builtin/organize.c b/builtin/organize.c
index 354ba2151f..35247c5aef 100644
--- a/builtin/organize.c
+++ b/builtin/organize.c
@@ -15,13 +15,13 @@
 #include "repository.h"
 
 static const char *const organize_usage[] = {
-	"git organize status",
+	"git organize status [--exit-code]",
 	"git organize apply",
 	"git organize apply --labels-only [--reseed]",
 	NULL
 };
 
-static int organize_status(struct repository *repo)
+static int organize_status(struct repository *repo, int exit_code)
 {
 	struct organize_plan plan = ORGANIZE_PLAN_INIT;
 	int to_move, backlog, unrecorded, orphans;
@@ -67,7 +67,7 @@ static int organize_status(struct repository *repo)
 	}
 
 	organize_plan_release(&plan);
-	return 0;
+	return exit_code && (to_move || unrecorded || orphans) ? 1 : 0;
 }
 
 static int organize_apply(struct repository *repo)
@@ -106,8 +106,10 @@ int cmd_organize(int argc,
 		 const char *prefix,
 		 struct repository *repo)
 {
-	int labels_only = 0, reseed = 0;
+	int exit_code = 0, labels_only = 0, reseed = 0;
 	struct option options[] = {
+		OPT_BOOL(0, "exit-code", &exit_code,
+			 N_("exit non-zero from status when a file is out of place")),
 		OPT_BOOL(0, "labels-only", &labels_only,
 			 N_("with apply, run the labeler and record the labels")),
 		OPT_BOOL(0, "reseed", &reseed,
@@ -126,7 +128,7 @@ int cmd_organize(int argc,
 	if (!strcmp(subcmd, "status")) {
 		if (labels_only)
 			die(_("git organize: --labels-only is an apply option"));
-		ret = organize_status(repo);
+		ret = organize_status(repo, exit_code);
 	} else if (!strcmp(subcmd, "apply")) {
 		if (labels_only) {
 			organize_run_labeler(repo, reseed);
diff --git a/t/t0096-organize.sh b/t/t0096-organize.sh
index c2e6539ef2..d8de3c7e90 100755
--- a/t/t0096-organize.sh
+++ b/t/t0096-organize.sh
@@ -156,6 +156,10 @@ test_expect_success 'status reports the files to move' '
 	test_grep "2 file(s) would move" actual
 '
 
+test_expect_success 'status --exit-code fails when a file is out of place' '
+	test_expect_code 1 git organize status --exit-code
+'
+
 test_expect_success 'apply moves files as content-identical renames and repoints [labels]' '
 	git organize apply &&
 	git diff --cached -M --name-status >actual &&
@@ -169,6 +173,7 @@ test_expect_success 'apply moves files as content-identical renames and repoints
 	git diff --cached --name-only >staged &&
 	test_grep "^.gitorganize$" staged &&
 	git commit -m reconciled &&
+	git organize status --exit-code &&
 	git organize status >actual &&
 	test_grep "nothing to move" actual &&
 	test_grep "^odb/blob.c component=odb" .gitorganize &&
@@ -435,7 +440,8 @@ test_expect_success 'a basename shared across directories does not collide' '
 		test_path_is_file odb/dup.c &&
 		test_path_is_file sub/dup.c &&
 		test_path_is_missing dup.c &&
-		git commit -m reconciled
+		git commit -m reconciled &&
+		git organize status --exit-code
 	)
 '
 
@@ -484,17 +490,20 @@ test_expect_success 'a file in scope with no recorded label is unrecorded' '
 		git commit -m declare &&
 		git organize apply --labels-only &&
 		git commit -m labels &&
-		# a.c is recorded but matches no rule: backlog
+		# a.c is recorded but matches no rule, so it is backlog; a
+		# standing backlog alone does not fail --exit-code
 		git organize status >actual &&
 		test_grep "backlog:" actual &&
 		test_grep "^  a.c$" actual &&
-		# a source in scope that [labels] never recorded is unrecorded
+		git organize status --exit-code &&
+		# a source in scope that [labels] never recorded is unrecorded drift
 		echo b >b.c &&
 		git add b.c &&
 		git commit -m add-b &&
 		git organize status >actual &&
 		test_grep "in scope but unrecorded:" actual &&
-		test_grep "^  b.c$" actual
+		test_grep "^  b.c$" actual &&
+		test_expect_code 1 git organize status --exit-code
 	)
 '
 
@@ -519,7 +528,8 @@ test_expect_success 'status reports a recorded path that no longer exists' '
 		git commit -m drop-b &&
 		git organize status >actual &&
 		test_grep "declared but missing" actual &&
-		test_grep "  b.c" actual
+		test_grep "  b.c" actual &&
+		test_expect_code 1 git organize status --exit-code
 	)
 '
 
-- 
2.54.0


  parent reply	other threads:[~2026-08-23 17:19 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-23 17:18 [RFC PATCH 00/14] git organize: record file placement and apply it Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 01/14] organize: add the git organize builtin Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 02/14] organize: add the labeler, organizer, and apply --labels-only Michael Montalbo
2026-08-23 17:18 ` Michael Montalbo [this message]
2026-08-23 17:18 ` [RFC PATCH 04/14] organize: add the --label selector Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 05/14] organize: declare Git's scope and layout Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 06/14] organize: record a label for every source in scope Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 07/14] odb: gather the odb sources under odb/ Michael Montalbo
2026-08-24 14:08   ` Oswald Buddenhagen
2026-08-24 14:43     ` Junio C Hamano
2026-08-24 20:03       ` Oswald Buddenhagen
2026-08-23 17:18 ` [RFC PATCH 08/14] refs: gather the refs sources under refs/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 09/14] pack: gather the pack sources under pack/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 10/14] diff: gather the diff sources under diff/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 11/14] revision: gather the revision sources under revision/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 12/14] index: gather the index sources under index/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 13/14] setup: gather the setup sources under setup/ Michael Montalbo
2026-08-23 17:18 ` [RFC PATCH 14/14] transport: gather the transport sources under transport/ Michael Montalbo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260823171915.2662373-4-mmontalbo@gmail.com \
    --to=mmontalbo@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=pks@pks.im \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox