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@imag.fr
---
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;