[RFC PATCH 03/14] organize: add status --exit-code

Michael Montalbo <[email protected]>
Newsgroups org.kernel.vger.git
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.