Coverage Report

Created: 2025-12-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/refspec.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 "gettext.h"
6
#include "hash.h"
7
#include "hex.h"
8
#include "string-list.h"
9
#include "strvec.h"
10
#include "refs.h"
11
#include "refspec.h"
12
#include "remote.h"
13
#include "strbuf.h"
14
15
/*
16
 * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
17
 * Returns 1 if successful and 0 if the refspec is invalid.
18
 */
19
static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch)
20
0
{
21
0
  size_t llen;
22
0
  int is_glob;
23
0
  const char *lhs, *rhs;
24
0
  int flags;
25
26
0
  is_glob = 0;
27
28
0
  lhs = refspec;
29
0
  if (*lhs == '+') {
30
0
    item->force = 1;
31
0
    lhs++;
32
0
  } else if (*lhs == '^') {
33
0
    item->negative = 1;
34
0
    lhs++;
35
0
  }
36
37
0
  rhs = strrchr(lhs, ':');
38
39
  /* negative refspecs only have one side */
40
0
  if (item->negative && rhs)
41
0
    return 0;
42
43
  /*
44
   * Before going on, special case ":" (or "+:") as a refspec
45
   * for pushing matching refs.
46
   */
47
0
  if (!fetch && rhs == lhs && rhs[1] == '\0') {
48
0
    item->matching = 1;
49
0
    return 1;
50
0
  }
51
52
0
  if (rhs) {
53
0
    size_t rlen = strlen(++rhs);
54
0
    is_glob = (1 <= rlen && strchr(rhs, '*'));
55
0
    item->dst = xstrndup(rhs, rlen);
56
0
  } else {
57
0
    item->dst = NULL;
58
0
  }
59
60
0
  llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
61
0
  if (1 <= llen && memchr(lhs, '*', llen)) {
62
0
    if ((rhs && !is_glob) || (!rhs && !item->negative && fetch))
63
0
      return 0;
64
0
    is_glob = 1;
65
0
  } else if (rhs && is_glob) {
66
0
    return 0;
67
0
  }
68
69
0
  item->pattern = is_glob;
70
0
  if (llen == 1 && *lhs == '@')
71
0
    item->src = xstrdup("HEAD");
72
0
  else
73
0
    item->src = xstrndup(lhs, llen);
74
0
  flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
75
76
0
  if (item->negative) {
77
0
    struct object_id unused;
78
79
    /*
80
     * Negative refspecs only have a LHS, which indicates a ref
81
     * (or pattern of refs) to exclude from other matches. This
82
     * can either be a simple ref, or a glob pattern. Exact sha1
83
     * match is not currently supported.
84
     */
85
0
    if (!*item->src)
86
0
      return 0; /* negative refspecs must not be empty */
87
0
    else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
88
0
      return 0; /* negative refpsecs cannot be exact sha1 */
89
0
    else if (!check_refname_format(item->src, flags))
90
0
      ; /* valid looking ref is ok */
91
0
    else
92
0
      return 0;
93
94
    /* the other rules below do not apply to negative refspecs */
95
0
    return 1;
96
0
  }
97
98
0
  if (fetch) {
99
0
    struct object_id unused;
100
101
    /* LHS */
102
0
    if (!*item->src)
103
0
      ; /* empty is ok; it means "HEAD" */
104
0
    else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
105
0
      item->exact_sha1 = 1; /* ok */
106
0
    else if (!check_refname_format(item->src, flags))
107
0
      ; /* valid looking ref is ok */
108
0
    else
109
0
      return 0;
110
    /* RHS */
111
0
    if (!item->dst)
112
0
      ; /* missing is ok; it is the same as empty */
113
0
    else if (!*item->dst)
114
0
      ; /* empty is ok; it means "do not store" */
115
0
    else if (!check_refname_format(item->dst, flags))
116
0
      ; /* valid looking ref is ok */
117
0
    else
118
0
      return 0;
119
0
  } else {
120
    /*
121
     * LHS
122
     * - empty is allowed; it means delete.
123
     * - when wildcarded, it must be a valid looking ref.
124
     * - otherwise, it must be an extended SHA-1, but
125
     *   there is no existing way to validate this.
126
     */
127
0
    if (!*item->src)
128
0
      ; /* empty is ok */
129
0
    else if (is_glob) {
130
0
      if (check_refname_format(item->src, flags))
131
0
        return 0;
132
0
    }
133
0
    else
134
0
      ; /* anything goes, for now */
135
    /*
136
     * RHS
137
     * - missing is allowed, but LHS then must be a
138
     *   valid looking ref.
139
     * - empty is not allowed.
140
     * - otherwise it must be a valid looking ref.
141
     */
142
0
    if (!item->dst) {
143
0
      if (check_refname_format(item->src, flags))
144
0
        return 0;
145
0
    } else if (!*item->dst) {
146
0
      return 0;
147
0
    } else {
148
0
      if (check_refname_format(item->dst, flags))
149
0
        return 0;
150
0
    }
151
0
  }
152
153
0
  return 1;
154
0
}
155
156
static int refspec_item_init(struct refspec_item *item, const char *refspec,
157
           int fetch)
158
0
{
159
0
  memset(item, 0, sizeof(*item));
160
0
  item->raw = xstrdup(refspec);
161
0
  return parse_refspec(item, refspec, fetch);
162
0
}
163
164
int refspec_item_init_fetch(struct refspec_item *item, const char *refspec)
165
0
{
166
0
  return refspec_item_init(item, refspec, 1);
167
0
}
168
169
int refspec_item_init_push(struct refspec_item *item, const char *refspec)
170
0
{
171
0
  return refspec_item_init(item, refspec, 0);
172
0
}
173
174
void refspec_item_clear(struct refspec_item *item)
175
0
{
176
0
  FREE_AND_NULL(item->src);
177
0
  FREE_AND_NULL(item->dst);
178
0
  FREE_AND_NULL(item->raw);
179
0
  item->force = 0;
180
0
  item->pattern = 0;
181
0
  item->matching = 0;
182
0
  item->exact_sha1 = 0;
183
0
}
184
185
void refspec_init_fetch(struct refspec *rs)
186
0
{
187
0
  struct refspec blank = REFSPEC_INIT_FETCH;
188
0
  memcpy(rs, &blank, sizeof(*rs));
189
0
}
190
191
void refspec_init_push(struct refspec *rs)
192
0
{
193
0
  struct refspec blank = REFSPEC_INIT_PUSH;
194
0
  memcpy(rs, &blank, sizeof(*rs));
195
0
}
196
197
void refspec_append(struct refspec *rs, const char *refspec)
198
0
{
199
0
  struct refspec_item item;
200
0
  int ret;
201
202
0
  if (rs->fetch)
203
0
    ret = refspec_item_init_fetch(&item, refspec);
204
0
  else
205
0
    ret = refspec_item_init_push(&item, refspec);
206
0
  if (!ret)
207
0
    die(_("invalid refspec '%s'"), refspec);
208
209
0
  ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc);
210
0
  rs->items[rs->nr] = item;
211
212
0
  rs->nr++;
213
0
}
214
215
void refspec_appendf(struct refspec *rs, const char *fmt, ...)
216
0
{
217
0
  va_list ap;
218
0
  char *buf;
219
220
0
  va_start(ap, fmt);
221
0
  buf = xstrvfmt(fmt, ap);
222
0
  va_end(ap);
223
224
0
  refspec_append(rs, buf);
225
0
  free(buf);
226
0
}
227
228
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
229
0
{
230
0
  int i;
231
0
  for (i = 0; i < nr; i++)
232
0
    refspec_append(rs, refspecs[i]);
233
0
}
234
235
void refspec_clear(struct refspec *rs)
236
0
{
237
0
  int i;
238
239
0
  for (i = 0; i < rs->nr; i++)
240
0
    refspec_item_clear(&rs->items[i]);
241
242
0
  FREE_AND_NULL(rs->items);
243
0
  rs->alloc = 0;
244
0
  rs->nr = 0;
245
246
0
  rs->fetch = 0;
247
0
}
248
249
int valid_fetch_refspec(const char *fetch_refspec_str)
250
0
{
251
0
  struct refspec_item refspec;
252
0
  int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str);
253
0
  refspec_item_clear(&refspec);
254
0
  return ret;
255
0
}
256
257
void refspec_ref_prefixes(const struct refspec *rs,
258
        struct strvec *ref_prefixes)
259
0
{
260
0
  int i;
261
0
  for (i = 0; i < rs->nr; i++) {
262
0
    const struct refspec_item *item = &rs->items[i];
263
0
    const char *prefix = NULL;
264
265
0
    if (item->negative)
266
0
      continue;
267
268
0
    if (rs->fetch) {
269
0
      if (item->exact_sha1)
270
0
        continue;
271
0
      prefix = item->src;
272
0
    } else {
273
      /*
274
       * Pushes can have an explicit destination like
275
       * "foo:bar", or can implicitly use the src for both
276
       * ("foo" is the same as "foo:foo").
277
       */
278
0
      if (item->dst)
279
0
        prefix = item->dst;
280
0
      else if (item->src && !item->exact_sha1)
281
0
        prefix = item->src;
282
0
    }
283
284
0
    if (!prefix)
285
0
      continue;
286
287
0
    if (item->pattern) {
288
0
      const char *glob = strchr(prefix, '*');
289
0
      strvec_pushf(ref_prefixes, "%.*s",
290
0
             (int)(glob - prefix),
291
0
             prefix);
292
0
    } else {
293
0
      expand_ref_prefix(ref_prefixes, prefix);
294
0
    }
295
0
  }
296
0
}
297
298
int match_refname_with_pattern(const char *pattern, const char *refname,
299
           const char *replacement, char **result)
300
0
{
301
0
  const char *kstar = strchr(pattern, '*');
302
0
  size_t klen;
303
0
  size_t ksuffixlen;
304
0
  size_t namelen;
305
0
  int ret;
306
0
  if (!kstar)
307
0
    die(_("pattern '%s' has no '*'"), pattern);
308
0
  klen = kstar - pattern;
309
0
  ksuffixlen = strlen(kstar + 1);
310
0
  namelen = strlen(refname);
311
0
  ret = !strncmp(refname, pattern, klen) && namelen >= klen + ksuffixlen &&
312
0
    !memcmp(refname + namelen - ksuffixlen, kstar + 1, ksuffixlen);
313
0
  if (ret && replacement) {
314
0
    struct strbuf sb = STRBUF_INIT;
315
0
    const char *vstar = strchr(replacement, '*');
316
0
    if (!vstar)
317
0
      die(_("replacement '%s' has no '*'"), replacement);
318
0
    strbuf_add(&sb, replacement, vstar - replacement);
319
0
    strbuf_add(&sb, refname + klen, namelen - klen - ksuffixlen);
320
0
    strbuf_addstr(&sb, vstar + 1);
321
0
    *result = strbuf_detach(&sb, NULL);
322
0
  }
323
0
  return ret;
324
0
}
325
326
static int refspec_match(const struct refspec_item *refspec,
327
       const char *name)
328
0
{
329
0
  if (refspec->pattern)
330
0
    return match_refname_with_pattern(refspec->src, name, NULL, NULL);
331
332
0
  return !strcmp(refspec->src, name);
333
0
}
334
335
int refname_matches_negative_refspec_item(const char *refname, struct refspec *rs)
336
0
{
337
0
  int i;
338
339
0
  for (i = 0; i < rs->nr; i++) {
340
0
    if (rs->items[i].negative && refspec_match(&rs->items[i], refname))
341
0
      return 1;
342
0
  }
343
0
  return 0;
344
0
}
345
346
static int refspec_find_negative_match(struct refspec *rs, struct refspec_item *query)
347
0
{
348
0
  int i, matched_negative = 0;
349
0
  int find_src = !query->src;
350
0
  struct string_list reversed = STRING_LIST_INIT_DUP;
351
0
  const char *needle = find_src ? query->dst : query->src;
352
353
  /*
354
   * Check whether the queried ref matches any negative refpsec. If so,
355
   * then we should ultimately treat this as not matching the query at
356
   * all.
357
   *
358
   * Note that negative refspecs always match the source, but the query
359
   * item uses the destination. To handle this, we apply pattern
360
   * refspecs in reverse to figure out if the query source matches any
361
   * of the negative refspecs.
362
   *
363
   * The first loop finds and expands all positive refspecs
364
   * matched by the queried ref.
365
   *
366
   * The second loop checks if any of the results of the first loop
367
   * match any negative refspec.
368
   */
369
0
  for (i = 0; i < rs->nr; i++) {
370
0
    struct refspec_item *refspec = &rs->items[i];
371
0
    char *expn_name;
372
373
0
    if (refspec->negative)
374
0
      continue;
375
376
    /* Note the reversal of src and dst */
377
0
    if (refspec->pattern) {
378
0
      const char *key = refspec->dst ? refspec->dst : refspec->src;
379
0
      const char *value = refspec->src;
380
381
0
      if (match_refname_with_pattern(key, needle, value, &expn_name))
382
0
        string_list_append_nodup(&reversed, expn_name);
383
0
    } else if (refspec->matching) {
384
      /* For the special matching refspec, any query should match */
385
0
      string_list_append(&reversed, needle);
386
0
    } else if (!refspec->src) {
387
0
      BUG("refspec->src should not be null here");
388
0
    } else if (!strcmp(needle, refspec->src)) {
389
0
      string_list_append(&reversed, refspec->src);
390
0
    }
391
0
  }
392
393
0
  for (i = 0; !matched_negative && i < reversed.nr; i++) {
394
0
    if (refname_matches_negative_refspec_item(reversed.items[i].string, rs))
395
0
      matched_negative = 1;
396
0
  }
397
398
0
  string_list_clear(&reversed, 0);
399
400
0
  return matched_negative;
401
0
}
402
403
void refspec_find_all_matches(struct refspec *rs,
404
            struct refspec_item *query,
405
            struct string_list *results)
406
0
{
407
0
  int i;
408
0
  int find_src = !query->src;
409
410
0
  if (find_src && !query->dst)
411
0
    BUG("refspec_find_all_matches: need either src or dst");
412
413
0
  if (refspec_find_negative_match(rs, query))
414
0
    return;
415
416
0
  for (i = 0; i < rs->nr; i++) {
417
0
    struct refspec_item *refspec = &rs->items[i];
418
0
    const char *key = find_src ? refspec->dst : refspec->src;
419
0
    const char *value = find_src ? refspec->src : refspec->dst;
420
0
    const char *needle = find_src ? query->dst : query->src;
421
0
    char **result = find_src ? &query->src : &query->dst;
422
423
0
    if (!refspec->dst || refspec->negative)
424
0
      continue;
425
0
    if (refspec->pattern) {
426
0
      if (match_refname_with_pattern(key, needle, value, result))
427
0
        string_list_append_nodup(results, *result);
428
0
    } else if (!strcmp(needle, key)) {
429
0
      string_list_append(results, value);
430
0
    }
431
0
  }
432
0
}
433
434
int refspec_find_match(struct refspec *rs, struct refspec_item *query)
435
0
{
436
0
  int i;
437
0
  int find_src = !query->src;
438
0
  const char *needle = find_src ? query->dst : query->src;
439
0
  char **result = find_src ? &query->src : &query->dst;
440
441
0
  if (find_src && !query->dst)
442
0
    BUG("refspec_find_match: need either src or dst");
443
444
0
  if (refspec_find_negative_match(rs, query))
445
0
    return -1;
446
447
0
  for (i = 0; i < rs->nr; i++) {
448
0
    struct refspec_item *refspec = &rs->items[i];
449
0
    const char *key = find_src ? refspec->dst : refspec->src;
450
0
    const char *value = find_src ? refspec->src : refspec->dst;
451
452
0
    if (!refspec->dst || refspec->negative)
453
0
      continue;
454
0
    if (refspec->pattern) {
455
0
      if (match_refname_with_pattern(key, needle, value, result)) {
456
0
        query->force = refspec->force;
457
0
        return 0;
458
0
      }
459
0
    } else if (!strcmp(needle, key)) {
460
0
      *result = xstrdup(value);
461
0
      query->force = refspec->force;
462
0
      return 0;
463
0
    }
464
0
  }
465
0
  return -1;
466
0
}
467
468
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
469
0
{
470
0
  struct ref **tail;
471
472
0
  for (tail = &ref_map; *tail; ) {
473
0
    struct ref *ref = *tail;
474
475
0
    if (refname_matches_negative_refspec_item(ref->name, rs)) {
476
0
      *tail = ref->next;
477
0
      free(ref->peer_ref);
478
0
      free(ref);
479
0
    } else
480
0
      tail = &ref->next;
481
0
  }
482
483
0
  return ref_map;
484
0
}
485
486
char *apply_refspecs(struct refspec *rs, const char *name)
487
0
{
488
0
  struct refspec_item query;
489
490
0
  memset(&query, 0, sizeof(struct refspec_item));
491
0
  query.src = (char *)name;
492
493
0
  if (refspec_find_match(rs, &query))
494
0
    return NULL;
495
496
0
  return query.dst;
497
0
}