checkout: die() on ambiguous tracking branches
cover checkout: die() on ambiguous tracking branches
Please refer to commit messages for details.
Rationale: this issue was standing in my way while working on my --pathspec-from-file patches. Since this looks like an oversight, I decided to fix it.
Alexandr Miloslavskiy (2):
parse_branchname_arg(): extract part as new function
checkout: die() on ambiguous tracking branches
builtin/checkout.c | 71 ++++++++++++++++++++++------------------ t/t2024-checkout-dwim.sh | 23 +++++++++++-- 2 files changed, 60 insertions(+), 34 deletions(-)
base-commit: d9f6f3b6195a0ca35642561e530798ad1469bd41
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-477%2FSyntevoAlex%2F%230224(git)_deny_ambiguous_checkout-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-477/SyntevoAlex/#0224(git)_deny_ambiguous_checkout-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/477
Signature · 1 line
gitgitgadget
PATCH 1/2 parse_branchname_arg(): extract part as new function
From: Alexandr Miloslavskiy <[email protected]>
This is done for the next commit to avoid crazy 7x tab code padding.
--- builtin/checkout.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-)
builtin/checkout.c +19 -6
@@ -1113,6 +1113,22 @@ static void setup_new_branch_info_and_source_tree( } } +static const char *parse_remote_branch (const char *arg, +struct object_id *rev, +int could_be_checkout_paths, +int *dwim_remotes_matched) +{ +const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); + +if (remote && could_be_checkout_paths) { +die (_ ("'%s' could be both a local file and a tracking branch.\n" +"Please use -- (and optionally --no-guess) to disambiguate" ), + arg); + } + +return remote; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info,
@@ -1223,13 +1239,10 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim =0 ;if (recover_with_dwim) { -const char *remote =unique_tracking_name (arg, rev, - dwim_remotes_matched); +const char *remote =parse_remote_branch (arg, rev, + could_be_checkout_paths, + dwim_remotes_matched);if (remote) { -if (could_be_checkout_paths) -die (_ ("'%s' could be both a local file and a tracking branch.\n" -"Please use -- (and optionally --no-guess) to disambiguate" ), - arg); *new_branch = arg; arg = remote;/* DWIMmed to create local branch, case (3).(b) */
Signature · 1 line
gitgitgadget
PATCH 2/2 checkout: die() on ambiguous tracking branches
In reply to Alexandr Miloslavskiy via GitGitGadgetFrom: Alexandr Miloslavskiy <[email protected]>
Before this patch, when there were multiple DWIM candidates for remote branch, git decided to try the argument as pathspec instead. I believe that such behavior is a surprise: adding another remote suddenly causes git to discard file contents, because it was unsure which branch to pick. There was an incomplete attempt to prevent that in [3].
I understand that this was never intended:
[1] introduces the unexpected behavior. Before, there was fallback from not-a-ref to pathspec. This is reasonable DWIM. After, there is another fallback from ambiguous-remote to pathspec. I understand that it was kind of copy&paste oversight.
[2] noticed the unexpected behavior but chose to semi-document it instead of forbidding, because the goal of the patch series was focused on something else.
[3] adds die() when there is ambiguity between branch and file. The case of multiple tracking branches is seemingly overlooked.
Change to complain about ambiguity instead of doing unexpected things.
[1] Commit 70c9ac2f ("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"" 2009-10-18)
https://public-inbox.org/git/[email protected]/
[2] Commit ad8d5104 ("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05)
https://public-inbox.org/git/[email protected]/ [3] Commit be4908f1 ("checkout: disambiguate dwim tracking branches and local files", 2018-11-13) https://public-inbox.org/git/[email protected]/
--- builtin/checkout.c | 56 ++++++++++++++++++---------------------- t/t2024-checkout-dwim.sh | 23 +++++++++++++++-- 2 files changed, 46 insertions(+), 33 deletions(-)
builtin/checkout.c +25 -31
@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(static const char *parse_remote_branch (const char *arg,struct object_id *rev, -int could_be_checkout_paths, -int *dwim_remotes_matched) +int could_be_checkout_paths) { -const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); +int num_matches =0 ; +const char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg, arg); } +if (!remote && (num_matches >1 )) { +if (advice_checkout_ambiguous_remote_branch_name) { +advise (_ ("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" +"you can do so by fully qualifying the name with the --track option:\n" +"\n" +" git checkout --track origin/<name>\n" +"\n" +"If you'd like to always have checkouts of an ambiguous <name> prefer\n" +"one remote, e.g. the 'origin' remote, consider setting\n" +"checkout.defaultRemote=origin in your config." )); + } + +die (_ ("'%s' matched multiple (%d) remote tracking branches" ), + arg, num_matches); + } +return remote; }
@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch;int argcount =0 ;
@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,if (recover_with_dwim) {const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths, - dwim_remotes_matched); + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;
@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) {struct branch_info new_branch_info; -int dwim_remotes_matched =0 ;int parseopt_flags =0 ;memset (&new_branch_info,0 ,sizeof (new_branch_info));
@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->track ==BRANCH_TRACK_UNSPECIFIED && !opts->new_branch;int n =parse_branchname_arg (argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; }else if (!opts->accept_ref && opts->from_treeish) {
@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, }UNLEAK (opts); -if (opts->patch_mode || opts->pathspec.nr) { -int ret =checkout_paths (opts, new_branch_info.name); -if (ret && dwim_remotes_matched >1 && - advice_checkout_ambiguous_remote_branch_name) -advise (_ ("'%s' matched more than one remote tracking branch.\n" -"We found %d remotes with a reference that matched. So we fell back\n" -"on trying to resolve the argument as a path, but failed there too!\n" -"\n" -"If you meant to check out a remote tracking branch on, e.g. 'origin',\n" -"you can do so by fully qualifying the name with the --track option:\n" -"\n" -" git checkout --track origin/<name>\n" -"\n" -"If you'd like to always have checkouts of an ambiguous <name> prefer\n" -"one remote, e.g. the 'origin' remote, consider setting\n" -"checkout.defaultRemote=origin in your config." ), - argv[0 ], - dwim_remotes_matched); -return ret; - }else { +if (opts->patch_mode || opts->pathspec.nr) +return checkout_paths (opts, new_branch_info.name); +else return checkout_branch (opts, &new_branch_info); - } }int cmd_checkout (int argc,const char **argv,const char *prefix)
t/t2024-checkout-dwim.sh +21 -2
@@ -37,7 +37,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit a_foo && git checkout -b bar && - test_commit a_bar + test_commit a_bar && + git checkout -b ambiguous_branch_and_file && + test_commit a_ambiguous_branch_and_file ) && git init repo_b && (
@@ -46,7 +48,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit b_foo && git checkout -b baz && - test_commit b_baz + test_commit b_baz && + git checkout -b ambiguous_branch_and_file && + test_commit b_ambiguous_branch_and_file ) && git remote add repo_a repo_a && git remote add repo_b repo_b &&
@@ -75,6 +79,21 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' ' test_branch master ' +test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' ' + git checkout -b t_ambiguous_branch_and_file && + >ambiguous_branch_and_file && + git add ambiguous_branch_and_file && + git commit -m "ambiguous_branch_and_file" && + + test_when_finished "git checkout -- ambiguous_branch_and_file" && + echo "file contents" >ambiguous_branch_and_file && + cp ambiguous_branch_and_file expect && + + test_must_fail git checkout ambiguous_branch_and_file && + + test_cmp expect ambiguous_branch_and_file +' + test_expect_success 'checkout of branch from multiple remotes fails with advice' ' git checkout -B master && test_might_fail git branch -D foo &&
Signature · 1 line
gitgitgadget
Quoted 56 lines · Alexandr Miloslavskiy via GitGitGadget
From: Alexandr Miloslavskiy <[email protected]>
Before this patch, when there were multiple DWIM candidates for remote branch, git decided to try the argument as pathspec instead. I believe that such behavior is a surprise: adding another remote suddenly causes git to discard file contents, because it was unsure which branch to pick. There was an incomplete attempt to prevent that in [3].
I understand that this was never intended:
[1] introduces the unexpected behavior. Before, there was fallback from not-a-ref to pathspec. This is reasonable DWIM. After, there is another fallback from ambiguous-remote to pathspec. I understand that it was kind of copy&paste oversight.
[2] noticed the unexpected behavior but chose to semi-document it instead of forbidding, because the goal of the patch series was focused on something else.
[3] adds
die()when there is ambiguity between branch and file. The case of multiple tracking branches is seemingly overlooked.Change to complain about ambiguity instead of doing unexpected things.
[1] Commit
70c9ac2f("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"" 2009-10-18)
https://public-inbox.org/git/[email protected]/
[2] Commitad8d5104("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05)https://public-inbox.org/git/[email protected]/ [3] Commit be4908f1 ("checkout: disambiguate dwim tracking branches and local files", 2018-11-13) https://public-inbox.org/git/[email protected]/Signed-off-by Alexandr Miloslavskiy <[email protected]>--- builtin/checkout.c | 56 ++++++++++++++++++---------------------- t/t2024-checkout-dwim.sh | 23 +++++++++++++++-- 2 files changed, 46 insertions(+), 33 deletions(-)builtin/checkout.c +3
-3@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(static const char *parse_remote_branch (const char *arg,struct object_id *rev, -int could_be_checkout_paths, -int *dwim_remotes_matched) +int could_be_checkout_paths) { -const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); +int num_matches =0 ; +const char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
builtin/checkout.c static const char *parse_remote_branch(const char *arg, · line 1129
Quoted 4 lines
builtin/checkout.c +1
-0@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg, arg); } +if (!remote && (num_matches >1 )) {
I believe the parentheses around "num_matched > 1" are not needed here.
Quoted 122 lines
builtin/checkout.c +21
-28+if (advice_checkout_ambiguous_remote_branch_name) { +advise (_ ("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" +"you can do so by fully qualifying the name with the --track option:\n" +"\n" +" git checkout --track origin/<name>\n" +"\n" +"If you'd like to always have checkouts of an ambiguous <name> prefer\n" +"one remote, e.g. the 'origin' remote, consider setting\n" +"checkout.defaultRemote=origin in your config." )); + } + +die (_ ("'%s' matched multiple (%d) remote tracking branches" ), + arg, num_matches); + } +return remote; }@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch;int argcount =0 ;@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,if (recover_with_dwim) {const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths, - dwim_remotes_matched); + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) {struct branch_info new_branch_info; -int dwim_remotes_matched =0 ;int parseopt_flags =0 ;memset (&new_branch_info,0 ,sizeof (new_branch_info));@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->track ==BRANCH_TRACK_UNSPECIFIED && !opts->new_branch;int n =parse_branchname_arg (argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; }else if (!opts->accept_ref && opts->from_treeish) {@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, }UNLEAK (opts); -if (opts->patch_mode || opts->pathspec.nr) { -int ret =checkout_paths (opts, new_branch_info.name); -if (ret && dwim_remotes_matched >1 && - advice_checkout_ambiguous_remote_branch_name) -advise (_ ("'%s' matched more than one remote tracking branch.\n" -"We found %d remotes with a reference that matched. So we fell back\n" -"on trying to resolve the argument as a path, but failed there too!\n" -"\n" -"If you meant to check out a remote tracking branch on, e.g. 'origin',\n" -"you can do so by fully qualifying the name with the --track option:\n" -"\n" -" git checkout --track origin/<name>\n" -"\n" -"If you'd like to always have checkouts of an ambiguous <name> prefer\n" -"one remote, e.g. the 'origin' remote, consider setting\n" -"checkout.defaultRemote=origin in your config." ), - argv[0 ], - dwim_remotes_matched); -return ret; - }else { +if (opts->patch_mode || opts->pathspec.nr) +return checkout_paths (opts, new_branch_info.name); +else return checkout_branch (opts, &new_branch_info); - } }int cmd_checkout (int argc,const char **argv,const char *prefix)t/t2024-checkout-dwim.sh +10
-2@@ -37,7 +37,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit a_foo && git checkout -b bar && - test_commit a_bar + test_commit a_bar && + git checkout -b ambiguous_branch_and_file && + test_commit a_ambiguous_branch_and_file ) && git init repo_b && (@@ -46,7 +48,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit b_foo && git checkout -b baz && - test_commit b_baz + test_commit b_baz && + git checkout -b ambiguous_branch_and_file && + test_commit b_ambiguous_branch_and_file ) && git remote add repo_a repo_a && git remote add repo_b repo_b &&@@ -75,6 +79,21 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' ' test_branch master ' +test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' ' + git checkout -b t_ambiguous_branch_and_file && + >ambiguous_branch_and_file && + git add ambiguous_branch_and_file &&
t/t2024-checkout-dwim.sh test_expect_success 'checkout of branch from multiple remotes fails #1' ' · line 95
Quoted 10 lines
t/t2024-checkout-dwim.sh +10
-0+ git commit -m "ambiguous_branch_and_file" && + + test_when_finished "git checkout -- ambiguous_branch_and_file" && + echo "file contents" >ambiguous_branch_and_file && + cp ambiguous_branch_and_file expect && + + test_must_fail git checkout ambiguous_branch_and_file && + + test_cmp expect ambiguous_branch_and_file +'
I like that you added a test for this non-obvious situation. One thing that would help ensure that your test is checking the right thing is to redirect stderr to a file and grep for the die() message you included. Something like:
test_must_fail git checkout ambiguous_branch_and_file 2>err && test_i18ngrep "matched multiple (2) remote tracking branches" err &&
Thanks,
-Stolee
Quoted 3 lines · Derrick Stolee
builtin/checkout.c static const char *parse_remote_branch(const char *arg, · line 1129
Quoted 1 line
builtin/checkout.c +1
-0+if (!remote && (num_matches >1 )) {I believe the parentheses around "
num_matched> 1" are not needed here.
Fixed in v2.
Quoted 6 lines
I like that you added a test for this non-obvious situation. One thing that would help ensure that your test is checking the right thing is to redirect stderr to a file and grep for the
die()message you included. Something like:test_must_fail git checkout ambiguous_branch_and_file 2>err && test_i18ngrep "matched multiple (2) remote tracking branches" err &&
This is a good idea, thanks! Added in v2.
Quoted 22 lines · Alexandr Miloslavskiy via GitGitGadget
From: Alexandr Miloslavskiy <[email protected]>
Before this patch, when there were multiple DWIM candidates for remote branch, git decided to try the argument as pathspec instead. I believe that such behavior is a surprise: adding another remote suddenly causes git to discard file contents, because it was unsure which branch to pick. There was an incomplete attempt to prevent that in [3].
I understand that this was never intended:
[1] introduces the unexpected behavior. Before, there was fallback from not-a-ref to pathspec. This is reasonable DWIM. After, there is another fallback from ambiguous-remote to pathspec. I understand that it was kind of copy&paste oversight.
[2] noticed the unexpected behavior but chose to semi-document it instead of forbidding, because the goal of the patch series was focused on something else.
[3] adds
die()when there is ambiguity between branch and file. The case of multiple tracking branches is seemingly overlooked.
Quoted 8 lines
Change to complain about ambiguity instead of doing unexpected things.
[1] Commit
70c9ac2f("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"" 2009-10-18)
https://public-inbox.org/git/[email protected]/
[2] Commitad8d5104("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05)https://public-inbox.org/git/[email protected]/ [3] Commit be4908f1 ("checkout: disambiguate dwim tracking branches and local files", 2018-11-13) https://public-inbox.org/git/[email protected]/
I'll reserve judgement on whether we really should do this for now, my current opinion on the matter is undefined as I haven't re-paged this behavior of checkout into my brain.
But a giant red flag here for me is that you say "I understand that this was never intended".
Just from a cursory look at this that's not true, for better or worse it *is* intended behavior. Most of the code you're moving around here is what I added in ad8d5104b4 ("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05), and the very start of that commit message refers to the checkout documentation we have that explicitly documents this edge case.
Digging a bit further reveals that we've had this behavior (again, intended, not emergent) since 70c9ac2f19 ("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"", 2009-10-18), and had it documented since 00bb4378c7 ("Documentation/git-checkout.txt: document 70c9ac2 behavior", 2012-12-17).
So at the very least I'd say you need a v2 where you amend the relevant docs & commit message to make a case to the effect of "we've had this since 2009, but it was never really all that important etc.".
Such a change should also be changing the docs etc. added in 8d7b558bae ("checkout & worktree: introduce checkout.defaultRemote", 2018-06-05). With this series our docs don't make a lot of sense anymore & don't describe the behavior with the patches applied.
Quoted 165 lines
Signed-off-by Alexandr Miloslavskiy <[email protected]>--- builtin/checkout.c | 56 ++++++++++++++++++---------------------- t/t2024-checkout-dwim.sh | 23 +++++++++++++++-- 2 files changed, 46 insertions(+), 33 deletions(-)builtin/checkout.c +25
-31@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(static const char *parse_remote_branch (const char *arg,struct object_id *rev, -int could_be_checkout_paths, -int *dwim_remotes_matched) +int could_be_checkout_paths) { -const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); +int num_matches =0 ; +const char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg, arg); } +if (!remote && (num_matches >1 )) { +if (advice_checkout_ambiguous_remote_branch_name) { +advise (_ ("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" +"you can do so by fully qualifying the name with the --track option:\n" +"\n" +" git checkout --track origin/<name>\n" +"\n" +"If you'd like to always have checkouts of an ambiguous <name> prefer\n" +"one remote, e.g. the 'origin' remote, consider setting\n" +"checkout.defaultRemote=origin in your config." )); + } + +die (_ ("'%s' matched multiple (%d) remote tracking branches" ), + arg, num_matches); + } +return remote; }@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch;int argcount =0 ;@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,if (recover_with_dwim) {const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths, - dwim_remotes_matched); + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) {struct branch_info new_branch_info; -int dwim_remotes_matched =0 ;int parseopt_flags =0 ;memset (&new_branch_info,0 ,sizeof (new_branch_info));@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->track ==BRANCH_TRACK_UNSPECIFIED && !opts->new_branch;int n =parse_branchname_arg (argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; }else if (!opts->accept_ref && opts->from_treeish) {@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, }UNLEAK (opts); -if (opts->patch_mode || opts->pathspec.nr) { -int ret =checkout_paths (opts, new_branch_info.name); -if (ret && dwim_remotes_matched >1 && - advice_checkout_ambiguous_remote_branch_name) -advise (_ ("'%s' matched more than one remote tracking branch.\n" -"We found %d remotes with a reference that matched. So we fell back\n" -"on trying to resolve the argument as a path, but failed there too!\n" -"\n" -"If you meant to check out a remote tracking branch on, e.g. 'origin',\n" -"you can do so by fully qualifying the name with the --track option:\n" -"\n" -" git checkout --track origin/<name>\n" -"\n" -"If you'd like to always have checkouts of an ambiguous <name> prefer\n" -"one remote, e.g. the 'origin' remote, consider setting\n" -"checkout.defaultRemote=origin in your config." ), - argv[0 ], - dwim_remotes_matched); -return ret; - }else { +if (opts->patch_mode || opts->pathspec.nr) +return checkout_paths (opts, new_branch_info.name); +else return checkout_branch (opts, &new_branch_info); - } }int cmd_checkout (int argc,const char **argv,const char *prefix)t/t2024-checkout-dwim.sh +21
-2@@ -37,7 +37,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit a_foo && git checkout -b bar && - test_commit a_bar + test_commit a_bar && + git checkout -b ambiguous_branch_and_file && + test_commit a_ambiguous_branch_and_file ) && git init repo_b && (@@ -46,7 +48,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit b_foo && git checkout -b baz && - test_commit b_baz + test_commit b_baz && + git checkout -b ambiguous_branch_and_file && + test_commit b_ambiguous_branch_and_file ) && git remote add repo_a repo_a && git remote add repo_b repo_b &&@@ -75,6 +79,21 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' ' test_branch master ' +test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' ' + git checkout -b t_ambiguous_branch_and_file && + >ambiguous_branch_and_file && + git add ambiguous_branch_and_file && + git commit -m "ambiguous_branch_and_file" && + + test_when_finished "git checkout -- ambiguous_branch_and_file" && + echo "file contents" >ambiguous_branch_and_file && + cp ambiguous_branch_and_file expect && + + test_must_fail git checkout ambiguous_branch_and_file && + + test_cmp expect ambiguous_branch_and_file +' + test_expect_success 'checkout of branch from multiple remotes fails with advice' ' git checkout -B master && test_might_fail git branch -D foo &&
Quoted 20 lines · Ævar Arnfjörð Bjarmason
I'll reserve judgement on whether we really should do this for now, my current opinion on the matter is undefined as I haven't re-paged this behavior of checkout into my brain.
But a giant red flag here for me is that you say "I understand that this was never intended".
Just from a cursory look at this that's not true, for better or worse it *is* intended behavior. Most of the code you're moving around here is what I added in
ad8d5104b4("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05), and the very start of that commit message refers to the checkout documentation we have that explicitly documents this edge case.Digging a bit further reveals that we've had this behavior (again, intended, not emergent) since
70c9ac2f19("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"", 2009-10-18), and had it documented since00bb4378c7("Documentation/git-checkout.txt: document 70c9ac2 behavior", 2012-12-17).
Quoted 4 lines
So at the very least I'd say you need a v2 where you amend the relevant docs & commit message to make a case to the effect of "we've had this since 2009, but it was never really all that important etc.".
I'm sorry, can you please clarify?
My patch addresses the situation where there are *multiple* remote candidates *and* a file with the same name.
My feeling is that in this case, reverting a file is an unintended surprise.
I understand previous patches this way:ad8d5104 - patch series is mostly for "if there are *multiple* remotes,
disambiguate via checkout.defaultRemote". This essentially converts the case of multiple remotes into a single remote. However, this also semi-documents what I'm now preventing. Was that really intended?70c9ac2f - if there is *one* remote, DWIM it.
This isn't what I'm changing.be4908f1 - if there is *one* remote *and* file, die().
This is what I'm extending further.
If the prevented behavior is documented, could you please quote it explicitly?
Quoted 4 lines
Such a change should also be changing the docs etc. added in
8d7b558bae("checkout & worktree: introduce checkout.defaultRemote", 2018-06-05). With this series our docs don't make a lot of sense anymore & don't describe the behavior with the patches applied.
To my understanding, my patch doesn't affect those pieces of docs? Please correct me if I'm wrong.
Ævar, please let me know if your concern is resolved?
Quoted 53 lines · Alexandr Miloslavskiy
Quoted 20 lines · Ævar Arnfjörð Bjarmason
I'll reserve judgement on whether we really should do this for now, my current opinion on the matter is undefined as I haven't re-paged this behavior of checkout into my brain.
But a giant red flag here for me is that you say "I understand that this was never intended".
Just from a cursory look at this that's not true, for better or worse it *is* intended behavior. Most of the code you're moving around here is what I added in
ad8d5104b4("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05), and the very start of that commit message refers to the checkout documentation we have that explicitly documents this edge case.Digging a bit further reveals that we've had this behavior (again, intended, not emergent) since
70c9ac2f19("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"", 2009-10-18), and had it documented since00bb4378c7("Documentation/git-checkout.txt: document 70c9ac2 behavior", 2012-12-17).Quoted 4 lines
So at the very least I'd say you need a v2 where you amend the relevant docs & commit message to make a case to the effect of "we've had this since 2009, but it was never really all that important etc.".
I'm sorry, can you please clarify?
My patch addresses the situation where there are *multiple* remote candidates *and* a file with the same name.
My feeling is that in this case, reverting a file is an unintended surprise.
I understand previous patches this way:
ad8d5104- patch series is mostly for "if there are *multiple* remotes,
disambiguate via checkout.defaultRemote". This essentially converts the case of multiple remotes into a single remote. However, this also semi-documents what I'm now preventing. Was that really intended?70c9ac2f- if there is *one* remote, DWIM it.
This isn't what I'm changing.be4908f1- if there is *one* remote *and* file,die().
This is what I'm extending further.If the prevented behavior is documented, could you please quote it explicitly?
Quoted 4 lines
Such a change should also be changing the docs etc. added in
8d7b558bae("checkout & worktree: introduce checkout.defaultRemote", 2018-06-05). With this series our docs don't make a lot of sense anymore & don't describe the behavior with the patches applied.To my understanding, my patch doesn't affect those pieces of docs? Please correct me if I'm wrong.
v2 cover checkout: die() on ambiguous tracking branches
In reply to Alexandr Miloslavskiy via GitGitGadgetPlease refer to commit messages for details.
Rationale: this issue was standing in my way while working on my --pathspec-from-file patches. Since this looks like an oversight, I decided to fix it.
----------------------------------------------------------------------------
Changes from v1:
1) As suggested, removed parentheses in '&& (num_matches > 1)' 2) As suggested, added additional checks in test. 3) Added some code comments in test
Alexandr Miloslavskiy (2):
parse_branchname_arg(): extract part as new function
checkout: die() on ambiguous tracking branches
builtin/checkout.c | 71 ++++++++++++++++++++++------------------ t/t2024-checkout-dwim.sh | 28 ++++++++++++++-- 2 files changed, 65 insertions(+), 34 deletions(-)
base-commit: d9f6f3b6195a0ca35642561e530798ad1469bd41
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-477%2FSyntevoAlex%2F%230224(git)_deny_ambiguous_checkout-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-477/SyntevoAlex/#0224(git)_deny_ambiguous_checkout-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/477
Range-diff vs v1:
1: c5b92ce3ed = 1: 1e40cc485b parse_branchname_arg(): extract part as new function
2: 7dde1a3b4e ! 2: 575eeb97ba checkout: die() on ambiguous tracking branches
@@ -54,7 +54,7 @@
arg);
}
-+ if (!remote && (num_matches > 1)) {
++ if (!remote && num_matches > 1) {
+ if (advice_checkout_ambiguous_remote_branch_name) {
+ advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+ "you can do so by fully qualifying the name with the --track option:\n"
@@ -174,17 +174,22 @@
'
+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '
++ # create a file with name matching remote branch name
+ git checkout -b t_ambiguous_branch_and_file &&
+ >ambiguous_branch_and_file &&
+ git add ambiguous_branch_and_file &&
+ git commit -m "ambiguous_branch_and_file" &&
+
++ # modify file to verify that it will not be touched by checkout
+ test_when_finished "git checkout -- ambiguous_branch_and_file" &&
+ echo "file contents" >ambiguous_branch_and_file &&
+ cp ambiguous_branch_and_file expect &&
+
-+ test_must_fail git checkout ambiguous_branch_and_file &&
++ test_must_fail git checkout ambiguous_branch_and_file 2>err &&
+
++ test_i18ngrep "matched multiple (2) remote tracking branches" err &&
++
++ # file must not be altered
+ test_cmp expect ambiguous_branch_and_file
+'
+Signature · 1 line
gitgitgadget
PATCH v2 1/2 parse_branchname_arg(): extract part as new function
From: Alexandr Miloslavskiy <[email protected]>
This is done for the next commit to avoid crazy 7x tab code padding.
--- builtin/checkout.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-)
builtin/checkout.c +19 -6
@@ -1113,6 +1113,22 @@ static void setup_new_branch_info_and_source_tree( } } +static const char *parse_remote_branch (const char *arg, +struct object_id *rev, +int could_be_checkout_paths, +int *dwim_remotes_matched) +{ +const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); + +if (remote && could_be_checkout_paths) { +die (_ ("'%s' could be both a local file and a tracking branch.\n" +"Please use -- (and optionally --no-guess) to disambiguate" ), + arg); + } + +return remote; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info,
@@ -1223,13 +1239,10 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim =0 ;if (recover_with_dwim) { -const char *remote =unique_tracking_name (arg, rev, - dwim_remotes_matched); +const char *remote =parse_remote_branch (arg, rev, + could_be_checkout_paths, + dwim_remotes_matched);if (remote) { -if (could_be_checkout_paths) -die (_ ("'%s' could be both a local file and a tracking branch.\n" -"Please use -- (and optionally --no-guess) to disambiguate" ), - arg); *new_branch = arg; arg = remote;/* DWIMmed to create local branch, case (3).(b) */
Signature · 1 line
gitgitgadget
PATCH v2 2/2 checkout: die() on ambiguous tracking branches
In reply to Alexandr Miloslavskiy via GitGitGadgetFrom: Alexandr Miloslavskiy <[email protected]>
Before this patch, when there were multiple DWIM candidates for remote branch, git decided to try the argument as pathspec instead. I believe that such behavior is a surprise: adding another remote suddenly causes git to discard file contents, because it was unsure which branch to pick. There was an incomplete attempt to prevent that in [3].
I understand that this was never intended:
[1] introduces the unexpected behavior. Before, there was fallback from not-a-ref to pathspec. This is reasonable DWIM. After, there is another fallback from ambiguous-remote to pathspec. I understand that it was kind of copy&paste oversight.
[2] noticed the unexpected behavior but chose to semi-document it instead of forbidding, because the goal of the patch series was focused on something else.
[3] adds die() when there is ambiguity between branch and file. The case of multiple tracking branches is seemingly overlooked.
Change to complain about ambiguity instead of doing unexpected things.
[1] Commit 70c9ac2f ("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"" 2009-10-18)
https://public-inbox.org/git/[email protected]/
[2] Commit ad8d5104 ("checkout: add advice for ambiguous "checkout <branch>"", 2018-06-05)
https://public-inbox.org/git/[email protected]/ [3] Commit be4908f1 ("checkout: disambiguate dwim tracking branches and local files", 2018-11-13) https://public-inbox.org/git/[email protected]/
--- builtin/checkout.c | 56 ++++++++++++++++++---------------------- t/t2024-checkout-dwim.sh | 28 ++++++++++++++++++-- 2 files changed, 51 insertions(+), 33 deletions(-)
builtin/checkout.c +25 -31
@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(static const char *parse_remote_branch (const char *arg,struct object_id *rev, -int could_be_checkout_paths, -int *dwim_remotes_matched) +int could_be_checkout_paths) { -const char *remote =unique_tracking_name (arg, rev, dwim_remotes_matched); +int num_matches =0 ; +const char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg, arg); } +if (!remote && num_matches >1 ) { +if (advice_checkout_ambiguous_remote_branch_name) { +advise (_ ("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" +"you can do so by fully qualifying the name with the --track option:\n" +"\n" +" git checkout --track origin/<name>\n" +"\n" +"If you'd like to always have checkouts of an ambiguous <name> prefer\n" +"one remote, e.g. the 'origin' remote, consider setting\n" +"checkout.defaultRemote=origin in your config." )); + } + +die (_ ("'%s' matched multiple (%d) remote tracking branches" ), + arg, num_matches); + } +return remote; }
@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch;int argcount =0 ;
@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,if (recover_with_dwim) {const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths, - dwim_remotes_matched); + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;
@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) {struct branch_info new_branch_info; -int dwim_remotes_matched =0 ;int parseopt_flags =0 ;memset (&new_branch_info,0 ,sizeof (new_branch_info));
@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->track ==BRANCH_TRACK_UNSPECIFIED && !opts->new_branch;int n =parse_branchname_arg (argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; }else if (!opts->accept_ref && opts->from_treeish) {
@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, }UNLEAK (opts); -if (opts->patch_mode || opts->pathspec.nr) { -int ret =checkout_paths (opts, new_branch_info.name); -if (ret && dwim_remotes_matched >1 && - advice_checkout_ambiguous_remote_branch_name) -advise (_ ("'%s' matched more than one remote tracking branch.\n" -"We found %d remotes with a reference that matched. So we fell back\n" -"on trying to resolve the argument as a path, but failed there too!\n" -"\n" -"If you meant to check out a remote tracking branch on, e.g. 'origin',\n" -"you can do so by fully qualifying the name with the --track option:\n" -"\n" -" git checkout --track origin/<name>\n" -"\n" -"If you'd like to always have checkouts of an ambiguous <name> prefer\n" -"one remote, e.g. the 'origin' remote, consider setting\n" -"checkout.defaultRemote=origin in your config." ), - argv[0 ], - dwim_remotes_matched); -return ret; - }else { +if (opts->patch_mode || opts->pathspec.nr) +return checkout_paths (opts, new_branch_info.name); +else return checkout_branch (opts, &new_branch_info); - } }int cmd_checkout (int argc,const char **argv,const char *prefix)
t/t2024-checkout-dwim.sh +26 -2
@@ -37,7 +37,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit a_foo && git checkout -b bar && - test_commit a_bar + test_commit a_bar && + git checkout -b ambiguous_branch_and_file && + test_commit a_ambiguous_branch_and_file ) && git init repo_b && (
@@ -46,7 +48,9 @@ test_expect_success 'setup' ' git checkout -b foo && test_commit b_foo && git checkout -b baz && - test_commit b_baz + test_commit b_baz && + git checkout -b ambiguous_branch_and_file && + test_commit b_ambiguous_branch_and_file ) && git remote add repo_a repo_a && git remote add repo_b repo_b &&
@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' ' test_branch master ' +test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' ' + # create a file with name matching remote branch name + git checkout -b t_ambiguous_branch_and_file && + >ambiguous_branch_and_file && + git add ambiguous_branch_and_file && + git commit -m "ambiguous_branch_and_file" && + + # modify file to verify that it will not be touched by checkout + test_when_finished "git checkout -- ambiguous_branch_and_file" && + echo "file contents" >ambiguous_branch_and_file && + cp ambiguous_branch_and_file expect && + + test_must_fail git checkout ambiguous_branch_and_file 2>err && + + test_i18ngrep "matched multiple (2) remote tracking branches" err && + + # file must not be altered + test_cmp expect ambiguous_branch_and_file +' + test_expect_success 'checkout of branch from multiple remotes fails with advice' ' git checkout -B master && test_might_fail git branch -D foo &&
Signature · 1 line
gitgitgadget
"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>
Quoted 1 line · writes:
Please refer to commit messages for details.
When sending an updated series after getting review comments from others to your earlier round of the same series, please make sure these reviewers are CC'ed, especially those who thought the earlier one(s) were not quite right, so that they can say "oh, I changed my mind. This round is good", or "I think it is still wrong in this round, because of ...".
You do not necessarily have to include me on CC: line before the list seems to have reached concensus that the patches are good.
Thanks.
Quoted 9 lines · Junio C Hamano
When sending an updated series after getting review comments from others to your earlier round of the same series, please make sure these reviewers are CC'ed, especially those who thought the earlier one(s) were not quite right, so that they can say "oh, I changed my mind. This round is good", or "I think it is still wrong in this round, because of ...".
You do not necessarily have to include me on CC: line before the list seems to have reached concensus that the patches are good.
OK, thanks for explaining! I'm new and didn't know about that.
For this patch, I already informed reviewers via explicit replies. From the next one, I'll try to remember to also CC them.