Coverage Report

Created: 2023-11-19 07:08

/src/git/add-patch.c
Line
Count
Source (jump to first uncovered line)
1
#include "git-compat-util.h"
2
#include "add-interactive.h"
3
#include "advice.h"
4
#include "editor.h"
5
#include "environment.h"
6
#include "gettext.h"
7
#include "object-name.h"
8
#include "read-cache-ll.h"
9
#include "repository.h"
10
#include "strbuf.h"
11
#include "run-command.h"
12
#include "strvec.h"
13
#include "pathspec.h"
14
#include "color.h"
15
#include "diff.h"
16
#include "compat/terminal.h"
17
#include "prompt.h"
18
19
enum prompt_mode_type {
20
  PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK,
21
  PROMPT_MODE_MAX, /* must be last */
22
};
23
24
struct patch_mode {
25
  /*
26
   * The magic constant 4 is chosen such that all patch modes
27
   * provide enough space for three command-line arguments followed by a
28
   * trailing `NULL`.
29
   */
30
  const char *diff_cmd[4], *apply_args[4], *apply_check_args[4];
31
  unsigned is_reverse:1, index_only:1, apply_for_checkout:1;
32
  const char *prompt_mode[PROMPT_MODE_MAX];
33
  const char *edit_hunk_hint, *help_patch_text;
34
};
35
36
static struct patch_mode patch_mode_add = {
37
  .diff_cmd = { "diff-files", NULL },
38
  .apply_args = { "--cached", NULL },
39
  .apply_check_args = { "--cached", NULL },
40
  .prompt_mode = {
41
    N_("Stage mode change [y,n,q,a,d%s,?]? "),
42
    N_("Stage deletion [y,n,q,a,d%s,?]? "),
43
    N_("Stage addition [y,n,q,a,d%s,?]? "),
44
    N_("Stage this hunk [y,n,q,a,d%s,?]? ")
45
  },
46
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
47
           "will immediately be marked for staging."),
48
  .help_patch_text =
49
    N_("y - stage this hunk\n"
50
       "n - do not stage this hunk\n"
51
       "q - quit; do not stage this hunk or any of the remaining "
52
      "ones\n"
53
       "a - stage this hunk and all later hunks in the file\n"
54
       "d - do not stage this hunk or any of the later hunks in "
55
      "the file\n")
56
};
57
58
static struct patch_mode patch_mode_stash = {
59
  .diff_cmd = { "diff-index", "HEAD", NULL },
60
  .apply_args = { "--cached", NULL },
61
  .apply_check_args = { "--cached", NULL },
62
  .prompt_mode = {
63
    N_("Stash mode change [y,n,q,a,d%s,?]? "),
64
    N_("Stash deletion [y,n,q,a,d%s,?]? "),
65
    N_("Stash addition [y,n,q,a,d%s,?]? "),
66
    N_("Stash this hunk [y,n,q,a,d%s,?]? "),
67
  },
68
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
69
           "will immediately be marked for stashing."),
70
  .help_patch_text =
71
    N_("y - stash this hunk\n"
72
       "n - do not stash this hunk\n"
73
       "q - quit; do not stash this hunk or any of the remaining "
74
      "ones\n"
75
       "a - stash this hunk and all later hunks in the file\n"
76
       "d - do not stash this hunk or any of the later hunks in "
77
      "the file\n"),
78
};
79
80
static struct patch_mode patch_mode_reset_head = {
81
  .diff_cmd = { "diff-index", "--cached", NULL },
82
  .apply_args = { "-R", "--cached", NULL },
83
  .apply_check_args = { "-R", "--cached", NULL },
84
  .is_reverse = 1,
85
  .index_only = 1,
86
  .prompt_mode = {
87
    N_("Unstage mode change [y,n,q,a,d%s,?]? "),
88
    N_("Unstage deletion [y,n,q,a,d%s,?]? "),
89
    N_("Unstage addition [y,n,q,a,d%s,?]? "),
90
    N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
91
  },
92
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
93
           "will immediately be marked for unstaging."),
94
  .help_patch_text =
95
    N_("y - unstage this hunk\n"
96
       "n - do not unstage this hunk\n"
97
       "q - quit; do not unstage this hunk or any of the remaining "
98
      "ones\n"
99
       "a - unstage this hunk and all later hunks in the file\n"
100
       "d - do not unstage this hunk or any of the later hunks in "
101
      "the file\n"),
102
};
103
104
static struct patch_mode patch_mode_reset_nothead = {
105
  .diff_cmd = { "diff-index", "-R", "--cached", NULL },
106
  .apply_args = { "--cached", NULL },
107
  .apply_check_args = { "--cached", NULL },
108
  .index_only = 1,
109
  .prompt_mode = {
110
    N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
111
    N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
112
    N_("Apply addition to index [y,n,q,a,d%s,?]? "),
113
    N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
114
  },
115
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
116
           "will immediately be marked for applying."),
117
  .help_patch_text =
118
    N_("y - apply this hunk to index\n"
119
       "n - do not apply this hunk to index\n"
120
       "q - quit; do not apply this hunk or any of the remaining "
121
      "ones\n"
122
       "a - apply this hunk and all later hunks in the file\n"
123
       "d - do not apply this hunk or any of the later hunks in "
124
      "the file\n"),
125
};
126
127
static struct patch_mode patch_mode_checkout_index = {
128
  .diff_cmd = { "diff-files", NULL },
129
  .apply_args = { "-R", NULL },
130
  .apply_check_args = { "-R", NULL },
131
  .is_reverse = 1,
132
  .prompt_mode = {
133
    N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
134
    N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
135
    N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
136
    N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
137
  },
138
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
139
           "will immediately be marked for discarding."),
140
  .help_patch_text =
141
    N_("y - discard this hunk from worktree\n"
142
       "n - do not discard this hunk from worktree\n"
143
       "q - quit; do not discard this hunk or any of the remaining "
144
      "ones\n"
145
       "a - discard this hunk and all later hunks in the file\n"
146
       "d - do not discard this hunk or any of the later hunks in "
147
      "the file\n"),
148
};
149
150
static struct patch_mode patch_mode_checkout_head = {
151
  .diff_cmd = { "diff-index", NULL },
152
  .apply_for_checkout = 1,
153
  .apply_check_args = { "-R", NULL },
154
  .is_reverse = 1,
155
  .prompt_mode = {
156
    N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
157
    N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
158
    N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
159
    N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
160
  },
161
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
162
           "will immediately be marked for discarding."),
163
  .help_patch_text =
164
    N_("y - discard this hunk from index and worktree\n"
165
       "n - do not discard this hunk from index and worktree\n"
166
       "q - quit; do not discard this hunk or any of the remaining "
167
      "ones\n"
168
       "a - discard this hunk and all later hunks in the file\n"
169
       "d - do not discard this hunk or any of the later hunks in "
170
      "the file\n"),
171
};
172
173
static struct patch_mode patch_mode_checkout_nothead = {
174
  .diff_cmd = { "diff-index", "-R", NULL },
175
  .apply_for_checkout = 1,
176
  .apply_check_args = { NULL },
177
  .prompt_mode = {
178
    N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
179
    N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
180
    N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
181
    N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
182
  },
183
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
184
           "will immediately be marked for applying."),
185
  .help_patch_text =
186
    N_("y - apply this hunk to index and worktree\n"
187
       "n - do not apply this hunk to index and worktree\n"
188
       "q - quit; do not apply this hunk or any of the remaining "
189
      "ones\n"
190
       "a - apply this hunk and all later hunks in the file\n"
191
       "d - do not apply this hunk or any of the later hunks in "
192
      "the file\n"),
193
};
194
195
static struct patch_mode patch_mode_worktree_head = {
196
  .diff_cmd = { "diff-index", NULL },
197
  .apply_args = { "-R", NULL },
198
  .apply_check_args = { "-R", NULL },
199
  .is_reverse = 1,
200
  .prompt_mode = {
201
    N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
202
    N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
203
    N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
204
    N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
205
  },
206
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
207
           "will immediately be marked for discarding."),
208
  .help_patch_text =
209
    N_("y - discard this hunk from worktree\n"
210
       "n - do not discard this hunk from worktree\n"
211
       "q - quit; do not discard this hunk or any of the remaining "
212
      "ones\n"
213
       "a - discard this hunk and all later hunks in the file\n"
214
       "d - do not discard this hunk or any of the later hunks in "
215
      "the file\n"),
216
};
217
218
static struct patch_mode patch_mode_worktree_nothead = {
219
  .diff_cmd = { "diff-index", "-R", NULL },
220
  .apply_args = { NULL },
221
  .apply_check_args = { NULL },
222
  .prompt_mode = {
223
    N_("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
224
    N_("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
225
    N_("Apply addition to worktree [y,n,q,a,d%s,?]? "),
226
    N_("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
227
  },
228
  .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
229
           "will immediately be marked for applying."),
230
  .help_patch_text =
231
    N_("y - apply this hunk to worktree\n"
232
       "n - do not apply this hunk to worktree\n"
233
       "q - quit; do not apply this hunk or any of the remaining "
234
      "ones\n"
235
       "a - apply this hunk and all later hunks in the file\n"
236
       "d - do not apply this hunk or any of the later hunks in "
237
      "the file\n"),
238
};
239
240
struct hunk_header {
241
  unsigned long old_offset, old_count, new_offset, new_count;
242
  /*
243
   * Start/end offsets to the extra text after the second `@@` in the
244
   * hunk header, e.g. the function signature. This is expected to
245
   * include the newline.
246
   */
247
  size_t extra_start, extra_end, colored_extra_start, colored_extra_end;
248
  unsigned suppress_colored_line_range:1;
249
};
250
251
struct hunk {
252
  size_t start, end, colored_start, colored_end, splittable_into;
253
  ssize_t delta;
254
  enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
255
  struct hunk_header header;
256
};
257
258
struct add_p_state {
259
  struct add_i_state s;
260
  struct strbuf answer, buf;
261
262
  /* parsed diff */
263
  struct strbuf plain, colored;
264
  struct file_diff {
265
    struct hunk head;
266
    struct hunk *hunk;
267
    size_t hunk_nr, hunk_alloc;
268
    unsigned deleted:1, added:1, mode_change:1,binary:1;
269
  } *file_diff;
270
  size_t file_diff_nr;
271
272
  /* patch mode */
273
  struct patch_mode *mode;
274
  const char *revision;
275
};
276
277
static void add_p_state_clear(struct add_p_state *s)
278
0
{
279
0
  size_t i;
280
281
0
  strbuf_release(&s->answer);
282
0
  strbuf_release(&s->buf);
283
0
  strbuf_release(&s->plain);
284
0
  strbuf_release(&s->colored);
285
0
  for (i = 0; i < s->file_diff_nr; i++)
286
0
    free(s->file_diff[i].hunk);
287
0
  free(s->file_diff);
288
0
  clear_add_i_state(&s->s);
289
0
}
290
291
__attribute__((format (printf, 2, 3)))
292
static void err(struct add_p_state *s, const char *fmt, ...)
293
0
{
294
0
  va_list args;
295
296
0
  va_start(args, fmt);
297
0
  fputs(s->s.error_color, stderr);
298
0
  vfprintf(stderr, fmt, args);
299
0
  fputs(s->s.reset_color, stderr);
300
0
  fputc('\n', stderr);
301
0
  va_end(args);
302
0
}
303
304
static void setup_child_process(struct add_p_state *s,
305
        struct child_process *cp, ...)
306
0
{
307
0
  va_list ap;
308
0
  const char *arg;
309
310
0
  va_start(ap, cp);
311
0
  while ((arg = va_arg(ap, const char *)))
312
0
    strvec_push(&cp->args, arg);
313
0
  va_end(ap);
314
315
0
  cp->git_cmd = 1;
316
0
  strvec_pushf(&cp->env,
317
0
         INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
318
0
}
319
320
static int parse_range(const char **p,
321
           unsigned long *offset, unsigned long *count)
322
0
{
323
0
  char *pend;
324
325
0
  *offset = strtoul(*p, &pend, 10);
326
0
  if (pend == *p)
327
0
    return -1;
328
0
  if (*pend != ',') {
329
0
    *count = 1;
330
0
    *p = pend;
331
0
    return 0;
332
0
  }
333
0
  *count = strtoul(pend + 1, (char **)p, 10);
334
0
  return *p == pend + 1 ? -1 : 0;
335
0
}
336
337
static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
338
0
{
339
0
  struct hunk_header *header = &hunk->header;
340
0
  const char *line = s->plain.buf + hunk->start, *p = line;
341
0
  char *eol = memchr(p, '\n', s->plain.len - hunk->start);
342
343
0
  if (!eol)
344
0
    eol = s->plain.buf + s->plain.len;
345
346
0
  if (!skip_prefix(p, "@@ -", &p) ||
347
0
      parse_range(&p, &header->old_offset, &header->old_count) < 0 ||
348
0
      !skip_prefix(p, " +", &p) ||
349
0
      parse_range(&p, &header->new_offset, &header->new_count) < 0 ||
350
0
      !skip_prefix(p, " @@", &p))
351
0
    return error(_("could not parse hunk header '%.*s'"),
352
0
           (int)(eol - line), line);
353
354
0
  hunk->start = eol - s->plain.buf + (*eol == '\n');
355
0
  header->extra_start = p - s->plain.buf;
356
0
  header->extra_end = hunk->start;
357
358
0
  if (!s->colored.len) {
359
0
    header->colored_extra_start = header->colored_extra_end = 0;
360
0
    return 0;
361
0
  }
362
363
  /* Now find the extra text in the colored diff */
364
0
  line = s->colored.buf + hunk->colored_start;
365
0
  eol = memchr(line, '\n', s->colored.len - hunk->colored_start);
366
0
  if (!eol)
367
0
    eol = s->colored.buf + s->colored.len;
368
0
  p = memmem(line, eol - line, "@@ -", 4);
369
0
  if (p && (p = memmem(p + 4, eol - p - 4, " @@", 3))) {
370
0
    header->colored_extra_start = p + 3 - s->colored.buf;
371
0
  } else {
372
    /* could not parse colored hunk header, leave as-is */
373
0
    header->colored_extra_start = hunk->colored_start;
374
0
    header->suppress_colored_line_range = 1;
375
0
  }
376
0
  hunk->colored_start = eol - s->colored.buf + (*eol == '\n');
377
0
  header->colored_extra_end = hunk->colored_start;
378
379
0
  return 0;
380
0
}
381
382
static int is_octal(const char *p, size_t len)
383
0
{
384
0
  if (!len)
385
0
    return 0;
386
387
0
  while (len--)
388
0
    if (*p < '0' || *(p++) > '7')
389
0
      return 0;
390
0
  return 1;
391
0
}
392
393
static void complete_file(char marker, struct hunk *hunk)
394
0
{
395
0
  if (marker == '-' || marker == '+')
396
    /*
397
     * Last hunk ended in non-context line (i.e. it
398
     * appended lines to the file, so there are no
399
     * trailing context lines).
400
     */
401
0
    hunk->splittable_into++;
402
0
}
403
404
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
405
0
{
406
0
  struct strvec args = STRVEC_INIT;
407
0
  const char *diff_algorithm = s->s.interactive_diff_algorithm;
408
0
  struct strbuf *plain = &s->plain, *colored = NULL;
409
0
  struct child_process cp = CHILD_PROCESS_INIT;
410
0
  char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
411
0
  size_t file_diff_alloc = 0, i, color_arg_index;
412
0
  struct file_diff *file_diff = NULL;
413
0
  struct hunk *hunk = NULL;
414
0
  int res;
415
416
0
  strvec_pushv(&args, s->mode->diff_cmd);
417
0
  if (diff_algorithm)
418
0
    strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
419
0
  if (s->revision) {
420
0
    struct object_id oid;
421
0
    strvec_push(&args,
422
          /* could be on an unborn branch */
423
0
          !strcmp("HEAD", s->revision) &&
424
0
          repo_get_oid(the_repository, "HEAD", &oid) ?
425
0
          empty_tree_oid_hex() : s->revision);
426
0
  }
427
0
  color_arg_index = args.nr;
428
  /* Use `--no-color` explicitly, just in case `diff.color = always`. */
429
0
  strvec_pushl(&args, "--no-color", "--ignore-submodules=dirty", "-p",
430
0
         "--", NULL);
431
0
  for (i = 0; i < ps->nr; i++)
432
0
    strvec_push(&args, ps->items[i].original);
433
434
0
  setup_child_process(s, &cp, NULL);
435
0
  strvec_pushv(&cp.args, args.v);
436
0
  res = capture_command(&cp, plain, 0);
437
0
  if (res) {
438
0
    strvec_clear(&args);
439
0
    return error(_("could not parse diff"));
440
0
  }
441
0
  if (!plain->len) {
442
0
    strvec_clear(&args);
443
0
    return 0;
444
0
  }
445
0
  strbuf_complete_line(plain);
446
447
0
  if (want_color_fd(1, -1)) {
448
0
    struct child_process colored_cp = CHILD_PROCESS_INIT;
449
0
    const char *diff_filter = s->s.interactive_diff_filter;
450
451
0
    setup_child_process(s, &colored_cp, NULL);
452
0
    xsnprintf((char *)args.v[color_arg_index], 8, "--color");
453
0
    strvec_pushv(&colored_cp.args, args.v);
454
0
    colored = &s->colored;
455
0
    res = capture_command(&colored_cp, colored, 0);
456
0
    strvec_clear(&args);
457
0
    if (res)
458
0
      return error(_("could not parse colored diff"));
459
460
0
    if (diff_filter) {
461
0
      struct child_process filter_cp = CHILD_PROCESS_INIT;
462
463
0
      setup_child_process(s, &filter_cp,
464
0
              diff_filter, NULL);
465
0
      filter_cp.git_cmd = 0;
466
0
      filter_cp.use_shell = 1;
467
0
      strbuf_reset(&s->buf);
468
0
      if (pipe_command(&filter_cp,
469
0
           colored->buf, colored->len,
470
0
           &s->buf, colored->len,
471
0
           NULL, 0) < 0)
472
0
        return error(_("failed to run '%s'"),
473
0
               diff_filter);
474
0
      strbuf_swap(colored, &s->buf);
475
0
    }
476
477
0
    strbuf_complete_line(colored);
478
0
    colored_p = colored->buf;
479
0
    colored_pend = colored_p + colored->len;
480
0
  }
481
0
  strvec_clear(&args);
482
483
  /* parse files and hunks */
484
0
  p = plain->buf;
485
0
  pend = p + plain->len;
486
0
  while (p != pend) {
487
0
    char *eol = memchr(p, '\n', pend - p);
488
0
    const char *deleted = NULL, *mode_change = NULL;
489
490
0
    if (!eol)
491
0
      eol = pend;
492
493
0
    if (starts_with(p, "diff ") ||
494
0
        starts_with(p, "* Unmerged path ")) {
495
0
      complete_file(marker, hunk);
496
0
      ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1,
497
0
           file_diff_alloc);
498
0
      file_diff = s->file_diff + s->file_diff_nr - 1;
499
0
      hunk = &file_diff->head;
500
0
      hunk->start = p - plain->buf;
501
0
      if (colored_p)
502
0
        hunk->colored_start = colored_p - colored->buf;
503
0
      marker = '\0';
504
0
    } else if (p == plain->buf)
505
0
      BUG("diff starts with unexpected line:\n"
506
0
          "%.*s\n", (int)(eol - p), p);
507
0
    else if (file_diff->deleted)
508
0
      ; /* keep the rest of the file in a single "hunk" */
509
0
    else if (starts_with(p, "@@ ") ||
510
0
       (hunk == &file_diff->head &&
511
0
        (skip_prefix(p, "deleted file", &deleted)))) {
512
0
      if (marker == '-' || marker == '+')
513
        /*
514
         * Should not happen; previous hunk did not end
515
         * in a context line? Handle it anyway.
516
         */
517
0
        hunk->splittable_into++;
518
519
0
      ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1,
520
0
           file_diff->hunk_alloc);
521
0
      hunk = file_diff->hunk + file_diff->hunk_nr - 1;
522
523
0
      hunk->start = p - plain->buf;
524
0
      if (colored)
525
0
        hunk->colored_start = colored_p - colored->buf;
526
527
0
      if (deleted)
528
0
        file_diff->deleted = 1;
529
0
      else if (parse_hunk_header(s, hunk) < 0)
530
0
        return -1;
531
532
      /*
533
       * Start counting into how many hunks this one can be
534
       * split
535
       */
536
0
      marker = *p;
537
0
    } else if (hunk == &file_diff->head &&
538
0
         starts_with(p, "new file")) {
539
0
      file_diff->added = 1;
540
0
    } else if (hunk == &file_diff->head &&
541
0
         skip_prefix(p, "old mode ", &mode_change) &&
542
0
         is_octal(mode_change, eol - mode_change)) {
543
0
      if (file_diff->mode_change)
544
0
        BUG("double mode change?\n\n%.*s",
545
0
            (int)(eol - plain->buf), plain->buf);
546
0
      if (file_diff->hunk_nr)
547
0
        BUG("mode change in the middle?\n\n%.*s",
548
0
            (int)(eol - plain->buf), plain->buf);
549
550
      /*
551
       * Do *not* change `hunk`: the mode change pseudo-hunk
552
       * is _part of_ the header "hunk".
553
       */
554
0
      file_diff->mode_change = 1;
555
0
      ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1,
556
0
           file_diff->hunk_alloc);
557
0
      file_diff->hunk->start = p - plain->buf;
558
0
      if (colored_p)
559
0
        file_diff->hunk->colored_start =
560
0
          colored_p - colored->buf;
561
0
    } else if (hunk == &file_diff->head &&
562
0
         skip_prefix(p, "new mode ", &mode_change) &&
563
0
         is_octal(mode_change, eol - mode_change)) {
564
565
      /*
566
       * Extend the "mode change" pseudo-hunk to include also
567
       * the "new mode" line.
568
       */
569
0
      if (!file_diff->mode_change)
570
0
        BUG("'new mode' without 'old mode'?\n\n%.*s",
571
0
            (int)(eol - plain->buf), plain->buf);
572
0
      if (file_diff->hunk_nr != 1)
573
0
        BUG("mode change in the middle?\n\n%.*s",
574
0
            (int)(eol - plain->buf), plain->buf);
575
0
      if (p - plain->buf != file_diff->hunk->end)
576
0
        BUG("'new mode' does not immediately follow "
577
0
            "'old mode'?\n\n%.*s",
578
0
            (int)(eol - plain->buf), plain->buf);
579
0
    } else if (hunk == &file_diff->head &&
580
0
         starts_with(p, "Binary files "))
581
0
      file_diff->binary = 1;
582
583
0
    if (!!file_diff->deleted + !!file_diff->added +
584
0
        !!file_diff->mode_change > 1)
585
0
      BUG("diff can only contain delete *or* add *or* a "
586
0
          "mode change?!?\n%.*s",
587
0
          (int)(eol - (plain->buf + file_diff->head.start)),
588
0
          plain->buf + file_diff->head.start);
589
590
0
    if ((marker == '-' || marker == '+') && *p == ' ')
591
0
      hunk->splittable_into++;
592
0
    if (marker && *p != '\\')
593
0
      marker = *p;
594
595
0
    p = eol == pend ? pend : eol + 1;
596
0
    hunk->end = p - plain->buf;
597
598
0
    if (colored) {
599
0
      char *colored_eol = memchr(colored_p, '\n',
600
0
               colored_pend - colored_p);
601
0
      if (colored_eol)
602
0
        colored_p = colored_eol + 1;
603
0
      else if (p != pend)
604
        /* non-colored has more lines? */
605
0
        goto mismatched_output;
606
0
      else if (colored_p == colored_pend)
607
        /* last line has no matching colored one? */
608
0
        goto mismatched_output;
609
0
      else
610
0
        colored_p = colored_pend;
611
612
0
      hunk->colored_end = colored_p - colored->buf;
613
0
    }
614
615
0
    if (mode_change) {
616
0
      if (file_diff->hunk_nr != 1)
617
0
        BUG("mode change in hunk #%d???",
618
0
            (int)file_diff->hunk_nr);
619
      /* Adjust the end of the "mode change" pseudo-hunk */
620
0
      file_diff->hunk->end = hunk->end;
621
0
      if (colored)
622
0
        file_diff->hunk->colored_end = hunk->colored_end;
623
0
    }
624
0
  }
625
0
  complete_file(marker, hunk);
626
627
  /* non-colored shorter than colored? */
628
0
  if (colored_p != colored_pend) {
629
0
mismatched_output:
630
0
    error(_("mismatched output from interactive.diffFilter"));
631
0
    advise(_("Your filter must maintain a one-to-one correspondence\n"
632
0
       "between its input and output lines."));
633
0
    return -1;
634
0
  }
635
636
0
  return 0;
637
0
}
638
639
static size_t find_next_line(struct strbuf *sb, size_t offset)
640
0
{
641
0
  char *eol;
642
643
0
  if (offset >= sb->len)
644
0
    BUG("looking for next line beyond buffer (%d >= %d)\n%s",
645
0
        (int)offset, (int)sb->len, sb->buf);
646
647
0
  eol = memchr(sb->buf + offset, '\n', sb->len - offset);
648
0
  if (!eol)
649
0
    return sb->len;
650
0
  return eol - sb->buf + 1;
651
0
}
652
653
static void render_hunk(struct add_p_state *s, struct hunk *hunk,
654
      ssize_t delta, int colored, struct strbuf *out)
655
0
{
656
0
  struct hunk_header *header = &hunk->header;
657
658
0
  if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) {
659
    /*
660
     * Generate the hunk header dynamically, except for special
661
     * hunks (such as the diff header).
662
     */
663
0
    const char *p;
664
0
    size_t len;
665
0
    unsigned long old_offset = header->old_offset;
666
0
    unsigned long new_offset = header->new_offset;
667
668
0
    if (!colored) {
669
0
      p = s->plain.buf + header->extra_start;
670
0
      len = header->extra_end - header->extra_start;
671
0
    } else if (header->suppress_colored_line_range) {
672
0
      strbuf_add(out,
673
0
           s->colored.buf + header->colored_extra_start,
674
0
           header->colored_extra_end -
675
0
           header->colored_extra_start);
676
677
0
      strbuf_add(out, s->colored.buf + hunk->colored_start,
678
0
           hunk->colored_end - hunk->colored_start);
679
0
      return;
680
0
    } else {
681
0
      strbuf_addstr(out, s->s.fraginfo_color);
682
0
      p = s->colored.buf + header->colored_extra_start;
683
0
      len = header->colored_extra_end
684
0
        - header->colored_extra_start;
685
0
    }
686
687
0
    if (s->mode->is_reverse)
688
0
      old_offset -= delta;
689
0
    else
690
0
      new_offset += delta;
691
692
0
    strbuf_addf(out, "@@ -%lu", old_offset);
693
0
    if (header->old_count != 1)
694
0
      strbuf_addf(out, ",%lu", header->old_count);
695
0
    strbuf_addf(out, " +%lu", new_offset);
696
0
    if (header->new_count != 1)
697
0
      strbuf_addf(out, ",%lu", header->new_count);
698
0
    strbuf_addstr(out, " @@");
699
700
0
    if (len)
701
0
      strbuf_add(out, p, len);
702
0
    else if (colored)
703
0
      strbuf_addf(out, "%s\n", s->s.reset_color);
704
0
    else
705
0
      strbuf_addch(out, '\n');
706
0
  }
707
708
0
  if (colored)
709
0
    strbuf_add(out, s->colored.buf + hunk->colored_start,
710
0
         hunk->colored_end - hunk->colored_start);
711
0
  else
712
0
    strbuf_add(out, s->plain.buf + hunk->start,
713
0
         hunk->end - hunk->start);
714
0
}
715
716
static void render_diff_header(struct add_p_state *s,
717
             struct file_diff *file_diff, int colored,
718
             struct strbuf *out)
719
0
{
720
  /*
721
   * If there was a mode change, the first hunk is a pseudo hunk that
722
   * corresponds to the mode line in the header. If the user did not want
723
   * to stage that "hunk", we actually have to cut it out from the header.
724
   */
725
0
  int skip_mode_change =
726
0
    file_diff->mode_change && file_diff->hunk->use != USE_HUNK;
727
0
  struct hunk *head = &file_diff->head, *first = file_diff->hunk;
728
729
0
  if (!skip_mode_change) {
730
0
    render_hunk(s, head, 0, colored, out);
731
0
    return;
732
0
  }
733
734
0
  if (colored) {
735
0
    const char *p = s->colored.buf;
736
737
0
    strbuf_add(out, p + head->colored_start,
738
0
          first->colored_start - head->colored_start);
739
0
    strbuf_add(out, p + first->colored_end,
740
0
          head->colored_end - first->colored_end);
741
0
  } else {
742
0
    const char *p = s->plain.buf;
743
744
0
    strbuf_add(out, p + head->start, first->start - head->start);
745
0
    strbuf_add(out, p + first->end, head->end - first->end);
746
0
  }
747
0
}
748
749
/* Coalesce hunks again that were split */
750
static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff,
751
           size_t *hunk_index, int use_all, struct hunk *merged)
752
0
{
753
0
  size_t i = *hunk_index, delta;
754
0
  struct hunk *hunk = file_diff->hunk + i;
755
  /* `header` corresponds to the merged hunk */
756
0
  struct hunk_header *header = &merged->header, *next;
757
758
0
  if (!use_all && hunk->use != USE_HUNK)
759
0
    return 0;
760
761
0
  *merged = *hunk;
762
  /* We simply skip the colored part (if any) when merging hunks */
763
0
  merged->colored_start = merged->colored_end = 0;
764
765
0
  for (; i + 1 < file_diff->hunk_nr; i++) {
766
0
    hunk++;
767
0
    next = &hunk->header;
768
769
    /*
770
     * Stop merging hunks when:
771
     *
772
     * - the hunk is not selected for use, or
773
     * - the hunk does not overlap with the already-merged hunk(s)
774
     */
775
0
    if ((!use_all && hunk->use != USE_HUNK) ||
776
0
        header->new_offset >= next->new_offset + merged->delta ||
777
0
        header->new_offset + header->new_count
778
0
        < next->new_offset + merged->delta)
779
0
      break;
780
781
    /*
782
     * If the hunks were not edited, and overlap, we can simply
783
     * extend the line range.
784
     */
785
0
    if (merged->start < hunk->start && merged->end > hunk->start) {
786
0
      merged->end = hunk->end;
787
0
      merged->colored_end = hunk->colored_end;
788
0
      delta = 0;
789
0
    } else {
790
0
      const char *plain = s->plain.buf;
791
0
      size_t  overlapping_line_count = header->new_offset
792
0
        + header->new_count - merged->delta
793
0
        - next->new_offset;
794
0
      size_t overlap_end = hunk->start;
795
0
      size_t overlap_start = overlap_end;
796
0
      size_t overlap_next, len, j;
797
798
      /*
799
       * One of the hunks was edited: the modified hunk was
800
       * appended to the strbuf `s->plain`.
801
       *
802
       * Let's ensure that at least the last context line of
803
       * the first hunk overlaps with the corresponding line
804
       * of the second hunk, and then merge.
805
       */
806
0
      for (j = 0; j < overlapping_line_count; j++) {
807
0
        overlap_next = find_next_line(&s->plain,
808
0
                    overlap_end);
809
810
0
        if (overlap_next > hunk->end)
811
0
          BUG("failed to find %d context lines "
812
0
              "in:\n%.*s",
813
0
              (int)overlapping_line_count,
814
0
              (int)(hunk->end - hunk->start),
815
0
              plain + hunk->start);
816
817
0
        if (plain[overlap_end] != ' ')
818
0
          return error(_("expected context line "
819
0
                   "#%d in\n%.*s"),
820
0
                 (int)(j + 1),
821
0
                 (int)(hunk->end
822
0
                 - hunk->start),
823
0
                 plain + hunk->start);
824
825
0
        overlap_start = overlap_end;
826
0
        overlap_end = overlap_next;
827
0
      }
828
0
      len = overlap_end - overlap_start;
829
830
0
      if (len > merged->end - merged->start ||
831
0
          memcmp(plain + merged->end - len,
832
0
           plain + overlap_start, len))
833
0
        return error(_("hunks do not overlap:\n%.*s\n"
834
0
                 "\tdoes not end with:\n%.*s"),
835
0
               (int)(merged->end - merged->start),
836
0
               plain + merged->start,
837
0
               (int)len, plain + overlap_start);
838
839
      /*
840
       * Since the start-end ranges are not adjacent, we
841
       * cannot simply take the union of the ranges. To
842
       * address that, we temporarily append the union of the
843
       * lines to the `plain` strbuf.
844
       */
845
0
      if (merged->end != s->plain.len) {
846
0
        size_t start = s->plain.len;
847
848
0
        strbuf_add(&s->plain, plain + merged->start,
849
0
             merged->end - merged->start);
850
0
        plain = s->plain.buf;
851
0
        merged->start = start;
852
0
        merged->end = s->plain.len;
853
0
      }
854
855
0
      strbuf_add(&s->plain,
856
0
           plain + overlap_end,
857
0
           hunk->end - overlap_end);
858
0
      merged->end = s->plain.len;
859
0
      merged->splittable_into += hunk->splittable_into;
860
0
      delta = merged->delta;
861
0
      merged->delta += hunk->delta;
862
0
    }
863
864
0
    header->old_count = next->old_offset + next->old_count
865
0
      - header->old_offset;
866
0
    header->new_count = next->new_offset + delta
867
0
      + next->new_count - header->new_offset;
868
0
  }
869
870
0
  if (i == *hunk_index)
871
0
    return 0;
872
873
0
  *hunk_index = i;
874
0
  return 1;
875
0
}
876
877
static void reassemble_patch(struct add_p_state *s,
878
           struct file_diff *file_diff, int use_all,
879
           struct strbuf *out)
880
0
{
881
0
  struct hunk *hunk;
882
0
  size_t save_len = s->plain.len, i;
883
0
  ssize_t delta = 0;
884
885
0
  render_diff_header(s, file_diff, 0, out);
886
887
0
  for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
888
0
    struct hunk merged = { 0 };
889
890
0
    hunk = file_diff->hunk + i;
891
0
    if (!use_all && hunk->use != USE_HUNK)
892
0
      delta += hunk->header.old_count
893
0
        - hunk->header.new_count;
894
0
    else {
895
      /* merge overlapping hunks into a temporary hunk */
896
0
      if (merge_hunks(s, file_diff, &i, use_all, &merged))
897
0
        hunk = &merged;
898
899
0
      render_hunk(s, hunk, delta, 0, out);
900
901
      /*
902
       * In case `merge_hunks()` used `plain` as a scratch
903
       * pad (this happens when an edited hunk had to be
904
       * coalesced with another hunk).
905
       */
906
0
      strbuf_setlen(&s->plain, save_len);
907
908
0
      delta += hunk->delta;
909
0
    }
910
0
  }
911
0
}
912
913
static int split_hunk(struct add_p_state *s, struct file_diff *file_diff,
914
           size_t hunk_index)
915
0
{
916
0
  int colored = !!s->colored.len, first = 1;
917
0
  struct hunk *hunk = file_diff->hunk + hunk_index;
918
0
  size_t splittable_into;
919
0
  size_t end, colored_end, current, colored_current = 0, context_line_count;
920
0
  struct hunk_header remaining, *header;
921
0
  char marker, ch;
922
923
0
  if (hunk_index >= file_diff->hunk_nr)
924
0
    BUG("invalid hunk index: %d (must be >= 0 and < %d)",
925
0
        (int)hunk_index, (int)file_diff->hunk_nr);
926
927
0
  if (hunk->splittable_into < 2)
928
0
    return 0;
929
0
  splittable_into = hunk->splittable_into;
930
931
0
  end = hunk->end;
932
0
  colored_end = hunk->colored_end;
933
934
0
  remaining = hunk->header;
935
936
0
  file_diff->hunk_nr += splittable_into - 1;
937
0
  ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc);
938
0
  if (hunk_index + splittable_into < file_diff->hunk_nr)
939
0
    memmove(file_diff->hunk + hunk_index + splittable_into,
940
0
      file_diff->hunk + hunk_index + 1,
941
0
      (file_diff->hunk_nr - hunk_index - splittable_into)
942
0
      * sizeof(*hunk));
943
0
  hunk = file_diff->hunk + hunk_index;
944
0
  hunk->splittable_into = 1;
945
0
  memset(hunk + 1, 0, (splittable_into - 1) * sizeof(*hunk));
946
947
0
  header = &hunk->header;
948
0
  header->old_count = header->new_count = 0;
949
950
0
  current = hunk->start;
951
0
  if (colored)
952
0
    colored_current = hunk->colored_start;
953
0
  marker = '\0';
954
0
  context_line_count = 0;
955
956
0
  while (splittable_into > 1) {
957
0
    ch = s->plain.buf[current];
958
959
0
    if (!ch)
960
0
      BUG("buffer overrun while splitting hunks");
961
962
    /*
963
     * Is this the first context line after a chain of +/- lines?
964
     * Then record the start of the next split hunk.
965
     */
966
0
    if ((marker == '-' || marker == '+') && ch == ' ') {
967
0
      first = 0;
968
0
      hunk[1].start = current;
969
0
      if (colored)
970
0
        hunk[1].colored_start = colored_current;
971
0
      context_line_count = 0;
972
0
    }
973
974
    /*
975
     * Was the previous line a +/- one? Alternatively, is this the
976
     * first line (and not a +/- one)?
977
     *
978
     * Then just increment the appropriate counter and continue
979
     * with the next line.
980
     */
981
0
    if (marker != ' ' || (ch != '-' && ch != '+')) {
982
0
next_hunk_line:
983
      /* Comment lines are attached to the previous line */
984
0
      if (ch == '\\')
985
0
        ch = marker ? marker : ' ';
986
987
      /* current hunk not done yet */
988
0
      if (ch == ' ')
989
0
        context_line_count++;
990
0
      else if (ch == '-')
991
0
        header->old_count++;
992
0
      else if (ch == '+')
993
0
        header->new_count++;
994
0
      else
995
0
        BUG("unhandled diff marker: '%c'", ch);
996
0
      marker = ch;
997
0
      current = find_next_line(&s->plain, current);
998
0
      if (colored)
999
0
        colored_current =
1000
0
          find_next_line(&s->colored,
1001
0
                   colored_current);
1002
0
      continue;
1003
0
    }
1004
1005
    /*
1006
     * We got us the start of a new hunk!
1007
     *
1008
     * This is a context line, so it is shared with the previous
1009
     * hunk, if any.
1010
     */
1011
1012
0
    if (first) {
1013
0
      if (header->old_count || header->new_count)
1014
0
        BUG("counts are off: %d/%d",
1015
0
            (int)header->old_count,
1016
0
            (int)header->new_count);
1017
1018
0
      header->old_count = context_line_count;
1019
0
      header->new_count = context_line_count;
1020
0
      context_line_count = 0;
1021
0
      first = 0;
1022
0
      goto next_hunk_line;
1023
0
    }
1024
1025
0
    remaining.old_offset += header->old_count;
1026
0
    remaining.old_count -= header->old_count;
1027
0
    remaining.new_offset += header->new_count;
1028
0
    remaining.new_count -= header->new_count;
1029
1030
    /* initialize next hunk header's offsets */
1031
0
    hunk[1].header.old_offset =
1032
0
      header->old_offset + header->old_count;
1033
0
    hunk[1].header.new_offset =
1034
0
      header->new_offset + header->new_count;
1035
1036
    /* add one split hunk */
1037
0
    header->old_count += context_line_count;
1038
0
    header->new_count += context_line_count;
1039
1040
0
    hunk->end = current;
1041
0
    if (colored)
1042
0
      hunk->colored_end = colored_current;
1043
1044
0
    hunk++;
1045
0
    hunk->splittable_into = 1;
1046
0
    hunk->use = hunk[-1].use;
1047
0
    header = &hunk->header;
1048
1049
0
    header->old_count = header->new_count = context_line_count;
1050
0
    context_line_count = 0;
1051
1052
0
    splittable_into--;
1053
0
    marker = ch;
1054
0
  }
1055
1056
  /* last hunk simply gets the rest */
1057
0
  if (header->old_offset != remaining.old_offset)
1058
0
    BUG("miscounted old_offset: %lu != %lu",
1059
0
        header->old_offset, remaining.old_offset);
1060
0
  if (header->new_offset != remaining.new_offset)
1061
0
    BUG("miscounted new_offset: %lu != %lu",
1062
0
        header->new_offset, remaining.new_offset);
1063
0
  header->old_count = remaining.old_count;
1064
0
  header->new_count = remaining.new_count;
1065
0
  hunk->end = end;
1066
0
  if (colored)
1067
0
    hunk->colored_end = colored_end;
1068
1069
0
  return 0;
1070
0
}
1071
1072
static void recolor_hunk(struct add_p_state *s, struct hunk *hunk)
1073
0
{
1074
0
  const char *plain = s->plain.buf;
1075
0
  size_t current, eol, next;
1076
1077
0
  if (!s->colored.len)
1078
0
    return;
1079
1080
0
  hunk->colored_start = s->colored.len;
1081
0
  for (current = hunk->start; current < hunk->end; ) {
1082
0
    for (eol = current; eol < hunk->end; eol++)
1083
0
      if (plain[eol] == '\n')
1084
0
        break;
1085
0
    next = eol + (eol < hunk->end);
1086
0
    if (eol > current && plain[eol - 1] == '\r')
1087
0
      eol--;
1088
1089
0
    strbuf_addstr(&s->colored,
1090
0
            plain[current] == '-' ?
1091
0
            s->s.file_old_color :
1092
0
            plain[current] == '+' ?
1093
0
            s->s.file_new_color :
1094
0
            s->s.context_color);
1095
0
    strbuf_add(&s->colored, plain + current, eol - current);
1096
0
    strbuf_addstr(&s->colored, s->s.reset_color);
1097
0
    if (next > eol)
1098
0
      strbuf_add(&s->colored, plain + eol, next - eol);
1099
0
    current = next;
1100
0
  }
1101
0
  hunk->colored_end = s->colored.len;
1102
0
}
1103
1104
static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
1105
0
{
1106
0
  size_t i;
1107
1108
0
  strbuf_reset(&s->buf);
1109
0
  strbuf_commented_addf(&s->buf, comment_line_char,
1110
0
            _("Manual hunk edit mode -- see bottom for "
1111
0
        "a quick guide.\n"));
1112
0
  render_hunk(s, hunk, 0, 0, &s->buf);
1113
0
  strbuf_commented_addf(&s->buf, comment_line_char,
1114
0
            _("---\n"
1115
0
        "To remove '%c' lines, make them ' ' lines "
1116
0
        "(context).\n"
1117
0
        "To remove '%c' lines, delete them.\n"
1118
0
        "Lines starting with %c will be removed.\n"),
1119
0
            s->mode->is_reverse ? '+' : '-',
1120
0
            s->mode->is_reverse ? '-' : '+',
1121
0
            comment_line_char);
1122
0
  strbuf_commented_addf(&s->buf, comment_line_char, "%s",
1123
0
            _(s->mode->edit_hunk_hint));
1124
  /*
1125
   * TRANSLATORS: 'it' refers to the patch mentioned in the previous
1126
   * messages.
1127
   */
1128
0
  strbuf_commented_addf(&s->buf, comment_line_char,
1129
0
            _("If it does not apply cleanly, you will be "
1130
0
        "given an opportunity to\n"
1131
0
        "edit again.  If all lines of the hunk are "
1132
0
        "removed, then the edit is\n"
1133
0
        "aborted and the hunk is left unchanged.\n"));
1134
1135
0
  if (strbuf_edit_interactively(&s->buf, "addp-hunk-edit.diff", NULL) < 0)
1136
0
    return -1;
1137
1138
  /* strip out commented lines */
1139
0
  hunk->start = s->plain.len;
1140
0
  for (i = 0; i < s->buf.len; ) {
1141
0
    size_t next = find_next_line(&s->buf, i);
1142
1143
0
    if (s->buf.buf[i] != comment_line_char)
1144
0
      strbuf_add(&s->plain, s->buf.buf + i, next - i);
1145
0
    i = next;
1146
0
  }
1147
1148
0
  hunk->end = s->plain.len;
1149
0
  if (hunk->end == hunk->start)
1150
    /* The user aborted editing by deleting everything */
1151
0
    return 0;
1152
1153
0
  recolor_hunk(s, hunk);
1154
1155
  /*
1156
   * If the hunk header is intact, parse it, otherwise simply use the
1157
   * hunk header prior to editing (which will adjust `hunk->start` to
1158
   * skip the hunk header).
1159
   */
1160
0
  if (s->plain.buf[hunk->start] == '@' &&
1161
0
      parse_hunk_header(s, hunk) < 0)
1162
0
    return error(_("could not parse hunk header"));
1163
1164
0
  return 1;
1165
0
}
1166
1167
static ssize_t recount_edited_hunk(struct add_p_state *s, struct hunk *hunk,
1168
           size_t orig_old_count, size_t orig_new_count)
1169
0
{
1170
0
  struct hunk_header *header = &hunk->header;
1171
0
  size_t i;
1172
1173
0
  header->old_count = header->new_count = 0;
1174
0
  for (i = hunk->start; i < hunk->end; ) {
1175
0
    switch (s->plain.buf[i]) {
1176
0
    case '-':
1177
0
      header->old_count++;
1178
0
      break;
1179
0
    case '+':
1180
0
      header->new_count++;
1181
0
      break;
1182
0
    case ' ': case '\r': case '\n':
1183
0
      header->old_count++;
1184
0
      header->new_count++;
1185
0
      break;
1186
0
    }
1187
1188
0
    i = find_next_line(&s->plain, i);
1189
0
  }
1190
1191
0
  return orig_old_count - orig_new_count
1192
0
    - header->old_count + header->new_count;
1193
0
}
1194
1195
static int run_apply_check(struct add_p_state *s,
1196
         struct file_diff *file_diff)
1197
0
{
1198
0
  struct child_process cp = CHILD_PROCESS_INIT;
1199
1200
0
  strbuf_reset(&s->buf);
1201
0
  reassemble_patch(s, file_diff, 1, &s->buf);
1202
1203
0
  setup_child_process(s, &cp,
1204
0
          "apply", "--check", NULL);
1205
0
  strvec_pushv(&cp.args, s->mode->apply_check_args);
1206
0
  if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
1207
0
    return error(_("'git apply --cached' failed"));
1208
1209
0
  return 0;
1210
0
}
1211
1212
static int read_single_character(struct add_p_state *s)
1213
0
{
1214
0
  if (s->s.use_single_key) {
1215
0
    int res = read_key_without_echo(&s->answer);
1216
0
    printf("%s\n", res == EOF ? "" : s->answer.buf);
1217
0
    return res;
1218
0
  }
1219
1220
0
  if (git_read_line_interactively(&s->answer) == EOF)
1221
0
    return EOF;
1222
0
  return 0;
1223
0
}
1224
1225
static int prompt_yesno(struct add_p_state *s, const char *prompt)
1226
0
{
1227
0
  for (;;) {
1228
0
    color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
1229
0
    fflush(stdout);
1230
0
    if (read_single_character(s) == EOF)
1231
0
      return -1;
1232
0
    switch (tolower(s->answer.buf[0])) {
1233
0
    case 'n': return 0;
1234
0
    case 'y': return 1;
1235
0
    }
1236
0
  }
1237
0
}
1238
1239
static int edit_hunk_loop(struct add_p_state *s,
1240
        struct file_diff *file_diff, struct hunk *hunk)
1241
0
{
1242
0
  size_t plain_len = s->plain.len, colored_len = s->colored.len;
1243
0
  struct hunk backup;
1244
1245
0
  backup = *hunk;
1246
1247
0
  for (;;) {
1248
0
    int res = edit_hunk_manually(s, hunk);
1249
0
    if (res == 0) {
1250
      /* abandoned */
1251
0
      *hunk = backup;
1252
0
      return -1;
1253
0
    }
1254
1255
0
    if (res > 0) {
1256
0
      hunk->delta +=
1257
0
        recount_edited_hunk(s, hunk,
1258
0
                backup.header.old_count,
1259
0
                backup.header.new_count);
1260
0
      if (!run_apply_check(s, file_diff))
1261
0
        return 0;
1262
0
    }
1263
1264
    /* Drop edits (they were appended to s->plain) */
1265
0
    strbuf_setlen(&s->plain, plain_len);
1266
0
    strbuf_setlen(&s->colored, colored_len);
1267
0
    *hunk = backup;
1268
1269
    /*
1270
     * TRANSLATORS: do not translate [y/n]
1271
     * The program will only accept that input at this point.
1272
     * Consider translating (saying "no" discards!) as
1273
     * (saying "n" for "no" discards!) if the translation
1274
     * of the word "no" does not start with n.
1275
     */
1276
0
    res = prompt_yesno(s, _("Your edited hunk does not apply. "
1277
0
          "Edit again (saying \"no\" discards!) "
1278
0
          "[y/n]? "));
1279
0
    if (res < 1)
1280
0
      return -1;
1281
0
  }
1282
0
}
1283
1284
static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff,
1285
            int is_reverse)
1286
0
{
1287
0
  const char *reverse = is_reverse ? "-R" : NULL;
1288
0
  struct child_process check_index = CHILD_PROCESS_INIT;
1289
0
  struct child_process check_worktree = CHILD_PROCESS_INIT;
1290
0
  struct child_process apply_index = CHILD_PROCESS_INIT;
1291
0
  struct child_process apply_worktree = CHILD_PROCESS_INIT;
1292
0
  int applies_index, applies_worktree;
1293
1294
0
  setup_child_process(s, &check_index,
1295
0
          "apply", "--cached", "--check", reverse, NULL);
1296
0
  applies_index = !pipe_command(&check_index, diff->buf, diff->len,
1297
0
              NULL, 0, NULL, 0);
1298
1299
0
  setup_child_process(s, &check_worktree,
1300
0
          "apply", "--check", reverse, NULL);
1301
0
  applies_worktree = !pipe_command(&check_worktree, diff->buf, diff->len,
1302
0
           NULL, 0, NULL, 0);
1303
1304
0
  if (applies_worktree && applies_index) {
1305
0
    setup_child_process(s, &apply_index,
1306
0
            "apply", "--cached", reverse, NULL);
1307
0
    pipe_command(&apply_index, diff->buf, diff->len,
1308
0
           NULL, 0, NULL, 0);
1309
1310
0
    setup_child_process(s, &apply_worktree,
1311
0
            "apply", reverse, NULL);
1312
0
    pipe_command(&apply_worktree, diff->buf, diff->len,
1313
0
           NULL, 0, NULL, 0);
1314
1315
0
    return 1;
1316
0
  }
1317
1318
0
  if (!applies_index) {
1319
0
    err(s, _("The selected hunks do not apply to the index!"));
1320
0
    if (prompt_yesno(s, _("Apply them to the worktree "
1321
0
            "anyway? ")) > 0) {
1322
0
      setup_child_process(s, &apply_worktree,
1323
0
              "apply", reverse, NULL);
1324
0
      return pipe_command(&apply_worktree, diff->buf,
1325
0
              diff->len, NULL, 0, NULL, 0);
1326
0
    }
1327
0
    err(s, _("Nothing was applied.\n"));
1328
0
  } else
1329
    /* As a last resort, show the diff to the user */
1330
0
    fwrite(diff->buf, diff->len, 1, stderr);
1331
1332
0
  return 0;
1333
0
}
1334
1335
0
#define SUMMARY_HEADER_WIDTH 20
1336
0
#define SUMMARY_LINE_WIDTH 80
1337
static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
1338
         struct strbuf *out)
1339
0
{
1340
0
  struct hunk_header *header = &hunk->header;
1341
0
  struct strbuf *plain = &s->plain;
1342
0
  size_t len = out->len, i;
1343
1344
0
  strbuf_addf(out, " -%lu,%lu +%lu,%lu ",
1345
0
        header->old_offset, header->old_count,
1346
0
        header->new_offset, header->new_count);
1347
0
  if (out->len - len < SUMMARY_HEADER_WIDTH)
1348
0
    strbuf_addchars(out, ' ',
1349
0
        SUMMARY_HEADER_WIDTH + len - out->len);
1350
0
  for (i = hunk->start; i < hunk->end; i = find_next_line(plain, i))
1351
0
    if (plain->buf[i] != ' ')
1352
0
      break;
1353
0
  if (i < hunk->end)
1354
0
    strbuf_add(out, plain->buf + i, find_next_line(plain, i) - i);
1355
0
  if (out->len - len > SUMMARY_LINE_WIDTH)
1356
0
    strbuf_setlen(out, len + SUMMARY_LINE_WIDTH);
1357
0
  strbuf_complete_line(out);
1358
0
}
1359
1360
0
#define DISPLAY_HUNKS_LINES 20
1361
static size_t display_hunks(struct add_p_state *s,
1362
          struct file_diff *file_diff, size_t start_index)
1363
0
{
1364
0
  size_t end_index = start_index + DISPLAY_HUNKS_LINES;
1365
1366
0
  if (end_index > file_diff->hunk_nr)
1367
0
    end_index = file_diff->hunk_nr;
1368
1369
0
  while (start_index < end_index) {
1370
0
    struct hunk *hunk = file_diff->hunk + start_index++;
1371
1372
0
    strbuf_reset(&s->buf);
1373
0
    strbuf_addf(&s->buf, "%c%2d: ", hunk->use == USE_HUNK ? '+'
1374
0
          : hunk->use == SKIP_HUNK ? '-' : ' ',
1375
0
          (int)start_index);
1376
0
    summarize_hunk(s, hunk, &s->buf);
1377
0
    fputs(s->buf.buf, stdout);
1378
0
  }
1379
1380
0
  return end_index;
1381
0
}
1382
1383
static const char help_patch_remainder[] =
1384
N_("j - leave this hunk undecided, see next undecided hunk\n"
1385
   "J - leave this hunk undecided, see next hunk\n"
1386
   "k - leave this hunk undecided, see previous undecided hunk\n"
1387
   "K - leave this hunk undecided, see previous hunk\n"
1388
   "g - select a hunk to go to\n"
1389
   "/ - search for a hunk matching the given regex\n"
1390
   "s - split the current hunk into smaller hunks\n"
1391
   "e - manually edit the current hunk\n"
1392
   "? - print help\n");
1393
1394
static int patch_update_file(struct add_p_state *s,
1395
           struct file_diff *file_diff)
1396
0
{
1397
0
  size_t hunk_index = 0;
1398
0
  ssize_t i, undecided_previous, undecided_next;
1399
0
  struct hunk *hunk;
1400
0
  char ch;
1401
0
  struct child_process cp = CHILD_PROCESS_INIT;
1402
0
  int colored = !!s->colored.len, quit = 0;
1403
0
  enum prompt_mode_type prompt_mode_type;
1404
0
  enum {
1405
0
    ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
1406
0
    ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
1407
0
    ALLOW_GOTO_NEXT_HUNK = 1 << 2,
1408
0
    ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
1409
0
    ALLOW_SEARCH_AND_GOTO = 1 << 4,
1410
0
    ALLOW_SPLIT = 1 << 5,
1411
0
    ALLOW_EDIT = 1 << 6
1412
0
  } permitted = 0;
1413
1414
  /* Empty added files have no hunks */
1415
0
  if (!file_diff->hunk_nr && !file_diff->added)
1416
0
    return 0;
1417
1418
0
  strbuf_reset(&s->buf);
1419
0
  render_diff_header(s, file_diff, colored, &s->buf);
1420
0
  fputs(s->buf.buf, stdout);
1421
0
  for (;;) {
1422
0
    if (hunk_index >= file_diff->hunk_nr)
1423
0
      hunk_index = 0;
1424
0
    hunk = file_diff->hunk_nr
1425
0
        ? file_diff->hunk + hunk_index
1426
0
        : &file_diff->head;
1427
0
    undecided_previous = -1;
1428
0
    undecided_next = -1;
1429
1430
0
    if (file_diff->hunk_nr) {
1431
0
      for (i = hunk_index - 1; i >= 0; i--)
1432
0
        if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
1433
0
          undecided_previous = i;
1434
0
          break;
1435
0
        }
1436
1437
0
      for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
1438
0
        if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
1439
0
          undecided_next = i;
1440
0
          break;
1441
0
        }
1442
0
    }
1443
1444
    /* Everything decided? */
1445
0
    if (undecided_previous < 0 && undecided_next < 0 &&
1446
0
        hunk->use != UNDECIDED_HUNK)
1447
0
      break;
1448
1449
0
    strbuf_reset(&s->buf);
1450
0
    if (file_diff->hunk_nr) {
1451
0
      render_hunk(s, hunk, 0, colored, &s->buf);
1452
0
      fputs(s->buf.buf, stdout);
1453
1454
0
      strbuf_reset(&s->buf);
1455
0
      if (undecided_previous >= 0) {
1456
0
        permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
1457
0
        strbuf_addstr(&s->buf, ",k");
1458
0
      }
1459
0
      if (hunk_index) {
1460
0
        permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
1461
0
        strbuf_addstr(&s->buf, ",K");
1462
0
      }
1463
0
      if (undecided_next >= 0) {
1464
0
        permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
1465
0
        strbuf_addstr(&s->buf, ",j");
1466
0
      }
1467
0
      if (hunk_index + 1 < file_diff->hunk_nr) {
1468
0
        permitted |= ALLOW_GOTO_NEXT_HUNK;
1469
0
        strbuf_addstr(&s->buf, ",J");
1470
0
      }
1471
0
      if (file_diff->hunk_nr > 1) {
1472
0
        permitted |= ALLOW_SEARCH_AND_GOTO;
1473
0
        strbuf_addstr(&s->buf, ",g,/");
1474
0
      }
1475
0
      if (hunk->splittable_into > 1) {
1476
0
        permitted |= ALLOW_SPLIT;
1477
0
        strbuf_addstr(&s->buf, ",s");
1478
0
      }
1479
0
      if (hunk_index + 1 > file_diff->mode_change &&
1480
0
          !file_diff->deleted) {
1481
0
        permitted |= ALLOW_EDIT;
1482
0
        strbuf_addstr(&s->buf, ",e");
1483
0
      }
1484
0
    }
1485
0
    if (file_diff->deleted)
1486
0
      prompt_mode_type = PROMPT_DELETION;
1487
0
    else if (file_diff->added)
1488
0
      prompt_mode_type = PROMPT_ADDITION;
1489
0
    else if (file_diff->mode_change && !hunk_index)
1490
0
      prompt_mode_type = PROMPT_MODE_CHANGE;
1491
0
    else
1492
0
      prompt_mode_type = PROMPT_HUNK;
1493
1494
0
    printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->s.prompt_color,
1495
0
            (uintmax_t)hunk_index + 1,
1496
0
            (uintmax_t)(file_diff->hunk_nr
1497
0
            ? file_diff->hunk_nr
1498
0
            : 1));
1499
0
    printf(_(s->mode->prompt_mode[prompt_mode_type]),
1500
0
           s->buf.buf);
1501
0
    if (*s->s.reset_color)
1502
0
      fputs(s->s.reset_color, stdout);
1503
0
    fflush(stdout);
1504
0
    if (read_single_character(s) == EOF)
1505
0
      break;
1506
1507
0
    if (!s->answer.len)
1508
0
      continue;
1509
0
    ch = tolower(s->answer.buf[0]);
1510
0
    if (ch == 'y') {
1511
0
      hunk->use = USE_HUNK;
1512
0
soft_increment:
1513
0
      hunk_index = undecided_next < 0 ?
1514
0
        file_diff->hunk_nr : undecided_next;
1515
0
    } else if (ch == 'n') {
1516
0
      hunk->use = SKIP_HUNK;
1517
0
      goto soft_increment;
1518
0
    } else if (ch == 'a') {
1519
0
      if (file_diff->hunk_nr) {
1520
0
        for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1521
0
          hunk = file_diff->hunk + hunk_index;
1522
0
          if (hunk->use == UNDECIDED_HUNK)
1523
0
            hunk->use = USE_HUNK;
1524
0
        }
1525
0
      } else if (hunk->use == UNDECIDED_HUNK) {
1526
0
        hunk->use = USE_HUNK;
1527
0
      }
1528
0
    } else if (ch == 'd' || ch == 'q') {
1529
0
      if (file_diff->hunk_nr) {
1530
0
        for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1531
0
          hunk = file_diff->hunk + hunk_index;
1532
0
          if (hunk->use == UNDECIDED_HUNK)
1533
0
            hunk->use = SKIP_HUNK;
1534
0
        }
1535
0
      } else if (hunk->use == UNDECIDED_HUNK) {
1536
0
        hunk->use = SKIP_HUNK;
1537
0
      }
1538
0
      if (ch == 'q') {
1539
0
        quit = 1;
1540
0
        break;
1541
0
      }
1542
0
    } else if (s->answer.buf[0] == 'K') {
1543
0
      if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
1544
0
        hunk_index--;
1545
0
      else
1546
0
        err(s, _("No previous hunk"));
1547
0
    } else if (s->answer.buf[0] == 'J') {
1548
0
      if (permitted & ALLOW_GOTO_NEXT_HUNK)
1549
0
        hunk_index++;
1550
0
      else
1551
0
        err(s, _("No next hunk"));
1552
0
    } else if (s->answer.buf[0] == 'k') {
1553
0
      if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK)
1554
0
        hunk_index = undecided_previous;
1555
0
      else
1556
0
        err(s, _("No previous hunk"));
1557
0
    } else if (s->answer.buf[0] == 'j') {
1558
0
      if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK)
1559
0
        hunk_index = undecided_next;
1560
0
      else
1561
0
        err(s, _("No next hunk"));
1562
0
    } else if (s->answer.buf[0] == 'g') {
1563
0
      char *pend;
1564
0
      unsigned long response;
1565
1566
0
      if (!(permitted & ALLOW_SEARCH_AND_GOTO)) {
1567
0
        err(s, _("No other hunks to goto"));
1568
0
        continue;
1569
0
      }
1570
0
      strbuf_remove(&s->answer, 0, 1);
1571
0
      strbuf_trim(&s->answer);
1572
0
      i = hunk_index - DISPLAY_HUNKS_LINES / 2;
1573
0
      if (i < (int)file_diff->mode_change)
1574
0
        i = file_diff->mode_change;
1575
0
      while (s->answer.len == 0) {
1576
0
        i = display_hunks(s, file_diff, i);
1577
0
        printf("%s", i < file_diff->hunk_nr ?
1578
0
               _("go to which hunk (<ret> to see "
1579
0
           "more)? ") : _("go to which hunk? "));
1580
0
        fflush(stdout);
1581
0
        if (strbuf_getline(&s->answer,
1582
0
               stdin) == EOF)
1583
0
          break;
1584
0
        strbuf_trim_trailing_newline(&s->answer);
1585
0
      }
1586
1587
0
      strbuf_trim(&s->answer);
1588
0
      response = strtoul(s->answer.buf, &pend, 10);
1589
0
      if (*pend || pend == s->answer.buf)
1590
0
        err(s, _("Invalid number: '%s'"),
1591
0
            s->answer.buf);
1592
0
      else if (0 < response && response <= file_diff->hunk_nr)
1593
0
        hunk_index = response - 1;
1594
0
      else
1595
0
        err(s, Q_("Sorry, only %d hunk available.",
1596
0
            "Sorry, only %d hunks available.",
1597
0
            file_diff->hunk_nr),
1598
0
            (int)file_diff->hunk_nr);
1599
0
    } else if (s->answer.buf[0] == '/') {
1600
0
      regex_t regex;
1601
0
      int ret;
1602
1603
0
      if (!(permitted & ALLOW_SEARCH_AND_GOTO)) {
1604
0
        err(s, _("No other hunks to search"));
1605
0
        continue;
1606
0
      }
1607
0
      strbuf_remove(&s->answer, 0, 1);
1608
0
      strbuf_trim_trailing_newline(&s->answer);
1609
0
      if (s->answer.len == 0) {
1610
0
        printf("%s", _("search for regex? "));
1611
0
        fflush(stdout);
1612
0
        if (strbuf_getline(&s->answer,
1613
0
               stdin) == EOF)
1614
0
          break;
1615
0
        strbuf_trim_trailing_newline(&s->answer);
1616
0
        if (s->answer.len == 0)
1617
0
          continue;
1618
0
      }
1619
0
      ret = regcomp(&regex, s->answer.buf,
1620
0
              REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
1621
0
      if (ret) {
1622
0
        char errbuf[1024];
1623
1624
0
        regerror(ret, &regex, errbuf, sizeof(errbuf));
1625
0
        err(s, _("Malformed search regexp %s: %s"),
1626
0
            s->answer.buf, errbuf);
1627
0
        continue;
1628
0
      }
1629
0
      i = hunk_index;
1630
0
      for (;;) {
1631
        /* render the hunk into a scratch buffer */
1632
0
        render_hunk(s, file_diff->hunk + i, 0, 0,
1633
0
              &s->buf);
1634
0
        if (regexec(&regex, s->buf.buf, 0, NULL, 0)
1635
0
            != REG_NOMATCH)
1636
0
          break;
1637
0
        i++;
1638
0
        if (i == file_diff->hunk_nr)
1639
0
          i = 0;
1640
0
        if (i != hunk_index)
1641
0
          continue;
1642
0
        err(s, _("No hunk matches the given pattern"));
1643
0
        break;
1644
0
      }
1645
0
      hunk_index = i;
1646
0
    } else if (s->answer.buf[0] == 's') {
1647
0
      size_t splittable_into = hunk->splittable_into;
1648
0
      if (!(permitted & ALLOW_SPLIT))
1649
0
        err(s, _("Sorry, cannot split this hunk"));
1650
0
      else if (!split_hunk(s, file_diff,
1651
0
               hunk - file_diff->hunk))
1652
0
        color_fprintf_ln(stdout, s->s.header_color,
1653
0
             _("Split into %d hunks."),
1654
0
             (int)splittable_into);
1655
0
    } else if (s->answer.buf[0] == 'e') {
1656
0
      if (!(permitted & ALLOW_EDIT))
1657
0
        err(s, _("Sorry, cannot edit this hunk"));
1658
0
      else if (edit_hunk_loop(s, file_diff, hunk) >= 0) {
1659
0
        hunk->use = USE_HUNK;
1660
0
        goto soft_increment;
1661
0
      }
1662
0
    } else {
1663
0
      const char *p = _(help_patch_remainder), *eol = p;
1664
1665
0
      color_fprintf(stdout, s->s.help_color, "%s",
1666
0
              _(s->mode->help_patch_text));
1667
1668
      /*
1669
       * Show only those lines of the remainder that are
1670
       * actually applicable with the current hunk.
1671
       */
1672
0
      for (; *p; p = eol + (*eol == '\n')) {
1673
0
        eol = strchrnul(p, '\n');
1674
1675
        /*
1676
         * `s->buf` still contains the part of the
1677
         * commands shown in the prompt that are not
1678
         * always available.
1679
         */
1680
0
        if (*p != '?' && !strchr(s->buf.buf, *p))
1681
0
          continue;
1682
1683
0
        color_fprintf_ln(stdout, s->s.help_color,
1684
0
             "%.*s", (int)(eol - p), p);
1685
0
      }
1686
0
    }
1687
0
  }
1688
1689
  /* Any hunk to be used? */
1690
0
  for (i = 0; i < file_diff->hunk_nr; i++)
1691
0
    if (file_diff->hunk[i].use == USE_HUNK)
1692
0
      break;
1693
1694
0
  if (i < file_diff->hunk_nr ||
1695
0
      (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
1696
    /* At least one hunk selected: apply */
1697
0
    strbuf_reset(&s->buf);
1698
0
    reassemble_patch(s, file_diff, 0, &s->buf);
1699
1700
0
    discard_index(s->s.r->index);
1701
0
    if (s->mode->apply_for_checkout)
1702
0
      apply_for_checkout(s, &s->buf,
1703
0
             s->mode->is_reverse);
1704
0
    else {
1705
0
      setup_child_process(s, &cp, "apply", NULL);
1706
0
      strvec_pushv(&cp.args, s->mode->apply_args);
1707
0
      if (pipe_command(&cp, s->buf.buf, s->buf.len,
1708
0
           NULL, 0, NULL, 0))
1709
0
        error(_("'git apply' failed"));
1710
0
    }
1711
0
    if (repo_read_index(s->s.r) >= 0)
1712
0
      repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
1713
0
                 1, NULL, NULL, NULL);
1714
0
  }
1715
1716
0
  putchar('\n');
1717
0
  return quit;
1718
0
}
1719
1720
int run_add_p(struct repository *r, enum add_p_mode mode,
1721
        const char *revision, const struct pathspec *ps)
1722
0
{
1723
0
  struct add_p_state s = {
1724
0
    { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
1725
0
  };
1726
0
  size_t i, binary_count = 0;
1727
1728
0
  init_add_i_state(&s.s, r);
1729
1730
0
  if (mode == ADD_P_STASH)
1731
0
    s.mode = &patch_mode_stash;
1732
0
  else if (mode == ADD_P_RESET) {
1733
    /*
1734
     * NEEDSWORK: Instead of comparing to the literal "HEAD",
1735
     * compare the commit objects instead so that other ways of
1736
     * saying the same thing (such as "@") are also handled
1737
     * appropriately.
1738
     *
1739
     * This applies to the cases below too.
1740
     */
1741
0
    if (!revision || !strcmp(revision, "HEAD"))
1742
0
      s.mode = &patch_mode_reset_head;
1743
0
    else
1744
0
      s.mode = &patch_mode_reset_nothead;
1745
0
  } else if (mode == ADD_P_CHECKOUT) {
1746
0
    if (!revision)
1747
0
      s.mode = &patch_mode_checkout_index;
1748
0
    else if (!strcmp(revision, "HEAD"))
1749
0
      s.mode = &patch_mode_checkout_head;
1750
0
    else
1751
0
      s.mode = &patch_mode_checkout_nothead;
1752
0
  } else if (mode == ADD_P_WORKTREE) {
1753
0
    if (!revision)
1754
0
      s.mode = &patch_mode_checkout_index;
1755
0
    else if (!strcmp(revision, "HEAD"))
1756
0
      s.mode = &patch_mode_worktree_head;
1757
0
    else
1758
0
      s.mode = &patch_mode_worktree_nothead;
1759
0
  } else
1760
0
    s.mode = &patch_mode_add;
1761
0
  s.revision = revision;
1762
1763
0
  discard_index(r->index);
1764
0
  if (repo_read_index(r) < 0 ||
1765
0
      (!s.mode->index_only &&
1766
0
       repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
1767
0
            NULL, NULL, NULL) < 0) ||
1768
0
      parse_diff(&s, ps) < 0) {
1769
0
    add_p_state_clear(&s);
1770
0
    return -1;
1771
0
  }
1772
1773
0
  for (i = 0; i < s.file_diff_nr; i++)
1774
0
    if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr)
1775
0
      binary_count++;
1776
0
    else if (patch_update_file(&s, s.file_diff + i))
1777
0
      break;
1778
1779
0
  if (s.file_diff_nr == 0)
1780
0
    fprintf(stderr, _("No changes.\n"));
1781
0
  else if (binary_count == s.file_diff_nr)
1782
0
    fprintf(stderr, _("Only binary files changed.\n"));
1783
1784
0
  add_p_state_clear(&s);
1785
0
  return 0;
1786
0
}