Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/merge-ll.c
Line
Count
Source
1
/*
2
 * Low level 3-way in-core file merge.
3
 *
4
 * Copyright (c) 2007 Junio C Hamano
5
 */
6
7
#define USE_THE_REPOSITORY_VARIABLE
8
#define DISABLE_SIGN_COMPARE_WARNINGS
9
10
#include "git-compat-util.h"
11
#include "config.h"
12
#include "convert.h"
13
#include "attr.h"
14
#include "xdiff-interface.h"
15
#include "run-command.h"
16
#include "merge-ll.h"
17
#include "quote.h"
18
#include "strbuf.h"
19
#include "gettext.h"
20
#include "tempfile.h"
21
22
struct ll_merge_driver;
23
24
typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
25
         mmfile_t *result,
26
         const char *path,
27
         mmfile_t *orig, const char *orig_name,
28
         mmfile_t *src1, const char *name1,
29
         mmfile_t *src2, const char *name2,
30
         const struct ll_merge_options *opts,
31
         int marker_size);
32
33
struct ll_merge_driver {
34
  const char *name;
35
  const char *description;
36
  ll_merge_fn fn;
37
  char *recursive;
38
  struct ll_merge_driver *next;
39
  char *cmdline;
40
};
41
42
static struct attr_check *merge_attributes;
43
static struct attr_check *load_merge_attributes(void)
44
0
{
45
0
  if (!merge_attributes)
46
0
    merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL);
47
0
  return merge_attributes;
48
0
}
49
50
void reset_merge_attributes(void)
51
0
{
52
0
  attr_check_free(merge_attributes);
53
0
  merge_attributes = NULL;
54
0
}
55
56
/*
57
 * Built-in low-levels
58
 */
59
static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED,
60
         mmfile_t *result,
61
         const char *path UNUSED,
62
         mmfile_t *orig, const char *orig_name UNUSED,
63
         mmfile_t *src1, const char *name1 UNUSED,
64
         mmfile_t *src2, const char *name2 UNUSED,
65
         const struct ll_merge_options *opts,
66
         int marker_size UNUSED)
67
0
{
68
0
  enum ll_merge_result ret;
69
0
  mmfile_t *stolen;
70
0
  assert(opts);
71
72
  /*
73
   * The tentative merge result is the common ancestor for an
74
   * internal merge.  For the final merge, it is "ours" by
75
   * default but -Xours/-Xtheirs can tweak the choice.
76
   */
77
0
  if (opts->virtual_ancestor) {
78
0
    stolen = orig;
79
0
    ret = LL_MERGE_OK;
80
0
  } else {
81
0
    switch (opts->variant) {
82
0
    default:
83
0
      ret = LL_MERGE_BINARY_CONFLICT;
84
0
      stolen = src1;
85
0
      break;
86
0
    case XDL_MERGE_FAVOR_OURS:
87
0
      ret = LL_MERGE_OK;
88
0
      stolen = src1;
89
0
      break;
90
0
    case XDL_MERGE_FAVOR_THEIRS:
91
0
      ret = LL_MERGE_OK;
92
0
      stolen = src2;
93
0
      break;
94
0
    }
95
0
  }
96
97
0
  result->ptr = stolen->ptr;
98
0
  result->size = stolen->size;
99
0
  stolen->ptr = NULL;
100
101
0
  return ret;
102
0
}
103
104
static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
105
      mmfile_t *result,
106
      const char *path,
107
      mmfile_t *orig, const char *orig_name,
108
      mmfile_t *src1, const char *name1,
109
      mmfile_t *src2, const char *name2,
110
      const struct ll_merge_options *opts,
111
      int marker_size)
112
0
{
113
0
  enum ll_merge_result ret;
114
0
  xmparam_t xmp;
115
0
  int status;
116
0
  assert(opts);
117
118
0
  if (orig->size > MAX_XDIFF_SIZE ||
119
0
      src1->size > MAX_XDIFF_SIZE ||
120
0
      src2->size > MAX_XDIFF_SIZE ||
121
0
      buffer_is_binary(orig->ptr, orig->size) ||
122
0
      buffer_is_binary(src1->ptr, src1->size) ||
123
0
      buffer_is_binary(src2->ptr, src2->size)) {
124
0
    return ll_binary_merge(drv_unused, result,
125
0
               path,
126
0
               orig, orig_name,
127
0
               src1, name1,
128
0
               src2, name2,
129
0
               opts, marker_size);
130
0
  }
131
132
0
  memset(&xmp, 0, sizeof(xmp));
133
0
  xmp.level = XDL_MERGE_ZEALOUS;
134
0
  xmp.favor = opts->variant;
135
0
  xmp.xpp.flags = opts->xdl_opts;
136
0
  if (opts->conflict_style >= 0)
137
0
    xmp.style = opts->conflict_style;
138
0
  else if (git_xmerge_style >= 0)
139
0
    xmp.style = git_xmerge_style;
140
0
  if (marker_size > 0)
141
0
    xmp.marker_size = marker_size;
142
0
  xmp.ancestor = orig_name;
143
0
  xmp.file1 = name1;
144
0
  xmp.file2 = name2;
145
0
  status = xdl_merge(orig, src1, src2, &xmp, result);
146
0
  ret = (status > 0) ? LL_MERGE_CONFLICT : status;
147
0
  return ret;
148
0
}
149
150
static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
151
        mmfile_t *result,
152
        const char *path,
153
        mmfile_t *orig, const char *orig_name,
154
        mmfile_t *src1, const char *name1,
155
        mmfile_t *src2, const char *name2,
156
        const struct ll_merge_options *opts,
157
        int marker_size)
158
0
{
159
  /* Use union favor */
160
0
  struct ll_merge_options o;
161
0
  assert(opts);
162
0
  o = *opts;
163
0
  o.variant = XDL_MERGE_FAVOR_UNION;
164
0
  return ll_xdl_merge(drv_unused, result, path,
165
0
          orig, orig_name, src1, name1, src2, name2,
166
0
          &o, marker_size);
167
0
}
168
169
0
#define LL_BINARY_MERGE 0
170
0
#define LL_TEXT_MERGE 1
171
#define LL_UNION_MERGE 2
172
static struct ll_merge_driver ll_merge_drv[] = {
173
  { "binary", "built-in binary merge", ll_binary_merge },
174
  { "text", "built-in 3-way text merge", ll_xdl_merge },
175
  { "union", "built-in union merge", ll_union_merge },
176
};
177
178
static struct tempfile *create_temp(mmfile_t *src)
179
0
{
180
0
  struct tempfile *t = xmks_tempfile(".merge_file_XXXXXX");
181
0
  if (write_in_full(t->fd, src->ptr, src->size) < 0)
182
0
    die_errno(_("unable to write %s"), get_tempfile_path(t));
183
0
  if (close_tempfile_gently(t) < 0)
184
0
    die_errno(_("unable to close %s"), get_tempfile_path(t));
185
0
  return t;
186
0
}
187
188
static const char *temp_path_basename(struct tempfile *t)
189
0
{
190
  /*
191
   * basename() takes a non-const pointer because it can
192
   * modify the input string to remove trailing directory
193
   * separators. We know that we don't have any because
194
   * this is a clean path generated from our vanilla
195
   * tempfile template.
196
   *
197
   * So casting away the const here is safe, albeit gross.
198
   */
199
0
  return basename((char *)get_tempfile_path(t));
200
0
}
201
202
/*
203
 * User defined low-level merge driver support.
204
 */
205
static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
206
      mmfile_t *result,
207
      const char *path,
208
      mmfile_t *orig, const char *orig_name,
209
      mmfile_t *src1, const char *name1,
210
      mmfile_t *src2, const char *name2,
211
      const struct ll_merge_options *opts,
212
      int marker_size)
213
0
{
214
0
  struct tempfile *tmp_o, *tmp_a, *tmp_b;
215
0
  struct strbuf cmd = STRBUF_INIT;
216
0
  const char *format = fn->cmdline;
217
0
  struct child_process child = CHILD_PROCESS_INIT;
218
0
  int status;
219
0
  enum ll_merge_result ret;
220
0
  assert(opts);
221
222
0
  if (!fn->cmdline)
223
0
    die("custom merge driver %s lacks command line.", fn->name);
224
225
0
  result->ptr = NULL;
226
0
  result->size = 0;
227
0
  tmp_o = create_temp(orig);
228
0
  tmp_a = create_temp(src1);
229
0
  tmp_b = create_temp(src2);
230
231
0
  while (strbuf_expand_step(&cmd, &format)) {
232
0
    if (skip_prefix(format, "%", &format))
233
0
      strbuf_addch(&cmd, '%');
234
0
    else if (skip_prefix(format, "O", &format))
235
0
      strbuf_addstr(&cmd, temp_path_basename(tmp_o));
236
0
    else if (skip_prefix(format, "A", &format))
237
0
      strbuf_addstr(&cmd, temp_path_basename(tmp_a));
238
0
    else if (skip_prefix(format, "B", &format))
239
0
      strbuf_addstr(&cmd, temp_path_basename(tmp_b));
240
0
    else if (skip_prefix(format, "L", &format))
241
0
      strbuf_addf(&cmd, "%d", marker_size);
242
0
    else if (skip_prefix(format, "P", &format))
243
0
      sq_quote_buf(&cmd, path);
244
0
    else if (skip_prefix(format, "S", &format))
245
0
      sq_quote_buf(&cmd, orig_name ? orig_name : "");
246
0
    else if (skip_prefix(format, "X", &format))
247
0
      sq_quote_buf(&cmd, name1 ? name1 : "");
248
0
    else if (skip_prefix(format, "Y", &format))
249
0
      sq_quote_buf(&cmd, name2 ? name2 : "");
250
0
    else
251
0
      strbuf_addch(&cmd, '%');
252
0
  }
253
254
0
  child.use_shell = 1;
255
0
  strvec_push(&child.args, cmd.buf);
256
0
  status = run_command(&child);
257
0
  if (!status)
258
0
    ret = LL_MERGE_OK;
259
0
  else if (status <= 128)
260
0
    ret = LL_MERGE_CONFLICT;
261
0
  else
262
    /* died due to a signal: WTERMSIG(status) + 128 */
263
0
    ret = LL_MERGE_ERROR;
264
265
0
  if (read_mmfile(result, temp_path_basename(tmp_a)) < 0)
266
0
    ret = LL_MERGE_ERROR;
267
268
0
  delete_tempfile(&tmp_o);
269
0
  delete_tempfile(&tmp_a);
270
0
  delete_tempfile(&tmp_b);
271
0
  strbuf_release(&cmd);
272
0
  return ret;
273
0
}
274
275
/*
276
 * merge.default and merge.driver configuration items
277
 */
278
static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
279
static char *default_ll_merge;
280
281
static int read_merge_config(const char *var, const char *value,
282
           const struct config_context *ctx UNUSED,
283
           void *cb UNUSED)
284
0
{
285
0
  struct ll_merge_driver *fn;
286
0
  const char *key, *name;
287
0
  size_t namelen;
288
289
0
  if (!strcmp(var, "merge.default"))
290
0
    return git_config_string(&default_ll_merge, var, value);
291
292
  /*
293
   * We are not interested in anything but "merge.<name>.variable";
294
   * especially, we do not want to look at variables such as
295
   * "merge.summary", "merge.tool", and "merge.verbosity".
296
   */
297
0
  if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name)
298
0
    return 0;
299
300
  /*
301
   * Find existing one as we might be processing merge.<name>.var2
302
   * after seeing merge.<name>.var1.
303
   */
304
0
  for (fn = ll_user_merge; fn; fn = fn->next)
305
0
    if (!xstrncmpz(fn->name, name, namelen))
306
0
      break;
307
0
  if (!fn) {
308
0
    CALLOC_ARRAY(fn, 1);
309
0
    fn->name = xmemdupz(name, namelen);
310
0
    fn->fn = ll_ext_merge;
311
0
    *ll_user_merge_tail = fn;
312
0
    ll_user_merge_tail = &(fn->next);
313
0
  }
314
315
0
  if (!strcmp("name", key)) {
316
    /*
317
     * The description is leaking, but that's okay as we want to
318
     * keep around the merge drivers anyway.
319
     */
320
0
    return git_config_string((char **) &fn->description, var, value);
321
0
  }
322
323
0
  if (!strcmp("driver", key)) {
324
0
    if (!value)
325
0
      return config_error_nonbool(var);
326
    /*
327
     * merge.<name>.driver specifies the command line:
328
     *
329
     *  command-line
330
     *
331
     * The command-line will be interpolated with the following
332
     * tokens and is given to the shell:
333
     *
334
     *    %O - temporary file name for the merge base.
335
     *    %A - temporary file name for our version.
336
     *    %B - temporary file name for the other branches' version.
337
     *    %L - conflict marker length
338
     *    %P - the original path (safely quoted for the shell)
339
     *    %S - the revision for the merge base
340
     *    %X - the revision for our version
341
     *    %Y - the revision for their version
342
     *
343
     * If the file is not named identically in all versions, then each
344
     * revision is joined with the corresponding path, separated by a colon.
345
     * The external merge driver should write the results in the
346
     * file named by %A, and signal that it has done with zero exit
347
     * status.
348
     */
349
0
    fn->cmdline = xstrdup(value);
350
0
    return 0;
351
0
  }
352
353
0
  if (!strcmp("recursive", key))
354
0
    return git_config_string(&fn->recursive, var, value);
355
356
0
  return 0;
357
0
}
358
359
static void initialize_ll_merge(void)
360
0
{
361
0
  if (ll_user_merge_tail)
362
0
    return;
363
0
  ll_user_merge_tail = &ll_user_merge;
364
0
  repo_config(the_repository, read_merge_config, NULL);
365
0
}
366
367
static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
368
0
{
369
0
  struct ll_merge_driver *fn;
370
0
  const char *name;
371
0
  int i;
372
373
0
  initialize_ll_merge();
374
375
0
  if (ATTR_TRUE(merge_attr))
376
0
    return &ll_merge_drv[LL_TEXT_MERGE];
377
0
  else if (ATTR_FALSE(merge_attr))
378
0
    return &ll_merge_drv[LL_BINARY_MERGE];
379
0
  else if (ATTR_UNSET(merge_attr)) {
380
0
    if (!default_ll_merge)
381
0
      return &ll_merge_drv[LL_TEXT_MERGE];
382
0
    else
383
0
      name = default_ll_merge;
384
0
  }
385
0
  else
386
0
    name = merge_attr;
387
388
0
  for (fn = ll_user_merge; fn; fn = fn->next)
389
0
    if (!strcmp(fn->name, name))
390
0
      return fn;
391
392
0
  for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
393
0
    if (!strcmp(ll_merge_drv[i].name, name))
394
0
      return &ll_merge_drv[i];
395
396
  /* default to the 3-way */
397
0
  return &ll_merge_drv[LL_TEXT_MERGE];
398
0
}
399
400
static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate)
401
0
{
402
0
  struct strbuf strbuf = STRBUF_INIT;
403
0
  if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) {
404
0
    free(mm->ptr);
405
0
    mm->size = strbuf.len;
406
0
    mm->ptr = strbuf_detach(&strbuf, NULL);
407
0
  }
408
0
}
409
410
enum ll_merge_result ll_merge(mmfile_t *result_buf,
411
       const char *path,
412
       mmfile_t *ancestor, const char *ancestor_label,
413
       mmfile_t *ours, const char *our_label,
414
       mmfile_t *theirs, const char *their_label,
415
       struct index_state *istate,
416
       const struct ll_merge_options *opts)
417
0
{
418
0
  struct attr_check *check = load_merge_attributes();
419
0
  static const struct ll_merge_options default_opts = LL_MERGE_OPTIONS_INIT;
420
0
  const char *ll_driver_name = NULL;
421
0
  int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
422
0
  const struct ll_merge_driver *driver;
423
424
0
  if (!opts)
425
0
    opts = &default_opts;
426
427
0
  if (opts->renormalize) {
428
0
    normalize_file(ancestor, path, istate);
429
0
    normalize_file(ours, path, istate);
430
0
    normalize_file(theirs, path, istate);
431
0
  }
432
433
0
  git_check_attr(istate, path, check);
434
0
  ll_driver_name = check->items[0].value;
435
0
  if (check->items[1].value) {
436
0
    if (strtol_i(check->items[1].value, 10, &marker_size)) {
437
0
      marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
438
0
      warning(_("invalid marker-size '%s', expecting an integer"), check->items[1].value);
439
0
    }
440
0
    if (marker_size <= 0)
441
0
      marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
442
0
  }
443
0
  driver = find_ll_merge_driver(ll_driver_name);
444
445
0
  if (opts->virtual_ancestor) {
446
0
    if (driver->recursive)
447
0
      driver = find_ll_merge_driver(driver->recursive);
448
0
  }
449
0
  if (opts->extra_marker_size) {
450
0
    marker_size += opts->extra_marker_size;
451
0
  }
452
0
  return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
453
0
        ours, our_label, theirs, their_label,
454
0
        opts, marker_size);
455
0
}
456
457
int ll_merge_marker_size(struct index_state *istate, const char *path)
458
0
{
459
0
  static struct attr_check *check;
460
0
  int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
461
462
0
  if (!check)
463
0
    check = attr_check_initl("conflict-marker-size", NULL);
464
0
  git_check_attr(istate, path, check);
465
0
  if (check->items[0].value) {
466
0
    if (strtol_i(check->items[0].value, 10, &marker_size)) {
467
0
      marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
468
0
      warning(_("invalid marker-size '%s', expecting an integer"), check->items[0].value);
469
0
    }
470
0
    if (marker_size <= 0)
471
0
      marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
472
0
  }
473
0
  return marker_size;
474
0
}
475
476
int is_conflict_marker_line(const char *line, unsigned long len, int marker_size)
477
0
{
478
0
  char firstchar;
479
0
  int cnt;
480
481
0
  if (len < marker_size + 1)
482
0
    return 0;
483
484
0
  firstchar = line[0];
485
0
  switch (firstchar) {
486
0
  case '=': case '>': case '<': case '|':
487
0
    break;
488
0
  default:
489
0
    return 0;
490
0
  }
491
492
0
  for (cnt = 1; cnt < marker_size; cnt++) {
493
0
    if (line[cnt] != firstchar)
494
0
      return 0;
495
0
  }
496
497
0
  if (((firstchar == '<') || (firstchar == '>')) &&
498
0
      line[marker_size] != ' ')
499
0
    return 0;
500
501
0
  if (!isspace((unsigned char)line[marker_size]))
502
0
    return 0;
503
504
0
  return firstchar;
505
0
}
506
507
int has_conflict_markers(struct index_state *istate, const char *path)
508
0
{
509
0
  FILE *f;
510
0
  struct strbuf sb = STRBUF_INIT;
511
0
  int marker_size = ll_merge_marker_size(istate, path);
512
0
  int has_markers = 0;
513
514
0
  f = fopen(path, "r");
515
0
  if (!f)
516
0
    return 0;
517
518
0
  while (strbuf_getwholeline(&sb, f, '\n') != EOF) {
519
0
    if (is_conflict_marker_line(sb.buf, sb.len, marker_size)) {
520
0
      has_markers = 1;
521
0
      break;
522
0
    }
523
0
    if (buffer_is_binary(sb.buf,
524
0
             ULONG_MAX <= sb.len ? ULONG_MAX : sb.len))
525
0
      break;
526
0
  }
527
0
  fclose(f);
528
0
  strbuf_release(&sb);
529
0
  return has_markers;
530
0
}