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:
Junio C Hamano 2013-03-25 14:00:40 -07:00
commit 55f6fbef3d
9 changed files with 225 additions and 21 deletions

View file

@ -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

View file

@ -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()
};

View file

@ -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)

View file

@ -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,

View file

@ -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) {

View file

@ -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 */

View file

@ -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

View file

@ -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)) {

View file

@ -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)