Merge branch 'ar/diff'
authorJunio C Hamano <junkio@cox.net>
Sun, 18 Mar 2007 22:48:06 +0000 (15:48 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 18 Mar 2007 22:48:06 +0000 (15:48 -0700)
* 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)

12 files changed:
Documentation/diff-options.txt
builtin-diff-files.c
builtin-diff-index.c
builtin-diff-tree.c
builtin-diff.c
diff-lib.c
diff.c
diff.h
revision.c
t/t4017-diff-retval.sh [new file with mode: 0755]
t/t4017-quiet.sh [new file with mode: 0755]
tree-diff.c
index d8696b7b36ff81f47214da9d7c4ec53142b97653..77a3f78dd75514667b77ae4e1dbbb3731b05a33d 100644 (file)
 -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].
index aec83384298042fc4509605299f82e82745ae675..6ba5077a2be6619f110280622cf46a7f4cfb8b01 100644 (file)
@@ -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;
 }
index 083599d5c4c174cfab7c148428630534e4cd8174..d90eba95a6be17bd0486e0311c2657b1f69ab7d9 100644 (file)
@@ -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;
 }
index 24cb2d7f84064d7833fa04f33aeca6eafc8b931d..0b591c87169ff4b8c2173bedb26d6ed1a8a84b68 100644 (file)
@@ -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;
 }
index 4efbb8237bd49e8717a42833b2d9b2db064b45ac..21d13f0b30359295b8385754fccb4bb71f995dba 100644 (file)
@@ -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;
 }
index 6abb981534bf032d50e28eb3a14033c1b6546762..5c5b05bfe32bc90484b5bd6a9c171e0f9b04fbd6 100644 (file)
@@ -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) &&
@@ -321,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;
 
@@ -562,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/diff.c b/diff.c
index 954ca83e0b0c95f55c0287d2deeb4cab76153fe0..d8f9242ea8fe2ee92884623939a85b6587a3ad9d 100644 (file)
--- 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;
 }
 
@@ -2134,6 +2151,10 @@ 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 if (!strcmp(arg, "--quiet"))
+               options->quiet = 1;
        else
                return 0;
        return 1;
@@ -2898,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)
@@ -2910,18 +2933,11 @@ void diffcore_std(struct diff_options *options)
                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,
@@ -2957,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,
@@ -2982,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 4b435e8b1933222da02f9e4cf2c28d2fef44d292..a0d2ce13994c1a8751bf7b207671e95c5bc5db97 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -56,7 +56,10 @@ struct diff_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;
@@ -170,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" \
index 3c2eb125e6e9332fe32be9bcec6fb2005228c211..bcdb6a12122871e743dd3b2362ca7bd5b1b787df 100644 (file)
@@ -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,
@@ -236,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,
@@ -245,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)
@@ -254,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;
@@ -277,11 +288,12 @@ 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;
+       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)
@@ -545,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;
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
new file mode 100755 (executable)
index 0000000..6873190
--- /dev/null
@@ -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
diff --git a/t/t4017-quiet.sh b/t/t4017-quiet.sh
new file mode 100755 (executable)
index 0000000..e747e84
--- /dev/null
@@ -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) = 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
index f89b9d31e4542724ff44b6f3c3bc6ae3adcaa1a6..3940962e79a5094e61270cd951d5d37c307d11a9 100644 (file)
@@ -163,6 +163,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
        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;