Skip to content

Commit 7cdebd8

Browse files
committed
Merge branch 'jc/push-refmap'
Make "git push origin master" update the same ref that would be updated by our 'master' when "git push origin" (no refspecs) is run while the 'master' branch is checked out, which makes "git push" more symmetric to "git fetch" and more usable for the triangular workflow. * jc/push-refmap: push: also use "upstream" mapping when pushing a single ref push: use remote.$name.push as a refmap builtin/push.c: use strbuf instead of manual allocation
2 parents 7794a68 + fc9261c commit 7cdebd8

File tree

5 files changed

+150
-28
lines changed

5 files changed

+150
-28
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: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,35 +35,75 @@ 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+
if (push_default == PUSH_DEFAULT_UPSTREAM &&
62+
!prefixcmp(matched->name, "refs/heads/")) {
63+
struct branch *branch = branch_get(matched->name + 11);
64+
if (branch->merge_nr == 1 && branch->merge[0]->src) {
65+
struct strbuf buf = STRBUF_INIT;
66+
strbuf_addf(&buf, "%s:%s",
67+
ref, branch->merge[0]->src);
68+
return strbuf_detach(&buf, NULL);
69+
}
70+
}
71+
72+
return ref;
73+
}
74+
75+
static void set_refspecs(const char **refs, int nr, const char *repo)
76+
{
77+
struct remote *remote = NULL;
78+
struct ref *local_refs = NULL;
4079
int i;
80+
4181
for (i = 0; i < nr; i++) {
4282
const char *ref = refs[i];
4383
if (!strcmp("tag", ref)) {
44-
char *tag;
45-
int len;
84+
struct strbuf tagref = STRBUF_INIT;
4685
if (nr <= ++i)
4786
die(_("tag shorthand without <tag>"));
48-
len = strlen(refs[i]) + 11;
49-
if (deleterefs) {
50-
tag = xmalloc(len+1);
51-
strcpy(tag, ":refs/tags/");
52-
} else {
53-
tag = xmalloc(len);
54-
strcpy(tag, "refs/tags/");
87+
ref = refs[i];
88+
if (deleterefs)
89+
strbuf_addf(&tagref, ":refs/tags/%s", ref);
90+
else
91+
strbuf_addf(&tagref, "refs/tags/%s", ref);
92+
ref = strbuf_detach(&tagref, NULL);
93+
} else if (deleterefs) {
94+
struct strbuf delref = STRBUF_INIT;
95+
if (strchr(ref, ':'))
96+
die(_("--delete only accepts plain target ref names"));
97+
strbuf_addf(&delref, ":%s", ref);
98+
ref = strbuf_detach(&delref, NULL);
99+
} else if (!strchr(ref, ':')) {
100+
if (!remote) {
101+
/* lazily grab remote and local_refs */
102+
remote = remote_get(repo);
103+
local_refs = get_local_heads();
55104
}
56-
strcat(tag, refs[i]);
57-
ref = tag;
58-
} else if (deleterefs && !strchr(ref, ':')) {
59-
char *delref;
60-
int len = strlen(ref)+1;
61-
delref = xmalloc(len+1);
62-
strcpy(delref, ":");
63-
strcat(delref, ref);
64-
ref = delref;
65-
} else if (deleterefs)
66-
die(_("--delete only accepts plain target ref names"));
105+
ref = map_refspec(ref, remote, local_refs);
106+
}
67107
add_refspec(ref);
68108
}
69109
}
@@ -501,7 +541,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
501541

502542
if (argc > 0) {
503543
repo = argv[0];
504-
set_refspecs(argv + 1, argc - 1);
544+
set_refspecs(argv + 1, argc - 1, repo);
505545
}
506546

507547
rc = do_push(repo, flags);

remote.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ static int match_name_with_pattern(const char *key, const char *name,
852852
return ret;
853853
}
854854

855-
static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
855+
int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
856856
{
857857
int i;
858858
int find_src = !query->src;
@@ -986,9 +986,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
986986
*l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
987987
}
988988

989-
static int count_refspec_match(const char *pattern,
990-
struct ref *refs,
991-
struct ref **matched_ref)
989+
int count_refspec_match(const char *pattern,
990+
struct ref *refs,
991+
struct ref **matched_ref)
992992
{
993993
int patlen = strlen(pattern);
994994
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);
@@ -162,6 +163,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
162163

163164
void free_refspec(int nr_refspec, struct refspec *refspec);
164165

166+
extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
165167
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
166168
const char *name);
167169

t/t5516-fetch-push.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,81 @@ 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 config push.default matching &&
1164+
git push dst master &&
1165+
git show-ref refs/heads/master >../dst/expect
1166+
) &&
1167+
(
1168+
cd dst &&
1169+
test_must_fail git show-ref refs/heads/next &&
1170+
git show-ref refs/heads/master >actual
1171+
) &&
1172+
test_cmp dst/expect dst/actual
1173+
'
1174+
1175+
test_expect_success 'with no remote.$name.push, upstream mapping is used' '
1176+
mk_test testrepo heads/master &&
1177+
rm -fr src dst &&
1178+
git init src &&
1179+
git init --bare dst &&
1180+
(
1181+
cd src &&
1182+
git pull ../testrepo master &&
1183+
git branch next &&
1184+
git config remote.dst.url ../dst &&
1185+
git config remote.dst.fetch "+refs/heads/*:refs/remotes/dst/*" &&
1186+
git config push.default upstream &&
1187+
1188+
git config branch.master.merge refs/heads/trunk &&
1189+
git config branch.master.remote dst &&
1190+
1191+
git push dst master &&
1192+
git show-ref refs/heads/master |
1193+
sed -e "s|refs/heads/master|refs/heads/trunk|" >../dst/expect
1194+
) &&
1195+
(
1196+
cd dst &&
1197+
test_must_fail git show-ref refs/heads/master &&
1198+
test_must_fail git show-ref refs/heads/next &&
1199+
git show-ref refs/heads/trunk >actual
1200+
) &&
1201+
test_cmp dst/expect dst/actual
1202+
'
1203+
11291204
test_expect_success 'push does not follow tags by default' '
11301205
mk_test testrepo heads/master &&
11311206
rm -fr src dst &&

0 commit comments

Comments
 (0)