@@ -28,7 +28,7 @@ struct hunk_header {
28
28
};
29
29
30
30
struct hunk {
31
- size_t start , end , colored_start , colored_end ;
31
+ size_t start , end , colored_start , colored_end , splittable_into ;
32
32
enum { UNDECIDED_HUNK = 0 , SKIP_HUNK , USE_HUNK } use ;
33
33
struct hunk_header header ;
34
34
};
@@ -155,7 +155,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
155
155
struct argv_array args = ARGV_ARRAY_INIT ;
156
156
struct strbuf * plain = & s -> plain , * colored = NULL ;
157
157
struct child_process cp = CHILD_PROCESS_INIT ;
158
- char * p , * pend , * colored_p = NULL , * colored_pend = NULL ;
158
+ char * p , * pend , * colored_p = NULL , * colored_pend = NULL , marker = '\0' ;
159
159
size_t file_diff_alloc = 0 , i , color_arg_index ;
160
160
struct file_diff * file_diff = NULL ;
161
161
struct hunk * hunk = NULL ;
@@ -217,6 +217,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
217
217
hunk -> start = p - plain -> buf ;
218
218
if (colored_p )
219
219
hunk -> colored_start = colored_p - colored -> buf ;
220
+ marker = '\0' ;
220
221
} else if (p == plain -> buf )
221
222
BUG ("diff starts with unexpected line:\n"
222
223
"%.*s\n" , (int )(eol - p ), p );
@@ -225,6 +226,13 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
225
226
else if (starts_with (p , "@@ " ) ||
226
227
(hunk == & file_diff -> head &&
227
228
skip_prefix (p , "deleted file" , & deleted ))) {
229
+ if (marker == '-' || marker == '+' )
230
+ /*
231
+ * Should not happen; previous hunk did not end
232
+ * in a context line? Handle it anyway.
233
+ */
234
+ hunk -> splittable_into ++ ;
235
+
228
236
file_diff -> hunk_nr ++ ;
229
237
ALLOC_GROW (file_diff -> hunk , file_diff -> hunk_nr ,
230
238
file_diff -> hunk_alloc );
@@ -239,6 +247,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
239
247
file_diff -> deleted = 1 ;
240
248
else if (parse_hunk_header (s , hunk ) < 0 )
241
249
return -1 ;
250
+
251
+ /*
252
+ * Start counting into how many hunks this one can be
253
+ * split
254
+ */
255
+ marker = * p ;
242
256
} else if (hunk == & file_diff -> head &&
243
257
skip_prefix (p , "old mode " , & mode_change ) &&
244
258
is_octal (mode_change , eol - mode_change )) {
@@ -286,6 +300,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
286
300
(int )(eol - (plain -> buf + file_diff -> head .start )),
287
301
plain -> buf + file_diff -> head .start );
288
302
303
+ if ((marker == '-' || marker == '+' ) && * p == ' ' )
304
+ hunk -> splittable_into ++ ;
305
+ if (marker && * p != '\\' )
306
+ marker = * p ;
307
+
289
308
p = eol == pend ? pend : eol + 1 ;
290
309
hunk -> end = p - plain -> buf ;
291
310
@@ -311,9 +330,30 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
311
330
}
312
331
}
313
332
333
+ if (marker == '-' || marker == '+' )
334
+ /*
335
+ * Last hunk ended in non-context line (i.e. it appended lines
336
+ * to the file, so there are no trailing context lines).
337
+ */
338
+ hunk -> splittable_into ++ ;
339
+
314
340
return 0 ;
315
341
}
316
342
343
+ static size_t find_next_line (struct strbuf * sb , size_t offset )
344
+ {
345
+ char * eol ;
346
+
347
+ if (offset >= sb -> len )
348
+ BUG ("looking for next line beyond buffer (%d >= %d)\n%s" ,
349
+ (int )offset , (int )sb -> len , sb -> buf );
350
+
351
+ eol = memchr (sb -> buf + offset , '\n' , sb -> len - offset );
352
+ if (!eol )
353
+ return sb -> len ;
354
+ return eol - sb -> buf + 1 ;
355
+ }
356
+
317
357
static void render_hunk (struct add_p_state * s , struct hunk * hunk ,
318
358
ssize_t delta , int colored , struct strbuf * out )
319
359
{
@@ -412,6 +452,165 @@ static void reassemble_patch(struct add_p_state *s,
412
452
}
413
453
}
414
454
455
+ static int split_hunk (struct add_p_state * s , struct file_diff * file_diff ,
456
+ size_t hunk_index )
457
+ {
458
+ int colored = !!s -> colored .len , first = 1 ;
459
+ struct hunk * hunk = file_diff -> hunk + hunk_index ;
460
+ size_t splittable_into ;
461
+ size_t end , colored_end , current , colored_current = 0 , context_line_count ;
462
+ struct hunk_header remaining , * header ;
463
+ char marker , ch ;
464
+
465
+ if (hunk_index >= file_diff -> hunk_nr )
466
+ BUG ("invalid hunk index: %d (must be >= 0 and < %d)" ,
467
+ (int )hunk_index , (int )file_diff -> hunk_nr );
468
+
469
+ if (hunk -> splittable_into < 2 )
470
+ return 0 ;
471
+ splittable_into = hunk -> splittable_into ;
472
+
473
+ end = hunk -> end ;
474
+ colored_end = hunk -> colored_end ;
475
+
476
+ remaining = hunk -> header ;
477
+
478
+ file_diff -> hunk_nr += splittable_into - 1 ;
479
+ ALLOC_GROW (file_diff -> hunk , file_diff -> hunk_nr , file_diff -> hunk_alloc );
480
+ if (hunk_index + splittable_into < file_diff -> hunk_nr )
481
+ memmove (file_diff -> hunk + hunk_index + splittable_into ,
482
+ file_diff -> hunk + hunk_index + 1 ,
483
+ (file_diff -> hunk_nr - hunk_index - splittable_into )
484
+ * sizeof (* hunk ));
485
+ hunk = file_diff -> hunk + hunk_index ;
486
+ hunk -> splittable_into = 1 ;
487
+ memset (hunk + 1 , 0 , (splittable_into - 1 ) * sizeof (* hunk ));
488
+
489
+ header = & hunk -> header ;
490
+ header -> old_count = header -> new_count = 0 ;
491
+
492
+ current = hunk -> start ;
493
+ if (colored )
494
+ colored_current = hunk -> colored_start ;
495
+ marker = '\0' ;
496
+ context_line_count = 0 ;
497
+
498
+ while (splittable_into > 1 ) {
499
+ ch = s -> plain .buf [current ];
500
+
501
+ if (!ch )
502
+ BUG ("buffer overrun while splitting hunks" );
503
+
504
+ /*
505
+ * Is this the first context line after a chain of +/- lines?
506
+ * Then record the start of the next split hunk.
507
+ */
508
+ if ((marker == '-' || marker == '+' ) && ch == ' ' ) {
509
+ first = 0 ;
510
+ hunk [1 ].start = current ;
511
+ if (colored )
512
+ hunk [1 ].colored_start = colored_current ;
513
+ context_line_count = 0 ;
514
+ }
515
+
516
+ /*
517
+ * Was the previous line a +/- one? Alternatively, is this the
518
+ * first line (and not a +/- one)?
519
+ *
520
+ * Then just increment the appropriate counter and continue
521
+ * with the next line.
522
+ */
523
+ if (marker != ' ' || (ch != '-' && ch != '+' )) {
524
+ next_hunk_line :
525
+ /* Comment lines are attached to the previous line */
526
+ if (ch == '\\' )
527
+ ch = marker ? marker : ' ' ;
528
+
529
+ /* current hunk not done yet */
530
+ if (ch == ' ' )
531
+ context_line_count ++ ;
532
+ else if (ch == '-' )
533
+ header -> old_count ++ ;
534
+ else if (ch == '+' )
535
+ header -> new_count ++ ;
536
+ else
537
+ BUG ("unhandled diff marker: '%c'" , ch );
538
+ marker = ch ;
539
+ current = find_next_line (& s -> plain , current );
540
+ if (colored )
541
+ colored_current =
542
+ find_next_line (& s -> colored ,
543
+ colored_current );
544
+ continue ;
545
+ }
546
+
547
+ /*
548
+ * We got us the start of a new hunk!
549
+ *
550
+ * This is a context line, so it is shared with the previous
551
+ * hunk, if any.
552
+ */
553
+
554
+ if (first ) {
555
+ if (header -> old_count || header -> new_count )
556
+ BUG ("counts are off: %d/%d" ,
557
+ (int )header -> old_count ,
558
+ (int )header -> new_count );
559
+
560
+ header -> old_count = context_line_count ;
561
+ header -> new_count = context_line_count ;
562
+ context_line_count = 0 ;
563
+ first = 0 ;
564
+ goto next_hunk_line ;
565
+ }
566
+
567
+ remaining .old_offset += header -> old_count ;
568
+ remaining .old_count -= header -> old_count ;
569
+ remaining .new_offset += header -> new_count ;
570
+ remaining .new_count -= header -> new_count ;
571
+
572
+ /* initialize next hunk header's offsets */
573
+ hunk [1 ].header .old_offset =
574
+ header -> old_offset + header -> old_count ;
575
+ hunk [1 ].header .new_offset =
576
+ header -> new_offset + header -> new_count ;
577
+
578
+ /* add one split hunk */
579
+ header -> old_count += context_line_count ;
580
+ header -> new_count += context_line_count ;
581
+
582
+ hunk -> end = current ;
583
+ if (colored )
584
+ hunk -> colored_end = colored_current ;
585
+
586
+ hunk ++ ;
587
+ hunk -> splittable_into = 1 ;
588
+ hunk -> use = hunk [-1 ].use ;
589
+ header = & hunk -> header ;
590
+
591
+ header -> old_count = header -> new_count = context_line_count ;
592
+ context_line_count = 0 ;
593
+
594
+ splittable_into -- ;
595
+ marker = ch ;
596
+ }
597
+
598
+ /* last hunk simply gets the rest */
599
+ if (header -> old_offset != remaining .old_offset )
600
+ BUG ("miscounted old_offset: %lu != %lu" ,
601
+ header -> old_offset , remaining .old_offset );
602
+ if (header -> new_offset != remaining .new_offset )
603
+ BUG ("miscounted new_offset: %lu != %lu" ,
604
+ header -> new_offset , remaining .new_offset );
605
+ header -> old_count = remaining .old_count ;
606
+ header -> new_count = remaining .new_count ;
607
+ hunk -> end = end ;
608
+ if (colored )
609
+ hunk -> colored_end = colored_end ;
610
+
611
+ return 0 ;
612
+ }
613
+
415
614
static const char help_patch_text [] =
416
615
N_ ("y - stage this hunk\n"
417
616
"n - do not stage this hunk\n"
@@ -421,6 +620,7 @@ N_("y - stage this hunk\n"
421
620
"J - leave this hunk undecided, see next hunk\n"
422
621
"k - leave this hunk undecided, see previous undecided hunk\n"
423
622
"K - leave this hunk undecided, see previous hunk\n"
623
+ "s - split the current hunk into smaller hunks\n"
424
624
"? - print help\n" );
425
625
426
626
static int patch_update_file (struct add_p_state * s ,
@@ -477,6 +677,8 @@ static int patch_update_file(struct add_p_state *s,
477
677
strbuf_addstr (& s -> buf , ",j" );
478
678
if (hunk_index + 1 < file_diff -> hunk_nr )
479
679
strbuf_addstr (& s -> buf , ",J" );
680
+ if (hunk -> splittable_into > 1 )
681
+ strbuf_addstr (& s -> buf , ",s" );
480
682
481
683
if (file_diff -> deleted )
482
684
prompt_mode_type = PROMPT_DELETION ;
@@ -539,6 +741,15 @@ static int patch_update_file(struct add_p_state *s,
539
741
hunk_index = undecided_next ;
540
742
else
541
743
err (s , _ ("No next hunk" ));
744
+ } else if (s -> answer .buf [0 ] == 's' ) {
745
+ size_t splittable_into = hunk -> splittable_into ;
746
+ if (splittable_into < 2 )
747
+ err (s , _ ("Sorry, cannot split this hunk" ));
748
+ else if (!split_hunk (s , file_diff ,
749
+ hunk - file_diff -> hunk ))
750
+ color_fprintf_ln (stdout , s -> s .header_color ,
751
+ _ ("Split into %d hunks." ),
752
+ (int )splittable_into );
542
753
} else
543
754
color_fprintf (stdout , s -> s .help_color ,
544
755
_ (help_patch_text ));
0 commit comments