summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2007-06-16 01:22:45 -0700
committerLibravatar Junio C Hamano <gitster@pobox.com>2007-06-16 01:22:45 -0700
commit952c8c56380734d45bddf369fe478895672c5a3a (patch)
tree4e3909fdfce84a4189f9e35b79f479d7f05fddb3
parentMerge branch 'gp/branch' (diff)
parentgit-push: Update description of refspecs and add examples (diff)
downloadtgif-952c8c56380734d45bddf369fe478895672c5a3a.tar.xz
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()
-rw-r--r--Documentation/git-push.txt25
-rw-r--r--remote.c171
-rwxr-xr-xt/t5516-fetch-push.sh164
3 files changed, 275 insertions, 85 deletions
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 366c5dbdce..665f6dc709 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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
diff --git a/remote.c b/remote.c
index 54c9401a6b..500ca4d968 100644
--- a/remote.c
+++ b/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 int match_explicit_refs(struct ref *src, struct ref *dst,
- struct ref ***dst_tail, struct refspec *rs,
- int rs_nr)
+static struct ref *make_dst(const char *name, struct ref ***dst_tail)
{
- int i, errs;
- for (i = errs = 0; i < rs_nr; i++) {
- struct ref *matched_src, *matched_dst;
+ struct ref *dst;
+ size_t len;
- const char *dst_value = rs[i].dst;
+ len = strlen(name) + 1;
+ dst = xcalloc(1, sizeof(*dst) + len);
+ memcpy(dst->name, name, len);
+ link_dst_tail(dst, dst_tail);
+ return dst;
+}
- if (rs[i].pattern)
- continue;
+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;
- if (dst_value == NULL)
- dst_value = rs[i].src;
+ const char *dst_value = rs->dst;
- 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);
+ 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;
- }
- 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;
- }
+ 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++)
+ 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:
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index dba018f667..08d58e1c8c 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -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