From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
Hi,
This is second iteration of this series. The initial three patches are
unchanged, although the commit message of #3 has been rephrased based
on Junio's comments.
Patches #4-#6 fixes existing tests in preparation for patch #7, which
changes the validation of the remote-tracking branch passed to --track:
We now require the --track argument to refer to a ref that matches a
configured refspec - otherwise, we can not reliably deduce the upstream
information to store into branch.<name>.remote and branch.<name>.merge.
Finally, patch #8 updates the paragraph on remote-tracking branches in
the glossary to be somewhat closer to the current state of things.
Have fun! :)
...Johan
Johan Herland (8):
t2024: Add tests verifying current DWIM behavior of 'git checkout <branch>'
t2024: Show failure to use refspec when DWIMming remote branch names
checkout: Use remote refspecs when DWIMming tracking branches
t3200.39: tracking setup should fail if there is no matching refspec.
t7201.24: Add refspec to keep --track working
t9114.2: Don't use --track option against "svn-remote"-tracking branches
branch.c: Validate tracking branches with refspecs instead of refs/remotes/*
glossary: Update and rephrase the definition of a remote-tracking branch
Documentation/git-checkout.txt | 6 +-
Documentation/glossary-content.txt | 13 +++--
branch.c | 17 +++++-
builtin/checkout.c | 42 +++++++-------
t/t2024-checkout-dwim.sh | 116 +++++++++++++++++++++++++++++++++++++
t/t3200-branch.sh | 8 +--
t/t7201-co.sh | 1 +
t/t9114-git-svn-dcommit-merge.sh | 2 +-
8 files changed, 170 insertions(+), 35 deletions(-)
create mode 100755 t/t2024-checkout-dwim.sh
--
1.8.1.3.704.g33f7d4f
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
The DWIM mode of checkout allows you to run "git checkout foo" when there
is no existing local ref or path called "foo", and there is exactly _one_
remote with a remote-tracking branch called "foo". Git will automatically
create a new local branch called "foo" using the remote-tracking "foo" as
its starting point and configured upstream.
For example, consider the following unconventional (but perfectly valid)
remote setup:
[remote "origin"]
fetch = refs/heads/*:refs/remotes/origin/*
[remote "frotz"]
fetch = refs/heads/*:refs/remotes/frotz/nitfol/*
Case 1: Assume both "origin" and "frotz" have remote-tracking branches called
"foo", at "refs/remotes/origin/foo" and "refs/remotes/frotz/nitfol/foo"
respectively. In this case "git checkout foo" should fail, because there is
more than one remote with a "foo" branch.
Case 2: Assume only "frotz" have a remote-tracking branch called "foo". In
this case "git checkout foo" should succeed, and create a local branch "foo"
from "refs/remotes/frotz/nitfol/foo", using remote branch "foo" from "frotz"
as its upstream.
The current code hardcodes the assumption that all remote-tracking branches
must match the "refs/remotes/$remote/*" pattern (which is true for remotes
with "conventional" refspecs, but not true for the "frotz" remote above).
When running "git checkout foo", the current code looks for exactly one ref
matching "refs/remotes/*/foo", hence in the above example, it fails to find
"refs/remotes/frotz/nitfol/foo", which causes it to fail both case #1 and #2.
The better way to handle the above example is to actually study the fetch
refspecs to deduce the candidate remote-tracking branches for "foo"; i.e.
assume "foo" is a remote branch being fetched, and then map "refs/heads/foo"
through the refspecs in order to get the corresponding remote-tracking
branches "refs/remotes/origin/foo" and "refs/remotes/frotz/nitfol/foo".
Finally we check which of these happens to exist in the local repo, and
if there is exactly one, we have an unambiguous match for "git checkout foo",
and may proceed.
This fixes most of the failing tests introduced in the previous patch.
Signed-off-by: Johan Herland <redacted>
---
Documentation/git-checkout.txt | 6 +++---
builtin/checkout.c | 42 ++++++++++++++++++++++--------------------
t/t2024-checkout-dwim.sh | 6 +++---
3 files changed, 28 insertions(+), 26 deletions(-)
@@ -131,9 +131,9 @@ entries; instead, unmerged entries are ignored. "--track" in linkgit:git-branch[1] for details. + If no '-b' option is given, the name of the new branch will be-derived from the remote-tracking branch. If "remotes/" or "refs/remotes/"-is prefixed it is stripped away, and then the part up to the-next slash (which would be the nickname of the remote) is removed.+derived from the remote-tracking branch, by looking at the local part of+the refspec configured for the corresponding remote, and then stripping+the initial part up to the "*". This would tell us to use "hack" as the local branch when branching off of "origin/hack" (or "remotes/origin/hack", or even "refs/remotes/origin/hack"). If the given name has no slash, or the above
@@ -95,15 +95,15 @@ test_expect_success 'setup more remotes with unconventional refspecs' 'gitfetchrepo_d'-test_expect_failure'checkout of branch from multiple remotes fails #2''+test_expect_success'checkout of branch from multiple remotes fails #2''test_must_failgitcheckoutbar'-test_expect_failure'checkout of branch from multiple remotes fails #3''+test_expect_success'checkout of branch from multiple remotes fails #3''test_must_failgitcheckoutbaz'-test_expect_failure'checkout of branch from a single remote succeeds #3''+test_expect_success'checkout of branch from a single remote succeeds #3''gitcheckoutspam&&test_tracking_branchspamrepo_crefs/remotes/extra_dir/repo_c/extra_dir/spam'
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
The DWIM mode of checkout allows you to run "git checkout foo" when there is
no existing local ref or path called "foo", and there is exactly one remote
with a remote-tracking branch called "foo". Git will then automatically
create a new local branch called "foo" using the remote-tracking "foo" as
its starting point and configured upstream.
Signed-off-by: Johan Herland <redacted>
---
t/t2024-checkout-dwim.sh | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100755 t/t2024-checkout-dwim.sh
@@ -0,0 +1,66 @@+#!/bin/sh++test_description='checkout<branch>++Ensuresthatcheckoutonanunbornbranchdoeswhattheuserexpects'++../test-lib.sh++# Arguments: <branch> <remote> <remote-tracking>+#+# Verify that we have checked out <branch>, and that it is at the same+# commit as <remote-tracking>, and that has appropriate tracking config+# setup against <remote>+test_tracking_branch(){+branch=$1&&+remote=$2&&+remote_track=$3&&+test"refs/heads/$branch"="$(gitrev-parse--symbolic-full-nameHEAD)"&&+test"$(gitrev-parse--verifyHEAD)"="$(gitrev-parse--verify"$remote_track")"&&+test"$remote"="$(gitconfig"branch.$branch.remote")"&&+test"refs/heads/$branch"="$(gitconfig"branch.$branch.merge")"+}++test_expect_success'setup''+(gitinitrepo_a&&+cdrepo_a&&+test_commita_master&&+gitcheckout-bfoo&&+test_commita_foo&&+gitcheckout-bbar&&+test_commita_bar+)&&+(gitinitrepo_b&&+cdrepo_b&&+test_commitb_master&&+gitcheckout-bfoo&&+test_commitb_foo&&+gitcheckout-bbaz&&+test_commitb_baz+)&&+gitremoteaddrepo_arepo_a&&+gitremoteaddrepo_brepo_b&&+gitconfigremote.repo_b.fetch\+"+refs/heads/*:refs/remotes/other_b/*"&&+gitfetch--all+'++test_expect_success'checkout of non-existing branch fails''+test_must_failgitcheckoutxyzzy+'++test_expect_success'checkout of branch from multiple remotes fails''+test_must_failgitcheckoutfoo+'++test_expect_success'checkout of branch from a single remote succeeds #1''+gitcheckoutbar&&+test_tracking_branchbarrepo_arefs/remotes/repo_a/bar+'++test_expect_success'checkout of branch from a single remote succeeds #2''+gitcheckoutbaz&&+test_tracking_branchbazrepo_brefs/remotes/other_b/baz+'++test_done
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
When using "git checkout foo" to DWIM the creation of local "foo" from some
existing upstream "foo", we assume conventional refspecs as created by "git
clone" or "git remote add", and fail to work correctly if the current
refspecs do not follow the conventional "refs/remotes/$remote/*" pattern.
Signed-off-by: Johan Herland <redacted>
---
t/t2024-checkout-dwim.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
@@ -49,7 +50,7 @@ test_expect_success 'checkout of non-existing branch fails' 'test_must_failgitcheckoutxyzzy'-test_expect_success'checkout of branch from multiple remotes fails''+test_expect_success'checkout of branch from multiple remotes fails #1''test_must_failgitcheckoutfoo'
@@ -63,4 +64,53 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' 'test_tracking_branchbazrepo_brefs/remotes/other_b/baz'+test_expect_success'setup more remotes with unconventional refspecs''+gitcheckoutmaster&&+gitbranch-Dbar&&+gitbranch-Dbaz&&+test"$(gitrev-parse--verifyHEAD)"="$(gitrev-parse--verifymy_master)"&&+(gitinitrepo_c&&+cdrepo_c&&+test_commitc_master&&+gitcheckout-bbar&&+test_commitc_bar+gitcheckout-bspam&&+test_commitc_spam+)&&+(gitinitrepo_d&&+cdrepo_d&&+test_commitd_master&&+gitcheckout-bbaz&&+test_commitf_baz+gitcheckout-beggs&&+test_commitc_eggs+)&&+gitremoteaddrepo_crepo_c&&+gitconfigremote.repo_c.fetch\+"+refs/heads/*:refs/remotes/extra_dir/repo_c/extra_dir/*"&&+gitfetchrepo_c&&+gitremoteaddrepo_drepo_d&&+gitconfigremote.repo_d.fetch\+"+refs/heads/*:refs/repo_d/*"&&+gitfetchrepo_d+'++test_expect_failure'checkout of branch from multiple remotes fails #2''+test_must_failgitcheckoutbar+'++test_expect_failure'checkout of branch from multiple remotes fails #3''+test_must_failgitcheckoutbaz+'++test_expect_failure'checkout of branch from a single remote succeeds #3''+gitcheckoutspam&&+test_tracking_branchspamrepo_crefs/remotes/extra_dir/repo_c/extra_dir/spam+'++test_expect_failure'checkout of branch from a single remote succeeds #4''+gitcheckouteggs&&+test_tracking_brancheggsrepo_drefs/repo_d/eggs+'+ test_done
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
We are formalizing a requirement that any remote-tracking branch to be used
as an upstream (i.e. as an argument to --track), _must_ "belong" to a
configured remote by being matched by the "dst" side of a fetch refspec.
This patch encodes the new expected behavior of this test, and marks the
test with "test_expect_failure" in anticipation of a following patch to
introduce the new behavior.
Signed-off-by: Johan Herland <redacted>
---
t/t3200-branch.sh | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
We are formalizing a requirement that any remote-tracking branch to be used
as an upstream (i.e. as an argument to --track), _must_ "belong" to a
configured remote by being matched by the "dst" side of a fetch refspec.
Without this patch, this test would start failing when the new behavior is
introduced.
Signed-off-by: Johan Herland <redacted>
---
t/t7201-co.sh | 1 +
1 file changed, 1 insertion(+)
@@ -431,6 +431,7 @@ test_expect_success 'detach a symbolic link HEAD' ' test_expect_success\'checkout with --track fakes a sensible -b <name>''+gitconfigremote.origin.fetch"+refs/heads/*:refs/remotes/origin/*"&&gitupdate-refrefs/remotes/origin/koala/bearrenamer&&gitcheckout--trackorigin/koala/bear&&
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
The current code for validating tracking branches (e.g. the argument to
the -t/--track option) hardcodes refs/heads/* and refs/remotes/* as the
potential locations for tracking branches. This works with the refspecs
created by "git clone" or "git remote add", but is suboptimal in other
cases:
- If "refs/remotes/foo/bar" exists without any association to a remote
(i.e. there is no remote named "foo", or no remote with a refspec
that matches "refs/remotes/foo/bar"), then it is impossible to set up
a valid upstream config that tracks it. Currently, the code defaults
to using "refs/remotes/foo/bar" from repo "." as the upstream, which
works, but is probably not what the user had in mind when running
"git branch baz --track foo/bar".
- If the user has tweaked the fetch refspec for a remote to put its
remote-tracking branches outside of refs/remotes/*, e.g. by running
git config remote.foo.fetch "+refs/heads/*:refs/foo_stuff/*"
then the current code will refuse to use its remote-tracking branches
as --track arguments, since they do not match refs/remotes/*.
This patch removes the "refs/remotes/*" requirement for upstream branches,
and replaces it with explicit checking of the refspecs for each remote to
determine whether a given --track argument is a valid remote-tracking
branch. This solves both of the above problems, since the matching refspec
guarantees that there is a both a remote name and a remote branch name
that can be used for the upstream config.
However, this means that refs located within refs/remotes/* without a
corresponding remote/refspec will no longer be usable as upstreams.
The few existing tests which depended on this behavioral quirk has
already been fixed in the preceding patches.
This patch fixes the last remaining test failure in t2024-checkout-dwim.
Signed-off-by: Johan Herland <redacted>
---
branch.c | 17 ++++++++++++++++-
t/t2024-checkout-dwim.sh | 2 +-
t/t3200-branch.sh | 2 +-
3 files changed, 18 insertions(+), 3 deletions(-)
@@ -197,6 +197,21 @@ int validate_new_branchname(const char *name, struct strbuf *ref,return1;}+staticintcheck_tracking_branch(structremote*remote,void*cb_data)+{+char*tracking_branch=cb_data;+structrefspecquery;+memset(&query,0,sizeof(structrefspec));+query.dst=tracking_branch;+return!(remote_find_tracking(remote,&query)||+prefixcmp(query.src,"refs/heads/"));+}++staticintvalidate_remote_tracking_branch(char*ref)+{+return!for_each_remote(check_tracking_branch,ref);+}+staticconstcharupstream_not_branch[]=N_("Cannot setup tracking information; starting point '%s' is not a branch.");staticconstcharupstream_missing[]=
@@ -259,7 +274,7 @@ void create_branch(const char *head,case1:/* Unique completion -- good, only if it is a real branch */if(prefixcmp(real_ref,"refs/heads/")&&-prefixcmp(real_ref,"refs/remotes/")){+validate_remote_tracking_branch(real_ref)){if(explicit_tracking)die(_(upstream_not_branch),start_name);else
@@ -108,7 +108,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' 'test_tracking_branchspamrepo_crefs/remotes/extra_dir/repo_c/extra_dir/spam'-test_expect_failure'checkout of branch from a single remote succeeds #4''+test_expect_success'checkout of branch from a single remote succeeds #4''gitcheckouteggs&&test_tracking_brancheggsrepo_drefs/repo_d/eggs'
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
We are formalizing a requirement that any remote-tracking branch to be used
as an upstream (i.e. as an argument to --track), _must_ "belong" to a
configured remote by being matched by the "dst" side of a fetch refspec.
This test uses --track against a "remotes/trunk" ref which does not belong
to any configured (git) remotes, but is instead created by "git svn fetch"
operating on an svn-remote. It does not make sense to use an svn-remote as
an upstream for a local branch, as a regular "git pull" from (or "git push"
to) it would obviously fail (instead you would need to use "git svn" to
communicate with this remote). Furthermore, the usage of --track in this
case is unnecessary, since the upstreaming config that would be created is
never used.
Simply removing --track fixes the issue without changing the expected
behavior of the test.
Cc: Eric Wong <redacted>
Signed-off-by: Johan Herland <redacted>
---
t/t9114-git-svn-dcommit-merge.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
The definition of a remote-tracking branch in the glossary have been
out-of-date for a while (by e.g. referring to "Pull:" from old-style
$GIT_DIR/remotes files).
Also, the preceding patches have formalized that a remote-tracking branch
must match a configured refspec in order to be usable as an upstream.
This patch rewrites the paragraph on remote-tracking branches accordingly.
Signed-off-by: Johan Herland <redacted>
---
Documentation/glossary-content.txt | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
@@ -423,12 +423,13 @@ should not be combined with other pathspec. linkgit:git-push[1]. [[def_remote_tracking_branch]]remote-tracking branch::- A regular Git <<def_branch,branch>> that is used to follow changes from- another <<def_repository,repository>>. A remote-tracking- branch should not contain direct modifications or have local commits- made to it. A remote-tracking branch can usually be- identified as the right-hand-side <<def_ref,ref>> in a Pull:- <<def_refspec,refspec>>.+ A <<def_ref,ref>> that is used to follow changes from another+ <<def_repository,repository>>. It typically looks like+ 'refs/remotes/foo/bar' (indicating that it tracks a branch named+ 'bar' in a remote named 'foo'), and matches the right-hand-side of+ a configured fetch <<def_refspec,refspec>>. A remote-tracking+ branch should not contain direct modifications or have local+ commits made to it. [[def_repository]]repository:: A collection of <<def_ref,refs>> together with an
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:56:55
Johan Herland wrote:
The DWIM mode of checkout allows you to run "git checkout foo" when there is
no existing local ref or path called "foo" and there is exactly one remote
with a remote-tracking branch called "foo".
Thanks for testing this. I'm surprised no one suggested a test since
v1.7.0-rc0~51^2~6 (2009-10-18).
Maybe it would also be worthwhile to also test --no-guess? (c.f.
46148dd7, 2009-10-18)
[...]
quoted hunk
+++ b/t/t2024-checkout-dwim.sh
@@ -0,0 +1,66 @@
[...]
+# Arguments: <branch> <remote> <remote-tracking>
+#
+# Verify that we have checked out <branch>, and that it is at the same
+# commit as <remote-tracking>, and that has appropriate tracking config
+# setup against <remote>
+test_tracking_branch() {
+ branch=$1 &&
+ remote=$2 &&
+ remote_track=$3 &&
+ test "refs/heads/$branch" = "$(git rev-parse --symbolic-full-name HEAD)" &&
+ test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify "$remote_track")" &&
+ test "$remote" = "$(git config "branch.$branch.remote")" &&
+ test "refs/heads/$branch" = "$(git config "branch.$branch.merge")"
Stylistic tweaks:
* setting all local vars on one line
* avoiding command substitution so we notice if commands fail
* using test_cmp in place of test $foo = $bar for better output
when the test fails
# Is the current branch "refs/heads/$1"?
test_branch () {
printf "%s\n" "refs/heads/$1" >expect.HEAD &&
git symbolic-ref HEAD >actual.HEAD &&
test_cmp expect.HEAD actual.HEAD
}
# Is branch "refs/heads/$1" set to pull from "$2/$3"?
test_branch_upstream () {
printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
{
git config "branch.$1.remote" &&
git config "branch.$1.merge"
} >actual.upstream &&
test_cmp expect.upstream actual.upstream
}
test_tracking_branch () {
branch=$1 remote=$2 remote_branch=$3 &&
test_branch "$branch" &&
test_cmp_rev "refs/remotes/$remote/$remote_branch" HEAD &&
test_branch_upstream "$branch" "$remote" "$remote_branch"
}
Maybe, to defend against state from previous tests and confirm that
the checkout didn't do anything:
git checkout -B master &&
test_might_fail git branch -D xyzzy &&
test_must_fail git checkout xyzzy &&
test_must_fail git rev-parse --verify refs/heads/xyzzy &&
test_branch master
+
+test_expect_success 'checkout of branch from multiple remotes fails' '
+ test_must_fail git checkout foo
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #1' '
+ git checkout bar &&
+ test_tracking_branch bar repo_a refs/remotes/repo_a/bar
git checkout -B master &&
test_might_fail git branch -D bar &&
git checkout bar &&
test_branch bar &&
test_cmp_rev remotes/repo_a/bar HEAD &&
test_branch_upstream bar repo_a bar
+test_expect_success 'checkout of branch from a single remote succeeds #2' '
+ git checkout baz &&
+ test_tracking_branch baz repo_b refs/remotes/other_b/baz
From: Johan Herland <hidden> Date: 2016-06-15 22:56:55
On Sat, Apr 20, 2013 at 10:44 PM, Jonathan Nieder [off-list ref] wrote:
Johan Herland wrote:
quoted
The DWIM mode of checkout allows you to run "git checkout foo" when there is
no existing local ref or path called "foo" and there is exactly one remote
with a remote-tracking branch called "foo".
Thanks for testing this. I'm surprised no one suggested a test since
v1.7.0-rc0~51^2~6 (2009-10-18).
Maybe it would also be worthwhile to also test --no-guess? (c.f.
46148dd7, 2009-10-18)
[...]
Sane?
Yes. Thanks!
Will incorporate your suggestions into the next iteration.
...Johan
--
Johan Herland, [off-list ref]
www.herland.net