Merge branch 'pb/push'
* pb/push: add special "matching refs" refspec
This commit is contained in:
commit
182fb4df91
@ -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
|
||||
|
@ -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
81
remote.c
@ -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);
|
||||
}
|
||||
|
1
remote.h
1
remote.h
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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…
Reference in New Issue
Block a user