Merge branch 'ab/fetch-tags-noclobber'
The rules used by "git push" and "git fetch" to determine if a ref can or cannot be updated were inconsistent; specifically, fetching to update existing tags were allowed even though tags are supposed to be unmoving anchoring points. "git fetch" was taught to forbid updates to existing tags without the "--force" option. * ab/fetch-tags-noclobber: fetch: stop clobbering existing tags without --force fetch: document local ref updates with/without --force push doc: correct lies about how push refspecs work push doc: move mention of "tag <tag>" later in the prose push doc: remove confusing mention of remote merger fetch tests: add a test for clobbering tag behavior push tests: use spaces in interpolated string push tests: make use of unused $1 in test description fetch: change "branch" to "reference" in --force -h outputmaint
commit
d39cab3989
|
@ -68,11 +68,16 @@ endif::git-pull[]
|
||||||
|
|
||||||
-f::
|
-f::
|
||||||
--force::
|
--force::
|
||||||
When 'git fetch' is used with `<rbranch>:<lbranch>`
|
When 'git fetch' is used with `<src>:<dst>` refspec it may
|
||||||
refspec, it refuses to update the local branch
|
refuse to update the local branch as discussed
|
||||||
`<lbranch>` unless the remote branch `<rbranch>` it
|
ifdef::git-pull[]
|
||||||
fetches is a descendant of `<lbranch>`. This option
|
in the `<refspec>` part of the linkgit:git-fetch[1]
|
||||||
overrides that check.
|
documentation.
|
||||||
|
endif::git-pull[]
|
||||||
|
ifndef::git-pull[]
|
||||||
|
in the `<refspec>` part below.
|
||||||
|
endif::git-pull[]
|
||||||
|
This option overrides that check.
|
||||||
|
|
||||||
-k::
|
-k::
|
||||||
--keep::
|
--keep::
|
||||||
|
|
|
@ -74,22 +74,57 @@ without any `<refspec>` on the command line. Otherwise, missing
|
||||||
`:<dst>` means to update the same ref as the `<src>`.
|
`:<dst>` means to update the same ref as the `<src>`.
|
||||||
+
|
+
|
||||||
The object referenced by <src> is used to update the <dst> reference
|
The object referenced by <src> is used to update the <dst> reference
|
||||||
on the remote side. By default this is only allowed if <dst> is not
|
on the remote side. Whether this is allowed depends on where in
|
||||||
a tag (annotated or lightweight), and then only if it can fast-forward
|
`refs/*` the <dst> reference lives as described in detail below, in
|
||||||
<dst>. By having the optional leading `+`, you can tell Git to update
|
those sections "update" means any modifications except deletes, which
|
||||||
the <dst> ref even if it is not allowed by default (e.g., it is not a
|
as noted after the next few sections are treated differently.
|
||||||
fast-forward.) This does *not* attempt to merge <src> into <dst>. See
|
|
||||||
EXAMPLES below for details.
|
|
||||||
+
|
+
|
||||||
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
The `refs/heads/*` namespace will only accept commit objects, and
|
||||||
|
updates only if they can be fast-forwarded.
|
||||||
+
|
+
|
||||||
Pushing an empty <src> allows you to delete the <dst> ref from
|
The `refs/tags/*` namespace will accept any kind of object (as
|
||||||
the remote repository.
|
commits, trees and blobs can be tagged), and any updates to them will
|
||||||
|
be rejected.
|
||||||
|
+
|
||||||
|
It's possible to push any type of object to any namespace outside of
|
||||||
|
`refs/{tags,heads}/*`. In the case of tags and commits, these will be
|
||||||
|
treated as if they were the commits inside `refs/heads/*` for the
|
||||||
|
purposes of whether the update is allowed.
|
||||||
|
+
|
||||||
|
I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*`
|
||||||
|
is allowed, even in cases where what's being fast-forwarded is not a
|
||||||
|
commit, but a tag object which happens to point to a new commit which
|
||||||
|
is a fast-forward of the commit the last tag (or commit) it's
|
||||||
|
replacing. Replacing a tag with an entirely different tag is also
|
||||||
|
allowed, if it points to the same commit, as well as pushing a peeled
|
||||||
|
tag, i.e. pushing the commit that existing tag object points to, or a
|
||||||
|
new tag object which an existing commit points to.
|
||||||
|
+
|
||||||
|
Tree and blob objects outside of `refs/{tags,heads}/*` will be treated
|
||||||
|
the same way as if they were inside `refs/tags/*`, any update of them
|
||||||
|
will be rejected.
|
||||||
|
+
|
||||||
|
All of the rules described above about what's not allowed as an update
|
||||||
|
can be overridden by adding an the optional leading `+` to a refspec
|
||||||
|
(or using `--force` command line option). The only exception to this
|
||||||
|
is that no amount of forcing will make the `refs/heads/*` namespace
|
||||||
|
accept a non-commit object. Hooks and configuration can also override
|
||||||
|
or amend these rules, see e.g. `receive.denyNonFastForwards` in
|
||||||
|
linkgit:git-config[1] and`pre-receive` and `update` in
|
||||||
|
linkgit:githooks[5].
|
||||||
|
+
|
||||||
|
Pushing an empty <src> allows you to delete the <dst> ref from the
|
||||||
|
remote repository. Deletions are always accepted without a leading `+`
|
||||||
|
in the refspec (or `--force`), except when forbidden by configuration
|
||||||
|
or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and
|
||||||
|
`pre-receive` and `update` in linkgit:githooks[5].
|
||||||
+
|
+
|
||||||
The special refspec `:` (or `+:` to allow non-fast-forward updates)
|
The special refspec `:` (or `+:` to allow non-fast-forward updates)
|
||||||
directs Git to push "matching" branches: for every branch that exists on
|
directs Git to push "matching" branches: for every branch that exists on
|
||||||
the local side, the remote side is updated if a branch of the same name
|
the local side, the remote side is updated if a branch of the same name
|
||||||
already exists on the remote side.
|
already exists on the remote side.
|
||||||
|
+
|
||||||
|
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
||||||
|
|
||||||
--all::
|
--all::
|
||||||
Push all branches (i.e. refs under `refs/heads/`); cannot be
|
Push all branches (i.e. refs under `refs/heads/`); cannot be
|
||||||
|
|
|
@ -19,9 +19,10 @@ walk the revision graph (such as linkgit:git-log[1]), all commits which are
|
||||||
reachable from that commit. For commands that walk the revision graph one can
|
reachable from that commit. For commands that walk the revision graph one can
|
||||||
also specify a range of revisions explicitly.
|
also specify a range of revisions explicitly.
|
||||||
|
|
||||||
In addition, some Git commands (such as linkgit:git-show[1]) also take
|
In addition, some Git commands (such as linkgit:git-show[1] and
|
||||||
revision parameters which denote other objects than commits, e.g. blobs
|
linkgit:git-push[1]) can also take revision parameters which denote
|
||||||
("files") or trees ("directories of files").
|
other objects than commits, e.g. blobs ("files") or trees
|
||||||
|
("directories of files").
|
||||||
|
|
||||||
include::revisions.txt[]
|
include::revisions.txt[]
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,40 @@ name.
|
||||||
it requests fetching everything up to the given tag.
|
it requests fetching everything up to the given tag.
|
||||||
+
|
+
|
||||||
The remote ref that matches <src>
|
The remote ref that matches <src>
|
||||||
is fetched, and if <dst> is not an empty string, the local
|
is fetched, and if <dst> is not an empty string, an attempt
|
||||||
ref that matches it is fast-forwarded using <src>.
|
is made to update the local ref that matches it.
|
||||||
If the optional plus `+` is used, the local ref
|
+
|
||||||
is updated even if it does not result in a fast-forward
|
Whether that update is allowed without `--force` depends on the ref
|
||||||
update.
|
namespace it's being fetched to, the type of object being fetched, and
|
||||||
|
whether the update is considered to be a fast-forward. Generally, the
|
||||||
|
same rules apply for fetching as when pushing, see the `<refspec>...`
|
||||||
|
section of linkgit:git-push[1] for what those are. Exceptions to those
|
||||||
|
rules particular to 'git fetch' are noted below.
|
||||||
|
+
|
||||||
|
Until Git version 2.20, and unlike when pushing with
|
||||||
|
linkgit:git-push[1], any updates to `refs/tags/*` would be accepted
|
||||||
|
without `+` in the refspec (or `--force`). The receiving promiscuously
|
||||||
|
considered all tag updates from a remote to be forced fetches. Since
|
||||||
|
Git version 2.20, fetching to update `refs/tags/*` work the same way
|
||||||
|
as when pushing. I.e. any updates will be rejected without `+` in the
|
||||||
|
refspec (or `--force`).
|
||||||
|
+
|
||||||
|
Unlike when pushing with linkgit:git-push[1], any updates outside of
|
||||||
|
`refs/{tags,heads}/*` will be accepted without `+` in the refspec (or
|
||||||
|
`--force`), whether that's swapping e.g. a tree object for a blob, or
|
||||||
|
a commit for another commit that's doesn't have the previous commit as
|
||||||
|
an ancestor etc.
|
||||||
|
+
|
||||||
|
Unlike when pushing with linkgit:git-push[1], there is no
|
||||||
|
configuration which'll amend these rules, and nothing like a
|
||||||
|
`pre-fetch` hook analogous to the `pre-receive` hook.
|
||||||
|
+
|
||||||
|
As with pushing with linkgit:git-push[1], all of the rules described
|
||||||
|
above about what's not allowed as an update can be overridden by
|
||||||
|
adding an the optional leading `+` to a refspec (or using `--force`
|
||||||
|
command line option). The only exception to this is that no amount of
|
||||||
|
forcing will make the `refs/heads/*` namespace accept a non-commit
|
||||||
|
object.
|
||||||
+
|
+
|
||||||
[NOTE]
|
[NOTE]
|
||||||
When the remote branch you want to fetch is known to
|
When the remote branch you want to fetch is known to
|
||||||
|
|
|
@ -115,7 +115,7 @@ static struct option builtin_fetch_options[] = {
|
||||||
N_("append to .git/FETCH_HEAD instead of overwriting")),
|
N_("append to .git/FETCH_HEAD instead of overwriting")),
|
||||||
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
|
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
|
||||||
N_("path to upload pack on remote end")),
|
N_("path to upload pack on remote end")),
|
||||||
OPT__FORCE(&force, N_("force overwrite of local branch"), 0),
|
OPT__FORCE(&force, N_("force overwrite of local reference"), 0),
|
||||||
OPT_BOOL('m', "multiple", &multiple,
|
OPT_BOOL('m', "multiple", &multiple,
|
||||||
N_("fetch from multiple remotes")),
|
N_("fetch from multiple remotes")),
|
||||||
OPT_SET_INT('t', "tags", &tags,
|
OPT_SET_INT('t', "tags", &tags,
|
||||||
|
@ -668,12 +668,18 @@ static int update_local_ref(struct ref *ref,
|
||||||
|
|
||||||
if (!is_null_oid(&ref->old_oid) &&
|
if (!is_null_oid(&ref->old_oid) &&
|
||||||
starts_with(ref->name, "refs/tags/")) {
|
starts_with(ref->name, "refs/tags/")) {
|
||||||
|
if (force || ref->force) {
|
||||||
int r;
|
int r;
|
||||||
r = s_update_ref("updating tag", ref, 0);
|
r = s_update_ref("updating tag", ref, 0);
|
||||||
format_display(display, r ? '!' : 't', _("[tag update]"),
|
format_display(display, r ? '!' : 't', _("[tag update]"),
|
||||||
r ? _("unable to update local ref") : NULL,
|
r ? _("unable to update local ref") : NULL,
|
||||||
remote, pretty_ref, summary_width);
|
remote, pretty_ref, summary_width);
|
||||||
return r;
|
return r;
|
||||||
|
} else {
|
||||||
|
format_display(display, '!', _("[rejected]"), _("would clobber existing tag"),
|
||||||
|
remote, pretty_ref, summary_width);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = lookup_commit_reference_gently(the_repository,
|
current = lookup_commit_reference_gently(the_repository,
|
||||||
|
|
|
@ -969,7 +969,7 @@ test_force_push_tag () {
|
||||||
tag_type_description=$1
|
tag_type_description=$1
|
||||||
tag_args=$2
|
tag_args=$2
|
||||||
|
|
||||||
test_expect_success 'force pushing required to update lightweight tag' "
|
test_expect_success "force pushing required to update $tag_type_description" "
|
||||||
mk_test testrepo heads/master &&
|
mk_test testrepo heads/master &&
|
||||||
mk_child testrepo child1 &&
|
mk_child testrepo child1 &&
|
||||||
mk_child testrepo child2 &&
|
mk_child testrepo child2 &&
|
||||||
|
@ -1009,7 +1009,32 @@ test_force_push_tag () {
|
||||||
}
|
}
|
||||||
|
|
||||||
test_force_push_tag "lightweight tag" "-f"
|
test_force_push_tag "lightweight tag" "-f"
|
||||||
test_force_push_tag "annotated tag" "-f -a -m'msg'"
|
test_force_push_tag "annotated tag" "-f -a -m'tag message'"
|
||||||
|
|
||||||
|
test_force_fetch_tag () {
|
||||||
|
tag_type_description=$1
|
||||||
|
tag_args=$2
|
||||||
|
|
||||||
|
test_expect_success "fetch will not clobber an existing $tag_type_description without --force" "
|
||||||
|
mk_test testrepo heads/master &&
|
||||||
|
mk_child testrepo child1 &&
|
||||||
|
mk_child testrepo child2 &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git tag testTag &&
|
||||||
|
git -C ../child1 fetch origin tag testTag &&
|
||||||
|
>file1 &&
|
||||||
|
git add file1 &&
|
||||||
|
git commit -m 'file1' &&
|
||||||
|
git tag $tag_args testTag &&
|
||||||
|
test_must_fail git -C ../child1 fetch origin tag testTag &&
|
||||||
|
git -C ../child1 fetch origin '+refs/tags/*:refs/tags/*'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_force_fetch_tag "lightweight tag" "-f"
|
||||||
|
test_force_fetch_tag "annotated tag" "-f -a -m'tag message'"
|
||||||
|
|
||||||
test_expect_success 'push --porcelain' '
|
test_expect_success 'push --porcelain' '
|
||||||
mk_empty testrepo &&
|
mk_empty testrepo &&
|
||||||
|
|
|
@ -103,7 +103,7 @@ test_expect_success 'clone with --no-tags' '
|
||||||
test_expect_success '--single-branch while HEAD pointing at master' '
|
test_expect_success '--single-branch while HEAD pointing at master' '
|
||||||
(
|
(
|
||||||
cd dir_master &&
|
cd dir_master &&
|
||||||
git fetch &&
|
git fetch --force &&
|
||||||
git for-each-ref refs/remotes/origin |
|
git for-each-ref refs/remotes/origin |
|
||||||
sed -e "/HEAD$/d" \
|
sed -e "/HEAD$/d" \
|
||||||
-e "s|/remotes/origin/|/heads/|" >../actual
|
-e "s|/remotes/origin/|/heads/|" >../actual
|
||||||
|
@ -114,7 +114,7 @@ test_expect_success '--single-branch while HEAD pointing at master' '
|
||||||
test_cmp expect actual &&
|
test_cmp expect actual &&
|
||||||
(
|
(
|
||||||
cd dir_master &&
|
cd dir_master &&
|
||||||
git fetch --tags &&
|
git fetch --tags --force &&
|
||||||
git for-each-ref refs/tags >../actual
|
git for-each-ref refs/tags >../actual
|
||||||
) &&
|
) &&
|
||||||
git for-each-ref refs/tags >expect &&
|
git for-each-ref refs/tags >expect &&
|
||||||
|
|
Loading…
Reference in New Issue