29
30
struct hunk {
31
size_t start, end, colored_start, colored_end, splittable_into;
32
+ ssize_t delta;
33
enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
34
struct hunk_header header;
35
};
436
437
/* Coalesce hunks again that were split */
438
static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff,
438
- size_t *hunk_index, struct hunk *merged)
439
+ size_t *hunk_index, int use_all, struct hunk *merged)
440
{
440
- size_t i = *hunk_index;
441
+ size_t i = *hunk_index, delta;
442
struct hunk *hunk = file_diff->hunk + i;
443
/* `header` corresponds to the merged hunk */
444
struct hunk_header *header = &merged->header, *next;
445
445
- if (hunk->use != USE_HUNK)
446
+ if (!use_all && hunk->use != USE_HUNK)
447
return 0;
448
449
*merged = *hunk;
460
* - the hunk is not selected for use, or
461
* - the hunk does not overlap with the already-merged hunk(s)
462
*/
462
- if (hunk->use != USE_HUNK ||
463
- header->new_offset >= next->new_offset ||
464
- header->new_offset + header->new_count < next->new_offset ||
465
- merged->start >= hunk->start ||
466
- merged->end < hunk->start)
463
+ if ((!use_all && hunk->use != USE_HUNK) ||
464
+ header->new_offset >= next->new_offset + merged->delta ||
465
+ header->new_offset + header->new_count
466
+ < next->new_offset + merged->delta)
467
break;
468
469
- merged->end = hunk->end;
470
- merged->colored_end = hunk->colored_end;
469
+ /*
470
+ * If the hunks were not edited, and overlap, we can simply
471
+ * extend the line range.
472
+ */
473
+ if (merged->start < hunk->start && merged->end > hunk->start) {
474
+ merged->end = hunk->end;
475
+ merged->colored_end = hunk->colored_end;
476
+ delta = 0;
477
+ } else {
478
+ const char *plain = s->plain.buf;
479
+ size_t overlapping_line_count = header->new_offset
480
+ + header->new_count - merged->delta
481
+ - next->new_offset;
482
+ size_t overlap_end = hunk->start;
483
+ size_t overlap_start = overlap_end;
484
+ size_t overlap_next, len, j;
485
+
486
+ /*
487
+ * One of the hunks was edited: the modified hunk was
488
+ * appended to the strbuf `s->plain`.
489
+ *
490
+ * Let's ensure that at least the last context line of
491
+ * the first hunk overlaps with the corresponding line
492
+ * of the second hunk, and then merge.
493
+ */
494
+ for (j = 0; j < overlapping_line_count; j++) {
495
+ overlap_next = find_next_line(&s->plain,
496
+ overlap_end);
497
+
498
+ if (overlap_next > hunk->end)
499
+ BUG("failed to find %d context lines "
500
+ "in:\n%.*s",
501
+ (int)overlapping_line_count,
502
+ (int)(hunk->end - hunk->start),
503
+ plain + hunk->start);
504
+
505
+ if (plain[overlap_end] != ' ')
506
+ return error(_("expected context line "
507
+ "#%d in\n%.*s"),
508
+ (int)(j + 1),
509
+ (int)(hunk->end
510
+ - hunk->start),
511
+ plain + hunk->start);
512
+
513
+ overlap_start = overlap_end;
514
+ overlap_end = overlap_next;
515
+ }
516
+ len = overlap_end - overlap_start;
517
+
518
+ if (len > merged->end - merged->start ||
519
+ memcmp(plain + merged->end - len,
520
+ plain + overlap_start, len))
521
+ return error(_("hunks do not overlap:\n%.*s\n"
522
+ "\tdoes not end with:\n%.*s"),
523
+ (int)(merged->end - merged->start),
524
+ plain + merged->start,
525
+ (int)len, plain + overlap_start);
526
+
527
+ /*
528
+ * Since the start-end ranges are not adjacent, we
529
+ * cannot simply take the union of the ranges. To
530
+ * address that, we temporarily append the union of the
531
+ * lines to the `plain` strbuf.
532
+ */
533
+ if (merged->end != s->plain.len) {
534
+ size_t start = s->plain.len;
535
+
536
+ strbuf_add(&s->plain, plain + merged->start,
537
+ merged->end - merged->start);
538
+ plain = s->plain.buf;
539
+ merged->start = start;
540
+ merged->end = s->plain.len;
541
+ }
542
+
543
+ strbuf_add(&s->plain,
544
+ plain + overlap_end,
545
+ hunk->end - overlap_end);
546
+ merged->end = s->plain.len;
547
+ merged->splittable_into += hunk->splittable_into;
548
+ delta = merged->delta;
549
+ merged->delta += hunk->delta;
550
+ }
551
552
header->old_count = next->old_offset + next->old_count
553
- header->old_offset;
474
- header->new_count = next->new_offset + next->new_count
475
- - header->new_offset;
554
+ header->new_count = next->new_offset + delta
555
+ + next->new_count - header->new_offset;
556
}
557
558
if (i == *hunk_index)
563
}
564
565
static void reassemble_patch(struct add_p_state *s,
486
- struct file_diff *file_diff, struct strbuf *out)
566
+ struct file_diff *file_diff, int use_all,
567
+ struct strbuf *out)
568
{
569
struct hunk *hunk;
489
- size_t i;
570
+ size_t save_len = s->plain.len, i;
571
ssize_t delta = 0;
572
573
render_diff_header(s, file_diff, 0, out);
576
struct hunk merged = { 0 };
577
578
hunk = file_diff->hunk + i;
498
- if (hunk->use != USE_HUNK)
579
+ if (!use_all && hunk->use != USE_HUNK)
580
delta += hunk->header.old_count
581
- hunk->header.new_count;
582
else {
583
/* merge overlapping hunks into a temporary hunk */
503
- if (merge_hunks(s, file_diff, &i, &merged))
584
+ if (merge_hunks(s, file_diff, &i, use_all, &merged))
585
hunk = &merged;
586
587
render_hunk(s, hunk, delta, 0, out);
588
+
589
+ /*
590
+ * In case `merge_hunks()` used `plain` as a scratch
591
+ * pad (this happens when an edited hunk had to be
592
+ * coalesced with another hunk).
593
+ */
594
+ strbuf_setlen(&s->plain, save_len);
595
+
596
+ delta += hunk->delta;
597
}
598
}
599
}
757
return 0;
758
}
759
760
+static void recolor_hunk(struct add_p_state *s, struct hunk *hunk)
761
+{
762
+ const char *plain = s->plain.buf;
763
+ size_t current, eol, next;
764
+
765
+ if (!s->colored.len)
766
+ return;
767
+
768
+ hunk->colored_start = s->colored.len;
769
+ for (current = hunk->start; current < hunk->end; ) {
770
+ for (eol = current; eol < hunk->end; eol++)
771
+ if (plain[eol] == '\n')
772
+ break;
773
+ next = eol + (eol < hunk->end);
774
+ if (eol > current && plain[eol - 1] == '\r')
775
+ eol--;
776
+
777
+ strbuf_addstr(&s->colored,
778
+ plain[current] == '-' ?
779
+ s->s.file_old_color :
780
+ plain[current] == '+' ?
781
+ s->s.file_new_color :
782
+ s->s.context_color);
783
+ strbuf_add(&s->colored, plain + current, eol - current);
784
+ strbuf_addstr(&s->colored, GIT_COLOR_RESET);
785
+ if (next > eol)
786
+ strbuf_add(&s->colored, plain + eol, next - eol);
787
+ current = next;
788
+ }
789
+ hunk->colored_end = s->colored.len;
790
+}
791
+
792
+static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
793
+{
794
+ size_t i;
795
+
796
+ strbuf_reset(&s->buf);
797
+ strbuf_commented_addf(&s->buf, _("Manual hunk edit mode -- see bottom for "
798
+ "a quick guide.\n"));
799
+ render_hunk(s, hunk, 0, 0, &s->buf);
800
+ strbuf_commented_addf(&s->buf,
801
+ _("---\n"
802
+ "To remove '%c' lines, make them ' ' lines "
803
+ "(context).\n"
804
+ "To remove '%c' lines, delete them.\n"
805
+ "Lines starting with %c will be removed.\n"),
806
+ '-', '+', comment_line_char);
807
+ strbuf_commented_addf(&s->buf,
808
+ _("If the patch applies cleanly, the edited hunk "
809
+ "will immediately be\n"
810
+ "marked for staging.\n"));
811
+ /*
812
+ * TRANSLATORS: 'it' refers to the patch mentioned in the previous
813
+ * messages.
814
+ */
815
+ strbuf_commented_addf(&s->buf,
816
+ _("If it does not apply cleanly, you will be "
817
+ "given an opportunity to\n"
818
+ "edit again. If all lines of the hunk are "
819
+ "removed, then the edit is\n"
820
+ "aborted and the hunk is left unchanged.\n"));
821
+
822
+ if (strbuf_edit_interactively(&s->buf, "addp-hunk-edit.diff", NULL) < 0)
823
+ return -1;
824
+
825
+ /* strip out commented lines */
826
+ hunk->start = s->plain.len;
827
+ for (i = 0; i < s->buf.len; ) {
828
+ size_t next = find_next_line(&s->buf, i);
829
+
830
+ if (s->buf.buf[i] != comment_line_char)
831
+ strbuf_add(&s->plain, s->buf.buf + i, next - i);
832
+ i = next;
833
+ }
834
+
835
+ hunk->end = s->plain.len;
836
+ if (hunk->end == hunk->start)
837
+ /* The user aborted editing by deleting everything */
838
+ return 0;
839
+
840
+ recolor_hunk(s, hunk);
841
+
842
+ /*
843
+ * If the hunk header is intact, parse it, otherwise simply use the
844
+ * hunk header prior to editing (which will adjust `hunk->start` to
845
+ * skip the hunk header).
846
+ */
847
+ if (s->plain.buf[hunk->start] == '@' &&
848
+ parse_hunk_header(s, hunk) < 0)
849
+ return error(_("could not parse hunk header"));
850
+
851
+ return 1;
852
+}
853
+
854
+static ssize_t recount_edited_hunk(struct add_p_state *s, struct hunk *hunk,
855
+ size_t orig_old_count, size_t orig_new_count)
856
+{
857
+ struct hunk_header *header = &hunk->header;
858
+ size_t i;
859
+
860
+ header->old_count = header->new_count = 0;
861
+ for (i = hunk->start; i < hunk->end; ) {
862
+ switch (s->plain.buf[i]) {
863
+ case '-':
864
+ header->old_count++;
865
+ break;
866
+ case '+':
867
+ header->new_count++;
868
+ break;
869
+ case ' ': case '\r': case '\n':
870
+ header->old_count++;
871
+ header->new_count++;
872
+ break;
873
+ }
874
+
875
+ i = find_next_line(&s->plain, i);
876
+ }
877
+
878
+ return orig_old_count - orig_new_count
879
+ - header->old_count + header->new_count;
880
+}
881
+
882
+static int run_apply_check(struct add_p_state *s,
883
+ struct file_diff *file_diff)
884
+{
885
+ struct child_process cp = CHILD_PROCESS_INIT;
886
+
887
+ strbuf_reset(&s->buf);
888
+ reassemble_patch(s, file_diff, 1, &s->buf);
889
+
890
+ setup_child_process(s, &cp,
891
+ "apply", "--cached", "--check", NULL);
892
+ if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
893
+ return error(_("'git apply --cached' failed"));
894
+
895
+ return 0;
896
+}
897
+
898
+static int prompt_yesno(struct add_p_state *s, const char *prompt)
899
+{
900
+ for (;;) {
901
+ color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
902
+ fflush(stdout);
903
+ if (strbuf_getline(&s->answer, stdin) == EOF)
904
+ return -1;
905
+ strbuf_trim_trailing_newline(&s->answer);
906
+ switch (tolower(s->answer.buf[0])) {
907
+ case 'n': return 0;
908
+ case 'y': return 1;
909
+ }
910
+ }
911
+}
912
+
913
+static int edit_hunk_loop(struct add_p_state *s,
914
+ struct file_diff *file_diff, struct hunk *hunk)
915
+{
916
+ size_t plain_len = s->plain.len, colored_len = s->colored.len;
917
+ struct hunk backup;
918
+
919
+ backup = *hunk;
920
+
921
+ for (;;) {
922
+ int res = edit_hunk_manually(s, hunk);
923
+ if (res == 0) {
924
+ /* abandonded */
925
+ *hunk = backup;
926
+ return -1;
927
+ }
928
+
929
+ if (res > 0) {
930
+ hunk->delta +=
931
+ recount_edited_hunk(s, hunk,
932
+ backup.header.old_count,
933
+ backup.header.new_count);
934
+ if (!run_apply_check(s, file_diff))
935
+ return 0;
936
+ }
937
+
938
+ /* Drop edits (they were appended to s->plain) */
939
+ strbuf_setlen(&s->plain, plain_len);
940
+ strbuf_setlen(&s->colored, colored_len);
941
+ *hunk = backup;
942
+
943
+ /*
944
+ * TRANSLATORS: do not translate [y/n]
945
+ * The program will only accept that input at this point.
946
+ * Consider translating (saying "no" discards!) as
947
+ * (saying "n" for "no" discards!) if the translation
948
+ * of the word "no" does not start with n.
949
+ */
950
+ res = prompt_yesno(s, _("Your edited hunk does not apply. "
951
+ "Edit again (saying \"no\" discards!) "
952
+ "[y/n]? "));
953
+ if (res < 1)
954
+ return -1;
955
+ }
956
+}
957
+
958
static const char help_patch_text[] =
959
N_("y - stage this hunk\n"
960
"n - do not stage this hunk\n"
965
"k - leave this hunk undecided, see previous undecided hunk\n"
966
"K - leave this hunk undecided, see previous hunk\n"
967
"s - split the current hunk into smaller hunks\n"
968
+ "e - manually edit the current hunk\n"
969
"? - print help\n");
970
971
static int patch_update_file(struct add_p_state *s,
1024
strbuf_addstr(&s->buf, ",J");
1025
if (hunk->splittable_into > 1)
1026
strbuf_addstr(&s->buf, ",s");
1027
+ if (hunk_index + 1 > file_diff->mode_change &&
1028
+ !file_diff->deleted)
1029
+ strbuf_addstr(&s->buf, ",e");
1030
1031
if (file_diff->deleted)
1032
prompt_mode_type = PROMPT_DELETION;
1098
color_fprintf_ln(stdout, s->s.header_color,
1099
_("Split into %d hunks."),
1100
(int)splittable_into);
1101
+ } else if (s->answer.buf[0] == 'e') {
1102
+ if (hunk_index + 1 == file_diff->mode_change)
1103
+ err(s, _("Sorry, cannot edit this hunk"));
1104
+ else if (edit_hunk_loop(s, file_diff, hunk) >= 0) {
1105
+ hunk->use = USE_HUNK;
1106
+ goto soft_increment;
1107
+ }
1108
} else
1109
color_fprintf(stdout, s->s.help_color,
1110
_(help_patch_text));
1118
if (i < file_diff->hunk_nr) {
1119
/* At least one hunk selected: apply */
1120
strbuf_reset(&s->buf);
822
- reassemble_patch(s, file_diff, &s->buf);
1121
+ reassemble_patch(s, file_diff, 0, &s->buf);
1122
1123
discard_index(s->s.r->index);
1124
setup_child_process(s, &cp, "apply", "--cached", NULL);