Skip to content

Commit 8e3ec76

Browse files
committed
Merge branch 'jk/refspecs-negative'
"git fetch" and "git push" support negative refspecs. * jk/refspecs-negative: refspec: add support for negative refspecs
2 parents f6b06b4 + c0192df commit 8e3ec76

File tree

7 files changed

+367
-13
lines changed

7 files changed

+367
-13
lines changed

Documentation/pull-fetch-param.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ The colon can be omitted when <dst> is empty. <src> is
3030
typically a ref, but it can also be a fully spelled hex object
3131
name.
3232
+
33+
A <refspec> may contain a `*` in its <src> to indicate a simple pattern
34+
match. Such a refspec functions like a glob that matches any ref with the
35+
same prefix. A pattern <refspec> must have a `*` in both the <src> and
36+
<dst>. It will map refs to the destination by replacing the `*` with the
37+
contents matched from the source.
38+
+
39+
If a refspec is prefixed by `^`, it will be interpreted as a negative
40+
refspec. Rather than specifying which refs to fetch or which local refs to
41+
update, such a refspec will instead specify refs to exclude. A ref will be
42+
considered to match if it matches at least one positive refspec, and does
43+
not match any negative refspec. Negative refspecs can be useful to restrict
44+
the scope of a pattern refspec so that it will not include specific refs.
45+
Negative refspecs can themselves be pattern refspecs. However, they may only
46+
contain a <src> and do not specify a <dst>. Fully spelled out hex object
47+
names are also not supported.
48+
+
3349
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
3450
it requests fetching everything up to the given tag.
3551
+

builtin/fetch.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,16 @@ static struct ref *get_ref_map(struct remote *remote,
539539
tail = &rm->next;
540540
}
541541

542+
/*
543+
* apply negative refspecs first, before we remove duplicates. This is
544+
* necessary as negative refspecs might remove an otherwise conflicting
545+
* duplicate.
546+
*/
547+
if (rs->nr)
548+
ref_map = apply_negative_refspecs(ref_map, rs);
549+
else
550+
ref_map = apply_negative_refspecs(ref_map, &remote->fetch);
551+
542552
ref_map = ref_remove_duplicates(ref_map);
543553

544554
for (rm = ref_map; rm; rm = rm->next) {

refspec.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ static struct refspec_item s_tag_refspec = {
88
1,
99
0,
1010
0,
11+
0,
1112
"refs/tags/*",
1213
"refs/tags/*"
1314
};
@@ -32,10 +33,17 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
3233
if (*lhs == '+') {
3334
item->force = 1;
3435
lhs++;
36+
} else if (*lhs == '^') {
37+
item->negative = 1;
38+
lhs++;
3539
}
3640

3741
rhs = strrchr(lhs, ':');
3842

43+
/* negative refspecs only have one side */
44+
if (item->negative && rhs)
45+
return 0;
46+
3947
/*
4048
* Before going on, special case ":" (or "+:") as a refspec
4149
* for pushing matching refs.
@@ -55,7 +63,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
5563

5664
llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
5765
if (1 <= llen && memchr(lhs, '*', llen)) {
58-
if ((rhs && !is_glob) || (!rhs && fetch))
66+
if ((rhs && !is_glob) || (!rhs && !item->negative && fetch))
5967
return 0;
6068
is_glob = 1;
6169
} else if (rhs && is_glob) {
@@ -66,6 +74,28 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
6674
item->src = xstrndup(lhs, llen);
6775
flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
6876

77+
if (item->negative) {
78+
struct object_id unused;
79+
80+
/*
81+
* Negative refspecs only have a LHS, which indicates a ref
82+
* (or pattern of refs) to exclude from other matches. This
83+
* can either be a simple ref, or a glob pattern. Exact sha1
84+
* match is not currently supported.
85+
*/
86+
if (!*item->src)
87+
return 0; /* negative refspecs must not be empty */
88+
else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
89+
return 0; /* negative refpsecs cannot be exact sha1 */
90+
else if (!check_refname_format(item->src, flags))
91+
; /* valid looking ref is ok */
92+
else
93+
return 0;
94+
95+
/* the other rules below do not apply to negative refspecs */
96+
return 1;
97+
}
98+
6999
if (fetch) {
70100
struct object_id unused;
71101

@@ -223,7 +253,7 @@ void refspec_ref_prefixes(const struct refspec *rs,
223253
const struct refspec_item *item = &rs->items[i];
224254
const char *prefix = NULL;
225255

226-
if (item->exact_sha1)
256+
if (item->exact_sha1 || item->negative)
227257
continue;
228258
if (rs->fetch == REFSPEC_FETCH)
229259
prefix = item->src;

refspec.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
extern const struct refspec_item *tag_refspec;
66
77
/**
8-
* A struct refspec_item holds the parsed interpretation of a refspec. If it will
9-
* force updates (starts with a '+'), force is true. If it is a pattern
10-
* (sides end with '*') pattern is true. src and dest are the two sides
11-
* (including '*' characters if present); if there is only one side, it is src,
12-
* and dst is NULL; if sides exist but are empty (i.e., the refspec either
13-
* starts or ends with ':'), the corresponding side is "".
8+
* A struct refspec_item holds the parsed interpretation of a refspec. If it
9+
* will force updates (starts with a '+'), force is true. If it is a pattern
10+
* (sides end with '*') pattern is true. If it is a negative refspec, (starts
11+
* with '^'), negative is true. src and dest are the two sides (including '*'
12+
* characters if present); if there is only one side, it is src, and dst is
13+
* NULL; if sides exist but are empty (i.e., the refspec either starts or ends
14+
* with ':'), the corresponding side is "".
1415
*
1516
* remote_find_tracking(), given a remote and a struct refspec_item with either src
1617
* or dst filled out, will fill out the other such that the result is in the
@@ -22,6 +23,7 @@ struct refspec_item {
2223
unsigned pattern : 1;
2324
unsigned matching : 1;
2425
unsigned exact_sha1 : 1;
26+
unsigned negative : 1;
2527

2628
char *src;
2729
char *dst;

remote.c

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,91 @@ static int match_name_with_pattern(const char *key, const char *name,
682682
return ret;
683683
}
684684

685+
static int refspec_match(const struct refspec_item *refspec,
686+
const char *name)
687+
{
688+
if (refspec->pattern)
689+
return match_name_with_pattern(refspec->src, name, NULL, NULL);
690+
691+
return !strcmp(refspec->src, name);
692+
}
693+
694+
static int omit_name_by_refspec(const char *name, struct refspec *rs)
695+
{
696+
int i;
697+
698+
for (i = 0; i < rs->nr; i++) {
699+
if (rs->items[i].negative && refspec_match(&rs->items[i], name))
700+
return 1;
701+
}
702+
return 0;
703+
}
704+
705+
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
706+
{
707+
struct ref **tail;
708+
709+
for (tail = &ref_map; *tail; ) {
710+
struct ref *ref = *tail;
711+
712+
if (omit_name_by_refspec(ref->name, rs)) {
713+
*tail = ref->next;
714+
free(ref->peer_ref);
715+
free(ref);
716+
} else
717+
tail = &ref->next;
718+
}
719+
720+
return ref_map;
721+
}
722+
723+
static int query_matches_negative_refspec(struct refspec *rs, struct refspec_item *query)
724+
{
725+
int i, matched_negative = 0;
726+
int find_src = !query->src;
727+
struct string_list reversed = STRING_LIST_INIT_NODUP;
728+
const char *needle = find_src ? query->dst : query->src;
729+
730+
/*
731+
* Check whether the queried ref matches any negative refpsec. If so,
732+
* then we should ultimately treat this as not matching the query at
733+
* all.
734+
*
735+
* Note that negative refspecs always match the source, but the query
736+
* item uses the destination. To handle this, we apply pattern
737+
* refspecs in reverse to figure out if the query source matches any
738+
* of the negative refspecs.
739+
*/
740+
for (i = 0; i < rs->nr; i++) {
741+
struct refspec_item *refspec = &rs->items[i];
742+
char *expn_name;
743+
744+
if (refspec->negative)
745+
continue;
746+
747+
/* Note the reversal of src and dst */
748+
if (refspec->pattern) {
749+
const char *key = refspec->dst ? refspec->dst : refspec->src;
750+
const char *value = refspec->src;
751+
752+
if (match_name_with_pattern(key, needle, value, &expn_name))
753+
string_list_append_nodup(&reversed, expn_name);
754+
} else {
755+
if (!strcmp(needle, refspec->src))
756+
string_list_append(&reversed, refspec->src);
757+
}
758+
}
759+
760+
for (i = 0; !matched_negative && i < reversed.nr; i++) {
761+
if (omit_name_by_refspec(reversed.items[i].string, rs))
762+
matched_negative = 1;
763+
}
764+
765+
string_list_clear(&reversed, 0);
766+
767+
return matched_negative;
768+
}
769+
685770
static void query_refspecs_multiple(struct refspec *rs,
686771
struct refspec_item *query,
687772
struct string_list *results)
@@ -692,14 +777,17 @@ static void query_refspecs_multiple(struct refspec *rs,
692777
if (find_src && !query->dst)
693778
BUG("query_refspecs_multiple: need either src or dst");
694779

780+
if (query_matches_negative_refspec(rs, query))
781+
return;
782+
695783
for (i = 0; i < rs->nr; i++) {
696784
struct refspec_item *refspec = &rs->items[i];
697785
const char *key = find_src ? refspec->dst : refspec->src;
698786
const char *value = find_src ? refspec->src : refspec->dst;
699787
const char *needle = find_src ? query->dst : query->src;
700788
char **result = find_src ? &query->src : &query->dst;
701789

702-
if (!refspec->dst)
790+
if (!refspec->dst || refspec->negative)
703791
continue;
704792
if (refspec->pattern) {
705793
if (match_name_with_pattern(key, needle, value, result))
@@ -720,12 +808,15 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
720808
if (find_src && !query->dst)
721809
BUG("query_refspecs: need either src or dst");
722810

811+
if (query_matches_negative_refspec(rs, query))
812+
return -1;
813+
723814
for (i = 0; i < rs->nr; i++) {
724815
struct refspec_item *refspec = &rs->items[i];
725816
const char *key = find_src ? refspec->dst : refspec->src;
726817
const char *value = find_src ? refspec->src : refspec->dst;
727818

728-
if (!refspec->dst)
819+
if (!refspec->dst || refspec->negative)
729820
continue;
730821
if (refspec->pattern) {
731822
if (match_name_with_pattern(key, needle, value, result)) {
@@ -1054,7 +1145,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
10541145
const char *dst_value = rs->dst;
10551146
char *dst_guess;
10561147

1057-
if (rs->pattern || rs->matching)
1148+
if (rs->pattern || rs->matching || rs->negative)
10581149
return 0;
10591150

10601151
matched_src = matched_dst = NULL;
@@ -1130,6 +1221,10 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
11301221
int matching_refs = -1;
11311222
for (i = 0; i < rs->nr; i++) {
11321223
const struct refspec_item *item = &rs->items[i];
1224+
1225+
if (item->negative)
1226+
continue;
1227+
11331228
if (item->matching &&
11341229
(matching_refs == -1 || item->force)) {
11351230
matching_refs = i;
@@ -1335,7 +1430,7 @@ int check_push_refs(struct ref *src, struct refspec *rs)
13351430
for (i = 0; i < rs->nr; i++) {
13361431
struct refspec_item *item = &rs->items[i];
13371432

1338-
if (item->pattern || item->matching)
1433+
if (item->pattern || item->matching || item->negative)
13391434
continue;
13401435

13411436
ret |= match_explicit_lhs(src, item, NULL, NULL);
@@ -1437,6 +1532,8 @@ int match_push_refs(struct ref *src, struct ref **dst,
14371532
string_list_clear(&src_ref_index, 0);
14381533
}
14391534

1535+
*dst = apply_negative_refspecs(*dst, rs);
1536+
14401537
if (errs)
14411538
return -1;
14421539
return 0;
@@ -1806,6 +1903,9 @@ int get_fetch_map(const struct ref *remote_refs,
18061903
{
18071904
struct ref *ref_map, **rmp;
18081905

1906+
if (refspec->negative)
1907+
return 0;
1908+
18091909
if (refspec->pattern) {
18101910
ref_map = get_expanded_map(remote_refs, refspec);
18111911
} else {

remote.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ int resolve_remote_symref(struct ref *ref, struct ref *list);
202202
*/
203203
struct ref *ref_remove_duplicates(struct ref *ref_map);
204204

205+
/*
206+
* Remove all entries in the input list which match any negative refspec in
207+
* the refspec list.
208+
*/
209+
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs);
210+
205211
int query_refspecs(struct refspec *rs, struct refspec_item *query);
206212
char *apply_refspecs(struct refspec *rs, const char *name);
207213

@@ -214,7 +220,8 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
214220
/*
215221
* Given a list of the remote refs and the specification of things to
216222
* fetch, makes a (separate) list of the refs to fetch and the local
217-
* refs to store into.
223+
* refs to store into. Note that negative refspecs are ignored here, and
224+
* should be handled separately.
218225
*
219226
* *tail is the pointer to the tail pointer of the list of results
220227
* beforehand, and will be set to the tail pointer of the list of

0 commit comments

Comments
 (0)