Skip to content

Commit ec067ed

Browse files
committed
WIP range-diff: support reading mbox files
TODO: - document - add a test (probably by using `git format-patch --stdout`) Internally, the `git range-diff` command spawns a `git log` process and parses its output for the given commit ranges. This works well when the patches that need to be compared are present in the local repository in the form of commits. In scenarios where that is not the case, the `range-diff` command is unhelpful. For example, if one desired to compare commits in a local repository to patches that were sent to the Git mailing list, it would be necessary to apply the patches locally first, which requires finding an appropriate base commit or adjusting the patches manually so that they apply. This is a lot of effort given that `range-diff` would then go and internally re-generate the patches, anyway. Let's offer a way to read those patches from pre-prepared MBox files instead when an argument "mbox:<filename>" is passed instead of a commit range. This addresses gitgitgadget#207 Signed-off-by: Johannes Schindelin <[email protected]>
1 parent abe6bb3 commit ec067ed

File tree

2 files changed

+208
-2
lines changed

2 files changed

+208
-2
lines changed

builtin/range-diff.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
5656
diffopt.use_color = 1;
5757

5858
if (argc == 2) {
59-
if (!is_range_diff_range(argv[0]))
59+
if (!starts_with(argv[0], "mbox:") && !is_range_diff_range(argv[0]))
6060
die(_("not a commit range: '%s'"), argv[0]);
6161
strbuf_addstr(&range1, argv[0]);
6262

63-
if (!is_range_diff_range(argv[1]))
63+
if (!starts_with(argv[1], "mbox:") && !is_range_diff_range(argv[1]))
6464
die(_("not a commit range: '%s'"), argv[1]);
6565
strbuf_addstr(&range2, argv[1]);
6666
} else if (argc == 3) {

range-diff.c

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,192 @@ struct patch_util {
2626
struct object_id oid;
2727
};
2828

29+
static int read_mbox(const char *path, struct string_list *list)
30+
{
31+
struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT;
32+
struct patch_util *util = NULL;
33+
enum {
34+
MBOX_BEFORE_HEADER,
35+
MBOX_IN_HEADER,
36+
MBOX_IN_COMMIT_MESSAGE,
37+
MBOX_AFTER_TRIPLE_DASH,
38+
MBOX_IN_DIFF
39+
} state = MBOX_BEFORE_HEADER;
40+
char *line, *current_filename = NULL;
41+
int offset, len;
42+
size_t size;
43+
const char *author = NULL, *subject = NULL;
44+
45+
if (!strcmp(path, "-")) {
46+
if (strbuf_read(&contents, STDIN_FILENO, 0) < 0)
47+
return error_errno(_("could not read stdin"));
48+
} else if (strbuf_read_file(&contents, path, 0) < 0)
49+
return error_errno(_("could not read '%s'"), path);
50+
51+
line = contents.buf;
52+
size = contents.len;
53+
for (offset = 0; size > 0; offset += len, size -= len, line += len) {
54+
char *eol;
55+
const char *p;
56+
57+
eol = memchr(line, '\n', size);
58+
if (!eol)
59+
len = eol - line;
60+
else {
61+
len = eol + 1 - line;
62+
if (eol != line && eol[-1] == '\r')
63+
eol[-1] = '\0';
64+
else
65+
*eol = '\0';
66+
}
67+
68+
if (state == MBOX_BEFORE_HEADER) {
69+
if (starts_with(line, "From "))
70+
state = MBOX_IN_HEADER;
71+
else
72+
continue;
73+
}
74+
75+
if (starts_with(line, "diff --git ")) {
76+
struct patch patch = { 0 };
77+
struct strbuf root = STRBUF_INIT;
78+
int linenr = 0;
79+
int orig_len;
80+
81+
state = MBOX_IN_DIFF;
82+
strbuf_addch(&buf, '\n');
83+
if (!util) {
84+
util = xcalloc(sizeof(*util), 1);
85+
oidcpy(&util->oid, null_oid());
86+
util->matching = -1;
87+
author = subject = NULL;
88+
}
89+
if (!util->diff_offset)
90+
util->diff_offset = buf.len;
91+
line[len - 1] = '\n';
92+
orig_len = len;
93+
len = parse_git_diff_header(&root, &linenr, 0, line,
94+
len, size, &patch);
95+
if (len < 0) {
96+
error(_("could not parse git header '%.*s'"),
97+
orig_len, line);
98+
free(util);
99+
free(current_filename);
100+
string_list_clear(list, 1);
101+
strbuf_release(&buf);
102+
strbuf_release(&contents);
103+
return -1;
104+
}
105+
106+
if (patch.old_name)
107+
skip_prefix(patch.old_name, "a/",
108+
(const char **)&patch.old_name);
109+
if (patch.new_name)
110+
skip_prefix(patch.new_name, "b/",
111+
(const char **)&patch.new_name);
112+
113+
strbuf_addstr(&buf, " ## ");
114+
if (patch.is_new > 0)
115+
strbuf_addf(&buf, "%s (new)", patch.new_name);
116+
else if (patch.is_delete > 0)
117+
strbuf_addf(&buf, "%s (deleted)", patch.old_name);
118+
else if (patch.is_rename)
119+
strbuf_addf(&buf, "%s => %s", patch.old_name, patch.new_name);
120+
else
121+
strbuf_addstr(&buf, patch.new_name);
122+
123+
free(current_filename);
124+
if (patch.is_delete > 0)
125+
current_filename = xstrdup(patch.old_name);
126+
else
127+
current_filename = xstrdup(patch.new_name);
128+
129+
if (patch.new_mode && patch.old_mode &&
130+
patch.old_mode != patch.new_mode)
131+
strbuf_addf(&buf, " (mode change %06o => %06o)",
132+
patch.old_mode, patch.new_mode);
133+
134+
strbuf_addstr(&buf, " ##\n");
135+
util->diffsize++;
136+
} else if (state == MBOX_IN_HEADER) {
137+
if (!line[0]) {
138+
state = MBOX_IN_COMMIT_MESSAGE;
139+
strbuf_addstr(&buf, " ## Metadata ##\n");
140+
if (author)
141+
strbuf_addf(&buf, "Author: %s\n", author);
142+
strbuf_addstr(&buf, "\n ## Commit message ##\n");
143+
if (subject)
144+
strbuf_addf(&buf, " %s\n\n", subject);
145+
} else if (skip_prefix(line, "From: ", &p)) {
146+
author = p;
147+
} else if (skip_prefix(line, "Subject: ", &p)) {
148+
const char *q;
149+
150+
subject = p;
151+
152+
if (starts_with(p, "[PATCH") &&
153+
(q = strchr(p, ']'))) {
154+
q++;
155+
while (isspace(*q))
156+
q++;
157+
subject = q;
158+
}
159+
}
160+
} else if (state == MBOX_IN_COMMIT_MESSAGE) {
161+
if (!strcmp(line, "---"))
162+
state = MBOX_AFTER_TRIPLE_DASH;
163+
else
164+
strbuf_addf(&buf, " %s\n", line);
165+
} else if (state == MBOX_IN_DIFF) {
166+
switch (line[0]) {
167+
case '\0':
168+
continue; /* ignore empty lines after diff */
169+
case '+':
170+
case '-':
171+
case ' ':
172+
case '\\':
173+
strbuf_addstr(&buf, line);
174+
strbuf_addch(&buf, '\n');
175+
util->diffsize++;
176+
continue;
177+
case '@':
178+
if (skip_prefix(line, "@@ ", &p)) {
179+
p = strstr(p, "@@");
180+
strbuf_addstr(&buf, "@@");
181+
if (current_filename && p[2])
182+
strbuf_addf(&buf, " %s:",
183+
current_filename);
184+
if (p)
185+
strbuf_addstr(&buf, p + 2);
186+
187+
strbuf_addch(&buf, '\n');
188+
util->diffsize++;
189+
continue;
190+
}
191+
break;
192+
}
193+
194+
if (util) {
195+
string_list_append(list, buf.buf)->util = util;
196+
strbuf_reset(&buf);
197+
}
198+
util = xcalloc(sizeof(*util), 1);
199+
oidcpy(&util->oid, null_oid());
200+
util->matching = -1;
201+
author = subject = NULL;
202+
state = MBOX_BEFORE_HEADER;
203+
}
204+
}
205+
strbuf_release(&contents);
206+
207+
if (util)
208+
string_list_append(list, buf.buf)->util = util;
209+
strbuf_release(&buf);
210+
free(current_filename);
211+
212+
return 0;
213+
}
214+
29215
/*
30216
* Reads the patches into a string list, with the `util` field being populated
31217
* as struct object_id (will need to be free()d).
@@ -40,6 +226,10 @@ static int read_patches(const char *range, struct string_list *list,
40226
char *line, *current_filename = NULL;
41227
ssize_t len;
42228
size_t size;
229+
const char *path;
230+
231+
if (skip_prefix(range, "mbox:", &path))
232+
return read_mbox(path, list);
43233

44234
strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
45235
"--reverse", "--date-order", "--decorate=no",
@@ -428,6 +618,19 @@ static void output_pair_header(struct diff_options *diffopt,
428618

429619
strbuf_addch(buf, ' ');
430620
pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
621+
} else {
622+
struct patch_util *util = b_util ? b_util : a_util;
623+
const char *needle = "\n ## Commit message ##\n";
624+
const char *p = !util || !util->patch ?
625+
NULL : strstr(util->patch, needle);
626+
if (p) {
627+
if (status == '!')
628+
strbuf_addf(buf, "%s%s", color_reset, color);
629+
630+
strbuf_addch(buf, ' ');
631+
p += strlen(needle);
632+
strbuf_add(buf, p, strchrnul(p, '\n') - p);
633+
}
431634
}
432635
strbuf_addf(buf, "%s\n", color_reset);
433636

@@ -558,6 +761,9 @@ int show_range_diff(const char *range1, const char *range2,
558761
if (range_diff_opts->left_only && range_diff_opts->right_only)
559762
res = error(_("--left-only and --right-only are mutually exclusive"));
560763

764+
if (!strcmp(range1, "mbox:-") && !strcmp(range2, "mbox:-"))
765+
res = error(_("only one mbox can be read from stdin"));
766+
561767
if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg))
562768
res = error(_("could not parse log for '%s'"), range1);
563769
if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg))

0 commit comments

Comments
 (0)