Skip to content

Commit a4b649a

Browse files
committed
Merge branch 'jt/diff-pairs' into seen
* 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 5fa7835 + 4243065 commit a4b649a

File tree

13 files changed

+427
-19
lines changed

13 files changed

+427
-19
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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
git-diff-pairs(1)
2+
=================
3+
4+
NAME
5+
----
6+
git-diff-pairs - Compare blob pairs generated by `diff-tree --raw`
7+
8+
SYNOPSIS
9+
--------
10+
[verse]
11+
'git diff-pairs' [diff-options]
12+
13+
DESCRIPTION
14+
-----------
15+
16+
Given the output of `diff-tree -z` on its stdin, `diff-pairs` will
17+
reformat that output into whatever format is requested on its command
18+
line. For example:
19+
20+
-----------------------------
21+
git diff-tree -z -M $a $b |
22+
git diff-pairs -p
23+
-----------------------------
24+
25+
will compute the tree diff in one step (including renames), and then
26+
`diff-pairs` will compute and format the blob-level diffs for each pair.
27+
This can be used to modify the raw diff in the middle (without having to
28+
parse or re-create more complicated formats like `--patch`), or to
29+
compute diffs progressively over the course of multiple invocations of
30+
`diff-pairs`.
31+
32+
Each blob pair is fed to the diff machinery individually queued and the output
33+
is flushed on stdin EOF.
34+
35+
To explicitly flush the diff queue, a single nul byte can be written to stdin
36+
between filepairs. Diff output between flushes is separated by a single line
37+
terminator.
38+
39+
OPTIONS
40+
-------
41+
42+
include::diff-options.adoc[]
43+
44+
include::diff-generate-patch.adoc[]
45+
46+
NOTES
47+
----
48+
49+
`diff-pairs` should handle any input generated by `diff-tree --raw -z`.
50+
It may choke or otherwise misbehave on output from `diff-files`, etc.
51+
52+
Here's an incomplete list of things that `diff-pairs` could do, but
53+
doesn't (mostly in the name of simplicity):
54+
55+
- Only `-z` input is accepted, not normal `--raw` input.
56+
57+
- Abbreviated sha1s are rejected in the input from `diff-tree`; if you
58+
want to abbreviate the output, you can pass `--abbrev` to
59+
`diff-pairs`.
60+
61+
- Pathspecs are not handled by `diff-pairs`; you can limit the diff via
62+
the initial `diff-tree` invocation.
63+
64+
GIT
65+
---
66+
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: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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(const char *p, struct object_id *oid, const char **endp,
24+
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 unsigned short parse_score(const char *score)
31+
{
32+
unsigned long ret;
33+
char *endp;
34+
35+
errno = 0;
36+
ret = strtoul(score, &endp, 10);
37+
ret *= MAX_SCORE / 100;
38+
if (errno || endp == score || *endp || (unsigned short)ret != ret)
39+
die("unable to parse rename/copy score: %s", score);
40+
return ret;
41+
}
42+
43+
static void flush_diff_queue(struct diff_options *options)
44+
{
45+
/*
46+
* If rename detection is not requested, use rename information from the
47+
* raw diff formatted input. Setting found_follow ensures diffcore_std()
48+
* does not mess with rename information already present in queued
49+
* filepairs.
50+
*/
51+
if (!options->detect_rename)
52+
options->found_follow = 1;
53+
diffcore_std(options);
54+
diff_flush(options);
55+
}
56+
57+
int cmd_diff_pairs(int argc, const char **argv, const char *prefix,
58+
struct repository *repo)
59+
{
60+
struct strbuf path_dst = STRBUF_INIT;
61+
struct strbuf path = STRBUF_INIT;
62+
struct strbuf meta = STRBUF_INIT;
63+
struct rev_info revs;
64+
int ret;
65+
66+
const char * const usage[] = {
67+
N_("git diff-pairs [diff-options]"),
68+
NULL
69+
};
70+
struct option options[] = {
71+
OPT_END()
72+
};
73+
74+
show_usage_with_options_if_asked(argc, argv, usage, options);
75+
76+
repo_init_revisions(repo, &revs, prefix);
77+
repo_config(repo, git_diff_basic_config, NULL);
78+
revs.disable_stdin = 1;
79+
revs.abbrev = 0;
80+
revs.diff = 1;
81+
82+
argc = setup_revisions(argc, argv, &revs, NULL);
83+
84+
/* Don't allow pathspecs at all. */
85+
if (revs.prune_data.nr)
86+
usage_with_options(usage, options);
87+
88+
if (!revs.diffopt.output_format)
89+
revs.diffopt.output_format = DIFF_FORMAT_RAW;
90+
91+
while (1) {
92+
struct object_id oid_a, oid_b;
93+
struct diff_filepair *pair;
94+
unsigned mode_a, mode_b;
95+
const char *p;
96+
char status;
97+
98+
if (strbuf_getline_nul(&meta, stdin) == EOF)
99+
break;
100+
101+
p = meta.buf;
102+
if (!*p) {
103+
flush_diff_queue(&revs.diffopt);
104+
/*
105+
* When the diff queue is explicitly flushed, append an
106+
* additional terminator to separate batches of diffs.
107+
*/
108+
fprintf(revs.diffopt.file, "%c",
109+
revs.diffopt.line_termination);
110+
continue;
111+
}
112+
113+
if (*p != ':')
114+
die("invalid raw diff input");
115+
p++;
116+
117+
mode_a = parse_mode_or_die(p, &p);
118+
mode_b = parse_mode_or_die(p, &p);
119+
120+
parse_oid(p, &oid_a, &p, repo->hash_algo);
121+
parse_oid(p, &oid_b, &p, repo->hash_algo);
122+
123+
status = *p++;
124+
125+
if (strbuf_getline_nul(&path, stdin) == EOF)
126+
die("got EOF while reading path");
127+
128+
switch (status) {
129+
case DIFF_STATUS_ADDED:
130+
pair = diff_filepair_addremove(&revs.diffopt, '+',
131+
mode_b, &oid_b,
132+
1, path.buf, 0);
133+
if (pair)
134+
pair->status = status;
135+
break;
136+
137+
case DIFF_STATUS_DELETED:
138+
pair = diff_filepair_addremove(&revs.diffopt, '-',
139+
mode_a, &oid_a,
140+
1, path.buf, 0);
141+
if (pair)
142+
pair->status = status;
143+
break;
144+
145+
case DIFF_STATUS_TYPE_CHANGED:
146+
case DIFF_STATUS_MODIFIED:
147+
pair = diff_filepair_change(&revs.diffopt,
148+
mode_a, mode_b,
149+
&oid_a, &oid_b, 1, 1,
150+
path.buf, 0, 0);
151+
if (pair)
152+
pair->status = status;
153+
break;
154+
155+
case DIFF_STATUS_RENAMED:
156+
case DIFF_STATUS_COPIED:
157+
{
158+
struct diff_filespec *a, *b;
159+
160+
if (strbuf_getline_nul(&path_dst, stdin) == EOF)
161+
die("got EOF while reading destination path");
162+
163+
a = alloc_filespec(path.buf);
164+
b = alloc_filespec(path_dst.buf);
165+
fill_filespec(a, &oid_a, 1, mode_a);
166+
fill_filespec(b, &oid_b, 1, mode_b);
167+
168+
pair = diff_queue(&diff_queued_diff, a, b);
169+
pair->status = status;
170+
pair->score = parse_score(p);
171+
pair->renamed_pair = 1;
172+
}
173+
break;
174+
175+
default:
176+
die("unknown diff status: %c", status);
177+
}
178+
}
179+
180+
flush_diff_queue(&revs.diffopt);
181+
ret = diff_result_code(&revs);
182+
183+
strbuf_release(&path_dst);
184+
strbuf_release(&path);
185+
strbuf_release(&meta);
186+
release_revisions(&revs);
187+
188+
return ret;
189+
}

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)