Coverage Report

Created: 2025-12-31 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/xdiff-interface.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
#define DISABLE_SIGN_COMPARE_WARNINGS
3
4
#include "git-compat-util.h"
5
#include "environment.h"
6
#include "gettext.h"
7
#include "config.h"
8
#include "hex.h"
9
#include "odb.h"
10
#include "strbuf.h"
11
#include "xdiff-interface.h"
12
#include "xdiff/xtypes.h"
13
#include "xdiff/xdiffi.h"
14
#include "xdiff/xutils.h"
15
16
struct xdiff_emit_state {
17
  xdiff_emit_hunk_fn hunk_fn;
18
  xdiff_emit_line_fn line_fn;
19
  void *consume_callback_data;
20
  struct strbuf remainder;
21
};
22
23
static int xdiff_out_hunk(void *priv_,
24
        long old_begin, long old_nr,
25
        long new_begin, long new_nr,
26
        const char *func, long funclen)
27
0
{
28
0
  struct xdiff_emit_state *priv = priv_;
29
30
0
  if (priv->remainder.len)
31
0
    BUG("xdiff emitted hunk in the middle of a line");
32
33
0
  priv->hunk_fn(priv->consume_callback_data,
34
0
          old_begin, old_nr, new_begin, new_nr,
35
0
          func, funclen);
36
0
  return 0;
37
0
}
38
39
static int consume_one(void *priv_, char *s, unsigned long size)
40
0
{
41
0
  struct xdiff_emit_state *priv = priv_;
42
0
  char *ep;
43
0
  while (size) {
44
0
    unsigned long this_size;
45
0
    int ret;
46
0
    ep = memchr(s, '\n', size);
47
0
    this_size = (ep == NULL) ? size : (ep - s + 1);
48
0
    ret = priv->line_fn(priv->consume_callback_data, s, this_size);
49
0
    if (ret)
50
0
      return ret;
51
0
    size -= this_size;
52
0
    s += this_size;
53
0
  }
54
0
  return 0;
55
0
}
56
57
static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
58
0
{
59
0
  struct xdiff_emit_state *priv = priv_;
60
0
  int i;
61
0
  int stop = 0;
62
63
0
  if (!priv->line_fn)
64
0
    return 0;
65
66
0
  for (i = 0; i < nbuf; i++) {
67
0
    if (stop)
68
0
      return 1;
69
0
    if (mb[i].ptr[mb[i].size-1] != '\n') {
70
      /* Incomplete line */
71
0
      strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
72
0
      continue;
73
0
    }
74
75
    /* we have a complete line */
76
0
    if (!priv->remainder.len) {
77
0
      stop = consume_one(priv, mb[i].ptr, mb[i].size);
78
0
      continue;
79
0
    }
80
0
    strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
81
0
    stop = consume_one(priv, priv->remainder.buf, priv->remainder.len);
82
0
    strbuf_reset(&priv->remainder);
83
0
  }
84
0
  if (stop)
85
0
    return -1;
86
0
  if (priv->remainder.len) {
87
0
    stop = consume_one(priv, priv->remainder.buf, priv->remainder.len);
88
0
    strbuf_reset(&priv->remainder);
89
0
  }
90
0
  if (stop)
91
0
    return -1;
92
0
  return 0;
93
0
}
94
95
/*
96
 * Trim down common substring at the end of the buffers,
97
 * but end on a complete line.
98
 */
99
static void trim_common_tail(mmfile_t *a, mmfile_t *b)
100
0
{
101
0
  const int blk = 1024;
102
0
  long trimmed = 0, recovered = 0;
103
0
  char *ap = a->size ? a->ptr + a->size : a->ptr;
104
0
  char *bp = b->size ? b->ptr + b->size : b->ptr;
105
0
  long smaller = (a->size < b->size) ? a->size : b->size;
106
107
0
  while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
108
0
    trimmed += blk;
109
0
    ap -= blk;
110
0
    bp -= blk;
111
0
  }
112
113
0
  while (recovered < trimmed)
114
0
    if (ap[recovered++] == '\n')
115
0
      break;
116
0
  a->size -= trimmed - recovered;
117
0
  b->size -= trimmed - recovered;
118
0
}
119
120
int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *xecb)
121
0
{
122
0
  mmfile_t a = *mf1;
123
0
  mmfile_t b = *mf2;
124
125
0
  if (mf1->size > MAX_XDIFF_SIZE || mf2->size > MAX_XDIFF_SIZE)
126
0
    return -1;
127
128
0
  if (!xecfg->ctxlen && !(xecfg->flags & XDL_EMIT_FUNCCONTEXT))
129
0
    trim_common_tail(&a, &b);
130
131
0
  return xdl_diff(&a, &b, xpp, xecfg, xecb);
132
0
}
133
134
int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
135
      xdiff_emit_hunk_fn hunk_fn,
136
      xdiff_emit_line_fn line_fn,
137
      void *consume_callback_data,
138
      xpparam_t const *xpp, xdemitconf_t const *xecfg)
139
0
{
140
0
  int ret;
141
0
  struct xdiff_emit_state state;
142
0
  xdemitcb_t ecb;
143
144
0
  memset(&state, 0, sizeof(state));
145
0
  state.hunk_fn = hunk_fn;
146
0
  state.line_fn = line_fn;
147
0
  state.consume_callback_data = consume_callback_data;
148
0
  memset(&ecb, 0, sizeof(ecb));
149
0
  if (hunk_fn)
150
0
    ecb.out_hunk = xdiff_out_hunk;
151
0
  ecb.out_line = xdiff_outf;
152
0
  ecb.priv = &state;
153
0
  strbuf_init(&state.remainder, 0);
154
0
  ret = xdi_diff(mf1, mf2, xpp, xecfg, &ecb);
155
0
  strbuf_release(&state.remainder);
156
0
  return ret;
157
0
}
158
159
int read_mmfile(mmfile_t *ptr, const char *filename)
160
0
{
161
0
  struct stat st;
162
0
  FILE *f;
163
0
  size_t sz;
164
165
0
  if (stat(filename, &st))
166
0
    return error_errno("Could not stat %s", filename);
167
0
  if (!(f = fopen(filename, "rb")))
168
0
    return error_errno("Could not open %s", filename);
169
0
  sz = xsize_t(st.st_size);
170
0
  ptr->ptr = xmalloc(sz ? sz : 1);
171
0
  if (sz && fread(ptr->ptr, sz, 1, f) != 1) {
172
0
    fclose(f);
173
0
    return error("Could not read %s", filename);
174
0
  }
175
0
  fclose(f);
176
0
  ptr->size = sz;
177
0
  return 0;
178
0
}
179
180
void read_mmblob(mmfile_t *ptr, const struct object_id *oid)
181
0
{
182
0
  unsigned long size;
183
0
  enum object_type type;
184
185
0
  if (oideq(oid, null_oid(the_hash_algo))) {
186
0
    ptr->ptr = xstrdup("");
187
0
    ptr->size = 0;
188
0
    return;
189
0
  }
190
191
0
  ptr->ptr = odb_read_object(the_repository->objects, oid, &type, &size);
192
0
  if (!ptr->ptr || type != OBJ_BLOB)
193
0
    die("unable to read blob object %s", oid_to_hex(oid));
194
0
  ptr->size = size;
195
0
}
196
197
0
#define FIRST_FEW_BYTES 8000
198
int buffer_is_binary(const char *ptr, unsigned long size)
199
0
{
200
0
  if (FIRST_FEW_BYTES < size)
201
0
    size = FIRST_FEW_BYTES;
202
0
  return !!memchr(ptr, 0, size);
203
0
}
204
205
struct ff_regs {
206
  int nr;
207
  struct ff_reg {
208
    regex_t re;
209
    int negate;
210
  } *array;
211
};
212
213
static long ff_regexp(const char *line, long len,
214
    char *buffer, long buffer_size, void *priv)
215
0
{
216
0
  struct ff_regs *regs = priv;
217
0
  regmatch_t pmatch[2];
218
0
  int i;
219
0
  int result;
220
221
  /* Exclude terminating newline (and cr) from matching */
222
0
  if (len > 0 && line[len-1] == '\n') {
223
0
    if (len > 1 && line[len-2] == '\r')
224
0
      len -= 2;
225
0
    else
226
0
      len--;
227
0
  }
228
229
0
  for (i = 0; i < regs->nr; i++) {
230
0
    struct ff_reg *reg = regs->array + i;
231
0
    if (!regexec_buf(&reg->re, line, len, 2, pmatch, 0)) {
232
0
      if (reg->negate)
233
0
        return -1;
234
0
      break;
235
0
    }
236
0
  }
237
0
  if (regs->nr <= i)
238
0
    return -1;
239
0
  i = pmatch[1].rm_so >= 0 ? 1 : 0;
240
0
  line += pmatch[i].rm_so;
241
0
  result = pmatch[i].rm_eo - pmatch[i].rm_so;
242
0
  if (result > buffer_size)
243
0
    result = buffer_size;
244
0
  while (result > 0 && (isspace(line[result - 1])))
245
0
    result--;
246
0
  memcpy(buffer, line, result);
247
0
  return result;
248
0
}
249
250
void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags)
251
0
{
252
0
  int i;
253
0
  struct ff_regs *regs;
254
255
0
  xecfg->find_func = ff_regexp;
256
0
  regs = xecfg->find_func_priv = xmalloc(sizeof(struct ff_regs));
257
0
  for (i = 0, regs->nr = 1; value[i]; i++)
258
0
    if (value[i] == '\n')
259
0
      regs->nr++;
260
0
  ALLOC_ARRAY(regs->array, regs->nr);
261
0
  for (i = 0; i < regs->nr; i++) {
262
0
    struct ff_reg *reg = regs->array + i;
263
0
    const char *ep, *expression;
264
0
    char *buffer = NULL;
265
266
0
    if (!value)
267
0
      BUG("mismatch between line count and parsing");
268
0
    ep = strchr(value, '\n');
269
270
0
    reg->negate = (*value == '!');
271
0
    if (reg->negate && i == regs->nr - 1)
272
0
      die("Last expression must not be negated: %s", value);
273
0
    if (*value == '!')
274
0
      value++;
275
0
    if (ep)
276
0
      expression = buffer = xstrndup(value, ep - value);
277
0
    else
278
0
      expression = value;
279
0
    if (regcomp(&reg->re, expression, cflags))
280
0
      die("Invalid regexp to look for hunk header: %s", expression);
281
0
    free(buffer);
282
0
    value = ep ? ep + 1 : NULL;
283
0
  }
284
0
}
285
286
void xdiff_clear_find_func(xdemitconf_t *xecfg)
287
0
{
288
0
  if (xecfg->find_func) {
289
0
    int i;
290
0
    struct ff_regs *regs = xecfg->find_func_priv;
291
292
0
    for (i = 0; i < regs->nr; i++)
293
0
      regfree(&regs->array[i].re);
294
0
    free(regs->array);
295
0
    free(regs);
296
0
    xecfg->find_func = NULL;
297
0
    xecfg->find_func_priv = NULL;
298
0
  }
299
0
}
300
301
unsigned long xdiff_hash_string(const char *s, size_t len, long flags)
302
0
{
303
0
  return xdl_hash_record((uint8_t const**)&s, (uint8_t const*)s + len, flags);
304
0
}
305
306
int xdiff_compare_lines(const char *l1, long s1,
307
      const char *l2, long s2, long flags)
308
0
{
309
0
  return xdl_recmatch(l1, s1, l2, s2, flags);
310
0
}
311
312
int parse_conflict_style_name(const char *value)
313
0
{
314
0
  if (!strcmp(value, "diff3"))
315
0
    return XDL_MERGE_DIFF3;
316
0
  else if (!strcmp(value, "zdiff3"))
317
0
    return XDL_MERGE_ZEALOUS_DIFF3;
318
0
  else if (!strcmp(value, "merge"))
319
0
    return 0;
320
  /*
321
   * Please update _git_checkout() in git-completion.bash when
322
   * you add new merge config
323
   */
324
0
  else
325
0
    return -1;
326
0
}
327
328
int git_xmerge_style = -1;
329
330
int git_xmerge_config(const char *var, const char *value,
331
          const struct config_context *ctx, void *cb)
332
0
{
333
0
  if (!strcmp(var, "merge.conflictstyle")) {
334
0
    if (!value)
335
0
      return config_error_nonbool(var);
336
0
    git_xmerge_style = parse_conflict_style_name(value);
337
0
    if (git_xmerge_style == -1)
338
0
      return error(_("unknown style '%s' given for '%s'"),
339
0
             value, var);
340
0
    return 0;
341
0
  }
342
0
  return git_default_config(var, value, ctx, cb);
343
0
}