worktree add: improve message for ambiguous remote branch name

When the user runs 'git worktree add ../foo-dir bar-topic' without
specifying a remote, and there is no local branch named bar-topic, we
try to guess which remote branch bar-topic refers to, then create a
new branch named bar-topic that tracks the remote branch.

If multiple remotes have a branch named bar-topic, we silently gave
up, leaving the variable 'branch' intact.  We then entered the
conditional clause 'if (!opts.orphan &&
!lookup_commit_reference_by_name(branch))' and triggered an "invalid
reference" error.  This error message did not provide enough
information to resolve the ambiguity.

When multiple matching branches are found, display a hint and a
descriptive error message and die immediately.

Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
next
Yoichi NAKAYAMA 2026-08-27 14:41:56 +00:00 committed by Junio C Hamano
parent 05cf4ceb5b
commit 85489606d8
2 changed files with 36 additions and 5 deletions

View File

@ -763,6 +763,25 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)
return 1;
}

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"
"<remote>, you can do so by:\n"
"\n"
" git worktree add -b %s %s <remote>/%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 char *path, char **new_branch)
{
int n;
@ -897,17 +916,29 @@ static int add(int ac, const char **av, const char *prefix,
/* DWIM: Infer --orphan when repo has no refs. */
opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);
} else if (ac == 2) {
struct object_id oid;
struct commit *commit;
char *remote;

commit = lookup_commit_reference_by_name(branch);
if (!commit) {
remote = unique_tracking_name(branch, &oid, NULL, NULL);
struct object_id oid;
char *remote;
int num_matches = 0;
struct string_list matched_remote_names = STRING_LIST_INIT_DUP;

remote = unique_tracking_name(branch, &oid, &num_matches,
&matched_remote_names);
if (remote) {
new_branch = branch;
branch = new_branch_to_free = remote;
} else if (num_matches > 1) {
if (!opts.quiet &&
advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
advise_disambiguating_remotes(path, branch,
&matched_remote_names);
die(_("'%s' matched multiple (%d) remote tracking branches"),
branch, num_matches);
}
string_list_clear(&matched_remote_names, 0);
}

if (!strcmp(branch, "HEAD"))

View File

@ -624,12 +624,12 @@ test_expect_success '"add" <path> <branch> dwims' '
test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
test_when_finished rm -rf repo_upstream repo_dwim foo &&
setup_remote_repo repo_upstream repo_dwim &&
git init repo_dwim &&
(
cd repo_dwim &&
git remote add repo_upstream2 ../repo_upstream &&
git fetch repo_upstream2 &&
test_must_fail git worktree add ../foo foo &&
test_must_fail git worktree add ../foo foo 2>error.actual &&
test_grep "matched multiple (2) remote tracking branches" error.actual &&
git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
git status -uno --porcelain >status.actual &&
test_must_be_empty status.actual