Skip to content

Commit 21be306

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 6b873ab commit 21be306

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
};
@@ -128,6 +128,14 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
128128
return 0;
129129
}
130130

131+
static int is_octal(const char *p, size_t len)
132+
{
133+
while (len--)
134+
if (*p < '0' || *(p++) > '7')
135+
return 0;
136+
return 1;
137+
}
138+
131139
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
132140
{
133141
struct argv_array args = ARGV_ARRAY_INIT;
@@ -180,7 +188,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
180188
pend = p + plain->len;
181189
while (p != pend) {
182190
char *eol = memchr(p, '\n', pend - p);
183-
const char *deleted = NULL;
191+
const char *deleted = NULL, *mode_change = NULL;
184192

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

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

@@ -232,6 +262,13 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
232262

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

237274
return 0;
@@ -280,16 +317,49 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
280317
hunk->end - hunk->start);
281318
}
282319

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

290-
render_hunk(s, &file_diff->head, 0, 0, out);
360+
render_diff_header(s, file_diff, 0, out);
291361

292-
for (i = 0; i < file_diff->hunk_nr; i++) {
362+
for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
293363
hunk = file_diff->hunk + i;
294364
if (hunk->use != USE_HUNK)
295365
delta += hunk->header.old_count
@@ -324,7 +394,7 @@ static int patch_update_file(struct add_p_state *s,
324394
return 0;
325395

326396
strbuf_reset(&s->buf);
327-
render_hunk(s, &file_diff->head, 0, colored, &s->buf);
397+
render_diff_header(s, file_diff, colored, &s->buf);
328398
fputs(s->buf.buf, stdout);
329399
for (;;) {
330400
if (hunk_index >= file_diff->hunk_nr)

0 commit comments

Comments
 (0)