Skip to content

Commit a83619d

Browse files
bonzinigitster
authored andcommitted
add special "matching refs" refspec
This patch provides a way to specify "push matching heads" using a special refspec ":". This is useful because it allows "push = +:" as a way to specify that matching refs will be pushed but, in addition, forced updates will be allowed, which was not possible before. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent c697ad1 commit a83619d

File tree

6 files changed

+116
-35
lines changed

6 files changed

+116
-35
lines changed

Documentation/git-push.txt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,20 @@ specified, the same ref that <src> referred to locally). If
4646
the optional leading plus `+` is used, the remote ref is updated
4747
even if it does not result in a fast forward update.
4848
+
49-
Note: If no explicit refspec is found, (that is neither
50-
on the command line nor in any Push line of the
51-
corresponding remotes file---see below), then "matching" heads are
52-
pushed: for every head that exists on the local side, the remote side is
53-
updated if a head of the same name already exists on the remote side.
54-
+
5549
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
5650
+
5751
A parameter <ref> without a colon pushes the <ref> from the source
5852
repository to the destination repository under the same name.
5953
+
6054
Pushing an empty <src> allows you to delete the <dst> ref from
6155
the remote repository.
56+
+
57+
The special refspec `:` (or `+:` to allow non-fast forward updates)
58+
directs git to push "matching" heads: for every head that exists on
59+
the local side, the remote side is updated if a head of the same name
60+
already exists on the remote side. This is the default operation mode
61+
if no explicit refspec is found (that is neither on the command line
62+
nor in any Push line of the corresponding remotes file---see below).
6263

6364
\--all::
6465
Instead of naming each ref to push, specifies that all

builtin-send-pack.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
537537
int i;
538538

539539
for (i = 0; i < nr_heads; i++) {
540+
const char *local = heads[i];
540541
const char *remote = strrchr(heads[i], ':');
541542

542-
remote = remote ? (remote + 1) : heads[i];
543+
if (*local == '+')
544+
local++;
545+
546+
/* A matching refspec is okay. */
547+
if (remote == local && remote[1] == '\0')
548+
continue;
549+
550+
remote = remote ? (remote + 1) : local;
543551
switch (check_ref_format(remote)) {
544552
case 0: /* ok */
545553
case CHECK_REF_FORMAT_ONELEVEL:

remote.c

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
429429
}
430430

431431
rhs = strrchr(lhs, ':');
432+
433+
/*
434+
* Before going on, special case ":" (or "+:") as a refspec
435+
* for matching refs.
436+
*/
437+
if (!fetch && rhs == lhs && rhs[1] == '\0') {
438+
rs[i].matching = 1;
439+
continue;
440+
}
441+
432442
if (rhs) {
433443
rhs++;
434444
rlen = strlen(rhs);
@@ -842,7 +852,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
842852
const char *dst_value = rs->dst;
843853
char *dst_guess;
844854

845-
if (rs->pattern)
855+
if (rs->pattern || rs->matching)
846856
return errs;
847857

848858
matched_src = matched_dst = NULL;
@@ -932,13 +942,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
932942
const struct ref *src)
933943
{
934944
int i;
945+
int matching_refs = -1;
935946
for (i = 0; i < rs_nr; i++) {
947+
if (rs[i].matching &&
948+
(matching_refs == -1 || rs[i].force)) {
949+
matching_refs = i;
950+
continue;
951+
}
952+
936953
if (rs[i].pattern &&
937954
!prefixcmp(src->name, rs[i].src) &&
938955
src->name[strlen(rs[i].src)] == '/')
939956
return rs + i;
940957
}
941-
return NULL;
958+
if (matching_refs != -1)
959+
return rs + matching_refs;
960+
else
961+
return NULL;
942962
}
943963

944964
/*
@@ -949,11 +969,16 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
949969
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
950970
int nr_refspec, const char **refspec, int flags)
951971
{
952-
struct refspec *rs =
953-
parse_push_refspec(nr_refspec, (const char **) refspec);
972+
struct refspec *rs;
954973
int send_all = flags & MATCH_REFS_ALL;
955974
int send_mirror = flags & MATCH_REFS_MIRROR;
975+
static const char *default_refspec[] = { ":", 0 };
956976

977+
if (!nr_refspec) {
978+
nr_refspec = 1;
979+
refspec = default_refspec;
980+
}
981+
rs = parse_push_refspec(nr_refspec, (const char **) refspec);
957982
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
958983
return -1;
959984

@@ -964,48 +989,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
964989
char *dst_name;
965990
if (src->peer_ref)
966991
continue;
967-
if (nr_refspec) {
968-
pat = check_pattern_match(rs, nr_refspec, src);
969-
if (!pat)
970-
continue;
971-
}
972-
else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
992+
993+
pat = check_pattern_match(rs, nr_refspec, src);
994+
if (!pat)
995+
continue;
996+
997+
if (pat->matching) {
973998
/*
974999
* "matching refs"; traditionally we pushed everything
9751000
* including refs outside refs/heads/ hierarchy, but
9761001
* that does not make much sense these days.
9771002
*/
978-
continue;
1003+
if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
1004+
continue;
1005+
dst_name = xstrdup(src->name);
9791006

980-
if (pat) {
1007+
} else {
9811008
const char *dst_side = pat->dst ? pat->dst : pat->src;
9821009
dst_name = xmalloc(strlen(dst_side) +
9831010
strlen(src->name) -
9841011
strlen(pat->src) + 2);
9851012
strcpy(dst_name, dst_side);
9861013
strcat(dst_name, src->name + strlen(pat->src));
987-
} else
988-
dst_name = xstrdup(src->name);
1014+
}
9891015
dst_peer = find_ref_by_name(dst, dst_name);
990-
if (dst_peer && dst_peer->peer_ref)
991-
/* We're already sending something to this ref. */
992-
goto free_name;
1016+
if (dst_peer) {
1017+
if (dst_peer->peer_ref)
1018+
/* We're already sending something to this ref. */
1019+
goto free_name;
1020+
1021+
} else {
1022+
if (pat->matching && !(send_all || send_mirror))
1023+
/*
1024+
* Remote doesn't have it, and we have no
1025+
* explicit pattern, and we don't have
1026+
* --all nor --mirror.
1027+
*/
1028+
goto free_name;
9931029

994-
if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
995-
/*
996-
* Remote doesn't have it, and we have no
997-
* explicit pattern, and we don't have
998-
* --all nor --mirror.
999-
*/
1000-
goto free_name;
1001-
if (!dst_peer) {
10021030
/* Create a new one and link it */
10031031
dst_peer = make_linked_ref(dst_name, dst_tail);
10041032
hashcpy(dst_peer->new_sha1, src->new_sha1);
10051033
}
10061034
dst_peer->peer_ref = src;
1007-
if (pat)
1008-
dst_peer->force = pat->force;
1035+
dst_peer->force = pat->force;
10091036
free_name:
10101037
free(dst_name);
10111038
}

remote.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ int remote_has_url(struct remote *remote, const char *url);
4646
struct refspec {
4747
unsigned force : 1;
4848
unsigned pattern : 1;
49+
unsigned matching : 1;
4950

5051
char *src;
5152
char *dst;

t/t5511-refspec.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ test_refspec () {
2323
}
2424

2525
test_refspec push '' invalid
26-
test_refspec push ':' invalid
26+
test_refspec push ':'
27+
test_refspec push '::' invalid
28+
test_refspec push '+:'
2729

2830
test_refspec fetch ''
2931
test_refspec fetch ':'
32+
test_refspec fetch '::' invalid
3033

3134
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
3235
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid

t/t5516-fetch-push.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,47 @@ test_expect_success 'push with matching heads' '
165165
166166
'
167167

168+
test_expect_success 'push with matching heads on the command line' '
169+
170+
mk_test heads/master &&
171+
git push testrepo : &&
172+
check_push_result $the_commit heads/master
173+
174+
'
175+
176+
test_expect_success 'failed (non-fast-forward) push with matching heads' '
177+
178+
mk_test heads/master &&
179+
git push testrepo : &&
180+
git commit --amend -massaged &&
181+
! git push testrepo &&
182+
check_push_result $the_commit heads/master &&
183+
git reset --hard $the_commit
184+
185+
'
186+
187+
test_expect_success 'push --force with matching heads' '
188+
189+
mk_test heads/master &&
190+
git push testrepo : &&
191+
git commit --amend -massaged &&
192+
git push --force testrepo &&
193+
! check_push_result $the_commit heads/master &&
194+
git reset --hard $the_commit
195+
196+
'
197+
198+
test_expect_success 'push with matching heads and forced update' '
199+
200+
mk_test heads/master &&
201+
git push testrepo : &&
202+
git commit --amend -massaged &&
203+
git push testrepo +: &&
204+
! check_push_result $the_commit heads/master &&
205+
git reset --hard $the_commit
206+
207+
'
208+
168209
test_expect_success 'push with no ambiguity (1)' '
169210
170211
mk_test heads/master &&

0 commit comments

Comments
 (0)