Merge branch 'jc/remote'
* jc/remote: git-push: Update description of refspecs and add examples remote.c: "git-push frotz" should update what matches at the source. remote.c: fix "git push" weak match disambiguation remote.c: minor clean-up of match_explicit() remote.c: refactor creation of new dst ref remote.c: refactor match_explicit_refs()
This commit is contained in:
commit
952c8c5638
@ -53,9 +53,8 @@ side are updated.
|
||||
+
|
||||
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
||||
+
|
||||
A parameter <ref> without a colon is equivalent to
|
||||
<ref>`:`<ref>, hence updates <ref> in the destination from <ref>
|
||||
in the source.
|
||||
A parameter <ref> without a colon pushes the <ref> from the source
|
||||
repository to the destination repository under the same name.
|
||||
+
|
||||
Pushing an empty <src> allows you to delete the <dst> ref from
|
||||
the remote repository.
|
||||
@ -98,6 +97,26 @@ the remote repository.
|
||||
|
||||
include::urls.txt[]
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
git push origin master::
|
||||
Find a ref that matches `master` in the source repository
|
||||
(most likely, it would find `refs/heads/master`), and update
|
||||
the same ref (e.g. `refs/heads/master`) in `origin` repository
|
||||
with it.
|
||||
|
||||
git push origin :experimental::
|
||||
Find a ref that matches `experimental` in the `origin` repository
|
||||
(e.g. `refs/heads/experimental`), and delete it.
|
||||
|
||||
git push origin master:satellite/master::
|
||||
Find a ref that matches `master` in the source repository
|
||||
(most likely, it would find `refs/heads/master`), and update
|
||||
the ref that matches `satellite/master` (most likely, it would
|
||||
be `refs/remotes/satellite/master`) in `origin` repository with it.
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <junkio@cox.net>, later rewritten in C
|
||||
|
175
remote.c
175
remote.c
@ -333,7 +333,6 @@ static int count_refspec_match(const char *pattern,
|
||||
for (weak_match = match = 0; refs; refs = refs->next) {
|
||||
char *name = refs->name;
|
||||
int namelen = strlen(name);
|
||||
int weak_match;
|
||||
|
||||
if (namelen < patlen ||
|
||||
memcmp(name + namelen - patlen, pattern, patlen))
|
||||
@ -406,90 +405,96 @@ static struct ref *try_explicit_object_name(const char *name)
|
||||
return ref;
|
||||
}
|
||||
|
||||
static struct ref *make_dst(const char *name, struct ref ***dst_tail)
|
||||
{
|
||||
struct ref *dst;
|
||||
size_t len;
|
||||
|
||||
len = strlen(name) + 1;
|
||||
dst = xcalloc(1, sizeof(*dst) + len);
|
||||
memcpy(dst->name, name, len);
|
||||
link_dst_tail(dst, dst_tail);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static int match_explicit(struct ref *src, struct ref *dst,
|
||||
struct ref ***dst_tail,
|
||||
struct refspec *rs,
|
||||
int errs)
|
||||
{
|
||||
struct ref *matched_src, *matched_dst;
|
||||
|
||||
const char *dst_value = rs->dst;
|
||||
|
||||
if (rs->pattern)
|
||||
return errs;
|
||||
|
||||
matched_src = matched_dst = NULL;
|
||||
switch (count_refspec_match(rs->src, src, &matched_src)) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
/* The source could be in the get_sha1() format
|
||||
* not a reference name. :refs/other is a
|
||||
* way to delete 'other' ref at the remote end.
|
||||
*/
|
||||
matched_src = try_explicit_object_name(rs->src);
|
||||
if (matched_src)
|
||||
break;
|
||||
error("src refspec %s does not match any.",
|
||||
rs->src);
|
||||
break;
|
||||
default:
|
||||
matched_src = NULL;
|
||||
error("src refspec %s matches more than one.",
|
||||
rs->src);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matched_src)
|
||||
errs = 1;
|
||||
|
||||
if (dst_value == NULL)
|
||||
dst_value = matched_src->name;
|
||||
|
||||
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
if (!memcmp(dst_value, "refs/", 5))
|
||||
matched_dst = make_dst(dst_value, dst_tail);
|
||||
else
|
||||
error("dst refspec %s does not match any "
|
||||
"existing ref on the remote and does "
|
||||
"not start with refs/.", dst_value);
|
||||
break;
|
||||
default:
|
||||
matched_dst = NULL;
|
||||
error("dst refspec %s matches more than one.",
|
||||
dst_value);
|
||||
break;
|
||||
}
|
||||
if (errs || matched_dst == NULL)
|
||||
return 1;
|
||||
if (matched_dst->peer_ref) {
|
||||
errs = 1;
|
||||
error("dst ref %s receives from more than one src.",
|
||||
matched_dst->name);
|
||||
}
|
||||
else {
|
||||
matched_dst->peer_ref = matched_src;
|
||||
matched_dst->force = rs->force;
|
||||
}
|
||||
return errs;
|
||||
}
|
||||
|
||||
static int match_explicit_refs(struct ref *src, struct ref *dst,
|
||||
struct ref ***dst_tail, struct refspec *rs,
|
||||
int rs_nr)
|
||||
{
|
||||
int i, errs;
|
||||
for (i = errs = 0; i < rs_nr; i++) {
|
||||
struct ref *matched_src, *matched_dst;
|
||||
|
||||
const char *dst_value = rs[i].dst;
|
||||
|
||||
if (rs[i].pattern)
|
||||
continue;
|
||||
|
||||
if (dst_value == NULL)
|
||||
dst_value = rs[i].src;
|
||||
|
||||
matched_src = matched_dst = NULL;
|
||||
switch (count_refspec_match(rs[i].src, src, &matched_src)) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
/* The source could be in the get_sha1() format
|
||||
* not a reference name. :refs/other is a
|
||||
* way to delete 'other' ref at the remote end.
|
||||
*/
|
||||
matched_src = try_explicit_object_name(rs[i].src);
|
||||
if (matched_src)
|
||||
break;
|
||||
errs = 1;
|
||||
error("src refspec %s does not match any.",
|
||||
rs[i].src);
|
||||
break;
|
||||
default:
|
||||
errs = 1;
|
||||
error("src refspec %s matches more than one.",
|
||||
rs[i].src);
|
||||
break;
|
||||
}
|
||||
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
if (!memcmp(dst_value, "refs/", 5)) {
|
||||
int len = strlen(dst_value) + 1;
|
||||
matched_dst = xcalloc(1, sizeof(*dst) + len);
|
||||
memcpy(matched_dst->name, dst_value, len);
|
||||
link_dst_tail(matched_dst, dst_tail);
|
||||
}
|
||||
else if (!strcmp(rs[i].src, dst_value) &&
|
||||
matched_src) {
|
||||
/* pushing "master:master" when
|
||||
* remote does not have master yet.
|
||||
*/
|
||||
int len = strlen(matched_src->name) + 1;
|
||||
matched_dst = xcalloc(1, sizeof(*dst) + len);
|
||||
memcpy(matched_dst->name, matched_src->name,
|
||||
len);
|
||||
link_dst_tail(matched_dst, dst_tail);
|
||||
}
|
||||
else {
|
||||
errs = 1;
|
||||
error("dst refspec %s does not match any "
|
||||
"existing ref on the remote and does "
|
||||
"not start with refs/.", dst_value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errs = 1;
|
||||
error("dst refspec %s matches more than one.",
|
||||
dst_value);
|
||||
break;
|
||||
}
|
||||
if (errs)
|
||||
continue;
|
||||
if (matched_dst->peer_ref) {
|
||||
errs = 1;
|
||||
error("dst ref %s receives from more than one src.",
|
||||
matched_dst->name);
|
||||
}
|
||||
else {
|
||||
matched_dst->peer_ref = matched_src;
|
||||
matched_dst->force = rs[i].force;
|
||||
}
|
||||
}
|
||||
for (i = errs = 0; i < rs_nr; i++)
|
||||
errs |= match_explicit(src, dst, dst_tail, &rs[i], errs);
|
||||
return -errs;
|
||||
}
|
||||
|
||||
@ -513,6 +518,11 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note. This is used only by "push"; refspec matching rules for
|
||||
* push and fetch are subtly different, so do not try to reuse it
|
||||
* without thinking.
|
||||
*/
|
||||
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
||||
int nr_refspec, char **refspec, int all)
|
||||
{
|
||||
@ -555,11 +565,8 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
|
||||
goto free_name;
|
||||
if (!dst_peer) {
|
||||
/* Create a new one and link it */
|
||||
int len = strlen(dst_name) + 1;
|
||||
dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
|
||||
memcpy(dst_peer->name, dst_name, len);
|
||||
dst_peer = make_dst(dst_name, dst_tail);
|
||||
hashcpy(dst_peer->new_sha1, src->new_sha1);
|
||||
link_dst_tail(dst_peer, dst_tail);
|
||||
}
|
||||
dst_peer->peer_ref = src;
|
||||
free_name:
|
||||
|
@ -15,12 +15,58 @@ mk_empty () {
|
||||
)
|
||||
}
|
||||
|
||||
mk_test () {
|
||||
mk_empty &&
|
||||
(
|
||||
for ref in "$@"
|
||||
do
|
||||
git push testrepo $the_first_commit:refs/$ref || {
|
||||
echo "Oops, push refs/$ref failure"
|
||||
exit 1
|
||||
}
|
||||
done &&
|
||||
cd testrepo &&
|
||||
for ref in "$@"
|
||||
do
|
||||
r=$(git show-ref -s --verify refs/$ref) &&
|
||||
test "z$r" = "z$the_first_commit" || {
|
||||
echo "Oops, refs/$ref is wrong"
|
||||
exit 1
|
||||
}
|
||||
done &&
|
||||
git fsck --full
|
||||
)
|
||||
}
|
||||
|
||||
check_push_result () {
|
||||
(
|
||||
cd testrepo &&
|
||||
it="$1" &&
|
||||
shift
|
||||
for ref in "$@"
|
||||
do
|
||||
r=$(git show-ref -s --verify refs/$ref) &&
|
||||
test "z$r" = "z$it" || {
|
||||
echo "Oops, refs/$ref is wrong"
|
||||
exit 1
|
||||
}
|
||||
done &&
|
||||
git fsck --full
|
||||
)
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
|
||||
: >path1 &&
|
||||
git add path1 &&
|
||||
test_tick &&
|
||||
git commit -a -m repo &&
|
||||
the_first_commit=$(git show-ref -s --verify refs/heads/master) &&
|
||||
|
||||
: >path2 &&
|
||||
git add path2 &&
|
||||
test_tick &&
|
||||
git commit -a -m second &&
|
||||
the_commit=$(git show-ref -s --verify refs/heads/master)
|
||||
|
||||
'
|
||||
@ -79,4 +125,122 @@ test_expect_success 'push with wildcard' '
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'push with matching heads' '
|
||||
|
||||
mk_test heads/master &&
|
||||
git push testrepo &&
|
||||
check_push_result $the_commit heads/master
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with no ambiguity (1)' '
|
||||
|
||||
mk_test heads/master &&
|
||||
git push testrepo master:master &&
|
||||
check_push_result $the_commit heads/master
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with no ambiguity (2)' '
|
||||
|
||||
mk_test remotes/origin/master &&
|
||||
git push testrepo master:master &&
|
||||
check_push_result $the_commit remotes/origin/master
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with weak ambiguity (1)' '
|
||||
|
||||
mk_test heads/master remotes/origin/master &&
|
||||
git push testrepo master:master &&
|
||||
check_push_result $the_commit heads/master &&
|
||||
check_push_result $the_first_commit remotes/origin/master
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with weak ambiguity (2)' '
|
||||
|
||||
mk_test heads/master remotes/origin/master remotes/another/master &&
|
||||
git push testrepo master:master &&
|
||||
check_push_result $the_commit heads/master &&
|
||||
check_push_result $the_first_commit remotes/origin/master remotes/another/master
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with ambiguity (1)' '
|
||||
|
||||
mk_test remotes/origin/master remotes/frotz/master &&
|
||||
if git push testrepo master:master
|
||||
then
|
||||
echo "Oops, should have failed"
|
||||
false
|
||||
else
|
||||
check_push_result $the_first_commit remotes/origin/master remotes/frotz/master
|
||||
fi
|
||||
'
|
||||
|
||||
test_expect_success 'push with ambiguity (2)' '
|
||||
|
||||
mk_test heads/frotz tags/frotz &&
|
||||
if git push testrepo master:frotz
|
||||
then
|
||||
echo "Oops, should have failed"
|
||||
false
|
||||
else
|
||||
check_push_result $the_first_commit heads/frotz tags/frotz
|
||||
fi
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with colon-less refspec (1)' '
|
||||
|
||||
mk_test heads/frotz tags/frotz &&
|
||||
git branch -f frotz master &&
|
||||
git push testrepo frotz &&
|
||||
check_push_result $the_commit heads/frotz &&
|
||||
check_push_result $the_first_commit tags/frotz
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with colon-less refspec (2)' '
|
||||
|
||||
mk_test heads/frotz tags/frotz &&
|
||||
if git show-ref --verify -q refs/heads/frotz
|
||||
then
|
||||
git branch -D frotz
|
||||
fi &&
|
||||
git tag -f frotz &&
|
||||
git push testrepo frotz &&
|
||||
check_push_result $the_commit tags/frotz &&
|
||||
check_push_result $the_first_commit heads/frotz
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'push with colon-less refspec (3)' '
|
||||
|
||||
mk_test &&
|
||||
if git show-ref --verify -q refs/tags/frotz
|
||||
then
|
||||
git tag -d frotz
|
||||
fi &&
|
||||
git branch -f frotz master &&
|
||||
git push testrepo frotz &&
|
||||
check_push_result $the_commit heads/frotz &&
|
||||
test "$( cd testrepo && git show-ref | wc -l )" = 1
|
||||
'
|
||||
|
||||
test_expect_success 'push with colon-less refspec (4)' '
|
||||
|
||||
mk_test &&
|
||||
if git show-ref --verify -q refs/heads/frotz
|
||||
then
|
||||
git branch -D frotz
|
||||
fi &&
|
||||
git tag -f frotz &&
|
||||
git push testrepo frotz &&
|
||||
check_push_result $the_commit tags/frotz &&
|
||||
test "$( cd testrepo && git show-ref | wc -l )" = 1
|
||||
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user