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
|
set to true, `worktree add` tries to find a remote-tracking
|
||||||
branch whose name uniquely matches the new branch name. If
|
branch whose name uniquely matches the new branch name. If
|
||||||
such a branch exists, it is checked out and set as "upstream"
|
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
|
for the new branch. If multiple matches are found, the command
|
||||||
back to creating a new branch from the current `HEAD`.
|
fails. If no such match can be found, it falls back to
|
||||||
|
creating a new branch from the current `HEAD`.
|
||||||
|
|
||||||
`worktree.useRelativePaths`::
|
`worktree.useRelativePaths`::
|
||||||
Link worktrees using relative paths (when "`true`") or absolute
|
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
|
of creating a new branch from `HEAD`, if there exists a tracking
|
||||||
branch in exactly one remote matching the basename of _<path>_,
|
branch in exactly one remote matching the basename of _<path>_,
|
||||||
base the new branch on the remote-tracking branch, and mark
|
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
|
This can also be set up as the default behaviour by using the
|
||||||
`worktree.guessRemote` config option.
|
`worktree.guessRemote` config option.
|
||||||
|
|
|
||||||
|
|
@ -1343,13 +1343,51 @@ enum checkout_command {
|
||||||
CHECKOUT_RESTORE = 3,
|
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,
|
static char *parse_remote_branch(const char *arg,
|
||||||
struct object_id *rev,
|
struct object_id *rev,
|
||||||
int could_be_checkout_paths,
|
int could_be_checkout_paths,
|
||||||
enum checkout_command which_command)
|
enum checkout_command which_command)
|
||||||
{
|
{
|
||||||
int num_matches = 0;
|
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) {
|
if (remote && could_be_checkout_paths) {
|
||||||
die(_("'%s' could be both a local file and a tracking branch.\n"
|
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 (!remote && num_matches > 1) {
|
||||||
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
|
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
|
||||||
const char *cmdname;
|
advise_disambiguating_remotes(which_command, arg,
|
||||||
|
&matched_remote_names);
|
||||||
switch (which_command) {
|
die(_("'%s' matched multiple (%d) remote tracking branches"),
|
||||||
case CHECKOUT_CHECKOUT:
|
arg, num_matches);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string_list_clear(&matched_remote_names, 0);
|
||||||
|
|
||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -764,7 +764,26 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)
|
||||||
return 1;
|
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 n;
|
||||||
int branch_exists;
|
int branch_exists;
|
||||||
|
|
@ -782,7 +801,21 @@ static char *dwim_branch(const char *path, char **new_branch)
|
||||||
*new_branch = branchname;
|
*new_branch = branchname;
|
||||||
if (guess_remote) {
|
if (guess_remote) {
|
||||||
struct object_id oid;
|
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 remote;
|
||||||
}
|
}
|
||||||
return NULL;
|
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);
|
opts.orphan = dwim_orphan(&opts, !!opt_track, 0);
|
||||||
} else if (ac < 2) {
|
} else if (ac < 2) {
|
||||||
/* DWIM: Guess branch name from path. */
|
/* 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)
|
if (s)
|
||||||
branch = branch_to_free = s;
|
branch = branch_to_free = s;
|
||||||
new_branch = new_branch_to_free;
|
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"))
|
if (!strcmp(branch, "HEAD"))
|
||||||
can_use_local_refs(&opts);
|
can_use_local_refs(&opts);
|
||||||
} else if (ac == 2) {
|
} else if (ac == 2) {
|
||||||
struct object_id oid;
|
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
char *remote;
|
|
||||||
|
|
||||||
commit = lookup_commit_reference_by_name(branch);
|
commit = lookup_commit_reference_by_name(branch);
|
||||||
if (!commit) {
|
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) {
|
if (remote) {
|
||||||
new_branch = branch;
|
new_branch = branch;
|
||||||
branch = new_branch_to_free = remote;
|
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"))
|
if (!strcmp(branch, "HEAD"))
|
||||||
|
|
|
||||||
13
checkout.c
13
checkout.c
|
|
@ -8,6 +8,7 @@
|
||||||
#include "checkout.h"
|
#include "checkout.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
|
#include "string-list.h"
|
||||||
|
|
||||||
struct tracking_name_data {
|
struct tracking_name_data {
|
||||||
/* const */ char *src_ref;
|
/* const */ char *src_ref;
|
||||||
|
|
@ -17,6 +18,7 @@ struct tracking_name_data {
|
||||||
const char *default_remote;
|
const char *default_remote;
|
||||||
char *default_dst_ref;
|
char *default_dst_ref;
|
||||||
struct object_id *default_dst_oid;
|
struct object_id *default_dst_oid;
|
||||||
|
struct string_list *remote_names;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TRACKING_NAME_DATA_INIT { 0 }
|
#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);
|
oidcpy(dst, cb->dst_oid);
|
||||||
cb->default_dst_oid = dst;
|
cb->default_dst_oid = dst;
|
||||||
}
|
}
|
||||||
|
if (cb->remote_names)
|
||||||
|
string_list_append(cb->remote_names, remote->name);
|
||||||
if (cb->dst_ref) {
|
if (cb->dst_ref) {
|
||||||
free(query.dst);
|
free(query.dst);
|
||||||
return 0;
|
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,
|
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;
|
struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
|
||||||
const char *default_remote = NULL;
|
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.default_remote = default_remote;
|
||||||
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
|
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
|
||||||
cb_data.dst_oid = oid;
|
cb_data.dst_oid = oid;
|
||||||
|
cb_data.remote_names = dwim_remote_names;
|
||||||
for_each_remote(check_tracking_name, &cb_data);
|
for_each_remote(check_tracking_name, &cb_data);
|
||||||
if (dwim_remotes_matched)
|
if (dwim_remotes_matched)
|
||||||
*dwim_remotes_matched = cb_data.num_matches;
|
*dwim_remotes_matched = cb_data.num_matches;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
|
|
||||||
|
struct string_list;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the branch name uniquely matches a branch name on a remote
|
* 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
|
* tracking branch. Return the name of the remote if such a branch
|
||||||
|
|
@ -10,6 +12,7 @@
|
||||||
*/
|
*/
|
||||||
char *unique_tracking_name(const char *name,
|
char *unique_tracking_name(const char *name,
|
||||||
struct object_id *oid,
|
struct object_id *oid,
|
||||||
int *dwim_remotes_matched);
|
int *dwim_remotes_matched,
|
||||||
|
struct string_list *dwim_remote_names);
|
||||||
|
|
||||||
#endif /* CHECKOUT_H */
|
#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_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
|
||||||
test_when_finished rm -rf repo_upstream repo_dwim foo &&
|
test_when_finished rm -rf repo_upstream repo_dwim foo &&
|
||||||
setup_remote_repo repo_upstream repo_dwim &&
|
setup_remote_repo repo_upstream repo_dwim &&
|
||||||
git init repo_dwim &&
|
|
||||||
(
|
(
|
||||||
cd repo_dwim &&
|
cd repo_dwim &&
|
||||||
git remote add repo_upstream2 ../repo_upstream &&
|
git remote add repo_upstream2 ../repo_upstream &&
|
||||||
git fetch repo_upstream2 &&
|
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 -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
|
||||||
git status -uno --porcelain >status.actual &&
|
git status -uno --porcelain >status.actual &&
|
||||||
test_must_be_empty 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_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_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '
|
||||||
test_when_finished rm -rf repo_a repo_b foo &&
|
test_when_finished rm -rf repo_a repo_b foo &&
|
||||||
setup_remote_repo repo_a repo_b &&
|
setup_remote_repo repo_a repo_b &&
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue