add special "matching refs" refspec
This patch provides a way to specify "push matching heads" using a special refspec ":". This is useful because it allows "push = +:" as a way to specify that matching refs will be pushed but, in addition, forced updates will be allowed, which was not possible before. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
c697ad143b
commit
a83619d692
|
@ -46,12 +46,6 @@ specified, the same ref that <src> referred to locally). If
|
||||||
the optional leading plus `+` is used, the remote ref is updated
|
the optional leading plus `+` is used, the remote ref is updated
|
||||||
even if it does not result in a fast forward update.
|
even if it does not result in a fast forward update.
|
||||||
+
|
+
|
||||||
Note: If no explicit refspec is found, (that is neither
|
|
||||||
on the command line nor in any Push line of the
|
|
||||||
corresponding remotes file---see below), then "matching" heads are
|
|
||||||
pushed: for every head that exists on the local side, the remote side is
|
|
||||||
updated if a head of the same name already exists on the remote side.
|
|
||||||
+
|
|
||||||
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
||||||
+
|
+
|
||||||
A parameter <ref> without a colon pushes the <ref> from the source
|
A parameter <ref> without a colon pushes the <ref> from the source
|
||||||
|
@ -59,6 +53,13 @@ repository to the destination repository under the same name.
|
||||||
+
|
+
|
||||||
Pushing an empty <src> allows you to delete the <dst> ref from
|
Pushing an empty <src> allows you to delete the <dst> ref from
|
||||||
the remote repository.
|
the remote repository.
|
||||||
|
+
|
||||||
|
The special refspec `:` (or `+:` to allow non-fast forward updates)
|
||||||
|
directs git to push "matching" heads: for every head that exists on
|
||||||
|
the local side, the remote side is updated if a head of the same name
|
||||||
|
already exists on the remote side. This is the default operation mode
|
||||||
|
if no explicit refspec is found (that is neither on the command line
|
||||||
|
nor in any Push line of the corresponding remotes file---see below).
|
||||||
|
|
||||||
\--all::
|
\--all::
|
||||||
Instead of naming each ref to push, specifies that all
|
Instead of naming each ref to push, specifies that all
|
||||||
|
|
|
@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < nr_heads; i++) {
|
for (i = 0; i < nr_heads; i++) {
|
||||||
|
const char *local = heads[i];
|
||||||
const char *remote = strrchr(heads[i], ':');
|
const char *remote = strrchr(heads[i], ':');
|
||||||
|
|
||||||
remote = remote ? (remote + 1) : heads[i];
|
if (*local == '+')
|
||||||
|
local++;
|
||||||
|
|
||||||
|
/* A matching refspec is okay. */
|
||||||
|
if (remote == local && remote[1] == '\0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
remote = remote ? (remote + 1) : local;
|
||||||
switch (check_ref_format(remote)) {
|
switch (check_ref_format(remote)) {
|
||||||
case 0: /* ok */
|
case 0: /* ok */
|
||||||
case CHECK_REF_FORMAT_ONELEVEL:
|
case CHECK_REF_FORMAT_ONELEVEL:
|
||||||
|
|
81
remote.c
81
remote.c
|
@ -429,6 +429,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
|
||||||
}
|
}
|
||||||
|
|
||||||
rhs = strrchr(lhs, ':');
|
rhs = strrchr(lhs, ':');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Before going on, special case ":" (or "+:") as a refspec
|
||||||
|
* for matching refs.
|
||||||
|
*/
|
||||||
|
if (!fetch && rhs == lhs && rhs[1] == '\0') {
|
||||||
|
rs[i].matching = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (rhs) {
|
if (rhs) {
|
||||||
rhs++;
|
rhs++;
|
||||||
rlen = strlen(rhs);
|
rlen = strlen(rhs);
|
||||||
|
@ -842,7 +852,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)
|
if (rs->pattern || rs->matching)
|
||||||
return errs;
|
return errs;
|
||||||
|
|
||||||
matched_src = matched_dst = NULL;
|
matched_src = matched_dst = NULL;
|
||||||
|
@ -932,13 +942,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
|
||||||
const struct ref *src)
|
const struct ref *src)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
int matching_refs = -1;
|
||||||
for (i = 0; i < rs_nr; i++) {
|
for (i = 0; i < rs_nr; i++) {
|
||||||
|
if (rs[i].matching &&
|
||||||
|
(matching_refs == -1 || rs[i].force)) {
|
||||||
|
matching_refs = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (rs[i].pattern &&
|
if (rs[i].pattern &&
|
||||||
!prefixcmp(src->name, rs[i].src) &&
|
!prefixcmp(src->name, rs[i].src) &&
|
||||||
src->name[strlen(rs[i].src)] == '/')
|
src->name[strlen(rs[i].src)] == '/')
|
||||||
return rs + i;
|
return rs + i;
|
||||||
}
|
}
|
||||||
return NULL;
|
if (matching_refs != -1)
|
||||||
|
return rs + matching_refs;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -949,11 +969,16 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
|
||||||
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
||||||
int nr_refspec, const char **refspec, int flags)
|
int nr_refspec, const char **refspec, int flags)
|
||||||
{
|
{
|
||||||
struct refspec *rs =
|
struct refspec *rs;
|
||||||
parse_push_refspec(nr_refspec, (const char **) refspec);
|
|
||||||
int send_all = flags & MATCH_REFS_ALL;
|
int send_all = flags & MATCH_REFS_ALL;
|
||||||
int send_mirror = flags & MATCH_REFS_MIRROR;
|
int send_mirror = flags & MATCH_REFS_MIRROR;
|
||||||
|
static const char *default_refspec[] = { ":", 0 };
|
||||||
|
|
||||||
|
if (!nr_refspec) {
|
||||||
|
nr_refspec = 1;
|
||||||
|
refspec = default_refspec;
|
||||||
|
}
|
||||||
|
rs = parse_push_refspec(nr_refspec, (const char **) refspec);
|
||||||
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
|
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -964,48 +989,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
||||||
char *dst_name;
|
char *dst_name;
|
||||||
if (src->peer_ref)
|
if (src->peer_ref)
|
||||||
continue;
|
continue;
|
||||||
if (nr_refspec) {
|
|
||||||
pat = check_pattern_match(rs, nr_refspec, src);
|
pat = check_pattern_match(rs, nr_refspec, src);
|
||||||
if (!pat)
|
if (!pat)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
|
if (pat->matching) {
|
||||||
/*
|
/*
|
||||||
* "matching refs"; traditionally we pushed everything
|
* "matching refs"; traditionally we pushed everything
|
||||||
* including refs outside refs/heads/ hierarchy, but
|
* including refs outside refs/heads/ hierarchy, but
|
||||||
* that does not make much sense these days.
|
* that does not make much sense these days.
|
||||||
*/
|
*/
|
||||||
continue;
|
if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
|
||||||
|
continue;
|
||||||
|
dst_name = xstrdup(src->name);
|
||||||
|
|
||||||
if (pat) {
|
} else {
|
||||||
const char *dst_side = pat->dst ? pat->dst : pat->src;
|
const char *dst_side = pat->dst ? pat->dst : pat->src;
|
||||||
dst_name = xmalloc(strlen(dst_side) +
|
dst_name = xmalloc(strlen(dst_side) +
|
||||||
strlen(src->name) -
|
strlen(src->name) -
|
||||||
strlen(pat->src) + 2);
|
strlen(pat->src) + 2);
|
||||||
strcpy(dst_name, dst_side);
|
strcpy(dst_name, dst_side);
|
||||||
strcat(dst_name, src->name + strlen(pat->src));
|
strcat(dst_name, src->name + strlen(pat->src));
|
||||||
} else
|
}
|
||||||
dst_name = xstrdup(src->name);
|
|
||||||
dst_peer = find_ref_by_name(dst, dst_name);
|
dst_peer = find_ref_by_name(dst, dst_name);
|
||||||
if (dst_peer && dst_peer->peer_ref)
|
if (dst_peer) {
|
||||||
/* We're already sending something to this ref. */
|
if (dst_peer->peer_ref)
|
||||||
goto free_name;
|
/* We're already sending something to this ref. */
|
||||||
|
goto free_name;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (pat->matching && !(send_all || send_mirror))
|
||||||
|
/*
|
||||||
|
* Remote doesn't have it, and we have no
|
||||||
|
* explicit pattern, and we don't have
|
||||||
|
* --all nor --mirror.
|
||||||
|
*/
|
||||||
|
goto free_name;
|
||||||
|
|
||||||
if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
|
|
||||||
/*
|
|
||||||
* Remote doesn't have it, and we have no
|
|
||||||
* explicit pattern, and we don't have
|
|
||||||
* --all nor --mirror.
|
|
||||||
*/
|
|
||||||
goto free_name;
|
|
||||||
if (!dst_peer) {
|
|
||||||
/* Create a new one and link it */
|
/* Create a new one and link it */
|
||||||
dst_peer = make_linked_ref(dst_name, dst_tail);
|
dst_peer = make_linked_ref(dst_name, dst_tail);
|
||||||
hashcpy(dst_peer->new_sha1, src->new_sha1);
|
hashcpy(dst_peer->new_sha1, src->new_sha1);
|
||||||
}
|
}
|
||||||
dst_peer->peer_ref = src;
|
dst_peer->peer_ref = src;
|
||||||
if (pat)
|
dst_peer->force = pat->force;
|
||||||
dst_peer->force = pat->force;
|
|
||||||
free_name:
|
free_name:
|
||||||
free(dst_name);
|
free(dst_name);
|
||||||
}
|
}
|
||||||
|
|
1
remote.h
1
remote.h
|
@ -46,6 +46,7 @@ int remote_has_url(struct remote *remote, const char *url);
|
||||||
struct refspec {
|
struct refspec {
|
||||||
unsigned force : 1;
|
unsigned force : 1;
|
||||||
unsigned pattern : 1;
|
unsigned pattern : 1;
|
||||||
|
unsigned matching : 1;
|
||||||
|
|
||||||
char *src;
|
char *src;
|
||||||
char *dst;
|
char *dst;
|
||||||
|
|
|
@ -23,10 +23,13 @@ test_refspec () {
|
||||||
}
|
}
|
||||||
|
|
||||||
test_refspec push '' invalid
|
test_refspec push '' invalid
|
||||||
test_refspec push ':' invalid
|
test_refspec push ':'
|
||||||
|
test_refspec push '::' invalid
|
||||||
|
test_refspec push '+:'
|
||||||
|
|
||||||
test_refspec fetch ''
|
test_refspec fetch ''
|
||||||
test_refspec fetch ':'
|
test_refspec fetch ':'
|
||||||
|
test_refspec fetch '::' invalid
|
||||||
|
|
||||||
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
|
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
|
||||||
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid
|
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid
|
||||||
|
|
|
@ -165,6 +165,47 @@ test_expect_success 'push with matching heads' '
|
||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'push with matching heads on the command line' '
|
||||||
|
|
||||||
|
mk_test heads/master &&
|
||||||
|
git push testrepo : &&
|
||||||
|
check_push_result $the_commit heads/master
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'failed (non-fast-forward) push with matching heads' '
|
||||||
|
|
||||||
|
mk_test heads/master &&
|
||||||
|
git push testrepo : &&
|
||||||
|
git commit --amend -massaged &&
|
||||||
|
! git push testrepo &&
|
||||||
|
check_push_result $the_commit heads/master &&
|
||||||
|
git reset --hard $the_commit
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'push --force with matching heads' '
|
||||||
|
|
||||||
|
mk_test heads/master &&
|
||||||
|
git push testrepo : &&
|
||||||
|
git commit --amend -massaged &&
|
||||||
|
git push --force testrepo &&
|
||||||
|
! check_push_result $the_commit heads/master &&
|
||||||
|
git reset --hard $the_commit
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'push with matching heads and forced update' '
|
||||||
|
|
||||||
|
mk_test heads/master &&
|
||||||
|
git push testrepo : &&
|
||||||
|
git commit --amend -massaged &&
|
||||||
|
git push testrepo +: &&
|
||||||
|
! check_push_result $the_commit heads/master &&
|
||||||
|
git reset --hard $the_commit
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'push with no ambiguity (1)' '
|
test_expect_success 'push with no ambiguity (1)' '
|
||||||
|
|
||||||
mk_test heads/master &&
|
mk_test heads/master &&
|
||||||
|
|
Loading…
Reference in New Issue