Commits

Anonymous committed d54fe39 Merge

Merge branch 'ar/diff'

* ar/diff:
Add tests for --quiet option of diff programs
try-to-simplify-commit: use diff-tree --quiet machinery.
revision.c: explain what tree_difference does
Teach --quiet to diff backends.
diff --quiet
Remove unused diffcore_std_no_resolve
Allow git-diff exit with codes similar to diff(1)

Comments (0)

Files changed (12)

Documentation/diff-options.txt

 -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].

builtin-diff-files.c

 {
 	struct rev_info rev;
 	int nongit = 0;
+	int result;
 
 	prefix = setup_git_directory_gently(&nongit);
 	init_revisions(&rev, 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;
 }

builtin-diff-index.c

 	struct rev_info rev;
 	int cached = 0;
 	int i;
+	int result;
 
 	init_revisions(&rev, prefix);
 	git_config(git_default_config); /* no "diff" UI options */
 		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;
 }

builtin-diff-tree.c

 	}
 
 	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 |
 		else
 			diff_tree_stdin(line);
 	}
-	return 0;
+	return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0;
 }
 	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.
 	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);
 	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;
 }
 		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
 			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) &&
 		struct cache_entry *ce = active_cache[i];
 		int changed;
 
+		if (revs->diffopt.quiet && revs->diffopt.has_changes)
+			break;
+
 		if (!ce_path_match(ce, revs->prune_data))
 			continue;
 
 		struct cache_entry *ce = *ac;
 		int same = (entries > 1) && ce_same_name(ce, ac[1]);
 
+		if (revs->diffopt.quiet && revs->diffopt.has_changes)
+			break;
+
 		if (!ce_path_match(ce, pathspec))
 			goto skip_entry;
 
 	if (options->abbrev <= 0 || 40 < options->abbrev)
 		options->abbrev = 40; /* full */
 
+	/*
+	 * It does not make sense to show the first hit we happened
+	 * to have found.  It does not make sense not to return with
+	 * exit code in such a case either.
+	 */
+	if (options->quiet) {
+		options->output_format = DIFF_FORMAT_NO_OUTPUT;
+		options->exit_with_status = 1;
+	}
+
+	/*
+	 * If we postprocess in diffcore, we cannot simply return
+	 * upon the first hit.  We need to run diff as usual.
+	 */
+	if (options->pickaxe || options->filter)
+		options->quiet = 0;
+
 	return 0;
 }
 
 		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 if (!strcmp(arg, "--quiet"))
+		options->quiet = 1;
 	else
 		return 0;
 	return 1;
 
 void diffcore_std(struct diff_options *options)
 {
+	if (options->quiet)
+		return;
 	if (options->break_opt != -1)
 		diffcore_break(options->break_opt);
 	if (options->detect_rename)
 		diffcore_order(options->orderfile);
 	diff_resolve_rename_copy();
 	diffcore_apply_filter(options->filter);
-}
 
-
-void diffcore_std_no_resolve(struct diff_options *options)
-{
-	if (options->pickaxe)
-		diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
-	if (options->orderfile)
-		diffcore_order(options->orderfile);
-	diffcore_apply_filter(options->filter);
+	options->has_changes = !!diff_queued_diff.nr;
 }
 
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const unsigned char *sha1,
 		fill_filespec(two, sha1, mode);
 
 	diff_queue(&diff_queued_diff, one, two);
+	options->has_changes = 1;
 }
 
 void diff_change(struct diff_options *options,
 	fill_filespec(two, new_sha1, new_mode);
 
 	diff_queue(&diff_queued_diff, one, two);
+	options->has_changes = 1;
 }
 
 void diff_unmerge(struct diff_options *options,
 		 silent_on_remove:1,
 		 find_copies_harder:1,
 		 color_diff:1,
-		 color_diff_words:1;
+		 color_diff_words:1,
+		 has_changes:1,
+		 quiet:1,
+		 exit_with_status:1;
 	int context;
 	int break_opt;
 	int detect_rename;
 
 extern void diffcore_std(struct diff_options *);
 
-extern void diffcore_std_no_resolve(struct diff_options *);
-
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
 "  -z            output diff-raw with lines terminated with NUL.\n" \
 	return 1;
 }
 
+/*
+ * The goal is to get REV_TREE_NEW as the result only if the
+ * diff consists of all '+' (and no other changes), and
+ * REV_TREE_DIFFERENT otherwise (of course if the trees are
+ * the same we want REV_TREE_SAME).  That means that once we
+ * get to REV_TREE_DIFFERENT, we do not have to look any further.
+ */
 static int tree_difference = REV_TREE_SAME;
 
 static void file_add_remove(struct diff_options *options,
 		diff = REV_TREE_NEW;
 	}
 	tree_difference = diff;
+	if (tree_difference == REV_TREE_DIFFERENT)
+		options->has_changes = 1;
 }
 
 static void file_change(struct diff_options *options,
 		 const char *base, const char *path)
 {
 	tree_difference = REV_TREE_DIFFERENT;
+	options->has_changes = 1;
 }
 
 int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
 	if (!t2)
 		return REV_TREE_DIFFERENT;
 	tree_difference = REV_TREE_SAME;
+	revs->pruning.has_changes = 0;
 	if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
 			   &revs->pruning) < 0)
 		return REV_TREE_DIFFERENT;
 	empty.buf = "";
 	empty.size = 0;
 
-	tree_difference = 0;
+	tree_difference = REV_TREE_SAME;
+	revs->pruning.has_changes = 0;
 	retval = diff_tree(&empty, &real, "", &revs->pruning);
 	free(tree);
 
-	return retval >= 0 && !tree_difference;
+	return retval >= 0 && (tree_difference == REV_TREE_SAME);
 }
 
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 	revs->ignore_merges = 1;
 	revs->simplify_history = 1;
 	revs->pruning.recursive = 1;
+	revs->pruning.quiet = 1;
 	revs->pruning.add_remove = file_add_remove;
 	revs->pruning.change = file_change;
 	revs->lifo = 1;

t/t4017-diff-retval.sh

+#!/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
+#!/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 --quiet HEAD^ HEAD >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
+	git diff-tree --quiet HEAD^ HEAD -- a >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
+	git diff-tree --quiet HEAD^ HEAD -- b >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+# this diff outputs one line: sha1 of the given head
+test_expect_success 'echo HEAD | git diff-tree --stdin' '
+	echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
+	test $? = 1 && test $(wc -l <cnt) = 1
+'
+test_expect_success 'git diff-tree HEAD HEAD' '
+	git diff-tree --quiet HEAD HEAD >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+	git diff-files --quiet >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git diff-index --quiet --cached HEAD >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	git diff-index --quiet --cached HEAD^ >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	echo text >>b &&
+	echo 3 >c &&
+	git add . && {
+		git diff-index --quiet --cached HEAD^ >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
+	git commit -m "text in b" && {
+		git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
+	git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+	echo 3 >>c && {
+		git diff-files --quiet >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git update-index c && {
+		git diff-index --quiet --cached HEAD >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+
+test_done
 	int baselen = strlen(base);
 
 	while (t1->size | t2->size) {
+		if (opt->quiet && opt->has_changes)
+			break;
 		if (opt->nr_paths && t1->size && !interesting(t1, base, baselen, opt)) {
 			update_tree_entry(t1);
 			continue;