Skip to content

Commit c2aba15

Browse files
committed
push: --follow-tags
The new option "--follow-tags" tells "git push" to push annotated tags that are missing from the other side and that can be reached by the history that is otherwise pushed out. For example, if you are using the "simple", "current", or "upstream" push, you would ordinarily push the history leading to the commit at your current HEAD and nothing else. With this option, you would also push all annotated tags that can be reached from that commit to the other side. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 557899f commit c2aba15

File tree

7 files changed

+186
-2
lines changed

7 files changed

+186
-2
lines changed

Documentation/git-push.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
12+
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
1313
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
1414
[<repository> [<refspec>...]]
1515

@@ -111,6 +111,12 @@ no `push.default` configuration variable is set.
111111
addition to refspecs explicitly listed on the command
112112
line.
113113

114+
--follow-tags::
115+
Push all the refs that would be pushed without this option,
116+
and also push annotated tags in `refs/tags` that are missing
117+
from the remote but are pointing at committish that are
118+
reachable from the refs being pushed.
119+
114120
--receive-pack=<git-receive-pack>::
115121
--exec=<git-receive-pack>::
116122
Path to the 'git-receive-pack' program on the remote

builtin/push.c

+2
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
399399
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
400400
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
401401
TRANSPORT_PUSH_PRUNE),
402+
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
403+
TRANSPORT_PUSH_FOLLOW_TAGS),
402404
OPT_END()
403405
};
404406

remote.c

+99
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,101 @@ static struct ref **tail_ref(struct ref **head)
11951195
return tail;
11961196
}
11971197

1198+
struct tips {
1199+
struct commit **tip;
1200+
int nr, alloc;
1201+
};
1202+
1203+
static void add_to_tips(struct tips *tips, const unsigned char *sha1)
1204+
{
1205+
struct commit *commit;
1206+
1207+
if (is_null_sha1(sha1))
1208+
return;
1209+
commit = lookup_commit_reference_gently(sha1, 1);
1210+
if (!commit || (commit->object.flags & TMP_MARK))
1211+
return;
1212+
commit->object.flags |= TMP_MARK;
1213+
ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
1214+
tips->tip[tips->nr++] = commit;
1215+
}
1216+
1217+
static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
1218+
{
1219+
struct string_list dst_tag = STRING_LIST_INIT_NODUP;
1220+
struct string_list src_tag = STRING_LIST_INIT_NODUP;
1221+
struct string_list_item *item;
1222+
struct ref *ref;
1223+
struct tips sent_tips;
1224+
1225+
/*
1226+
* Collect everything we know they would have at the end of
1227+
* this push, and collect all tags they have.
1228+
*/
1229+
memset(&sent_tips, 0, sizeof(sent_tips));
1230+
for (ref = *dst; ref; ref = ref->next) {
1231+
if (ref->peer_ref &&
1232+
!is_null_sha1(ref->peer_ref->new_sha1))
1233+
add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
1234+
else
1235+
add_to_tips(&sent_tips, ref->old_sha1);
1236+
if (!prefixcmp(ref->name, "refs/tags/"))
1237+
string_list_append(&dst_tag, ref->name);
1238+
}
1239+
clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
1240+
1241+
sort_string_list(&dst_tag);
1242+
1243+
/* Collect tags they do not have. */
1244+
for (ref = src; ref; ref = ref->next) {
1245+
if (prefixcmp(ref->name, "refs/tags/"))
1246+
continue; /* not a tag */
1247+
if (string_list_has_string(&dst_tag, ref->name))
1248+
continue; /* they already have it */
1249+
if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG)
1250+
continue; /* be conservative */
1251+
item = string_list_append(&src_tag, ref->name);
1252+
item->util = ref;
1253+
}
1254+
string_list_clear(&dst_tag, 0);
1255+
1256+
/*
1257+
* At this point, src_tag lists tags that are missing from
1258+
* dst, and sent_tips lists the tips we are pushing or those
1259+
* that we know they already have. An element in the src_tag
1260+
* that is an ancestor of any of the sent_tips needs to be
1261+
* sent to the other side.
1262+
*/
1263+
if (sent_tips.nr) {
1264+
for_each_string_list_item(item, &src_tag) {
1265+
struct ref *ref = item->util;
1266+
struct ref *dst_ref;
1267+
struct commit *commit;
1268+
1269+
if (is_null_sha1(ref->new_sha1))
1270+
continue;
1271+
commit = lookup_commit_reference_gently(ref->new_sha1, 1);
1272+
if (!commit)
1273+
/* not pushing a commit, which is not an error */
1274+
continue;
1275+
1276+
/*
1277+
* Is this tag, which they do not have, reachable from
1278+
* any of the commits we are sending?
1279+
*/
1280+
if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
1281+
continue;
1282+
1283+
/* Add it in */
1284+
dst_ref = make_linked_ref(ref->name, dst_tail);
1285+
hashcpy(dst_ref->new_sha1, ref->new_sha1);
1286+
dst_ref->peer_ref = copy_ref(ref);
1287+
}
1288+
}
1289+
string_list_clear(&src_tag, 0);
1290+
free(sent_tips.tip);
1291+
}
1292+
11981293
/*
11991294
* Given the set of refs the local repository has, the set of refs the
12001295
* remote repository has, and the refspec used for push, determine
@@ -1257,6 +1352,10 @@ int match_push_refs(struct ref *src, struct ref **dst,
12571352
free_name:
12581353
free(dst_name);
12591354
}
1355+
1356+
if (flags & MATCH_REFS_FOLLOW_TAGS)
1357+
add_missing_tags(src, dst, &dst_tail);
1358+
12601359
if (send_prune) {
12611360
/* check for missing refs on the remote */
12621361
for (ref = *dst; ref; ref = ref->next) {

remote.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ enum match_refs_flags {
148148
MATCH_REFS_NONE = 0,
149149
MATCH_REFS_ALL = (1 << 0),
150150
MATCH_REFS_MIRROR = (1 << 1),
151-
MATCH_REFS_PRUNE = (1 << 2)
151+
MATCH_REFS_PRUNE = (1 << 2),
152+
MATCH_REFS_FOLLOW_TAGS = (1 << 3)
152153
};
153154

154155
/* Reporting of tracking info */

t/t5516-fetch-push.sh

+73
Original file line numberDiff line numberDiff line change
@@ -995,4 +995,77 @@ test_expect_success 'push --prune refspec' '
995995
! check_push_result $the_first_commit tmp/foo tmp/bar
996996
'
997997

998+
test_expect_success 'fetch follows tags by default' '
999+
mk_test heads/master &&
1000+
rm -fr src dst &&
1001+
git init src &&
1002+
(
1003+
cd src &&
1004+
git pull ../testrepo master &&
1005+
git tag -m "annotated" tag &&
1006+
git for-each-ref >tmp1 &&
1007+
(
1008+
cat tmp1
1009+
sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
1010+
) |
1011+
sort -k 3 >../expect
1012+
) &&
1013+
git init dst &&
1014+
(
1015+
cd dst &&
1016+
git remote add origin ../src &&
1017+
git config branch.master.remote origin &&
1018+
git config branch.master.merge refs/heads/master &&
1019+
git pull &&
1020+
git for-each-ref >../actual
1021+
) &&
1022+
test_cmp expect actual
1023+
'
1024+
1025+
test_expect_success 'push does not follow tags by default' '
1026+
mk_test heads/master &&
1027+
rm -fr src dst &&
1028+
git init src &&
1029+
git init --bare dst &&
1030+
(
1031+
cd src &&
1032+
git pull ../testrepo master &&
1033+
git tag -m "annotated" tag &&
1034+
git checkout -b another &&
1035+
git commit --allow-empty -m "future commit" &&
1036+
git tag -m "future" future &&
1037+
git checkout master &&
1038+
git for-each-ref refs/heads/master >../expect &&
1039+
git push ../dst master
1040+
) &&
1041+
(
1042+
cd dst &&
1043+
git for-each-ref >../actual
1044+
) &&
1045+
test_cmp expect actual
1046+
'
1047+
1048+
test_expect_success 'push --follow-tag only pushes relevant tags' '
1049+
mk_test heads/master &&
1050+
rm -fr src dst &&
1051+
git init src &&
1052+
git init --bare dst &&
1053+
(
1054+
cd src &&
1055+
git pull ../testrepo master &&
1056+
git tag -m "annotated" tag &&
1057+
git checkout -b another &&
1058+
git commit --allow-empty -m "future commit" &&
1059+
git tag -m "future" future &&
1060+
git checkout master &&
1061+
git for-each-ref refs/heads/master refs/tags/tag >../expect
1062+
git push --follow-tag ../dst master
1063+
) &&
1064+
(
1065+
cd dst &&
1066+
git for-each-ref >../actual
1067+
) &&
1068+
test_cmp expect actual
1069+
'
1070+
9981071
test_done

transport.c

+2
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,8 @@ int transport_push(struct transport *transport,
10591059
match_flags |= MATCH_REFS_MIRROR;
10601060
if (flags & TRANSPORT_PUSH_PRUNE)
10611061
match_flags |= MATCH_REFS_PRUNE;
1062+
if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
1063+
match_flags |= MATCH_REFS_FOLLOW_TAGS;
10621064

10631065
if (match_push_refs(local_refs, &remote_refs,
10641066
refspec_nr, refspec, match_flags)) {

transport.h

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ struct transport {
104104
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
105105
#define TRANSPORT_PUSH_PRUNE 128
106106
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
107+
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
107108

108109
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
109110
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)

0 commit comments

Comments
 (0)