Skip to content

Commit 8d7b558

Browse files
avargitster
authored andcommitted
checkout & worktree: introduce checkout.defaultRemote
Introduce a checkout.defaultRemote setting which can be used to designate a remote to prefer (via checkout.defaultRemote=origin) when running e.g. "git checkout master" to mean origin/master, even though there's other remotes that have the "master" branch. I want this because it's very handy to use this workflow to checkout a repository and create a topic branch, then get back to a "master" as retrieved from upstream: ( cd /tmp && rm -rf tbdiff && git clone [email protected]:trast/tbdiff.git && cd tbdiff && git branch -m topic && git checkout master ) That will output: Branch 'master' set up to track remote branch 'master' from 'origin'. Switched to a new branch 'master' But as soon as a new remote is added (e.g. just to inspect something from someone else) the DWIMery goes away: ( cd /tmp && rm -rf tbdiff && git clone [email protected]:trast/tbdiff.git && cd tbdiff && git branch -m topic && git remote add avar [email protected]:avar/tbdiff.git && git fetch avar && git checkout master ) Will output (without the advice output added earlier in this series): error: pathspec 'master' did not match any file(s) known to git. The new checkout.defaultRemote config allows me to say that whenever that ambiguity comes up I'd like to prefer "origin", and it'll still work as though the only remote I had was "origin". Also adjust the advice.checkoutAmbiguousRemoteBranchName message to mention this new config setting to the user, the full output on my git.git is now (the last paragraph is new): $ ./git --exec-path=$PWD checkout master error: pathspec 'master' did not match any file(s) known to git. hint: 'master' matched more than one remote tracking branch. hint: We found 26 remotes with a reference that matched. So we fell back hint: on trying to resolve the argument as a path, but failed there too! hint: hint: If you meant to check out a remote tracking branch on, e.g. 'origin', hint: you can do so by fully qualifying the name with the --track option: hint: hint: git checkout --track origin/<name> hint: hint: If you'd like to always have checkouts of an ambiguous <name> prefer hint: one remote, e.g. the 'origin' remote, consider setting hint: checkout.defaultRemote=origin in your config. I considered splitting this into checkout.defaultRemote and worktree.defaultRemote, but it's probably less confusing to break our own rules that anything shared between config should live in core.* than have two config settings, and I couldn't come up with a short name under core.* that made sense (core.defaultRemoteForCheckout?). See also 70c9ac2 ("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"", 2009-10-18) which introduced this DWIM feature to begin with, and 4e85333 ("worktree: make add <path> <branch> dwim", 2017-11-26) which added it to git-worktree. Signed-off-by: Ævar Arnfjörð Bjarmason <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ad8d510 commit 8d7b558

File tree

7 files changed

+109
-7
lines changed

7 files changed

+109
-7
lines changed

Documentation/config.txt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,10 @@ advice.*::
350350
remote tracking branch on more than one remote in
351351
situations where an unambiguous argument would have
352352
otherwise caused a remote-tracking branch to be
353-
checked out.
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.
354357
amWorkDir::
355358
Advice that shows the location of the patch file when
356359
linkgit:git-am[1] fails to apply it.
@@ -1105,6 +1108,22 @@ browser.<tool>.path::
11051108
browse HTML help (see `-w` option in linkgit:git-help[1]) or a
11061109
working repository in gitweb (see linkgit:git-instaweb[1]).
11071110

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

builtin/checkout.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,10 @@ static int parse_branchname_arg(int argc, const char **argv,
912912
* (b) If <something> is _not_ a commit, either "--" is present
913913
* or <something> is not a path, no -t or -b was given, and
914914
* and there is a tracking branch whose name is <something>
915-
* in one and only one remote, then this is a short-hand to
916-
* fork local <something> from that remote-tracking branch.
915+
* in one and only one remote (or if the branch exists on the
916+
* remote named in checkout.defaultRemote), then this is a
917+
* short-hand to fork local <something> from that
918+
* remote-tracking branch.
917919
*
918920
* (c) Otherwise, if "--" is present, treat it like case (1).
919921
*
@@ -1277,7 +1279,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12771279
"If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
12781280
"you can do so by fully qualifying the name with the --track option:\n"
12791281
"\n"
1280-
" git checkout --track origin/<name>"),
1282+
" git checkout --track origin/<name>\n"
1283+
"\n"
1284+
"If you'd like to always have checkouts of an ambiguous <name> prefer\n"
1285+
"one remote, e.g. the 'origin' remote, consider setting\n"
1286+
"checkout.defaultRemote=origin in your config."),
12811287
argv[0],
12821288
dwim_remotes_matched);
12831289
return ret;

checkout.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
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;
1011
int num_matches;
12+
const char *default_remote;
13+
char *default_dst_ref;
14+
struct object_id *default_dst_oid;
1115
};
1216

13-
#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
17+
#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
1418

1519
static int check_tracking_name(struct remote *remote, void *cb_data)
1620
{
@@ -24,6 +28,12 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
2428
return 0;
2529
}
2630
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+
}
2737
if (cb->dst_ref) {
2838
free(query.dst);
2939
return 0;
@@ -36,14 +46,26 @@ const char *unique_tracking_name(const char *name, struct object_id *oid,
3646
int *dwim_remotes_matched)
3747
{
3848
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;
3952
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
4053
cb_data.dst_oid = oid;
4154
for_each_remote(check_tracking_name, &cb_data);
4255
if (dwim_remotes_matched)
4356
*dwim_remotes_matched = cb_data.num_matches;
4457
free(cb_data.src_ref);
45-
if (cb_data.num_matches == 1)
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);
4662
return cb_data.dst_ref;
63+
}
4764
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+
}
4870
return NULL;
4971
}

t/t2024-checkout-dwim.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,23 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
8787
checkout foo 2>stderr &&
8888
test_branch master &&
8989
status_uno_is_clean &&
90-
test_i18ngrep ! "^hint: " stderr
90+
test_i18ngrep ! "^hint: " stderr &&
91+
# Make sure the likes of checkout -p do not print this hint
92+
git checkout -p foo 2>stderr &&
93+
test_i18ngrep ! "^hint: " stderr &&
94+
status_uno_is_clean
95+
'
96+
97+
test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
98+
git checkout -B master &&
99+
status_uno_is_clean &&
100+
test_might_fail git branch -D foo &&
101+
102+
git -c checkout.defaultRemote=repo_a checkout foo &&
103+
status_uno_is_clean &&
104+
test_branch foo &&
105+
test_cmp_rev remotes/repo_a/foo HEAD &&
106+
test_branch_upstream foo repo_a foo
91107
'
92108

93109
test_expect_success 'checkout of branch from a single remote succeeds #1' '

t/t2025-worktree-add.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,27 @@ test_expect_success '"add" <path> <branch> dwims' '
402402
)
403403
'
404404

405+
test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
406+
test_when_finished rm -rf repo_upstream repo_dwim foo &&
407+
setup_remote_repo repo_upstream repo_dwim &&
408+
git init repo_dwim &&
409+
(
410+
cd repo_dwim &&
411+
git remote add repo_upstream2 ../repo_upstream &&
412+
git fetch repo_upstream2 &&
413+
test_must_fail git worktree add ../foo foo &&
414+
git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
415+
>status.expect &&
416+
git status -uno --porcelain >status.actual &&
417+
test_cmp status.expect status.actual
418+
) &&
419+
(
420+
cd foo &&
421+
test_branch_upstream foo repo_upstream foo &&
422+
test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
423+
)
424+
'
425+
405426
test_expect_success 'git worktree add does not match remote' '
406427
test_when_finished rm -rf repo_a repo_b foo &&
407428
setup_remote_repo repo_a repo_b &&

0 commit comments

Comments
 (0)