Skip to content

Commit 0642ef9

Browse files
committed
Merge branch 'jt/diff-pairs' into seen
A post-processing filter for "diff --raw" output has been introduced. Comments? * jt/diff-pairs: builtin/diff-pairs: allow explicit diff queue flush builtin: introduce diff-pairs command diff: return diff_filepair from diff queue helpers
2 parents af2daab + 34176d0 commit 0642ef9

File tree

13 files changed

+432
-20
lines changed

13 files changed

+432
-20
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
@@ -1243,6 +1243,7 @@ BUILTIN_OBJS += builtin/describe.o
12431243
BUILTIN_OBJS += builtin/diagnose.o
12441244
BUILTIN_OBJS += builtin/diff-files.o
12451245
BUILTIN_OBJS += builtin/diff-index.o
1246+
BUILTIN_OBJS += builtin/diff-pairs.o
12461247
BUILTIN_OBJS += builtin/diff-tree.o
12471248
BUILTIN_OBJS += builtin/diff.o
12481249
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: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#include "builtin.h"
2+
#include "commit.h"
3+
#include "config.h"
4+
#include "diff.h"
5+
#include "diffcore.h"
6+
#include "gettext.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 **endp)
14+
{
15+
uint16_t ret;
16+
17+
*endp = parse_mode(mode, &ret);
18+
if (!*endp)
19+
die(_("unable to parse mode: %s"), mode);
20+
return ret;
21+
}
22+
23+
static void parse_oid_or_die(const char *p, struct object_id *oid,
24+
const char **endp, const struct git_hash_algo *algop)
25+
{
26+
if (parse_oid_hex_algop(p, oid, endp, algop) || *(*endp)++ != ' ')
27+
die(_("unable to parse object id: %s"), p);
28+
}
29+
30+
static void flush_diff_queue(struct diff_options *options)
31+
{
32+
/*
33+
* If rename detection is not requested, use rename information from the
34+
* raw diff formatted input. Setting found_follow ensures diffcore_std()
35+
* does not mess with rename information already present in queued
36+
* filepairs.
37+
*/
38+
if (!options->detect_rename)
39+
options->found_follow = 1;
40+
diffcore_std(options);
41+
diff_flush(options);
42+
}
43+
44+
int cmd_diff_pairs(int argc, const char **argv, const char *prefix,
45+
struct repository *repo)
46+
{
47+
struct strbuf path_dst = STRBUF_INIT;
48+
struct strbuf path = STRBUF_INIT;
49+
struct strbuf meta = STRBUF_INIT;
50+
struct rev_info revs;
51+
int ret;
52+
53+
const char * const usage[] = {
54+
N_("git diff-pairs -z [<diff-options>]"),
55+
NULL
56+
};
57+
struct option options[] = {
58+
OPT_END()
59+
};
60+
struct option *parseopts = add_diff_options(options, &revs.diffopt);
61+
62+
show_usage_with_options_if_asked(argc, argv, usage, parseopts);
63+
64+
repo_init_revisions(repo, &revs, prefix);
65+
repo_config(repo, git_diff_basic_config, NULL);
66+
revs.diffopt.no_free = 1;
67+
revs.disable_stdin = 1;
68+
revs.abbrev = 0;
69+
revs.diff = 1;
70+
71+
if (setup_revisions(argc, argv, &revs, NULL) > 1)
72+
usage_with_options(usage, parseopts);
73+
74+
/*
75+
* With the -z option, both command input and raw output are
76+
* NUL-delimited (this mode does not effect patch output). At present
77+
* only NUL-delimited raw diff formatted input is supported.
78+
*/
79+
if (revs.diffopt.line_termination) {
80+
error(_("working without -z is not supported"));
81+
usage_with_options(usage, parseopts);
82+
}
83+
84+
if (revs.prune_data.nr) {
85+
error(_("pathspec arguments not supported"));
86+
usage_with_options(usage, parseopts);
87+
}
88+
89+
if (revs.pending.nr || revs.max_count != -1 ||
90+
revs.min_age != (timestamp_t)-1 ||
91+
revs.max_age != (timestamp_t)-1) {
92+
error(_("revision arguments not allowed"));
93+
usage_with_options(usage, parseopts);
94+
}
95+
96+
if (!revs.diffopt.output_format)
97+
revs.diffopt.output_format = DIFF_FORMAT_PATCH;
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_getline_nul(&meta, stdin) == EOF)
107+
break;
108+
109+
p = meta.buf;
110+
if (!*p) {
111+
flush_diff_queue(&revs.diffopt);
112+
/*
113+
* When the diff queue is explicitly flushed, append a
114+
* NUL byte to separate batches of diffs.
115+
*/
116+
fputc('\0', revs.diffopt.file);
117+
fflush(revs.diffopt.file);
118+
continue;
119+
}
120+
121+
if (*p != ':')
122+
die(_("invalid raw diff input"));
123+
p++;
124+
125+
mode_a = parse_mode_or_die(p, &p);
126+
mode_b = parse_mode_or_die(p, &p);
127+
128+
if (S_ISDIR(mode_a) || S_ISDIR(mode_b))
129+
die(_("tree objects not supported"));
130+
131+
parse_oid_or_die(p, &oid_a, &p, repo->hash_algo);
132+
parse_oid_or_die(p, &oid_b, &p, repo->hash_algo);
133+
134+
status = *p++;
135+
136+
if (strbuf_getline_nul(&path, stdin) == EOF)
137+
die(_("got EOF while reading path"));
138+
139+
switch (status) {
140+
case DIFF_STATUS_ADDED:
141+
pair = diff_queue_addremove(&diff_queued_diff,
142+
&revs.diffopt, '+', mode_b,
143+
&oid_b, 1, path.buf, 0);
144+
if (pair)
145+
pair->status = status;
146+
break;
147+
148+
case DIFF_STATUS_DELETED:
149+
pair = diff_queue_addremove(&diff_queued_diff,
150+
&revs.diffopt, '-', mode_a,
151+
&oid_a, 1, path.buf, 0);
152+
if (pair)
153+
pair->status = status;
154+
break;
155+
156+
case DIFF_STATUS_TYPE_CHANGED:
157+
case DIFF_STATUS_MODIFIED:
158+
pair = diff_queue_change(&diff_queued_diff, &revs.diffopt,
159+
mode_a, mode_b, &oid_a, &oid_b,
160+
1, 1, path.buf, 0, 0);
161+
if (pair)
162+
pair->status = status;
163+
break;
164+
165+
case DIFF_STATUS_RENAMED:
166+
case DIFF_STATUS_COPIED:
167+
{
168+
struct diff_filespec *a, *b;
169+
unsigned int score;
170+
171+
if (strbuf_getline_nul(&path_dst, stdin) == 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+
flush_diff_queue(&revs.diffopt);
197+
ret = diff_result_code(&revs);
198+
199+
strbuf_release(&path_dst);
200+
strbuf_release(&path);
201+
strbuf_release(&meta);
202+
release_revisions(&revs);
203+
FREE_AND_NULL(parseopts);
204+
205+
return ret;
206+
}

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)