fetch --tags: fetch tags *in addition to* other stuff

Previously, fetch's "--tags" option was considered equivalent to
specifying the refspec "refs/tags/*:refs/tags/*" on the command line;
in particular, it caused the remote.<name>.refspec configuration to be
ignored.

But it is not very useful to fetch tags without also fetching other
references, whereas it *is* quite useful to be able to fetch tags *in
addition to* other references.  So change the semantics of this option
to do the latter.

If a user wants to fetch *only* tags, then it is still possible to
specifying an explicit refspec:

    git fetch <remote> 'refs/tags/*:refs/tags/*'

Please note that the documentation prior to 1.8.0.3 was ambiguous
about this aspect of "fetch --tags" behavior.  Commit

    f0cb2f137c 2012-12-14 fetch --tags: clarify documentation

made the documentation match the old behavior.  This commit changes
the documentation to match the new behavior.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Michael Haggerty 2013-10-30 06:32:59 +01:00 committed by Junio C Hamano
parent 0281c930f1
commit c5a84e92a2
7 changed files with 78 additions and 38 deletions

View file

@ -61,11 +61,9 @@ endif::git-pull[]
ifndef::git-pull[] ifndef::git-pull[]
-t:: -t::
--tags:: --tags::
This is a short-hand for giving `refs/tags/*:refs/tags/*` Request that all tags be fetched from the remote in addition
refspec from the command line, to ask all tags to be fetched to whatever else is being fetched. Its effect is similar to
and stored locally. Because this acts as an explicit that of the refspec `refs/tags/*:refs/tags/*`.
refspec, the default refspecs (configured with the
remote.$name.fetch variable) are overridden and not used.
--recurse-submodules[=yes|on-demand|no]:: --recurse-submodules[=yes|on-demand|no]::
This option controls if and under what conditions new commits of This option controls if and under what conditions new commits of

View file

@ -269,12 +269,12 @@ static struct ref *get_ref_map(struct transport *transport,
struct ref *ref_map = NULL; struct ref *ref_map = NULL;
struct ref **tail = &ref_map; struct ref **tail = &ref_map;
/* opportunistically-updated references: */
struct ref *orefs = NULL, **oref_tail = &orefs;
const struct ref *remote_refs = transport_get_remote_refs(transport); const struct ref *remote_refs = transport_get_remote_refs(transport);
if (refspec_count || tags == TAGS_SET) { if (refspec_count) {
/* opportunistically-updated references: */
struct ref *orefs = NULL, **oref_tail = &orefs;
for (i = 0; i < refspec_count; i++) { for (i = 0; i < refspec_count; i++) {
get_fetch_map(remote_refs, &refspecs[i], &tail, 0); get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
if (refspecs[i].dst && refspecs[i].dst[0]) if (refspecs[i].dst && refspecs[i].dst[0])
@ -310,12 +310,6 @@ static struct ref *get_ref_map(struct transport *transport,
if (tags == TAGS_SET) if (tags == TAGS_SET)
get_fetch_map(remote_refs, tag_refspec, &tail, 0); get_fetch_map(remote_refs, tag_refspec, &tail, 0);
*tail = orefs;
for (rm = orefs; rm; rm = rm->next) {
rm->fetch_head_status = FETCH_HEAD_IGNORE;
tail = &rm->next;
}
} else { } else {
/* Use the defaults */ /* Use the defaults */
struct remote *remote = transport->remote; struct remote *remote = transport->remote;
@ -353,9 +347,19 @@ static struct ref *get_ref_map(struct transport *transport,
} }
} }
if (tags == TAGS_DEFAULT && *autotags) if (tags == TAGS_SET)
/* also fetch all tags */
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
else if (tags == TAGS_DEFAULT && *autotags)
find_non_local_tags(transport, &ref_map, &tail); find_non_local_tags(transport, &ref_map, &tail);
/* Now append any refs to be updated opportunistically: */
*tail = orefs;
for (rm = orefs; rm; rm = rm->next) {
rm->fetch_head_status = FETCH_HEAD_IGNORE;
tail = &rm->next;
}
ref_remove_duplicates(ref_map); ref_remove_duplicates(ref_map);
return ref_map; return ref_map;
@ -846,31 +850,38 @@ static int do_fetch(struct transport *transport,
goto cleanup; goto cleanup;
} }
if (prune) { if (prune) {
/* struct refspec *prune_refspecs;
* If --tags was specified, pretend that the user gave us int prune_refspec_count;
* the canonical tags refspec
*/ if (ref_count) {
prune_refspecs = refs;
prune_refspec_count = ref_count;
} else {
prune_refspecs = transport->remote->fetch;
prune_refspec_count = transport->remote->fetch_refspec_nr;
}
if (tags == TAGS_SET) { if (tags == TAGS_SET) {
/*
* --tags was specified. Pretend that the user also
* gave us the canonical tags refspec
*/
const char *tags_str = "refs/tags/*:refs/tags/*"; const char *tags_str = "refs/tags/*:refs/tags/*";
struct refspec *tags_refspec, *refspec; struct refspec *tags_refspec, *refspec;
/* Copy the refspec and add the tags to it */ /* Copy the refspec and add the tags to it */
refspec = xcalloc(ref_count + 1, sizeof(struct refspec)); refspec = xcalloc(prune_refspec_count + 1, sizeof(*refspec));
tags_refspec = parse_fetch_refspec(1, &tags_str); tags_refspec = parse_fetch_refspec(1, &tags_str);
memcpy(refspec, refs, ref_count * sizeof(struct refspec)); memcpy(refspec, prune_refspecs, prune_refspec_count * sizeof(*refspec));
memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec)); memcpy(&refspec[prune_refspec_count], tags_refspec, sizeof(*refspec));
ref_count++;
prune_refs(refspec, ref_count, ref_map); prune_refs(refspec, prune_refspec_count + 1, ref_map);
ref_count--;
/* The rest of the strings belong to fetch_one */ /* The rest of the strings belong to fetch_one */
free_refspec(1, tags_refspec); free_refspec(1, tags_refspec);
free(refspec); free(refspec);
} else if (ref_count) {
prune_refs(refs, ref_count, ref_map);
} else { } else {
prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map); prune_refs(prune_refspecs, prune_refspec_count, ref_map);
} }
} }
free_refs(ref_map); free_refs(ref_map);

View file

@ -172,7 +172,7 @@ error_on_no_merge_candidates () {
do do
case "$opt" in case "$opt" in
-t|--t|--ta|--tag|--tags) -t|--t|--ta|--tag|--tags)
echo "Fetching tags only, you probably meant:" echo "It doesn't make sense to pull all tags; you probably meant:"
echo " git fetch --tags" echo " git fetch --tags"
exit 1 exit 1
esac esac

View file

@ -113,7 +113,7 @@ test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
git rev-parse origin/master git rev-parse origin/master
' '
test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' ' test_expect_success 'fetch --prune --tags prunes tags and branches' '
cd "$D" && cd "$D" &&
git clone . prune-tags && git clone . prune-tags &&
cd prune-tags && cd prune-tags &&
@ -124,7 +124,7 @@ test_expect_success 'fetch --prune --tags does not delete the remote-tracking br
git fetch --prune --tags origin && git fetch --prune --tags origin &&
git rev-parse origin/master && git rev-parse origin/master &&
git rev-parse origin/fake-remote && test_must_fail git rev-parse origin/fake-remote &&
test_must_fail git rev-parse sometag test_must_fail git rev-parse sometag
' '
@ -132,10 +132,26 @@ test_expect_success 'fetch --prune --tags with branch does not delete other remo
cd "$D" && cd "$D" &&
git clone . prune-tags-branch && git clone . prune-tags-branch &&
cd prune-tags-branch && cd prune-tags-branch &&
git tag sometag master &&
git update-ref refs/remotes/origin/extrabranch master && git update-ref refs/remotes/origin/extrabranch master &&
git fetch --prune --tags origin master && git fetch --prune --tags origin master &&
git rev-parse origin/extrabranch git rev-parse origin/extrabranch &&
test_must_fail git rev-parse sometag
'
test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' '
cd "$D" &&
git clone . prune-tags-refspec &&
cd prune-tags-refspec &&
git tag sometag master &&
git update-ref refs/remotes/origin/foo/otherbranch master &&
git update-ref refs/remotes/origin/extrabranch master &&
git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
git rev-parse origin/extrabranch &&
test_must_fail git rev-parse sometag
' '
test_expect_success 'fetch tags when there is no tags' ' test_expect_success 'fetch tags when there is no tags' '

View file

@ -1,4 +1,5 @@
# br-unconfig --tags ../.git # br-unconfig --tags ../.git
0567da4d5edd2ff4bb292a465ba9e64dcad9536b ../
6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../

View file

@ -1,4 +1,5 @@
# master --tags ../.git # master --tags ../.git
0567da4d5edd2ff4bb292a465ba9e64dcad9536b ../
6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../ 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../ 8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../ 22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../

View file

@ -8,7 +8,8 @@ setup_clone () {
git clone --mirror . $1 && git clone --mirror . $1 &&
git remote add remote_$1 $1 && git remote add remote_$1 $1 &&
(cd $1 && (cd $1 &&
git tag tag_$1) git tag tag_$1 &&
git branch branch_$1)
} }
test_expect_success setup ' test_expect_success setup '
@ -21,21 +22,33 @@ test_expect_success setup '
test_expect_success "fetch with tagopt=--no-tags does not get tag" ' test_expect_success "fetch with tagopt=--no-tags does not get tag" '
git fetch remote_one && git fetch remote_one &&
test_must_fail git show-ref tag_one test_must_fail git show-ref tag_one &&
git show-ref remote_one/branch_one
' '
test_expect_success "fetch --tags with tagopt=--no-tags gets tag" ' test_expect_success "fetch --tags with tagopt=--no-tags gets tag" '
(
cd one &&
git branch second_branch_one
) &&
git fetch --tags remote_one && git fetch --tags remote_one &&
git show-ref tag_one git show-ref tag_one &&
git show-ref remote_one/second_branch_one
' '
test_expect_success "fetch --no-tags with tagopt=--tags does not get tag" ' test_expect_success "fetch --no-tags with tagopt=--tags does not get tag" '
git fetch --no-tags remote_two && git fetch --no-tags remote_two &&
test_must_fail git show-ref tag_two test_must_fail git show-ref tag_two &&
git show-ref remote_two/branch_two
' '
test_expect_success "fetch with tagopt=--tags gets tag" ' test_expect_success "fetch with tagopt=--tags gets tag" '
(
cd two &&
git branch second_branch_two
) &&
git fetch remote_two && git fetch remote_two &&
git show-ref tag_two git show-ref tag_two &&
git show-ref remote_two/second_branch_two
' '
test_done test_done