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>
Paolo Bonzini committed
Apr 28, 2008 at 11:32 UTC
a83619d692deeb2565335144078465acb2dd1457
6 files changed
+116
-35
Documentation/git-push.txt
+7
-6
@@ -46,12 +46,6 @@ specified, the same ref that <src> referred to locally). If
46
the optional leading plus `+` is used, the remote ref is updated
47
even if it does not result in a fast forward update.
48
+
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
-+
49
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
50
+
51
A parameter <ref> without a colon pushes the <ref> from the source
@@ -59,6 +53,13 @@ repository to the destination repository under the same name.
53
+
54
Pushing an empty <src> allows you to delete the <dst> ref from
55
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).
63
64
\--all::
65
Instead of naming each ref to push, specifies that all
builtin-send-pack.c
+9
-1
@@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
537
int i;
538
539
for (i = 0; i < nr_heads; i++) {
540
+ const char *local = heads[i];
541
const char *remote = strrchr(heads[i], ':');
542
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;
551
switch (check_ref_format(remote)) {
552
case 0: /* ok */
553
case CHECK_REF_FORMAT_ONELEVEL:
remote.c
+54
-27
@@ -429,6 +429,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
429
}
430
431
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
+
442
if (rhs) {
443
rhs++;
444
rlen = strlen(rhs);
@@ -842,7 +852,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
852
const char *dst_value = rs->dst;
853
char *dst_guess;
854
845
- if (rs->pattern)
855
+ if (rs->pattern || rs->matching)
856
return errs;
857
858
matched_src = matched_dst = NULL;
@@ -932,13 +942,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
942
const struct ref *src)
943
{
944
int i;
945
+ int matching_refs = -1;
946
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
+
953
if (rs[i].pattern &&
954
!prefixcmp(src->name, rs[i].src) &&
955
src->name[strlen(rs[i].src)] == '/')
956
return rs + i;
957
}
941
- return NULL;
958
+ if (matching_refs != -1)
959
+ return rs + matching_refs;
960
+ else
961
+ return NULL;
962
}
963
964
/*
@@ -949,11 +969,16 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
969
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
970
int nr_refspec, const char **refspec, int flags)
971
{
952
- struct refspec *rs =
953
- parse_push_refspec(nr_refspec, (const char **) refspec);
972
+ struct refspec *rs;
973
int send_all = flags & MATCH_REFS_ALL;
974
int send_mirror = flags & MATCH_REFS_MIRROR;
975
+ static const char *default_refspec[] = { ":", 0 };
976
977
+ if (!nr_refspec) {
978
+ nr_refspec = 1;
979
+ refspec = default_refspec;
980
+ }
981
+ rs = parse_push_refspec(nr_refspec, (const char **) refspec);
982
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
983
return -1;
984
@@ -964,48 +989,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
989
char *dst_name;
990
if (src->peer_ref)
991
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) {
998
/*
999
* "matching refs"; traditionally we pushed everything
1000
* including refs outside refs/heads/ hierarchy, but
1001
* that does not make much sense these days.
1002
*/
978
- continue;
1003
+ if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
1004
+ continue;
1005
+ dst_name = xstrdup(src->name);
1006
980
- if (pat) {
1007
+ } else {
1008
const char *dst_side = pat->dst ? pat->dst : pat->src;
1009
dst_name = xmalloc(strlen(dst_side) +
1010
strlen(src->name) -
1011
strlen(pat->src) + 2);
1012
strcpy(dst_name, dst_side);
1013
strcat(dst_name, src->name + strlen(pat->src));
987
- } else
988
- dst_name = xstrdup(src->name);
1014
+ }
1015
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;
1029
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) {
1030
/* Create a new one and link it */
1031
dst_peer = make_linked_ref(dst_name, dst_tail);
1032
hashcpy(dst_peer->new_sha1, src->new_sha1);
1033
}
1034
dst_peer->peer_ref = src;
1007
- if (pat)
1008
- dst_peer->force = pat->force;
1035
+ dst_peer->force = pat->force;
1036
free_name:
1037
free(dst_name);
1038
}
remote.h
+1
@@ -46,6 +46,7 @@ int remote_has_url(struct remote *remote, const char *url);
46
struct refspec {
47
unsigned force : 1;
48
unsigned pattern : 1;
49
+ unsigned matching : 1;
50
51
char *src;
52
char *dst;
t/t5511-refspec.sh
+4
-1
@@ -23,10 +23,13 @@ test_refspec () {
23
}
24
25
test_refspec push '' invalid
26
-test_refspec push ':' invalid
26
+test_refspec push ':'
27
+test_refspec push '::' invalid
28
+test_refspec push '+:'
29
30
test_refspec fetch ''
31
test_refspec fetch ':'
32
+test_refspec fetch '::' invalid
33
34
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
35
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid
t/t5516-fetch-push.sh
+41
@@ -165,6 +165,47 @@ test_expect_success 'push with matching heads' '
165
166
'
167
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
+
209
test_expect_success 'push with no ambiguity (1)' '
210
211
mk_test heads/master &&