Skip to content

Commit d76ce4f

Browse files
newrengitster
authored andcommitted
log,diff-tree: add --combined-all-paths option
The combined diff format for merges will only list one filename, even if rename or copy detection is active. For example, with raw format one might see: ::100644 100644 100644 fabadb8 cc95eb0 4866510 MM describe.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh ::100644 100644 100644 e07d6c5 9042e82 ee91881 RR phooey.c This doesn't let us know what the original name of bar.sh was in the first parent, and doesn't let us know what either of the original names of phooey.c were in either of the parents. In contrast, for non-merge commits, raw format does provide original filenames (and a rename score to boot). In order to also provide original filenames for merge commits, add a --combined-all-paths option (which must be used with either -c or --cc, and is likely only useful with rename or copy detection active) so that we can print tab-separated filenames when renames are involved. This transforms the above output to: ::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 Further, in patch format, this changes the from/to headers so that instead of just having one "from" header, we get one for each parent. For example, instead of having --- a/phooey.c +++ b/phooey.c we would see --- a/fooey.c --- a/fuey.c +++ b/phooey.c Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b5101f9 commit d76ce4f

File tree

10 files changed

+212
-17
lines changed

10 files changed

+212
-17
lines changed

Documentation/diff-format.txt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,26 @@ from the format described above in the following way:
9595
. there are more "src" modes and "src" sha1
9696
. status is concatenated status characters for each parent
9797
. no optional "score" number
98-
. single path, only for "dst"
98+
. tab-separated pathname(s) of the file
9999

100-
Example:
100+
For `-c` and `--cc`, only the destination or final path is shown even
101+
if the file was renamed on any side of history. With
102+
`--combined-all-paths`, the name of the path in each parent is shown
103+
followed by the name of the path in the merge commit.
104+
105+
Examples for `-c` and `--cc` without `--combined-all-paths`:
106+
------------------------------------------------
107+
::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c
108+
::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh
109+
::100644 100644 100644 e07d6c5 9042e82 ee91881 RR phooey.c
110+
------------------------------------------------
111+
112+
Examples when `--combined-all-paths` added to either `-c` or `--cc`:
101113

102114
------------------------------------------------
103-
::100644 100644 100644 fabadb8 cc95eb0 4866510 MM describe.c
115+
::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c desc.c desc.c
116+
::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM foo.sh bar.sh bar.sh
117+
::100644 100644 100644 e07d6c5 9042e82 ee91881 RR fooey.c fuey.c phooey.c
104118
------------------------------------------------
105119

106120
Note that 'combined diff' lists only files which were modified from

Documentation/diff-generate-patch.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ copying detection) are designed to work with diff of two
143143
Similar to two-line header for traditional 'unified' diff
144144
format, `/dev/null` is used to signal created or deleted
145145
files.
146+
+
147+
However, if the --combined-all-paths option is provided, instead of a
148+
two-line from-file/to-file you get a N+1 line from-file/to-file header,
149+
where N is the number of parents in the merge commit
150+
151+
--- a/file
152+
--- a/file
153+
--- a/file
154+
+++ b/file
155+
+
156+
This extended format can be useful if rename or copy detection is
157+
active, to allow you to see the original name of the file in different
158+
parents.
146159

147160
4. Chunk header format is modified to prevent people from
148161
accidentally feeding it to `patch -p1`. Combined diff format

Documentation/git-diff-tree.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
13-
[-t] [-r] [-c | --cc] [--root] [<common diff options>]
14-
<tree-ish> [<tree-ish>] [<path>...]
13+
[-t] [-r] [-c | --cc] [--combined-all-paths] [--root]
14+
[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
1515

1616
DESCRIPTION
1717
-----------
@@ -108,6 +108,13 @@ include::pretty-options.txt[]
108108
itself and the commit log message is not shown, just like in any other
109109
"empty diff" case.
110110

111+
--combined-all-paths::
112+
This flag causes combined diffs (used for merge commits) to
113+
list the name of the file from all parents. It thus only has
114+
effect when -c or --cc are specified, and is likely only
115+
useful if filename changes are detected (i.e. when either
116+
rename or copy detection have been requested).
117+
111118
--always::
112119
Show the commit itself and the commit log message even
113120
if the diff itself is empty.

Documentation/rev-list-options.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,13 @@ options may be given. See linkgit:git-diff-files[1] for more options.
948948
the parents have only two variants and the merge result picks
949949
one of them without modification.
950950

951+
--combined-all-paths::
952+
This flag causes combined diffs (used for merge commits) to
953+
list the name of the file from all parents. It thus only has
954+
effect when -c or --cc are specified, and is likely only
955+
useful if filename changes are detected (i.e. when either
956+
rename or copy detection have been requested).
957+
951958
-m::
952959
This flag makes the merge commits show the full diff like
953960
regular commits; for each merge parent, a separate log entry

builtin/diff-tree.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,13 @@ static int diff_tree_stdin(char *line)
8282
}
8383

8484
static const char diff_tree_usage[] =
85-
"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
85+
"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
8686
"[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n"
8787
" -r diff recursively\n"
88+
" -c show combined diff for merge commits\n"
89+
" --cc show combined diff for merge commits removing uninteresting hunks\n"
90+
" --combined-all-paths\n"
91+
" show name of file in all parents for combined diffs\n"
8892
" --root include the initial commit as diff against /dev/null\n"
8993
COMMON_DIFF_OPTIONS_HELP;
9094

combine-diff.c

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,20 @@ static int compare_paths(const struct combine_diff_path *one,
2323
two->path, strlen(two->path), two->mode);
2424
}
2525

26-
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
26+
static int filename_changed(char status)
27+
{
28+
return status == 'R' || status == 'C';
29+
}
30+
31+
static struct combine_diff_path *intersect_paths(
32+
struct combine_diff_path *curr,
33+
int n,
34+
int num_parent,
35+
int combined_all_paths)
2736
{
2837
struct diff_queue_struct *q = &diff_queued_diff;
2938
struct combine_diff_path *p, **tail = &curr;
30-
int i, cmp;
39+
int i, j, cmp;
3140

3241
if (!n) {
3342
for (i = 0; i < q->nr; i++) {
@@ -50,6 +59,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
5059
oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
5160
p->parent[n].mode = q->queue[i]->one->mode;
5261
p->parent[n].status = q->queue[i]->status;
62+
63+
if (combined_all_paths &&
64+
filename_changed(p->parent[n].status)) {
65+
strbuf_init(&p->parent[n].path, 0);
66+
strbuf_addstr(&p->parent[n].path,
67+
q->queue[i]->one->path);
68+
}
5369
*tail = p;
5470
tail = &p->next;
5571
}
@@ -68,6 +84,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
6884
if (cmp < 0) {
6985
/* p->path not in q->queue[]; drop it */
7086
*tail = p->next;
87+
for (j = 0; j < num_parent; j++)
88+
if (combined_all_paths &&
89+
filename_changed(p->parent[j].status))
90+
strbuf_release(&p->parent[j].path);
7191
free(p);
7292
continue;
7393
}
@@ -81,6 +101,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
81101
oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
82102
p->parent[n].mode = q->queue[i]->one->mode;
83103
p->parent[n].status = q->queue[i]->status;
104+
if (combined_all_paths &&
105+
filename_changed(p->parent[n].status))
106+
strbuf_addstr(&p->parent[n].path,
107+
q->queue[i]->one->path);
84108

85109
tail = &p->next;
86110
i++;
@@ -960,12 +984,25 @@ static void show_combined_header(struct combine_diff_path *elem,
960984
if (!show_file_header)
961985
return;
962986

963-
if (added)
964-
dump_quoted_path("--- ", "", "/dev/null",
965-
line_prefix, c_meta, c_reset);
966-
else
967-
dump_quoted_path("--- ", a_prefix, elem->path,
968-
line_prefix, c_meta, c_reset);
987+
if (rev->combined_all_paths) {
988+
for (i = 0; i < num_parent; i++) {
989+
char *path = filename_changed(elem->parent[i].status)
990+
? elem->parent[i].path.buf : elem->path;
991+
if (elem->parent[i].status == DIFF_STATUS_ADDED)
992+
dump_quoted_path("--- ", "", "/dev/null",
993+
line_prefix, c_meta, c_reset);
994+
else
995+
dump_quoted_path("--- ", a_prefix, path,
996+
line_prefix, c_meta, c_reset);
997+
}
998+
} else {
999+
if (added)
1000+
dump_quoted_path("--- ", "", "/dev/null",
1001+
line_prefix, c_meta, c_reset);
1002+
else
1003+
dump_quoted_path("--- ", a_prefix, elem->path,
1004+
line_prefix, c_meta, c_reset);
1005+
}
9691006
if (deleted)
9701007
dump_quoted_path("+++ ", "", "/dev/null",
9711008
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
12271264
putchar(inter_name_termination);
12281265
}
12291266

1267+
for (i = 0; i < num_parent; i++)
1268+
if (rev->combined_all_paths) {
1269+
if (filename_changed(p->parent[i].status))
1270+
write_name_quoted(p->parent[i].path.buf, stdout,
1271+
inter_name_termination);
1272+
else
1273+
write_name_quoted(p->path, stdout,
1274+
inter_name_termination);
1275+
}
12301276
write_name_quoted(p->path, stdout, line_termination);
12311277
}
12321278

@@ -1324,7 +1370,9 @@ static const char *path_path(void *obj)
13241370

13251371
/* find set of paths that every parent touches */
13261372
static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
1327-
const struct oid_array *parents, struct diff_options *opt)
1373+
const struct oid_array *parents,
1374+
struct diff_options *opt,
1375+
int combined_all_paths)
13281376
{
13291377
struct combine_diff_path *paths = NULL;
13301378
int i, num_parent = parents->nr;
@@ -1350,7 +1398,8 @@ static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
13501398
opt->output_format = DIFF_FORMAT_NO_OUTPUT;
13511399
diff_tree_oid(&parents->oid[i], oid, "", opt);
13521400
diffcore_std(opt);
1353-
paths = intersect_paths(paths, i, num_parent);
1401+
paths = intersect_paths(paths, i, num_parent,
1402+
combined_all_paths);
13541403

13551404
/* if showing diff, show it in requested order */
13561405
if (opt->output_format != DIFF_FORMAT_NO_OUTPUT &&
@@ -1460,7 +1509,8 @@ void diff_tree_combined(const struct object_id *oid,
14601509
* diff(sha1,parent_i) for all i to do the job, specifically
14611510
* for parent0.
14621511
*/
1463-
paths = find_paths_generic(oid, parents, &diffopts);
1512+
paths = find_paths_generic(oid, parents, &diffopts,
1513+
rev->combined_all_paths);
14641514
}
14651515
else {
14661516
int stat_opt;
@@ -1535,6 +1585,10 @@ void diff_tree_combined(const struct object_id *oid,
15351585
while (paths) {
15361586
struct combine_diff_path *tmp = paths;
15371587
paths = paths->next;
1588+
for (i = 0; i < num_parent; i++)
1589+
if (rev->combined_all_paths &&
1590+
filename_changed(tmp->parent[i].status))
1591+
strbuf_release(&tmp->parent[i].path);
15381592
free(tmp);
15391593
}
15401594

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ struct combine_diff_path {
294294
char status;
295295
unsigned int mode;
296296
struct object_id oid;
297+
struct strbuf path;
297298
} parent[FLEX_ARRAY];
298299
};
299300
#define combine_diff_path_size(n, l) \

revision.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
19951995
revs->diff = 1;
19961996
revs->dense_combined_merges = 0;
19971997
revs->combine_merges = 1;
1998+
} else if (!strcmp(arg, "--combined-all-paths")) {
1999+
revs->diff = 1;
2000+
revs->combined_all_paths = 1;
19982001
} else if (!strcmp(arg, "--cc")) {
19992002
revs->diff = 1;
20002003
revs->dense_combined_merges = 1;
@@ -2491,6 +2494,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
24912494
}
24922495
if (revs->combine_merges)
24932496
revs->ignore_merges = 0;
2497+
if (revs->combined_all_paths && !revs->combine_merges)
2498+
die("--combined-all-paths makes no sense without -c or --cc");
2499+
24942500
revs->diffopt.abbrev = revs->abbrev;
24952501

24962502
if (revs->line_level_traverse) {

revision.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ struct rev_info {
171171
verbose_header:1,
172172
ignore_merges:1,
173173
combine_merges:1,
174+
combined_all_paths:1,
174175
dense_combined_merges:1,
175176
always_show_header:1;
176177

t/t4038-diff-combined.sh

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,92 @@ test_expect_success 'combine diff gets tree sorting right' '
435435
test_cmp expect actual
436436
'
437437

438+
test_expect_success 'setup for --combined-all-paths' '
439+
git branch side1c &&
440+
git branch side2c &&
441+
git checkout side1c &&
442+
test_seq 1 10 >filename-side1c &&
443+
git add filename-side1c &&
444+
git commit -m with &&
445+
git checkout side2c &&
446+
test_seq 1 9 >filename-side2c &&
447+
echo ten >>filename-side2c &&
448+
git add filename-side2c &&
449+
git commit -m iam &&
450+
git checkout -b mergery side1c &&
451+
git merge --no-commit side2c &&
452+
git rm filename-side1c &&
453+
echo eleven >>filename-side2c &&
454+
git mv filename-side2c filename-merged &&
455+
git add filename-merged &&
456+
git commit
457+
'
458+
459+
test_expect_success '--combined-all-paths and --raw' '
460+
cat <<-\EOF >expect &&
461+
::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR filename-side1c filename-side2c filename-merged
462+
EOF
463+
git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
464+
sed 1d <actual.tmp >actual &&
465+
test_cmp expect actual
466+
'
467+
468+
test_expect_success '--combined-all-paths and --cc' '
469+
cat <<-\EOF >expect &&
470+
--- a/filename-side1c
471+
--- a/filename-side2c
472+
+++ b/filename-merged
473+
EOF
474+
git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
475+
grep ^[-+][-+][-+] <actual.tmp >actual &&
476+
test_cmp expect actual
477+
'
478+
479+
test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' '
480+
git branch side1d &&
481+
git branch side2d &&
482+
git checkout side1d &&
483+
test_seq 1 10 >$(printf "file\twith\ttabs") &&
484+
git add file* &&
485+
git commit -m with &&
486+
git checkout side2d &&
487+
test_seq 1 9 >$(printf "i\tam\ttabbed") &&
488+
echo ten >>$(printf "i\tam\ttabbed") &&
489+
git add *tabbed &&
490+
git commit -m iam &&
491+
git checkout -b funny-names-mergery side1d &&
492+
git merge --no-commit side2d &&
493+
git rm *tabs &&
494+
echo eleven >>$(printf "i\tam\ttabbed") &&
495+
git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" &&
496+
git add fickle* &&
497+
git commit
498+
'
499+
500+
test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' '
501+
cat <<-\EOF >expect &&
502+
::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR "file\twith\ttabs" "i\tam\ttabbed" "fickle\tnaming"
503+
EOF
504+
git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
505+
sed 1d <actual.tmp >actual &&
506+
test_cmp expect actual
507+
'
508+
509+
test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' '
510+
printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect &&
511+
git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual &&
512+
test_cmp -a expect actual
513+
'
514+
515+
test_expect_success FUNNYNAMES '--combined-all-paths and --cc and funny names' '
516+
cat <<-\EOF >expect &&
517+
--- "a/file\twith\ttabs"
518+
--- "a/i\tam\ttabbed"
519+
+++ "b/fickle\tnaming"
520+
EOF
521+
git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
522+
grep ^[-+][-+][-+] <actual.tmp >actual &&
523+
test_cmp expect actual
524+
'
525+
438526
test_done

0 commit comments

Comments
 (0)