Coverage Report

Created: 2026-11-12 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/refspec.c
Line
Count
Source
1
#define DISABLE_SIGN_COMPARE_WARNINGS
2
3
#include "git-compat-util.h"
4
#include "gettext.h"
5
#include "hash.h"
6
#include "hex.h"
7
#include "string-list.h"
8
#include "strvec.h"
9
#include "refs.h"
10
#include "refspec.h"
11
#include "remote.h"
12
#include "strbuf.h"
13
14
/*
15
 * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
16
 * Returns 1 if successful and 0 if the refspec is invalid.
17
 */
18
static int parse_refspec(struct refspec_item *item, const char *refspec,
19
       const struct git_hash_algo *algo, 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 == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo))
88
0
      return 0; /* negative refspecs 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 == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo))
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
           const struct git_hash_algo *algo, int fetch)
158
0
{
159
0
  memset(item, 0, sizeof(*item));
160
0
  item->raw = xstrdup(refspec);
161
0
  return parse_refspec(item, refspec, algo, fetch);
162
0
}
163
164
int refspec_item_init_fetch(struct refspec_item *item, const char *refspec,
165
          const struct git_hash_algo *algo)
166
0
{
167
0
  return refspec_item_init(item, refspec, algo, 1);
168
0
}
169
170
int refspec_item_init_push(struct refspec_item *item, const char *refspec,
171
         const struct git_hash_algo *algo)
172
0
{
173
0
  return refspec_item_init(item, refspec, algo, 0);
174
0
}
175
176
void refspec_item_clear(struct refspec_item *item)
177
0
{
178
0
  FREE_AND_NULL(item->src);
179
0
  FREE_AND_NULL(item->dst);
180
0
  FREE_AND_NULL(item->raw);
181
0
  item->force = 0;
182
0
  item->pattern = 0;
183
0
  item->matching = 0;
184
0
  item->exact_sha1 = 0;
185
0
}
186
187
void refspec_init_fetch(struct refspec *rs, const struct git_hash_algo *algo)
188
0
{
189
0
  struct refspec blank = REFSPEC_INIT_FETCH(algo);
190
0
  memcpy(rs, &blank, sizeof(*rs));
191
0
}
192
193
void refspec_init_push(struct refspec *rs, const struct git_hash_algo *algo)
194
0
{
195
0
  struct refspec blank = REFSPEC_INIT_PUSH(algo);
196
0
  memcpy(rs, &blank, sizeof(*rs));
197
0
}
198
199
void refspec_append(struct refspec *rs, const char *refspec)
200
0
{
201
0
  struct refspec_item item;
202
0
  int ret;
203
204
0
  if (rs->fetch)
205
0
    ret = refspec_item_init_fetch(&item, refspec, rs->hash_algo);
206
0
  else
207
0
    ret = refspec_item_init_push(&item, refspec, rs->hash_algo);
208
0
  if (!ret)
209
0
    die(_("invalid refspec '%s'"), refspec);
210
211
0
  ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc);
212
0
  rs->items[rs->nr] = item;
213
214
0
  rs->nr++;
215
0
}
216
217
void refspec_appendf(struct refspec *rs, const char *fmt, ...)
218
0
{
219
0
  va_list ap;
220
0
  char *buf;
221
222
0
  va_start(ap, fmt);
223
0
  buf = xstrvfmt(fmt, ap);
224
0
  va_end(ap);
225
226
0
  refspec_append(rs, buf);
227
0
  free(buf);
228
0
}
229
230
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
231
0
{
232
0
  int i;
233
0
  for (i = 0; i < nr; i++)
234
0
    refspec_append(rs, refspecs[i]);
235
0
}
236
237
void refspec_clear(struct refspec *rs)
238
0
{
239
0
  int i;
240
241
0
  for (i = 0; i < rs->nr; i++)
242
0
    refspec_item_clear(&rs->items[i]);
243
244
0
  FREE_AND_NULL(rs->items);
245
0
  rs->alloc = 0;
246
0
  rs->nr = 0;
247
248
0
  rs->fetch = 0;
249
0
}
250
251
int valid_fetch_refspec(const char *fetch_refspec_str,
252
      const struct git_hash_algo *algo)
253
0
{
254
0
  struct refspec_item refspec;
255
0
  int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str, algo);
256
0
  refspec_item_clear(&refspec);
257
0
  return ret;
258
0
}
259
260
void refspec_ref_prefixes(const struct refspec *rs,
261
        struct strvec *ref_prefixes)
262
0
{
263
0
  int i;
264
0
  for (i = 0; i < rs->nr; i++) {
265
0
    const struct refspec_item *item = &rs->items[i];
266
0
    const char *prefix = NULL;
267
268
0
    if (item->negative)
269
0
      continue;
270
271
0
    if (rs->fetch) {
272
0
      if (item->exact_sha1)
273
0
        continue;
274
0
      prefix = item->src;
275
0
    } else {
276
      /*
277
       * Pushes can have an explicit destination like
278
       * "foo:bar", or can implicitly use the src for both
279
       * ("foo" is the same as "foo:foo").
280
       */
281
0
      if (item->dst)
282
0
        prefix = item->dst;
283
0
      else if (item->src && !item->exact_sha1)
284
0
        prefix = item->src;
285
0
    }
286
287
0
    if (!prefix)
288
0
      continue;
289
290
0
    if (item->pattern) {
291
0
      const char *glob = strchr(prefix, '*');
292
0
      strvec_pushf(ref_prefixes, "%.*s",
293
0
             (int)(glob - prefix),
294
0
             prefix);
295
0
    } else {
296
0
      expand_ref_prefix(ref_prefixes, prefix);
297
0
    }
298
0
  }
299
0
}
300
301
int match_refname_with_pattern(const char *pattern, const char *refname,
302
           const char *replacement, char **result)
303
0
{
304
0
  const char *kstar = strchr(pattern, '*');
305
0
  size_t klen;
306
0
  size_t ksuffixlen;
307
0
  size_t namelen;
308
0
  int ret;
309
0
  if (!kstar)
310
0
    die(_("pattern '%s' has no '*'"), pattern);
311
0
  klen = kstar - pattern;
312
0
  ksuffixlen = strlen(kstar + 1);
313
0
  namelen = strlen(refname);
314
0
  ret = !strncmp(refname, pattern, klen) && namelen >= klen + ksuffixlen &&
315
0
    !memcmp(refname + namelen - ksuffixlen, kstar + 1, ksuffixlen);
316
0
  if (ret && replacement) {
317
0
    struct strbuf sb = STRBUF_INIT;
318
0
    const char *vstar = strchr(replacement, '*');
319
0
    if (!vstar)
320
0
      die(_("replacement '%s' has no '*'"), replacement);
321
0
    strbuf_add(&sb, replacement, vstar - replacement);
322
0
    strbuf_add(&sb, refname + klen, namelen - klen - ksuffixlen);
323
0
    strbuf_addstr(&sb, vstar + 1);
324
0
    *result = strbuf_detach(&sb, NULL);
325
0
  }
326
0
  return ret;
327
0
}
328
329
static int refspec_match(const struct refspec_item *refspec,
330
       const char *name)
331
0
{
332
0
  if (refspec->pattern)
333
0
    return match_refname_with_pattern(refspec->src, name, NULL, NULL);
334
335
0
  return !strcmp(refspec->src, name);
336
0
}
337
338
int refname_matches_negative_refspec_item(const char *refname, struct refspec *rs)
339
0
{
340
0
  int i;
341
342
0
  for (i = 0; i < rs->nr; i++) {
343
0
    if (rs->items[i].negative && refspec_match(&rs->items[i], refname))
344
0
      return 1;
345
0
  }
346
0
  return 0;
347
0
}
348
349
static int refspec_find_negative_match(struct refspec *rs, struct refspec_item *query)
350
0
{
351
0
  int i, matched_negative = 0;
352
0
  int find_src = !query->src;
353
0
  struct string_list reversed = STRING_LIST_INIT_DUP;
354
0
  const char *needle = find_src ? query->dst : query->src;
355
356
  /*
357
   * Check whether the queried ref matches any negative refpsec. If so,
358
   * then we should ultimately treat this as not matching the query at
359
   * all.
360
   *
361
   * Note that negative refspecs always match the source, but the query
362
   * item uses the destination. To handle this, we apply pattern
363
   * refspecs in reverse to figure out if the query source matches any
364
   * of the negative refspecs.
365
   *
366
   * The first loop finds and expands all positive refspecs
367
   * matched by the queried ref.
368
   *
369
   * The second loop checks if any of the results of the first loop
370
   * match any negative refspec.
371
   */
372
0
  for (i = 0; i < rs->nr; i++) {
373
0
    struct refspec_item *refspec = &rs->items[i];
374
0
    char *expn_name;
375
376
0
    if (refspec->negative)
377
0
      continue;
378
379
    /* Note the reversal of src and dst */
380
0
    if (refspec->pattern) {
381
0
      const char *key = refspec->dst ? refspec->dst : refspec->src;
382
0
      const char *value = refspec->src;
383
384
0
      if (match_refname_with_pattern(key, needle, value, &expn_name))
385
0
        string_list_append_nodup(&reversed, expn_name);
386
0
    } else if (refspec->matching) {
387
      /* For the special matching refspec, any query should match */
388
0
      string_list_append(&reversed, needle);
389
0
    } else if (!refspec->src) {
390
0
      BUG("refspec->src should not be null here");
391
0
    } else if (!strcmp(needle, refspec->src)) {
392
0
      string_list_append(&reversed, refspec->src);
393
0
    }
394
0
  }
395
396
0
  for (i = 0; !matched_negative && i < reversed.nr; i++) {
397
0
    if (refname_matches_negative_refspec_item(reversed.items[i].string, rs))
398
0
      matched_negative = 1;
399
0
  }
400
401
0
  string_list_clear(&reversed, 0);
402
403
0
  return matched_negative;
404
0
}
405
406
void refspec_find_all_matches(struct refspec *rs,
407
            struct refspec_item *query,
408
            struct string_list *results)
409
0
{
410
0
  int i;
411
0
  int find_src = !query->src;
412
413
0
  if (find_src && !query->dst)
414
0
    BUG("refspec_find_all_matches: need either src or dst");
415
416
0
  if (refspec_find_negative_match(rs, query))
417
0
    return;
418
419
0
  for (i = 0; i < rs->nr; i++) {
420
0
    struct refspec_item *refspec = &rs->items[i];
421
0
    const char *key = find_src ? refspec->dst : refspec->src;
422
0
    const char *value = find_src ? refspec->src : refspec->dst;
423
0
    const char *needle = find_src ? query->dst : query->src;
424
0
    char **result = find_src ? &query->src : &query->dst;
425
426
0
    if (!refspec->dst || refspec->negative)
427
0
      continue;
428
0
    if (refspec->pattern) {
429
0
      if (match_refname_with_pattern(key, needle, value, result))
430
0
        string_list_append_nodup(results, *result);
431
0
    } else if (!strcmp(needle, key)) {
432
0
      string_list_append(results, value);
433
0
    }
434
0
  }
435
0
}
436
437
int refspec_find_match(struct refspec *rs, struct refspec_item *query)
438
0
{
439
0
  int i;
440
0
  int find_src = !query->src;
441
0
  const char *needle = find_src ? query->dst : query->src;
442
0
  char **result = find_src ? &query->src : &query->dst;
443
444
0
  if (find_src && !query->dst)
445
0
    BUG("refspec_find_match: need either src or dst");
446
447
0
  if (refspec_find_negative_match(rs, query))
448
0
    return -1;
449
450
0
  for (i = 0; i < rs->nr; i++) {
451
0
    struct refspec_item *refspec = &rs->items[i];
452
0
    const char *key = find_src ? refspec->dst : refspec->src;
453
0
    const char *value = find_src ? refspec->src : refspec->dst;
454
455
0
    if (!refspec->dst || refspec->negative)
456
0
      continue;
457
0
    if (refspec->pattern) {
458
0
      if (match_refname_with_pattern(key, needle, value, result)) {
459
0
        query->force = refspec->force;
460
0
        return 0;
461
0
      }
462
0
    } else if (!strcmp(needle, key)) {
463
0
      *result = xstrdup(value);
464
0
      query->force = refspec->force;
465
0
      return 0;
466
0
    }
467
0
  }
468
0
  return -1;
469
0
}
470
471
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
472
0
{
473
0
  struct ref **tail;
474
475
0
  for (tail = &ref_map; *tail; ) {
476
0
    struct ref *ref = *tail;
477
478
0
    if (refname_matches_negative_refspec_item(ref->name, rs)) {
479
0
      *tail = ref->next;
480
0
      free(ref->peer_ref);
481
0
      free(ref);
482
0
    } else
483
0
      tail = &ref->next;
484
0
  }
485
486
0
  return ref_map;
487
0
}
488
489
char *apply_refspecs(struct refspec *rs, const char *name)
490
0
{
491
0
  struct refspec_item query;
492
493
0
  memset(&query, 0, sizeof(struct refspec_item));
494
0
  query.src = (char *)name;
495
496
0
  if (refspec_find_match(rs, &query))
497
0
    return NULL;
498
499
0
  return query.dst;
500
0
}