{"thread":{"id":"35471","subject":"[PATCH 0/3] Teaching \"git push\" to map pushed refs","startedAt":"2013-12-04T00:39:51Z","lastAt":"2013-12-05T01:27:29Z","messageCount":8,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"231477","messageId":"1386117594-22062-1-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":null,"subject":"[PATCH 0/3] Teaching \"git push\" to map pushed refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-04T00:39:51Z","receivedAt":"2013-12-04T00:39:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Earlier, Peff taught \"git fetch origin master\" to update a\nremote-tracking branch by internally mapping the colon-less refspec\n'master' to '+refs/heads/master:refs/remotes/origin/master'.  Both\n\n\tgit fetch origin\n\tgit fetch origin master\n\nwould update the same refs/remotes/origin/master, which avoids\nsurprises.\n\nHowever, we did not have a similar refspec mapping on the push\nside.  This three-patch series does just that, and would help\ntriangular workflow by making these two:\n\n\tgit checkout master && git push origin\n\tgit push origin master\n\nupdate the same ref at the 'origin'.\n\nJunio C Hamano (3):\n  builtin/push.c: use strbuf instead of manual allocation\n  push: use remote.$name.push as a refmap\n  push: also use \"upstream\" mapping when pushing a single ref\n\n Documentation/git-push.txt |  9 ++++--\n builtin/push.c             | 79 ++++++++++++++++++++++++++++++++--------------\n remote.c                   |  8 ++---\n remote.h                   |  2 ++\n t/t5516-fetch-push.sh      | 75 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 144 insertions(+), 29 deletions(-)\n\n-- \n1.8.5.1-400-gbc1da41\n"},{"id":"231478","messageId":"1386117594-22062-2-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386117594-22062-1-git-send-email-gitster@pobox.com","subject":"[PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-04T00:39:52Z","receivedAt":"2013-12-04T00:39:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The command line arguments given to \"git push\" are massaged into\na list of refspecs in set_refspecs() function. This was implemented\nusing xmalloc, strcpy and friends, but it is much easier to read if\ndone using strbuf.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c | 35 ++++++++++++++---------------------\n 1 file changed, 14 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 7b1b66c..76e4400 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -41,29 +41,22 @@ static void set_refspecs(const char **refs, int nr)\n \tfor (i = 0; i < nr; i++) {\n \t\tconst char *ref = refs[i];\n \t\tif (!strcmp(\"tag\", ref)) {\n-\t\t\tchar *tag;\n-\t\t\tint len;\n+\t\t\tstruct strbuf tagref = STRBUF_INIT;\n \t\t\tif (nr <= ++i)\n \t\t\t\tdie(_(\"tag shorthand without <tag>\"));\n-\t\t\tlen = strlen(refs[i]) + 11;\n-\t\t\tif (deleterefs) {\n-\t\t\t\ttag = xmalloc(len+1);\n-\t\t\t\tstrcpy(tag, \":refs/tags/\");\n-\t\t\t} else {\n-\t\t\t\ttag = xmalloc(len);\n-\t\t\t\tstrcpy(tag, \"refs/tags/\");\n-\t\t\t}\n-\t\t\tstrcat(tag, refs[i]);\n-\t\t\tref = tag;\n-\t\t} else if (deleterefs && !strchr(ref, ':')) {\n-\t\t\tchar *delref;\n-\t\t\tint len = strlen(ref)+1;\n-\t\t\tdelref = xmalloc(len+1);\n-\t\t\tstrcpy(delref, \":\");\n-\t\t\tstrcat(delref, ref);\n-\t\t\tref = delref;\n-\t\t} else if (deleterefs)\n-\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n+\t\t\tref = refs[i];\n+\t\t\tif (deleterefs)\n+\t\t\t\tstrbuf_addf(&tagref, \":refs/tags/%s\", ref);\n+\t\t\telse\n+\t\t\t\tstrbuf_addf(&tagref, \"refs/tags/%s\", ref);\n+\t\t\tref = strbuf_detach(&tagref, NULL);\n+\t\t} else if (deleterefs) {\n+\t\t\tstruct strbuf delref = STRBUF_INIT;\n+\t\t\tif (strchr(ref, ':'))\n+\t\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n+\t\t\tstrbuf_addf(&delref, \":%s\", ref);\n+\t\t\tref = strbuf_detach(&delref, NULL);\n+\t\t}\n \t\tadd_refspec(ref);\n \t}\n }\n-- \n1.8.5.1-400-gbc1da41\n"},{"id":"231479","messageId":"1386117594-22062-3-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386117594-22062-1-git-send-email-gitster@pobox.com","subject":"[PATCH 2/3] push: use remote.$name.push as a refmap","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-04T00:39:53Z","receivedAt":"2013-12-04T00:39:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Since f2690487 (fetch: opportunistically update tracking refs,\n2013-05-11), we stopped taking a non-storing refspec given on the\ncommand line of \"git fetch\" literally, and instead started mapping\nit via remote.$name.fetch refspecs.  This allows\n\n    $ git fetch origin master\n\nfrom the 'origin' repository, which is configured with\n\n    [remote \"origin\"]\n        fetch = +refs/heads/*:refs/remotes/origin/*\n\nto update refs/remotes/origin/master with the result, as if the\ncommand line were\n\n    $ git fetch origin +master:refs/remotes/origin/master\n\nto reduce surprises and improve usability.  Before that change, a\nrefspec on the command line without a colon was only to fetch the\nhistory and leave the result in FETCH_HEAD, without updating the\nremote-tracking branches.\n\nWhen you are simulating a fetch from you by your mothership with a\npush by you into your mothership, instead of having:\n\n    [remote \"satellite\"]\n        fetch = +refs/heads/*:refs/remotes/satellite/*\n\non the mothership repository and running:\n\n    mothership$ git fetch satellite\n\nyou would have:\n\n    [remote \"mothership\"]\n        push = +refs/heads/*:refs/remotes/satellite/*\n\non your satellite machine, and run:\n\n    satellite$ git push mothership\n\nBecause we so far did not make the corresponding change to the push\nside, this command:\n\n    satellite$ git push mothership master\n\ndoes _not_ allow you on the satellite to only push 'master' out but\nstill to the usual destination (i.e. refs/remotes/satellite/master).\n\nImplement the logic to map an unqualified refspec given on the\ncommand line via the remote.$name.push refspec.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-push.txt |  9 +++++++--\n builtin/push.c             | 35 ++++++++++++++++++++++++++++++++---\n remote.c                   |  8 ++++----\n remote.h                   |  2 ++\n t/t5516-fetch-push.sh      | 45 +++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 90 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 9eec740..2b7f4f9 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -56,8 +56,13 @@ it can be any arbitrary \"SHA-1 expression\", such as `master~4` or\n +\n The <dst> tells which ref on the remote side is updated with this\n push. Arbitrary expressions cannot be used here, an actual ref must\n-be named. If `:`<dst> is omitted, the same ref as <src> will be\n-updated.\n+be named.\n+If `git push [<repository>]` without any `<refspec>` argument is set to\n+update some ref at the destination with `<src>` with\n+`remote.<repository>.push` configuration variable, `:<dst>` part can\n+be omitted---such a push will update a ref that `<src>` normally updates\n+without any `<refspec>` on the command line.  Otherwise, missing\n+`:<dst>` means to update the same ref as the `<src>`.\n +\n The object referenced by <src> is used to update the <dst> reference\n on the remote side.  By default this is only allowed if <dst> is not\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 76e4400..bdc1fc1 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -35,7 +35,34 @@ static void add_refspec(const char *ref)\n \trefspec[refspec_nr-1] = ref;\n }\n \n-static void set_refspecs(const char **refs, int nr)\n+static const char *map_refspec(const char *ref,\n+\t\t\t       struct remote *remote, struct ref *local_refs)\n+{\n+\tstruct ref *matched = NULL;\n+\n+\t/* Does \"ref\" uniquely name our ref? */\n+\tif (count_refspec_match(ref, local_refs, &matched) != 1)\n+\t\treturn ref;\n+\n+\tif (remote->push) {\n+\t\tstruct refspec query;\n+\t\tmemset(&query, 0, sizeof(struct refspec));\n+\t\tquery.src = matched->name;\n+\t\tif (!query_refspecs(remote->push, remote->push_refspec_nr, &query) &&\n+\t\t    query.dst) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&buf, \"%s%s:%s\",\n+\t\t\t\t    query.force ? \"+\" : \"\",\n+\t\t\t\t    query.src, query.dst);\n+\t\t\treturn strbuf_detach(&buf, NULL);\n+\t\t}\n+\t}\n+\n+\treturn ref;\n+}\n+\n+static void set_refspecs(const char **refs, int nr,\n+\t\t\t struct remote *remote, struct ref *local_refs)\n {\n \tint i;\n \tfor (i = 0; i < nr; i++) {\n@@ -56,7 +83,8 @@ static void set_refspecs(const char **refs, int nr)\n \t\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n \t\t\tstrbuf_addf(&delref, \":%s\", ref);\n \t\t\tref = strbuf_detach(&delref, NULL);\n-\t\t}\n+\t\t} else if (!strchr(ref, ':'))\n+\t\t\tref = map_refspec(ref, remote, local_refs);\n \t\tadd_refspec(ref);\n \t}\n }\n@@ -487,7 +515,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \n \tif (argc > 0) {\n \t\trepo = argv[0];\n-\t\tset_refspecs(argv + 1, argc - 1);\n+\t\tset_refspecs(argv + 1, argc - 1,\n+\t\t\t     remote_get(repo), get_local_heads());\n \t}\n \n \trc = do_push(repo, flags);\ndiff --git a/remote.c b/remote.c\nindex 9f1a8aa..6ffa149 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -821,7 +821,7 @@ static int match_name_with_pattern(const char *key, const char *name,\n \treturn ret;\n }\n \n-static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)\n+int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)\n {\n \tint i;\n \tint find_src = !query->src;\n@@ -955,9 +955,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))\n \t*l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);\n }\n \n-static int count_refspec_match(const char *pattern,\n-\t\t\t       struct ref *refs,\n-\t\t\t       struct ref **matched_ref)\n+int count_refspec_match(const char *pattern,\n+\t\t\tstruct ref *refs,\n+\t\t\tstruct ref **matched_ref)\n {\n \tint patlen = strlen(pattern);\n \tstruct ref *matched_weak = NULL;\ndiff --git a/remote.h b/remote.h\nindex 131130a..f7d43b4 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -128,6 +128,7 @@ struct ref *alloc_ref(const char *name);\n struct ref *copy_ref(const struct ref *ref);\n struct ref *copy_ref_list(const struct ref *ref);\n void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));\n+extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);\n int ref_compare_name(const void *, const void *);\n \n int check_ref_type(const struct ref *ref, int flags);\n@@ -158,6 +159,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);\n \n void free_refspec(int nr_refspec, struct refspec *refspec);\n \n+extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);\n char *apply_refspecs(struct refspec *refspecs, int nr_refspec,\n \t\t     const char *name);\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 99c32d7..6d7f102 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1126,6 +1126,51 @@ test_expect_success 'fetch follows tags by default' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'pushing a specific ref applies remote.$name.push as refmap' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit config remote.dst.push \"+refs/heads/*:refs/remotes/src/*\" &&\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master |\n+\t\tsed -e \"s|refs/heads/|refs/remotes/src/|\" >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\ttest_must_fail git show-ref refs/heads/master &&\n+\t\tgit show-ref refs/remotes/src/master >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n+test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\tgit show-ref refs/heads/master >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n test_expect_success 'push does not follow tags by default' '\n \tmk_test testrepo heads/master &&\n \trm -fr src dst &&\n-- \n1.8.5.1-400-gbc1da41\n"},{"id":"231480","messageId":"1386117594-22062-4-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386117594-22062-1-git-send-email-gitster@pobox.com","subject":"[PATCH 3/3] push: also use \"upstream\" mapping when pushing a single ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-04T00:39:54Z","receivedAt":"2013-12-04T00:39:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When the user is using the 'upstream' mode, these commands:\n\n    $ git push\n    $ git push origin\n\nwould find the 'upstream' branch for the current branch, and then\npush the current branch to update it.  However, pushing a single\nbranch explicitly, i.e.\n\n    $ git push origin $(git symbolic-ref --short HEAD)\n\nwould not go through the same ref mapping process, and ends up\nupdating the branch at 'origin' of the same name, which may not\nnecessarily be the upstream of the branch being pushed.\n\nIn the spirit similar to the previous one, map a colon-less refspec\nusing the upstream mapping logic.\n\nNote that this is deliberately done after the logic to map refspec\nvia the remote.$name.push configuration did not find any.  This is\nbecause most of those that employ a simple 'upstream' mode do not\nhave the push refspec, and checking with remote.$name.push refspec\nfirst will allow it to be used as a mechanism to override the usual\n'upstream' push mapping (which is primarily useful for centralized\nworkflow) by those who want to use a triangular workflow.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c        | 11 +++++++++++\n t/t5516-fetch-push.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 41 insertions(+)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex bdc1fc1..71c081e 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -58,6 +58,17 @@ static const char *map_refspec(const char *ref,\n \t\t}\n \t}\n \n+\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\t    !prefixcmp(matched->name, \"refs/heads/\")) {\n+\t\tstruct branch *branch = branch_get(matched->name + 11);\n+\t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&buf, \"%s:%s\",\n+\t\t\t\t    ref, branch->merge[0]->src);\n+\t\t\treturn strbuf_detach(&buf, NULL);\n+\t\t}\n+\t}\n+\n \treturn ref;\n }\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 6d7f102..926e7f6 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1160,6 +1160,7 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n \t\tgit pull ../testrepo master &&\n \t\tgit branch next &&\n \t\tgit config remote.dst.url ../dst &&\n+\t\tgit config push.default matching &&\n \t\tgit push dst master &&\n \t\tgit show-ref refs/heads/master >../dst/expect\n \t) &&\n@@ -1171,6 +1172,35 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n \ttest_cmp dst/expect dst/actual\n '\n \n+test_expect_success 'with no remote.$name.push, upstream mapping is used' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit config remote.dst.fetch \"+refs/heads/*:refs/remotes/dst/*\" &&\n+\t\tgit config push.default upstream &&\n+\n+\t\tgit config branch.master.merge refs/heads/trunk &&\n+\t\tgit config branch.master.remote dst &&\n+\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master |\n+\t\tsed -e \"s|refs/heads/master|refs/heads/trunk|\" >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/master &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\tgit show-ref refs/heads/trunk >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n test_expect_success 'push does not follow tags by default' '\n \tmk_test testrepo heads/master &&\n \trm -fr src dst &&\n-- \n1.8.5.1-400-gbc1da41\n"},{"id":"231561","messageId":"1386206849-6503-1-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386117594-22062-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 0/3] Teaching \"git push\" to map pushed refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-05T01:27:26Z","receivedAt":"2013-12-05T01:27:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The 'master' refspec in \"git fetch origin master\" used to mean \"We\ngrab their 'master' branch, but we do not store it in any of our\nremote-tracking branches.\" but in modern Git, the remote-tracking\nbranch that would receive updates from 'master' with a corresponding\n\"git fetch origin\" (without any specific refs on the command line)\nis updated.  Updating the same refs/remotes/origin/master with\n\n\tgit fetch origin\n\tgit fetch origin master\n\navoids surprises.\n\nHowever, we did not have a similar refspec mapping on the push side.\nThis three-patch series does just that.  It would help triangular\nworkflow by making these two:\n\n\tgit checkout master && git push origin\n\tgit push origin master\n\nupdate the same ref at the 'origin'.  It also would help those who\nneed to emulate a fetch run on mothership from satellite with a push\nrun on satellite into mothership (due to e.g. network connectivity\nissues).\n\nThe second round avoids instantiating the remote and the list of\nlocal heads until the very last minute when they are actually\nneeded (the change is in the second patch).\n\nJunio C Hamano (3):\n  builtin/push.c: use strbuf instead of manual allocation\n  push: use remote.$name.push as a refmap\n  push: also use \"upstream\" mapping when pushing a single ref\n\n Documentation/git-push.txt |  9 +++--\n builtin/push.c             | 84 ++++++++++++++++++++++++++++++++++------------\n remote.c                   |  8 ++---\n remote.h                   |  2 ++\n t/t5516-fetch-push.sh      | 75 +++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 150 insertions(+), 28 deletions(-)\n\n-- \n1.8.5.1-402-gdd8f092\n"},{"id":"231563","messageId":"1386206849-6503-2-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386206849-6503-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-05T01:27:27Z","receivedAt":"2013-12-05T01:27:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The command line arguments given to \"git push\" are massaged into\na list of refspecs in set_refspecs() function. This was implemented\nusing xmalloc, strcpy and friends, but it is much easier to read if\ndone using strbuf.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c | 35 ++++++++++++++---------------------\n 1 file changed, 14 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 7b1b66c..76e4400 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -41,29 +41,22 @@ static void set_refspecs(const char **refs, int nr)\n \tfor (i = 0; i < nr; i++) {\n \t\tconst char *ref = refs[i];\n \t\tif (!strcmp(\"tag\", ref)) {\n-\t\t\tchar *tag;\n-\t\t\tint len;\n+\t\t\tstruct strbuf tagref = STRBUF_INIT;\n \t\t\tif (nr <= ++i)\n \t\t\t\tdie(_(\"tag shorthand without <tag>\"));\n-\t\t\tlen = strlen(refs[i]) + 11;\n-\t\t\tif (deleterefs) {\n-\t\t\t\ttag = xmalloc(len+1);\n-\t\t\t\tstrcpy(tag, \":refs/tags/\");\n-\t\t\t} else {\n-\t\t\t\ttag = xmalloc(len);\n-\t\t\t\tstrcpy(tag, \"refs/tags/\");\n-\t\t\t}\n-\t\t\tstrcat(tag, refs[i]);\n-\t\t\tref = tag;\n-\t\t} else if (deleterefs && !strchr(ref, ':')) {\n-\t\t\tchar *delref;\n-\t\t\tint len = strlen(ref)+1;\n-\t\t\tdelref = xmalloc(len+1);\n-\t\t\tstrcpy(delref, \":\");\n-\t\t\tstrcat(delref, ref);\n-\t\t\tref = delref;\n-\t\t} else if (deleterefs)\n-\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n+\t\t\tref = refs[i];\n+\t\t\tif (deleterefs)\n+\t\t\t\tstrbuf_addf(&tagref, \":refs/tags/%s\", ref);\n+\t\t\telse\n+\t\t\t\tstrbuf_addf(&tagref, \"refs/tags/%s\", ref);\n+\t\t\tref = strbuf_detach(&tagref, NULL);\n+\t\t} else if (deleterefs) {\n+\t\t\tstruct strbuf delref = STRBUF_INIT;\n+\t\t\tif (strchr(ref, ':'))\n+\t\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n+\t\t\tstrbuf_addf(&delref, \":%s\", ref);\n+\t\t\tref = strbuf_detach(&delref, NULL);\n+\t\t}\n \t\tadd_refspec(ref);\n \t}\n }\n-- \n1.8.5.1-402-gdd8f092\n"},{"id":"231562","messageId":"1386206849-6503-3-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386206849-6503-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 2/3] push: use remote.$name.push as a refmap","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-05T01:27:28Z","receivedAt":"2013-12-05T01:27:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Since f2690487 (fetch: opportunistically update tracking refs,\n2013-05-11), we stopped taking a non-storing refspec given on the\ncommand line of \"git fetch\" literally, and instead started mapping\nit via remote.$name.fetch refspecs.  This allows\n\n    $ git fetch origin master\n\nfrom the 'origin' repository, which is configured with\n\n    [remote \"origin\"]\n        fetch = +refs/heads/*:refs/remotes/origin/*\n\nto update refs/remotes/origin/master with the result, as if the\ncommand line were\n\n    $ git fetch origin +master:refs/remotes/origin/master\n\nto reduce surprises and improve usability.  Before that change, a\nrefspec on the command line without a colon was only to fetch the\nhistory and leave the result in FETCH_HEAD, without updating the\nremote-tracking branches.\n\nWhen you are simulating a fetch from you by your mothership with a\npush by you into your mothership, instead of having:\n\n    [remote \"satellite\"]\n        fetch = +refs/heads/*:refs/remotes/satellite/*\n\non the mothership repository and running:\n\n    mothership$ git fetch satellite\n\nyou would have:\n\n    [remote \"mothership\"]\n        push = +refs/heads/*:refs/remotes/satellite/*\n\non your satellite machine, and run:\n\n    satellite$ git push mothership\n\nBecause we so far did not make the corresponding change to the push\nside, this command:\n\n    satellite$ git push mothership master\n\ndoes _not_ allow you on the satellite to only push 'master' out but\nstill to the usual destination (i.e. refs/remotes/satellite/master).\n\nImplement the logic to map an unqualified refspec given on the\ncommand line via the remote.$name.push refspec.  This will bring a\nbit more symmetry between \"fetch\" and \"push\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-push.txt |  9 +++++++--\n builtin/push.c             | 40 ++++++++++++++++++++++++++++++++++++++--\n remote.c                   |  8 ++++----\n remote.h                   |  2 ++\n t/t5516-fetch-push.sh      | 45 +++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 96 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 9eec740..2b7f4f9 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -56,8 +56,13 @@ it can be any arbitrary \"SHA-1 expression\", such as `master~4` or\n +\n The <dst> tells which ref on the remote side is updated with this\n push. Arbitrary expressions cannot be used here, an actual ref must\n-be named. If `:`<dst> is omitted, the same ref as <src> will be\n-updated.\n+be named.\n+If `git push [<repository>]` without any `<refspec>` argument is set to\n+update some ref at the destination with `<src>` with\n+`remote.<repository>.push` configuration variable, `:<dst>` part can\n+be omitted---such a push will update a ref that `<src>` normally updates\n+without any `<refspec>` on the command line.  Otherwise, missing\n+`:<dst>` means to update the same ref as the `<src>`.\n +\n The object referenced by <src> is used to update the <dst> reference\n on the remote side.  By default this is only allowed if <dst> is not\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 76e4400..857f76d 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -35,9 +35,38 @@ static void add_refspec(const char *ref)\n \trefspec[refspec_nr-1] = ref;\n }\n \n-static void set_refspecs(const char **refs, int nr)\n+static const char *map_refspec(const char *ref,\n+\t\t\t       struct remote *remote, struct ref *local_refs)\n {\n+\tstruct ref *matched = NULL;\n+\n+\t/* Does \"ref\" uniquely name our ref? */\n+\tif (count_refspec_match(ref, local_refs, &matched) != 1)\n+\t\treturn ref;\n+\n+\tif (remote->push) {\n+\t\tstruct refspec query;\n+\t\tmemset(&query, 0, sizeof(struct refspec));\n+\t\tquery.src = matched->name;\n+\t\tif (!query_refspecs(remote->push, remote->push_refspec_nr, &query) &&\n+\t\t    query.dst) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&buf, \"%s%s:%s\",\n+\t\t\t\t    query.force ? \"+\" : \"\",\n+\t\t\t\t    query.src, query.dst);\n+\t\t\treturn strbuf_detach(&buf, NULL);\n+\t\t}\n+\t}\n+\n+\treturn ref;\n+}\n+\n+static void set_refspecs(const char **refs, int nr, const char *repo)\n+{\n+\tstruct remote *remote = NULL;\n+\tstruct ref *local_refs = NULL;\n \tint i;\n+\n \tfor (i = 0; i < nr; i++) {\n \t\tconst char *ref = refs[i];\n \t\tif (!strcmp(\"tag\", ref)) {\n@@ -56,6 +85,13 @@ static void set_refspecs(const char **refs, int nr)\n \t\t\t\tdie(_(\"--delete only accepts plain target ref names\"));\n \t\t\tstrbuf_addf(&delref, \":%s\", ref);\n \t\t\tref = strbuf_detach(&delref, NULL);\n+\t\t} else if (!strchr(ref, ':')) {\n+\t\t\tif (!remote) {\n+\t\t\t\t/* lazily grab remote and local_refs */\n+\t\t\t\tremote = remote_get(repo);\n+\t\t\t\tlocal_refs = get_local_heads();\n+\t\t\t}\n+\t\t\tref = map_refspec(ref, remote, local_refs);\n \t\t}\n \t\tadd_refspec(ref);\n \t}\n@@ -487,7 +523,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \n \tif (argc > 0) {\n \t\trepo = argv[0];\n-\t\tset_refspecs(argv + 1, argc - 1);\n+\t\tset_refspecs(argv + 1, argc - 1, repo);\n \t}\n \n \trc = do_push(repo, flags);\ndiff --git a/remote.c b/remote.c\nindex 9f1a8aa..6ffa149 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -821,7 +821,7 @@ static int match_name_with_pattern(const char *key, const char *name,\n \treturn ret;\n }\n \n-static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)\n+int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)\n {\n \tint i;\n \tint find_src = !query->src;\n@@ -955,9 +955,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))\n \t*l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);\n }\n \n-static int count_refspec_match(const char *pattern,\n-\t\t\t       struct ref *refs,\n-\t\t\t       struct ref **matched_ref)\n+int count_refspec_match(const char *pattern,\n+\t\t\tstruct ref *refs,\n+\t\t\tstruct ref **matched_ref)\n {\n \tint patlen = strlen(pattern);\n \tstruct ref *matched_weak = NULL;\ndiff --git a/remote.h b/remote.h\nindex 131130a..f7d43b4 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -128,6 +128,7 @@ struct ref *alloc_ref(const char *name);\n struct ref *copy_ref(const struct ref *ref);\n struct ref *copy_ref_list(const struct ref *ref);\n void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));\n+extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);\n int ref_compare_name(const void *, const void *);\n \n int check_ref_type(const struct ref *ref, int flags);\n@@ -158,6 +159,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);\n \n void free_refspec(int nr_refspec, struct refspec *refspec);\n \n+extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);\n char *apply_refspecs(struct refspec *refspecs, int nr_refspec,\n \t\t     const char *name);\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 99c32d7..6d7f102 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1126,6 +1126,51 @@ test_expect_success 'fetch follows tags by default' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'pushing a specific ref applies remote.$name.push as refmap' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit config remote.dst.push \"+refs/heads/*:refs/remotes/src/*\" &&\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master |\n+\t\tsed -e \"s|refs/heads/|refs/remotes/src/|\" >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\ttest_must_fail git show-ref refs/heads/master &&\n+\t\tgit show-ref refs/remotes/src/master >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n+test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\tgit show-ref refs/heads/master >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n test_expect_success 'push does not follow tags by default' '\n \tmk_test testrepo heads/master &&\n \trm -fr src dst &&\n-- \n1.8.5.1-402-gdd8f092\n"},{"id":"231564","messageId":"1386206849-6503-4-git-send-email-gitster@pobox.com","threadId":"35471","inReplyTo":"1386206849-6503-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 3/3] push: also use \"upstream\" mapping when pushing a single ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-05T01:27:29Z","receivedAt":"2013-12-05T01:27:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When the user is using the 'upstream' mode, these commands:\n\n    $ git push\n    $ git push origin\n\nwould find the 'upstream' branch for the current branch, and then\npush the current branch to update it.  However, pushing a single\nbranch explicitly, i.e.\n\n    $ git push origin $(git symbolic-ref --short HEAD)\n\nwould not go through the same ref mapping process, and ends up\nupdating the branch at 'origin' of the same name, which may not\nnecessarily be the upstream of the branch being pushed.\n\nIn the spirit similar to the previous one, map a colon-less refspec\nusing the upstream mapping logic.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c        | 11 +++++++++++\n t/t5516-fetch-push.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 41 insertions(+)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 857f76d..9e47c29 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -58,6 +58,17 @@ static const char *map_refspec(const char *ref,\n \t\t}\n \t}\n \n+\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\t    !prefixcmp(matched->name, \"refs/heads/\")) {\n+\t\tstruct branch *branch = branch_get(matched->name + 11);\n+\t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&buf, \"%s:%s\",\n+\t\t\t\t    ref, branch->merge[0]->src);\n+\t\t\treturn strbuf_detach(&buf, NULL);\n+\t\t}\n+\t}\n+\n \treturn ref;\n }\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 6d7f102..926e7f6 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1160,6 +1160,7 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n \t\tgit pull ../testrepo master &&\n \t\tgit branch next &&\n \t\tgit config remote.dst.url ../dst &&\n+\t\tgit config push.default matching &&\n \t\tgit push dst master &&\n \t\tgit show-ref refs/heads/master >../dst/expect\n \t) &&\n@@ -1171,6 +1172,35 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '\n \ttest_cmp dst/expect dst/actual\n '\n \n+test_expect_success 'with no remote.$name.push, upstream mapping is used' '\n+\tmk_test testrepo heads/master &&\n+\trm -fr src dst &&\n+\tgit init src &&\n+\tgit init --bare dst &&\n+\t(\n+\t\tcd src &&\n+\t\tgit pull ../testrepo master &&\n+\t\tgit branch next &&\n+\t\tgit config remote.dst.url ../dst &&\n+\t\tgit config remote.dst.fetch \"+refs/heads/*:refs/remotes/dst/*\" &&\n+\t\tgit config push.default upstream &&\n+\n+\t\tgit config branch.master.merge refs/heads/trunk &&\n+\t\tgit config branch.master.remote dst &&\n+\n+\t\tgit push dst master &&\n+\t\tgit show-ref refs/heads/master |\n+\t\tsed -e \"s|refs/heads/master|refs/heads/trunk|\" >../dst/expect\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git show-ref refs/heads/master &&\n+\t\ttest_must_fail git show-ref refs/heads/next &&\n+\t\tgit show-ref refs/heads/trunk >actual\n+\t) &&\n+\ttest_cmp dst/expect dst/actual\n+'\n+\n test_expect_success 'push does not follow tags by default' '\n \tmk_test testrepo heads/master &&\n \trm -fr src dst &&\n-- \n1.8.5.1-402-gdd8f092\n"}]}