From 41bbf9d58575095234c64df979ee884334469758 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 14 Mar 2007 01:17:04 +0100 Subject: [PATCH 1/7] 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 Signed-off-by: Junio C Hamano --- 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 d8696b7b36..77a3f78dd7 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 aec8338429..6ba5077a2b 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 083599d5c4..d90eba95a6 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 24cb2d7f84..0b591c8716 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 4efbb8237b..21d13f0b30 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 6abb981534..f9a1a10cc0 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 954ca83e0b..cc818011b9 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 4b435e8b19..d13fc89768 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 0000000000..68731908be --- /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 From 3161b4b52112acb6a3eb57f3bf882e8ca131e7d3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 10:36:42 -0700 Subject: [PATCH 2/7] Remove unused diffcore_std_no_resolve This was only used by diff-tree-helper program, whose purpose was to translate a raw diff to a patch. Signed-off-by: Junio C Hamano --- diff.c | 11 ----------- diff.h | 2 -- 2 files changed, 13 deletions(-) diff --git a/diff.c b/diff.c index cc818011b9..7d938c1484 100644 --- a/diff.c +++ b/diff.c @@ -2917,17 +2917,6 @@ void diffcore_std(struct diff_options *options) } -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); - if (options->exit_with_status) - options->has_changes = !!diff_queued_diff.nr; -} - void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, diff --git a/diff.h b/diff.h index d13fc89768..81fa265656 100644 --- a/diff.h +++ b/diff.h @@ -173,8 +173,6 @@ extern int diff_setup_done(struct diff_options *); 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" \ From 68aacb2f3ceef528ded945b510094918bfe3cb37 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 11:12:13 -0700 Subject: [PATCH 3/7] diff --quiet This adds the command line option 'quiet' to tell 'git diff-*' that we are not interested in the actual diff contents but only want to know if there is any change. This option automatically turns --exit-code on, and turns off output formatting, as it does not make much sense to show the first hit we happened to have found. The --quiet option is silently turned off (but --exit-code is still in effect, so is silent output) if postprocessing filters such as pickaxe and diff-filter are used. For all practical purposes I do not think of a reason to want to use these filters and not viewing the diff output. The backends have not been taught about the option with this patch. That is a topic for later rounds. Signed-off-by: Junio C Hamano --- diff.c | 27 +++++++++++++++++++++++++-- diff.h | 4 ++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 7d938c1484..d8f9242ea8 100644 --- a/diff.c +++ b/diff.c @@ -1958,6 +1958,23 @@ int diff_setup_done(struct diff_options *options) 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; } @@ -2136,6 +2153,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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; @@ -2900,6 +2919,8 @@ static void diffcore_apply_filter(const char *filter) 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) @@ -2912,8 +2933,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; + + options->has_changes = !!diff_queued_diff.nr; } @@ -2952,6 +2973,7 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); + options->has_changes = 1; } void diff_change(struct diff_options *options, @@ -2977,6 +2999,7 @@ 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, diff --git a/diff.h b/diff.h index 81fa265656..a0d2ce1399 100644 --- a/diff.h +++ b/diff.h @@ -57,6 +57,8 @@ struct diff_options { find_copies_harder:1, color_diff:1, color_diff_words:1, + has_changes:1, + quiet:1, exit_with_status:1; int context; int break_opt; @@ -72,8 +74,6 @@ 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; From 822cac015589889c1a9e6d49a2c054b7f1b838ba Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 11:12:51 -0700 Subject: [PATCH 4/7] Teach --quiet to diff backends. This teaches git-diff-files, git-diff-index and git-diff-tree backends to exit early under --quiet option. Signed-off-by: Junio C Hamano --- diff-lib.c | 6 ++++++ tree-diff.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/diff-lib.c b/diff-lib.c index f9a1a10cc0..5c5b05bfe3 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -324,6 +324,9 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) 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; @@ -565,6 +568,9 @@ static int diff_cache(struct rev_info *revs, 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; diff --git a/tree-diff.c b/tree-diff.c index c8275823d0..44cde74caf 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -161,6 +161,8 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { while (t1->size | t2->size) { + if (opt->quiet && opt->has_changes) + break; if (opt->nr_paths && t1->size && !interesting(t1, base, opt)) { update_tree_entry(t1); continue; From 0a4ba7f8c6140c516f0ee073a6b71d0db24d6242 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 13:12:18 -0700 Subject: [PATCH 5/7] revision.c: explain what tree_difference does This explains how tree_difference variable is used, and updates two places where the code knows symbolic constant REV_TREE_SAME is 0. Signed-off-by: Junio C Hamano --- revision.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/revision.c b/revision.c index 3c2eb125e6..129d1978e4 100644 --- a/revision.c +++ b/revision.c @@ -213,6 +213,13 @@ static int everybody_uninteresting(struct commit_list *orig) 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, @@ -277,11 +284,11 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) empty.buf = ""; empty.size = 0; - tree_difference = 0; + tree_difference = REV_TREE_SAME; 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) From dd47aa31339d2b8acdf909fd0067544c31cd9358 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 13:18:15 -0700 Subject: [PATCH 6/7] try-to-simplify-commit: use diff-tree --quiet machinery. This uses diff-tree --quiet machinery to terminate the internal diff-tree between a commit and its parents via revs.pruning (not revs.diffopt) as soon as we find enough about the tree change. With respect to the optionally given pathspec, we are interested if the tree of commit is identical to the parent's, only adds new paths to the parent's, or there are other differences. As soon as we find out that there is one such other kind of difference, we do not have to compare the rest of the tree. Because we do not call standard diff_addremove/diff_change, we instruct the diff-tree machinery to stop early by setting has_changes when we say we found the trees to be different. Signed-off-by: Junio C Hamano --- revision.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/revision.c b/revision.c index 129d1978e4..bcdb6a1212 100644 --- a/revision.c +++ b/revision.c @@ -243,6 +243,8 @@ 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, @@ -252,6 +254,7 @@ 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) @@ -261,6 +264,7 @@ 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; @@ -285,6 +289,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) empty.size = 0; tree_difference = REV_TREE_SAME; + revs->pruning.has_changes = 0; retval = diff_tree(&empty, &real, "", &revs->pruning); free(tree); @@ -552,6 +557,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) 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; From 0c66d6be4f888096865b8f3d5fdc00c83e4ecc3f Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 14 Mar 2007 23:57:23 +0100 Subject: [PATCH 7/7] Add tests for --quiet option of diff programs Signed-off-by: Alex Riesen --- t/t4017-quiet.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 t/t4017-quiet.sh diff --git a/t/t4017-quiet.sh b/t/t4017-quiet.sh new file mode 100755 index 0000000000..e747e84227 --- /dev/null +++ b/t/t4017-quiet.sh @@ -0,0 +1,80 @@ +#!/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 + test $? = 0 && test $(wc -l cnt + test $? = 1 && test $(wc -l cnt + test $? = 1 && test $(wc -l cnt + test $? = 0 && test $(wc -l cnt + test $? = 0 && test $(wc -l cnt + test $? = 0 && test $(wc -l cnt + test $? = 1 && test $(wc -l >b && + echo 3 >c && + git add . && { + git diff-index --quiet --cached HEAD^ >cnt + test $? = 1 && test $(wc -l cnt + test $? = 1 && test $(wc -l cnt + test $? = 0 && test $(wc -l >c && { + git diff-files --quiet >cnt + test $? = 1 && test $(wc -l cnt + test $? = 1 && test $(wc -l