Skip to content

Commit 55f6fbe

Browse files
committed
Merge branch 'jc/push-follow-tag'
The new "--follow-tags" option tells "git push" to push relevant annotated tags when pushing branches out. * jc/push-follow-tag: push: --follow-tags commit.c: use clear_commit_marks_many() in in_merge_bases_many() commit.c: add in_merge_bases_many() commit.c: add clear_commit_marks_many()
2 parents 212ca64 + c2aba15 commit 55f6fbe

File tree

9 files changed

+218
-14
lines changed

9 files changed

+218
-14
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

@@ -117,6 +117,12 @@ already exists on the remote side.
117117
addition to refspecs explicitly listed on the command
118118
line.
119119

120+
--follow-tags::
121+
Push all the refs that would be pushed without this option,
122+
and also push annotated tags in `refs/tags` that are missing
123+
from the remote but are pointing at committish that are
124+
reachable from the refs being pushed.
125+
120126
--receive-pack=<git-receive-pack>::
121127
--exec=<git-receive-pack>::
122128
Path to the 'git-receive-pack' program on the remote

builtin/push.c

+2
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
437437
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
438438
TRANSPORT_PUSH_PRUNE),
439439
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
440+
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
441+
TRANSPORT_PUSH_FOLLOW_TAGS),
440442
OPT_END()
441443
};
442444

commit.c

+30-12
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,23 @@ static void clear_commit_marks_1(struct commit_list **plist,
463463
}
464464
}
465465

466-
void clear_commit_marks(struct commit *commit, unsigned int mark)
466+
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
467467
{
468468
struct commit_list *list = NULL;
469-
commit_list_insert(commit, &list);
469+
470+
while (nr--) {
471+
commit_list_insert(*commit, &list);
472+
commit++;
473+
}
470474
while (list)
471475
clear_commit_marks_1(&list, pop_commit(&list), mark);
472476
}
473477

478+
void clear_commit_marks(struct commit *commit, unsigned int mark)
479+
{
480+
clear_commit_marks_many(1, &commit, mark);
481+
}
482+
474483
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
475484
{
476485
struct object *object;
@@ -797,8 +806,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
797806
if (!result || !result->next) {
798807
if (cleanup) {
799808
clear_commit_marks(one, all_flags);
800-
for (i = 0; i < n; i++)
801-
clear_commit_marks(twos[i], all_flags);
809+
clear_commit_marks_many(n, twos, all_flags);
802810
}
803811
return result;
804812
}
@@ -816,8 +824,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
816824
free_commit_list(result);
817825

818826
clear_commit_marks(one, all_flags);
819-
for (i = 0; i < n; i++)
820-
clear_commit_marks(twos[i], all_flags);
827+
clear_commit_marks_many(n, twos, all_flags);
821828

822829
cnt = remove_redundant(rslt, cnt);
823830
result = NULL;
@@ -852,25 +859,36 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
852859
}
853860

854861
/*
855-
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
862+
* Is "commit" an ancestor of one of the "references"?
856863
*/
857-
int in_merge_bases(struct commit *commit, struct commit *reference)
864+
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
858865
{
859866
struct commit_list *bases;
860-
int ret = 0;
867+
int ret = 0, i;
861868

862-
if (parse_commit(commit) || parse_commit(reference))
869+
if (parse_commit(commit))
863870
return ret;
871+
for (i = 0; i < nr_reference; i++)
872+
if (parse_commit(reference[i]))
873+
return ret;
864874

865-
bases = paint_down_to_common(commit, 1, &reference);
875+
bases = paint_down_to_common(commit, nr_reference, reference);
866876
if (commit->object.flags & PARENT2)
867877
ret = 1;
868878
clear_commit_marks(commit, all_flags);
869-
clear_commit_marks(reference, all_flags);
879+
clear_commit_marks_many(nr_reference, reference, all_flags);
870880
free_commit_list(bases);
871881
return ret;
872882
}
873883

884+
/*
885+
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
886+
*/
887+
int in_merge_bases(struct commit *commit, struct commit *reference)
888+
{
889+
return in_merge_bases_many(commit, 1, &reference);
890+
}
891+
874892
struct commit_list *reduce_heads(struct commit_list *heads)
875893
{
876894
struct commit_list *p;

commit.h

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
137137
struct commit *pop_commit(struct commit_list **stack);
138138

139139
void clear_commit_marks(struct commit *commit, unsigned int mark);
140+
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
140141
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
141142

142143
/*
@@ -176,6 +177,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
176177

177178
int is_descendant_of(struct commit *, struct commit_list *);
178179
int in_merge_bases(struct commit *, struct commit *);
180+
int in_merge_bases_many(struct commit *, int, struct commit **);
179181

180182
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
181183
extern int run_add_interactive(const char *revision, const char *patch_mode,

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
@@ -149,7 +149,8 @@ enum match_refs_flags {
149149
MATCH_REFS_NONE = 0,
150150
MATCH_REFS_ALL = (1 << 0),
151151
MATCH_REFS_MIRROR = (1 << 1),
152-
MATCH_REFS_PRUNE = (1 << 2)
152+
MATCH_REFS_PRUNE = (1 << 2),
153+
MATCH_REFS_FOLLOW_TAGS = (1 << 3)
153154
};
154155

155156
/* Reporting of tracking info */

t/t5516-fetch-push.sh

+73
Original file line numberDiff line numberDiff line change
@@ -1077,4 +1077,77 @@ test_expect_success 'fetch exact SHA1' '
10771077
)
10781078
'
10791079

1080+
test_expect_success 'fetch follows tags by default' '
1081+
mk_test heads/master &&
1082+
rm -fr src dst &&
1083+
git init src &&
1084+
(
1085+
cd src &&
1086+
git pull ../testrepo master &&
1087+
git tag -m "annotated" tag &&
1088+
git for-each-ref >tmp1 &&
1089+
(
1090+
cat tmp1
1091+
sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
1092+
) |
1093+
sort -k 3 >../expect
1094+
) &&
1095+
git init dst &&
1096+
(
1097+
cd dst &&
1098+
git remote add origin ../src &&
1099+
git config branch.master.remote origin &&
1100+
git config branch.master.merge refs/heads/master &&
1101+
git pull &&
1102+
git for-each-ref >../actual
1103+
) &&
1104+
test_cmp expect actual
1105+
'
1106+
1107+
test_expect_success 'push does not follow tags by default' '
1108+
mk_test heads/master &&
1109+
rm -fr src dst &&
1110+
git init src &&
1111+
git init --bare dst &&
1112+
(
1113+
cd src &&
1114+
git pull ../testrepo master &&
1115+
git tag -m "annotated" tag &&
1116+
git checkout -b another &&
1117+
git commit --allow-empty -m "future commit" &&
1118+
git tag -m "future" future &&
1119+
git checkout master &&
1120+
git for-each-ref refs/heads/master >../expect &&
1121+
git push ../dst master
1122+
) &&
1123+
(
1124+
cd dst &&
1125+
git for-each-ref >../actual
1126+
) &&
1127+
test_cmp expect actual
1128+
'
1129+
1130+
test_expect_success 'push --follow-tag only pushes relevant tags' '
1131+
mk_test heads/master &&
1132+
rm -fr src dst &&
1133+
git init src &&
1134+
git init --bare dst &&
1135+
(
1136+
cd src &&
1137+
git pull ../testrepo master &&
1138+
git tag -m "annotated" tag &&
1139+
git checkout -b another &&
1140+
git commit --allow-empty -m "future commit" &&
1141+
git tag -m "future" future &&
1142+
git checkout master &&
1143+
git for-each-ref refs/heads/master refs/tags/tag >../expect
1144+
git push --follow-tag ../dst master
1145+
) &&
1146+
(
1147+
cd dst &&
1148+
git for-each-ref >../actual
1149+
) &&
1150+
test_cmp expect actual
1151+
'
1152+
10801153
test_done

transport.c

+2
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,8 @@ int transport_push(struct transport *transport,
11281128
match_flags |= MATCH_REFS_MIRROR;
11291129
if (flags & TRANSPORT_PUSH_PRUNE)
11301130
match_flags |= MATCH_REFS_PRUNE;
1131+
if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
1132+
match_flags |= MATCH_REFS_FOLLOW_TAGS;
11311133

11321134
if (match_push_refs(local_refs, &remote_refs,
11331135
refspec_nr, refspec, match_flags)) {

transport.h

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ struct transport {
105105
#define TRANSPORT_PUSH_PRUNE 128
106106
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
107107
#define TRANSPORT_PUSH_NO_HOOK 512
108+
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
108109

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

0 commit comments

Comments
 (0)