push: use remote.$name.push as a refmap

Since f2690487 (fetch: opportunistically update tracking refs, 2013-05-11), we stopped taking a non-storing refspec given on the command line of "git fetch" literally, and instead started mapping it via remote.$name.fetch refspecs. This allows $ git fetch origin master from the 'origin' repository, which is configured with [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* to update refs/remotes/origin/master with the result, as if the command line were $ git fetch origin +master:refs/remotes/origin/master to reduce surprises and improve usability. Before that change, a refspec on the command line without a colon was only to fetch the history and leave the result in FETCH_HEAD, without updating the remote-tracking branches. When you are simulating a fetch from you by your mothership with a push by you into your mothership, instead of having: [remote "satellite"] fetch = +refs/heads/*:refs/remotes/satellite/* on the mothership repository and running: mothership$ git fetch satellite you would have: [remote "mothership"] push = +refs/heads/*:refs/remotes/satellite/* on your satellite machine, and run: satellite$ git push mothership Because we so far did not make the corresponding change to the push side, this command: satellite$ git push mothership master does _not_ allow you on the satellite to only push 'master' out but still to the usual destination (i.e. refs/remotes/satellite/master). Implement the logic to map an unqualified refspec given on the command line via the remote.$name.push refspec. This will bring a bit more symmetry between "fetch" and "push". Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Dec 3, 2013 at 15:41 UTC ca02465b41311fe7634acb9bb5b5c61975ef5f38
5 files changed +96 -8
Documentation/git-push.txt
+7 -2
@@ -56,8 +56,13 @@ it can be any arbitrary "SHA-1 expression", such as `master~4` or
56 +
57 The <dst> tells which ref on the remote side is updated with this
58 push. Arbitrary expressions cannot be used here, an actual ref must
59 -be named. If `:`<dst> is omitted, the same ref as <src> will be
60 -updated.
59 +be named.
60 +If `git push [<repository>]` without any `<refspec>` argument is set to
61 +update some ref at the destination with `<src>` with
62 +`remote.<repository>.push` configuration variable, `:<dst>` part can
63 +be omitted---such a push will update a ref that `<src>` normally updates
64 +without any `<refspec>` on the command line. Otherwise, missing
65 +`:<dst>` means to update the same ref as the `<src>`.
66 +
67 The object referenced by <src> is used to update the <dst> reference
68 on the remote side. By default this is only allowed if <dst> is not
builtin/push.c
+38 -2
@@ -35,9 +35,38 @@ static void add_refspec(const char *ref)
35 refspec[refspec_nr-1] = ref;
36 }
37
38 -static void set_refspecs(const char **refs, int nr)
38 +static const char *map_refspec(const char *ref,
39 + struct remote *remote, struct ref *local_refs)
40 {
41 + struct ref *matched = NULL;
42 +
43 + /* Does "ref" uniquely name our ref? */
44 + if (count_refspec_match(ref, local_refs, &matched) != 1)
45 + return ref;
46 +
47 + if (remote->push) {
48 + struct refspec query;
49 + memset(&query, 0, sizeof(struct refspec));
50 + query.src = matched->name;
51 + if (!query_refspecs(remote->push, remote->push_refspec_nr, &query) &&
52 + query.dst) {
53 + struct strbuf buf = STRBUF_INIT;
54 + strbuf_addf(&buf, "%s%s:%s",
55 + query.force ? "+" : "",
56 + query.src, query.dst);
57 + return strbuf_detach(&buf, NULL);
58 + }
59 + }
60 +
61 + return ref;
62 +}
63 +
64 +static void set_refspecs(const char **refs, int nr, const char *repo)
65 +{
66 + struct remote *remote = NULL;
67 + struct ref *local_refs = NULL;
68 int i;
69 +
70 for (i = 0; i < nr; i++) {
71 const char *ref = refs[i];
72 if (!strcmp("tag", ref)) {
@@ -56,6 +85,13 @@ static void set_refspecs(const char **refs, int nr)
85 die(_("--delete only accepts plain target ref names"));
86 strbuf_addf(&delref, ":%s", ref);
87 ref = strbuf_detach(&delref, NULL);
88 + } else if (!strchr(ref, ':')) {
89 + if (!remote) {
90 + /* lazily grab remote and local_refs */
91 + remote = remote_get(repo);
92 + local_refs = get_local_heads();
93 + }
94 + ref = map_refspec(ref, remote, local_refs);
95 }
96 add_refspec(ref);
97 }
@@ -487,7 +523,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
523
524 if (argc > 0) {
525 repo = argv[0];
490 - set_refspecs(argv + 1, argc - 1);
526 + set_refspecs(argv + 1, argc - 1, repo);
527 }
528
529 rc = do_push(repo, flags);
remote.c
+4 -4
@@ -821,7 +821,7 @@ static int match_name_with_pattern(const char *key, const char *name,
821 return ret;
822 }
823
824 -static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
824 +int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
825 {
826 int i;
827 int find_src = !query->src;
@@ -955,9 +955,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
955 *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
956 }
957
958 -static int count_refspec_match(const char *pattern,
959 - struct ref *refs,
960 - struct ref **matched_ref)
958 +int count_refspec_match(const char *pattern,
959 + struct ref *refs,
960 + struct ref **matched_ref)
961 {
962 int patlen = strlen(pattern);
963 struct ref *matched_weak = NULL;
remote.h
+2
@@ -128,6 +128,7 @@ struct ref *alloc_ref(const char *name);
128 struct ref *copy_ref(const struct ref *ref);
129 struct ref *copy_ref_list(const struct ref *ref);
130 void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
131 +extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
132 int ref_compare_name(const void *, const void *);
133
134 int check_ref_type(const struct ref *ref, int flags);
@@ -158,6 +159,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
159
160 void free_refspec(int nr_refspec, struct refspec *refspec);
161
162 +extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
163 char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
164 const char *name);
165
t/t5516-fetch-push.sh
+45
@@ -1126,6 +1126,51 @@ test_expect_success 'fetch follows tags by default' '
1126 test_cmp expect actual
1127 '
1128
1129 +test_expect_success 'pushing a specific ref applies remote.$name.push as refmap' '
1130 + mk_test testrepo heads/master &&
1131 + rm -fr src dst &&
1132 + git init src &&
1133 + git init --bare dst &&
1134 + (
1135 + cd src &&
1136 + git pull ../testrepo master &&
1137 + git branch next &&
1138 + git config remote.dst.url ../dst &&
1139 + git config remote.dst.push "+refs/heads/*:refs/remotes/src/*" &&
1140 + git push dst master &&
1141 + git show-ref refs/heads/master |
1142 + sed -e "s|refs/heads/|refs/remotes/src/|" >../dst/expect
1143 + ) &&
1144 + (
1145 + cd dst &&
1146 + test_must_fail git show-ref refs/heads/next &&
1147 + test_must_fail git show-ref refs/heads/master &&
1148 + git show-ref refs/remotes/src/master >actual
1149 + ) &&
1150 + test_cmp dst/expect dst/actual
1151 +'
1152 +
1153 +test_expect_success 'with no remote.$name.push, it is not used as refmap' '
1154 + mk_test testrepo heads/master &&
1155 + rm -fr src dst &&
1156 + git init src &&
1157 + git init --bare dst &&
1158 + (
1159 + cd src &&
1160 + git pull ../testrepo master &&
1161 + git branch next &&
1162 + git config remote.dst.url ../dst &&
1163 + git push dst master &&
1164 + git show-ref refs/heads/master >../dst/expect
1165 + ) &&
1166 + (
1167 + cd dst &&
1168 + test_must_fail git show-ref refs/heads/next &&
1169 + git show-ref refs/heads/master >actual
1170 + ) &&
1171 + test_cmp dst/expect dst/actual
1172 +'
1173 +
1174 test_expect_success 'push does not follow tags by default' '
1175 mk_test testrepo heads/master &&
1176 rm -fr src dst &&