Skip to content

Commit

Permalink
Merge branch 'jc/push-follow-tag'
Browse files Browse the repository at this point in the history
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()
  • Loading branch information
gitster committed Mar 25, 2013
2 parents 212ca64 + c2aba15 commit 55f6fbe
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 14 deletions.
8 changes: 7 additions & 1 deletion Documentation/git-push.txt
Expand Up @@ -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>...]]

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions builtin/push.c
Expand Up @@ -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()
};

Expand Down
42 changes: 30 additions & 12 deletions commit.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -852,25 +859,36 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
}

/*
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
* Is "commit" an ancestor of one of the "references"?
*/
int in_merge_bases(struct commit *commit, struct commit *reference)
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
{
struct commit_list *bases;
int ret = 0;
int ret = 0, i;

if (parse_commit(commit) || parse_commit(reference))
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, 1, &reference);
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(reference, 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)
{
return in_merge_bases_many(commit, 1, &reference);
}

struct commit_list *reduce_heads(struct commit_list *heads)
{
struct commit_list *p;
Expand Down
2 changes: 2 additions & 0 deletions commit.h
Expand Up @@ -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);

/*
Expand Down Expand Up @@ -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,
Expand Down
99 changes: 99 additions & 0 deletions remote.c
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion remote.h
Expand Up @@ -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 */
Expand Down
73 changes: 73 additions & 0 deletions t/t5516-fetch-push.sh
Expand Up @@ -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
2 changes: 2 additions & 0 deletions transport.c
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions transport.h
Expand Up @@ -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)
Expand Down

0 comments on commit 55f6fbe

Please sign in to comment.