Skip to content

Commit f916911

Browse files
committed
built-in app -p: allow selecting a mode change as a "hunk"
This imitates the way the Perl version treats mode changes: it offers the mode change up for the user to decide, as if it was a diff hunk. In contrast to the Perl version, we make use of the fact that the mode line is the first hunk, and explicitly strip out that line from the diff header if that "hunk" was not selected to be applied, and skipping that hunk while coalescing the diff. The Perl version plays some kind of diff line lego instead. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent b7856f2 commit f916911

File tree

1 file changed

+75
-5
lines changed

1 file changed

+75
-5
lines changed

add-patch.c

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct add_p_state {
3333
struct hunk head;
3434
struct hunk *hunk;
3535
size_t hunk_nr, hunk_alloc;
36-
unsigned deleted:1;
36+
unsigned deleted:1, mode_change:1;
3737
} *file_diff;
3838
size_t file_diff_nr;
3939
};
@@ -129,6 +129,14 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
129129
return 0;
130130
}
131131

132+
static int is_octal(const char *p, size_t len)
133+
{
134+
while (len--)
135+
if (*p < '0' || *(p++) > '7')
136+
return 0;
137+
return 1;
138+
}
139+
132140
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
133141
{
134142
struct argv_array args = ARGV_ARRAY_INIT;
@@ -181,7 +189,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
181189
pend = p + plain->len;
182190
while (p != pend) {
183191
char *eol = memchr(p, '\n', pend - p);
184-
const char *deleted = NULL;
192+
const char *deleted = NULL, *mode_change = NULL;
185193

186194
if (!eol)
187195
eol = pend;
@@ -218,8 +226,30 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
218226
file_diff->deleted = 1;
219227
else if (parse_hunk_header(s, hunk) < 0)
220228
return -1;
229+
} else if (hunk == &file_diff->head &&
230+
((skip_prefix(p, "old mode ", &mode_change) ||
231+
skip_prefix(p, "new mode ", &mode_change)) &&
232+
is_octal(mode_change, eol - mode_change))) {
233+
if (!file_diff->mode_change) {
234+
if (file_diff->hunk_nr++)
235+
BUG("mode change before first hunk");
236+
ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
237+
file_diff->hunk_alloc);
238+
memset(file_diff->hunk, 0, sizeof(struct hunk));
239+
file_diff->hunk->start = p - plain->buf;
240+
if (colored_p)
241+
file_diff->hunk->colored_start =
242+
colored_p - colored->buf;
243+
file_diff->mode_change = 1;
244+
} else if (file_diff->hunk_nr != 1)
245+
BUG("mode change after first hunk?");
221246
}
222247

248+
if (file_diff->deleted && file_diff->mode_change)
249+
BUG("diff contains delete *and* a mode change?!?\n%.*s",
250+
(int)(eol - (plain->buf + file_diff->head.start)),
251+
plain->buf + file_diff->head.start);
252+
223253
p = eol == pend ? pend : eol + 1;
224254
hunk->end = p - plain->buf;
225255

@@ -233,6 +263,13 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
233263

234264
hunk->colored_end = colored_p - colored->buf;
235265
}
266+
267+
if (mode_change) {
268+
file_diff->hunk->end = hunk->end;
269+
if (colored_p)
270+
file_diff->hunk->colored_end =
271+
hunk->colored_end;
272+
}
236273
}
237274

238275
return 0;
@@ -281,16 +318,49 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
281318
hunk->end - hunk->start);
282319
}
283320

321+
static void render_diff_header(struct add_p_state *s,
322+
struct file_diff *file_diff, int colored,
323+
struct strbuf *out)
324+
{
325+
/*
326+
* If there was a mode change, the first hunk is a pseudo hunk that
327+
* corresponds to the mode line in the header. If the user did not want
328+
* to stage that "hunk", we actually have to cut it out from the header.
329+
*/
330+
int skip_mode_change =
331+
file_diff->mode_change && file_diff->hunk->use != USE_HUNK;
332+
struct hunk *head = &file_diff->head, *first = file_diff->hunk;
333+
334+
if (!skip_mode_change) {
335+
render_hunk(s, head, 0, colored, out);
336+
return;
337+
}
338+
339+
if (colored) {
340+
const char *p = s->colored.buf;
341+
342+
strbuf_add(out, p + head->colored_start,
343+
first->colored_start - head->colored_start);
344+
strbuf_add(out, p + first->colored_end,
345+
head->colored_end - first->colored_end);
346+
} else {
347+
const char *p = s->plain.buf;
348+
349+
strbuf_add(out, p + head->start, first->start - head->start);
350+
strbuf_add(out, p + first->end, head->end - first->end);
351+
}
352+
}
353+
284354
static void reassemble_patch(struct add_p_state *s,
285355
struct file_diff *file_diff, struct strbuf *out)
286356
{
287357
struct hunk *hunk;
288358
size_t i;
289359
ssize_t delta = 0;
290360

291-
render_hunk(s, &file_diff->head, 0, 0, out);
361+
render_diff_header(s, file_diff, 0, out);
292362

293-
for (i = 0; i < file_diff->hunk_nr; i++) {
363+
for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
294364
hunk = file_diff->hunk + i;
295365
if (hunk->use != USE_HUNK)
296366
delta += hunk->header.old_count
@@ -325,7 +395,7 @@ static int patch_update_file(struct add_p_state *s,
325395
return 0;
326396

327397
strbuf_reset(&s->buf);
328-
render_hunk(s, &file_diff->head, 0, colored, &s->buf);
398+
render_diff_header(s, file_diff, colored, &s->buf);
329399
fputs(s->buf.buf, stdout);
330400
for (;;) {
331401
if (hunk_index >= file_diff->hunk_nr)

0 commit comments

Comments
 (0)