The "--" notation disambiguates files and branches, but as a side-effect of the previous implementation, also disabled the branch auto-creation when $branch does not exist. A possible scenario is then: git checkout $branch => fails if $branch is both a ref and a file, and suggests -- git checkout $branch -- => refuses to create the $branch This patch allows the second form to create $branch, and since the -- is provided, it does not look for file named $branch. Signed-off-by: Matthieu Moy <Matthieu.Moy@xxxxxxx> --- Since v2: essentially Jonathan's comments. builtin/checkout.c | 70 ++++++++++++++++++++++++++++++++++-------------- t/t2024-checkout-dwim.sh | 21 +++++++++++++++ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 0f57397..9edd9c3 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -863,6 +863,13 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } +static int error_invalid_ref(const char *arg, int has_dash_dash, int argcount) +{ + if (has_dash_dash) + die(_("invalid reference: %s"), arg); + return argcount; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, @@ -885,20 +892,30 @@ static int parse_branchname_arg(int argc, const char **argv, * * everything after the '--' must be paths. * - * case 3: git checkout <something> [<paths>] + * case 3: git checkout <something> [--] + * + * (a) If <something> is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if <something> is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. + * + * (b) If <something> is _not_ a commit, either "--" is present + * or <something> is not a path, no -t nor -b was given, and + * and there is a tracking branch whose name is <something> + * in one and only one remote, then this is a short-hand to + * fork local <something> from that remote-tracking branch. + * + * (c) Otherwise, if "--" is present, treat it like case (1). * - * With no paths, if <something> is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if <something> is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. + * (d) Otherwise : + * - if it's a reference, treat it like case (1) + * - else if it's a path, treat it like case (2) + * - else: fail. * - * With no paths, if <something> is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * <something> in one and only one remote, then this is a short-hand - * to fork local <something> from that remote-tracking branch. + * case 4: git checkout <something> <paths> * - * Otherwise <something> shall not be ambiguous. + * The first argument must not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). * - else: fail. @@ -917,19 +934,32 @@ static int parse_branchname_arg(int argc, const char **argv, arg = "@{-1}"; if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die(_("invalid reference: %s"), arg); - if (dwim_new_local_branch_ok && - !check_filename(NULL, arg) && - argc == 1) { + /* + * Either case (3) or (4), with <something> not being + * a commit, or an attempt to use case (1) with an + * invalid ref. + */ + int try_dwim = dwim_new_local_branch_ok; + + if (check_filename(NULL, arg) && !has_dash_dash) + try_dwim = 0; + /* + * Accept "git checkout foo" and "git checkout foo --" + * as candidates for dwim. + */ + if (!(argc == 1 && !has_dash_dash) && + !(argc == 2 && has_dash_dash)) + try_dwim = 0; + + if (try_dwim) { const char *remote = unique_tracking_name(arg, rev); if (!remote) - return argcount; + return error_invalid_ref(arg, has_dash_dash, argcount); *new_branch = arg; arg = remote; - /* DWIMmed to create local branch */ + /* DWIMmed to create local branch, case (3).(b) */ } else { - return argcount; + return error_invalid_ref(arg, has_dash_dash, argcount); } } @@ -958,7 +988,7 @@ static int parse_branchname_arg(int argc, const char **argv, if (!*source_tree) /* case (1): want a tree */ die(_("reference is not a tree: %s"), arg); - if (!has_dash_dash) {/* case (3 -> 1) */ + if (!has_dash_dash) {/* case (3).(d) -> (1) */ /* * Do not complain the most common case * git checkout branch diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index 094b92e..6ecb559 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -164,4 +164,25 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' ' test_branch_upstream eggs repo_d eggs ' +test_expect_success 'checkout of branch with a file having the same name fails' ' + git checkout -B master && + test_might_fail git branch -D spam && + + >spam && + test_must_fail git checkout spam && + test_must_fail git rev-parse --verify refs/heads/spam && + test_branch master +' + +test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' ' + git checkout -B master && + test_might_fail git branch -D spam && + + >spam && + git checkout spam -- && + test_branch spam && + test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD && + test_branch_upstream spam repo_c spam +' + test_done -- 1.8.4.474.g128a96c -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html