checkout: restrict @-expansions when finding branch
authorJeff King <peff@peff.net>
Thu, 2 Mar 2017 08:23:18 +0000 (03:23 -0500)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Mar 2017 19:05:04 +0000 (11:05 -0800)
When we parse "git checkout $NAME", we try to interpret
$NAME as a local branch-name. If it is, then we point HEAD
to that branch. Otherwise, we detach the HEAD at whatever
commit $NAME points to.

We do the interpretation by calling strbuf_branchname(), and
then blindly sticking "refs/heads/" on the front. This leads
to nonsense results when expansions like "@{upstream}" or
"@" point to something besides a local branch. We end up
with a local branch name like "refs/heads/origin/master" or
"refs/heads/HEAD".

Normally this has no user-visible effect because those
branches don't exist, and so we fallback to feeding the
result to get_sha1(), which resolves them correctly.

But as the new test in t3204 shows, there are corner cases
where the effect is observable, and we check out the wrong
local branch rather than detaching to the correct one.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/checkout.c
t/t3204-branch-name-interpretation.sh
index 711a92353061f6d30192668a7f60c975062fecba..767ca9e18c2d3c859e31117cb0d2583b24ffe496 100644 (file)
@@ -452,7 +452,7 @@ static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
 
-       strbuf_branchname(&buf, branch->name, 0);
+       strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
        if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
        strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
index 05e88f92d98a7d15db08345633cf624209ffcce2..698d9cc4f3d65754552b8ccdd46b0cb64aea09c5 100755 (executable)
@@ -120,4 +120,14 @@ test_expect_success 'delete branch named "@"' '
        expect_deleted refs/heads/@
 '
 
+test_expect_success 'checkout does not treat remote @{upstream} as a branch' '
+       git update-ref refs/remotes/origin/checkout one &&
+       git branch --set-upstream-to=origin/checkout &&
+       git update-ref refs/heads/origin/checkout two &&
+       git update-ref refs/heads/remotes/origin/checkout two &&
+
+       git checkout @{upstream} &&
+       expect_branch HEAD one
+'
+
 test_done