Merge branch 'yn/worktree-ambiguous-remote-advice' into jch
'git worktree add' did not prevent DWIM behavior when '-b' or '-B' was specified, which has been corrected. * yn/worktree-ambiguous-remote-advice: worktree add: treat multiple matches with --guess-remote as an error worktree add: improve message for ambiguous remote branch name checkout: improve message for ambiguous remote branch name checkout: extract function to display advice for ambiguous remotesjch
commit
411769ef6f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 _<path>_,
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1343,13 +1343,51 @@ enum checkout_command {
|
|||
CHECKOUT_RESTORE = 3,
|
||||
};
|
||||
|
||||
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:
|
||||
cmdname = "checkout";
|
||||
break;
|
||||
case CHECKOUT_SWITCH:
|
||||
cmdname = "switch";
|
||||
break;
|
||||
default:
|
||||
BUG("command <%d> should not reach advise_disambiguating_remotes",
|
||||
which_command);
|
||||
break;
|
||||
}
|
||||
|
||||
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 <remote>/%s\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."),
|
||||
cmdname, branch);
|
||||
}
|
||||
|
||||
static char *parse_remote_branch(const char *arg,
|
||||
struct object_id *rev,
|
||||
int could_be_checkout_paths,
|
||||
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"
|
||||
|
|
@ -1358,37 +1396,15 @@ static char *parse_remote_branch(const char *arg,
|
|||
}
|
||||
|
||||
if (!remote && num_matches > 1) {
|
||||
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
|
||||
const char *cmdname;
|
||||
|
||||
switch (which_command) {
|
||||
case CHECKOUT_CHECKOUT:
|
||||
cmdname = "checkout";
|
||||
break;
|
||||
case CHECKOUT_SWITCH:
|
||||
cmdname = "switch";
|
||||
break;
|
||||
default:
|
||||
BUG("command <%d> should not reach parse_remote_branch",
|
||||
which_command);
|
||||
break;
|
||||
}
|
||||
|
||||
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 %s --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."),
|
||||
cmdname);
|
||||
}
|
||||
|
||||
die(_("'%s' matched multiple (%d) remote tracking branches"),
|
||||
arg, num_matches);
|
||||
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
"<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 struct add_opts *opts, const char *path, char **new_branch)
|
||||
{
|
||||
int n;
|
||||
int branch_exists;
|
||||
|
|
@ -782,7 +801,21 @@ 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;
|
||||
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;
|
||||
|
|
@ -890,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;
|
||||
|
|
@ -901,17 +934,29 @@ static int add(int ac, const char **av, const char *prefix,
|
|||
if (!strcmp(branch, "HEAD"))
|
||||
can_use_local_refs(&opts);
|
||||
} 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);
|
||||
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"))
|
||||
|
|
|
|||
13
checkout.c
13
checkout.c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -634,12 +634,12 @@ test_expect_success '"add" <path> <branch> does not dwim with -b' '
|
|||
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
|
||||
|
|
@ -679,6 +679,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 &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue