Merge branch 'en/combined-all-paths'
authorJunio C Hamano <gitster@pobox.com>
Thu, 7 Mar 2019 00:59:54 +0000 (09:59 +0900)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Mar 2019 00:59:54 +0000 (09:59 +0900)
Output from "diff --cc" did not show the original paths when the
merge involved renames. A new option adds the paths in the
original trees to the output.

* en/combined-all-paths:
log,diff-tree: add --combined-all-paths option

Documentation/diff-format.txt
Documentation/diff-generate-patch.txt
Documentation/git-diff-tree.txt
Documentation/rev-list-options.txt
builtin/diff-tree.c
combine-diff.c
diff.h
revision.c
revision.h
t/t4038-diff-combined.sh
index cdcc17f0ad574b435bac76c0876a3b344e2839e0..4d846d73463c520046e8fa22d5da375733fa3fe9 100644 (file)
@@ -95,12 +95,26 @@ from the format described above in the following way:
 . there are more "src" modes and "src" sha1
 . status is concatenated status characters for each parent
 . no optional "score" number
-. single path, only for "dst"
+. tab-separated pathname(s) of the file
 
-Example:
+For `-c` and `--cc`, only the destination or final path is shown even
+if the file was renamed on any side of history.  With
+`--combined-all-paths`, the name of the path in each parent is shown
+followed by the name of the path in the merge commit.
+
+Examples for `-c` and `--cc` without `--combined-all-paths`:
+------------------------------------------------
+::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      desc.c
+::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM      bar.sh
+::100644 100644 100644 e07d6c5 9042e82 ee91881 RR      phooey.c
+------------------------------------------------
+
+Examples when `--combined-all-paths` added to either `-c` or `--cc`:
 
 ------------------------------------------------
-::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      describe.c
+::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      desc.c  desc.c  desc.c
+::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM      foo.sh  bar.sh  bar.sh
+::100644 100644 100644 e07d6c5 9042e82 ee91881 RR      fooey.c fuey.c  phooey.c
 ------------------------------------------------
 
 Note that 'combined diff' lists only files which were modified from
index 231105cff48d9c109b2001623c67d51690c595ba..f10ca410ad8a937e1acc62864c6b33b1db965ac4 100644 (file)
@@ -143,6 +143,19 @@ copying detection) are designed to work with diff of two
 Similar to two-line header for traditional 'unified' diff
 format, `/dev/null` is used to signal created or deleted
 files.
++
+However, if the --combined-all-paths option is provided, instead of a
+two-line from-file/to-file you get a N+1 line from-file/to-file header,
+where N is the number of parents in the merge commit
+
+       --- a/file
+       --- a/file
+       --- a/file
+       +++ b/file
++
+This extended format can be useful if rename or copy detection is
+active, to allow you to see the original name of the file in different
+parents.
 
 4.   Chunk header format is modified to prevent people from
      accidentally feeding it to `patch -p1`. Combined diff format
index 43daa7c046f472ac856d89292ede96f5e5dcb64e..24f32e8c544a455829b25193ac6b646504ab5f34 100644 (file)
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
-             [-t] [-r] [-c | --cc] [--root] [<common diff options>]
-             <tree-ish> [<tree-ish>] [<path>...]
+             [-t] [-r] [-c | --cc] [--combined-all-paths] [--root]
+             [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
 
 DESCRIPTION
 -----------
@@ -105,6 +105,13 @@ include::pretty-options.txt[]
        itself and the commit log message is not shown, just like in any other
        "empty diff" case.
 
+--combined-all-paths::
+       This flag causes combined diffs (used for merge commits) to
+       list the name of the file from all parents.  It thus only has
+       effect when -c or --cc are specified, and is likely only
+       useful if filename changes are detected (i.e. when either
+       rename or copy detection have been requested).
+
 --always::
        Show the commit itself and the commit log message even
        if the diff itself is empty.
index cad711ce0ac060d9356e49599ffc13d130fc593c..ca959a72862048c205d67576fcd6c538f0c91b26 100644 (file)
@@ -960,6 +960,13 @@ options may be given. See linkgit:git-diff-files[1] for more options.
        the parents have only two variants and the merge result picks
        one of them without modification.
 
+--combined-all-paths::
+       This flag causes combined diffs (used for merge commits) to
+       list the name of the file from all parents.  It thus only has
+       effect when -c or --cc are specified, and is likely only
+       useful if filename changes are detected (i.e. when either
+       rename or copy detection have been requested).
+
 -m::
        This flag makes the merge commits show the full diff like
        regular commits; for each merge parent, a separate log entry
index a90681bcbafcba04af3b669f0758c78ccecdd11f..cb9ea793675c850fb4bf9ab87a8e1b62f1695b02 100644 (file)
@@ -83,9 +83,13 @@ static int diff_tree_stdin(char *line)
 }
 
 static const char diff_tree_usage[] =
-"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
 "[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n"
 "  -r            diff recursively\n"
+"  -c            show combined diff for merge commits\n"
+"  --cc          show combined diff for merge commits removing uninteresting hunks\n"
+"  --combined-all-paths\n"
+"                show name of file in all parents for combined diffs\n"
 "  --root        include the initial commit as diff against /dev/null\n"
 COMMON_DIFF_OPTIONS_HELP;
 
index 23d8fabe75d9d80b2811acfc20f62d210ec6bba8..3e49f3bda84b929f5ac6846dccc51df8de902b89 100644 (file)
@@ -23,11 +23,20 @@ static int compare_paths(const struct combine_diff_path *one,
                                 two->path, strlen(two->path), two->mode);
 }
 
-static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
+static int filename_changed(char status)
+{
+       return status == 'R' || status == 'C';
+}
+
+static struct combine_diff_path *intersect_paths(
+       struct combine_diff_path *curr,
+       int n,
+       int num_parent,
+       int combined_all_paths)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        struct combine_diff_path *p, **tail = &curr;
-       int i, cmp;
+       int i, j, cmp;
 
        if (!n) {
                for (i = 0; i < q->nr; i++) {
@@ -50,6 +59,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                        oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
                        p->parent[n].mode = q->queue[i]->one->mode;
                        p->parent[n].status = q->queue[i]->status;
+
+                       if (combined_all_paths &&
+                           filename_changed(p->parent[n].status)) {
+                               strbuf_init(&p->parent[n].path, 0);
+                               strbuf_addstr(&p->parent[n].path,
+                                             q->queue[i]->one->path);
+                       }
                        *tail = p;
                        tail = &p->next;
                }
@@ -68,6 +84,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                if (cmp < 0) {
                        /* p->path not in q->queue[]; drop it */
                        *tail = p->next;
+                       for (j = 0; j < num_parent; j++)
+                               if (combined_all_paths &&
+                                   filename_changed(p->parent[j].status))
+                                       strbuf_release(&p->parent[j].path);
                        free(p);
                        continue;
                }
@@ -81,6 +101,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
+               if (combined_all_paths &&
+                   filename_changed(p->parent[n].status))
+                       strbuf_addstr(&p->parent[n].path,
+                                     q->queue[i]->one->path);
 
                tail = &p->next;
                i++;
@@ -960,12 +984,25 @@ static void show_combined_header(struct combine_diff_path *elem,
        if (!show_file_header)
                return;
 
-       if (added)
-               dump_quoted_path("--- ", "", "/dev/null",
-                                line_prefix, c_meta, c_reset);
-       else
-               dump_quoted_path("--- ", a_prefix, elem->path,
-                                line_prefix, c_meta, c_reset);
+       if (rev->combined_all_paths) {
+               for (i = 0; i < num_parent; i++) {
+                       char *path = filename_changed(elem->parent[i].status)
+                               ? elem->parent[i].path.buf : elem->path;
+                       if (elem->parent[i].status == DIFF_STATUS_ADDED)
+                               dump_quoted_path("--- ", "", "/dev/null",
+                                                line_prefix, c_meta, c_reset);
+                       else
+                               dump_quoted_path("--- ", a_prefix, path,
+                                                line_prefix, c_meta, c_reset);
+               }
+       } else {
+               if (added)
+                       dump_quoted_path("--- ", "", "/dev/null",
+                                        line_prefix, c_meta, c_reset);
+               else
+                       dump_quoted_path("--- ", a_prefix, elem->path,
+                                        line_prefix, c_meta, c_reset);
+       }
        if (deleted)
                dump_quoted_path("+++ ", "", "/dev/null",
                                 line_prefix, c_meta, c_reset);
@@ -1227,6 +1264,15 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
                putchar(inter_name_termination);
        }
 
+       for (i = 0; i < num_parent; i++)
+               if (rev->combined_all_paths) {
+                       if (filename_changed(p->parent[i].status))
+                               write_name_quoted(p->parent[i].path.buf, stdout,
+                                                 inter_name_termination);
+                       else
+                               write_name_quoted(p->path, stdout,
+                                                 inter_name_termination);
+               }
        write_name_quoted(p->path, stdout, line_termination);
 }
 
@@ -1332,7 +1378,9 @@ static const char *path_path(void *obj)
 
 /* find set of paths that every parent touches */
 static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
-       const struct oid_array *parents, struct diff_options *opt)
+       const struct oid_array *parents,
+       struct diff_options *opt,
+       int combined_all_paths)
 {
        struct combine_diff_path *paths = NULL;
        int i, num_parent = parents->nr;
@@ -1357,7 +1405,8 @@ static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
                        opt->output_format = DIFF_FORMAT_NO_OUTPUT;
                diff_tree_oid(&parents->oid[i], oid, "", opt);
                diffcore_std(opt);
-               paths = intersect_paths(paths, i, num_parent);
+               paths = intersect_paths(paths, i, num_parent,
+                                       combined_all_paths);
 
                /* if showing diff, show it in requested order */
                if (opt->output_format != DIFF_FORMAT_NO_OUTPUT &&
@@ -1467,7 +1516,8 @@ void diff_tree_combined(const struct object_id *oid,
                 * diff(sha1,parent_i) for all i to do the job, specifically
                 * for parent0.
                 */
-               paths = find_paths_generic(oid, parents, &diffopts);
+               paths = find_paths_generic(oid, parents, &diffopts,
+                                          rev->combined_all_paths);
        }
        else {
                int stat_opt;
@@ -1540,6 +1590,10 @@ void diff_tree_combined(const struct object_id *oid,
        while (paths) {
                struct combine_diff_path *tmp = paths;
                paths = paths->next;
+               for (i = 0; i < num_parent; i++)
+                       if (rev->combined_all_paths &&
+                           filename_changed(tmp->parent[i].status))
+                               strbuf_release(&tmp->parent[i].path);
                free(tmp);
        }
 
diff --git a/diff.h b/diff.h
index d9ad73f0e171e325240ff67f3351245547ca1858..3199ba31f8d697d0a73f64cde12433d8f1244181 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -296,6 +296,7 @@ struct combine_diff_path {
                char status;
                unsigned int mode;
                struct object_id oid;
+               struct strbuf path;
        } parent[FLEX_ARRAY];
 };
 #define combine_diff_path_size(n, l) \
index 162d511d4686bc43f2032a57fa95e3601eb3d6b2..eb8e51bc6302b65dfc5a921d59b2bcacd6cc19d9 100644 (file)
@@ -2151,6 +2151,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->diff = 1;
                revs->dense_combined_merges = 0;
                revs->combine_merges = 1;
+       } else if (!strcmp(arg, "--combined-all-paths")) {
+               revs->diff = 1;
+               revs->combined_all_paths = 1;
        } else if (!strcmp(arg, "--cc")) {
                revs->diff = 1;
                revs->dense_combined_merges = 1;
@@ -2647,6 +2650,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
+       if (revs->combined_all_paths && !revs->combine_merges)
+               die("--combined-all-paths makes no sense without -c or --cc");
+
        revs->diffopt.abbrev = revs->abbrev;
 
        if (revs->line_level_traverse) {
index d32d62abc6a7e65ba04a9f25c5024d6f5bbb5f49..4134dc6029c40f39659b39927ba239aed845673b 100644 (file)
@@ -172,6 +172,7 @@ struct rev_info {
                        verbose_header:1,
                        ignore_merges:1,
                        combine_merges:1,
+                       combined_all_paths:1,
                        dense_combined_merges:1,
                        always_show_header:1;
 
index e2824d343783438f9a3ae78884df7c8b8c63670d..07b49f6d6d9ef93476af2af10fe694d2f971b59d 100755 (executable)
@@ -435,4 +435,92 @@ test_expect_success 'combine diff gets tree sorting right' '
        test_cmp expect actual
 '
 
+test_expect_success 'setup for --combined-all-paths' '
+       git branch side1c &&
+       git branch side2c &&
+       git checkout side1c &&
+       test_seq 1 10 >filename-side1c &&
+       git add filename-side1c &&
+       git commit -m with &&
+       git checkout side2c &&
+       test_seq 1 9 >filename-side2c &&
+       echo ten >>filename-side2c &&
+       git add filename-side2c &&
+       git commit -m iam &&
+       git checkout -b mergery side1c &&
+       git merge --no-commit side2c &&
+       git rm filename-side1c &&
+       echo eleven >>filename-side2c &&
+       git mv filename-side2c filename-merged &&
+       git add filename-merged &&
+       git commit
+'
+
+test_expect_success '--combined-all-paths and --raw' '
+       cat <<-\EOF >expect &&
+       ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR    filename-side1c filename-side2c filename-merged
+       EOF
+       git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
+       sed 1d <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--combined-all-paths and --cc' '
+       cat <<-\EOF >expect &&
+       --- a/filename-side1c
+       --- a/filename-side2c
+       +++ b/filename-merged
+       EOF
+       git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
+       grep ^[-+][-+][-+] <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' '
+       git branch side1d &&
+       git branch side2d &&
+       git checkout side1d &&
+       test_seq 1 10 >$(printf "file\twith\ttabs") &&
+       git add file* &&
+       git commit -m with &&
+       git checkout side2d &&
+       test_seq 1 9 >$(printf "i\tam\ttabbed") &&
+       echo ten >>$(printf "i\tam\ttabbed") &&
+       git add *tabbed &&
+       git commit -m iam &&
+       git checkout -b funny-names-mergery side1d &&
+       git merge --no-commit side2d &&
+       git rm *tabs &&
+       echo eleven >>$(printf "i\tam\ttabbed") &&
+       git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" &&
+       git add fickle* &&
+       git commit
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' '
+       cat <<-\EOF >expect &&
+       ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR    "file\twith\ttabs"      "i\tam\ttabbed" "fickle\tnaming"
+       EOF
+       git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
+       sed 1d <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' '
+       printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect &&
+       git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual &&
+       test_cmp -a expect actual
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --cc and funny names' '
+       cat <<-\EOF >expect &&
+       --- "a/file\twith\ttabs"
+       --- "a/i\tam\ttabbed"
+       +++ "b/fickle\tnaming"
+       EOF
+       git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
+       grep ^[-+][-+][-+] <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
 test_done