Merge branch 'pb/push'

* pb/push:
  add special "matching refs" refspec
This commit is contained in:
Junio C Hamano 2008-05-23 16:06:07 -07:00
commit 182fb4df91
6 changed files with 116 additions and 35 deletions

View File

@ -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.
+
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

View File

@ -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:

View File

@ -434,6 +434,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);
@ -855,7 +865,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;
@ -945,13 +955,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;
}
/*
@ -962,11 +982,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;
@ -977,48 +1002,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);
}

View File

@ -47,6 +47,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;

View File

@ -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

View File

@ -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 &&