Coverage Report

Created: 2026-02-14 06:27

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