Skip to content

Commit 23cbcb5

Browse files
committed
built-in add -p: adjust hunk headers as needed
When skipping a hunk that adds a different number of lines than it removes, we need to adjust the subsequent hunk headers of non-skipped hunks: in pathological cases, the context is not enough to determine precisely where the patch should be applied. This problem was identified in 23fea4c (t3701: add failing test for pathological context lines, 2018-03-01) and fixed in the Perl version in fecc6f3 (add -p: adjust offsets of subsequent hunks when one is skipped, 2018-03-01). And this patch fixes it in the C version of `git add -p`. In contrast to the Perl version, we try to keep the extra text on the hunk header (which typically contains the signature of the function whose code is changed in the hunk) intact. Note: while the C version does not support staging mode changes at this stage, we already prepare for this by simply skipping the hunk header if both old and new offset is 0 (this cannot happen for regular hunks, and we will use this as an indicator that we are looking at a special hunk). Likewise, we already prepare for hunk splitting by handling the absence of extra text in the hunk header gracefully: only the first split hunk will have that text, the others will not (indicated by an empty extra text start/end range). Preparing for hunk splitting already at this stage avoids an indentation change of the entire hunk header-printing block later, and is almost as easy to review as without that handling. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent e79b65b commit 23cbcb5

File tree

3 files changed

+148
-21
lines changed

3 files changed

+148
-21
lines changed

add-interactive.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@
1212
#include "argv-array.h"
1313
#include "run-command.h"
1414

15-
struct add_i_state {
16-
struct repository *r;
17-
int use_color;
18-
char header_color[COLOR_MAXLEN];
19-
char help_color[COLOR_MAXLEN];
20-
char prompt_color[COLOR_MAXLEN];
21-
char error_color[COLOR_MAXLEN];
22-
char reset_color[COLOR_MAXLEN];
23-
};
24-
2515
static void init_color(struct repository *r, struct add_i_state *s,
2616
const char *slot_name, char *dst,
2717
const char *default_color)
@@ -38,7 +28,7 @@ static void init_color(struct repository *r, struct add_i_state *s,
3828
free(key);
3929
}
4030

41-
static int init_add_i_state(struct repository *r, struct add_i_state *s)
31+
int init_add_i_state(struct repository *r, struct add_i_state *s)
4232
{
4333
const char *value;
4434

@@ -57,6 +47,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
5747
init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
5848
init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
5949

50+
strlcpy(s->fraginfo_color,
51+
diff_get_color(s->use_color, DIFF_FRAGINFO), COLOR_MAXLEN);
52+
6053
return 0;
6154
}
6255

add-interactive.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
#ifndef ADD_INTERACTIVE_H
22
#define ADD_INTERACTIVE_H
33

4+
#include "color.h"
5+
6+
struct add_i_state {
7+
struct repository *r;
8+
int use_color;
9+
char header_color[COLOR_MAXLEN];
10+
char help_color[COLOR_MAXLEN];
11+
char prompt_color[COLOR_MAXLEN];
12+
char error_color[COLOR_MAXLEN];
13+
char reset_color[COLOR_MAXLEN];
14+
char fraginfo_color[COLOR_MAXLEN];
15+
};
16+
17+
int init_add_i_state(struct repository *r, struct add_i_state *s);
18+
419
struct repository;
520
struct pathspec;
621
int run_add_i(struct repository *r, const struct pathspec *ps);

add-patch.c

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,26 @@
55
#include "argv-array.h"
66
#include "pathspec.h"
77
#include "color.h"
8+
#include "diff.h"
9+
10+
struct hunk_header {
11+
unsigned long old_offset, old_count, new_offset, new_count;
12+
/*
13+
* Start/end offsets to the extra text after the second `@@` in the
14+
* hunk header, e.g. the function signature. This is expected to
15+
* include the newline.
16+
*/
17+
size_t extra_start, extra_end, colored_extra_start, colored_extra_end;
18+
};
819

920
struct hunk {
1021
size_t start, end, colored_start, colored_end;
1122
enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
23+
struct hunk_header header;
1224
};
1325

1426
struct add_p_state {
15-
struct repository *r;
27+
struct add_i_state s;
1628
struct strbuf answer, buf;
1729

1830
/* parsed diff */
@@ -35,7 +47,70 @@ static void setup_child_process(struct child_process *cp,
3547

3648
cp->git_cmd = 1;
3749
argv_array_pushf(&cp->env_array,
38-
INDEX_ENVIRONMENT "=%s", s->r->index_file);
50+
INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
51+
}
52+
53+
static int parse_range(const char **p,
54+
unsigned long *offset, unsigned long *count)
55+
{
56+
char *pend;
57+
58+
*offset = strtoul(*p, &pend, 10);
59+
if (pend == *p)
60+
return -1;
61+
if (*pend != ',') {
62+
*count = 1;
63+
*p = pend;
64+
return 0;
65+
}
66+
*count = strtoul(pend + 1, (char **)p, 10);
67+
return *p == pend + 1 ? -1 : 0;
68+
}
69+
70+
static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
71+
{
72+
struct hunk_header *header = &hunk->header;
73+
const char *line = s->plain.buf + hunk->start, *p = line;
74+
char *eol = memchr(p, '\n', s->plain.len - hunk->start);
75+
76+
if (!eol)
77+
eol = s->plain.buf + s->plain.len;
78+
79+
if (!skip_prefix(p, "@@ -", &p) ||
80+
parse_range(&p, &header->old_offset, &header->old_count) < 0 ||
81+
!skip_prefix(p, " +", &p) ||
82+
parse_range(&p, &header->new_offset, &header->new_count) < 0 ||
83+
!skip_prefix(p, " @@", &p))
84+
return error(_("could not parse hunk header '%.*s'"),
85+
(int)(eol - line), line);
86+
87+
hunk->start = eol - s->plain.buf + (*eol == '\n');
88+
header->extra_start = p - s->plain.buf;
89+
header->extra_end = hunk->start;
90+
91+
if (!s->colored.len) {
92+
header->colored_extra_start = header->colored_extra_end = 0;
93+
return 0;
94+
}
95+
96+
/* Now find the extra text in the colored diff */
97+
line = s->colored.buf + hunk->colored_start;
98+
eol = memchr(line, '\n', s->colored.len - hunk->colored_start);
99+
if (!eol)
100+
eol = s->colored.buf + s->colored.len;
101+
p = memmem(line, eol - line, "@@ -", 4);
102+
if (!p)
103+
return error(_("could not parse colored hunk header '%.*s'"),
104+
(int)(eol - line), line);
105+
p = memmem(p + 4, eol - p - 4, " @@", 3);
106+
if (!p)
107+
return error(_("could not parse colored hunk header '%.*s'"),
108+
(int)(eol - line), line);
109+
hunk->colored_start = eol - s->colored.buf + (*eol == '\n');
110+
header->colored_extra_start = p + 3 - s->colored.buf;
111+
header->colored_extra_end = hunk->colored_start;
112+
113+
return 0;
39114
}
40115

41116
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
@@ -109,6 +184,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
109184
hunk->start = p - plain->buf;
110185
if (colored)
111186
hunk->colored_start = colored_p - colored->buf;
187+
188+
if (parse_hunk_header(s, hunk) < 0)
189+
return -1;
112190
}
113191

114192
p = eol == pend ? pend : eol + 1;
@@ -130,8 +208,40 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
130208
}
131209

132210
static void render_hunk(struct add_p_state *s, struct hunk *hunk,
133-
int colored, struct strbuf *out)
211+
ssize_t delta, int colored, struct strbuf *out)
134212
{
213+
struct hunk_header *header = &hunk->header;
214+
215+
if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) {
216+
/*
217+
* Generate the hunk header dynamically, except for special
218+
* hunks (such as the diff header).
219+
*/
220+
const char *p;
221+
size_t len;
222+
223+
if (!colored) {
224+
p = s->plain.buf + header->extra_start;
225+
len = header->extra_end - header->extra_start;
226+
} else {
227+
strbuf_addstr(out, s->s.fraginfo_color);
228+
p = s->colored.buf + header->colored_extra_start;
229+
len = header->colored_extra_end
230+
- header->colored_extra_start;
231+
}
232+
233+
strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
234+
header->old_offset, header->old_count,
235+
(unsigned long)(header->new_offset + delta),
236+
header->new_count);
237+
if (len)
238+
strbuf_add(out, p, len);
239+
else if (colored)
240+
strbuf_addf(out, "%s\n", GIT_COLOR_RESET);
241+
else
242+
strbuf_addch(out, '\n');
243+
}
244+
135245
if (colored)
136246
strbuf_add(out, s->colored.buf + hunk->colored_start,
137247
hunk->colored_end - hunk->colored_start);
@@ -144,13 +254,17 @@ static void reassemble_patch(struct add_p_state *s, struct strbuf *out)
144254
{
145255
struct hunk *hunk;
146256
size_t i;
257+
ssize_t delta = 0;
147258

148-
render_hunk(s, &s->head, 0, out);
259+
render_hunk(s, &s->head, 0, 0, out);
149260

150261
for (i = 0; i < s->hunk_nr; i++) {
151262
hunk = s->hunk + i;
152-
if (hunk->use == USE_HUNK)
153-
render_hunk(s, hunk, 0, out);
263+
if (hunk->use != USE_HUNK)
264+
delta += hunk->header.old_count
265+
- hunk->header.new_count;
266+
else
267+
render_hunk(s, hunk, delta, 0, out);
154268
}
155269
}
156270

@@ -178,7 +292,7 @@ static int patch_update_file(struct add_p_state *s)
178292
return 0;
179293

180294
strbuf_reset(&s->buf);
181-
render_hunk(s, &s->head, colored, &s->buf);
295+
render_hunk(s, &s->head, 0, colored, &s->buf);
182296
fputs(s->buf.buf, stdout);
183297
for (;;) {
184298
if (hunk_index >= s->hunk_nr)
@@ -205,7 +319,7 @@ static int patch_update_file(struct add_p_state *s)
205319
break;
206320

207321
strbuf_reset(&s->buf);
208-
render_hunk(s, hunk, colored, &s->buf);
322+
render_hunk(s, hunk, 0, colored, &s->buf);
209323
fputs(s->buf.buf, stdout);
210324

211325
strbuf_reset(&s->buf);
@@ -276,7 +390,7 @@ static int patch_update_file(struct add_p_state *s)
276390
if (pipe_command(&cp, s->buf.buf, s->buf.len,
277391
NULL, 0, NULL, 0))
278392
error(_("'git apply --cached' failed"));
279-
repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0);
393+
repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0);
280394
}
281395

282396
putchar('\n');
@@ -285,7 +399,12 @@ static int patch_update_file(struct add_p_state *s)
285399

286400
int run_add_p(struct repository *r, const struct pathspec *ps)
287401
{
288-
struct add_p_state s = { r, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
402+
struct add_p_state s = {
403+
{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
404+
};
405+
406+
if (init_add_i_state(r, &s.s))
407+
return error("Could not read `add -i` config");
289408

290409
if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
291410
parse_diff(&s, ps) < 0) {

0 commit comments

Comments
 (0)