From: Yoichi NAKAYAMA via GitGitGadget Date: Tue, 25 Aug 2026 21:04:35 GMT Subject: [PATCH v8 4/4] worktree add: treat multiple matches with --guess-remote as an error Message-ID: <927856e0a07cf6d9e7e1093a4fbf979584141224.1787691875.git.gitgitgadget@gmail.com> In-Reply-To: From: Yoichi NAKAYAMA When 'git worktree add ' is invoked without and with the --guess-remote option (or when worktree.guessRemote is set to true), it tries to find a remote-tracking branch matching the basename of . Currently, the behavior when multiple matches are found is the same as when no match is found: it falls back to creating a branch from HEAD. This has been the behavior since 71d6682d8c (worktree: add --guess-remote option to add subcommand, 2017-11-29), when the option was first introduced. However, if the specified matches any remote-tracking branch, we infer that the user intended to use one of the remote-tracking branches as the start-point rather than HEAD. So we abort the creation of the branch and worktree when there are multiple matches, and instruct the user to choose the start-point. Signed-off-by: Yoichi NAKAYAMA --- Documentation/config/worktree.adoc | 5 +-- Documentation/git-worktree.adoc | 4 ++- builtin/worktree.c | 58 ++++++++++++++++++------------ t/t2400-worktree-add.sh | 13 +++++++ 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc index a248076ea5..0930183b91 100644 --- a/Documentation/config/worktree.adoc +++ b/Documentation/config/worktree.adoc @@ -5,8 +5,9 @@ set to true, `worktree add` tries to find a remote-tracking branch whose name uniquely matches the new branch name. If such a branch exists, it is checked out and set as "upstream" - for the new branch. If no such match can be found, it falls - back to creating a new branch from the current `HEAD`. + for the new branch. If multiple matches are found, the command + fails. If no such match can be found, it falls back to + creating a new branch from the current `HEAD`. `worktree.useRelativePaths`:: Link worktrees using relative paths (when "`true`") or absolute diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc index fbf8426cd9..32787eacc3 100644 --- a/Documentation/git-worktree.adoc +++ b/Documentation/git-worktree.adoc @@ -219,7 +219,9 @@ To remove a locked worktree, specify `--force` twice. of creating a new branch from `HEAD`, if there exists a tracking branch in exactly one remote matching the basename of __, base the new branch on the remote-tracking branch, and mark - the remote-tracking branch as "upstream" from the new branch. + the remote-tracking branch as "upstream" from the new branch. If + there are multiple matches, the command fails. If there is no + match, the command falls back to creating a new branch from `HEAD`. + This can also be set up as the default behaviour by using the `worktree.guessRemote` config option. diff --git a/builtin/worktree.c b/builtin/worktree.c index 320b18873d..07163bf9b7 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -764,7 +764,26 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote) return 1; } -static char *dwim_branch(const char *path, char **new_branch) +static void advise_disambiguating_remotes(const char *path, const char *branch, + const struct string_list *matched_remote_names) +{ + struct string_list_item *item; + + advise(_("Branch name '%s' appears in multiple remotes:"), branch); + for_each_string_list_item(item, matched_remote_names) { + advise(_(" %s"), item->string); + } + advise(_("If you meant to create a worktree from a remote tracking branch on\n" + ", you can do so by:\n" + "\n" + " git worktree add -b %s %s /%s\n" + "\n" + "If you'd like to always prefer some remote, e.g. 'origin',\n" + "consider setting checkout.defaultRemote=origin in your config."), + branch, path, branch); +} + +static char *dwim_branch(const struct add_opts *opts, const char *path, char **new_branch) { int n; int branch_exists; @@ -782,31 +801,26 @@ static char *dwim_branch(const char *path, char **new_branch) *new_branch = branchname; if (guess_remote) { struct object_id oid; - char *remote = unique_tracking_name(*new_branch, &oid, NULL, NULL); + char *remote; + int num_matches = 0; + struct string_list matched_remote_names = STRING_LIST_INIT_DUP; + + remote = unique_tracking_name(*new_branch, &oid, &num_matches, + &matched_remote_names); + if (!remote && num_matches > 1) { + if (!opts->quiet && + advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) + advise_disambiguating_remotes(path, *new_branch, + &matched_remote_names); + die(_("'%s' matched multiple (%d) remote tracking branches"), + *new_branch, num_matches); + } + string_list_clear(&matched_remote_names, 0); return remote; } return NULL; } -static void advise_disambiguating_remotes(const char *path, const char *branch, - const struct string_list *matched_remote_names) -{ - struct string_list_item *item; - - advise(_("Branch name '%s' appears in multiple remotes:"), branch); - for_each_string_list_item(item, matched_remote_names) { - advise(_(" %s"), item->string); - } - advise(_("If you meant to create a worktree from a remote tracking branch on\n" - ", you can do so by:\n" - "\n" - " git worktree add -b %s %s /%s\n" - "\n" - "If you'd like to always prefer some remote, e.g. 'origin',\n" - "consider setting checkout.defaultRemote=origin in your config."), - branch, path, branch); -} - static int add(int ac, const char **av, const char *prefix, struct repository *repo UNUSED) { @@ -909,7 +923,7 @@ static int add(int ac, const char **av, const char *prefix, opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { /* DWIM: Guess branch name from path. */ - char *s = dwim_branch(path, &new_branch_to_free); + char *s = dwim_branch(&opts, path, &new_branch_to_free); if (s) branch = branch_to_free = s; new_branch = new_branch_to_free; diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 5c105cf252..a37137042d 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -669,6 +669,19 @@ test_expect_success 'git worktree add --guess-remote sets up tracking' ' test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo ) ' + +test_expect_success 'git worktree add --guess-remote fails if there are multiple matches' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git remote add repo_a2 ../repo_a && + git fetch repo_a2 && + test_must_fail git worktree add --guess-remote ../foo 2>actual && + test_grep "matched multiple (2) remote tracking branches" actual + ) +' + test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' ' test_when_finished rm -rf repo_a repo_b foo && setup_remote_repo repo_a repo_b && -- gitgitgadget