Browse Source

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
Paolo Bonzini 17 years ago committed by Junio C Hamano
parent
commit
a83619d692
  1. 13
      Documentation/git-push.txt
  2. 10
      builtin-send-pack.c
  3. 81
      remote.c
  4. 1
      remote.h
  5. 5
      t/t5511-refspec.sh
  6. 41
      t/t5516-fetch-push.sh

13
Documentation/git-push.txt

@ -46,12 +46,6 @@ specified, the same ref that <src> referred to locally). If @@ -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
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>`.
+
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. @@ -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
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::
Instead of naming each ref to push, specifies that all

10
builtin-send-pack.c

@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads) @@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
int i;

for (i = 0; i < nr_heads; i++) {
const char *local = 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)) {
case 0: /* ok */
case CHECK_REF_FORMAT_ONELEVEL:

81
remote.c

@ -429,6 +429,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp @@ -429,6 +429,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
}

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) {
rhs++;
rlen = strlen(rhs);
@ -842,7 +852,7 @@ static int match_explicit(struct ref *src, struct ref *dst, @@ -842,7 +852,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
const char *dst_value = rs->dst;
char *dst_guess;

if (rs->pattern)
if (rs->pattern || rs->matching)
return errs;

matched_src = matched_dst = NULL;
@ -932,13 +942,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, @@ -932,13 +942,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
const struct ref *src)
{
int i;
int matching_refs = -1;
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 &&
!prefixcmp(src->name, rs[i].src) &&
src->name[strlen(rs[i].src)] == '/')
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, @@ -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 nr_refspec, const char **refspec, int flags)
{
struct refspec *rs =
parse_push_refspec(nr_refspec, (const char **) refspec);
struct refspec *rs;
int send_all = flags & MATCH_REFS_ALL;
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))
return -1;

@ -964,48 +989,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, @@ -964,48 +989,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
char *dst_name;
if (src->peer_ref)
continue;
if (nr_refspec) {
pat = check_pattern_match(rs, nr_refspec, src);
if (!pat)
continue;
}
else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))

pat = check_pattern_match(rs, nr_refspec, src);
if (!pat)
continue;

if (pat->matching) {
/*
* "matching refs"; traditionally we pushed everything
* including refs outside refs/heads/ hierarchy, but
* 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;
dst_name = xmalloc(strlen(dst_side) +
strlen(src->name) -
strlen(pat->src) + 2);
strcpy(dst_name, dst_side);
strcat(dst_name, src->name + strlen(pat->src));
} else
dst_name = xstrdup(src->name);
}
dst_peer = find_ref_by_name(dst, dst_name);
if (dst_peer && dst_peer->peer_ref)
/* We're already sending something to this ref. */
goto free_name;
if (dst_peer) {
if (dst_peer->peer_ref)
/* 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 */
dst_peer = make_linked_ref(dst_name, dst_tail);
hashcpy(dst_peer->new_sha1, src->new_sha1);
}
dst_peer->peer_ref = src;
if (pat)
dst_peer->force = pat->force;
dst_peer->force = pat->force;
free_name:
free(dst_name);
}

1
remote.h

@ -46,6 +46,7 @@ int remote_has_url(struct remote *remote, const char *url); @@ -46,6 +46,7 @@ int remote_has_url(struct remote *remote, const char *url);
struct refspec {
unsigned force : 1;
unsigned pattern : 1;
unsigned matching : 1;

char *src;
char *dst;

5
t/t5511-refspec.sh

@ -23,10 +23,13 @@ test_refspec () { @@ -23,10 +23,13 @@ test_refspec () {
}

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 '::' invalid

test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid

41
t/t5516-fetch-push.sh

@ -165,6 +165,47 @@ test_expect_success 'push with matching heads' ' @@ -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)' '

mk_test heads/master &&

Loading…
Cancel
Save