Merge branch 'jk/refspecs-negative'
"git fetch" and "git push" support negative refspecs. * jk/refspecs-negative: refspec: add support for negative refspecs
This commit is contained in:
@ -30,6 +30,22 @@ The colon can be omitted when <dst> is empty. <src> is
|
|||||||
typically a ref, but it can also be a fully spelled hex object
|
typically a ref, but it can also be a fully spelled hex object
|
||||||
name.
|
name.
|
||||||
+
|
+
|
||||||
|
A <refspec> may contain a `*` in its <src> to indicate a simple pattern
|
||||||
|
match. Such a refspec functions like a glob that matches any ref with the
|
||||||
|
same prefix. A pattern <refspec> must have a `*` in both the <src> and
|
||||||
|
<dst>. It will map refs to the destination by replacing the `*` with the
|
||||||
|
contents matched from the source.
|
||||||
|
+
|
||||||
|
If a refspec is prefixed by `^`, it will be interpreted as a negative
|
||||||
|
refspec. Rather than specifying which refs to fetch or which local refs to
|
||||||
|
update, such a refspec will instead specify refs to exclude. A ref will be
|
||||||
|
considered to match if it matches at least one positive refspec, and does
|
||||||
|
not match any negative refspec. Negative refspecs can be useful to restrict
|
||||||
|
the scope of a pattern refspec so that it will not include specific refs.
|
||||||
|
Negative refspecs can themselves be pattern refspecs. However, they may only
|
||||||
|
contain a <src> and do not specify a <dst>. Fully spelled out hex object
|
||||||
|
names are also not supported.
|
||||||
|
+
|
||||||
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
|
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
|
||||||
it requests fetching everything up to the given tag.
|
it requests fetching everything up to the given tag.
|
||||||
+
|
+
|
||||||
|
@ -539,6 +539,16 @@ static struct ref *get_ref_map(struct remote *remote,
|
|||||||
tail = &rm->next;
|
tail = &rm->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* apply negative refspecs first, before we remove duplicates. This is
|
||||||
|
* necessary as negative refspecs might remove an otherwise conflicting
|
||||||
|
* duplicate.
|
||||||
|
*/
|
||||||
|
if (rs->nr)
|
||||||
|
ref_map = apply_negative_refspecs(ref_map, rs);
|
||||||
|
else
|
||||||
|
ref_map = apply_negative_refspecs(ref_map, &remote->fetch);
|
||||||
|
|
||||||
ref_map = ref_remove_duplicates(ref_map);
|
ref_map = ref_remove_duplicates(ref_map);
|
||||||
|
|
||||||
for (rm = ref_map; rm; rm = rm->next) {
|
for (rm = ref_map; rm; rm = rm->next) {
|
||||||
|
34
refspec.c
34
refspec.c
@ -8,6 +8,7 @@ static struct refspec_item s_tag_refspec = {
|
|||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
"refs/tags/*",
|
"refs/tags/*",
|
||||||
"refs/tags/*"
|
"refs/tags/*"
|
||||||
};
|
};
|
||||||
@ -32,10 +33,17 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
|
|||||||
if (*lhs == '+') {
|
if (*lhs == '+') {
|
||||||
item->force = 1;
|
item->force = 1;
|
||||||
lhs++;
|
lhs++;
|
||||||
|
} else if (*lhs == '^') {
|
||||||
|
item->negative = 1;
|
||||||
|
lhs++;
|
||||||
}
|
}
|
||||||
|
|
||||||
rhs = strrchr(lhs, ':');
|
rhs = strrchr(lhs, ':');
|
||||||
|
|
||||||
|
/* negative refspecs only have one side */
|
||||||
|
if (item->negative && rhs)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Before going on, special case ":" (or "+:") as a refspec
|
* Before going on, special case ":" (or "+:") as a refspec
|
||||||
* for pushing matching refs.
|
* for pushing matching refs.
|
||||||
@ -55,7 +63,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
|
|||||||
|
|
||||||
llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
|
llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
|
||||||
if (1 <= llen && memchr(lhs, '*', llen)) {
|
if (1 <= llen && memchr(lhs, '*', llen)) {
|
||||||
if ((rhs && !is_glob) || (!rhs && fetch))
|
if ((rhs && !is_glob) || (!rhs && !item->negative && fetch))
|
||||||
return 0;
|
return 0;
|
||||||
is_glob = 1;
|
is_glob = 1;
|
||||||
} else if (rhs && is_glob) {
|
} else if (rhs && is_glob) {
|
||||||
@ -66,6 +74,28 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
|
|||||||
item->src = xstrndup(lhs, llen);
|
item->src = xstrndup(lhs, llen);
|
||||||
flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
|
flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
|
||||||
|
|
||||||
|
if (item->negative) {
|
||||||
|
struct object_id unused;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Negative refspecs only have a LHS, which indicates a ref
|
||||||
|
* (or pattern of refs) to exclude from other matches. This
|
||||||
|
* can either be a simple ref, or a glob pattern. Exact sha1
|
||||||
|
* match is not currently supported.
|
||||||
|
*/
|
||||||
|
if (!*item->src)
|
||||||
|
return 0; /* negative refspecs must not be empty */
|
||||||
|
else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
|
||||||
|
return 0; /* negative refpsecs cannot be exact sha1 */
|
||||||
|
else if (!check_refname_format(item->src, flags))
|
||||||
|
; /* valid looking ref is ok */
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* the other rules below do not apply to negative refspecs */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (fetch) {
|
if (fetch) {
|
||||||
struct object_id unused;
|
struct object_id unused;
|
||||||
|
|
||||||
@ -223,7 +253,7 @@ void refspec_ref_prefixes(const struct refspec *rs,
|
|||||||
const struct refspec_item *item = &rs->items[i];
|
const struct refspec_item *item = &rs->items[i];
|
||||||
const char *prefix = NULL;
|
const char *prefix = NULL;
|
||||||
|
|
||||||
if (item->exact_sha1)
|
if (item->exact_sha1 || item->negative)
|
||||||
continue;
|
continue;
|
||||||
if (rs->fetch == REFSPEC_FETCH)
|
if (rs->fetch == REFSPEC_FETCH)
|
||||||
prefix = item->src;
|
prefix = item->src;
|
||||||
|
14
refspec.h
14
refspec.h
@ -5,12 +5,13 @@
|
|||||||
extern const struct refspec_item *tag_refspec;
|
extern const struct refspec_item *tag_refspec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A struct refspec_item holds the parsed interpretation of a refspec. If it will
|
* A struct refspec_item holds the parsed interpretation of a refspec. If it
|
||||||
* force updates (starts with a '+'), force is true. If it is a pattern
|
* will force updates (starts with a '+'), force is true. If it is a pattern
|
||||||
* (sides end with '*') pattern is true. src and dest are the two sides
|
* (sides end with '*') pattern is true. If it is a negative refspec, (starts
|
||||||
* (including '*' characters if present); if there is only one side, it is src,
|
* with '^'), negative is true. src and dest are the two sides (including '*'
|
||||||
* and dst is NULL; if sides exist but are empty (i.e., the refspec either
|
* characters if present); if there is only one side, it is src, and dst is
|
||||||
* starts or ends with ':'), the corresponding side is "".
|
* NULL; if sides exist but are empty (i.e., the refspec either starts or ends
|
||||||
|
* with ':'), the corresponding side is "".
|
||||||
*
|
*
|
||||||
* remote_find_tracking(), given a remote and a struct refspec_item with either src
|
* remote_find_tracking(), given a remote and a struct refspec_item with either src
|
||||||
* or dst filled out, will fill out the other such that the result is in the
|
* or dst filled out, will fill out the other such that the result is in the
|
||||||
@ -22,6 +23,7 @@ struct refspec_item {
|
|||||||
unsigned pattern : 1;
|
unsigned pattern : 1;
|
||||||
unsigned matching : 1;
|
unsigned matching : 1;
|
||||||
unsigned exact_sha1 : 1;
|
unsigned exact_sha1 : 1;
|
||||||
|
unsigned negative : 1;
|
||||||
|
|
||||||
char *src;
|
char *src;
|
||||||
char *dst;
|
char *dst;
|
||||||
|
108
remote.c
108
remote.c
@ -682,6 +682,91 @@ static int match_name_with_pattern(const char *key, const char *name,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int refspec_match(const struct refspec_item *refspec,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
if (refspec->pattern)
|
||||||
|
return match_name_with_pattern(refspec->src, name, NULL, NULL);
|
||||||
|
|
||||||
|
return !strcmp(refspec->src, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int omit_name_by_refspec(const char *name, struct refspec *rs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < rs->nr; i++) {
|
||||||
|
if (rs->items[i].negative && refspec_match(&rs->items[i], name))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
|
||||||
|
{
|
||||||
|
struct ref **tail;
|
||||||
|
|
||||||
|
for (tail = &ref_map; *tail; ) {
|
||||||
|
struct ref *ref = *tail;
|
||||||
|
|
||||||
|
if (omit_name_by_refspec(ref->name, rs)) {
|
||||||
|
*tail = ref->next;
|
||||||
|
free(ref->peer_ref);
|
||||||
|
free(ref);
|
||||||
|
} else
|
||||||
|
tail = &ref->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int query_matches_negative_refspec(struct refspec *rs, struct refspec_item *query)
|
||||||
|
{
|
||||||
|
int i, matched_negative = 0;
|
||||||
|
int find_src = !query->src;
|
||||||
|
struct string_list reversed = STRING_LIST_INIT_NODUP;
|
||||||
|
const char *needle = find_src ? query->dst : query->src;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether the queried ref matches any negative refpsec. If so,
|
||||||
|
* then we should ultimately treat this as not matching the query at
|
||||||
|
* all.
|
||||||
|
*
|
||||||
|
* Note that negative refspecs always match the source, but the query
|
||||||
|
* item uses the destination. To handle this, we apply pattern
|
||||||
|
* refspecs in reverse to figure out if the query source matches any
|
||||||
|
* of the negative refspecs.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < rs->nr; i++) {
|
||||||
|
struct refspec_item *refspec = &rs->items[i];
|
||||||
|
char *expn_name;
|
||||||
|
|
||||||
|
if (refspec->negative)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Note the reversal of src and dst */
|
||||||
|
if (refspec->pattern) {
|
||||||
|
const char *key = refspec->dst ? refspec->dst : refspec->src;
|
||||||
|
const char *value = refspec->src;
|
||||||
|
|
||||||
|
if (match_name_with_pattern(key, needle, value, &expn_name))
|
||||||
|
string_list_append_nodup(&reversed, expn_name);
|
||||||
|
} else {
|
||||||
|
if (!strcmp(needle, refspec->src))
|
||||||
|
string_list_append(&reversed, refspec->src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; !matched_negative && i < reversed.nr; i++) {
|
||||||
|
if (omit_name_by_refspec(reversed.items[i].string, rs))
|
||||||
|
matched_negative = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string_list_clear(&reversed, 0);
|
||||||
|
|
||||||
|
return matched_negative;
|
||||||
|
}
|
||||||
|
|
||||||
static void query_refspecs_multiple(struct refspec *rs,
|
static void query_refspecs_multiple(struct refspec *rs,
|
||||||
struct refspec_item *query,
|
struct refspec_item *query,
|
||||||
struct string_list *results)
|
struct string_list *results)
|
||||||
@ -692,6 +777,9 @@ static void query_refspecs_multiple(struct refspec *rs,
|
|||||||
if (find_src && !query->dst)
|
if (find_src && !query->dst)
|
||||||
BUG("query_refspecs_multiple: need either src or dst");
|
BUG("query_refspecs_multiple: need either src or dst");
|
||||||
|
|
||||||
|
if (query_matches_negative_refspec(rs, query))
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 0; i < rs->nr; i++) {
|
for (i = 0; i < rs->nr; i++) {
|
||||||
struct refspec_item *refspec = &rs->items[i];
|
struct refspec_item *refspec = &rs->items[i];
|
||||||
const char *key = find_src ? refspec->dst : refspec->src;
|
const char *key = find_src ? refspec->dst : refspec->src;
|
||||||
@ -699,7 +787,7 @@ static void query_refspecs_multiple(struct refspec *rs,
|
|||||||
const char *needle = find_src ? query->dst : query->src;
|
const char *needle = find_src ? query->dst : query->src;
|
||||||
char **result = find_src ? &query->src : &query->dst;
|
char **result = find_src ? &query->src : &query->dst;
|
||||||
|
|
||||||
if (!refspec->dst)
|
if (!refspec->dst || refspec->negative)
|
||||||
continue;
|
continue;
|
||||||
if (refspec->pattern) {
|
if (refspec->pattern) {
|
||||||
if (match_name_with_pattern(key, needle, value, result))
|
if (match_name_with_pattern(key, needle, value, result))
|
||||||
@ -720,12 +808,15 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
|
|||||||
if (find_src && !query->dst)
|
if (find_src && !query->dst)
|
||||||
BUG("query_refspecs: need either src or dst");
|
BUG("query_refspecs: need either src or dst");
|
||||||
|
|
||||||
|
if (query_matches_negative_refspec(rs, query))
|
||||||
|
return -1;
|
||||||
|
|
||||||
for (i = 0; i < rs->nr; i++) {
|
for (i = 0; i < rs->nr; i++) {
|
||||||
struct refspec_item *refspec = &rs->items[i];
|
struct refspec_item *refspec = &rs->items[i];
|
||||||
const char *key = find_src ? refspec->dst : refspec->src;
|
const char *key = find_src ? refspec->dst : refspec->src;
|
||||||
const char *value = find_src ? refspec->src : refspec->dst;
|
const char *value = find_src ? refspec->src : refspec->dst;
|
||||||
|
|
||||||
if (!refspec->dst)
|
if (!refspec->dst || refspec->negative)
|
||||||
continue;
|
continue;
|
||||||
if (refspec->pattern) {
|
if (refspec->pattern) {
|
||||||
if (match_name_with_pattern(key, needle, value, result)) {
|
if (match_name_with_pattern(key, needle, value, result)) {
|
||||||
@ -1054,7 +1145,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
|
|||||||
const char *dst_value = rs->dst;
|
const char *dst_value = rs->dst;
|
||||||
char *dst_guess;
|
char *dst_guess;
|
||||||
|
|
||||||
if (rs->pattern || rs->matching)
|
if (rs->pattern || rs->matching || rs->negative)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
matched_src = matched_dst = NULL;
|
matched_src = matched_dst = NULL;
|
||||||
@ -1130,6 +1221,10 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
|
|||||||
int matching_refs = -1;
|
int matching_refs = -1;
|
||||||
for (i = 0; i < rs->nr; i++) {
|
for (i = 0; i < rs->nr; i++) {
|
||||||
const struct refspec_item *item = &rs->items[i];
|
const struct refspec_item *item = &rs->items[i];
|
||||||
|
|
||||||
|
if (item->negative)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (item->matching &&
|
if (item->matching &&
|
||||||
(matching_refs == -1 || item->force)) {
|
(matching_refs == -1 || item->force)) {
|
||||||
matching_refs = i;
|
matching_refs = i;
|
||||||
@ -1335,7 +1430,7 @@ int check_push_refs(struct ref *src, struct refspec *rs)
|
|||||||
for (i = 0; i < rs->nr; i++) {
|
for (i = 0; i < rs->nr; i++) {
|
||||||
struct refspec_item *item = &rs->items[i];
|
struct refspec_item *item = &rs->items[i];
|
||||||
|
|
||||||
if (item->pattern || item->matching)
|
if (item->pattern || item->matching || item->negative)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ret |= match_explicit_lhs(src, item, NULL, NULL);
|
ret |= match_explicit_lhs(src, item, NULL, NULL);
|
||||||
@ -1437,6 +1532,8 @@ int match_push_refs(struct ref *src, struct ref **dst,
|
|||||||
string_list_clear(&src_ref_index, 0);
|
string_list_clear(&src_ref_index, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*dst = apply_negative_refspecs(*dst, rs);
|
||||||
|
|
||||||
if (errs)
|
if (errs)
|
||||||
return -1;
|
return -1;
|
||||||
return 0;
|
return 0;
|
||||||
@ -1806,6 +1903,9 @@ int get_fetch_map(const struct ref *remote_refs,
|
|||||||
{
|
{
|
||||||
struct ref *ref_map, **rmp;
|
struct ref *ref_map, **rmp;
|
||||||
|
|
||||||
|
if (refspec->negative)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (refspec->pattern) {
|
if (refspec->pattern) {
|
||||||
ref_map = get_expanded_map(remote_refs, refspec);
|
ref_map = get_expanded_map(remote_refs, refspec);
|
||||||
} else {
|
} else {
|
||||||
|
9
remote.h
9
remote.h
@ -202,6 +202,12 @@ int resolve_remote_symref(struct ref *ref, struct ref *list);
|
|||||||
*/
|
*/
|
||||||
struct ref *ref_remove_duplicates(struct ref *ref_map);
|
struct ref *ref_remove_duplicates(struct ref *ref_map);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove all entries in the input list which match any negative refspec in
|
||||||
|
* the refspec list.
|
||||||
|
*/
|
||||||
|
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs);
|
||||||
|
|
||||||
int query_refspecs(struct refspec *rs, struct refspec_item *query);
|
int query_refspecs(struct refspec *rs, struct refspec_item *query);
|
||||||
char *apply_refspecs(struct refspec *rs, const char *name);
|
char *apply_refspecs(struct refspec *rs, const char *name);
|
||||||
|
|
||||||
@ -214,7 +220,8 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
|||||||
/*
|
/*
|
||||||
* Given a list of the remote refs and the specification of things to
|
* Given a list of the remote refs and the specification of things to
|
||||||
* fetch, makes a (separate) list of the refs to fetch and the local
|
* fetch, makes a (separate) list of the refs to fetch and the local
|
||||||
* refs to store into.
|
* refs to store into. Note that negative refspecs are ignored here, and
|
||||||
|
* should be handled separately.
|
||||||
*
|
*
|
||||||
* *tail is the pointer to the tail pointer of the list of results
|
* *tail is the pointer to the tail pointer of the list of results
|
||||||
* beforehand, and will be set to the tail pointer of the list of
|
* beforehand, and will be set to the tail pointer of the list of
|
||||||
|
189
t/t5582-fetch-negative-refspec.sh
Executable file
189
t/t5582-fetch-negative-refspec.sh
Executable file
@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Copyright (c) 2020, Jacob Keller.
|
||||||
|
|
||||||
|
test_description='"git fetch" with negative refspecs.
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
echo >file original &&
|
||||||
|
git add file &&
|
||||||
|
git commit -a -m original
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "clone and setup child repos" '
|
||||||
|
git clone . one &&
|
||||||
|
(
|
||||||
|
cd one &&
|
||||||
|
echo >file updated by one &&
|
||||||
|
git commit -a -m "updated by one" &&
|
||||||
|
git switch -c alternate &&
|
||||||
|
echo >file updated again by one &&
|
||||||
|
git commit -a -m "updated by one again" &&
|
||||||
|
git switch master
|
||||||
|
) &&
|
||||||
|
git clone . two &&
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
git config branch.master.remote one &&
|
||||||
|
git config remote.one.url ../one/.git/ &&
|
||||||
|
git config remote.one.fetch +refs/heads/*:refs/remotes/one/* &&
|
||||||
|
git config --add remote.one.fetch ^refs/heads/alternate
|
||||||
|
) &&
|
||||||
|
git clone . three
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch one" '
|
||||||
|
echo >file updated by origin &&
|
||||||
|
git commit -a -m "updated by origin" &&
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
test_must_fail git rev-parse --verify refs/remotes/one/alternate &&
|
||||||
|
git fetch one &&
|
||||||
|
test_must_fail git rev-parse --verify refs/remotes/one/alternate &&
|
||||||
|
git rev-parse --verify refs/remotes/one/master &&
|
||||||
|
mine=$(git rev-parse refs/remotes/one/master) &&
|
||||||
|
his=$(cd ../one && git rev-parse refs/heads/master) &&
|
||||||
|
test "z$mine" = "z$his"
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch with negative refspec on commandline" '
|
||||||
|
echo >file updated by origin again &&
|
||||||
|
git commit -a -m "updated by origin again" &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
|
||||||
|
echo $alternate_in_one >expect &&
|
||||||
|
git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^refs/heads/master &&
|
||||||
|
cut -f -1 .git/FETCH_HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch with negative sha1 refspec fails" '
|
||||||
|
echo >file updated by origin yet again &&
|
||||||
|
git commit -a -m "updated by origin yet again" &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
master_in_one=$(cd ../one && git rev-parse refs/heads/master) &&
|
||||||
|
test_must_fail git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^$master_in_one
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch with negative pattern refspec" '
|
||||||
|
echo >file updated by origin once more &&
|
||||||
|
git commit -a -m "updated by origin once more" &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
|
||||||
|
echo $alternate_in_one >expect &&
|
||||||
|
git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^refs/heads/m* &&
|
||||||
|
cut -f -1 .git/FETCH_HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch with negative pattern refspec does not expand prefix" '
|
||||||
|
echo >file updated by origin another time &&
|
||||||
|
git commit -a -m "updated by origin another time" &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
|
||||||
|
master_in_one=$(cd ../one && git rev-parse refs/heads/master) &&
|
||||||
|
echo $alternate_in_one >expect &&
|
||||||
|
echo $master_in_one >>expect &&
|
||||||
|
git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^master &&
|
||||||
|
cut -f -1 .git/FETCH_HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch with negative refspec avoids duplicate conflict" '
|
||||||
|
cd "$D" &&
|
||||||
|
(
|
||||||
|
cd one &&
|
||||||
|
git branch dups/a &&
|
||||||
|
git branch dups/b &&
|
||||||
|
git branch dups/c &&
|
||||||
|
git branch other/a &&
|
||||||
|
git rev-parse --verify refs/heads/other/a >../expect &&
|
||||||
|
git rev-parse --verify refs/heads/dups/b >>../expect &&
|
||||||
|
git rev-parse --verify refs/heads/dups/c >>../expect
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
git fetch ../one/.git ^refs/heads/dups/a refs/heads/dups/*:refs/dups/* refs/heads/other/a:refs/dups/a &&
|
||||||
|
git rev-parse --verify refs/dups/a >../actual &&
|
||||||
|
git rev-parse --verify refs/dups/b >>../actual &&
|
||||||
|
git rev-parse --verify refs/dups/c >>../actual
|
||||||
|
) &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "push --prune with negative refspec" '
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
git branch prune/a &&
|
||||||
|
git branch prune/b &&
|
||||||
|
git branch prune/c &&
|
||||||
|
git push ../three refs/heads/prune/* &&
|
||||||
|
git branch -d prune/a &&
|
||||||
|
git branch -d prune/b &&
|
||||||
|
git push --prune ../three refs/heads/prune/* ^refs/heads/prune/b
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
test_write_lines b c >expect &&
|
||||||
|
git for-each-ref --format="%(refname:lstrip=3)" refs/heads/prune/ >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "push --prune with negative refspec apply to the destination" '
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
git branch ours/a &&
|
||||||
|
git branch ours/b &&
|
||||||
|
git branch ours/c &&
|
||||||
|
git push ../three refs/heads/ours/*:refs/heads/theirs/* &&
|
||||||
|
git branch -d ours/a &&
|
||||||
|
git branch -d ours/b &&
|
||||||
|
git push --prune ../three refs/heads/ours/*:refs/heads/theirs/* ^refs/heads/theirs/b
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
test_write_lines b c >expect &&
|
||||||
|
git for-each-ref --format="%(refname:lstrip=3)" refs/heads/theirs/ >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "fetch --prune with negative refspec" '
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
git branch fetch/a &&
|
||||||
|
git branch fetch/b &&
|
||||||
|
git branch fetch/c
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
git fetch ../two/.git refs/heads/fetch/*:refs/heads/copied/*
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd two &&
|
||||||
|
git branch -d fetch/a &&
|
||||||
|
git branch -d fetch/b
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd three &&
|
||||||
|
test_write_lines b c >expect &&
|
||||||
|
git fetch -v ../two/.git --prune refs/heads/fetch/*:refs/heads/copied/* ^refs/heads/fetch/b &&
|
||||||
|
git for-each-ref --format="%(refname:lstrip=3)" refs/heads/copied/ >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Reference in New Issue
Block a user