mirror of
https://github.com/git/git.git
synced 2024-11-30 13:33:44 +08:00
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 <gitster@pobox.com>
This commit is contained in:
parent
557899ff6b
commit
c2aba155da
@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[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]
|
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
|
||||||
[<repository> [<refspec>...]]
|
[<repository> [<refspec>...]]
|
||||||
|
|
||||||
@ -111,6 +111,12 @@ no `push.default` configuration variable is set.
|
|||||||
addition to refspecs explicitly listed on the command
|
addition to refspecs explicitly listed on the command
|
||||||
line.
|
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>::
|
--receive-pack=<git-receive-pack>::
|
||||||
--exec=<git-receive-pack>::
|
--exec=<git-receive-pack>::
|
||||||
Path to the 'git-receive-pack' program on the remote
|
Path to the 'git-receive-pack' program on the remote
|
||||||
|
@ -399,6 +399,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
|
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
|
||||||
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
|
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
|
||||||
TRANSPORT_PUSH_PRUNE),
|
TRANSPORT_PUSH_PRUNE),
|
||||||
|
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
|
||||||
|
TRANSPORT_PUSH_FOLLOW_TAGS),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
99
remote.c
99
remote.c
@ -1195,6 +1195,101 @@ static struct ref **tail_ref(struct ref **head)
|
|||||||
return tail;
|
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
|
* Given the set of refs the local repository has, the set of refs the
|
||||||
* remote repository has, and the refspec used for push, determine
|
* 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_name:
|
||||||
free(dst_name);
|
free(dst_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags & MATCH_REFS_FOLLOW_TAGS)
|
||||||
|
add_missing_tags(src, dst, &dst_tail);
|
||||||
|
|
||||||
if (send_prune) {
|
if (send_prune) {
|
||||||
/* check for missing refs on the remote */
|
/* check for missing refs on the remote */
|
||||||
for (ref = *dst; ref; ref = ref->next) {
|
for (ref = *dst; ref; ref = ref->next) {
|
||||||
|
3
remote.h
3
remote.h
@ -148,7 +148,8 @@ enum match_refs_flags {
|
|||||||
MATCH_REFS_NONE = 0,
|
MATCH_REFS_NONE = 0,
|
||||||
MATCH_REFS_ALL = (1 << 0),
|
MATCH_REFS_ALL = (1 << 0),
|
||||||
MATCH_REFS_MIRROR = (1 << 1),
|
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 */
|
/* Reporting of tracking info */
|
||||||
|
@ -995,4 +995,77 @@ test_expect_success 'push --prune refspec' '
|
|||||||
! check_push_result $the_first_commit tmp/foo tmp/bar
|
! check_push_result $the_first_commit tmp/foo tmp/bar
|
||||||
'
|
'
|
||||||
|
|
||||||
|
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
|
test_done
|
||||||
|
@ -1059,6 +1059,8 @@ int transport_push(struct transport *transport,
|
|||||||
match_flags |= MATCH_REFS_MIRROR;
|
match_flags |= MATCH_REFS_MIRROR;
|
||||||
if (flags & TRANSPORT_PUSH_PRUNE)
|
if (flags & TRANSPORT_PUSH_PRUNE)
|
||||||
match_flags |= MATCH_REFS_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,
|
if (match_push_refs(local_refs, &remote_refs,
|
||||||
refspec_nr, refspec, match_flags)) {
|
refspec_nr, refspec, match_flags)) {
|
||||||
|
@ -104,6 +104,7 @@ struct transport {
|
|||||||
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
|
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
|
||||||
#define TRANSPORT_PUSH_PRUNE 128
|
#define TRANSPORT_PUSH_PRUNE 128
|
||||||
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
|
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
|
||||||
|
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
|
||||||
|
|
||||||
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
|
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
|
||||||
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
|
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
|
||||||
|
Loading…
Reference in New Issue
Block a user