git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] checkout & worktree: introduce a core.DWIMRemote setting

From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Date
May 2, 2018, 10:54 UTC
Message-ID
<20180502105452.17583-1-avarab@gmail.com>

Introduce a core.DWIMRemote setting which can be used to designate a remote to prefer (via core.DWIMRemote=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:

    (
        rm -rf /tmp/tbdiff &&
        git clone git@github.com: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:

    (
        rm -rf /tmp/tbdiff &&
        git clone git@github.com:trast/tbdiff.git &&
        cd tbdiff &&
        git branch -m topic &&
        git remote add avar git@github.com:avar/tbdiff.git &&
        git fetch avar &&
        git checkout master
    )
Will output:
    error: pathspec 'master' did not match any file(s) known to git.

The new core.DWIMRemote 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".

I considered calling the config setting checkout.DWIMRemote, but then discovered that this behavior is also used by "worktree" for similar purposes, so it makes sense to have it under core.*. As noted in the documentation we may also want to use this in other commands in the future if they have similar DWIM behavior in the presence of one remote.

See also 70c9ac2f19 ("DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"", 2009-10-18) which introduced this DWIM feature to begin with, and 4e85333197 ("worktree: make add <path> <branch> dwim", 2017-11-26) which added it to git-worktree.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/config.txt       | 15 +++++++++++++++
 Documentation/git-checkout.txt |  5 +++++
 Documentation/git-worktree.txt |  5 +++++
 builtin/checkout.c             |  3 ++-
 checkout.c                     | 17 +++++++++++++++--
 t/t2024-checkout-dwim.sh       | 10 ++++++++++
 t/t2025-worktree-add.sh        | 18 ++++++++++++++++++
 7 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2659153cb3..d4740fcdb9 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -898,6 +898,21 @@ core.notesRef::
 This setting defaults to "refs/notes/commits", and it can be overridden by
 the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
 
+core.DWIMRemote::
+	Various git commands will look at references on the configured
+	remotes and DW[YI]M (Do What (You|I) Mean) if the reference
+	only exists on one remote. This setting allows for setting the
+	name of a special remote that should always win when it comes
+	to disambiguation. The typical use-case is to set this to
+	`origin`.
++
+Currently this is used by linkgit:git-checkout[1] when `git checkout
+<something>' will checkout the '<something>' branch on another remote,
+and linkgit:git-worktree[1] when 'git worktree add' similarly DWYM
+when a branch is unique across remotes, or this setting is set to a
+special remote. This setting might be used for other commands or
+functionality in the future when appropriate.
+
 core.sparseCheckout::
 	Enable "sparse checkout" feature. See section "Sparse checkout" in
 	linkgit:git-read-tree[1] for more information.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..8ad7be6c53 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,11 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes the `core.DWIMRemote`
+variable can be used to pick the remote you really mean. Set it to
+e.g. `core.DWIMRemote=origin` to always checkout remote branches from
+there. See also `core.DWIMRemote` in linkgit:git-config[1].
++
 You could omit <branch>, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 2755ca90e3..37db12f816 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -60,6 +60,11 @@ with a matching name, treat as equivalent to:
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
+It's also possible to use the `core.DWIMRemote` setting to designate a
+special remote this rule should be applied to, even if the branch
+isn't unique across all remotes. See `core.DWIMRemote` in
+linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, a new branch based at HEAD is created automatically,
 as if `-b $(basename <path>)` was specified.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b49b582071..8622ad4ef1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -903,7 +903,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 	 *   (b) If <something> is _not_ a commit, either "--" is present
 	 *       or <something> is not a path, no -t or -b was given, and
 	 *       and there is a tracking branch whose name is <something>
-	 *       in one and only one remote, then this is a short-hand to
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in core.DWIMRemote), then this is a short-hand to
 	 *       fork local <something> from that remote-tracking branch.
 	 *
 	 *   (c) Otherwise, if "--" is present, treat it like case (1).
diff --git a/checkout.c b/checkout.c
index ac42630f74..6e474fbfbb 100644
--- a/checkout.c
+++ b/checkout.c
@@ -1,12 +1,15 @@
 #include "cache.h"
 #include "remote.h"
 #include "checkout.h"
+#include "config.h"
 
 struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int unique;
+	const char *dwim_remote;
+	char *dwim_dst_ref;
 };
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
@@ -20,6 +23,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		free(query.dst);
 		return 0;
 	}
+	if (cb->dwim_remote && !strcmp(remote->name, cb->dwim_remote))
+		cb->dwim_dst_ref = xstrdup(query.dst);
 	if (cb->dst_ref) {
 		free(query.dst);
 		cb->unique = 0;
@@ -31,13 +36,21 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 
 const char *unique_tracking_name(const char *name, struct object_id *oid)
 {
-	struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+	const char *dwim_remote = NULL;
+	struct tracking_name_data cb_data = { NULL, NULL, NULL, 1, NULL, NULL };
+	if (!git_config_get_string_const("core.dwimremote", &dwim_remote))
+		cb_data.dwim_remote = dwim_remote;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	free(cb_data.src_ref);
-	if (cb_data.unique)
+	free((char *)dwim_remote);
+	if (cb_data.unique) {
+		free(cb_data.dwim_dst_ref);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.dwim_dst_ref)
+		return cb_data.dwim_dst_ref;
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..d8db2a14d7 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -68,6 +68,16 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_branch master
 '
 
+test_expect_success 'checkout of branch from multiple remotes succeeds with core.DWIMRemote #1' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+
+	git -c core.DWIMRemote=repo_a checkout foo &&
+	test_branch foo &&
+	test_cmp_rev remotes/repo_a/foo HEAD &&
+	test_branch_upstream foo repo_a foo
+'
+
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	git checkout -B master &&
 	test_might_fail git branch -D bar &&
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index d0d2e4f7ec..6be4cd03da 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -450,6 +450,24 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' '
 	)
 '
 
+test_expect_success '"add" <path> <branch> dwims with core.DWIMRemote' '
+	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 &&
+		git -c core.DWIMRemote=repo_upstream worktree add ../foo foo
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_upstream foo &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+	)
+'
+
 post_checkout_hook () {
 	gitdir=${1:-.git}
 	test_when_finished "rm -f $gitdir/hooks/post-checkout" &&
-- 
2.17.0.290.gded63e768a
Next: Duy Nguyen
Message 1 of 95 in “checkout & worktree: introduce a core.DWIMRemote setting”
  1. checkout & worktree: introduce a core.DWIMRemote settingÆvar Arnfjörð Bjarmason, May 2, 2018
  2. Duy NguyenMay 2, 2018
  3. Eric SunshineMay 2, 2018
  4. Duy NguyenMay 2, 2018
  5. Ævar Arnfjörð BjarmasonMay 2, 2018
  6. checkout & worktree: introduce checkout.implicitRemoteÆvar Arnfjörð Bjarmason, May 3, 2018
  7. Duy NguyenMay 3, 2018
  8. Ævar Arnfjörð BjarmasonMay 4, 2018
  9. Duy NguyenMay 4, 2018
  10. Ævar Arnfjörð BjarmasonMay 4, 2018
  11. Eric SunshineMay 4, 2018
  12. checkout & worktree: introduce checkout.implicitRemoteÆvar Arnfjörð Bjarmason, May 24, 2018
  13. Junio C HamanoMay 25, 2018
  14. Duy NguyenMay 25, 2018
  15. Ævar Arnfjörð BjarmasonMay 25, 2018
  16. Duy NguyenMay 26, 2018
  17. Ævar Arnfjörð BjarmasonMay 31, 2018
  18. 0/9 ambiguous checkout UI & checkout.defaultRemoteÆvar Arnfjörð Bjarmason, May 31, 2018
  19. 1/9 checkout tests: index should be clean after dwim checkoutÆvar Arnfjörð Bjarmason, May 31, 2018
  20. Junio C HamanoJun 1, 2018
  21. Ævar Arnfjörð BjarmasonJun 1, 2018
  22. 2/9 checkout.h: wrap the arguments to unique_tracking_name()Ævar Arnfjörð Bjarmason, May 31, 2018
  23. 3/9 checkout.[ch]: move struct declaration into *.hÆvar Arnfjörð Bjarmason, May 31, 2018
  24. Thomas GummererMay 31, 2018
  25. Junio C HamanoJun 1, 2018
  26. Ævar Arnfjörð BjarmasonJun 1, 2018
  27. 4/9 checkout.[ch]: introduce an *_INIT macroÆvar Arnfjörð Bjarmason, May 31, 2018
  28. Junio C HamanoJun 1, 2018
  29. 5/9 checkout.[ch]: change "unique" member to "num_matches"Ævar Arnfjörð Bjarmason, May 31, 2018
  30. 7/9 builtin/checkout.c: use "ret" variable for returnÆvar Arnfjörð Bjarmason, May 31, 2018
  31. 6/9 checkout: pass the "num_matches" up to callersÆvar Arnfjörð Bjarmason, May 31, 2018
  32. 9/9 checkout & worktree: introduce checkout.defaultRemoteÆvar Arnfjörð Bjarmason, May 31, 2018
  33. Stefan BellerMay 31, 2018
  34. Eric SunshineJun 1, 2018
  35. Ævar Arnfjörð BjarmasonJun 1, 2018
  36. Thomas GummererMay 31, 2018
  37. Junio C HamanoJun 1, 2018
  38. 8/9 checkout: add advice for ambiguous "checkout <branch>"Ævar Arnfjörð Bjarmason, May 31, 2018
  39. Junio C HamanoJun 1, 2018
  40. Junio C HamanoJun 1, 2018
  41. Ævar Arnfjörð BjarmasonJun 1, 2018
  42. Junio C HamanoJun 4, 2018
  43. Ævar Arnfjörð BjarmasonJun 1, 2018
  44. Eric SunshineJun 1, 2018
  45. Ævar Arnfjörð BjarmasonJun 1, 2018
  46. 0/8 ambiguous checkout UI & checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 1, 2018
  47. 1/8 checkout tests: index should be clean after dwim checkoutÆvar Arnfjörð Bjarmason, Jun 1, 2018
  48. 2/8 checkout.h: wrap the arguments to unique_tracking_name()Ævar Arnfjörð Bjarmason, Jun 1, 2018
  49. 3/8 checkout.[ch]: introduce an *_INIT macroÆvar Arnfjörð Bjarmason, Jun 1, 2018
  50. Eric SunshineJun 1, 2018
  51. 4/8 checkout.[ch]: change "unique" member to "num_matches"Ævar Arnfjörð Bjarmason, Jun 1, 2018
  52. Eric SunshineJun 1, 2018
  53. 6/8 builtin/checkout.c: use "ret" variable for returnÆvar Arnfjörð Bjarmason, Jun 1, 2018
  54. 5/8 checkout: pass the "num_matches" up to callersÆvar Arnfjörð Bjarmason, Jun 1, 2018
  55. 8/8 checkout & worktree: introduce checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 1, 2018
  56. 7/8 checkout: add advice for ambiguous "checkout <branch>"Ævar Arnfjörð Bjarmason, Jun 1, 2018
  57. Eric SunshineJun 1, 2018
  58. 0/8 ambiguous checkout UI & checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 2, 2018
  59. 1/8 checkout tests: index should be clean after dwim checkoutÆvar Arnfjörð Bjarmason, Jun 2, 2018
  60. 2/8 checkout.h: wrap the arguments to unique_tracking_name()Ævar Arnfjörð Bjarmason, Jun 2, 2018
  61. 3/8 checkout.c: introduce an *_INIT macroÆvar Arnfjörð Bjarmason, Jun 2, 2018
  62. 4/8 checkout.c]: change "unique" member to "num_matches"Ævar Arnfjörð Bjarmason, Jun 2, 2018
  63. 6/8 builtin/checkout.c: use "ret" variable for returnÆvar Arnfjörð Bjarmason, Jun 2, 2018
  64. 5/8 checkout: pass the "num_matches" up to callersÆvar Arnfjörð Bjarmason, Jun 2, 2018
  65. 7/8 checkout: add advice for ambiguous "checkout <branch>"Ævar Arnfjörð Bjarmason, Jun 2, 2018
  66. 8/8 checkout & worktree: introduce checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 2, 2018
  67. Eric SunshineJun 3, 2018
  68. 0/8 ambiguous checkout UI & checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 5, 2018
  69. 1/8 checkout tests: index should be clean after dwim checkoutÆvar Arnfjörð Bjarmason, Jun 5, 2018
  70. SZEDER GáborJun 5, 2018
  71. tests: make use of the test_must_be_empty functionÆvar Arnfjörð Bjarmason, Jul 27, 2018
  72. Junio C HamanoJul 27, 2018
  73. SZEDER GáborJul 31, 2018
  74. t6018-rev-list-glob: fix 'empty stdin' testSZEDER Gábor, Aug 22, 2018
  75. Eric SunshineAug 22, 2018
  76. SZEDER GáborAug 22, 2018
  77. Eric SunshineAug 22, 2018
  78. Junio C HamanoAug 22, 2018
  79. Junio C HamanoAug 22, 2018
  80. Jeff KingAug 22, 2018
  81. rev-list: make empty --stdin not an errorJeff King, Aug 22, 2018
  82. Junio C HamanoAug 22, 2018
  83. Jeff KingAug 22, 2018
  84. Junio C HamanoAug 22, 2018
  85. Jeff KingAug 22, 2018
  86. Junio C HamanoAug 22, 2018
  87. 3/8 checkout.c: introduce an *_INIT macroÆvar Arnfjörð Bjarmason, Jun 5, 2018
  88. 2/8 checkout.h: wrap the arguments to unique_tracking_name()Ævar Arnfjörð Bjarmason, Jun 5, 2018
  89. 4/8 checkout.c: change "unique" member to "num_matches"Ævar Arnfjörð Bjarmason, Jun 5, 2018
  90. 6/8 builtin/checkout.c: use "ret" variable for returnÆvar Arnfjörð Bjarmason, Jun 5, 2018
  91. 7/8 checkout: add advice for ambiguous "checkout <branch>"Ævar Arnfjörð Bjarmason, Jun 5, 2018
  92. 8/8 checkout & worktree: introduce checkout.defaultRemoteÆvar Arnfjörð Bjarmason, Jun 5, 2018
  93. 5/8 checkout: pass the "num_matches" up to callersÆvar Arnfjörð Bjarmason, Jun 5, 2018
  94. Eric SunshineMay 4, 2018
  95. Eric SunshineMay 4, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.