Skip to content

Commit f50df87

Browse files
committed
Merge branch 'jt/diff-pairs'
A post-processing filter for "diff --raw" output has been introduced. * jt/diff-pairs: builtin/diff-pairs: allow explicit diff queue flush builtin: introduce diff-pairs command diff: add option to skip resolving diff statuses diff: return diff_filepair from diff queue helpers
2 parents 683c54c + cf15095 commit f50df87

File tree

13 files changed

+449
-21
lines changed

13 files changed

+449
-21
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
/git-diff
5656
/git-diff-files
5757
/git-diff-index
58+
/git-diff-pairs
5859
/git-diff-tree
5960
/git-difftool
6061
/git-difftool--helper

Documentation/git-diff-pairs.adoc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
git-diff-pairs(1)
2+
=================
3+
4+
NAME
5+
----
6+
git-diff-pairs - Compare the content and mode of provided blob pairs
7+
8+
SYNOPSIS
9+
--------
10+
[synopsis]
11+
git diff-pairs -z [<diff-options>]
12+
13+
DESCRIPTION
14+
-----------
15+
Show changes for file pairs provided on stdin. Input for this command must be
16+
in the NUL-terminated raw output format as generated by commands such as `git
17+
diff-tree -z -r --raw`. By default, the outputted diffs are computed and shown
18+
in the patch format when stdin closes.
19+
20+
A single NUL byte may be written to stdin between raw input lines to compute
21+
file pair diffs up to that point instead of waiting for stdin to close. A NUL
22+
byte is also written to the output to delimit between these batches of diffs.
23+
24+
Usage of this command enables the traditional diff pipeline to be broken up
25+
into separate stages where `diff-pairs` acts as the output phase. Other
26+
commands, such as `diff-tree`, may serve as a frontend to compute the raw
27+
diff format used as input.
28+
29+
Instead of computing diffs via `git diff-tree -p -M` in one step, `diff-tree`
30+
can compute the file pairs and rename information without the blob diffs. This
31+
output can be fed to `diff-pairs` to generate the underlying blob diffs as done
32+
in the following example:
33+
34+
-----------------------------
35+
git diff-tree -z -r -M $a $b |
36+
git diff-pairs -z
37+
-----------------------------
38+
39+
Computing the tree diff upfront with rename information allows patch output
40+
from `diff-pairs` to be progressively computed over the course of potentially
41+
multiple invocations.
42+
43+
Pathspecs are not currently supported by `diff-pairs`. Pathspec limiting should
44+
be performed by the upstream command generating the raw diffs used as input.
45+
46+
Tree objects are not currently supported as input and are rejected.
47+
48+
Abbreviated object IDs in the `diff-pairs` input are not supported. Outputted
49+
object IDs can be abbreviated using the `--abbrev` option.
50+
51+
OPTIONS
52+
-------
53+
54+
include::diff-options.adoc[]
55+
56+
include::diff-generate-patch.adoc[]
57+
58+
GIT
59+
---
60+
Part of the linkgit:git[1] suite

Documentation/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ manpages = {
4242
'git-diagnose.adoc' : 1,
4343
'git-diff-files.adoc' : 1,
4444
'git-diff-index.adoc' : 1,
45+
'git-diff-pairs.adoc' : 1,
4546
'git-difftool.adoc' : 1,
4647
'git-diff-tree.adoc' : 1,
4748
'git-diff.adoc' : 1,

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ BUILTIN_OBJS += builtin/describe.o
12421242
BUILTIN_OBJS += builtin/diagnose.o
12431243
BUILTIN_OBJS += builtin/diff-files.o
12441244
BUILTIN_OBJS += builtin/diff-index.o
1245+
BUILTIN_OBJS += builtin/diff-pairs.o
12451246
BUILTIN_OBJS += builtin/diff-tree.o
12461247
BUILTIN_OBJS += builtin/diff.o
12471248
BUILTIN_OBJS += builtin/difftool.o

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix, struct reposit
153153
int cmd_diff_files(int argc, const char **argv, const char *prefix, struct repository *repo);
154154
int cmd_diff_index(int argc, const char **argv, const char *prefix, struct repository *repo);
155155
int cmd_diff(int argc, const char **argv, const char *prefix, struct repository *repo);
156+
int cmd_diff_pairs(int argc, const char **argv, const char *prefix, struct repository *repo);
156157
int cmd_diff_tree(int argc, const char **argv, const char *prefix, struct repository *repo);
157158
int cmd_difftool(int argc, const char **argv, const char *prefix, struct repository *repo);
158159
int cmd_env__helper(int argc, const char **argv, const char *prefix, struct repository *repo);

builtin/diff-pairs.c

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#include "builtin.h"
2+
#include "config.h"
3+
#include "diff.h"
4+
#include "diffcore.h"
5+
#include "gettext.h"
6+
#include "hash.h"
7+
#include "hex.h"
8+
#include "object.h"
9+
#include "parse-options.h"
10+
#include "revision.h"
11+
#include "strbuf.h"
12+
13+
static unsigned parse_mode_or_die(const char *mode, const char **end)
14+
{
15+
uint16_t ret;
16+
17+
*end = parse_mode(mode, &ret);
18+
if (!*end)
19+
die(_("unable to parse mode: %s"), mode);
20+
return ret;
21+
}
22+
23+
static void parse_oid_or_die(const char *hex, struct object_id *oid,
24+
const char **end, const struct git_hash_algo *algop)
25+
{
26+
if (parse_oid_hex_algop(hex, oid, end, algop) || *(*end)++ != ' ')
27+
die(_("unable to parse object id: %s"), hex);
28+
}
29+
30+
int cmd_diff_pairs(int argc, const char **argv, const char *prefix,
31+
struct repository *repo)
32+
{
33+
struct strbuf path_dst = STRBUF_INIT;
34+
struct strbuf path = STRBUF_INIT;
35+
struct strbuf meta = STRBUF_INIT;
36+
struct option *parseopts;
37+
struct rev_info revs;
38+
int line_term = '\0';
39+
int ret;
40+
41+
const char * const builtin_diff_pairs_usage[] = {
42+
N_("git diff-pairs -z [<diff-options>]"),
43+
NULL
44+
};
45+
struct option builtin_diff_pairs_options[] = {
46+
OPT_END()
47+
};
48+
49+
repo_init_revisions(repo, &revs, prefix);
50+
51+
/*
52+
* Diff options are usually parsed implicitly as part of
53+
* setup_revisions(). Explicitly handle parsing to ensure options are
54+
* printed in the usage message.
55+
*/
56+
parseopts = add_diff_options(builtin_diff_pairs_options, &revs.diffopt);
57+
show_usage_with_options_if_asked(argc, argv, builtin_diff_pairs_usage, parseopts);
58+
59+
repo_config(repo, git_diff_basic_config, NULL);
60+
revs.diffopt.no_free = 1;
61+
revs.disable_stdin = 1;
62+
revs.abbrev = 0;
63+
revs.diff = 1;
64+
65+
argc = parse_options(argc, argv, prefix, parseopts, builtin_diff_pairs_usage,
66+
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_DASHDASH);
67+
68+
if (setup_revisions(argc, argv, &revs, NULL) > 1)
69+
usagef(_("unrecognized argument: %s"), argv[0]);
70+
71+
/*
72+
* With the -z option, both command input and raw output are
73+
* NUL-delimited (this mode does not affect patch output). At present
74+
* only NUL-delimited raw diff formatted input is supported.
75+
*/
76+
if (revs.diffopt.line_termination)
77+
usage(_("working without -z is not supported"));
78+
79+
if (revs.prune_data.nr)
80+
usage(_("pathspec arguments not supported"));
81+
82+
if (revs.pending.nr || revs.max_count != -1 ||
83+
revs.min_age != (timestamp_t)-1 ||
84+
revs.max_age != (timestamp_t)-1)
85+
usage(_("revision arguments not allowed"));
86+
87+
if (!revs.diffopt.output_format)
88+
revs.diffopt.output_format = DIFF_FORMAT_PATCH;
89+
90+
/*
91+
* If rename detection is not requested, use rename information from the
92+
* raw diff formatted input. Setting skip_resolving_statuses ensures
93+
* diffcore_std() does not mess with rename information already present
94+
* in queued filepairs.
95+
*/
96+
if (!revs.diffopt.detect_rename)
97+
revs.diffopt.skip_resolving_statuses = 1;
98+
99+
while (1) {
100+
struct object_id oid_a, oid_b;
101+
struct diff_filepair *pair;
102+
unsigned mode_a, mode_b;
103+
const char *p;
104+
char status;
105+
106+
if (strbuf_getwholeline(&meta, stdin, line_term) == EOF)
107+
break;
108+
109+
p = meta.buf;
110+
if (!*p) {
111+
diffcore_std(&revs.diffopt);
112+
diff_flush(&revs.diffopt);
113+
/*
114+
* When the diff queue is explicitly flushed, append a
115+
* NUL byte to separate batches of diffs.
116+
*/
117+
fputc('\0', revs.diffopt.file);
118+
fflush(revs.diffopt.file);
119+
continue;
120+
}
121+
122+
if (*p != ':')
123+
die(_("invalid raw diff input"));
124+
p++;
125+
126+
mode_a = parse_mode_or_die(p, &p);
127+
mode_b = parse_mode_or_die(p, &p);
128+
129+
if (S_ISDIR(mode_a) || S_ISDIR(mode_b))
130+
die(_("tree objects not supported"));
131+
132+
parse_oid_or_die(p, &oid_a, &p, repo->hash_algo);
133+
parse_oid_or_die(p, &oid_b, &p, repo->hash_algo);
134+
135+
status = *p++;
136+
137+
if (strbuf_getwholeline(&path, stdin, line_term) == EOF)
138+
die(_("got EOF while reading path"));
139+
140+
switch (status) {
141+
case DIFF_STATUS_ADDED:
142+
pair = diff_queue_addremove(&diff_queued_diff,
143+
&revs.diffopt, '+', mode_b,
144+
&oid_b, 1, path.buf, 0);
145+
if (pair)
146+
pair->status = status;
147+
break;
148+
149+
case DIFF_STATUS_DELETED:
150+
pair = diff_queue_addremove(&diff_queued_diff,
151+
&revs.diffopt, '-', mode_a,
152+
&oid_a, 1, path.buf, 0);
153+
if (pair)
154+
pair->status = status;
155+
break;
156+
157+
case DIFF_STATUS_TYPE_CHANGED:
158+
case DIFF_STATUS_MODIFIED:
159+
pair = diff_queue_change(&diff_queued_diff, &revs.diffopt,
160+
mode_a, mode_b, &oid_a, &oid_b,
161+
1, 1, path.buf, 0, 0);
162+
if (pair)
163+
pair->status = status;
164+
break;
165+
166+
case DIFF_STATUS_RENAMED:
167+
case DIFF_STATUS_COPIED: {
168+
struct diff_filespec *a, *b;
169+
unsigned int score;
170+
171+
if (strbuf_getwholeline(&path_dst, stdin, line_term) == EOF)
172+
die(_("got EOF while reading destination path"));
173+
174+
a = alloc_filespec(path.buf);
175+
b = alloc_filespec(path_dst.buf);
176+
fill_filespec(a, &oid_a, 1, mode_a);
177+
fill_filespec(b, &oid_b, 1, mode_b);
178+
179+
pair = diff_queue(&diff_queued_diff, a, b);
180+
181+
if (strtoul_ui(p, 10, &score))
182+
die(_("unable to parse rename/copy score: %s"), p);
183+
184+
pair->score = score * MAX_SCORE / 100;
185+
pair->status = status;
186+
pair->renamed_pair = 1;
187+
}
188+
break;
189+
190+
default:
191+
die(_("unknown diff status: %c"), status);
192+
}
193+
}
194+
195+
revs.diffopt.no_free = 0;
196+
diffcore_std(&revs.diffopt);
197+
diff_flush(&revs.diffopt);
198+
ret = diff_result_code(&revs);
199+
200+
strbuf_release(&path_dst);
201+
strbuf_release(&path);
202+
strbuf_release(&meta);
203+
release_revisions(&revs);
204+
FREE_AND_NULL(parseopts);
205+
206+
return ret;
207+
}

command-list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ git-diagnose ancillaryinterrogators
9696
git-diff mainporcelain info
9797
git-diff-files plumbinginterrogators
9898
git-diff-index plumbinginterrogators
99+
git-diff-pairs plumbinginterrogators
99100
git-diff-tree plumbinginterrogators
100101
git-difftool ancillaryinterrogators complete
101102
git-fast-export ancillarymanipulators

0 commit comments

Comments
 (0)