Skip to content

Commit ca02465

Browse files
committed
push: use remote.$name.push as a refmap
Since f269048 (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>
1 parent 50d829c commit ca02465

File tree

5 files changed

+96
-8
lines changed

5 files changed

+96
-8
lines changed

Documentation/git-push.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,13 @@ it can be any arbitrary "SHA-1 expression", such as `master~4` or
5656
+
5757
The <dst> tells which ref on the remote side is updated with this
5858
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>`.
6166
+
6267
The object referenced by <src> is used to update the <dst> reference
6368
on the remote side. By default this is only allowed if <dst> is not

builtin/push.c

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,38 @@ static void add_refspec(const char *ref)
3535
refspec[refspec_nr-1] = ref;
3636
}
3737

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)
3940
{
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;
4068
int i;
69+
4170
for (i = 0; i < nr; i++) {
4271
const char *ref = refs[i];
4372
if (!strcmp("tag", ref)) {
@@ -56,6 +85,13 @@ static void set_refspecs(const char **refs, int nr)
5685
die(_("--delete only accepts plain target ref names"));
5786
strbuf_addf(&delref, ":%s", ref);
5887
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);
5995
}
6096
add_refspec(ref);
6197
}
@@ -487,7 +523,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
487523

488524
if (argc > 0) {
489525
repo = argv[0];
490-
set_refspecs(argv + 1, argc - 1);
526+
set_refspecs(argv + 1, argc - 1, repo);
491527
}
492528

493529
rc = do_push(repo, flags);

remote.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ static int match_name_with_pattern(const char *key, const char *name,
821821
return ret;
822822
}
823823

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)
825825
{
826826
int i;
827827
int find_src = !query->src;
@@ -955,9 +955,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
955955
*l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
956956
}
957957

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)
961961
{
962962
int patlen = strlen(pattern);
963963
struct ref *matched_weak = NULL;

remote.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ struct ref *alloc_ref(const char *name);
128128
struct ref *copy_ref(const struct ref *ref);
129129
struct ref *copy_ref_list(const struct ref *ref);
130130
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);
131132
int ref_compare_name(const void *, const void *);
132133

133134
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);
158159

159160
void free_refspec(int nr_refspec, struct refspec *refspec);
160161

162+
extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
161163
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
162164
const char *name);
163165

t/t5516-fetch-push.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,51 @@ test_expect_success 'fetch follows tags by default' '
11261126
test_cmp expect actual
11271127
'
11281128

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+
11291174
test_expect_success 'push does not follow tags by default' '
11301175
mk_test testrepo heads/master &&
11311176
rm -fr src dst &&

0 commit comments

Comments
 (0)