checkout: improve message for ambiguous remote branch name

When the user runs 'git checkout 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 cannot determine
a single remote.

To make it easier to resolve the ambiguity, provide the names of the
matching remotes for the specified branch name.

To achieve that, add an optional feature to the
`unique_tracking_name()` function that allows the matching remote
names to be exposed to the caller.

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:55 +00:00 committed by Junio C Hamano
parent 1af4c26d69
commit 05cf4ceb5b
4 changed files with 37 additions and 13 deletions

View File

@ -1340,9 +1340,12 @@ enum checkout_command {
CHECKOUT_RESTORE = 3,
};

static void advise_disambiguating_remotes(enum checkout_command which_command)
static void advise_disambiguating_remotes(enum checkout_command which_command,
const char *branch,
const struct string_list *matched_remote_names)
{
const char *cmdname;
struct string_list_item *item;

switch (which_command) {
case CHECKOUT_CHECKOUT:
@ -1357,15 +1360,19 @@ static void advise_disambiguating_remotes(enum checkout_command which_command)
break;
}

advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
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 check out a remote tracking branch on <remote>,\n"
"you can do so by fully qualifying the name with the --track option:\n"
"\n"
" git %s --track origin/<name>\n"
" git %s --track <remote>/%s\n"
"\n"
"If you'd like to always have checkouts of an ambiguous <name> prefer\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."),
cmdname);
cmdname, branch);
}

static char *parse_remote_branch(const char *arg,
@ -1374,7 +1381,10 @@ static char *parse_remote_branch(const char *arg,
enum checkout_command which_command)
{
int num_matches = 0;
char *remote = unique_tracking_name(arg, rev, &num_matches);
struct string_list matched_remote_names = STRING_LIST_INIT_DUP;

char *remote = unique_tracking_name(arg, rev, &num_matches,
&matched_remote_names);

if (remote && could_be_checkout_paths) {
die(_("'%s' could be both a local file and a tracking branch.\n"
@ -1384,12 +1394,14 @@ static char *parse_remote_branch(const char *arg,

if (!remote && num_matches > 1) {
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
advise_disambiguating_remotes(which_command);

advise_disambiguating_remotes(which_command, arg,
&matched_remote_names);
die(_("'%s' matched multiple (%d) remote tracking branches"),
arg, num_matches);
}

string_list_clear(&matched_remote_names, 0);

return remote;
}


View File

@ -781,7 +781,7 @@ 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);
char *remote = unique_tracking_name(*new_branch, &oid, NULL, NULL);
return remote;
}
return NULL;
@ -903,7 +903,7 @@ static int add(int ac, const char **av, const char *prefix,

commit = lookup_commit_reference_by_name(branch);
if (!commit) {
remote = unique_tracking_name(branch, &oid, NULL);
remote = unique_tracking_name(branch, &oid, NULL, NULL);
if (remote) {
new_branch = branch;
branch = new_branch_to_free = remote;

View File

@ -8,6 +8,7 @@
#include "checkout.h"
#include "config.h"
#include "strbuf.h"
#include "string-list.h"

struct tracking_name_data {
/* const */ char *src_ref;
@ -17,6 +18,7 @@ struct tracking_name_data {
const char *default_remote;
char *default_dst_ref;
struct object_id *default_dst_oid;
struct string_list *remote_names;
};

#define TRACKING_NAME_DATA_INIT { 0 }
@ -39,6 +41,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
oidcpy(dst, cb->dst_oid);
cb->default_dst_oid = dst;
}
if (cb->remote_names)
string_list_append(cb->remote_names, remote->name);
if (cb->dst_ref) {
free(query.dst);
return 0;
@ -48,14 +52,19 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
}

char *unique_tracking_name(const char *name, struct object_id *oid,
int *dwim_remotes_matched)
int *dwim_remotes_matched,
struct string_list *dwim_remote_names)
{
struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
const char *default_remote = NULL;
if (!repo_config_get_string_tmp(the_repository, "checkout.defaultremote", &default_remote))

if (!repo_config_get_string_tmp(the_repository,
"checkout.defaultremote",
&default_remote))
cb_data.default_remote = default_remote;
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
cb_data.dst_oid = oid;
cb_data.remote_names = dwim_remote_names;
for_each_remote(check_tracking_name, &cb_data);
if (dwim_remotes_matched)
*dwim_remotes_matched = cb_data.num_matches;

View File

@ -3,6 +3,8 @@

#include "hash.h"

struct string_list;

/*
* Check if the branch name uniquely matches a branch name on a remote
* tracking branch. Return the name of the remote if such a branch
@ -10,6 +12,7 @@
*/
char *unique_tracking_name(const char *name,
struct object_id *oid,
int *dwim_remotes_matched);
int *dwim_remotes_matched,
struct string_list *dwim_remote_names);

#endif /* CHECKOUT_H */