@@ -33,7 +33,7 @@ struct add_p_state {
33
33
struct hunk head ;
34
34
struct hunk * hunk ;
35
35
size_t hunk_nr , hunk_alloc ;
36
- unsigned deleted :1 ;
36
+ unsigned deleted :1 , mode_change : 1 ;
37
37
} * file_diff ;
38
38
size_t file_diff_nr ;
39
39
};
@@ -129,6 +129,17 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
129
129
return 0 ;
130
130
}
131
131
132
+ static int is_octal (const char * p , size_t len )
133
+ {
134
+ if (!len )
135
+ return 0 ;
136
+
137
+ while (len -- )
138
+ if (* p < '0' || * (p ++ ) > '7' )
139
+ return 0 ;
140
+ return 1 ;
141
+ }
142
+
132
143
static int parse_diff (struct add_p_state * s , const struct pathspec * ps )
133
144
{
134
145
struct argv_array args = ARGV_ARRAY_INIT ;
@@ -181,7 +192,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
181
192
pend = p + plain -> len ;
182
193
while (p != pend ) {
183
194
char * eol = memchr (p , '\n' , pend - p );
184
- const char * deleted = NULL ;
195
+ const char * deleted = NULL , * mode_change = NULL ;
185
196
186
197
if (!eol )
187
198
eol = pend ;
@@ -218,8 +229,53 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
218
229
file_diff -> deleted = 1 ;
219
230
else if (parse_hunk_header (s , hunk ) < 0 )
220
231
return -1 ;
232
+ } else if (hunk == & file_diff -> head &&
233
+ skip_prefix (p , "old mode " , & mode_change ) &&
234
+ is_octal (mode_change , eol - mode_change )) {
235
+ if (file_diff -> mode_change )
236
+ BUG ("double mode change?\n\n%.*s" ,
237
+ (int )(eol - plain -> buf ), plain -> buf );
238
+ if (file_diff -> hunk_nr ++ )
239
+ BUG ("mode change in the middle?\n\n%.*s" ,
240
+ (int )(eol - plain -> buf ), plain -> buf );
241
+
242
+ /*
243
+ * Do *not* change `hunk`: the mode change pseudo-hunk
244
+ * is _part of_ the header "hunk".
245
+ */
246
+ file_diff -> mode_change = 1 ;
247
+ ALLOC_GROW (file_diff -> hunk , file_diff -> hunk_nr ,
248
+ file_diff -> hunk_alloc );
249
+ memset (file_diff -> hunk , 0 , sizeof (struct hunk ));
250
+ file_diff -> hunk -> start = p - plain -> buf ;
251
+ if (colored_p )
252
+ file_diff -> hunk -> colored_start =
253
+ colored_p - colored -> buf ;
254
+ } else if (hunk == & file_diff -> head &&
255
+ skip_prefix (p , "new mode " , & mode_change ) &&
256
+ is_octal (mode_change , eol - mode_change )) {
257
+
258
+ /*
259
+ * Extend the "mode change" pseudo-hunk to include also
260
+ * the "new mode" line.
261
+ */
262
+ if (!file_diff -> mode_change )
263
+ BUG ("'new mode' without 'old mode'?\n\n%.*s" ,
264
+ (int )(eol - plain -> buf ), plain -> buf );
265
+ if (file_diff -> hunk_nr != 1 )
266
+ BUG ("mode change in the middle?\n\n%.*s" ,
267
+ (int )(eol - plain -> buf ), plain -> buf );
268
+ if (p - plain -> buf != file_diff -> hunk -> end )
269
+ BUG ("'new mode' does not immediately follow "
270
+ "'old mode'?\n\n%.*s" ,
271
+ (int )(eol - plain -> buf ), plain -> buf );
221
272
}
222
273
274
+ if (file_diff -> deleted && file_diff -> mode_change )
275
+ BUG ("diff contains delete *and* a mode change?!?\n%.*s" ,
276
+ (int )(eol - (plain -> buf + file_diff -> head .start )),
277
+ plain -> buf + file_diff -> head .start );
278
+
223
279
p = eol == pend ? pend : eol + 1 ;
224
280
hunk -> end = p - plain -> buf ;
225
281
@@ -233,6 +289,16 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
233
289
234
290
hunk -> colored_end = colored_p - colored -> buf ;
235
291
}
292
+
293
+ if (mode_change ) {
294
+ if (file_diff -> hunk_nr != 1 )
295
+ BUG ("mode change in hunk #%d???" ,
296
+ (int )file_diff -> hunk_nr );
297
+ /* Adjust the end of the "mode change" pseudo-hunk */
298
+ file_diff -> hunk -> end = hunk -> end ;
299
+ if (colored )
300
+ file_diff -> hunk -> colored_end = hunk -> colored_end ;
301
+ }
236
302
}
237
303
238
304
return 0 ;
@@ -284,16 +350,49 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
284
350
hunk -> end - hunk -> start );
285
351
}
286
352
353
+ static void render_diff_header (struct add_p_state * s ,
354
+ struct file_diff * file_diff , int colored ,
355
+ struct strbuf * out )
356
+ {
357
+ /*
358
+ * If there was a mode change, the first hunk is a pseudo hunk that
359
+ * corresponds to the mode line in the header. If the user did not want
360
+ * to stage that "hunk", we actually have to cut it out from the header.
361
+ */
362
+ int skip_mode_change =
363
+ file_diff -> mode_change && file_diff -> hunk -> use != USE_HUNK ;
364
+ struct hunk * head = & file_diff -> head , * first = file_diff -> hunk ;
365
+
366
+ if (!skip_mode_change ) {
367
+ render_hunk (s , head , 0 , colored , out );
368
+ return ;
369
+ }
370
+
371
+ if (colored ) {
372
+ const char * p = s -> colored .buf ;
373
+
374
+ strbuf_add (out , p + head -> colored_start ,
375
+ first -> colored_start - head -> colored_start );
376
+ strbuf_add (out , p + first -> colored_end ,
377
+ head -> colored_end - first -> colored_end );
378
+ } else {
379
+ const char * p = s -> plain .buf ;
380
+
381
+ strbuf_add (out , p + head -> start , first -> start - head -> start );
382
+ strbuf_add (out , p + first -> end , head -> end - first -> end );
383
+ }
384
+ }
385
+
287
386
static void reassemble_patch (struct add_p_state * s ,
288
387
struct file_diff * file_diff , struct strbuf * out )
289
388
{
290
389
struct hunk * hunk ;
291
390
size_t i ;
292
391
ssize_t delta = 0 ;
293
392
294
- render_hunk (s , & file_diff -> head , 0 , 0 , out );
393
+ render_diff_header (s , file_diff , 0 , out );
295
394
296
- for (i = 0 ; i < file_diff -> hunk_nr ; i ++ ) {
395
+ for (i = file_diff -> mode_change ; i < file_diff -> hunk_nr ; i ++ ) {
297
396
hunk = file_diff -> hunk + i ;
298
397
if (hunk -> use != USE_HUNK )
299
398
delta += hunk -> header .old_count
@@ -328,7 +427,7 @@ static int patch_update_file(struct add_p_state *s,
328
427
return 0 ;
329
428
330
429
strbuf_reset (& s -> buf );
331
- render_hunk (s , & file_diff -> head , 0 , colored , & s -> buf );
430
+ render_diff_header (s , file_diff , colored , & s -> buf );
332
431
fputs (s -> buf .buf , stdout );
333
432
for (;;) {
334
433
if (hunk_index >= file_diff -> hunk_nr )
0 commit comments