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()
This commit is contained in:
commit
55f6fbef3d
@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
|
||||
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
|
||||
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
|
||||
[<repository> [<refspec>...]]
|
||||
|
||||
@ -117,6 +117,12 @@ already exists on the remote side.
|
||||
addition to refspecs explicitly listed on the command
|
||||
line.
|
||||
|
||||
--follow-tags::
|
||||
Push all the refs that would be pushed without this option,
|
||||
and also push annotated tags in `refs/tags` that are missing
|
||||
from the remote but are pointing at committish that are
|
||||
reachable from the refs being pushed.
|
||||
|
||||
--receive-pack=<git-receive-pack>::
|
||||
--exec=<git-receive-pack>::
|
||||
Path to the 'git-receive-pack' program on the remote
|
||||
|
@ -437,6 +437,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
|
||||
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
|
||||
TRANSPORT_PUSH_PRUNE),
|
||||
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
|
||||
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
|
||||
TRANSPORT_PUSH_FOLLOW_TAGS),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
56
commit.c
56
commit.c
@ -463,14 +463,23 @@ static void clear_commit_marks_1(struct commit_list **plist,
|
||||
}
|
||||
}
|
||||
|
||||
void clear_commit_marks(struct commit *commit, unsigned int mark)
|
||||
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
|
||||
{
|
||||
struct commit_list *list = NULL;
|
||||
commit_list_insert(commit, &list);
|
||||
|
||||
while (nr--) {
|
||||
commit_list_insert(*commit, &list);
|
||||
commit++;
|
||||
}
|
||||
while (list)
|
||||
clear_commit_marks_1(&list, pop_commit(&list), mark);
|
||||
}
|
||||
|
||||
void clear_commit_marks(struct commit *commit, unsigned int mark)
|
||||
{
|
||||
clear_commit_marks_many(1, &commit, mark);
|
||||
}
|
||||
|
||||
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
|
||||
{
|
||||
struct object *object;
|
||||
@ -797,8 +806,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
if (!result || !result->next) {
|
||||
if (cleanup) {
|
||||
clear_commit_marks(one, all_flags);
|
||||
for (i = 0; i < n; i++)
|
||||
clear_commit_marks(twos[i], all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -816,8 +824,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
free_commit_list(result);
|
||||
|
||||
clear_commit_marks(one, all_flags);
|
||||
for (i = 0; i < n; i++)
|
||||
clear_commit_marks(twos[i], all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
|
||||
cnt = remove_redundant(rslt, cnt);
|
||||
result = NULL;
|
||||
@ -851,24 +858,35 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of one of the "references"?
|
||||
*/
|
||||
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
|
||||
{
|
||||
struct commit_list *bases;
|
||||
int ret = 0, i;
|
||||
|
||||
if (parse_commit(commit))
|
||||
return ret;
|
||||
for (i = 0; i < nr_reference; i++)
|
||||
if (parse_commit(reference[i]))
|
||||
return ret;
|
||||
|
||||
bases = paint_down_to_common(commit, nr_reference, reference);
|
||||
if (commit->object.flags & PARENT2)
|
||||
ret = 1;
|
||||
clear_commit_marks(commit, all_flags);
|
||||
clear_commit_marks_many(nr_reference, reference, all_flags);
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
|
||||
*/
|
||||
int in_merge_bases(struct commit *commit, struct commit *reference)
|
||||
{
|
||||
struct commit_list *bases;
|
||||
int ret = 0;
|
||||
|
||||
if (parse_commit(commit) || parse_commit(reference))
|
||||
return ret;
|
||||
|
||||
bases = paint_down_to_common(commit, 1, &reference);
|
||||
if (commit->object.flags & PARENT2)
|
||||
ret = 1;
|
||||
clear_commit_marks(commit, all_flags);
|
||||
clear_commit_marks(reference, all_flags);
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
return in_merge_bases_many(commit, 1, &reference);
|
||||
}
|
||||
|
||||
struct commit_list *reduce_heads(struct commit_list *heads)
|
||||
|
2
commit.h
2
commit.h
@ -137,6 +137,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
|
||||
struct commit *pop_commit(struct commit_list **stack);
|
||||
|
||||
void clear_commit_marks(struct commit *commit, unsigned int mark);
|
||||
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
|
||||
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
|
||||
|
||||
/*
|
||||
@ -176,6 +177,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
|
||||
|
||||
int is_descendant_of(struct commit *, struct commit_list *);
|
||||
int in_merge_bases(struct commit *, struct commit *);
|
||||
int in_merge_bases_many(struct commit *, int, struct commit **);
|
||||
|
||||
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
|
||||
extern int run_add_interactive(const char *revision, const char *patch_mode,
|
||||
|
99
remote.c
99
remote.c
@ -1195,6 +1195,101 @@ static struct ref **tail_ref(struct ref **head)
|
||||
return tail;
|
||||
}
|
||||
|
||||
struct tips {
|
||||
struct commit **tip;
|
||||
int nr, alloc;
|
||||
};
|
||||
|
||||
static void add_to_tips(struct tips *tips, const unsigned char *sha1)
|
||||
{
|
||||
struct commit *commit;
|
||||
|
||||
if (is_null_sha1(sha1))
|
||||
return;
|
||||
commit = lookup_commit_reference_gently(sha1, 1);
|
||||
if (!commit || (commit->object.flags & TMP_MARK))
|
||||
return;
|
||||
commit->object.flags |= TMP_MARK;
|
||||
ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
|
||||
tips->tip[tips->nr++] = commit;
|
||||
}
|
||||
|
||||
static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
|
||||
{
|
||||
struct string_list dst_tag = STRING_LIST_INIT_NODUP;
|
||||
struct string_list src_tag = STRING_LIST_INIT_NODUP;
|
||||
struct string_list_item *item;
|
||||
struct ref *ref;
|
||||
struct tips sent_tips;
|
||||
|
||||
/*
|
||||
* Collect everything we know they would have at the end of
|
||||
* this push, and collect all tags they have.
|
||||
*/
|
||||
memset(&sent_tips, 0, sizeof(sent_tips));
|
||||
for (ref = *dst; ref; ref = ref->next) {
|
||||
if (ref->peer_ref &&
|
||||
!is_null_sha1(ref->peer_ref->new_sha1))
|
||||
add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
|
||||
else
|
||||
add_to_tips(&sent_tips, ref->old_sha1);
|
||||
if (!prefixcmp(ref->name, "refs/tags/"))
|
||||
string_list_append(&dst_tag, ref->name);
|
||||
}
|
||||
clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
|
||||
|
||||
sort_string_list(&dst_tag);
|
||||
|
||||
/* Collect tags they do not have. */
|
||||
for (ref = src; ref; ref = ref->next) {
|
||||
if (prefixcmp(ref->name, "refs/tags/"))
|
||||
continue; /* not a tag */
|
||||
if (string_list_has_string(&dst_tag, ref->name))
|
||||
continue; /* they already have it */
|
||||
if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG)
|
||||
continue; /* be conservative */
|
||||
item = string_list_append(&src_tag, ref->name);
|
||||
item->util = ref;
|
||||
}
|
||||
string_list_clear(&dst_tag, 0);
|
||||
|
||||
/*
|
||||
* At this point, src_tag lists tags that are missing from
|
||||
* dst, and sent_tips lists the tips we are pushing or those
|
||||
* that we know they already have. An element in the src_tag
|
||||
* that is an ancestor of any of the sent_tips needs to be
|
||||
* sent to the other side.
|
||||
*/
|
||||
if (sent_tips.nr) {
|
||||
for_each_string_list_item(item, &src_tag) {
|
||||
struct ref *ref = item->util;
|
||||
struct ref *dst_ref;
|
||||
struct commit *commit;
|
||||
|
||||
if (is_null_sha1(ref->new_sha1))
|
||||
continue;
|
||||
commit = lookup_commit_reference_gently(ref->new_sha1, 1);
|
||||
if (!commit)
|
||||
/* not pushing a commit, which is not an error */
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Is this tag, which they do not have, reachable from
|
||||
* any of the commits we are sending?
|
||||
*/
|
||||
if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
|
||||
continue;
|
||||
|
||||
/* Add it in */
|
||||
dst_ref = make_linked_ref(ref->name, dst_tail);
|
||||
hashcpy(dst_ref->new_sha1, ref->new_sha1);
|
||||
dst_ref->peer_ref = copy_ref(ref);
|
||||
}
|
||||
}
|
||||
string_list_clear(&src_tag, 0);
|
||||
free(sent_tips.tip);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the set of refs the local repository has, the set of refs the
|
||||
* remote repository has, and the refspec used for push, determine
|
||||
@ -1257,6 +1352,10 @@ int match_push_refs(struct ref *src, struct ref **dst,
|
||||
free_name:
|
||||
free(dst_name);
|
||||
}
|
||||
|
||||
if (flags & MATCH_REFS_FOLLOW_TAGS)
|
||||
add_missing_tags(src, dst, &dst_tail);
|
||||
|
||||
if (send_prune) {
|
||||
/* check for missing refs on the remote */
|
||||
for (ref = *dst; ref; ref = ref->next) {
|
||||
|
3
remote.h
3
remote.h
@ -149,7 +149,8 @@ enum match_refs_flags {
|
||||
MATCH_REFS_NONE = 0,
|
||||
MATCH_REFS_ALL = (1 << 0),
|
||||
MATCH_REFS_MIRROR = (1 << 1),
|
||||
MATCH_REFS_PRUNE = (1 << 2)
|
||||
MATCH_REFS_PRUNE = (1 << 2),
|
||||
MATCH_REFS_FOLLOW_TAGS = (1 << 3)
|
||||
};
|
||||
|
||||
/* Reporting of tracking info */
|
||||
|
@ -1077,4 +1077,77 @@ test_expect_success 'fetch exact SHA1' '
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'fetch follows tags by default' '
|
||||
mk_test heads/master &&
|
||||
rm -fr src dst &&
|
||||
git init src &&
|
||||
(
|
||||
cd src &&
|
||||
git pull ../testrepo master &&
|
||||
git tag -m "annotated" tag &&
|
||||
git for-each-ref >tmp1 &&
|
||||
(
|
||||
cat tmp1
|
||||
sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
|
||||
) |
|
||||
sort -k 3 >../expect
|
||||
) &&
|
||||
git init dst &&
|
||||
(
|
||||
cd dst &&
|
||||
git remote add origin ../src &&
|
||||
git config branch.master.remote origin &&
|
||||
git config branch.master.merge refs/heads/master &&
|
||||
git pull &&
|
||||
git for-each-ref >../actual
|
||||
) &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'push does not follow tags by default' '
|
||||
mk_test heads/master &&
|
||||
rm -fr src dst &&
|
||||
git init src &&
|
||||
git init --bare dst &&
|
||||
(
|
||||
cd src &&
|
||||
git pull ../testrepo master &&
|
||||
git tag -m "annotated" tag &&
|
||||
git checkout -b another &&
|
||||
git commit --allow-empty -m "future commit" &&
|
||||
git tag -m "future" future &&
|
||||
git checkout master &&
|
||||
git for-each-ref refs/heads/master >../expect &&
|
||||
git push ../dst master
|
||||
) &&
|
||||
(
|
||||
cd dst &&
|
||||
git for-each-ref >../actual
|
||||
) &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'push --follow-tag only pushes relevant tags' '
|
||||
mk_test heads/master &&
|
||||
rm -fr src dst &&
|
||||
git init src &&
|
||||
git init --bare dst &&
|
||||
(
|
||||
cd src &&
|
||||
git pull ../testrepo master &&
|
||||
git tag -m "annotated" tag &&
|
||||
git checkout -b another &&
|
||||
git commit --allow-empty -m "future commit" &&
|
||||
git tag -m "future" future &&
|
||||
git checkout master &&
|
||||
git for-each-ref refs/heads/master refs/tags/tag >../expect
|
||||
git push --follow-tag ../dst master
|
||||
) &&
|
||||
(
|
||||
cd dst &&
|
||||
git for-each-ref >../actual
|
||||
) &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -1128,6 +1128,8 @@ int transport_push(struct transport *transport,
|
||||
match_flags |= MATCH_REFS_MIRROR;
|
||||
if (flags & TRANSPORT_PUSH_PRUNE)
|
||||
match_flags |= MATCH_REFS_PRUNE;
|
||||
if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
|
||||
match_flags |= MATCH_REFS_FOLLOW_TAGS;
|
||||
|
||||
if (match_push_refs(local_refs, &remote_refs,
|
||||
refspec_nr, refspec, match_flags)) {
|
||||
|
@ -105,6 +105,7 @@ struct transport {
|
||||
#define TRANSPORT_PUSH_PRUNE 128
|
||||
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
|
||||
#define TRANSPORT_PUSH_NO_HOOK 512
|
||||
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
|
||||
|
||||
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
|
||||
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
|
||||
|
Loading…
Reference in New Issue
Block a user