Skip to content

Commit 50858ed

Browse files
committed
Merge branch 'ab/checkout-default-remote'
"git checkout" and "git worktree add" learned to honor checkout.defaultRemote when auto-vivifying a local branch out of a remote tracking branch in a repository with multiple remotes that have tracking branches that share the same names. * ab/checkout-default-remote: checkout & worktree: introduce checkout.defaultRemote checkout: add advice for ambiguous "checkout <branch>" builtin/checkout.c: use "ret" variable for return checkout: pass the "num_matches" up to callers checkout.c: change "unique" member to "num_matches" checkout.c: introduce an *_INIT macro checkout.h: wrap the arguments to unique_tracking_name() checkout tests: index should be clean after dwim checkout
2 parents a81575a + 8d7b558 commit 50858ed

File tree

11 files changed

+197
-16
lines changed

11 files changed

+197
-16
lines changed

Documentation/config.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,16 @@ advice.*::
344344
Advice shown when you used linkgit:git-checkout[1] to
345345
move to the detach HEAD state, to instruct how to create
346346
a local branch after the fact.
347+
checkoutAmbiguousRemoteBranchName::
348+
Advice shown when the argument to
349+
linkgit:git-checkout[1] ambiguously resolves to a
350+
remote tracking branch on more than one remote in
351+
situations where an unambiguous argument would have
352+
otherwise caused a remote-tracking branch to be
353+
checked out. See the `checkout.defaultRemote`
354+
configuration variable for how to set a given remote
355+
to used by default in some situations where this
356+
advice would be printed.
347357
amWorkDir::
348358
Advice that shows the location of the patch file when
349359
linkgit:git-am[1] fails to apply it.
@@ -1104,6 +1114,22 @@ browser.<tool>.path::
11041114
browse HTML help (see `-w` option in linkgit:git-help[1]) or a
11051115
working repository in gitweb (see linkgit:git-instaweb[1]).
11061116

1117+
checkout.defaultRemote::
1118+
When you run 'git checkout <something>' and only have one
1119+
remote, it may implicitly fall back on checking out and
1120+
tracking e.g. 'origin/<something>'. This stops working as soon
1121+
as you have more than one remote with a '<something>'
1122+
reference. This setting allows for setting the name of a
1123+
preferred remote that should always win when it comes to
1124+
disambiguation. The typical use-case is to set this to
1125+
`origin`.
1126+
+
1127+
Currently this is used by linkgit:git-checkout[1] when 'git checkout
1128+
<something>' will checkout the '<something>' branch on another remote,
1129+
and by linkgit:git-worktree[1] when 'git worktree add' refers to a
1130+
remote branch. This setting might be used for other checkout-like
1131+
commands or functionality in the future.
1132+
11071133
clean.requireForce::
11081134
A boolean to make git-clean do nothing unless given -f,
11091135
-i or -n. Defaults to true.

Documentation/git-checkout.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ equivalent to
3838
$ git checkout -b <branch> --track <remote>/<branch>
3939
------------
4040
+
41+
If the branch exists in multiple remotes and one of them is named by
42+
the `checkout.defaultRemote` configuration variable, we'll use that
43+
one for the purposes of disambiguation, even if the `<branch>` isn't
44+
unique across all remotes. Set it to
45+
e.g. `checkout.defaultRemote=origin` to always checkout remote
46+
branches from there if `<branch>` is ambiguous but exists on the
47+
'origin' remote. See also `checkout.defaultRemote` in
48+
linkgit:git-config[1].
49+
+
4150
You could omit <branch>, in which case the command degenerates to
4251
"check out the current branch", which is a glorified no-op with
4352
rather expensive side-effects to show only the tracking information,

Documentation/git-worktree.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
6060
$ git worktree add --track -b <branch> <path> <remote>/<branch>
6161
------------
6262
+
63+
If the branch exists in multiple remotes and one of them is named by
64+
the `checkout.defaultRemote` configuration variable, we'll use that
65+
one for the purposes of disambiguation, even if the `<branch>` isn't
66+
unique across all remotes. Set it to
67+
e.g. `checkout.defaultRemote=origin` to always checkout remote
68+
branches from there if `<branch>` is ambiguous but exists on the
69+
'origin' remote. See also `checkout.defaultRemote` in
70+
linkgit:git-config[1].
71+
+
6372
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
6473
then, as a convenience, the new worktree is associated with a branch
6574
(call it `<branch>`) named after `$(basename <path>)`. If `<branch>`

advice.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ int advice_add_embedded_repo = 1;
2323
int advice_ignored_hook = 1;
2424
int advice_waiting_for_editor = 1;
2525
int advice_graft_file_deprecated = 1;
26+
int advice_checkout_ambiguous_remote_branch_name = 1;
2627

2728
static int advice_use_color = -1;
2829
static char advice_colors[][COLOR_MAXLEN] = {
@@ -75,6 +76,7 @@ static struct {
7576
{ "ignoredHook", &advice_ignored_hook },
7677
{ "waitingForEditor", &advice_waiting_for_editor },
7778
{ "graftFileDeprecated", &advice_graft_file_deprecated },
79+
{ "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
7880

7981
/* make this an alias for backward compatibility */
8082
{ "pushNonFastForward", &advice_push_update_rejected }

advice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extern int advice_add_embedded_repo;
2323
extern int advice_ignored_hook;
2424
extern int advice_waiting_for_editor;
2525
extern int advice_graft_file_deprecated;
26+
extern int advice_checkout_ambiguous_remote_branch_name;
2627

2728
int git_default_advice_config(const char *var, const char *value);
2829
__attribute__((format (printf, 1, 2)))

builtin/checkout.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "resolve-undo.h"
2424
#include "submodule-config.h"
2525
#include "submodule.h"
26+
#include "advice.h"
2627

2728
static const char * const checkout_usage[] = {
2829
N_("git checkout [<options>] <branch>"),
@@ -879,7 +880,8 @@ static int parse_branchname_arg(int argc, const char **argv,
879880
int dwim_new_local_branch_ok,
880881
struct branch_info *new_branch_info,
881882
struct checkout_opts *opts,
882-
struct object_id *rev)
883+
struct object_id *rev,
884+
int *dwim_remotes_matched)
883885
{
884886
struct tree **source_tree = &opts->source_tree;
885887
const char **new_branch = &opts->new_branch;
@@ -911,8 +913,10 @@ static int parse_branchname_arg(int argc, const char **argv,
911913
* (b) If <something> is _not_ a commit, either "--" is present
912914
* or <something> is not a path, no -t or -b was given, and
913915
* and there is a tracking branch whose name is <something>
914-
* in one and only one remote, then this is a short-hand to
915-
* fork local <something> from that remote-tracking branch.
916+
* in one and only one remote (or if the branch exists on the
917+
* remote named in checkout.defaultRemote), then this is a
918+
* short-hand to fork local <something> from that
919+
* remote-tracking branch.
916920
*
917921
* (c) Otherwise, if "--" is present, treat it like case (1).
918922
*
@@ -973,7 +977,8 @@ static int parse_branchname_arg(int argc, const char **argv,
973977
recover_with_dwim = 0;
974978

975979
if (recover_with_dwim) {
976-
const char *remote = unique_tracking_name(arg, rev);
980+
const char *remote = unique_tracking_name(arg, rev,
981+
dwim_remotes_matched);
977982
if (remote) {
978983
*new_branch = arg;
979984
arg = remote;
@@ -1110,6 +1115,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
11101115
struct branch_info new_branch_info;
11111116
char *conflict_style = NULL;
11121117
int dwim_new_local_branch = 1;
1118+
int dwim_remotes_matched = 0;
11131119
struct option options[] = {
11141120
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
11151121
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1222,7 +1228,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12221228
opts.track == BRANCH_TRACK_UNSPECIFIED &&
12231229
!opts.new_branch;
12241230
int n = parse_branchname_arg(argc, argv, dwim_ok,
1225-
&new_branch_info, &opts, &rev);
1231+
&new_branch_info, &opts, &rev,
1232+
&dwim_remotes_matched);
12261233
argv += n;
12271234
argc -= n;
12281235
}
@@ -1264,8 +1271,26 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12641271
}
12651272

12661273
UNLEAK(opts);
1267-
if (opts.patch_mode || opts.pathspec.nr)
1268-
return checkout_paths(&opts, new_branch_info.name);
1269-
else
1274+
if (opts.patch_mode || opts.pathspec.nr) {
1275+
int ret = checkout_paths(&opts, new_branch_info.name);
1276+
if (ret && dwim_remotes_matched > 1 &&
1277+
advice_checkout_ambiguous_remote_branch_name)
1278+
advise(_("'%s' matched more than one remote tracking branch.\n"
1279+
"We found %d remotes with a reference that matched. So we fell back\n"
1280+
"on trying to resolve the argument as a path, but failed there too!\n"
1281+
"\n"
1282+
"If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
1283+
"you can do so by fully qualifying the name with the --track option:\n"
1284+
"\n"
1285+
" git checkout --track origin/<name>\n"
1286+
"\n"
1287+
"If you'd like to always have checkouts of an ambiguous <name> prefer\n"
1288+
"one remote, e.g. the 'origin' remote, consider setting\n"
1289+
"checkout.defaultRemote=origin in your config."),
1290+
argv[0],
1291+
dwim_remotes_matched);
1292+
return ret;
1293+
} else {
12701294
return checkout_branch(&opts, &new_branch_info);
1295+
}
12711296
}

builtin/worktree.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
412412
if (guess_remote) {
413413
struct object_id oid;
414414
const char *remote =
415-
unique_tracking_name(*new_branch, &oid);
415+
unique_tracking_name(*new_branch, &oid, NULL);
416416
return remote;
417417
}
418418
return NULL;
@@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)
484484

485485
commit = lookup_commit_reference_by_name(branch);
486486
if (!commit) {
487-
remote = unique_tracking_name(branch, &oid);
487+
remote = unique_tracking_name(branch, &oid, NULL);
488488
if (remote) {
489489
new_branch = branch;
490490
branch = remote;

checkout.c

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
#include "remote.h"
33
#include "refspec.h"
44
#include "checkout.h"
5+
#include "config.h"
56

67
struct tracking_name_data {
78
/* const */ char *src_ref;
89
char *dst_ref;
910
struct object_id *dst_oid;
10-
int unique;
11+
int num_matches;
12+
const char *default_remote;
13+
char *default_dst_ref;
14+
struct object_id *default_dst_oid;
1115
};
1216

17+
#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
18+
1319
static int check_tracking_name(struct remote *remote, void *cb_data)
1420
{
1521
struct tracking_name_data *cb = cb_data;
@@ -21,24 +27,45 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
2127
free(query.dst);
2228
return 0;
2329
}
30+
cb->num_matches++;
31+
if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
32+
struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
33+
cb->default_dst_ref = xstrdup(query.dst);
34+
oidcpy(dst, cb->dst_oid);
35+
cb->default_dst_oid = dst;
36+
}
2437
if (cb->dst_ref) {
2538
free(query.dst);
26-
cb->unique = 0;
2739
return 0;
2840
}
2941
cb->dst_ref = query.dst;
3042
return 0;
3143
}
3244

33-
const char *unique_tracking_name(const char *name, struct object_id *oid)
45+
const char *unique_tracking_name(const char *name, struct object_id *oid,
46+
int *dwim_remotes_matched)
3447
{
35-
struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
48+
struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
49+
const char *default_remote = NULL;
50+
if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
51+
cb_data.default_remote = default_remote;
3652
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
3753
cb_data.dst_oid = oid;
3854
for_each_remote(check_tracking_name, &cb_data);
55+
if (dwim_remotes_matched)
56+
*dwim_remotes_matched = cb_data.num_matches;
3957
free(cb_data.src_ref);
40-
if (cb_data.unique)
58+
free((char *)default_remote);
59+
if (cb_data.num_matches == 1) {
60+
free(cb_data.default_dst_ref);
61+
free(cb_data.default_dst_oid);
4162
return cb_data.dst_ref;
63+
}
4264
free(cb_data.dst_ref);
65+
if (cb_data.default_dst_ref) {
66+
oidcpy(oid, cb_data.default_dst_oid);
67+
free(cb_data.default_dst_oid);
68+
return cb_data.default_dst_ref;
69+
}
4370
return NULL;
4471
}

checkout.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* tracking branch. Return the name of the remote if such a branch
99
* exists, NULL otherwise.
1010
*/
11-
extern const char *unique_tracking_name(const char *name, struct object_id *oid);
11+
extern const char *unique_tracking_name(const char *name,
12+
struct object_id *oid,
13+
int *dwim_remotes_matched);
1214

1315
#endif /* CHECKOUT_H */

0 commit comments

Comments
 (0)