Skip to content

Commit be4908f

Browse files
pcloudsgitster
authored andcommitted
checkout: disambiguate dwim tracking branches and local files
When checkout dwim is added in [1], it is restricted to only dwim when certain conditions are met and fall back to default checkout behavior otherwise. It turns out falling back could be confusing. One of the conditions to turn git checkout frotz to git checkout -b frotz origin/frotz is that frotz must not exist as a file. But when the user comes to expect "git checkout frotz" to create the branch "frotz" and there happens to be a file named "frotz", git's silently reverting "frotz" file content is not helping. This is reported in Git mailing list [2] and even used as an example of "Git is bad" elsewhere [3]. We normally try to do the right thing, but when there are multiple "right things" to do, it's best to leave it to the user to decide. Check this case, ask the user to to disambiguate: - "git checkout -- foo" will check out path "foo" - "git checkout foo --" will dwim and create branch "foo" [4] For users who do not want dwim, use --no-guess. It's useless in this particular case because "git checkout --no-guess foo --" will just fail. But it could be used by scripts. [1] 70c9ac2 (DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz" - 2009-10-18) [2] https://public-inbox.org/git/CACsJy8B2TVr1g+k+eSQ=pBEO3WN4_LtgLo9gpur8X7Z9GOFL_A@mail.gmail.com/ [3] https://news.ycombinator.com/item?id=18230655 [4] a047faf (checkout: allow dwim for branch creation for "git checkout $branch --" - 2013-10-18) Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d166e6a commit be4908f

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

Documentation/git-checkout.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
276276
Just like linkgit:git-submodule[1], this will detach the
277277
submodules HEAD.
278278

279+
--no-guess::
280+
Do not attempt to create a branch if a remote tracking branch
281+
of the same name exists.
282+
279283
<branch>::
280284
Branch to checkout; if it refers to a branch (i.e., a name that,
281285
when prepended with "refs/heads/", is a valid ref), then that

builtin/checkout.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,9 +1079,12 @@ static int parse_branchname_arg(int argc, const char **argv,
10791079
*/
10801080
int recover_with_dwim = dwim_new_local_branch_ok;
10811081

1082-
if (!has_dash_dash &&
1083-
(check_filename(opts->prefix, arg) || !no_wildcard(arg)))
1082+
int could_be_checkout_paths = !has_dash_dash &&
1083+
check_filename(opts->prefix, arg);
1084+
1085+
if (!has_dash_dash && !no_wildcard(arg))
10841086
recover_with_dwim = 0;
1087+
10851088
/*
10861089
* Accept "git checkout foo" and "git checkout foo --"
10871090
* as candidates for dwim.
@@ -1094,6 +1097,10 @@ static int parse_branchname_arg(int argc, const char **argv,
10941097
const char *remote = unique_tracking_name(arg, rev,
10951098
dwim_remotes_matched);
10961099
if (remote) {
1100+
if (could_be_checkout_paths)
1101+
die(_("'%s' could be both a local file and a tracking branch.\n"
1102+
"Please use -- (and optionally --no-guess) to disambiguate"),
1103+
arg);
10971104
*new_branch = arg;
10981105
arg = remote;
10991106
/* DWIMmed to create local branch, case (3).(b) */
@@ -1228,7 +1235,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12281235
struct checkout_opts opts;
12291236
struct branch_info new_branch_info;
12301237
char *conflict_style = NULL;
1231-
int dwim_new_local_branch = 1;
1238+
int dwim_new_local_branch, no_dwim_new_local_branch = 0;
12321239
int dwim_remotes_matched = 0;
12331240
struct option options[] = {
12341241
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
@@ -1258,8 +1265,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12581265
OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
12591266
OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
12601267
N_("do not limit pathspecs to sparse entries only")),
1261-
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
1262-
N_("second guess 'git checkout <no-such-branch>'")),
1268+
OPT_BOOL(0, "no-guess", &no_dwim_new_local_branch,
1269+
N_("do not second guess 'git checkout <no-such-branch>'")),
12631270
OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
12641271
N_("do not check if another worktree is holding the given ref")),
12651272
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
@@ -1282,6 +1289,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12821289
argc = parse_options(argc, argv, prefix, options, checkout_usage,
12831290
PARSE_OPT_KEEP_DASHDASH);
12841291

1292+
dwim_new_local_branch = !no_dwim_new_local_branch;
12851293
if (opts.show_progress < 0) {
12861294
if (opts.quiet)
12871295
opts.show_progress = 0;

t/t2024-checkout-dwim.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,35 @@ test_expect_success 'loosely defined local base branch is reported correctly' '
278278
test_cmp expect actual
279279
'
280280

281+
test_expect_success 'reject when arg could be part of dwim branch' '
282+
git remote add foo file://non-existent-place &&
283+
git update-ref refs/remotes/foo/dwim-arg HEAD &&
284+
echo foo >dwim-arg &&
285+
git add dwim-arg &&
286+
echo bar >dwim-arg &&
287+
test_must_fail git checkout dwim-arg &&
288+
test_must_fail git rev-parse refs/heads/dwim-arg -- &&
289+
grep bar dwim-arg
290+
'
291+
292+
test_expect_success 'disambiguate dwim branch and checkout path (1)' '
293+
git update-ref refs/remotes/foo/dwim-arg1 HEAD &&
294+
echo foo >dwim-arg1 &&
295+
git add dwim-arg1 &&
296+
echo bar >dwim-arg1 &&
297+
git checkout -- dwim-arg1 &&
298+
test_must_fail git rev-parse refs/heads/dwim-arg1 -- &&
299+
grep foo dwim-arg1
300+
'
301+
302+
test_expect_success 'disambiguate dwim branch and checkout path (2)' '
303+
git update-ref refs/remotes/foo/dwim-arg2 HEAD &&
304+
echo foo >dwim-arg2 &&
305+
git add dwim-arg2 &&
306+
echo bar >dwim-arg2 &&
307+
git checkout dwim-arg2 -- &&
308+
git rev-parse refs/heads/dwim-arg2 -- &&
309+
grep bar dwim-arg2
310+
'
311+
281312
test_done

t/t9902-completion.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,8 @@ test_expect_success 'double dash "git checkout"' '
14341434
--ignore-other-worktrees Z
14351435
--recurse-submodules Z
14361436
--progress Z
1437-
--no-quiet Z
1437+
--guess Z
1438+
--no-guess Z
14381439
--no-... Z
14391440
EOF
14401441
'

0 commit comments

Comments
 (0)