From 5e9d0f87fd7d0151349081591c32b7f02db005e8 Mon Sep 17 00:00:00 2001
From: Alex Riesen <raa.lkml@gmail.com>
Date: Wed, 14 Mar 2007 01:17:04 +0100
Subject: [PATCH] Allow git-diff exit with codes similar to diff(1)

This introduces a new command-line option: --exit-code. The diff
programs will return 1 for differences, return 0 for equality, and
something else for errors.

Signed-off-by: Alex Riesen <raa.lkml@gmail.com>
---
 Documentation/diff-options.txt |    5 +++
 builtin-diff-files.c           |    4 ++-
 builtin-diff-index.c           |    4 ++-
 builtin-diff-tree.c            |    5 ++-
 builtin-diff.c                 |   19 ++++++----
 diff-lib.c                     |    5 ++-
 diff.c                         |    6 +++
 diff.h                         |    5 ++-
 t/t4017-diff-retval.sh         |   79 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 118 insertions(+), 14 deletions(-)
 create mode 100755 t/t4017-diff-retval.sh

diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index d8696b7..77a3f78 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -159,5 +159,10 @@
 -w::
 	Shorthand for "--ignore-all-space".
 
+--exit-code::
+	Make the program exit with codes similar to diff(1).
+	That is, it exits with 1 if there were differences and
+	0 means no differences.
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
index aec8338..6ba5077 100644
--- a/builtin-diff-files.c
+++ b/builtin-diff-files.c
@@ -17,6 +17,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info rev;
 	int nongit = 0;
+	int result;
 
 	prefix = setup_git_directory_gently(&nongit);
 	init_revisions(&rev, prefix);
@@ -29,5 +30,6 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
 		argc = setup_revisions(argc, argv, &rev, NULL);
 	if (!rev.diffopt.output_format)
 		rev.diffopt.output_format = DIFF_FORMAT_RAW;
-	return run_diff_files_cmd(&rev, argc, argv);
+	result = run_diff_files_cmd(&rev, argc, argv);
+	return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
 }
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
index 083599d..d90eba9 100644
--- a/builtin-diff-index.c
+++ b/builtin-diff-index.c
@@ -14,6 +14,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
 	struct rev_info rev;
 	int cached = 0;
 	int i;
+	int result;
 
 	init_revisions(&rev, prefix);
 	git_config(git_default_config); /* no "diff" UI options */
@@ -42,5 +43,6 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
 		perror("read_cache");
 		return -1;
 	}
-	return run_diff_index(&rev, cached);
+	result = run_diff_index(&rev, cached);
+	return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
 }
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
index 24cb2d7..0b591c8 100644
--- a/builtin-diff-tree.c
+++ b/builtin-diff-tree.c
@@ -118,7 +118,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
 	}
 
 	if (!read_stdin)
-		return 0;
+		return opt->diffopt.exit_with_status ?
+		    opt->diffopt.has_changes: 0;
 
 	if (opt->diffopt.detect_rename)
 		opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
@@ -133,5 +134,5 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
 		else
 			diff_tree_stdin(line);
 	}
-	return 0;
+	return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0;
 }
diff --git a/builtin-diff.c b/builtin-diff.c
index 4efbb82..21d13f0 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -190,6 +190,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	const char *path = NULL;
 	struct blobinfo blob[2];
 	int nongit = 0;
+	int result = 0;
 
 	/*
 	 * We could get N tree-ish in the rev.pending_objects list.
@@ -292,17 +293,17 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	if (!ents) {
 		switch (blobs) {
 		case 0:
-			return run_diff_files_cmd(&rev, argc, argv);
+			result = run_diff_files_cmd(&rev, argc, argv);
 			break;
 		case 1:
 			if (paths != 1)
 				usage(builtin_diff_usage);
-			return builtin_diff_b_f(&rev, argc, argv, blob, path);
+			result = builtin_diff_b_f(&rev, argc, argv, blob, path);
 			break;
 		case 2:
 			if (paths)
 				usage(builtin_diff_usage);
-			return builtin_diff_blobs(&rev, argc, argv, blob);
+			result = builtin_diff_blobs(&rev, argc, argv, blob);
 			break;
 		default:
 			usage(builtin_diff_usage);
@@ -311,19 +312,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	else if (blobs)
 		usage(builtin_diff_usage);
 	else if (ents == 1)
-		return builtin_diff_index(&rev, argc, argv);
+		result = builtin_diff_index(&rev, argc, argv);
 	else if (ents == 2)
-		return builtin_diff_tree(&rev, argc, argv, ent);
+		result = builtin_diff_tree(&rev, argc, argv, ent);
 	else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
 		/* diff A...B where there is one sane merge base between
 		 * A and B.  We have ent[0] == merge-base, ent[1] == A,
 		 * and ent[2] == B.  Show diff between the base and B.
 		 */
 		ent[1] = ent[2];
-		return builtin_diff_tree(&rev, argc, argv, ent);
+		result = builtin_diff_tree(&rev, argc, argv, ent);
 	}
 	else
-		return builtin_diff_combined(&rev, argc, argv,
+		result = builtin_diff_combined(&rev, argc, argv,
 					     ent, ents);
-	usage(builtin_diff_usage);
+	if (rev.diffopt.exit_with_status)
+		result = rev.diffopt.has_changes;
+	return result;
 }
diff --git a/diff-lib.c b/diff-lib.c
index 6abb981..f9a1a10 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -170,8 +170,10 @@ static int handle_diff_files_args(struct rev_info *revs,
 		else if (!strcmp(argv[1], "--theirs"))
 			revs->max_count = 3;
 		else if (!strcmp(argv[1], "-n") ||
-				!strcmp(argv[1], "--no-index"))
+				!strcmp(argv[1], "--no-index")) {
 			revs->max_count = -2;
+			revs->diffopt.exit_with_status = 1;
+		}
 		else if (!strcmp(argv[1], "-q"))
 			*silent = 1;
 		else
@@ -237,6 +239,7 @@ int setup_diff_no_index(struct rev_info *revs,
 			break;
 		} else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) {
 			i = argc - 3;
+			revs->diffopt.exit_with_status = 1;
 			break;
 		}
 	if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) &&
diff --git a/diff.c b/diff.c
index 954ca83..cc81801 100644
--- a/diff.c
+++ b/diff.c
@@ -2134,6 +2134,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 		options->color_diff = options->color_diff_words = 1;
 	else if (!strcmp(arg, "--no-renames"))
 		options->detect_rename = 0;
+	else if (!strcmp(arg, "--exit-code"))
+		options->exit_with_status = 1;
 	else
 		return 0;
 	return 1;
@@ -2910,6 +2912,8 @@ void diffcore_std(struct diff_options *options)
 		diffcore_order(options->orderfile);
 	diff_resolve_rename_copy();
 	diffcore_apply_filter(options->filter);
+	if (options->exit_with_status)
+		options->has_changes = !!diff_queued_diff.nr;
 }
 
 
@@ -2920,6 +2924,8 @@ void diffcore_std_no_resolve(struct diff_options *options)
 	if (options->orderfile)
 		diffcore_order(options->orderfile);
 	diffcore_apply_filter(options->filter);
+	if (options->exit_with_status)
+		options->has_changes = !!diff_queued_diff.nr;
 }
 
 void diff_addremove(struct diff_options *options,
diff --git a/diff.h b/diff.h
index 4b435e8..d13fc89 100644
--- a/diff.h
+++ b/diff.h
@@ -56,7 +56,8 @@ struct diff_options {
 		 silent_on_remove:1,
 		 find_copies_harder:1,
 		 color_diff:1,
-		 color_diff_words:1;
+		 color_diff_words:1,
+		 exit_with_status:1;
 	int context;
 	int break_opt;
 	int detect_rename;
@@ -71,6 +72,8 @@ struct diff_options {
 	const char *msg_sep;
 	const char *stat_sep;
 	long xdl_opts;
+	/* 0 - no differences; only meaningful if exit_with_status set */
+	int has_changes;
 
 	int stat_width;
 	int stat_name_width;
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
new file mode 100755
index 0000000..6873190
--- /dev/null
+++ b/t/t4017-diff-retval.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='Return value of diffs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo 1 >a &&
+	git add . &&
+	git commit -m first &&
+	echo 2 >b &&
+	git add . &&
+	git commit -a -m second
+'
+
+test_expect_success 'git diff-tree HEAD^ HEAD' '
+	git diff-tree --exit-code HEAD^ HEAD
+	test $? = 1
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
+	git diff-tree --exit-code HEAD^ HEAD -- a
+	test $? = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
+	git diff-tree --exit-code HEAD^ HEAD -- b
+	test $? = 1
+'
+test_expect_success 'echo HEAD | git diff-tree --stdin' '
+	echo $(git rev-parse HEAD) | git diff-tree --exit-code --stdin
+	test $? = 1
+'
+test_expect_success 'git diff-tree HEAD HEAD' '
+	git diff-tree --exit-code HEAD HEAD
+	test $? = 0
+'
+test_expect_success 'git diff-files' '
+	git diff-files --exit-code
+	test $? = 0
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git diff-index --exit-code --cached HEAD
+	test $? = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	git diff-index --exit-code --cached HEAD^
+	test $? = 1
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	echo text >>b &&
+	echo 3 >c &&
+	git add . && {
+		git diff-index --exit-code --cached HEAD^
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
+	git commit -m "text in b" && {
+		git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
+	git diff-tree -p --exit-code -Snot-found HEAD^ HEAD -- b
+	test $? = 0
+'
+test_expect_success 'git diff-files' '
+	echo 3 >>c && {
+		git diff-files --exit-code
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git update-index c && {
+		git diff-index --exit-code --cached HEAD
+		test $? = 1
+	}
+'
+
+test_done
-- 
1.5.0.3.471.g4cc25

