git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
@ 2018-05-02 10:54 Ævar Arnfjörð Bjarmason
  2018-05-02 15:21 ` Duy Nguyen
  0 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-02 10:54 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Thomas Gummerer,
	Nguyễn Thái Ngọc Duy,
	Ævar Arnfjörð Bjarmason

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


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-02 10:54 [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Ævar Arnfjörð Bjarmason
@ 2018-05-02 15:21 ` Duy Nguyen
  2018-05-02 18:00   ` Eric Sunshine
  0 siblings, 1 reply; 95+ messages in thread
From: Duy Nguyen @ 2018-05-02 15:21 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer

On Wed, May 2, 2018 at 12:54 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> 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.

Do we anticipate more dwimy customizations to justify a new dwim group
in config (i.e. dwim.remote instead of core.dwimRemote)?
-- 
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-02 15:21 ` Duy Nguyen
@ 2018-05-02 18:00   ` Eric Sunshine
  2018-05-02 18:09     ` Duy Nguyen
  2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
  0 siblings, 2 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-05-02 18:00 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Ævar Arnfjörð Bjarmason, Git Mailing List,
	Junio C Hamano, Jeff King, Thomas Gummerer

On Wed, May 2, 2018 at 11:21 AM, Duy Nguyen <pclouds@gmail.com> wrote:
> On Wed, May 2, 2018 at 12:54 PM, Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>> 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.
>
> Do we anticipate more dwimy customizations to justify a new dwim group
> in config (i.e. dwim.remote instead of core.dwimRemote)?

A few observations:

1. DWIM has broad meaning; while this certainly falls within DWIM,
it's also just a _default_, so should this instead be named
"defaultRemote"?

2. Building on #1: How well is the term "DWIM" understood by non-power
users? A term, such as "default" is more well known.

3. git-worktree learned --guess-remote and worktree.guessRemote in [1]
and [2], which, on the surface, sound confusingly similar to
"DWIMRemote". Even though I was well involved in the discussion and
repeatedly reviewed the patch series which introduced those, it still
took me an embarrassingly long time, and repeated re-reads of all
commit messages involved, to grasp (or re-grasp) how "guess remote"
and "DWIM remote" differ. If it was that difficult for me, as a person
involved in the patch series, to figure out "guess remote" vs. "DWIM
remote", then I worry that it may be too confusing in general. If the
new config option had been named "defaultRemote", I don't think I'd
have been confused at all.

It may sound as if I'm advocating the name "defaultRemote", but that
wasn't the intention, and I'm not wedded to it; that name simply fell
out naturally as I enumerated the above observations.

[1]: 71d6682d8c (worktree: add --guess-remote flag to add subcommand,
2017-11-29)
[2]: e92445a731 (add worktree.guessRemote config option, 2017-11-29)

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-02 18:00   ` Eric Sunshine
@ 2018-05-02 18:09     ` Duy Nguyen
  2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 95+ messages in thread
From: Duy Nguyen @ 2018-05-02 18:09 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Ævar Arnfjörð Bjarmason, Git Mailing List,
	Junio C Hamano, Jeff King, Thomas Gummerer

On Wed, May 2, 2018 at 8:00 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> 2. Building on #1: How well is the term "DWIM" understood by non-power
> users? A term, such as "default" is more well known.

I'm going off topic but I kinda dislike this term. First time I
encountered it in the code I didn't even know what it meant. Since it
has not been leaked to user documents (I only did a grep on
Documentation/) perhaps a better term should be used, preferably not
another acronym.
--
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-02 18:00   ` Eric Sunshine
  2018-05-02 18:09     ` Duy Nguyen
@ 2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
  2018-05-03 13:18       ` [PATCH v2] checkout & worktree: introduce checkout.implicitRemote Ævar Arnfjörð Bjarmason
  2018-05-04  7:14       ` [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Eric Sunshine
  1 sibling, 2 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-02 18:25 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Duy Nguyen, Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer


On Wed, May 02 2018, Eric Sunshine wrote:

> On Wed, May 2, 2018 at 11:21 AM, Duy Nguyen <pclouds@gmail.com> wrote:
>> On Wed, May 2, 2018 at 12:54 PM, Ævar Arnfjörð Bjarmason
>> <avarab@gmail.com> wrote:
>>> 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.
>>
>> Do we anticipate more dwimy customizations to justify a new dwim group
>> in config (i.e. dwim.remote instead of core.dwimRemote)?
>
> A few observations:
>
> 1. DWIM has broad meaning; while this certainly falls within DWIM,
> it's also just a _default_, so should this instead be named
> "defaultRemote"?
>
> 2. Building on #1: How well is the term "DWIM" understood by non-power
> users? A term, such as "default" is more well known.

I've got no love for the DWIM term. And I think I should change it in
v2, I just want some way to enable this functionality since this
behavior has annoyed me for a long time.

I wonder though if something like "core.defaultRemote" might not bring
up connotations about whether this would e.g. affect "push" in the minds
of users (not that my initial suggestion is better).

So maybe something like checkout.implicitRemote would be better? And we
could just break the rule that only git-checkout would use it, since
git-worktree could be said to be doing something checkout-like, or just
also add a worktree.implicitRemote.

> 3. git-worktree learned --guess-remote and worktree.guessRemote in [1]
> and [2], which, on the surface, sound confusingly similar to
> "DWIMRemote". Even though I was well involved in the discussion and
> repeatedly reviewed the patch series which introduced those, it still
> took me an embarrassingly long time, and repeated re-reads of all
> commit messages involved, to grasp (or re-grasp) how "guess remote"
> and "DWIM remote" differ. If it was that difficult for me, as a person
> involved in the patch series, to figure out "guess remote" vs. "DWIM
> remote", then I worry that it may be too confusing in general. If the
> new config option had been named "defaultRemote", I don't think I'd
> have been confused at all.

I hadn't looked at this at all before today when I wrote the patch, and
I've never used git-worktree (but maybe I should...), but my
understanding of this --[no-]guess-remote functionality is that it
effectively splits up the functionality that the "git checkout" does,
and we'll unconditionally check out the branch, but the option controls
whether or not we'd set up the equivalent of remote tracking for
git-worktree.

But maybe I've completely misunderstood it.

> It may sound as if I'm advocating the name "defaultRemote", but that
> wasn't the intention, and I'm not wedded to it; that name simply fell
> out naturally as I enumerated the above observations.
>
> [1]: 71d6682d8c (worktree: add --guess-remote flag to add subcommand,
> 2017-11-29)
> [2]: e92445a731 (add worktree.guessRemote config option, 2017-11-29)

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
@ 2018-05-03 13:18       ` Ævar Arnfjörð Bjarmason
  2018-05-03 15:14         ` Duy Nguyen
  2018-05-04  9:58         ` Eric Sunshine
  2018-05-04  7:14       ` [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Eric Sunshine
  1 sibling, 2 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-03 13:18 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Thomas Gummerer,
	Nguyễn Thái Ngọc Duy, Eric Sunshine,
	Johannes Schindelin, Ævar Arnfjörð Bjarmason

Introduce a checkout.implicitRemote setting which can be used to
designate a remote to prefer (via checkout.implicitRemote=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 checkout.implicitRemote 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 splitting this into checkout.implicitRemote and
worktree.implicitRemote, 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.implicitRemoteForCheckout?).

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>
---

In v2 I've changed the config name to checkout.implicitRemote as
suggested above, there's no worktree.implicitRemote, instead the
config just explains that it's used for checkout-like things (and may
be expanded in the future).

A branch-diff (new command from JS) follows:
    
    1:  18f13ecd89 ! 1:  905a17f35f checkout & worktree: introduce a core.DWIMRemote setting
        @@ -1,11 +1,11 @@
         Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         
        -    checkout & worktree: introduce a core.DWIMRemote setting
        +    checkout & worktree: introduce checkout.implicitRemote
         
        -    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.
        +    Introduce a checkout.implicitRemote setting which can be used to
        +    designate a remote to prefer (via checkout.implicitRemote=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
        @@ -41,16 +41,15 @@
         
                 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".
        +    The new checkout.implicitRemote 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.
        +    I considered splitting this into checkout.implicitRemote and
        +    worktree.implicitRemote, 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.implicitRemoteForCheckout?).
         
             See also 70c9ac2f19 ("DWIM "git checkout frotz" to "git checkout -b
             frotz origin/frotz"", 2009-10-18) which introduced this DWIM feature
        @@ -63,27 +62,29 @@
         --- a/Documentation/config.txt
         +++ b/Documentation/config.txt
         @@
        - This setting defaults to "refs/notes/commits", and it can be overridden by
        - the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
        + 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
        + 	working repository in gitweb (see linkgit:git-instaweb[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
        ++checkout.implicitRemote::
        ++	When you run 'git checkout <something>' and only have one
        ++	remote, it may implicitly fall back on checking out and
        ++	tracking e.g. 'origin/<something>'. This stops working as soon
        ++	as you have more than one remote with a '<something>'
        ++	reference. 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
        ++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.
        ++and by linkgit:git-worktree[1] when 'git worktree add' when referring
        ++to a remote branch.  This setting might be used for other
        ++checkout-like 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.
        + clean.requireForce::
        + 	A boolean to make git-clean do nothing unless given -f,
        + 	-i or -n.   Defaults to true.
         
         diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
         --- a/Documentation/git-checkout.txt
        @@ -92,10 +93,11 @@
          $ git checkout -b <branch> --track <remote>/<branch>
          ------------
          +
        -+If the branch exists in multiple remotes the `core.DWIMRemote`
        ++If the branch exists in multiple remotes the `checkout.implicitRemote`
         +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].
        ++e.g. `checkout.implicitRemote=origin` to always checkout remote
        ++branches from there. See also `checkout.implicitRemote` 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
        @@ -108,10 +110,10 @@
          $ 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].
        ++It's also possible to use the `checkout.implicitRemote` setting to
        ++designate a special remote this rule should be applied to, even if the
        ++branch isn't unique across all remotes. See `checkout.implicitRemote`
        ++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,
        @@ -125,11 +127,14 @@
          	 *       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
        +-	 *       fork local <something> from that remote-tracking branch.
         +	 *       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.
        ++	 *       remote named in checkout.implicitRemote), 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
         --- a/checkout.c
        @@ -145,8 +150,8 @@
          	char *dst_ref;
          	struct object_id *dst_oid;
          	int unique;
        -+	const char *dwim_remote;
        -+	char *dwim_dst_ref;
        ++	const char *implicit_remote;
        ++	char *implicit_dst_ref;
          };
          
          static int check_tracking_name(struct remote *remote, void *cb_data)
        @@ -154,8 +159,8 @@
          		free(query.dst);
          		return 0;
          	}
        -+	if (cb->dwim_remote && !strcmp(remote->name, cb->dwim_remote))
        -+		cb->dwim_dst_ref = xstrdup(query.dst);
        ++	if (cb->implicit_remote && !strcmp(remote->name, cb->implicit_remote))
        ++		cb->implicit_dst_ref = xstrdup(query.dst);
          	if (cb->dst_ref) {
          		free(query.dst);
          		cb->unique = 0;
        @@ -164,23 +169,23 @@
          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;
        ++	const char *implicit_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;
        ++	if (!git_config_get_string_const("checkout.implicitremote", &implicit_remote))
        ++		cb_data.implicit_remote = implicit_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);
        ++	free((char *)implicit_remote);
         +	if (cb_data.unique) {
        -+		free(cb_data.dwim_dst_ref);
        ++		free(cb_data.implicit_dst_ref);
          		return cb_data.dst_ref;
         +	}
          	free(cb_data.dst_ref);
        -+	if (cb_data.dwim_dst_ref)
        -+		return cb_data.dwim_dst_ref;
        ++	if (cb_data.implicit_dst_ref)
        ++		return cb_data.implicit_dst_ref;
          	return NULL;
          }
         
        @@ -191,11 +196,11 @@
          	test_branch master
          '
          
        -+test_expect_success 'checkout of branch from multiple remotes succeeds with core.DWIMRemote #1' '
        ++test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.implicitRemote #1' '
         +	git checkout -B master &&
         +	test_might_fail git branch -D foo &&
         +
        -+	git -c core.DWIMRemote=repo_a checkout foo &&
        ++	git -c checkout.implicitRemote=repo_a checkout foo &&
         +	test_branch foo &&
         +	test_cmp_rev remotes/repo_a/foo HEAD &&
         +	test_branch_upstream foo repo_a foo
        @@ -212,7 +217,7 @@
          	)
          '
          
        -+test_expect_success '"add" <path> <branch> dwims with core.DWIMRemote' '
        ++test_expect_success '"add" <path> <branch> dwims with checkout.implicitRemote' '
         +	test_when_finished rm -rf repo_upstream repo_dwim foo &&
         +	setup_remote_repo repo_upstream repo_dwim &&
         +	git init repo_dwim &&
        @@ -221,7 +226,7 @@
         +		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
        ++		git -c checkout.implicitRemote=repo_upstream worktree add ../foo foo
         +	) &&
         +	(
         +		cd foo &&

 Documentation/config.txt       | 17 +++++++++++++++++
 Documentation/git-checkout.txt |  6 ++++++
 Documentation/git-worktree.txt |  5 +++++
 builtin/checkout.c             |  6 ++++--
 checkout.c                     | 17 +++++++++++++++--
 t/t2024-checkout-dwim.sh       | 10 ++++++++++
 t/t2025-worktree-add.sh        | 18 ++++++++++++++++++
 7 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2659153cb3..83460bd443 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1084,6 +1084,23 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.implicitRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. 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 by linkgit:git-worktree[1] when 'git worktree add' when referring
+to a remote branch.  This setting might be used for other
+checkout-like commands or functionality in the future when
+appropriate.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..753aa4001f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,12 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes the `checkout.implicitRemote`
+variable can be used to pick the remote you really mean. Set it to
+e.g. `checkout.implicitRemote=origin` to always checkout remote
+branches from there. See also `checkout.implicitRemote` 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..35798a1132 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 `checkout.implicitRemote` setting to
+designate a special remote this rule should be applied to, even if the
+branch isn't unique across all remotes. See `checkout.implicitRemote`
+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..495a5d7955 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -903,8 +903,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.implicitRemote), 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..2c898cfe19 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 *implicit_remote;
+	char *implicit_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->implicit_remote && !strcmp(remote->name, cb->implicit_remote))
+		cb->implicit_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 *implicit_remote = NULL;
+	struct tracking_name_data cb_data = { NULL, NULL, NULL, 1, NULL, NULL };
+	if (!git_config_get_string_const("checkout.implicitremote", &implicit_remote))
+		cb_data.implicit_remote = implicit_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 *)implicit_remote);
+	if (cb_data.unique) {
+		free(cb_data.implicit_dst_ref);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.implicit_dst_ref)
+		return cb_data.implicit_dst_ref;
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..da6bd74bbc 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 checkout.implicitRemote #1' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.implicitRemote=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..6eb994ac12 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 checkout.implicitRemote' '
+	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 checkout.implicitRemote=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


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-03 13:18       ` [PATCH v2] checkout & worktree: introduce checkout.implicitRemote Ævar Arnfjörð Bjarmason
@ 2018-05-03 15:14         ` Duy Nguyen
  2018-05-04  7:54           ` Ævar Arnfjörð Bjarmason
  2018-05-04  9:58         ` Eric Sunshine
  1 sibling, 1 reply; 95+ messages in thread
From: Duy Nguyen @ 2018-05-03 15:14 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer,
	Eric Sunshine, Johannes Schindelin

On Thu, May 3, 2018 at 3:18 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> Introduce a checkout.implicitRemote setting which can be used to
> designate a remote to prefer (via checkout.implicitRemote=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 checkout.implicitRemote 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 splitting this into checkout.implicitRemote and
> worktree.implicitRemote, 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.implicitRemoteForCheckout?).

I wonder if it's difficult to add a dwim hook that allows us to
replace the entire dwim logic with a hook? Doing this "preferring
origin" in a shell script should be easy and it lets us play more with
tweaking dwim logic. (Yeah sorry I'm getting off topic again)
-- 
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
  2018-05-03 13:18       ` [PATCH v2] checkout & worktree: introduce checkout.implicitRemote Ævar Arnfjörð Bjarmason
@ 2018-05-04  7:14       ` Eric Sunshine
  2018-05-04  7:23         ` Eric Sunshine
  1 sibling, 1 reply; 95+ messages in thread
From: Eric Sunshine @ 2018-05-04  7:14 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Duy Nguyen, Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer

On Wed, May 2, 2018 at 2:25 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> On Wed, May 02 2018, Eric Sunshine wrote:
>> A few observations:
>>
>> 1. DWIM has broad meaning; while this certainly falls within DWIM,
>> it's also just a _default_, so should this instead be named
>> "defaultRemote"?
>>
>> 2. Building on #1: How well is the term "DWIM" understood by non-power
>> users? A term, such as "default" is more well known.
>
> I've got no love for the DWIM term. And I think I should change it in
> v2, I just want some way to enable this functionality since this
> behavior has annoyed me for a long time.
>
> I wonder though if something like "core.defaultRemote" might not bring
> up connotations about whether this would e.g. affect "push" in the minds
> of users (not that my initial suggestion is better).

A reasonable concern.

> So maybe something like checkout.implicitRemote would be better? And we
> could just break the rule that only git-checkout would use it, since
> git-worktree could be said to be doing something checkout-like, or just
> also add a worktree.implicitRemote.

Considering that git-worktree runs the post-checkout hook, it seems
pretty safe to say that it does something checkout-like.

Personally, I find "defaultRemote" easier to understand than
"implicitRemote", but I suppose I can see your reasoning for choosing
"implicit". Whereas "default" is something to "fall back upon" when
something is missing, "implicit" suggests what to choose when a
something has not been specified explicitly.

>> 3. git-worktree learned --guess-remote and worktree.guessRemote in [1]
>> and [2], which, on the surface, sound confusingly similar to
>> "DWIMRemote". Even though I was well involved in the discussion and
>> repeatedly reviewed the patch series which introduced those, it still
>> took me an embarrassingly long time, and repeated re-reads of all
>> commit messages involved, to grasp (or re-grasp) how "guess remote"
>> and "DWIM remote" differ. If it was that difficult for me, as a person
>> involved in the patch series, to figure out "guess remote" vs. "DWIM
>> remote", then I worry that it may be too confusing in general. If the
>> new config option had been named "defaultRemote", I don't think I'd
>> have been confused at all.
>
> I hadn't looked at this at all before today when I wrote the patch, and
> I've never used git-worktree (but maybe I should...), but my
> understanding of this --[no-]guess-remote functionality is that it
> effectively splits up the functionality that the "git checkout" does,
> and we'll unconditionally check out the branch, but the option controls
> whether or not we'd set up the equivalent of remote tracking for
> git-worktree.
>
> But maybe I've completely misunderstood it.

Yes, you misunderstood it.

The setting up of a remote-tracking branch is DWIM'd as of 4e85333197
("worktree: make add <path> <branch> dwim", 2017-11-26); it doesn't
require an explicit option to enable it (though tracking can be
disabled via --no-track). The "guess-remote" feature does something
entirely different; it was added to avoid backward compatibility
problems.

In long-form:

    git worktree add <path> <branch>

adds a new worktree at <path> and checks out <branch>. As originally
implemented, shortened:

    git worktree add <path>

does one type of DWIM, as a convenience, and pretends that the user
actually typed:

    branch=$(basename <path>)
    git branch $branch HEAD
    git workree add <path> $branch

which creates a new branch and then checks it out in the new worktree
as usual. The "guess remote" feature which Thomas added augments that
by adding a DWIM which checks if $(basename <path>) names a
remote-tracking branch, in which case, it becomes shorthand for
(something like):

    branch=$(basename <path>)
    if remote-tracking branch named $branch exists:
        git branch --track $branch $guessedRemote/$branch
    else:
        git branch $branch HEAD
    fi
    git worktree add <path> $branch

In retrospect, this DWIM-checking for a like-named remote-tracking
branch should have been implemented from the start in git-worktree
since it mirrors how "git checkout <branch>" will DWIM remote-tracking
<remote>/<branch>. However, such DWIM'ing was overlooked and
git-worktree existed long enough without it that, due to backward
compatibility concerns, the new DWIM'ing got hidden behind a switch,
hence --guess-remote ("worktree.guessRemote") to enable it.

Unrelated: Thomas added another DWIM, which we just finalized a few
days ago, which extends the shorthand "git worktree add <path>" to
first check if a local branch named $(basename <path>) already exists
and merely check that out into the new worktree rather than trying to
create a new branch of that name (which is another obvious DWIM missed
during initial implementation). In other words:

    branch=$(basename <path>)
    if local branch named $branch does _not_ exist:
        if remote-tracking branch named $branch exists:
            git branch --track $branch $guessedRemote/$branch
        else:
            git branch $branch HEAD
        fi
    fi
    git worktree add <path> $branch

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] checkout & worktree: introduce a core.DWIMRemote setting
  2018-05-04  7:14       ` [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Eric Sunshine
@ 2018-05-04  7:23         ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-05-04  7:23 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Duy Nguyen, Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer

On Fri, May 4, 2018 at 3:14 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> The setting up of a remote-tracking branch is DWIM'd as of 4e85333197
> ("worktree: make add <path> <branch> dwim", 2017-11-26); it doesn't
> require an explicit option to enable it (though tracking can be
> disabled via --no-track). The "guess-remote" feature does something
> entirely different; it was added to avoid backward compatibility
> problems.
>
> In long-form:
>
>     git worktree add <path> <branch>
>
> adds a new worktree at <path> and checks out <branch>. As originally
> implemented, shortened:
>
>     git worktree add <path>
>
> does one type of DWIM, as a convenience, and pretends that the user
> actually typed:
>
>     branch=$(basename <path>)
>     git branch $branch HEAD
>     git workree add <path> $branch

This explanation isn't wrong, but it conflates two separate features,
thus is confusing. The $(basename <branch>) DWIM'ing from the short
form of the command, "git worktree add <path>", doesn't actually have
anything to do with the DWIM'ing added by 4e85333197 ("worktree: make
add <path> <branch> dwim", 2017-11-26), which merely tries to DWIM a
remote-tracking branch of similar name to $branch (whether $branch
came from long or short form of the command), so I confused the issue
unnecessarily by talking about the short form.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-03 15:14         ` Duy Nguyen
@ 2018-05-04  7:54           ` Ævar Arnfjörð Bjarmason
  2018-05-04 14:58             ` Duy Nguyen
  0 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-04  7:54 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer,
	Eric Sunshine, Johannes Schindelin


On Thu, May 03 2018, Duy Nguyen wrote:

> On Thu, May 3, 2018 at 3:18 PM, Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>> Introduce a checkout.implicitRemote setting which can be used to
>> designate a remote to prefer (via checkout.implicitRemote=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 checkout.implicitRemote 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 splitting this into checkout.implicitRemote and
>> worktree.implicitRemote, 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.implicitRemoteForCheckout?).
>
> I wonder if it's difficult to add a dwim hook that allows us to
> replace the entire dwim logic with a hook? Doing this "preferring
> origin" in a shell script should be easy and it lets us play more with
> tweaking dwim logic. (Yeah sorry I'm getting off topic again)

Realistically the way we do hooks now would make the UI of this suck,
i.e. you couldn't configure it globally or system-wide easily. Something
like what I noted in
https://public-inbox.org/git/871sf3el01.fsf@evledraar.gmail.com/ would
make it suck less, but that's a much bigger task.

I think for now this is a common enough case users run into that it
makes sense to add it to the code, and if we ever do have more
extensible hook features in the future we can just go and replace
various options like these to use them.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-03 13:18       ` [PATCH v2] checkout & worktree: introduce checkout.implicitRemote Ævar Arnfjörð Bjarmason
  2018-05-03 15:14         ` Duy Nguyen
@ 2018-05-04  9:58         ` Eric Sunshine
  2018-05-24 19:47           ` [PATCH v3] " Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 95+ messages in thread
From: Eric Sunshine @ 2018-05-04  9:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Thomas Gummerer,
	Nguyễn Thái Ngọc Duy, Johannes Schindelin

On Thu, May 3, 2018 at 9:18 AM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> Introduce a checkout.implicitRemote setting which can be used to
> designate a remote to prefer (via checkout.implicitRemote=origin) when
> running e.g. "git checkout master" to mean origin/master, even though
> there's other remotes that have the "master" branch.
> [...]
> The new checkout.implicitRemote 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".
> [...]
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> @@ -1084,6 +1084,23 @@ browser.<tool>.path::
> +checkout.implicitRemote::
> +       When you run 'git checkout <something>' and only have one
> +       remote, it may implicitly fall back on checking out and
> +       tracking e.g. 'origin/<something>'. This stops working as soon
> +       as you have more than one remote with a '<something>'
> +       reference. This setting allows for setting the name of a
> +       special remote that should always win when it comes to

"special" is overly broad. "preferred" may better convey the intended
meaning. Simply dropping "special" also works.

Subjective; not worth a re-roll.

> +       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 by linkgit:git-worktree[1] when 'git worktree add' when referring

"when ... when"?

> +to a remote branch.  This setting might be used for other
> +checkout-like commands or functionality in the future when
> +appropriate.

Not sure the final sentence adds value as user-facing documentation
(versus the commit message in which it may).

> diff --git 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 `checkout.implicitRemote` setting to
> +designate a special remote this rule should be applied to, even if the

Again, you could drop "special".

> +branch isn't unique across all remotes. See `checkout.implicitRemote`
> +in linkgit:git-config[1].

I have a hard time digesting this paragraph. Perhaps it wants to say:

    Option `checkout.implicitRemote` can be configured to designate a
    <remote> to use when <branch> isn't unique across all remotes.
    See ...

> diff --git 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 checkout.implicitRemote' '
> +       test_when_finished rm -rf repo_upstream repo_dwim foo &&
> +       setup_remote_repo repo_upstream repo_dwim &&
> +       git init repo_dwim &&

Maybe replace "dwim" here and in the test title with something else
since checkout.implicitRemote is no longer about DWIM'ing?

> +       (
> +               cd repo_dwim &&
> +               git remote add repo_upstream2 ../repo_upstream &&
> +               git fetch repo_upstream2 &&
> +               test_must_fail git worktree add ../foo foo &&
> +               git -c checkout.implicitRemote=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
> +       )
> +'

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-04  7:54           ` Ævar Arnfjörð Bjarmason
@ 2018-05-04 14:58             ` Duy Nguyen
  2018-05-04 18:02               ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 95+ messages in thread
From: Duy Nguyen @ 2018-05-04 14:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer,
	Eric Sunshine, Johannes Schindelin

On Fri, May 4, 2018 at 9:54 AM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> Realistically the way we do hooks now would make the UI of this suck,
> i.e. you couldn't configure it globally or system-wide easily. Something
> like what I noted in
> https://public-inbox.org/git/871sf3el01.fsf@evledraar.gmail.com/ would
> make it suck less, but that's a much bigger task.

I thought you would bring this up :) I've given some more thoughts on
this topic and am willing to implement something like below, in a week
or two. Would that help change your mind?

I proposed hooks.* config space in that same thread. Here is the
extension to make it cover both of your points.

hooks.* can have multiple values. So you can specify
hooks.post-checkout multiple times and all those scripts will run (in
the same order you specified). Since we already have a search path for
config (/etc/gitconfig -> $HOME/.gitconfig -> $REPO/config) this helps
hooks management as well. Execution order is still the same: if you
specify hooks.post-checkout in both /etc/gitconfig and .git/config,
then the one in /etc/gitconfig is executed first, the one in .git
second.

And here's something extra to make it possible to override the search
order: if you specify hooks.post-checkout = reset (reset is a random
keyword) then _all_ post-checkout hooks before this point are ignored.
So you can put this in .git/config

[hooks]
    post-checkout = reset
    post-checkout = ~/some-hook

and can be sure that post-checkout specified in $HOME and /etc will
not affect you, only ~/some-hook will run. If you drop the second line
then you have no post-checkout hooks. This is a workaround for a
bigger problem (not being able to unset a config entry) but I think
it's sufficient for this use case.
-- 
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v2] checkout & worktree: introduce checkout.implicitRemote
  2018-05-04 14:58             ` Duy Nguyen
@ 2018-05-04 18:02               ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-04 18:02 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Jeff King, Thomas Gummerer,
	Eric Sunshine, Johannes Schindelin


On Fri, May 04 2018, Duy Nguyen wrote:

> On Fri, May 4, 2018 at 9:54 AM, Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>> Realistically the way we do hooks now would make the UI of this suck,
>> i.e. you couldn't configure it globally or system-wide easily. Something
>> like what I noted in
>> https://public-inbox.org/git/871sf3el01.fsf@evledraar.gmail.com/ would
>> make it suck less, but that's a much bigger task.
>
> I thought you would bring this up :) I've given some more thoughts on
> this topic and am willing to implement something like below, in a week
> or two. Would that help change your mind?
>
> I proposed hooks.* config space in that same thread. Here is the
> extension to make it cover both of your points.
>
> hooks.* can have multiple values. So you can specify
> hooks.post-checkout multiple times and all those scripts will run (in
> the same order you specified). Since we already have a search path for
> config (/etc/gitconfig -> $HOME/.gitconfig -> $REPO/config) this helps
> hooks management as well. Execution order is still the same: if you
> specify hooks.post-checkout in both /etc/gitconfig and .git/config,
> then the one in /etc/gitconfig is executed first, the one in .git
> second.
>
> And here's something extra to make it possible to override the search
> order: if you specify hooks.post-checkout = reset (reset is a random
> keyword) then _all_ post-checkout hooks before this point are ignored.
> So you can put this in .git/config
>
> [hooks]
>     post-checkout = reset
>     post-checkout = ~/some-hook
>
> and can be sure that post-checkout specified in $HOME and /etc will
> not affect you, only ~/some-hook will run. If you drop the second line
> then you have no post-checkout hooks. This is a workaround for a
> bigger problem (not being able to unset a config entry) but I think
> it's sufficient for this use case.

A few things:

 1) I don't see a reason to hold back this feature in particular waiting
    for some way to do it via config / hooks. If we grow some compatible
    way to do it via hooks in the future, great, then we can just make
    this (and numerous other config values) historical aliases for that
    facility.

 2) Let's not have some per-config type way to reset earlier config. I
    suggested a way to do it generally in
    https://public-inbox.org/git/874lkq11ug.fsf@evledraar.gmail.com/ I'm
    not saying we should go for that suggestion in particular, but that
    we should have something general.

 3) Doing #2 will take a lot longer to implement than what you're
    suggesting.

 4) I think such a facility should also be able to replace something
    like https://docs.gitlab.com/ee/administration/custom_hooks.html
    which requires not just supporting hooks from the config, but
    executing some hooks on the FS in glob() order.

 5) What you're describing above is just 1/2 of what we need to have a
    viable way to replace something like this checkout.implicitRemote
    with a hook while providing the same functionality to the end
    user.

    If something ships as a config value like checkout.implicitRemote
    users can just turn it on in their ~/.gitconfig without any further
    config, or you can tell users in docs via one-liner to enable it.

    We also need the other half which some method of shipping "standard"
    hooks with git, i.e. with your proposal something like (along with
    the general config reset):

        [config]
            reject = hooks.post-checkout
        [hooks]
            # Reads config from hooks.post-checkout-implicit-remote.*
            # (e.g. hooks.post-checkout-implicit-remote.remote = origin)
            post-checkout = git-hooks://post-checkout-implicit-remote-config

   Only then will this be as easy to enable as `git config --global..`
   (although you'll need two invocations of that, which is fine...)

 6) The feature being discussed here would not be a post-checkout hook,
    but would need to be fairly integrated with the internals of
    checkout, see `dwim_ok` and the big comment in
    parse_branchname_arg().

    I.e. the thing that makes this work is the Nth step in some fairly
    intricate fallback logic. It's not clear to me in the general case
    how we'd turn this into a hook without having N number of
    checkout-what-step-N-hooks, or requiring every checkout-what hook to
    implement a huge part of what checkout.c is doing now just to tweak
    some tiny aspect of it, such as this tweak of
    unique_tracking_name().

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-04  9:58         ` Eric Sunshine
@ 2018-05-24 19:47           ` Ævar Arnfjörð Bjarmason
  2018-05-25  8:12             ` Junio C Hamano
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
  0 siblings, 2 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-24 19:47 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Introduce a checkout.implicitRemote setting which can be used to
designate a remote to prefer (via checkout.implicitRemote=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 checkout.implicitRemote 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 splitting this into checkout.implicitRemote and
worktree.implicitRemote, 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.implicitRemoteForCheckout?).

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>
---

Took me a while to get around to this, but this v3 addresses feedback
by Eric about the docs. Thanks! interdiff:
    
    1: 905a17f35f ! 1: b23d1b71e9 checkout & worktree: introduce checkout.implicitRemote
        @@ -71,16 +71,15 @@
         +	tracking e.g. 'origin/<something>'. This stops working as soon
         +	as you have more than one remote with a '<something>'
         +	reference. This setting allows for setting the name of a
        -+	special remote that should always win when it comes to
        ++	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' when referring
        -+to a remote branch.  This setting might be used for other
        -+checkout-like commands or functionality in the future when
        -+appropriate.
        ++and by linkgit:git-worktree[1] when 'git worktree add' refers to a
        ++remote branch. This setting might be used for other checkout-like
        ++commands or functionality in the future.
         +
          clean.requireForce::
          	A boolean to make git-clean do nothing unless given -f,
        @@ -110,14 +109,14 @@
          $ git worktree add --track -b <branch> <path> <remote>/<branch>
          ------------
          +
        -+It's also possible to use the `checkout.implicitRemote` setting to
        -+designate a special remote this rule should be applied to, even if the
        -+branch isn't unique across all remotes. See `checkout.implicitRemote`
        -+in linkgit:git-config[1].
        ++The `checkout.implicitRemote` setting can be used to to designate a
        ++preferred `<remote>` this rule should be applied to, even if the
        ++`<branch>` isn't unique across all remotes. See
        ++`checkout.implicitRemote` 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.
        + then, as a convenience, the new worktree is associated with a branch
        + (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
         
         diff --git a/builtin/checkout.c b/builtin/checkout.c
         --- a/builtin/checkout.c
        @@ -235,6 +234,6 @@
         +	)
         +'
         +
        - post_checkout_hook () {
        - 	gitdir=${1:-.git}
        - 	test_when_finished "rm -f $gitdir/hooks/post-checkout" &&
        + test_expect_success 'git worktree add does not match remote' '
        + 	test_when_finished rm -rf repo_a repo_b foo &&
        + 	setup_remote_repo repo_a repo_b &&

 Documentation/config.txt       | 16 ++++++++++++++++
 Documentation/git-checkout.txt |  6 ++++++
 Documentation/git-worktree.txt |  5 +++++
 builtin/checkout.c             |  6 ++++--
 checkout.c                     | 17 +++++++++++++++--
 t/t2024-checkout-dwim.sh       | 10 ++++++++++
 t/t2025-worktree-add.sh        | 18 ++++++++++++++++++
 7 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 84e2891aed..7f095a260a 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1098,6 +1098,22 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.implicitRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. This setting allows for setting the name of a
+	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' refers to a
+remote branch. This setting might be used for other checkout-like
+commands or functionality in the future.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..753aa4001f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,12 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes the `checkout.implicitRemote`
+variable can be used to pick the remote you really mean. Set it to
+e.g. `checkout.implicitRemote=origin` to always checkout remote
+branches from there. See also `checkout.implicitRemote` 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 afc6576a14..6febb21dca 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>
 ------------
 +
+The `checkout.implicitRemote` setting can be used to to designate a
+preferred `<remote>` this rule should be applied to, even if the
+`<branch>` isn't unique across all remotes. See
+`checkout.implicitRemote` in linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch
 (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2b3b768eff..88e650f0f8 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -909,8 +909,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.implicitRemote), 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..2c898cfe19 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 *implicit_remote;
+	char *implicit_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->implicit_remote && !strcmp(remote->name, cb->implicit_remote))
+		cb->implicit_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 *implicit_remote = NULL;
+	struct tracking_name_data cb_data = { NULL, NULL, NULL, 1, NULL, NULL };
+	if (!git_config_get_string_const("checkout.implicitremote", &implicit_remote))
+		cb_data.implicit_remote = implicit_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 *)implicit_remote);
+	if (cb_data.unique) {
+		free(cb_data.implicit_dst_ref);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.implicit_dst_ref)
+		return cb_data.implicit_dst_ref;
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..da6bd74bbc 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 checkout.implicitRemote #1' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.implicitRemote=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 2240498924..271a6413f0 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -402,6 +402,24 @@ test_expect_success '"add" <path> <branch> dwims' '
 	)
 '
 
+test_expect_success '"add" <path> <branch> dwims with checkout.implicitRemote' '
+	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 checkout.implicitRemote=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
+	)
+'
+
 test_expect_success 'git worktree add does not match remote' '
 	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-24 19:47           ` [PATCH v3] " Ævar Arnfjörð Bjarmason
@ 2018-05-25  8:12             ` Junio C Hamano
  2018-05-25 14:42               ` Duy Nguyen
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-05-25  8:12 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> +checkout.implicitRemote::
> +	When you run 'git checkout <something>' and only have one
> +	remote, it may implicitly fall back on checking out and
> +	tracking e.g. 'origin/<something>'.

Yup, that is quite implicit.  It works without configuring anything,
only as long as you have a single remote.  That is quite implicit.


> This stops working as soon
> +	as you have more than one remote with a '<something>'
> +	reference. This setting allows for setting the name of a
> +	preferred remote that should always win when it comes to
> +	disambiguation. The typical use-case is to set this to
> +	`origin`.

So this feature and configuration feels more like an explicit one,
to be used to affect how Git works when the implicit one does not
work well.  I would have called it checkout.defaultRemote, as it
would be a nonsense name to call it checkout.explicitRemote ;-).

> +Currently this is used by linkgit:git-checkout[1] when 'git checkout
> +<something>' will checkout the '<something>' branch on another remote,
> +and by linkgit:git-worktree[1] when 'git worktree add' refers to a
> +remote branch. This setting might be used for other checkout-like
> +commands or functionality in the future.

Hmph, that is an interesting direction.  You foresee that you'll
have a single repository with multiple remotes to grab and share
objects from different people working on the same project, and use
multiple worktrees to work on different branches, yet you are happy
to declare that each worktree is to work with one particular remote?

We'd need a per-worktree config file to make it work, I guess, or
a three-level checkout.$worktree_id.defaultRemote configuration
variable, perhaps?

In any case I can see how this will help those with multiple remotes
(including me ;-).  Thanks for moving this topic forward.


^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-25  8:12             ` Junio C Hamano
@ 2018-05-25 14:42               ` Duy Nguyen
  2018-05-25 18:38                 ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 95+ messages in thread
From: Duy Nguyen @ 2018-05-25 14:42 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Ævar Arnfjörð Bjarmason, Git Mailing List,
	Jeff King, Johannes Schindelin, Thomas Gummerer, Eric Sunshine

On Fri, May 25, 2018 at 10:12 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> +Currently this is used by linkgit:git-checkout[1] when 'git checkout
>> +<something>' will checkout the '<something>' branch on another remote,
>> +and by linkgit:git-worktree[1] when 'git worktree add' refers to a
>> +remote branch. This setting might be used for other checkout-like
>> +commands or functionality in the future.
>
> Hmph, that is an interesting direction.  You foresee that you'll
> have a single repository with multiple remotes to grab and share
> objects from different people working on the same project, and use
> multiple worktrees to work on different branches, yet you are happy
> to declare that each worktree is to work with one particular remote?
>
> We'd need a per-worktree config file to make it work, I guess, or
> a three-level checkout.$worktree_id.defaultRemote configuration
> variable, perhaps?

I still plan to revisit per-worktree config support [1] at some point
and finish it off. Or is it decided that we don't need a generic
mechanism and will add a new level like this for each config var that
is per-worktree? If defaultRemote sets a precedence, then it'll be the
way to go from now on or we'll have another mess when some config does
"foo.$worktree.bar" while others use per-worktree config files.

[1] https://public-inbox.org/git/20170110112524.12870-1-pclouds@gmail.com/
-- 
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-25 14:42               ` Duy Nguyen
@ 2018-05-25 18:38                 ` Ævar Arnfjörð Bjarmason
  2018-05-26 12:49                   ` Duy Nguyen
  0 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-25 18:38 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Junio C Hamano, Git Mailing List, Jeff King, Johannes Schindelin,
	Thomas Gummerer, Eric Sunshine


On Fri, May 25 2018, Duy Nguyen wrote:

> On Fri, May 25, 2018 at 10:12 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>> +Currently this is used by linkgit:git-checkout[1] when 'git checkout
>>> +<something>' will checkout the '<something>' branch on another remote,
>>> +and by linkgit:git-worktree[1] when 'git worktree add' refers to a
>>> +remote branch. This setting might be used for other checkout-like
>>> +commands or functionality in the future.
>>
>> Hmph, that is an interesting direction.  You foresee that you'll
>> have a single repository with multiple remotes to grab and share
>> objects from different people working on the same project, and use
>> multiple worktrees to work on different branches, yet you are happy
>> to declare that each worktree is to work with one particular remote?
>>
>> We'd need a per-worktree config file to make it work, I guess, or
>> a three-level checkout.$worktree_id.defaultRemote configuration
>> variable, perhaps?
>
> I still plan to revisit per-worktree config support [1] at some point
> and finish it off. Or is it decided that we don't need a generic
> mechanism and will add a new level like this for each config var that
> is per-worktree? If defaultRemote sets a precedence, then it'll be the
> way to go from now on or we'll have another mess when some config does
> "foo.$worktree.bar" while others use per-worktree config files.

What do you think about including worktree in this at this time? I'm not
very familiar with it and just included it because I worked my way
backwards from the DWIM function, but I could exclude it if you think
it's too much trouble, i.e. if you'd like to hold out for some facility
like the per-worktree config Junio mentioned.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-25 18:38                 ` Ævar Arnfjörð Bjarmason
@ 2018-05-26 12:49                   ` Duy Nguyen
  0 siblings, 0 replies; 95+ messages in thread
From: Duy Nguyen @ 2018-05-26 12:49 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Junio C Hamano, Git Mailing List, Jeff King, Johannes Schindelin,
	Thomas Gummerer, Eric Sunshine

On Fri, May 25, 2018 at 8:38 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
> On Fri, May 25 2018, Duy Nguyen wrote:
>
>> On Fri, May 25, 2018 at 10:12 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>>> +Currently this is used by linkgit:git-checkout[1] when 'git checkout
>>>> +<something>' will checkout the '<something>' branch on another remote,
>>>> +and by linkgit:git-worktree[1] when 'git worktree add' refers to a
>>>> +remote branch. This setting might be used for other checkout-like
>>>> +commands or functionality in the future.
>>>
>>> Hmph, that is an interesting direction.  You foresee that you'll
>>> have a single repository with multiple remotes to grab and share
>>> objects from different people working on the same project, and use
>>> multiple worktrees to work on different branches, yet you are happy
>>> to declare that each worktree is to work with one particular remote?
>>>
>>> We'd need a per-worktree config file to make it work, I guess, or
>>> a three-level checkout.$worktree_id.defaultRemote configuration
>>> variable, perhaps?
>>
>> I still plan to revisit per-worktree config support [1] at some point
>> and finish it off. Or is it decided that we don't need a generic
>> mechanism and will add a new level like this for each config var that
>> is per-worktree? If defaultRemote sets a precedence, then it'll be the
>> way to go from now on or we'll have another mess when some config does
>> "foo.$worktree.bar" while others use per-worktree config files.
>
> What do you think about including worktree in this at this time? I'm not
> very familiar with it and just included it because I worked my way
> backwards from the DWIM function, but I could exclude it if you think
> it's too much trouble, i.e. if you'd like to hold out for some facility
> like the per-worktree config Junio mentioned.

I think Junio was talking about the future. What is currently in the
patch, both code and document, is fine. But if you're going to
implement core.$worktree.defaultRemote at this time, maybe wait a bit.
I could try to resurrect the per-worktree config topic in a month or
so and if it does not work out, then everybody will add new config
vars if they want per-worktree support.
-- 
Duy

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v3] checkout & worktree: introduce checkout.implicitRemote
  2018-05-24 19:47           ` [PATCH v3] " Ævar Arnfjörð Bjarmason
  2018-05-25  8:12             ` Junio C Hamano
@ 2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                 ` (9 more replies)
  1 sibling, 10 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31  7:45 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine


On Thu, May 24 2018, Ævar Arnfjörð Bjarmason wrote:

>  struct tracking_name_data {
>  	/* const */ char *src_ref;
>  	char *dst_ref;
>  	struct object_id *dst_oid;
>  	int unique;
> +	const char *implicit_remote;
> +	char *implicit_dst_ref;
>  };

There's a bug here that I'll fix in a v3. We need to have a implicit_*
variant for dst_oid as well. Currently this will be buggy and check out
origin/<branch>, but then check the index out to the tree of whatever
the last <someremote>/<branch> we iterated over was.

Easiy fix and I already have it locally, I just want to improve some of
the testing. I missed it because in my tests I'd just re-add the same
remote again, so the trees were the same.

>  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->implicit_remote && !strcmp(remote->name, cb->implicit_remote))
> +		cb->implicit_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 *implicit_remote = NULL;
> +	struct tracking_name_data cb_data = { NULL, NULL, NULL, 1, NULL, NULL };
> +	if (!git_config_get_string_const("checkout.implicitremote", &implicit_remote))
> +		cb_data.implicit_remote = implicit_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 *)implicit_remote);
> +	if (cb_data.unique) {
> +		free(cb_data.implicit_dst_ref);
>  		return cb_data.dst_ref;
> +	}
>  	free(cb_data.dst_ref);
> +	if (cb_data.implicit_dst_ref)
> +		return cb_data.implicit_dst_ref;
>  	return NULL;
>  }
> diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
> index 3e5ac81bd2..da6bd74bbc 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 checkout.implicitRemote #1' '
> +	git checkout -B master &&
> +	test_might_fail git branch -D foo &&
> +
> +	git -c checkout.implicitRemote=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 2240498924..271a6413f0 100755
> --- a/t/t2025-worktree-add.sh
> +++ b/t/t2025-worktree-add.sh
> @@ -402,6 +402,24 @@ test_expect_success '"add" <path> <branch> dwims' '
>  	)
>  '
>
> +test_expect_success '"add" <path> <branch> dwims with checkout.implicitRemote' '
> +	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 checkout.implicitRemote=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
> +	)
> +'
> +
>  test_expect_success 'git worktree add does not match remote' '
>  	test_when_finished rm -rf repo_a repo_b foo &&
>  	setup_remote_repo repo_a repo_b &&

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                   ` (8 more replies)
  2018-05-31 19:52               ` [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
                                 ` (8 subsequent siblings)
  9 siblings, 9 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

v4 started as a simple bug-fix for this one-part series, but since
it's not going to make 2.18.0 at this point I thought I'd do some more
work on it. Comments on patches below:

Ævar Arnfjörð Bjarmason (9):
  checkout tests: index should be clean after dwim checkout

Tests that would have revealed the bug in v3.

  checkout.h: wrap the arguments to unique_tracking_name()
  checkout.[ch]: move struct declaration into *.h

Boring moving code around.

  checkout.[ch]: introduce an *_INIT macro

Make checkout.h have a TRACKING_NAME_DATA_INIT for its struct.

  checkout.[ch]: change "unique" member to "num_matches"
  checkout: pass the "num_matches" up to callers
  builtin/checkout.c: use "ret" variable for return

Refactoring with no changes yet to make subsequent changes smaller.

  checkout: add advice for ambiguous "checkout <branch>"

Even if checkout.defaultRemote is off we now print advice telling the
user why their "git checkout branch" didn't work.

  checkout & worktree: introduce checkout.defaultRemote

It's now called checkout.defaultRemote not checkout.implicitRemote on
Junio's suggestion. On reflection that's better.

Improved tests for git-worktree (similar to the dwim checkout tests
improvements earlier), and the the documentation for git-checkout &
git-worktree.

I'm omitting the tbdiff because most of it's because of the new
patches in this series. Better just to read them.

 Documentation/config.txt       | 26 +++++++++++++++
 Documentation/git-checkout.txt |  9 ++++++
 Documentation/git-worktree.txt |  9 ++++++
 advice.c                       |  2 ++
 advice.h                       |  1 +
 builtin/checkout.c             | 44 ++++++++++++++++++++------
 builtin/worktree.c             |  4 +--
 checkout.c                     | 37 +++++++++++++++-------
 checkout.h                     | 16 +++++++++-
 t/t2024-checkout-dwim.sh       | 58 +++++++++++++++++++++++++++++++++-
 t/t2025-worktree-add.sh        | 21 ++++++++++++
 11 files changed, 203 insertions(+), 24 deletions(-)

-- 
2.17.0.290.gded63e768a


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-06-01  4:06                 ` Junio C Hamano
  2018-05-31 19:52               ` [PATCH v4 2/9] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
                                 ` (7 subsequent siblings)
  9 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Assert that whenever there's a DWIM checkout that the index should be
clean afterwards, in addition to the correct branch being checked-out.

The way the DWIM checkout code in checkout.[ch] works is by looping
over all remotes, and for each remote trying to find if a given
reference name only exists on that remote, or if it exists anywhere
else.

This is done by starting out with a `unique = 1` tracking variable in
a struct shared by the entire loop, which will get set to `0` if the
data reference is not unique.

Thus if we find a match we know the dst_oid member of
tracking_name_data must be correct, since it's associated with the
only reference on the only remote that could have matched our query.

But if there was ever a mismatch there for some reason we might end up
with the correct branch checked out, but at the wrong oid, which would
show whatever the difference between the two staged in the
index (checkout branch A, stage changes from the state of branch B).

So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
tests verifying current DWIM behavior of 'git checkout <branch>'",
2013-04-21) to always assert that "status" is clean after we run
"checkout", that's being done with "-uno" because there's going to be
some untracked files related to the test itself which we don't care
about.

Then if we ever run into this sort of regression, either in the
existing code or with a new feature, we'll know.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t2024-checkout-dwim.sh | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..29c1eada17 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -23,6 +23,12 @@ test_branch_upstream () {
 	test_cmp expect.upstream actual.upstream
 }
 
+status_uno_is_clean() {
+	>status.expect &&
+	git status -uno --porcelain >status.actual &&
+	test_cmp status.expect status.actual
+}
+
 test_expect_success 'setup' '
 	test_commit my_master &&
 	git init repo_a &&
@@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
 	test_might_fail git branch -D xyzzy &&
 
 	test_must_fail git checkout xyzzy &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
 	test_branch master
 '
@@ -64,8 +71,10 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_might_fail git branch -D foo &&
 
 	test_must_fail git checkout foo &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/foo &&
-	test_branch master
+	test_branch master &&
+	status_uno_is_clean
 '
 
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
@@ -73,6 +82,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	test_might_fail git branch -D bar &&
 
 	git checkout bar &&
+	status_uno_is_clean &&
 	test_branch bar &&
 	test_cmp_rev remotes/repo_a/bar HEAD &&
 	test_branch_upstream bar repo_a bar
@@ -83,6 +93,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 	test_might_fail git branch -D baz &&
 
 	git checkout baz &&
+	status_uno_is_clean &&
 	test_branch baz &&
 	test_cmp_rev remotes/other_b/baz HEAD &&
 	test_branch_upstream baz repo_b baz
@@ -90,6 +101,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 
 test_expect_success '--no-guess suppresses branch auto-vivification' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout --no-guess bar &&
@@ -99,6 +111,7 @@ test_expect_success '--no-guess suppresses branch auto-vivification' '
 
 test_expect_success 'setup more remotes with unconventional refspecs' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	git init repo_c &&
 	(
 		cd repo_c &&
@@ -128,27 +141,33 @@ test_expect_success 'setup more remotes with unconventional refspecs' '
 
 test_expect_success 'checkout of branch from multiple remotes fails #2' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout bar &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/bar &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from multiple remotes fails #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D baz &&
 
 	test_must_fail git checkout baz &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/baz &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from a single remote succeeds #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	git checkout spam &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -156,9 +175,11 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' '
 
 test_expect_success 'checkout of branch from a single remote succeeds #4' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D eggs &&
 
 	git checkout eggs &&
+	status_uno_is_clean &&
 	test_branch eggs &&
 	test_cmp_rev refs/repo_d/eggs HEAD &&
 	test_branch_upstream eggs repo_d eggs
@@ -166,32 +187,38 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' '
 
 test_expect_success 'checkout of branch with a file having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	test_must_fail git checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch with a file in subdir having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	mkdir sub &&
 	mv spam sub/spam &&
 	test_must_fail git -C sub checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	git checkout spam -- &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -200,6 +227,7 @@ test_expect_success 'checkout <branch> -- succeeds, even if a file with the same
 test_expect_success 'loosely defined local base branch is reported correctly' '
 
 	git checkout master &&
+	status_uno_is_clean &&
 	git branch strict &&
 	git branch loose &&
 	git commit --allow-empty -m "a bit more" &&
@@ -210,7 +238,9 @@ test_expect_success 'loosely defined local base branch is reported correctly' '
 	test_config branch.loose.merge master &&
 
 	git checkout strict | sed -e "s/strict/BRANCHNAME/g" >expect &&
+	status_uno_is_clean &&
 	git checkout loose | sed -e "s/loose/BRANCHNAME/g" >actual &&
+	status_uno_is_clean &&
 
 	test_cmp expect actual
 '
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 2/9] checkout.h: wrap the arguments to unique_tracking_name()
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h Ævar Arnfjörð Bjarmason
                                 ` (6 subsequent siblings)
  9 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

The line was too long already, and will be longer still when a later
change adds another argument.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/checkout.h b/checkout.h
index 9980711179..4cd4cd1c23 100644
--- a/checkout.h
+++ b/checkout.h
@@ -8,6 +8,7 @@
  * tracking branch.  Return the name of the remote if such a branch
  * exists, NULL otherwise.
  */
-extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+extern const char *unique_tracking_name(const char *name,
+					struct object_id *oid);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (2 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 2/9] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 21:45                 ` Thomas Gummerer
  2018-05-31 19:52               ` [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
                                 ` (5 subsequent siblings)
  9 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Move the tracking_name_data struct used in checkout.c into its
corresponding header file. This wasn't done in 7c85a87c54 ("checkout:
factor out functions to new lib file", 2017-11-26) when checkout.[ch]
were created, and is more consistent with the rest of the codebase.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 7 -------
 checkout.h | 7 +++++++
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/checkout.c b/checkout.c
index bdefc888ba..8d68f75ad1 100644
--- a/checkout.c
+++ b/checkout.c
@@ -3,13 +3,6 @@
 #include "refspec.h"
 #include "checkout.h"
 
-struct tracking_name_data {
-	/* const */ char *src_ref;
-	char *dst_ref;
-	struct object_id *dst_oid;
-	int unique;
-};
-
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
 	struct tracking_name_data *cb = cb_data;
diff --git a/checkout.h b/checkout.h
index 4cd4cd1c23..04b52f9ffe 100644
--- a/checkout.h
+++ b/checkout.h
@@ -3,6 +3,13 @@
 
 #include "cache.h"
 
+struct tracking_name_data {
+	/* const */ char *src_ref;
+	char *dst_ref;
+	struct object_id *dst_oid;
+	int unique;
+};
+
 /*
  * 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
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (3 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-06-01  4:16                 ` Junio C Hamano
  2018-05-31 19:52               ` [PATCH v4 5/9] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
                                 ` (4 subsequent siblings)
  9 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Add an *_INIT macro for the tracking_name_data similar to what exists
elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
will make it more idiomatic in later changes to add more fields to the
struct & its initialization macro.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 2 +-
 checkout.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/checkout.c b/checkout.c
index 8d68f75ad1..629fc1d5c4 100644
--- a/checkout.c
+++ b/checkout.c
@@ -25,7 +25,7 @@ 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 };
+	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
diff --git a/checkout.h b/checkout.h
index 04b52f9ffe..a61ec93e65 100644
--- a/checkout.h
+++ b/checkout.h
@@ -10,6 +10,8 @@ struct tracking_name_data {
 	int unique;
 };
 
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+
 /*
  * 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
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 5/9] checkout.[ch]: change "unique" member to "num_matches"
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (4 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 6/9] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
                                 ` (3 subsequent siblings)
  9 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Internally track how many matches we find in the check_tracking_name()
callback. Nothing uses this now, but it will be made use of in a later
change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 4 ++--
 checkout.h | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/checkout.c b/checkout.c
index 629fc1d5c4..7ce5306bc7 100644
--- a/checkout.c
+++ b/checkout.c
@@ -14,9 +14,9 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		free(query.dst);
 		return 0;
 	}
+	cb->num_matches++;
 	if (cb->dst_ref) {
 		free(query.dst);
-		cb->unique = 0;
 		return 0;
 	}
 	cb->dst_ref = query.dst;
@@ -30,7 +30,7 @@ const char *unique_tracking_name(const char *name, struct object_id *oid)
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	free(cb_data.src_ref);
-	if (cb_data.unique)
+	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
 	free(cb_data.dst_ref);
 	return NULL;
diff --git a/checkout.h b/checkout.h
index a61ec93e65..2decb9b820 100644
--- a/checkout.h
+++ b/checkout.h
@@ -7,10 +7,10 @@ struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
-	int unique;
+	int num_matches;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
 
 /*
  * Check if the branch name uniquely matches a branch name on a remote
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 6/9] checkout: pass the "num_matches" up to callers
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (5 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 5/9] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 7/9] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
                                 ` (2 subsequent siblings)
  9 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Pass the previously added "num_matches" struct value up to the callers
of unique_tracking_name(). This will allow callers to optionally print
better error messages in a later change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 16 +++++++++++-----
 builtin/worktree.c |  4 ++--
 checkout.c         |  5 ++++-
 checkout.h         |  3 ++-
 4 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2e1d2376d2..ec7cf93b4a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -239,7 +239,8 @@ static int checkout_merged(int pos, const struct checkout *state)
 }
 
 static int checkout_paths(const struct checkout_opts *opts,
-			  const char *revision)
+			  const char *revision,
+			  int *dwim_remotes_matched)
 {
 	int pos;
 	struct checkout state = CHECKOUT_INIT;
@@ -878,7 +879,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
 				struct checkout_opts *opts,
-				struct object_id *rev)
+				struct object_id *rev,
+				int *dwim_remotes_matched)
 {
 	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
@@ -972,7 +974,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 			recover_with_dwim = 0;
 
 		if (recover_with_dwim) {
-			const char *remote = unique_tracking_name(arg, rev);
+			const char *remote = unique_tracking_name(arg, rev,
+								  dwim_remotes_matched);
 			if (remote) {
 				*new_branch = arg;
 				arg = remote;
@@ -1109,6 +1112,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch = 1;
+	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
 		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1219,7 +1223,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts.track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts.new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev);
+					     &new_branch_info, &opts, &rev,
+					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
@@ -1262,7 +1267,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	UNLEAK(opts);
 	if (opts.patch_mode || opts.pathspec.nr)
-		return checkout_paths(&opts, new_branch_info.name);
+		return checkout_paths(&opts, new_branch_info.name,
+				      &dwim_remotes_matched);
 	else
 		return checkout_branch(&opts, &new_branch_info);
 }
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5c7d2bb180..a763dbdccb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
 	if (guess_remote) {
 		struct object_id oid;
 		const char *remote =
-			unique_tracking_name(*new_branch, &oid);
+			unique_tracking_name(*new_branch, &oid, NULL);
 		return remote;
 	}
 	return NULL;
@@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)
 
 		commit = lookup_commit_reference_by_name(branch);
 		if (!commit) {
-			remote = unique_tracking_name(branch, &oid);
+			remote = unique_tracking_name(branch, &oid, NULL);
 			if (remote) {
 				new_branch = branch;
 				branch = remote;
diff --git a/checkout.c b/checkout.c
index 7ce5306bc7..c578782baa 100644
--- a/checkout.c
+++ b/checkout.c
@@ -23,12 +23,15 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	return 0;
 }
 
-const char *unique_tracking_name(const char *name, struct object_id *oid)
+const char *unique_tracking_name(const char *name, struct object_id *oid,
+				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
+	if (dwim_remotes_matched)
+		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
 	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
diff --git a/checkout.h b/checkout.h
index 2decb9b820..4e518c801a 100644
--- a/checkout.h
+++ b/checkout.h
@@ -18,6 +18,7 @@ struct tracking_name_data {
  * exists, NULL otherwise.
  */
 extern const char *unique_tracking_name(const char *name,
-					struct object_id *oid);
+					struct object_id *oid,
+					int *dwim_remotes_matched);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 7/9] builtin/checkout.c: use "ret" variable for return
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (6 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 6/9] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
  2018-05-31 19:52               ` [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  9 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

There is no point in doing this right now, but in later change the
"ret" variable will be inspected. This change makes that meaningful
change smaller.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index ec7cf93b4a..423e056acd 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1266,9 +1266,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr)
-		return checkout_paths(&opts, new_branch_info.name,
-				      &dwim_remotes_matched);
-	else
+	if (opts.patch_mode || opts.pathspec.nr) {
+		int ret = checkout_paths(&opts, new_branch_info.name,
+					 &dwim_remotes_matched);
+		return ret;
+	} else {
 		return checkout_branch(&opts, &new_branch_info);
+	}
 }
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (7 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 7/9] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-06-01  4:32                 ` Junio C Hamano
  2018-06-01  7:53                 ` Eric Sunshine
  2018-05-31 19:52               ` [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  9 siblings, 2 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

As the "checkout" documentation describes:

    If <branch> is not found but there does exist a tracking branch in
    exactly one remote (call it <remote>) with a matching name, treat
    as equivalent to [...] <remote>/<branch.

This is a really useful feature, the problem is that when you another
remote (e.g. a fork) git won't find a unique branch name anymore, and
will instead print this nondescript message:

    $ git checkout master
    error: pathspec 'master' did not match any file(s) known to git

Now it will, on my git.git checkout, print:

    $ ./git --exec-path=$PWD checkout master
    error: pathspec 'master' did not match any file(s) known to git.
    hint: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
    hint: instead of <name>?

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/config.txt |  7 +++++++
 advice.c                 |  2 ++
 advice.h                 |  1 +
 builtin/checkout.c       | 11 +++++++++++
 t/t2024-checkout-dwim.sh | 14 ++++++++++++++
 5 files changed, 35 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 7d8383433c..08d3e70989 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -344,6 +344,13 @@ advice.*::
 		Advice shown when you used linkgit:git-checkout[1] to
 		move to the detach HEAD state, to instruct how to create
 		a local branch after the fact.
+	checkoutAmbiguousRemoteBranchName::
+		Advice shown when the argument to
+		linkgit:git-checkout[1] ambiguously resolves to a
+		remote tracking branch on more than one remote in
+		situations where an unambiguous argument would have
+		otherwise caused a remote-tracking branch to be
+		checked out.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
diff --git a/advice.c b/advice.c
index 370a56d054..75e7dede90 100644
--- a/advice.c
+++ b/advice.c
@@ -21,6 +21,7 @@ int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
+int advice_checkout_ambiguous_remote_branch_name = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -72,6 +73,7 @@ static struct {
 	{ "ignoredhook", &advice_ignored_hook },
 	{ "waitingforeditor", &advice_waiting_for_editor },
 	{ "graftfiledeprecated", &advice_graft_file_deprecated },
+	{ "checkoutambiguousremotebranchname", &advice_checkout_ambiguous_remote_branch_name },
 
 	/* make this an alias for backward compatibility */
 	{ "pushnonfastforward", &advice_push_update_rejected }
diff --git a/advice.h b/advice.h
index 9f5064e82a..4d11d51d43 100644
--- a/advice.h
+++ b/advice.h
@@ -22,6 +22,7 @@ extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
+extern int advice_checkout_ambiguous_remote_branch_name;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 423e056acd..710369a60a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -22,6 +22,7 @@
 #include "resolve-undo.h"
 #include "submodule-config.h"
 #include "submodule.h"
+#include "advice.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
@@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (opts.patch_mode || opts.pathspec.nr) {
 		int ret = checkout_paths(&opts, new_branch_info.name,
 					 &dwim_remotes_matched);
+		if (ret && dwim_remotes_matched > 1 &&
+		    advice_checkout_ambiguous_remote_branch_name)
+			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
+				 "We found %d remotes with a reference that matched. So we fell back\n"
+				 "on trying to resolve the argument as a path, but failed there too!\n"
+				 "\n"
+				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
+				 "instead of <name>?"),
+			       argv[0],
+			       dwim_remotes_matched);
 		return ret;
 	} else {
 		return checkout_branch(&opts, &new_branch_info);
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 29c1eada17..14735f5bb8 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -77,6 +77,20 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	status_uno_is_clean
 '
 
+test_expect_success 'checkout of branch from multiple remotes fails with advice' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+	test_must_fail git checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep "^hint: " stderr &&
+	test_must_fail git -c advice.checkoutAmbiguousRemoteBranchName=false \
+		checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep ! "^hint: " stderr
+'
+
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	git checkout -B master &&
 	test_might_fail git branch -D bar &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
                                 ` (8 preceding siblings ...)
  2018-05-31 19:52               ` [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-05-31 19:52               ` Ævar Arnfjörð Bjarmason
  2018-05-31 21:49                 ` Stefan Beller
  2018-05-31 22:22                 ` Thomas Gummerer
  9 siblings, 2 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-31 19:52 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

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 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:

    (
        cd /tmp &&
        rm -rf 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 (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: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
    hint: instead of <name>?
    hint:
    hint: If you'd like to always have checkouts of 'master' prefer one remote,
    hint: e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin
    hint: in your config. See the 'git-config' manual page for details.

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 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       | 21 ++++++++++++++++++++-
 Documentation/git-checkout.txt |  9 +++++++++
 Documentation/git-worktree.txt |  9 +++++++++
 builtin/checkout.c             | 15 +++++++++++----
 checkout.c                     | 21 ++++++++++++++++++++-
 checkout.h                     |  5 ++++-
 t/t2024-checkout-dwim.sh       | 12 ++++++++++++
 t/t2025-worktree-add.sh        | 21 +++++++++++++++++++++
 8 files changed, 106 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 08d3e70989..e0d92217ac 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -350,7 +350,10 @@ advice.*::
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
-		checked out.
+		checked out. See the `checkout.defaultRemote`
+		configuration variable for how to set a given remote
+		to used by default in some situations where this
+		advice would be printed.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
@@ -1105,6 +1108,22 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.defaultRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. This setting allows for setting the name of a
+	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' refers to a
+remote branch. This setting might be used for other checkout-like
+commands or functionality in the future.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..8cb77bddeb 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,15 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch> is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` 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 afc6576a14..6638d5ca3d 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch> is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch
 (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 710369a60a..78bbead000 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -913,8 +913,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.defaultRemote), 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).
 	 *
@@ -1277,9 +1279,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 				 "on trying to resolve the argument as a path, but failed there too!\n"
 				 "\n"
 				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
-				 "instead of <name>?"),
+				 "instead of <name>?\n"
+				 "\n"
+				 "If you'd like to always have checkouts of '%s' prefer one remote,\n"
+				 "e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin\n"
+				 "in your config. See the 'git-config' manual page for details."),
 			       argv[0],
-			       dwim_remotes_matched);
+			       dwim_remotes_matched,
+			       argv[0]);
 		return ret;
 	} else {
 		return checkout_branch(&opts, &new_branch_info);
diff --git a/checkout.c b/checkout.c
index c578782baa..e6cfe6d2ab 100644
--- a/checkout.c
+++ b/checkout.c
@@ -2,6 +2,7 @@
 #include "remote.h"
 #include "refspec.h"
 #include "checkout.h"
+#include "config.h"
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -15,6 +16,12 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		return 0;
 	}
 	cb->num_matches++;
+	if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
+		struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
+		cb->default_dst_ref = xstrdup(query.dst);
+		oidcpy(dst, cb->dst_oid);
+		cb->default_dst_oid = dst;
+	}
 	if (cb->dst_ref) {
 		free(query.dst);
 		return 0;
@@ -27,14 +34,26 @@ const char *unique_tracking_name(const char *name, struct object_id *oid,
 				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
+	const char *default_remote = NULL;
+	if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
+		cb_data.default_remote = default_remote;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
-	if (cb_data.num_matches == 1)
+	free((char *)default_remote);
+	if (cb_data.num_matches == 1) {
+		free(cb_data.default_dst_ref);
+		free(cb_data.default_dst_oid);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.default_dst_ref) {
+		oidcpy(oid, cb_data.default_dst_oid);
+		free(cb_data.default_dst_oid);
+		return cb_data.default_dst_ref;
+	}
 	return NULL;
 }
diff --git a/checkout.h b/checkout.h
index 4e518c801a..e9f1372f32 100644
--- a/checkout.h
+++ b/checkout.h
@@ -8,9 +8,12 @@ struct tracking_name_data {
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
+	const char *default_remote;
+	char *default_dst_ref;
+	struct object_id *default_dst_oid;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
 
 /*
  * Check if the branch name uniquely matches a branch name on a remote
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 14735f5bb8..ccd96ba85c 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -91,6 +91,18 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
 	test_i18ngrep ! "^hint: " stderr
 '
 
+test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
+	git checkout -B master &&
+	status_uno_is_clean &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.defaultRemote=repo_a checkout foo &&
+	status_uno_is_clean &&
+	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 d2e49f7632..be6e093142 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -402,6 +402,27 @@ test_expect_success '"add" <path> <branch> dwims' '
 	)
 '
 
+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 &&
+		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+		>status.expect &&
+		git status -uno --porcelain >status.actual &&
+		test_cmp status.expect status.actual
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_upstream foo &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+	)
+'
+
 test_expect_success 'git worktree add does not match remote' '
 	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h
  2018-05-31 19:52               ` [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h Ævar Arnfjörð Bjarmason
@ 2018-05-31 21:45                 ` Thomas Gummerer
  2018-06-01  2:14                   ` Junio C Hamano
  0 siblings, 1 reply; 95+ messages in thread
From: Thomas Gummerer @ 2018-05-31 21:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Eric Sunshine

On 05/31, Ævar Arnfjörð Bjarmason wrote:
> Move the tracking_name_data struct used in checkout.c into its
> corresponding header file. This wasn't done in 7c85a87c54 ("checkout:
> factor out functions to new lib file", 2017-11-26) when checkout.[ch]
> were created, and is more consistent with the rest of the codebase.

We seem to have plenty of structs defined in '.c' files, if they are
only needed there.  Its use also seems to be single purpose for the
callback data, so I'm a bit puzzled how having this in a header file
instead of the .c file would be helpful?

I feel like having only the "public" part in the header file also
helps developers that are just looking for documentation of the
functions they are looking at, by having less things to go through,
that they wouldn't necessarily care about.

> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  checkout.c | 7 -------
>  checkout.h | 7 +++++++
>  2 files changed, 7 insertions(+), 7 deletions(-)
> 
> diff --git a/checkout.c b/checkout.c
> index bdefc888ba..8d68f75ad1 100644
> --- a/checkout.c
> +++ b/checkout.c
> @@ -3,13 +3,6 @@
>  #include "refspec.h"
>  #include "checkout.h"
>  
> -struct tracking_name_data {
> -	/* const */ char *src_ref;
> -	char *dst_ref;
> -	struct object_id *dst_oid;
> -	int unique;
> -};
> -
>  static int check_tracking_name(struct remote *remote, void *cb_data)
>  {
>  	struct tracking_name_data *cb = cb_data;
> diff --git a/checkout.h b/checkout.h
> index 4cd4cd1c23..04b52f9ffe 100644
> --- a/checkout.h
> +++ b/checkout.h
> @@ -3,6 +3,13 @@
>  
>  #include "cache.h"
>  
> +struct tracking_name_data {
> +	/* const */ char *src_ref;
> +	char *dst_ref;
> +	struct object_id *dst_oid;
> +	int unique;
> +};
> +
>  /*
>   * 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
> -- 
> 2.17.0.290.gded63e768a
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 19:52               ` [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
@ 2018-05-31 21:49                 ` Stefan Beller
  2018-06-01  8:04                   ` Eric Sunshine
  2018-06-01  9:47                   ` Ævar Arnfjörð Bjarmason
  2018-05-31 22:22                 ` Thomas Gummerer
  1 sibling, 2 replies; 95+ messages in thread
From: Stefan Beller @ 2018-05-31 21:49 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Hi Ævar,

Sorry for chiming in late. I have a couple of thoughts:

>     (
>         cd /tmp &&
>         rm -rf 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'

I thought master is already there after the clone operation and
you'd merely switch back to the local branch that was created at
clone time?

    $ git clone git@github.com:trast/tbdiff.git && cd tbdiff
    $ git branch
    * master
    $ cat .git/config
...
[branch "master"]
    remote = origin
    merge = refs/heads/master

But the observation is right, we get that message. When do we
do the setup for the master branch specifically?

>
> 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 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 (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: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>

s/meant fully/meant to fully/
s/? E.g./?\nFor example/

>     hint: instead of <name>?

In builtin/submodule--helper.c there is get_default_remote() which also
hardcodes "origin". I think that is a safe thing to do.

>     hint:
>     hint: If you'd like to always have checkouts of 'master' prefer one remote,
>     hint: e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin
>     hint: in your config. See the 'git-config' manual page for details.

his new setting elevates one remote over all others, which may
be enough for most setups and not confusing, too.
Consider the following:

    git clone https://kernel.googlesource.com/pub/scm/git/git && cd git
    git remote add gitster https://github.com/gitster/git
    git remote add interesting-patches https://github.com/avar/git
    git remote add my-github https://github.com/stefanbeller/git

    git checkout master

This probably means I want to have origin/master (from kernel.org)

    git checkout ab/checkout-implicit-remote

This probably wants to have it from gitster/ (as it is not found on kernel.org);
I am not sure if it would want to look at interesting-patches/ that mirrors
github, but probably if it were not to be found at gitster.

So maybe we rather want a setup to give a defined priority for
the search order:

  git config dwim.remoteSearchOrder origin gitster avar

Stepping back a bit, there is already an order in the config file
for the remotes, and that order is used for example for 'fetch --all'.

I wonder if we want to take that order? (Or are the days of hand
editing the config over and this is too arcane? We would need a
config command to re order remotes). Then we could just have a
boolean switch to use the config order on ambiguity.
Although you might want to have a different order for fetching
and looking for the right checkout.

> 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?).

  core.dwimRemote ? It's a bit cryptic, though.

> 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.

Stefan

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 19:52               ` [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  2018-05-31 21:49                 ` Stefan Beller
@ 2018-05-31 22:22                 ` Thomas Gummerer
  2018-06-01  2:17                   ` Junio C Hamano
  1 sibling, 1 reply; 95+ messages in thread
From: Thomas Gummerer @ 2018-05-31 22:22 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Eric Sunshine

On 05/31, Ævar Arnfjörð Bjarmason wrote:
> 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 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:
> 
>     (
>         cd /tmp &&
>         rm -rf 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 (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: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
>     hint: instead of <name>?
>     hint:
>     hint: If you'd like to always have checkouts of 'master' prefer one remote,
>     hint: e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin
>     hint: in your config. See the 'git-config' manual page for details.
> 
> 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?).

I agree that splitting this into two variables would be needlessly
confusing.  'checkout' and 'worktree add' are similar enough in
spirit, that users only setting one of the configuration variables
would end up confused at some point.  Because the commands are so
similar, I also feel like it would be okay to break our own rules
here, and use the 'core.defaultRemote' name you suggested (I also
can't come up with anything better in core.* right now).

> 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       | 21 ++++++++++++++++++++-
>  Documentation/git-checkout.txt |  9 +++++++++
>  Documentation/git-worktree.txt |  9 +++++++++
>  builtin/checkout.c             | 15 +++++++++++----
>  checkout.c                     | 21 ++++++++++++++++++++-
>  checkout.h                     |  5 ++++-
>  t/t2024-checkout-dwim.sh       | 12 ++++++++++++
>  t/t2025-worktree-add.sh        | 21 +++++++++++++++++++++
>  8 files changed, 106 insertions(+), 7 deletions(-)
>
> [snip]
>
> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
> index ca5fc9c798..8cb77bddeb 100644
> --- a/Documentation/git-checkout.txt
> +++ b/Documentation/git-checkout.txt
> @@ -38,6 +38,15 @@ equivalent to
>  $ git checkout -b <branch> --track <remote>/<branch>
>  ------------
>  +
> +If the branch exists in multiple remotes and one of them is named by
> +the `checkout.defaultRemote` configuration variable, we'll use that
> +one for the purposes of disambiguation, even if the `<branch>` isn't
> +unique across all remotes. Set it to
> +e.g. `checkout.defaultRemote=origin` to always checkout remote
> +branches from there if `<branch> is ambiguous but exists on the

s/`<branch>/&`/

> +'origin' remote. See also `checkout.defaultRemote` 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 afc6576a14..6638d5ca3d 100644
> --- a/Documentation/git-worktree.txt
> +++ b/Documentation/git-worktree.txt
> @@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
>  $ git worktree add --track -b <branch> <path> <remote>/<branch>
>  ------------
>  +
> +If the branch exists in multiple remotes and one of them is named by
> +the `checkout.defaultRemote` configuration variable, we'll use that
> +one for the purposes of disambiguation, even if the `<branch>` isn't
> +unique across all remotes. Set it to
> +e.g. `checkout.defaultRemote=origin` to always checkout remote
> +branches from there if `<branch> is ambiguous but exists on the

s/`<branch>/&`/

> +'origin' remote. See also `checkout.defaultRemote` in
> +linkgit:git-config[1].
> ++
>  If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
>  then, as a convenience, the new worktree is associated with a branch
>  (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
>
> [snip]
>
> -- 
> 2.17.0.290.gded63e768a
> 

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h
  2018-05-31 21:45                 ` Thomas Gummerer
@ 2018-06-01  2:14                   ` Junio C Hamano
  2018-06-01  9:56                     ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  2:14 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Ævar Arnfjörð Bjarmason, git, Jeff King,
	Johannes Schindelin, Nguyễn Thái Ngọc Duy,
	Eric Sunshine

Thomas Gummerer <t.gummerer@gmail.com> writes:

> We seem to have plenty of structs defined in '.c' files, if they are
> only needed there.  Its use also seems to be single purpose for the
> callback data, so I'm a bit puzzled how having this in a header file
> instead of the .c file would be helpful?
>
> I feel like having only the "public" part in the header file also
> helps developers that are just looking for documentation of the
> functions they are looking at, by having less things to go through,
> that they wouldn't necessarily care about.

Yup, sounds like a sensible criterion to choose between <*.h> and
<*.c>.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 22:22                 ` Thomas Gummerer
@ 2018-06-01  2:17                   ` Junio C Hamano
  0 siblings, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  2:17 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Ævar Arnfjörð Bjarmason, git, Jeff King,
	Johannes Schindelin, Nguyễn Thái Ngọc Duy,
	Eric Sunshine

Thomas Gummerer <t.gummerer@gmail.com> writes:

>> 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?).

I do think "checkout" in name is grately helpful.  I do not see why
it is a bad idea for the worktree codepath to pay attention to the
checkout.defaultRemote configuration variable, especially when those
who are discussing this thread agree "checkout" and "worktree add"
are quite similar in end-users' minds.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout
  2018-05-31 19:52               ` [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-06-01  4:06                 ` Junio C Hamano
  2018-06-01 19:43                   ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  4:06 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Assert that whenever there's a DWIM checkout that the index should be
> clean afterwards, in addition to the correct branch being checked-out.
> ...
> So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
> tests verifying current DWIM behavior of 'git checkout <branch>'",
> 2013-04-21) to always assert that "status" is clean after we run
> "checkout", that's being done with "-uno" because there's going to be
> some untracked files related to the test itself which we don't care
> about.

It might not be absolutely necessary to state, but it would be
helpful to say that you are assuming to start a checkout (DWIM or
otherwise) from a clean state; without the assumption, the readers
need to think for a few breaths why "the index should be clean" is
true.

The intention and the implementation of the change both mostly look
good to me from a quick read.

>  test_expect_success 'setup' '
>  	test_commit my_master &&
>  	git init repo_a &&
> @@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
>  	test_might_fail git branch -D xyzzy &&
>  
>  	test_must_fail git checkout xyzzy &&
> +	status_uno_is_clean &&
>  	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
>  	test_branch master
>  '
> @@ -64,8 +71,10 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
>  	test_might_fail git branch -D foo &&
>  
>  	test_must_fail git checkout foo &&
> +	status_uno_is_clean &&
>  	test_must_fail git rev-parse --verify refs/heads/foo &&
> -	test_branch master
> +	test_branch master &&
> +	status_uno_is_clean

Hmm, what's the point of this second one?

>  '

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro
  2018-05-31 19:52               ` [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-06-01  4:16                 ` Junio C Hamano
  0 siblings, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  4:16 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Add an *_INIT macro for the tracking_name_data similar to what exists
> elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
> will make it more idiomatic in later changes to add more fields to the
> struct & its initialization macro.

Makes sense; Thomas's comment on 3/9 still stands at this point, as
we have no outside users of the definition of the callback data yet
at this point.

>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  checkout.c | 2 +-
>  checkout.h | 2 ++
>  2 files changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/checkout.c b/checkout.c
> index 8d68f75ad1..629fc1d5c4 100644
> --- a/checkout.c
> +++ b/checkout.c
> @@ -25,7 +25,7 @@ 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 };
> +	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
>  	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
>  	cb_data.dst_oid = oid;
>  	for_each_remote(check_tracking_name, &cb_data);
> diff --git a/checkout.h b/checkout.h
> index 04b52f9ffe..a61ec93e65 100644
> --- a/checkout.h
> +++ b/checkout.h
> @@ -10,6 +10,8 @@ struct tracking_name_data {
>  	int unique;
>  };
>  
> +#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
> +
>  /*
>   * 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

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-05-31 19:52               ` [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-06-01  4:32                 ` Junio C Hamano
  2018-06-01  5:11                   ` Junio C Hamano
  2018-06-01  9:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-01  7:53                 ` Eric Sunshine
  1 sibling, 2 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  4:32 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>  	if (opts.patch_mode || opts.pathspec.nr) {
>  		int ret = checkout_paths(&opts, new_branch_info.name,
>  					 &dwim_remotes_matched);
> +		if (ret && dwim_remotes_matched > 1 &&
> +		    advice_checkout_ambiguous_remote_branch_name)
> +			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
> +				 "We found %d remotes with a reference that matched. So we fell back\n"
> +				 "on trying to resolve the argument as a path, but failed there too!\n"
> +				 "\n"
> +				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
> +				 "instead of <name>?"),
> +			       argv[0],
> +			       dwim_remotes_matched);
>  		return ret;

Do we give "checkout -p no-such-file" the above wall of text?

Somehow checkout_paths(), which is "we were given a tree-ish and
pathspec and told to grab the matching paths out of it and stuff
them to the index and the working tree", is a wrong place to be
doing the "oh, what the caller thought was pathspec may turn out to
be a rev, so check that too for such a confused caller".  Shouldn't
the caller be doing all that (which would mean we wan't need to pass
"remotes-matched" to the function, as the helper has nothing to do
with deciding which arg is the tree-ish).


^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01  4:32                 ` Junio C Hamano
@ 2018-06-01  5:11                   ` Junio C Hamano
  2018-06-01  9:54                     ` Ævar Arnfjörð Bjarmason
  2018-06-01  9:50                   ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-06-01  5:11 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Junio C Hamano <gitster@pobox.com> writes:

> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>>  	if (opts.patch_mode || opts.pathspec.nr) {
>>  		int ret = checkout_paths(&opts, new_branch_info.name,
>>  					 &dwim_remotes_matched);
>> +		if (ret && dwim_remotes_matched > 1 &&
>> +		    advice_checkout_ambiguous_remote_branch_name)
>> +			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
>> +				 "We found %d remotes with a reference that matched. So we fell back\n"
>> +				 "on trying to resolve the argument as a path, but failed there too!\n"
>> +				 "\n"
>> +				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
>> +				 "instead of <name>?"),
>> +			       argv[0],
>> +			       dwim_remotes_matched);
>>  		return ret;
>
> Do we give "checkout -p no-such-file" the above wall of text?
>
> Somehow checkout_paths(), which is "we were given a tree-ish and
> pathspec and told to grab the matching paths out of it and stuff
> them to the index and the working tree", is a wrong place to be
> doing the "oh, what the caller thought was pathspec may turn out to
> be a rev, so check that too for such a confused caller".  Shouldn't
> the caller be doing all that (which would mean we wan't need to pass
> "remotes-matched" to the function, as the helper has nothing to do
> with deciding which arg is the tree-ish).

Well, upon closer inspection adding *dwim_remotes_matched parameter
to checkout_paths() done in an earlier step seems to be totally
bogus and only serves the purpose of confusing reviewers.  The
function does not touch the pointer in any way---it does not use the
pointer to return its findings, and it does not use an earlier
findings to affect its behaviour by dereferencing it.

The dwim_remotes_matched is set by an earlier call to
parse_branchname_arg(), which does gain an int* parameter in this
series.  And that addition _does_ make sense.  That codepath is
where the "do we have many remotes that could match, or none, or
unique?" determination is made.


^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-05-31 19:52               ` [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
  2018-06-01  4:32                 ` Junio C Hamano
@ 2018-06-01  7:53                 ` Eric Sunshine
  2018-06-01 19:59                   ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 95+ messages in thread
From: Eric Sunshine @ 2018-06-01  7:53 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Thu, May 31, 2018 at 3:52 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> As the "checkout" documentation describes:
>
>     If <branch> is not found but there does exist a tracking branch in
>     exactly one remote (call it <remote>) with a matching name, treat
>     as equivalent to [...] <remote>/<branch.
>
> This is a really useful feature, the problem is that when you another

s/, the/. The/
s/you/& add/

> remote (e.g. a fork) git won't find a unique branch name anymore, and
> will instead print this nondescript message:
>
>     $ git checkout master
>     error: pathspec 'master' did not match any file(s) known to git
>
> Now it will, on my git.git checkout, print:
>
>     $ ./git --exec-path=$PWD checkout master
>     error: pathspec 'master' did not match any file(s) known to git.
>     hint: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>

s/meant/& to/

>     hint: instead of <name>?
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
> +               if (ret && dwim_remotes_matched > 1 &&
> +                   advice_checkout_ambiguous_remote_branch_name)
> +                       advise(_("The argument '%s' matched more than one remote tracking branch.\n"
> +                                "We found %d remotes with a reference that matched. So we fell back\n"
> +                                "on trying to resolve the argument as a path, but failed there too!\n"
> +                                "\n"
> +                                "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"

s/meant/& to/

> +                                "instead of <name>?"),

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 21:49                 ` Stefan Beller
@ 2018-06-01  8:04                   ` Eric Sunshine
  2018-06-01  9:47                   ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-06-01  8:04 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Ævar Arnfjörð Bjarmason, git, Junio C Hamano,
	Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Thu, May 31, 2018 at 5:49 PM, Stefan Beller <sbeller@google.com> wrote:
>> 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?).
>
>   core.dwimRemote ? It's a bit cryptic, though.

This option started out as 'core.dwimRemote' in the very first version
of this series[1], but someone argued against it for several reasons
and suggested 'defaultRemote' instead[2].

[1]: https://public-inbox.org/git/20180502105452.17583-1-avarab@gmail.com/
[2]: https://public-inbox.org/git/CAPig+cTZyYC-1_TxL2PrfOF6HAktUxxM+g5EXcByS5fCDMdCHg@mail.gmail.com/

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 21:49                 ` Stefan Beller
  2018-06-01  8:04                   ` Eric Sunshine
@ 2018-06-01  9:47                   ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01  9:47 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine


On Thu, May 31 2018, Stefan Beller wrote:

> Hi Ævar,
>
> Sorry for chiming in late. I have a couple of thoughts:
>
>>     (
>>         cd /tmp &&
>>         rm -rf 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'
>
> I thought master is already there after the clone operation and
> you'd merely switch back to the local branch that was created at
> clone time?
>
>     $ git clone git@github.com:trast/tbdiff.git && cd tbdiff
>     $ git branch
>     * master
>     $ cat .git/config
> ...
> [branch "master"]
>     remote = origin
>     merge = refs/heads/master
>
> But the observation is right, we get that message. When do we
> do the setup for the master branch specifically?

What you're missing is this part:

    git branch -m topic

I.e. we clone the repo, and have a "master" branch, we then rename
"master" to "topic", now there's no local master branch. Then we
checkout master either with only one remote or two.

>>
>> 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 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 (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: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
>
> s/meant fully/meant to fully/
> s/? E.g./?\nFor example/

Thanks, will fix.

>>     hint: instead of <name>?
>
> In builtin/submodule--helper.c there is get_default_remote() which also
> hardcodes "origin". I think that is a safe thing to do.
>
>>     hint:
>>     hint: If you'd like to always have checkouts of 'master' prefer one remote,
>>     hint: e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin
>>     hint: in your config. See the 'git-config' manual page for details.
>
> his new setting elevates one remote over all others, which may
> be enough for most setups and not confusing, too.
> Consider the following:
>
>     git clone https://kernel.googlesource.com/pub/scm/git/git && cd git
>     git remote add gitster https://github.com/gitster/git
>     git remote add interesting-patches https://github.com/avar/git
>     git remote add my-github https://github.com/stefanbeller/git
>
>     git checkout master
>
> This probably means I want to have origin/master (from kernel.org)
>
>     git checkout ab/checkout-implicit-remote
>
> This probably wants to have it from gitster/ (as it is not found on kernel.org);
> I am not sure if it would want to look at interesting-patches/ that mirrors
> github, but probably if it were not to be found at gitster.
>
> So maybe we rather want a setup to give a defined priority for
> the search order:
>
>   git config dwim.remoteSearchOrder origin gitster avar
>
> Stepping back a bit, there is already an order in the config file
> for the remotes, and that order is used for example for 'fetch --all'.
>
> I wonder if we want to take that order? (Or are the days of hand
> editing the config over and this is too arcane? We would need a
> config command to re order remotes). Then we could just have a
> boolean switch to use the config order on ambiguity.
> Although you might want to have a different order for fetching
> and looking for the right checkout.

I thought about this use-case, and if we want this in the future I think
the most straightforward way is not to invent some new search order
variable, but just make use of git config allowing multi-values, i.e.:

    [checkout]
        defaultRemote = origin
        defaultRemote = gitster

Although I'm not interested in implementing that now, and unlike just
having one special remote I don't think it's of interest to the vast
majority of git users.

>> 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?).
>
>   core.dwimRemote ? It's a bit cryptic, though.

Covered by Eric's reply in
<CAPig+cSk9Dt3ZLQRjWwpxqMyP3npu3KbEQxkNfjV5RxRtro82Q@mail.gmail.com>

>> 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.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01  4:32                 ` Junio C Hamano
  2018-06-01  5:11                   ` Junio C Hamano
@ 2018-06-01  9:50                   ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01  9:50 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine


On Fri, Jun 01 2018, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>>  	if (opts.patch_mode || opts.pathspec.nr) {
>>  		int ret = checkout_paths(&opts, new_branch_info.name,
>>  					 &dwim_remotes_matched);
>> +		if (ret && dwim_remotes_matched > 1 &&
>> +		    advice_checkout_ambiguous_remote_branch_name)
>> +			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
>> +				 "We found %d remotes with a reference that matched. So we fell back\n"
>> +				 "on trying to resolve the argument as a path, but failed there too!\n"
>> +				 "\n"
>> +				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
>> +				 "instead of <name>?"),
>> +			       argv[0],
>> +			       dwim_remotes_matched);
>>  		return ret;
>
> Do we give "checkout -p no-such-file" the above wall of text?

No:

    $ ./git --exec-path=$PWD checkout -p master
    No changes.
    $ ./git --exec-path=$PWD checkout master
    error: pathspec 'master' did not match any file(s) known to git.
    hint: The argument 'master' matched more than one remote tracking branch.
    [...]

> Somehow checkout_paths(), which is "we were given a tree-ish and
> pathspec and told to grab the matching paths out of it and stuff
> them to the index and the working tree", is a wrong place to be
> doing the "oh, what the caller thought was pathspec may turn out to
> be a rev, so check that too for such a confused caller".  Shouldn't
> the caller be doing all that (which would mean we wan't need to pass
> "remotes-matched" to the function, as the helper has nothing to do
> with deciding which arg is the tree-ish).

I skimmed the rest of this thread and saw there were more specific
suggestions. I'm open to moving this somewhere else. The "what is this
and what do we do" logic in checkout.c is spread over multiple
functions, and this seemed the most straightforward way to add this.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01  5:11                   ` Junio C Hamano
@ 2018-06-01  9:54                     ` Ævar Arnfjörð Bjarmason
  2018-06-04  1:58                       ` Junio C Hamano
  0 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01  9:54 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine


On Fri, Jun 01 2018, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>>
>>> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>>>  	if (opts.patch_mode || opts.pathspec.nr) {
>>>  		int ret = checkout_paths(&opts, new_branch_info.name,
>>>  					 &dwim_remotes_matched);
>>> +		if (ret && dwim_remotes_matched > 1 &&
>>> +		    advice_checkout_ambiguous_remote_branch_name)
>>> +			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
>>> +				 "We found %d remotes with a reference that matched. So we fell back\n"
>>> +				 "on trying to resolve the argument as a path, but failed there too!\n"
>>> +				 "\n"
>>> +				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
>>> +				 "instead of <name>?"),
>>> +			       argv[0],
>>> +			       dwim_remotes_matched);
>>>  		return ret;
>>
>> Do we give "checkout -p no-such-file" the above wall of text?
>>
>> Somehow checkout_paths(), which is "we were given a tree-ish and
>> pathspec and told to grab the matching paths out of it and stuff
>> them to the index and the working tree", is a wrong place to be
>> doing the "oh, what the caller thought was pathspec may turn out to
>> be a rev, so check that too for such a confused caller".  Shouldn't
>> the caller be doing all that (which would mean we wan't need to pass
>> "remotes-matched" to the function, as the helper has nothing to do
>> with deciding which arg is the tree-ish).
>
> Well, upon closer inspection adding *dwim_remotes_matched parameter
> to checkout_paths() done in an earlier step seems to be totally
> bogus and only serves the purpose of confusing reviewers.  The
> function does not touch the pointer in any way---it does not use the
> pointer to return its findings, and it does not use an earlier
> findings to affect its behaviour by dereferencing it.

Yes, sorry. I'll fix that. This was a relic from an earlier version of
this that never escaped into the wild where I first tried printing out
this error printing it out near the report_path_error() codepath called
from checkout_paths().

I.e. I was trying to avoid printing out the "error: pathspec 'master'
did not match any file(s) known to git." error altogether. That's still
arguably a good direction, since we *know* "master" would have otherwise
matched a remote branch, so that's probably a more informative message
than falling back to checking out pathspecs and failing, and complaining
about there being no such pathspec.

But it was a pain to handle the various edge cases, e.g.:

    $ ./git --exec-path=$PWD checkout x y z
    error: pathspec 'x' did not match any file(s) known to git.
    error: pathspec 'y' did not match any file(s) known to git.
    error: pathspec 'z' did not match any file(s) known to git.

So I decided just to let checkout_paths() to its thing and then print
out an error about dwim branches if applicable if it failed.

> The dwim_remotes_matched is set by an earlier call to
> parse_branchname_arg(), which does gain an int* parameter in this
> series.  And that addition _does_ make sense.  That codepath is
> where the "do we have many remotes that could match, or none, or
> unique?" determination is made.

*nod*

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h
  2018-06-01  2:14                   ` Junio C Hamano
@ 2018-06-01  9:56                     ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01  9:56 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Thomas Gummerer, git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Eric Sunshine


On Fri, Jun 01 2018, Junio C Hamano wrote:

> Thomas Gummerer <t.gummerer@gmail.com> writes:
>
>> We seem to have plenty of structs defined in '.c' files, if they are
>> only needed there.  Its use also seems to be single purpose for the
>> callback data, so I'm a bit puzzled how having this in a header file
>> instead of the .c file would be helpful?
>>
>> I feel like having only the "public" part in the header file also
>> helps developers that are just looking for documentation of the
>> functions they are looking at, by having less things to go through,
>> that they wouldn't necessarily care about.
>
> Yup, sounds like a sensible criterion to choose between <*.h> and
> <*.c>.

Yes this makes sense. Thanks both. I misunderstood the idiom. Makes
sense in hindsight (& with both of your advices) only to put structs in
the *.h if it's actually used outside of that API. Will move this around
in v5.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout
  2018-06-01  4:06                 ` Junio C Hamano
@ 2018-06-01 19:43                   ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 19:43 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine


On Fri, Jun 01 2018, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> Assert that whenever there's a DWIM checkout that the index should be
>> clean afterwards, in addition to the correct branch being checked-out.
>> ...
>> So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
>> tests verifying current DWIM behavior of 'git checkout <branch>'",
>> 2013-04-21) to always assert that "status" is clean after we run
>> "checkout", that's being done with "-uno" because there's going to be
>> some untracked files related to the test itself which we don't care
>> about.
>
> It might not be absolutely necessary to state, but it would be
> helpful to say that you are assuming to start a checkout (DWIM or
> otherwise) from a clean state; without the assumption, the readers
> need to think for a few breaths why "the index should be clean" is
> true.
>
> The intention and the implementation of the change both mostly look
> good to me from a quick read.

Makes sense, will fix.

>>  test_expect_success 'setup' '
>>  	test_commit my_master &&
>>  	git init repo_a &&
>> @@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
>>  	test_might_fail git branch -D xyzzy &&
>>
>>  	test_must_fail git checkout xyzzy &&
>> +	status_uno_is_clean &&
>>  	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
>>  	test_branch master
>>  '
>> @@ -64,8 +71,10 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
>>  	test_might_fail git branch -D foo &&
>>
>>  	test_must_fail git checkout foo &&
>> +	status_uno_is_clean &&
>>  	test_must_fail git rev-parse --verify refs/heads/foo &&
>> -	test_branch master
>> +	test_branch master &&
>> +	status_uno_is_clean
>
> Hmm, what's the point of this second one?
>
>>  '

Slipped in, will remove. Thanks.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01  7:53                 ` Eric Sunshine
@ 2018-06-01 19:59                   ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 19:59 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer


On Fri, Jun 01 2018, Eric Sunshine wrote:

> On Thu, May 31, 2018 at 3:52 PM, Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>> As the "checkout" documentation describes:
>>
>>     If <branch> is not found but there does exist a tracking branch in
>>     exactly one remote (call it <remote>) with a matching name, treat
>>     as equivalent to [...] <remote>/<branch.
>>
>> This is a really useful feature, the problem is that when you another
>
> s/, the/. The/
> s/you/& add/

Thanks!

>> remote (e.g. a fork) git won't find a unique branch name anymore, and
>> will instead print this nondescript message:
>>
>>     $ git checkout master
>>     error: pathspec 'master' did not match any file(s) known to git
>>
>> Now it will, on my git.git checkout, print:
>>
>>     $ ./git --exec-path=$PWD checkout master
>>     error: pathspec 'master' did not match any file(s) known to git.
>>     hint: The argument '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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
>
> s/meant/& to/
>
>>     hint: instead of <name>?
>>
>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>> ---
>> diff --git a/builtin/checkout.c b/builtin/checkout.c
>> @@ -1269,6 +1270,16 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>> +               if (ret && dwim_remotes_matched > 1 &&
>> +                   advice_checkout_ambiguous_remote_branch_name)
>> +                       advise(_("The argument '%s' matched more than one remote tracking branch.\n"
>> +                                "We found %d remotes with a reference that matched. So we fell back\n"
>> +                                "on trying to resolve the argument as a path, but failed there too!\n"
>> +                                "\n"
>> +                                "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
>
> s/meant/& to/
>
>> +                                "instead of <name>?"),

Will rephrase to make this less confusing.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v5 0/8] ambiguous checkout UI & checkout.defaultRemote
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                     ` (8 more replies)
  2018-06-01 21:10                 ` [PATCH v5 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
                                   ` (7 subsequent siblings)
  8 siblings, 9 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

This v5 should address all the comments to v4. Thanks all! It's one
patch less because the struct isn't being moved around anymore.

tbdiff:
    
    1: 16d656ee3b ! 1: ab4529d9f5 checkout tests: index should be clean after dwim checkout
        @@ -29,6 +29,10 @@
             "checkout", that's being done with "-uno" because there's going to be
             some untracked files related to the test itself which we don't care
             about.
        +    
        +    In all these tests (DWIM or otherwise) we start with a clean index, so
        +    these tests are asserting that that's still the case after the
        +    "checkout", failed or otherwise.
             
             Then if we ever run into this sort of regression, either in the
             existing code or with a new feature, we'll know.
        @@ -65,12 +69,8 @@
          	test_must_fail git checkout foo &&
         +	status_uno_is_clean &&
          	test_must_fail git rev-parse --verify refs/heads/foo &&
        --	test_branch master
        -+	test_branch master &&
        -+	status_uno_is_clean
        - '
        - 
        - test_expect_success 'checkout of branch from a single remote succeeds #1' '
        + 	test_branch master
        + '
         @@
          	test_might_fail git branch -D bar &&
          
    2: 159cc0634b = 2: c8bbece403 checkout.h: wrap the arguments to unique_tracking_name()
    3: 3df4594e2d < -:  ------- checkout.[ch]: move struct declaration into *.h
    4: 35c6481208 < -:  ------- checkout.[ch]: introduce an *_INIT macro
    -:  ------- > 3: 4fc5ab27fa checkout.[ch]: introduce an *_INIT macro
    5: 69a834f010 ! 4: fbce6df584 checkout.[ch]: change "unique" member to "num_matches"
        @@ -11,6 +11,19 @@
         diff --git a/checkout.c b/checkout.c
         --- a/checkout.c
         +++ b/checkout.c
        +@@
        + 	/* const */ char *src_ref;
        + 	char *dst_ref;
        + 	struct object_id *dst_oid;
        +-	int unique;
        ++	int num_matches;
        + };
        + 
        +-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
        ++#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
        + 
        + static int check_tracking_name(struct remote *remote, void *cb_data)
        + {
         @@
          		free(query.dst);
          		return 0;
        @@ -31,20 +44,3 @@
          		return cb_data.dst_ref;
          	free(cb_data.dst_ref);
          	return NULL;
        -
        -diff --git a/checkout.h b/checkout.h
        ---- a/checkout.h
        -+++ b/checkout.h
        -@@
        - 	/* const */ char *src_ref;
        - 	char *dst_ref;
        - 	struct object_id *dst_oid;
        --	int unique;
        -+	int num_matches;
        - };
        - 
        --#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
        -+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
        - 
        - /*
        -  * Check if the branch name uniquely matches a branch name on a remote
    6: 13547824dc ! 5: 6e016d43d7 checkout: pass the "num_matches" up to callers
        @@ -11,16 +11,6 @@
         diff --git a/builtin/checkout.c b/builtin/checkout.c
         --- a/builtin/checkout.c
         +++ b/builtin/checkout.c
        -@@
        - }
        - 
        - static int checkout_paths(const struct checkout_opts *opts,
        --			  const char *revision)
        -+			  const char *revision,
        -+			  int *dwim_remotes_matched)
        - {
        - 	int pos;
        - 	struct checkout state = CHECKOUT_INIT;
         @@
          				int dwim_new_local_branch_ok,
          				struct branch_info *new_branch_info,
        @@ -59,16 +49,6 @@
          		argv += n;
          		argc -= n;
          	}
        -@@
        - 
        - 	UNLEAK(opts);
        - 	if (opts.patch_mode || opts.pathspec.nr)
        --		return checkout_paths(&opts, new_branch_info.name);
        -+		return checkout_paths(&opts, new_branch_info.name,
        -+				      &dwim_remotes_matched);
        - 	else
        - 		return checkout_branch(&opts, &new_branch_info);
        - }
         
         diff --git a/builtin/worktree.c b/builtin/worktree.c
         --- a/builtin/worktree.c
    7: 6895b5c903 ! 6: 07b11b133d builtin/checkout.c: use "ret" variable for return
        @@ -16,12 +16,10 @@
          
          	UNLEAK(opts);
         -	if (opts.patch_mode || opts.pathspec.nr)
        --		return checkout_paths(&opts, new_branch_info.name,
        --				      &dwim_remotes_matched);
        +-		return checkout_paths(&opts, new_branch_info.name);
         -	else
         +	if (opts.patch_mode || opts.pathspec.nr) {
        -+		int ret = checkout_paths(&opts, new_branch_info.name,
        -+					 &dwim_remotes_matched);
        ++		int ret = checkout_paths(&opts, new_branch_info.name);
         +		return ret;
         +	} else {
          		return checkout_branch(&opts, &new_branch_info);
    8: 5cfc0896e5 ! 7: 97e84f6e1c checkout: add advice for ambiguous "checkout <branch>"
        @@ -8,9 +8,9 @@
                 exactly one remote (call it <remote>) with a matching name, treat
                 as equivalent to [...] <remote>/<branch.
             
        -    This is a really useful feature, the problem is that when you another
        -    remote (e.g. a fork) git won't find a unique branch name anymore, and
        -    will instead print this nondescript message:
        +    This is a really useful feature. The problem is that when you and
        +    another remote (e.g. a fork) git won't find a unique branch name
        +    anymore, and will instead print this nondescript message:
             
                 $ git checkout master
                 error: pathspec 'master' did not match any file(s) known to git
        @@ -23,8 +23,10 @@
                 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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
        -        hint: instead of <name>?
        +        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>
             
             Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         
        @@ -90,17 +92,19 @@
          static const char * const checkout_usage[] = {
          	N_("git checkout [<options>] <branch>"),
         @@
        + 	UNLEAK(opts);
          	if (opts.patch_mode || opts.pathspec.nr) {
        - 		int ret = checkout_paths(&opts, new_branch_info.name,
        - 					 &dwim_remotes_matched);
        + 		int ret = checkout_paths(&opts, new_branch_info.name);
         +		if (ret && dwim_remotes_matched > 1 &&
         +		    advice_checkout_ambiguous_remote_branch_name)
         +			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
         +				 "We found %d remotes with a reference that matched. So we fell back\n"
         +				 "on trying to resolve the argument as a path, but failed there too!\n"
         +				 "\n"
        -+				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
        -+				 "instead of <name>?"),
        ++				 "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 checkout --track origin/<name>"),
         +			       argv[0],
         +			       dwim_remotes_matched);
          		return ret;
        @@ -111,7 +115,7 @@
         --- a/t/t2024-checkout-dwim.sh
         +++ b/t/t2024-checkout-dwim.sh
         @@
        - 	status_uno_is_clean
        + 	test_branch master
          '
          
         +test_expect_success 'checkout of branch from multiple remotes fails with advice' '
    9: fad1df1edd ! 8: a5cc070ebf checkout & worktree: introduce checkout.defaultRemote
        @@ -57,12 +57,14 @@
                 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: Perhaps you meant fully qualify the branch name? E.g. origin/<name>
        -        hint: instead of <name>?
        +        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: If you'd like to always have checkouts of 'master' prefer one remote,
        -        hint: e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin
        -        hint: in your config. See the 'git-config' manual page for details.
        +        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
        @@ -128,7 +130,7 @@
         +one for the purposes of disambiguation, even if the `<branch>` isn't
         +unique across all remotes. Set it to
         +e.g. `checkout.defaultRemote=origin` to always checkout remote
        -+branches from there if `<branch> is ambiguous but exists on the
        ++branches from there if `<branch>` is ambiguous but exists on the
         +'origin' remote. See also `checkout.defaultRemote` in
         +linkgit:git-config[1].
         ++
        @@ -148,7 +150,7 @@
         +one for the purposes of disambiguation, even if the `<branch>` isn't
         +unique across all remotes. Set it to
         +e.g. `checkout.defaultRemote=origin` to always checkout remote
        -+branches from there if `<branch> is ambiguous but exists on the
        ++branches from there if `<branch>` is ambiguous but exists on the
         +'origin' remote. See also `checkout.defaultRemote` in
         +linkgit:git-config[1].
         ++
        @@ -173,22 +175,18 @@
          	 *   (c) Otherwise, if "--" is present, treat it like case (1).
          	 *
         @@
        - 				 "on trying to resolve the argument as a path, but failed there too!\n"
        + 				 "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"
        - 				 "Perhaps you meant fully qualify the branch name? E.g. origin/<name>\n"
        --				 "instead of <name>?"),
        -+				 "instead of <name>?\n"
        +-				 "    git checkout --track origin/<name>"),
        ++				 "    git checkout --track origin/<name>\n"
         +				 "\n"
        -+				 "If you'd like to always have checkouts of '%s' prefer one remote,\n"
        -+				 "e.g. the 'origin' remote, consider setting checkout.defaultRemote=origin\n"
        -+				 "in your config. See the 'git-config' manual page for details."),
        ++				 "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."),
          			       argv[0],
        --			       dwim_remotes_matched);
        -+			       dwim_remotes_matched,
        -+			       argv[0]);
        + 			       dwim_remotes_matched);
          		return ret;
        - 	} else {
        - 		return checkout_branch(&opts, &new_branch_info);
         
         diff --git a/checkout.c b/checkout.c
         --- a/checkout.c
        @@ -198,6 +196,19 @@
          #include "refspec.h"
          #include "checkout.h"
         +#include "config.h"
        + 
        + struct tracking_name_data {
        + 	/* const */ char *src_ref;
        + 	char *dst_ref;
        + 	struct object_id *dst_oid;
        + 	int num_matches;
        ++	const char *default_remote;
        ++	char *default_dst_ref;
        ++	struct object_id *default_dst_oid;
        + };
        + 
        +-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
        ++#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
          
          static int check_tracking_name(struct remote *remote, void *cb_data)
          {
        @@ -243,31 +254,21 @@
          	return NULL;
          }
         
        -diff --git a/checkout.h b/checkout.h
        ---- a/checkout.h
        -+++ b/checkout.h
        -@@
        - 	char *dst_ref;
        - 	struct object_id *dst_oid;
        - 	int num_matches;
        -+	const char *default_remote;
        -+	char *default_dst_ref;
        -+	struct object_id *default_dst_oid;
        - };
        - 
        --#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
        -+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
        - 
        - /*
        -  * Check if the branch name uniquely matches a branch name on a remote
        -
         diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
         --- a/t/t2024-checkout-dwim.sh
         +++ b/t/t2024-checkout-dwim.sh
         @@
        - 	test_i18ngrep ! "^hint: " stderr
        - '
        - 
        + 		checkout foo 2>stderr &&
        + 	test_branch master &&
        + 	status_uno_is_clean &&
        +-	test_i18ngrep ! "^hint: " stderr
        ++	test_i18ngrep ! "^hint: " stderr &&
        ++	# Make sure the likes of checkout -p don not print this hint
        ++	git checkout -p foo 2>stderr &&
        ++	test_i18ngrep ! "^hint: " stderr &&
        ++	status_uno_is_clean
        ++'
        ++
         +test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
         +	git checkout -B master &&
         +	status_uno_is_clean &&
        @@ -278,11 +279,9 @@
         +	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
         --- a/t/t2025-worktree-add.sh

Ævar Arnfjörð Bjarmason (8):
  checkout tests: index should be clean after dwim checkout
  checkout.h: wrap the arguments to unique_tracking_name()
  checkout.[ch]: introduce an *_INIT macro
  checkout.[ch]: change "unique" member to "num_matches"
  checkout: pass the "num_matches" up to callers
  builtin/checkout.c: use "ret" variable for return
  checkout: add advice for ambiguous "checkout <branch>"
  checkout & worktree: introduce checkout.defaultRemote

 Documentation/config.txt       | 26 +++++++++++++++
 Documentation/git-checkout.txt |  9 ++++++
 Documentation/git-worktree.txt |  9 ++++++
 advice.c                       |  2 ++
 advice.h                       |  1 +
 builtin/checkout.c             | 41 ++++++++++++++++++-----
 builtin/worktree.c             |  4 +--
 checkout.c                     | 37 ++++++++++++++++++---
 checkout.h                     |  4 ++-
 t/t2024-checkout-dwim.sh       | 59 ++++++++++++++++++++++++++++++++++
 t/t2025-worktree-add.sh        | 21 ++++++++++++
 11 files changed, 197 insertions(+), 16 deletions(-)

-- 
2.17.0.290.gded63e768a


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v5 1/8] checkout tests: index should be clean after dwim checkout
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
                                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Assert that whenever there's a DWIM checkout that the index should be
clean afterwards, in addition to the correct branch being checked-out.

The way the DWIM checkout code in checkout.[ch] works is by looping
over all remotes, and for each remote trying to find if a given
reference name only exists on that remote, or if it exists anywhere
else.

This is done by starting out with a `unique = 1` tracking variable in
a struct shared by the entire loop, which will get set to `0` if the
data reference is not unique.

Thus if we find a match we know the dst_oid member of
tracking_name_data must be correct, since it's associated with the
only reference on the only remote that could have matched our query.

But if there was ever a mismatch there for some reason we might end up
with the correct branch checked out, but at the wrong oid, which would
show whatever the difference between the two staged in the
index (checkout branch A, stage changes from the state of branch B).

So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
tests verifying current DWIM behavior of 'git checkout <branch>'",
2013-04-21) to always assert that "status" is clean after we run
"checkout", that's being done with "-uno" because there's going to be
some untracked files related to the test itself which we don't care
about.

In all these tests (DWIM or otherwise) we start with a clean index, so
these tests are asserting that that's still the case after the
"checkout", failed or otherwise.

Then if we ever run into this sort of regression, either in the
existing code or with a new feature, we'll know.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t2024-checkout-dwim.sh | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..ed32828105 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -23,6 +23,12 @@ test_branch_upstream () {
 	test_cmp expect.upstream actual.upstream
 }
 
+status_uno_is_clean() {
+	>status.expect &&
+	git status -uno --porcelain >status.actual &&
+	test_cmp status.expect status.actual
+}
+
 test_expect_success 'setup' '
 	test_commit my_master &&
 	git init repo_a &&
@@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
 	test_might_fail git branch -D xyzzy &&
 
 	test_must_fail git checkout xyzzy &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
 	test_branch master
 '
@@ -64,6 +71,7 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_might_fail git branch -D foo &&
 
 	test_must_fail git checkout foo &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/foo &&
 	test_branch master
 '
@@ -73,6 +81,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	test_might_fail git branch -D bar &&
 
 	git checkout bar &&
+	status_uno_is_clean &&
 	test_branch bar &&
 	test_cmp_rev remotes/repo_a/bar HEAD &&
 	test_branch_upstream bar repo_a bar
@@ -83,6 +92,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 	test_might_fail git branch -D baz &&
 
 	git checkout baz &&
+	status_uno_is_clean &&
 	test_branch baz &&
 	test_cmp_rev remotes/other_b/baz HEAD &&
 	test_branch_upstream baz repo_b baz
@@ -90,6 +100,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 
 test_expect_success '--no-guess suppresses branch auto-vivification' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout --no-guess bar &&
@@ -99,6 +110,7 @@ test_expect_success '--no-guess suppresses branch auto-vivification' '
 
 test_expect_success 'setup more remotes with unconventional refspecs' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	git init repo_c &&
 	(
 		cd repo_c &&
@@ -128,27 +140,33 @@ test_expect_success 'setup more remotes with unconventional refspecs' '
 
 test_expect_success 'checkout of branch from multiple remotes fails #2' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout bar &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/bar &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from multiple remotes fails #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D baz &&
 
 	test_must_fail git checkout baz &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/baz &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from a single remote succeeds #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	git checkout spam &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -156,9 +174,11 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' '
 
 test_expect_success 'checkout of branch from a single remote succeeds #4' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D eggs &&
 
 	git checkout eggs &&
+	status_uno_is_clean &&
 	test_branch eggs &&
 	test_cmp_rev refs/repo_d/eggs HEAD &&
 	test_branch_upstream eggs repo_d eggs
@@ -166,32 +186,38 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' '
 
 test_expect_success 'checkout of branch with a file having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	test_must_fail git checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch with a file in subdir having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	mkdir sub &&
 	mv spam sub/spam &&
 	test_must_fail git -C sub checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	git checkout spam -- &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -200,6 +226,7 @@ test_expect_success 'checkout <branch> -- succeeds, even if a file with the same
 test_expect_success 'loosely defined local base branch is reported correctly' '
 
 	git checkout master &&
+	status_uno_is_clean &&
 	git branch strict &&
 	git branch loose &&
 	git commit --allow-empty -m "a bit more" &&
@@ -210,7 +237,9 @@ test_expect_success 'loosely defined local base branch is reported correctly' '
 	test_config branch.loose.merge master &&
 
 	git checkout strict | sed -e "s/strict/BRANCHNAME/g" >expect &&
+	status_uno_is_clean &&
 	git checkout loose | sed -e "s/loose/BRANCHNAME/g" >actual &&
+	status_uno_is_clean &&
 
 	test_cmp expect actual
 '
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 2/8] checkout.h: wrap the arguments to unique_tracking_name()
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
                                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

The line was too long already, and will be longer still when a later
change adds another argument.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/checkout.h b/checkout.h
index 9980711179..4cd4cd1c23 100644
--- a/checkout.h
+++ b/checkout.h
@@ -8,6 +8,7 @@
  * tracking branch.  Return the name of the remote if such a branch
  * exists, NULL otherwise.
  */
-extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+extern const char *unique_tracking_name(const char *name,
+					struct object_id *oid);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (2 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 22:40                   ` Eric Sunshine
  2018-06-01 21:10                 ` [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
                                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Add an *_INIT macro for the tracking_name_data similar to what exists
elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
will make it more idiomatic in later changes to add more fields to the
struct & its initialization macro.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/checkout.c b/checkout.c
index bdefc888ba..80e430cda8 100644
--- a/checkout.c
+++ b/checkout.c
@@ -10,6 +10,8 @@ struct tracking_name_data {
 	int unique;
 };
 
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
 	struct tracking_name_data *cb = cb_data;
@@ -32,7 +34,7 @@ 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 };
+	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches"
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (3 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 22:41                   ` Eric Sunshine
  2018-06-01 21:10                 ` [PATCH v5 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
                                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Internally track how many matches we find in the check_tracking_name()
callback. Nothing uses this now, but it will be made use of in a later
change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/checkout.c b/checkout.c
index 80e430cda8..7662a39a62 100644
--- a/checkout.c
+++ b/checkout.c
@@ -7,10 +7,10 @@ struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
-	int unique;
+	int num_matches;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -23,9 +23,9 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		free(query.dst);
 		return 0;
 	}
+	cb->num_matches++;
 	if (cb->dst_ref) {
 		free(query.dst);
-		cb->unique = 0;
 		return 0;
 	}
 	cb->dst_ref = query.dst;
@@ -39,7 +39,7 @@ const char *unique_tracking_name(const char *name, struct object_id *oid)
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	free(cb_data.src_ref);
-	if (cb_data.unique)
+	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
 	free(cb_data.dst_ref);
 	return NULL;
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 5/8] checkout: pass the "num_matches" up to callers
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (4 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
                                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Pass the previously added "num_matches" struct value up to the callers
of unique_tracking_name(). This will allow callers to optionally print
better error messages in a later change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 10 +++++++---
 builtin/worktree.c |  4 ++--
 checkout.c         |  5 ++++-
 checkout.h         |  3 ++-
 4 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2e1d2376d2..72457fb7d5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -878,7 +878,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
 				struct checkout_opts *opts,
-				struct object_id *rev)
+				struct object_id *rev,
+				int *dwim_remotes_matched)
 {
 	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
@@ -972,7 +973,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 			recover_with_dwim = 0;
 
 		if (recover_with_dwim) {
-			const char *remote = unique_tracking_name(arg, rev);
+			const char *remote = unique_tracking_name(arg, rev,
+								  dwim_remotes_matched);
 			if (remote) {
 				*new_branch = arg;
 				arg = remote;
@@ -1109,6 +1111,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch = 1;
+	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
 		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1219,7 +1222,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts.track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts.new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev);
+					     &new_branch_info, &opts, &rev,
+					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5c7d2bb180..a763dbdccb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
 	if (guess_remote) {
 		struct object_id oid;
 		const char *remote =
-			unique_tracking_name(*new_branch, &oid);
+			unique_tracking_name(*new_branch, &oid, NULL);
 		return remote;
 	}
 	return NULL;
@@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)
 
 		commit = lookup_commit_reference_by_name(branch);
 		if (!commit) {
-			remote = unique_tracking_name(branch, &oid);
+			remote = unique_tracking_name(branch, &oid, NULL);
 			if (remote) {
 				new_branch = branch;
 				branch = remote;
diff --git a/checkout.c b/checkout.c
index 7662a39a62..ee3a7e9c05 100644
--- a/checkout.c
+++ b/checkout.c
@@ -32,12 +32,15 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	return 0;
 }
 
-const char *unique_tracking_name(const char *name, struct object_id *oid)
+const char *unique_tracking_name(const char *name, struct object_id *oid,
+				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
+	if (dwim_remotes_matched)
+		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
 	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
diff --git a/checkout.h b/checkout.h
index 4cd4cd1c23..6b2073310c 100644
--- a/checkout.h
+++ b/checkout.h
@@ -9,6 +9,7 @@
  * exists, NULL otherwise.
  */
 extern const char *unique_tracking_name(const char *name,
-					struct object_id *oid);
+					struct object_id *oid,
+					int *dwim_remotes_matched);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 6/8] builtin/checkout.c: use "ret" variable for return
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (5 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
  2018-06-01 21:10                 ` [PATCH v5 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

There is no point in doing this right now, but in later change the
"ret" variable will be inspected. This change makes that meaningful
change smaller.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 72457fb7d5..8c93c55cbc 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1265,8 +1265,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr)
-		return checkout_paths(&opts, new_branch_info.name);
-	else
+	if (opts.patch_mode || opts.pathspec.nr) {
+		int ret = checkout_paths(&opts, new_branch_info.name);
+		return ret;
+	} else {
 		return checkout_branch(&opts, &new_branch_info);
+	}
 }
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>"
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (6 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  2018-06-01 22:52                   ` Eric Sunshine
  2018-06-01 21:10                 ` [PATCH v5 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

As the "checkout" documentation describes:

    If <branch> is not found but there does exist a tracking branch in
    exactly one remote (call it <remote>) with a matching name, treat
    as equivalent to [...] <remote>/<branch.

This is a really useful feature. The problem is that when you and
another remote (e.g. a fork) git won't find a unique branch name
anymore, and will instead print this nondescript message:

    $ git checkout master
    error: pathspec 'master' did not match any file(s) known to git

Now it will, on my git.git checkout, print:

    $ ./git --exec-path=$PWD checkout master
    error: pathspec 'master' did not match any file(s) known to git.
    hint: The argument '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>

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/config.txt |  7 +++++++
 advice.c                 |  2 ++
 advice.h                 |  1 +
 builtin/checkout.c       | 13 +++++++++++++
 t/t2024-checkout-dwim.sh | 14 ++++++++++++++
 5 files changed, 37 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index ab641bf5a9..dfc0413a84 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -344,6 +344,13 @@ advice.*::
 		Advice shown when you used linkgit:git-checkout[1] to
 		move to the detach HEAD state, to instruct how to create
 		a local branch after the fact.
+	checkoutAmbiguousRemoteBranchName::
+		Advice shown when the argument to
+		linkgit:git-checkout[1] ambiguously resolves to a
+		remote tracking branch on more than one remote in
+		situations where an unambiguous argument would have
+		otherwise caused a remote-tracking branch to be
+		checked out.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
diff --git a/advice.c b/advice.c
index 370a56d054..75e7dede90 100644
--- a/advice.c
+++ b/advice.c
@@ -21,6 +21,7 @@ int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
+int advice_checkout_ambiguous_remote_branch_name = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -72,6 +73,7 @@ static struct {
 	{ "ignoredhook", &advice_ignored_hook },
 	{ "waitingforeditor", &advice_waiting_for_editor },
 	{ "graftfiledeprecated", &advice_graft_file_deprecated },
+	{ "checkoutambiguousremotebranchname", &advice_checkout_ambiguous_remote_branch_name },
 
 	/* make this an alias for backward compatibility */
 	{ "pushnonfastforward", &advice_push_update_rejected }
diff --git a/advice.h b/advice.h
index 9f5064e82a..4d11d51d43 100644
--- a/advice.h
+++ b/advice.h
@@ -22,6 +22,7 @@ extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
+extern int advice_checkout_ambiguous_remote_branch_name;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8c93c55cbc..4dfb8f1535 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -22,6 +22,7 @@
 #include "resolve-undo.h"
 #include "submodule-config.h"
 #include "submodule.h"
+#include "advice.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
@@ -1267,6 +1268,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	UNLEAK(opts);
 	if (opts.patch_mode || opts.pathspec.nr) {
 		int ret = checkout_paths(&opts, new_branch_info.name);
+		if (ret && dwim_remotes_matched > 1 &&
+		    advice_checkout_ambiguous_remote_branch_name)
+			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
+				 "We found %d remotes with a reference that matched. So we fell back\n"
+				 "on trying to resolve the argument as a path, but failed there too!\n"
+				 "\n"
+				 "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 checkout --track origin/<name>"),
+			       argv[0],
+			       dwim_remotes_matched);
 		return ret;
 	} else {
 		return checkout_branch(&opts, &new_branch_info);
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index ed32828105..fef263a858 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -76,6 +76,20 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_branch master
 '
 
+test_expect_success 'checkout of branch from multiple remotes fails with advice' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+	test_must_fail git checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep "^hint: " stderr &&
+	test_must_fail git -c advice.checkoutAmbiguousRemoteBranchName=false \
+		checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep ! "^hint: " stderr
+'
+
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	git checkout -B master &&
 	test_might_fail git branch -D bar &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v5 8/8] checkout & worktree: introduce checkout.defaultRemote
  2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
                                   ` (7 preceding siblings ...)
  2018-06-01 21:10                 ` [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-06-01 21:10                 ` Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-01 21:10 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

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 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:

    (
        cd /tmp &&
        rm -rf 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 (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: The argument '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 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       | 21 ++++++++++++++++++++-
 Documentation/git-checkout.txt |  9 +++++++++
 Documentation/git-worktree.txt |  9 +++++++++
 builtin/checkout.c             | 12 +++++++++---
 checkout.c                     | 26 ++++++++++++++++++++++++--
 t/t2024-checkout-dwim.sh       | 18 +++++++++++++++++-
 t/t2025-worktree-add.sh        | 21 +++++++++++++++++++++
 7 files changed, 109 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index dfc0413a84..aef2769211 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -350,7 +350,10 @@ advice.*::
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
-		checked out.
+		checked out. See the `checkout.defaultRemote`
+		configuration variable for how to set a given remote
+		to used by default in some situations where this
+		advice would be printed.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
@@ -1105,6 +1108,22 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.defaultRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. This setting allows for setting the name of a
+	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' refers to a
+remote branch. This setting might be used for other checkout-like
+commands or functionality in the future.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..9db02928c4 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,15 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` 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 afc6576a14..9c26be40f4 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch
 (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4dfb8f1535..b78481dead 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -912,8 +912,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.defaultRemote), 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).
 	 *
@@ -1277,7 +1279,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 				 "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 checkout --track origin/<name>"),
+				 "    git checkout --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."),
 			       argv[0],
 			       dwim_remotes_matched);
 		return ret;
diff --git a/checkout.c b/checkout.c
index ee3a7e9c05..c72e9f9773 100644
--- a/checkout.c
+++ b/checkout.c
@@ -2,15 +2,19 @@
 #include "remote.h"
 #include "refspec.h"
 #include "checkout.h"
+#include "config.h"
 
 struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
+	const char *default_remote;
+	char *default_dst_ref;
+	struct object_id *default_dst_oid;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -24,6 +28,12 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		return 0;
 	}
 	cb->num_matches++;
+	if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
+		struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
+		cb->default_dst_ref = xstrdup(query.dst);
+		oidcpy(dst, cb->dst_oid);
+		cb->default_dst_oid = dst;
+	}
 	if (cb->dst_ref) {
 		free(query.dst);
 		return 0;
@@ -36,14 +46,26 @@ const char *unique_tracking_name(const char *name, struct object_id *oid,
 				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
+	const char *default_remote = NULL;
+	if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
+		cb_data.default_remote = default_remote;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
-	if (cb_data.num_matches == 1)
+	free((char *)default_remote);
+	if (cb_data.num_matches == 1) {
+		free(cb_data.default_dst_ref);
+		free(cb_data.default_dst_oid);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.default_dst_ref) {
+		oidcpy(oid, cb_data.default_dst_oid);
+		free(cb_data.default_dst_oid);
+		return cb_data.default_dst_ref;
+	}
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index fef263a858..1495c248a7 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -87,7 +87,23 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
 		checkout foo 2>stderr &&
 	test_branch master &&
 	status_uno_is_clean &&
-	test_i18ngrep ! "^hint: " stderr
+	test_i18ngrep ! "^hint: " stderr &&
+	# Make sure the likes of checkout -p don not print this hint
+	git checkout -p foo 2>stderr &&
+	test_i18ngrep ! "^hint: " stderr &&
+	status_uno_is_clean
+'
+
+test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
+	git checkout -B master &&
+	status_uno_is_clean &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.defaultRemote=repo_a checkout foo &&
+	status_uno_is_clean &&
+	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' '
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index d2e49f7632..be6e093142 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -402,6 +402,27 @@ test_expect_success '"add" <path> <branch> dwims' '
 	)
 '
 
+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 &&
+		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+		>status.expect &&
+		git status -uno --porcelain >status.actual &&
+		test_cmp status.expect status.actual
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_upstream foo &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+	)
+'
+
 test_expect_success 'git worktree add does not match remote' '
 	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro
  2018-06-01 21:10                 ` [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-06-01 22:40                   ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-06-01 22:40 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Fri, Jun 1, 2018 at 5:10 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> checkout.[ch]: introduce an *_INIT macro

   checkout.c: introduce...

> Add an *_INIT macro for the tracking_name_data similar to what exists
> elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
> will make it more idiomatic in later changes to add more fields to the
> struct & its initialization macro.
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches"
  2018-06-01 21:10                 ` [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
@ 2018-06-01 22:41                   ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-06-01 22:41 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Fri, Jun 1, 2018 at 5:10 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> checkout.[ch]: change "unique" member to "num_matches"

    checkout.c: change...

> Internally track how many matches we find in the check_tracking_name()
> callback. Nothing uses this now, but it will be made use of in a later
> change.
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01 21:10                 ` [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-06-01 22:52                   ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-06-01 22:52 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Fri, Jun 1, 2018 at 5:10 PM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> As the "checkout" documentation describes:
>
>     If <branch> is not found but there does exist a tracking branch in
>     exactly one remote (call it <remote>) with a matching name, treat
>     as equivalent to [...] <remote>/<branch.
>
> This is a really useful feature. The problem is that when you and

s/and/add/

> another remote (e.g. a fork) git won't find a unique branch name

Missing comma: s/)/),/

> anymore, and will instead print this nondescript message:

Perhaps s/nondescript/unhelpful/ ?

>     $ git checkout master
>     error: pathspec 'master' did not match any file(s) known to git
>
> Now it will, on my git.git checkout, print:
>
>     $ ./git --exec-path=$PWD checkout master
>     error: pathspec 'master' did not match any file(s) known to git.
>     hint: The argument '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'

Missing commas: s/on e.g. 'origin'/on, e.g. 'origin',/

>     hint: you can do so by fully-qualifying the name with the --track option:

s/fully-qualifying/fully qualifying/

>     hint:
>     hint:     git checkout --track origin/<name>
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Aside from the s/and/add/ botch, all of the above are tiny nits which
don't actually impact the meaning of the commit message, thus not
really important.

> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> @@ -1267,6 +1268,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
> +               if (ret && dwim_remotes_matched > 1 &&
> +                   advice_checkout_ambiguous_remote_branch_name)
> +                       advise(_("The argument '%s' matched more than one remote tracking branch.\n"

You could drop "The argument" prefix, without hurting the meaning at
all, in order to gain a bit more horizontal space for the '%s'
interpolation. (Not worth a re-roll.)

> +                                "We found %d remotes with a reference that matched. So we fell back\n"
> +                                "on trying to resolve the argument as a path, but failed there too!\n"
> +                                "\n"
> +                                "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 checkout --track origin/<name>"),
> +                              argv[0],
> +                              dwim_remotes_matched);

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v6 0/8] ambiguous checkout UI & checkout.defaultRemote
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 " Ævar Arnfjörð Bjarmason
                                       ` (8 more replies)
  2018-06-02 11:50                   ` [PATCH v6 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
                                     ` (7 subsequent siblings)
  8 siblings, 9 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Typo & grammar fixes suggested by Eric Sunshine. tbdiff from v5:
    
    1: ab4529d9f5 = 1: ab4529d9f5 checkout tests: index should be clean after dwim checkout
    2: c8bbece403 = 2: c8bbece403 checkout.h: wrap the arguments to unique_tracking_name()
    3: 4fc5ab27fa ! 3: 881fe63f4f checkout.c: introduce an *_INIT macro
        @@ -1,6 +1,6 @@
         Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         
        -    checkout.[ch]: introduce an *_INIT macro
        +    checkout.c: introduce an *_INIT macro
             
             Add an *_INIT macro for the tracking_name_data similar to what exists
             elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
    4: fbce6df584 ! 4: 72ddaeddd3 checkout.c]: change "unique" member to "num_matches"
        @@ -1,6 +1,6 @@
         Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         
        -    checkout.[ch]: change "unique" member to "num_matches"
        +    checkout.c]: change "unique" member to "num_matches"
             
             Internally track how many matches we find in the check_tracking_name()
             callback. Nothing uses this now, but it will be made use of in a later
    5: 6e016d43d7 = 5: 5e8c82680b checkout: pass the "num_matches" up to callers
    6: 07b11b133d = 6: 07e667f80a builtin/checkout.c: use "ret" variable for return
    7: 97e84f6e1c ! 7: 0a148182e6 checkout: add advice for ambiguous "checkout <branch>"
        @@ -8,9 +8,9 @@
                 exactly one remote (call it <remote>) with a matching name, treat
                 as equivalent to [...] <remote>/<branch.
             
        -    This is a really useful feature. The problem is that when you and
        -    another remote (e.g. a fork) git won't find a unique branch name
        -    anymore, and will instead print this nondescript message:
        +    This is a really useful feature. The problem is that when you add
        +    another remote (e.g. a fork), git won't find a unique branch name
        +    anymore, and will instead print this unhelpful message:
             
                 $ git checkout master
                 error: pathspec 'master' did not match any file(s) known to git
        @@ -19,12 +19,12 @@
             
                 $ ./git --exec-path=$PWD checkout master
                 error: pathspec 'master' did not match any file(s) known to git.
        -        hint: The argument 'master' matched more than one remote tracking branch.
        +        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: 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>
             
        @@ -97,12 +97,12 @@
          		int ret = checkout_paths(&opts, new_branch_info.name);
         +		if (ret && dwim_remotes_matched > 1 &&
         +		    advice_checkout_ambiguous_remote_branch_name)
        -+			advise(_("The argument '%s' matched more than one remote tracking branch.\n"
        ++			advise(_("'%s' matched more than one remote tracking branch.\n"
         +				 "We found %d remotes with a reference that matched. So we fell back\n"
         +				 "on trying to resolve the argument as a path, but failed there too!\n"
         +				 "\n"
        -+				 "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"
        ++				 "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 checkout --track origin/<name>"),
         +			       argv[0],
    8: a5cc070ebf ! 8: f3a52a26a2 checkout & worktree: introduce checkout.defaultRemote
        @@ -175,8 +175,8 @@
          	 *   (c) Otherwise, if "--" is present, treat it like case (1).
          	 *
         @@
        - 				 "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"
        + 				 "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 checkout --track origin/<name>"),
         +				 "    git checkout --track origin/<name>\n"

Ævar Arnfjörð Bjarmason (8):
  checkout tests: index should be clean after dwim checkout
  checkout.h: wrap the arguments to unique_tracking_name()
  checkout.c: introduce an *_INIT macro
  checkout.c]: change "unique" member to "num_matches"
  checkout: pass the "num_matches" up to callers
  builtin/checkout.c: use "ret" variable for return
  checkout: add advice for ambiguous "checkout <branch>"
  checkout & worktree: introduce checkout.defaultRemote

 Documentation/config.txt       | 26 +++++++++++++++
 Documentation/git-checkout.txt |  9 ++++++
 Documentation/git-worktree.txt |  9 ++++++
 advice.c                       |  2 ++
 advice.h                       |  1 +
 builtin/checkout.c             | 41 ++++++++++++++++++-----
 builtin/worktree.c             |  4 +--
 checkout.c                     | 37 ++++++++++++++++++---
 checkout.h                     |  4 ++-
 t/t2024-checkout-dwim.sh       | 59 ++++++++++++++++++++++++++++++++++
 t/t2025-worktree-add.sh        | 21 ++++++++++++
 11 files changed, 197 insertions(+), 16 deletions(-)

-- 
2.17.0.290.gded63e768a


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v6 1/8] checkout tests: index should be clean after dwim checkout
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
                                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Assert that whenever there's a DWIM checkout that the index should be
clean afterwards, in addition to the correct branch being checked-out.

The way the DWIM checkout code in checkout.[ch] works is by looping
over all remotes, and for each remote trying to find if a given
reference name only exists on that remote, or if it exists anywhere
else.

This is done by starting out with a `unique = 1` tracking variable in
a struct shared by the entire loop, which will get set to `0` if the
data reference is not unique.

Thus if we find a match we know the dst_oid member of
tracking_name_data must be correct, since it's associated with the
only reference on the only remote that could have matched our query.

But if there was ever a mismatch there for some reason we might end up
with the correct branch checked out, but at the wrong oid, which would
show whatever the difference between the two staged in the
index (checkout branch A, stage changes from the state of branch B).

So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
tests verifying current DWIM behavior of 'git checkout <branch>'",
2013-04-21) to always assert that "status" is clean after we run
"checkout", that's being done with "-uno" because there's going to be
some untracked files related to the test itself which we don't care
about.

In all these tests (DWIM or otherwise) we start with a clean index, so
these tests are asserting that that's still the case after the
"checkout", failed or otherwise.

Then if we ever run into this sort of regression, either in the
existing code or with a new feature, we'll know.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t2024-checkout-dwim.sh | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..ed32828105 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -23,6 +23,12 @@ test_branch_upstream () {
 	test_cmp expect.upstream actual.upstream
 }
 
+status_uno_is_clean() {
+	>status.expect &&
+	git status -uno --porcelain >status.actual &&
+	test_cmp status.expect status.actual
+}
+
 test_expect_success 'setup' '
 	test_commit my_master &&
 	git init repo_a &&
@@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
 	test_might_fail git branch -D xyzzy &&
 
 	test_must_fail git checkout xyzzy &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
 	test_branch master
 '
@@ -64,6 +71,7 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_might_fail git branch -D foo &&
 
 	test_must_fail git checkout foo &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/foo &&
 	test_branch master
 '
@@ -73,6 +81,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	test_might_fail git branch -D bar &&
 
 	git checkout bar &&
+	status_uno_is_clean &&
 	test_branch bar &&
 	test_cmp_rev remotes/repo_a/bar HEAD &&
 	test_branch_upstream bar repo_a bar
@@ -83,6 +92,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 	test_might_fail git branch -D baz &&
 
 	git checkout baz &&
+	status_uno_is_clean &&
 	test_branch baz &&
 	test_cmp_rev remotes/other_b/baz HEAD &&
 	test_branch_upstream baz repo_b baz
@@ -90,6 +100,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 
 test_expect_success '--no-guess suppresses branch auto-vivification' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout --no-guess bar &&
@@ -99,6 +110,7 @@ test_expect_success '--no-guess suppresses branch auto-vivification' '
 
 test_expect_success 'setup more remotes with unconventional refspecs' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	git init repo_c &&
 	(
 		cd repo_c &&
@@ -128,27 +140,33 @@ test_expect_success 'setup more remotes with unconventional refspecs' '
 
 test_expect_success 'checkout of branch from multiple remotes fails #2' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout bar &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/bar &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from multiple remotes fails #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D baz &&
 
 	test_must_fail git checkout baz &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/baz &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from a single remote succeeds #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	git checkout spam &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -156,9 +174,11 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' '
 
 test_expect_success 'checkout of branch from a single remote succeeds #4' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D eggs &&
 
 	git checkout eggs &&
+	status_uno_is_clean &&
 	test_branch eggs &&
 	test_cmp_rev refs/repo_d/eggs HEAD &&
 	test_branch_upstream eggs repo_d eggs
@@ -166,32 +186,38 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' '
 
 test_expect_success 'checkout of branch with a file having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	test_must_fail git checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch with a file in subdir having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	mkdir sub &&
 	mv spam sub/spam &&
 	test_must_fail git -C sub checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	git checkout spam -- &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -200,6 +226,7 @@ test_expect_success 'checkout <branch> -- succeeds, even if a file with the same
 test_expect_success 'loosely defined local base branch is reported correctly' '
 
 	git checkout master &&
+	status_uno_is_clean &&
 	git branch strict &&
 	git branch loose &&
 	git commit --allow-empty -m "a bit more" &&
@@ -210,7 +237,9 @@ test_expect_success 'loosely defined local base branch is reported correctly' '
 	test_config branch.loose.merge master &&
 
 	git checkout strict | sed -e "s/strict/BRANCHNAME/g" >expect &&
+	status_uno_is_clean &&
 	git checkout loose | sed -e "s/loose/BRANCHNAME/g" >actual &&
+	status_uno_is_clean &&
 
 	test_cmp expect actual
 '
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 2/8] checkout.h: wrap the arguments to unique_tracking_name()
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
                                     ` (5 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

The line was too long already, and will be longer still when a later
change adds another argument.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/checkout.h b/checkout.h
index 9980711179..4cd4cd1c23 100644
--- a/checkout.h
+++ b/checkout.h
@@ -8,6 +8,7 @@
  * tracking branch.  Return the name of the remote if such a branch
  * exists, NULL otherwise.
  */
-extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+extern const char *unique_tracking_name(const char *name,
+					struct object_id *oid);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 3/8] checkout.c: introduce an *_INIT macro
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (2 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 4/8] checkout.c]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
                                     ` (4 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Add an *_INIT macro for the tracking_name_data similar to what exists
elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
will make it more idiomatic in later changes to add more fields to the
struct & its initialization macro.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/checkout.c b/checkout.c
index bdefc888ba..80e430cda8 100644
--- a/checkout.c
+++ b/checkout.c
@@ -10,6 +10,8 @@ struct tracking_name_data {
 	int unique;
 };
 
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
 	struct tracking_name_data *cb = cb_data;
@@ -32,7 +34,7 @@ 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 };
+	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 4/8] checkout.c]: change "unique" member to "num_matches"
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (3 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
                                     ` (3 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Internally track how many matches we find in the check_tracking_name()
callback. Nothing uses this now, but it will be made use of in a later
change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/checkout.c b/checkout.c
index 80e430cda8..7662a39a62 100644
--- a/checkout.c
+++ b/checkout.c
@@ -7,10 +7,10 @@ struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
-	int unique;
+	int num_matches;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -23,9 +23,9 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		free(query.dst);
 		return 0;
 	}
+	cb->num_matches++;
 	if (cb->dst_ref) {
 		free(query.dst);
-		cb->unique = 0;
 		return 0;
 	}
 	cb->dst_ref = query.dst;
@@ -39,7 +39,7 @@ const char *unique_tracking_name(const char *name, struct object_id *oid)
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	free(cb_data.src_ref);
-	if (cb_data.unique)
+	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
 	free(cb_data.dst_ref);
 	return NULL;
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 5/8] checkout: pass the "num_matches" up to callers
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (4 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 4/8] checkout.c]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
                                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Pass the previously added "num_matches" struct value up to the callers
of unique_tracking_name(). This will allow callers to optionally print
better error messages in a later change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 10 +++++++---
 builtin/worktree.c |  4 ++--
 checkout.c         |  5 ++++-
 checkout.h         |  3 ++-
 4 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2e1d2376d2..72457fb7d5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -878,7 +878,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
 				struct checkout_opts *opts,
-				struct object_id *rev)
+				struct object_id *rev,
+				int *dwim_remotes_matched)
 {
 	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
@@ -972,7 +973,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 			recover_with_dwim = 0;
 
 		if (recover_with_dwim) {
-			const char *remote = unique_tracking_name(arg, rev);
+			const char *remote = unique_tracking_name(arg, rev,
+								  dwim_remotes_matched);
 			if (remote) {
 				*new_branch = arg;
 				arg = remote;
@@ -1109,6 +1111,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch = 1;
+	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
 		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1219,7 +1222,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts.track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts.new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev);
+					     &new_branch_info, &opts, &rev,
+					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5c7d2bb180..a763dbdccb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
 	if (guess_remote) {
 		struct object_id oid;
 		const char *remote =
-			unique_tracking_name(*new_branch, &oid);
+			unique_tracking_name(*new_branch, &oid, NULL);
 		return remote;
 	}
 	return NULL;
@@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)
 
 		commit = lookup_commit_reference_by_name(branch);
 		if (!commit) {
-			remote = unique_tracking_name(branch, &oid);
+			remote = unique_tracking_name(branch, &oid, NULL);
 			if (remote) {
 				new_branch = branch;
 				branch = remote;
diff --git a/checkout.c b/checkout.c
index 7662a39a62..ee3a7e9c05 100644
--- a/checkout.c
+++ b/checkout.c
@@ -32,12 +32,15 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	return 0;
 }
 
-const char *unique_tracking_name(const char *name, struct object_id *oid)
+const char *unique_tracking_name(const char *name, struct object_id *oid,
+				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
+	if (dwim_remotes_matched)
+		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
 	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
diff --git a/checkout.h b/checkout.h
index 4cd4cd1c23..6b2073310c 100644
--- a/checkout.h
+++ b/checkout.h
@@ -9,6 +9,7 @@
  * exists, NULL otherwise.
  */
 extern const char *unique_tracking_name(const char *name,
-					struct object_id *oid);
+					struct object_id *oid,
+					int *dwim_remotes_matched);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 6/8] builtin/checkout.c: use "ret" variable for return
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (5 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

There is no point in doing this right now, but in later change the
"ret" variable will be inspected. This change makes that meaningful
change smaller.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 72457fb7d5..8c93c55cbc 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1265,8 +1265,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr)
-		return checkout_paths(&opts, new_branch_info.name);
-	else
+	if (opts.patch_mode || opts.pathspec.nr) {
+		int ret = checkout_paths(&opts, new_branch_info.name);
+		return ret;
+	} else {
 		return checkout_branch(&opts, &new_branch_info);
+	}
 }
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 7/8] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (6 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-02 11:50                   ` [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

As the "checkout" documentation describes:

    If <branch> is not found but there does exist a tracking branch in
    exactly one remote (call it <remote>) with a matching name, treat
    as equivalent to [...] <remote>/<branch.

This is a really useful feature. The problem is that when you add
another remote (e.g. a fork), git won't find a unique branch name
anymore, and will instead print this unhelpful message:

    $ git checkout master
    error: pathspec 'master' did not match any file(s) known to git

Now it will, on my git.git checkout, print:

    $ ./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>

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/config.txt |  7 +++++++
 advice.c                 |  2 ++
 advice.h                 |  1 +
 builtin/checkout.c       | 13 +++++++++++++
 t/t2024-checkout-dwim.sh | 14 ++++++++++++++
 5 files changed, 37 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index ab641bf5a9..dfc0413a84 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -344,6 +344,13 @@ advice.*::
 		Advice shown when you used linkgit:git-checkout[1] to
 		move to the detach HEAD state, to instruct how to create
 		a local branch after the fact.
+	checkoutAmbiguousRemoteBranchName::
+		Advice shown when the argument to
+		linkgit:git-checkout[1] ambiguously resolves to a
+		remote tracking branch on more than one remote in
+		situations where an unambiguous argument would have
+		otherwise caused a remote-tracking branch to be
+		checked out.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
diff --git a/advice.c b/advice.c
index 370a56d054..75e7dede90 100644
--- a/advice.c
+++ b/advice.c
@@ -21,6 +21,7 @@ int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
+int advice_checkout_ambiguous_remote_branch_name = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -72,6 +73,7 @@ static struct {
 	{ "ignoredhook", &advice_ignored_hook },
 	{ "waitingforeditor", &advice_waiting_for_editor },
 	{ "graftfiledeprecated", &advice_graft_file_deprecated },
+	{ "checkoutambiguousremotebranchname", &advice_checkout_ambiguous_remote_branch_name },
 
 	/* make this an alias for backward compatibility */
 	{ "pushnonfastforward", &advice_push_update_rejected }
diff --git a/advice.h b/advice.h
index 9f5064e82a..4d11d51d43 100644
--- a/advice.h
+++ b/advice.h
@@ -22,6 +22,7 @@ extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
+extern int advice_checkout_ambiguous_remote_branch_name;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8c93c55cbc..baa027455a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -22,6 +22,7 @@
 #include "resolve-undo.h"
 #include "submodule-config.h"
 #include "submodule.h"
+#include "advice.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
@@ -1267,6 +1268,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	UNLEAK(opts);
 	if (opts.patch_mode || opts.pathspec.nr) {
 		int ret = checkout_paths(&opts, new_branch_info.name);
+		if (ret && dwim_remotes_matched > 1 &&
+		    advice_checkout_ambiguous_remote_branch_name)
+			advise(_("'%s' matched more than one remote tracking branch.\n"
+				 "We found %d remotes with a reference that matched. So we fell back\n"
+				 "on trying to resolve the argument as a path, but failed there too!\n"
+				 "\n"
+				 "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 checkout --track origin/<name>"),
+			       argv[0],
+			       dwim_remotes_matched);
 		return ret;
 	} else {
 		return checkout_branch(&opts, &new_branch_info);
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index ed32828105..fef263a858 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -76,6 +76,20 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_branch master
 '
 
+test_expect_success 'checkout of branch from multiple remotes fails with advice' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+	test_must_fail git checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep "^hint: " stderr &&
+	test_must_fail git -c advice.checkoutAmbiguousRemoteBranchName=false \
+		checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep ! "^hint: " stderr
+'
+
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	git checkout -B master &&
 	test_might_fail git branch -D bar &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote
  2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
                                     ` (7 preceding siblings ...)
  2018-06-02 11:50                   ` [PATCH v6 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-06-02 11:50                   ` Ævar Arnfjörð Bjarmason
  2018-06-03  7:58                     ` Eric Sunshine
  8 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-02 11:50 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

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 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:

    (
        cd /tmp &&
        rm -rf 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 (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: The argument '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 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       | 21 ++++++++++++++++++++-
 Documentation/git-checkout.txt |  9 +++++++++
 Documentation/git-worktree.txt |  9 +++++++++
 builtin/checkout.c             | 12 +++++++++---
 checkout.c                     | 26 ++++++++++++++++++++++++--
 t/t2024-checkout-dwim.sh       | 18 +++++++++++++++++-
 t/t2025-worktree-add.sh        | 21 +++++++++++++++++++++
 7 files changed, 109 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index dfc0413a84..aef2769211 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -350,7 +350,10 @@ advice.*::
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
-		checked out.
+		checked out. See the `checkout.defaultRemote`
+		configuration variable for how to set a given remote
+		to used by default in some situations where this
+		advice would be printed.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
@@ -1105,6 +1108,22 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.defaultRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. This setting allows for setting the name of a
+	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' refers to a
+remote branch. This setting might be used for other checkout-like
+commands or functionality in the future.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..9db02928c4 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,15 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` 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 afc6576a14..9c26be40f4 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch
 (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
diff --git a/builtin/checkout.c b/builtin/checkout.c
index baa027455a..5b357e922a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -912,8 +912,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.defaultRemote), 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).
 	 *
@@ -1277,7 +1279,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 				 "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 checkout --track origin/<name>"),
+				 "    git checkout --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."),
 			       argv[0],
 			       dwim_remotes_matched);
 		return ret;
diff --git a/checkout.c b/checkout.c
index ee3a7e9c05..c72e9f9773 100644
--- a/checkout.c
+++ b/checkout.c
@@ -2,15 +2,19 @@
 #include "remote.h"
 #include "refspec.h"
 #include "checkout.h"
+#include "config.h"
 
 struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
+	const char *default_remote;
+	char *default_dst_ref;
+	struct object_id *default_dst_oid;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -24,6 +28,12 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		return 0;
 	}
 	cb->num_matches++;
+	if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
+		struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
+		cb->default_dst_ref = xstrdup(query.dst);
+		oidcpy(dst, cb->dst_oid);
+		cb->default_dst_oid = dst;
+	}
 	if (cb->dst_ref) {
 		free(query.dst);
 		return 0;
@@ -36,14 +46,26 @@ const char *unique_tracking_name(const char *name, struct object_id *oid,
 				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
+	const char *default_remote = NULL;
+	if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
+		cb_data.default_remote = default_remote;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
-	if (cb_data.num_matches == 1)
+	free((char *)default_remote);
+	if (cb_data.num_matches == 1) {
+		free(cb_data.default_dst_ref);
+		free(cb_data.default_dst_oid);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.default_dst_ref) {
+		oidcpy(oid, cb_data.default_dst_oid);
+		free(cb_data.default_dst_oid);
+		return cb_data.default_dst_ref;
+	}
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index fef263a858..1495c248a7 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -87,7 +87,23 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
 		checkout foo 2>stderr &&
 	test_branch master &&
 	status_uno_is_clean &&
-	test_i18ngrep ! "^hint: " stderr
+	test_i18ngrep ! "^hint: " stderr &&
+	# Make sure the likes of checkout -p don not print this hint
+	git checkout -p foo 2>stderr &&
+	test_i18ngrep ! "^hint: " stderr &&
+	status_uno_is_clean
+'
+
+test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
+	git checkout -B master &&
+	status_uno_is_clean &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.defaultRemote=repo_a checkout foo &&
+	status_uno_is_clean &&
+	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' '
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index d2e49f7632..be6e093142 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -402,6 +402,27 @@ test_expect_success '"add" <path> <branch> dwims' '
 	)
 '
 
+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 &&
+		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+		>status.expect &&
+		git status -uno --porcelain >status.actual &&
+		test_cmp status.expect status.actual
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_upstream foo &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+	)
+'
+
 test_expect_success 'git worktree add does not match remote' '
 	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote
  2018-06-02 11:50                   ` [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
@ 2018-06-03  7:58                     ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-06-03  7:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Git List, Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer

On Sat, Jun 2, 2018 at 7:50 AM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> 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.
> [...]
> 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: The argument 'master' matched more than one remote tracking branch.

In v6, the "The argument" prefix has been dropped from the hint, so
this commit message needs a tweak to match.

>     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!
> [...]
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
> diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
> @@ -87,7 +87,23 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
> -       test_i18ngrep ! "^hint: " stderr
> +       test_i18ngrep ! "^hint: " stderr &&
> +       # Make sure the likes of checkout -p don not print this hint

s/don/do/

> +       git checkout -p foo 2>stderr &&
> +       test_i18ngrep ! "^hint: " stderr &&
> +       status_uno_is_clean
> +'

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-01  9:54                     ` Ævar Arnfjörð Bjarmason
@ 2018-06-04  1:58                       ` Junio C Hamano
  0 siblings, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-06-04  1:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> I.e. I was trying to avoid printing out the "error: pathspec 'master'
> did not match any file(s) known to git." error altogether. That's still
> arguably a good direction, since we *know* "master" would have otherwise
> matched a remote branch, so that's probably a more informative message
> than falling back to checking out pathspecs and failing, and complaining
> about there being no such pathspec.

That ideal behaviour makes sense.

> But it was a pain to handle the various edge cases, e.g.:
>
>     $ ./git --exec-path=$PWD checkout x y z
>     error: pathspec 'x' did not match any file(s) known to git.
>     error: pathspec 'y' did not match any file(s) known to git.
>     error: pathspec 'z' did not match any file(s) known to git.

Let's take a detour to a tangent, as this example does not have
anything to do with the remote-tracking auto-dwimming. 

Ideally what do we want to say in this case?  What's allowed for 'x'
(2 possibilities) is different from whats allowed for 'y' and 'z'
(only 1 possibility)---do we want to complain that 'x' is not a rev
noris a file (we do not say 'x' could be a misspelt rev name right
now), and then 'y' and 'z' are not files (which is what we do now)?

That might be an improvement.  I dunno.  In any case, that is a
tangent that we do not have to address with these patches.

In contrast, the command line without y and z gives three
possibilities to 'x'.  'x' is not a rev, is not a remote-tracking
branch name that only a single remote has, and is not a file.  Now,
if we are going to mention that we failed to interpret it as the
latter two, perhaps we should also mention that it was not a rev
(which could have been misspelt)?

> So I decided just to let checkout_paths() to its thing and then print
> out an error about dwim branches if applicable if it failed.

Yeah, I think I get it now.  If you want to silence the "error" from
report_path_error() and replace it with something else, you would
need to change checout_paths(), as this function is sort-of used as
the last ditch effort after all else failed, and right now it is not
aware of what exactly all these other failed efforts were.

Thanks.  I'm looking at v6 reroll for queuing.




^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7 0/8] ambiguous checkout UI & checkout.defaultRemote
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
                                       ` (7 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Fixes issues noted with v6, hopefully ready for queuing. A tbdiff with
v6:

1: ab4529d9f5 = 1: 2ca81c76fc checkout tests: index should be clean after dwim checkout
2: c8bbece403 = 2: 19b14a1c75 checkout.h: wrap the arguments to unique_tracking_name()
3: 881fe63f4f = 3: 8bc6a9c052 checkout.c: introduce an *_INIT macro
4: 72ddaeddd3 ! 4: 34f3b67f9b checkout.c: change "unique" member to "num_matches"
    @@ -1,6 +1,6 @@
     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    -    checkout.c]: change "unique" member to "num_matches"
    +    checkout.c: change "unique" member to "num_matches"
         
         Internally track how many matches we find in the check_tracking_name()
         callback. Nothing uses this now, but it will be made use of in a later
5: 5e8c82680b = 5: 7d81c06a23 checkout: pass the "num_matches" up to callers
6: 07e667f80a = 6: e86636ad2c builtin/checkout.c: use "ret" variable for return
7: 0a148182e6 ! 7: c2130b347c checkout: add advice for ambiguous "checkout <branch>"
    @@ -27,6 +27,28 @@
             hint: you can do so by fully qualifying the name with the --track option:
             hint:
             hint:     git checkout --track origin/<name>
    +    
    +    Note that the "error: pathspec[...]" message is still printed. This is
    +    because whatever else checkout may have tried earlier, its final
    +    fallback is to try to resolve the argument as a path. E.g. in this
    +    case:
    +    
    +        $ ./git --exec-path=$PWD checkout master pu
    +        error: pathspec 'master' did not match any file(s) known to git.
    +        error: pathspec 'pu' did not match any file(s) known to git.
    +    
    +    There we don't print the "hint:" implicitly due to earlier logic
    +    around the DWIM fallback. That fallback is only used if it looks like
    +    we have one argument that might be a branch.
    +    
    +    I can't think of an intrinsic reason for why we couldn't in some
    +    future change skip printing the "error: pathspec[...]" error. However,
    +    to do so we'd need to pass something down to checkout_paths() to make
    +    it suppress printing an error on its own, and for us to be confident
    +    that we're not silencing cases where those errors are meaningful.
    +    
    +    I don't think that's worth it since determining whether that's the
    +    case could easily change due to future changes in the checkout logic.
         
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
8: f3a52a26a2 ! 8: f1ac0f7351 checkout & worktree: introduce checkout.defaultRemote
    @@ -53,12 +53,12 @@
         
             $ ./git --exec-path=$PWD checkout master
             error: pathspec 'master' did not match any file(s) known to git.
    -        hint: The argument 'master' matched more than one remote tracking branch.
    +        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: 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:
    @@ -263,7 +263,7 @@
      	status_uno_is_clean &&
     -	test_i18ngrep ! "^hint: " stderr
     +	test_i18ngrep ! "^hint: " stderr &&
    -+	# Make sure the likes of checkout -p don not print this hint
    ++	# Make sure the likes of checkout -p do not print this hint
     +	git checkout -p foo 2>stderr &&
     +	test_i18ngrep ! "^hint: " stderr &&
     +	status_uno_is_clean

Ævar Arnfjörð Bjarmason (8):
  checkout tests: index should be clean after dwim checkout
  checkout.h: wrap the arguments to unique_tracking_name()
  checkout.c: introduce an *_INIT macro
  checkout.c: change "unique" member to "num_matches"
  checkout: pass the "num_matches" up to callers
  builtin/checkout.c: use "ret" variable for return
  checkout: add advice for ambiguous "checkout <branch>"
  checkout & worktree: introduce checkout.defaultRemote

 Documentation/config.txt       | 26 +++++++++++++++
 Documentation/git-checkout.txt |  9 ++++++
 Documentation/git-worktree.txt |  9 ++++++
 advice.c                       |  2 ++
 advice.h                       |  1 +
 builtin/checkout.c             | 41 ++++++++++++++++++-----
 builtin/worktree.c             |  4 +--
 checkout.c                     | 37 ++++++++++++++++++---
 checkout.h                     |  4 ++-
 t/t2024-checkout-dwim.sh       | 59 ++++++++++++++++++++++++++++++++++
 t/t2025-worktree-add.sh        | 21 ++++++++++++
 11 files changed, 197 insertions(+), 16 deletions(-)

-- 
2.17.0.290.gded63e768a


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 " Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 15:45                       ` SZEDER Gábor
  2018-06-05 14:40                     ` [PATCH v7 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
                                       ` (6 subsequent siblings)
  8 siblings, 1 reply; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Assert that whenever there's a DWIM checkout that the index should be
clean afterwards, in addition to the correct branch being checked-out.

The way the DWIM checkout code in checkout.[ch] works is by looping
over all remotes, and for each remote trying to find if a given
reference name only exists on that remote, or if it exists anywhere
else.

This is done by starting out with a `unique = 1` tracking variable in
a struct shared by the entire loop, which will get set to `0` if the
data reference is not unique.

Thus if we find a match we know the dst_oid member of
tracking_name_data must be correct, since it's associated with the
only reference on the only remote that could have matched our query.

But if there was ever a mismatch there for some reason we might end up
with the correct branch checked out, but at the wrong oid, which would
show whatever the difference between the two staged in the
index (checkout branch A, stage changes from the state of branch B).

So let's amend the tests (mostly added in) 399e4a1c56 ("t2024: Add
tests verifying current DWIM behavior of 'git checkout <branch>'",
2013-04-21) to always assert that "status" is clean after we run
"checkout", that's being done with "-uno" because there's going to be
some untracked files related to the test itself which we don't care
about.

In all these tests (DWIM or otherwise) we start with a clean index, so
these tests are asserting that that's still the case after the
"checkout", failed or otherwise.

Then if we ever run into this sort of regression, either in the
existing code or with a new feature, we'll know.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t2024-checkout-dwim.sh | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 3e5ac81bd2..ed32828105 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -23,6 +23,12 @@ test_branch_upstream () {
 	test_cmp expect.upstream actual.upstream
 }
 
+status_uno_is_clean() {
+	>status.expect &&
+	git status -uno --porcelain >status.actual &&
+	test_cmp status.expect status.actual
+}
+
 test_expect_success 'setup' '
 	test_commit my_master &&
 	git init repo_a &&
@@ -55,6 +61,7 @@ test_expect_success 'checkout of non-existing branch fails' '
 	test_might_fail git branch -D xyzzy &&
 
 	test_must_fail git checkout xyzzy &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/xyzzy &&
 	test_branch master
 '
@@ -64,6 +71,7 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_might_fail git branch -D foo &&
 
 	test_must_fail git checkout foo &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/foo &&
 	test_branch master
 '
@@ -73,6 +81,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	test_might_fail git branch -D bar &&
 
 	git checkout bar &&
+	status_uno_is_clean &&
 	test_branch bar &&
 	test_cmp_rev remotes/repo_a/bar HEAD &&
 	test_branch_upstream bar repo_a bar
@@ -83,6 +92,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 	test_might_fail git branch -D baz &&
 
 	git checkout baz &&
+	status_uno_is_clean &&
 	test_branch baz &&
 	test_cmp_rev remotes/other_b/baz HEAD &&
 	test_branch_upstream baz repo_b baz
@@ -90,6 +100,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '
 
 test_expect_success '--no-guess suppresses branch auto-vivification' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout --no-guess bar &&
@@ -99,6 +110,7 @@ test_expect_success '--no-guess suppresses branch auto-vivification' '
 
 test_expect_success 'setup more remotes with unconventional refspecs' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	git init repo_c &&
 	(
 		cd repo_c &&
@@ -128,27 +140,33 @@ test_expect_success 'setup more remotes with unconventional refspecs' '
 
 test_expect_success 'checkout of branch from multiple remotes fails #2' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D bar &&
 
 	test_must_fail git checkout bar &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/bar &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from multiple remotes fails #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D baz &&
 
 	test_must_fail git checkout baz &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/baz &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch from a single remote succeeds #3' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	git checkout spam &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -156,9 +174,11 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' '
 
 test_expect_success 'checkout of branch from a single remote succeeds #4' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D eggs &&
 
 	git checkout eggs &&
+	status_uno_is_clean &&
 	test_branch eggs &&
 	test_cmp_rev refs/repo_d/eggs HEAD &&
 	test_branch_upstream eggs repo_d eggs
@@ -166,32 +186,38 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' '
 
 test_expect_success 'checkout of branch with a file having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	test_must_fail git checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout of branch with a file in subdir having the same name fails' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	mkdir sub &&
 	mv spam sub/spam &&
 	test_must_fail git -C sub checkout spam &&
+	status_uno_is_clean &&
 	test_must_fail git rev-parse --verify refs/heads/spam &&
 	test_branch master
 '
 
 test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' '
 	git checkout -B master &&
+	status_uno_is_clean &&
 	test_might_fail git branch -D spam &&
 
 	>spam &&
 	git checkout spam -- &&
+	status_uno_is_clean &&
 	test_branch spam &&
 	test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
 	test_branch_upstream spam repo_c spam
@@ -200,6 +226,7 @@ test_expect_success 'checkout <branch> -- succeeds, even if a file with the same
 test_expect_success 'loosely defined local base branch is reported correctly' '
 
 	git checkout master &&
+	status_uno_is_clean &&
 	git branch strict &&
 	git branch loose &&
 	git commit --allow-empty -m "a bit more" &&
@@ -210,7 +237,9 @@ test_expect_success 'loosely defined local base branch is reported correctly' '
 	test_config branch.loose.merge master &&
 
 	git checkout strict | sed -e "s/strict/BRANCHNAME/g" >expect &&
+	status_uno_is_clean &&
 	git checkout loose | sed -e "s/loose/BRANCHNAME/g" >actual &&
+	status_uno_is_clean &&
 
 	test_cmp expect actual
 '
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 2/8] checkout.h: wrap the arguments to unique_tracking_name()
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 " Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
                                       ` (5 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

The line was too long already, and will be longer still when a later
change adds another argument.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/checkout.h b/checkout.h
index 9980711179..4cd4cd1c23 100644
--- a/checkout.h
+++ b/checkout.h
@@ -8,6 +8,7 @@
  * tracking branch.  Return the name of the remote if such a branch
  * exists, NULL otherwise.
  */
-extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+extern const char *unique_tracking_name(const char *name,
+					struct object_id *oid);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 3/8] checkout.c: introduce an *_INIT macro
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (2 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 4/8] checkout.c: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
                                       ` (4 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Add an *_INIT macro for the tracking_name_data similar to what exists
elsewhere in the codebase, e.g. OID_ARRAY_INIT in sha1-array.h. This
will make it more idiomatic in later changes to add more fields to the
struct & its initialization macro.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/checkout.c b/checkout.c
index bdefc888ba..80e430cda8 100644
--- a/checkout.c
+++ b/checkout.c
@@ -10,6 +10,8 @@ struct tracking_name_data {
 	int unique;
 };
 
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
 	struct tracking_name_data *cb = cb_data;
@@ -32,7 +34,7 @@ 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 };
+	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 4/8] checkout.c: change "unique" member to "num_matches"
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (3 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
                                       ` (3 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Internally track how many matches we find in the check_tracking_name()
callback. Nothing uses this now, but it will be made use of in a later
change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 checkout.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/checkout.c b/checkout.c
index 80e430cda8..7662a39a62 100644
--- a/checkout.c
+++ b/checkout.c
@@ -7,10 +7,10 @@ struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
-	int unique;
+	int num_matches;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 1 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -23,9 +23,9 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		free(query.dst);
 		return 0;
 	}
+	cb->num_matches++;
 	if (cb->dst_ref) {
 		free(query.dst);
-		cb->unique = 0;
 		return 0;
 	}
 	cb->dst_ref = query.dst;
@@ -39,7 +39,7 @@ const char *unique_tracking_name(const char *name, struct object_id *oid)
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	free(cb_data.src_ref);
-	if (cb_data.unique)
+	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
 	free(cb_data.dst_ref);
 	return NULL;
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 5/8] checkout: pass the "num_matches" up to callers
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (4 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 4/8] checkout.c: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
                                       ` (2 subsequent siblings)
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

Pass the previously added "num_matches" struct value up to the callers
of unique_tracking_name(). This will allow callers to optionally print
better error messages in a later change.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 10 +++++++---
 builtin/worktree.c |  4 ++--
 checkout.c         |  5 ++++-
 checkout.h         |  3 ++-
 4 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2e1d2376d2..72457fb7d5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -878,7 +878,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
 				struct checkout_opts *opts,
-				struct object_id *rev)
+				struct object_id *rev,
+				int *dwim_remotes_matched)
 {
 	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
@@ -972,7 +973,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 			recover_with_dwim = 0;
 
 		if (recover_with_dwim) {
-			const char *remote = unique_tracking_name(arg, rev);
+			const char *remote = unique_tracking_name(arg, rev,
+								  dwim_remotes_matched);
 			if (remote) {
 				*new_branch = arg;
 				arg = remote;
@@ -1109,6 +1111,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch = 1;
+	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
 		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1219,7 +1222,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts.track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts.new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev);
+					     &new_branch_info, &opts, &rev,
+					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5c7d2bb180..a763dbdccb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
 	if (guess_remote) {
 		struct object_id oid;
 		const char *remote =
-			unique_tracking_name(*new_branch, &oid);
+			unique_tracking_name(*new_branch, &oid, NULL);
 		return remote;
 	}
 	return NULL;
@@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)
 
 		commit = lookup_commit_reference_by_name(branch);
 		if (!commit) {
-			remote = unique_tracking_name(branch, &oid);
+			remote = unique_tracking_name(branch, &oid, NULL);
 			if (remote) {
 				new_branch = branch;
 				branch = remote;
diff --git a/checkout.c b/checkout.c
index 7662a39a62..ee3a7e9c05 100644
--- a/checkout.c
+++ b/checkout.c
@@ -32,12 +32,15 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	return 0;
 }
 
-const char *unique_tracking_name(const char *name, struct object_id *oid)
+const char *unique_tracking_name(const char *name, struct object_id *oid,
+				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
+	if (dwim_remotes_matched)
+		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
 	if (cb_data.num_matches == 1)
 		return cb_data.dst_ref;
diff --git a/checkout.h b/checkout.h
index 4cd4cd1c23..6b2073310c 100644
--- a/checkout.h
+++ b/checkout.h
@@ -9,6 +9,7 @@
  * exists, NULL otherwise.
  */
 extern const char *unique_tracking_name(const char *name,
-					struct object_id *oid);
+					struct object_id *oid,
+					int *dwim_remotes_matched);
 
 #endif /* CHECKOUT_H */
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 6/8] builtin/checkout.c: use "ret" variable for return
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (5 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

There is no point in doing this right now, but in later change the
"ret" variable will be inspected. This change makes that meaningful
change smaller.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 72457fb7d5..8c93c55cbc 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1265,8 +1265,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr)
-		return checkout_paths(&opts, new_branch_info.name);
-	else
+	if (opts.patch_mode || opts.pathspec.nr) {
+		int ret = checkout_paths(&opts, new_branch_info.name);
+		return ret;
+	} else {
 		return checkout_branch(&opts, &new_branch_info);
+	}
 }
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 7/8] checkout: add advice for ambiguous "checkout <branch>"
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (6 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  2018-06-05 14:40                     ` [PATCH v7 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

As the "checkout" documentation describes:

    If <branch> is not found but there does exist a tracking branch in
    exactly one remote (call it <remote>) with a matching name, treat
    as equivalent to [...] <remote>/<branch.

This is a really useful feature. The problem is that when you add
another remote (e.g. a fork), git won't find a unique branch name
anymore, and will instead print this unhelpful message:

    $ git checkout master
    error: pathspec 'master' did not match any file(s) known to git

Now it will, on my git.git checkout, print:

    $ ./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>

Note that the "error: pathspec[...]" message is still printed. This is
because whatever else checkout may have tried earlier, its final
fallback is to try to resolve the argument as a path. E.g. in this
case:

    $ ./git --exec-path=$PWD checkout master pu
    error: pathspec 'master' did not match any file(s) known to git.
    error: pathspec 'pu' did not match any file(s) known to git.

There we don't print the "hint:" implicitly due to earlier logic
around the DWIM fallback. That fallback is only used if it looks like
we have one argument that might be a branch.

I can't think of an intrinsic reason for why we couldn't in some
future change skip printing the "error: pathspec[...]" error. However,
to do so we'd need to pass something down to checkout_paths() to make
it suppress printing an error on its own, and for us to be confident
that we're not silencing cases where those errors are meaningful.

I don't think that's worth it since determining whether that's the
case could easily change due to future changes in the checkout logic.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/config.txt |  7 +++++++
 advice.c                 |  2 ++
 advice.h                 |  1 +
 builtin/checkout.c       | 13 +++++++++++++
 t/t2024-checkout-dwim.sh | 14 ++++++++++++++
 5 files changed, 37 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index ab641bf5a9..dfc0413a84 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -344,6 +344,13 @@ advice.*::
 		Advice shown when you used linkgit:git-checkout[1] to
 		move to the detach HEAD state, to instruct how to create
 		a local branch after the fact.
+	checkoutAmbiguousRemoteBranchName::
+		Advice shown when the argument to
+		linkgit:git-checkout[1] ambiguously resolves to a
+		remote tracking branch on more than one remote in
+		situations where an unambiguous argument would have
+		otherwise caused a remote-tracking branch to be
+		checked out.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
diff --git a/advice.c b/advice.c
index 370a56d054..75e7dede90 100644
--- a/advice.c
+++ b/advice.c
@@ -21,6 +21,7 @@ int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
+int advice_checkout_ambiguous_remote_branch_name = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -72,6 +73,7 @@ static struct {
 	{ "ignoredhook", &advice_ignored_hook },
 	{ "waitingforeditor", &advice_waiting_for_editor },
 	{ "graftfiledeprecated", &advice_graft_file_deprecated },
+	{ "checkoutambiguousremotebranchname", &advice_checkout_ambiguous_remote_branch_name },
 
 	/* make this an alias for backward compatibility */
 	{ "pushnonfastforward", &advice_push_update_rejected }
diff --git a/advice.h b/advice.h
index 9f5064e82a..4d11d51d43 100644
--- a/advice.h
+++ b/advice.h
@@ -22,6 +22,7 @@ extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
+extern int advice_checkout_ambiguous_remote_branch_name;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8c93c55cbc..baa027455a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -22,6 +22,7 @@
 #include "resolve-undo.h"
 #include "submodule-config.h"
 #include "submodule.h"
+#include "advice.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
@@ -1267,6 +1268,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	UNLEAK(opts);
 	if (opts.patch_mode || opts.pathspec.nr) {
 		int ret = checkout_paths(&opts, new_branch_info.name);
+		if (ret && dwim_remotes_matched > 1 &&
+		    advice_checkout_ambiguous_remote_branch_name)
+			advise(_("'%s' matched more than one remote tracking branch.\n"
+				 "We found %d remotes with a reference that matched. So we fell back\n"
+				 "on trying to resolve the argument as a path, but failed there too!\n"
+				 "\n"
+				 "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 checkout --track origin/<name>"),
+			       argv[0],
+			       dwim_remotes_matched);
 		return ret;
 	} else {
 		return checkout_branch(&opts, &new_branch_info);
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index ed32828105..fef263a858 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -76,6 +76,20 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_branch master
 '
 
+test_expect_success 'checkout of branch from multiple remotes fails with advice' '
+	git checkout -B master &&
+	test_might_fail git branch -D foo &&
+	test_must_fail git checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep "^hint: " stderr &&
+	test_must_fail git -c advice.checkoutAmbiguousRemoteBranchName=false \
+		checkout foo 2>stderr &&
+	test_branch master &&
+	status_uno_is_clean &&
+	test_i18ngrep ! "^hint: " stderr
+'
+
 test_expect_success 'checkout of branch from a single remote succeeds #1' '
 	git checkout -B master &&
 	test_might_fail git branch -D bar &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* [PATCH v7 8/8] checkout & worktree: introduce checkout.defaultRemote
  2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
                                       ` (7 preceding siblings ...)
  2018-06-05 14:40                     ` [PATCH v7 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
@ 2018-06-05 14:40                     ` Ævar Arnfjörð Bjarmason
  8 siblings, 0 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-06-05 14:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff King, Johannes Schindelin,
	Nguyễn Thái Ngọc Duy, Thomas Gummerer,
	Eric Sunshine, Ævar Arnfjörð Bjarmason

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 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:

    (
        cd /tmp &&
        rm -rf 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 (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 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       | 21 ++++++++++++++++++++-
 Documentation/git-checkout.txt |  9 +++++++++
 Documentation/git-worktree.txt |  9 +++++++++
 builtin/checkout.c             | 12 +++++++++---
 checkout.c                     | 26 ++++++++++++++++++++++++--
 t/t2024-checkout-dwim.sh       | 18 +++++++++++++++++-
 t/t2025-worktree-add.sh        | 21 +++++++++++++++++++++
 7 files changed, 109 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index dfc0413a84..aef2769211 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -350,7 +350,10 @@ advice.*::
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
-		checked out.
+		checked out. See the `checkout.defaultRemote`
+		configuration variable for how to set a given remote
+		to used by default in some situations where this
+		advice would be printed.
 	amWorkDir::
 		Advice that shows the location of the patch file when
 		linkgit:git-am[1] fails to apply it.
@@ -1105,6 +1108,22 @@ browser.<tool>.path::
 	browse HTML help (see `-w` option in linkgit:git-help[1]) or a
 	working repository in gitweb (see linkgit:git-instaweb[1]).
 
+checkout.defaultRemote::
+	When you run 'git checkout <something>' and only have one
+	remote, it may implicitly fall back on checking out and
+	tracking e.g. 'origin/<something>'. This stops working as soon
+	as you have more than one remote with a '<something>'
+	reference. This setting allows for setting the name of a
+	preferred 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 by linkgit:git-worktree[1] when 'git worktree add' refers to a
+remote branch. This setting might be used for other checkout-like
+commands or functionality in the future.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f,
 	-i or -n.   Defaults to true.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ca5fc9c798..9db02928c4 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -38,6 +38,15 @@ equivalent to
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` 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 afc6576a14..9c26be40f4 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch
 (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
diff --git a/builtin/checkout.c b/builtin/checkout.c
index baa027455a..5b357e922a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -912,8 +912,10 @@ 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
-	 *       fork local <something> from that remote-tracking branch.
+	 *       in one and only one remote (or if the branch exists on the
+	 *       remote named in checkout.defaultRemote), 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).
 	 *
@@ -1277,7 +1279,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 				 "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 checkout --track origin/<name>"),
+				 "    git checkout --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."),
 			       argv[0],
 			       dwim_remotes_matched);
 		return ret;
diff --git a/checkout.c b/checkout.c
index ee3a7e9c05..c72e9f9773 100644
--- a/checkout.c
+++ b/checkout.c
@@ -2,15 +2,19 @@
 #include "remote.h"
 #include "refspec.h"
 #include "checkout.h"
+#include "config.h"
 
 struct tracking_name_data {
 	/* const */ char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
+	const char *default_remote;
+	char *default_dst_ref;
+	struct object_id *default_dst_oid;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0 }
+#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
@@ -24,6 +28,12 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		return 0;
 	}
 	cb->num_matches++;
+	if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
+		struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
+		cb->default_dst_ref = xstrdup(query.dst);
+		oidcpy(dst, cb->dst_oid);
+		cb->default_dst_oid = dst;
+	}
 	if (cb->dst_ref) {
 		free(query.dst);
 		return 0;
@@ -36,14 +46,26 @@ const char *unique_tracking_name(const char *name, struct object_id *oid,
 				 int *dwim_remotes_matched)
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
+	const char *default_remote = NULL;
+	if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
+		cb_data.default_remote = default_remote;
 	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
 	free(cb_data.src_ref);
-	if (cb_data.num_matches == 1)
+	free((char *)default_remote);
+	if (cb_data.num_matches == 1) {
+		free(cb_data.default_dst_ref);
+		free(cb_data.default_dst_oid);
 		return cb_data.dst_ref;
+	}
 	free(cb_data.dst_ref);
+	if (cb_data.default_dst_ref) {
+		oidcpy(oid, cb_data.default_dst_oid);
+		free(cb_data.default_dst_oid);
+		return cb_data.default_dst_ref;
+	}
 	return NULL;
 }
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index fef263a858..9f17ac92f1 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -87,7 +87,23 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice'
 		checkout foo 2>stderr &&
 	test_branch master &&
 	status_uno_is_clean &&
-	test_i18ngrep ! "^hint: " stderr
+	test_i18ngrep ! "^hint: " stderr &&
+	# Make sure the likes of checkout -p do not print this hint
+	git checkout -p foo 2>stderr &&
+	test_i18ngrep ! "^hint: " stderr &&
+	status_uno_is_clean
+'
+
+test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' '
+	git checkout -B master &&
+	status_uno_is_clean &&
+	test_might_fail git branch -D foo &&
+
+	git -c checkout.defaultRemote=repo_a checkout foo &&
+	status_uno_is_clean &&
+	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' '
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index d2e49f7632..be6e093142 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -402,6 +402,27 @@ test_expect_success '"add" <path> <branch> dwims' '
 	)
 '
 
+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 &&
+		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+		>status.expect &&
+		git status -uno --porcelain >status.actual &&
+		test_cmp status.expect status.actual
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_upstream foo &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+	)
+'
+
 test_expect_success 'git worktree add does not match remote' '
 	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
-- 
2.17.0.290.gded63e768a


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout
  2018-06-05 14:40                     ` [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
@ 2018-06-05 15:45                       ` SZEDER Gábor
  2018-07-27 17:48                         ` [PATCH] tests: make use of the test_must_be_empty function Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 95+ messages in thread
From: SZEDER Gábor @ 2018-06-05 15:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: SZEDER Gábor, git, Junio C Hamano, Jeff King,
	Johannes Schindelin, Nguyễn Thái Ngọc Duy,
	Thomas Gummerer, Eric Sunshine

> diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
> index 3e5ac81bd2..ed32828105 100755
> --- a/t/t2024-checkout-dwim.sh
> +++ b/t/t2024-checkout-dwim.sh
> @@ -23,6 +23,12 @@ test_branch_upstream () {
>  	test_cmp expect.upstream actual.upstream
>  }
>  
> +status_uno_is_clean() {
> +	>status.expect &&
> +	git status -uno --porcelain >status.actual &&
> +	test_cmp status.expect status.actual

This function could be written a tad simpler as:

  git status -uno --porcelain >status.actual &&
  test_must_be_empty status.actual


^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] tests: make use of the test_must_be_empty function
  2018-06-05 15:45                       ` SZEDER Gábor
@ 2018-07-27 17:48                         ` Ævar Arnfjörð Bjarmason
  2018-07-27 21:50                           ` Junio C Hamano
                                             ` (2 more replies)
  0 siblings, 3 replies; 95+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-07-27 17:48 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, SZEDER Gábor,
	Ævar Arnfjörð Bjarmason

Change various tests that use an idiom of the form:

    >expect &&
    test_cmp expect actual

To instead use:

    test_must_be_empty actual

The test_must_be_empty() wrapper was introduced in ca8d148daf ("test:
test_must_be_empty helper", 2013-06-09). Many of these tests have been
added after that time. This was mostly found with, and manually pruned
from:

    git grep '^\s+>.*expect.* &&$' t

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---

As promised in
https://public-inbox.org/git/CACBZZX4yG5h5kk4NFQz_NzAweMa+Nh3H-39OHtcH4XWsA6FGpg@mail.gmail.com/
here's a series to refactor various test_cmp invocations to use
test_must_be_empty.

This patch is on top of "next" since one of the things being fixed up
is a test in my in-flight ab/checkout-default-remote series. It also
applies cleanly to "pu", and the only conflicts with "master" are due
to that series of mine.

 t/t0008-ignores.sh                   |  9 ++----
 t/t0030-stripspace.sh                | 17 +++++-----
 t/t0300-credentials.sh               |  3 +-
 t/t1011-read-tree-sparse-checkout.sh |  3 +-
 t/t1306-xdg-files.sh                 |  6 ++--
 t/t1403-show-ref.sh                  | 46 ++++++++++------------------
 t/t1507-rev-parse-upstream.sh        |  3 +-
 t/t2024-checkout-dwim.sh             |  3 +-
 t/t2025-worktree-add.sh              |  3 +-
 t/t2202-add-addremove.sh             |  3 +-
 t/t3001-ls-files-others-exclude.sh   | 15 +++------
 t/t3070-wildmatch.sh                 |  3 +-
 t/t3201-branch-contains.sh           | 15 +++------
 t/t3700-add.sh                       |  3 +-
 t/t3910-mac-os-precompose.sh         |  3 +-
 t/t4010-diff-pathspec.sh             |  3 +-
 t/t4015-diff-whitespace.sh           | 17 +++++-----
 t/t4039-diff-assume-unchanged.sh     |  3 +-
 t/t4200-rerere.sh                    |  9 ++----
 t/t4202-log.sh                       |  6 ++--
 t/t4210-log-i18n.sh                  |  6 ++--
 t/t5310-pack-bitmaps.sh              |  9 ++----
 t/t5313-pack-bounds-checks.sh        |  3 +-
 t/t5505-remote.sh                    |  6 ++--
 t/t5512-ls-remote.sh                 |  6 ++--
 t/t5514-fetch-multiple.sh            |  3 +-
 t/t5533-push-cas.sh                  |  6 ++--
 t/t5612-clone-refspec.sh             |  9 ++----
 t/t6000-rev-list-misc.sh             |  3 +-
 t/t6009-rev-list-parent.sh           |  6 ++--
 t/t6018-rev-list-glob.sh             | 12 +++-----
 t/t6019-rev-list-ancestry-path.sh    |  3 +-
 t/t6022-merge-rename.sh              |  3 +-
 t/t6060-merge-index.sh               |  3 +-
 t/t6300-for-each-ref.sh              |  3 +-
 t/t7004-tag.sh                       | 22 +++++--------
 t/t7006-pager.sh                     |  3 +-
 t/t7030-verify-tag.sh                |  3 +-
 t/t7063-status-untracked-cache.sh    |  3 +-
 t/t7102-reset.sh                     |  6 ++--
 t/t7106-reset-unborn-branch.sh       |  9 ++----
 t/t7401-submodule-summary.sh         |  3 +-
 t/t7502-commit.sh                    |  3 +-
 t/t7610-mergetool.sh                 |  3 +-
 t/t7810-grep.sh                      |  3 +-
 t/t9001-send-email.sh                |  3 +-
 t/t9300-fast-import.sh               |  7 ++---
 47 files changed, 113 insertions(+), 209 deletions(-)

diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index c03f155a35..1744cee5e9 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -807,10 +807,9 @@ test_expect_success 'trailing whitespace is ignored' '
 	cat >expect <<EOF &&
 whitespace/untracked
 EOF
-	: >err.expect &&
 	git ls-files -o -X ignore whitespace >actual 2>err &&
 	test_cmp expect actual &&
-	test_cmp err.expect err
+	test_must_be_empty err
 '
 
 test_expect_success !MINGW 'quoting allows trailing whitespace' '
@@ -820,10 +819,9 @@ test_expect_success !MINGW 'quoting allows trailing whitespace' '
 	>whitespace/untracked &&
 	echo "whitespace/trailing\\ \\ " >ignore &&
 	echo whitespace/untracked >expect &&
-	: >err.expect &&
 	git ls-files -o -X ignore whitespace >actual 2>err &&
 	test_cmp expect actual &&
-	test_cmp err.expect err
+	test_must_be_empty err
 '
 
 test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' '
@@ -845,10 +843,9 @@ test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' '
 	whitespace/trailing 6 \\a\\Z
 	EOF
 	echo whitespace/untracked >expect &&
-	>err.expect &&
 	git ls-files -o -X ignore whitespace >actual 2>err &&
 	test_cmp expect actual &&
-	test_cmp err.expect err
+	test_must_be_empty err
 '
 
 test_expect_success 'info/exclude trumps core.excludesfile' '
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
index bbf3e39e3d..b77948c618 100755
--- a/t/t0030-stripspace.sh
+++ b/t/t0030-stripspace.sh
@@ -110,31 +110,30 @@ test_expect_success \
 
 test_expect_success \
     'only consecutive blank lines should be completely removed' '
-    > expect &&
 
     printf "\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "\n\n\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "$sss\n$sss\n$sss\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "$sss$sss\n$sss\n\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "\n$sss\n$sss$sss\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "$sss$sss$sss$sss\n\n\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "\n$sss$sss$sss$sss\n\n" | git stripspace >actual &&
-    test_cmp expect actual &&
+    test_must_be_empty actual &&
 
     printf "\n\n$sss$sss$sss$sss\n" | git stripspace >actual &&
-    test_cmp expect actual
+    test_must_be_empty actual
 '
 
 test_expect_success \
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index 03bd31e9f2..82eaaea0f4 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -294,8 +294,7 @@ test_expect_success 'helpers can abort the process' '
 		-c credential.helper="!f() { echo quit=1; }; f" \
 		-c credential.helper="verbatim foo bar" \
 		credential fill >stdout &&
-	>expect &&
-	test_cmp expect stdout
+	test_must_be_empty stdout
 '
 
 test_expect_success 'empty helper spec resets helper list' '
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index 0c6f48f302..ba71b159ba 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -227,12 +227,11 @@ test_expect_success 'index removal and worktree narrowing at the same time' '
 '
 
 test_expect_success 'read-tree --reset removes outside worktree' '
-	>empty &&
 	echo init.t >.git/info/sparse-checkout &&
 	git checkout -f top &&
 	git reset --hard removed &&
 	git ls-files sub/added >result &&
-	test_cmp empty result
+	test_must_be_empty result
 '
 
 test_expect_success 'print errors when failed to update worktree' '
diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh
index 8b14ab187c..21e139a313 100755
--- a/t/t1306-xdg-files.sh
+++ b/t/t1306-xdg-files.sh
@@ -114,11 +114,10 @@ test_expect_success 'Exclusion in a non-XDG global ignore file' '
 '
 
 test_expect_success 'Checking XDG ignore file when HOME is unset' '
-	>expected &&
 	(sane_unset HOME &&
 	 git config --unset core.excludesfile &&
 	 git ls-files --exclude-standard --ignored >actual) &&
-	test_cmp expected actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'Checking attributes in the XDG attributes file' '
@@ -132,10 +131,9 @@ test_expect_success 'Checking attributes in the XDG attributes file' '
 '
 
 test_expect_success 'Checking XDG attributes when HOME is unset' '
-	>expected &&
 	(sane_unset HOME &&
 	 git check-attr -a f >actual) &&
-	test_cmp expected actual
+	test_must_be_empty actual
 '
 
 test_expect_success '$XDG_CONFIG_HOME overrides $HOME/.config/git/attributes' '
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 30354fd26c..5d955c3bff 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -26,26 +26,22 @@ test_expect_success 'show-ref' '
 	git show-ref refs/tags/A >actual &&
 	test_cmp expect actual &&
 
-	>expect &&
-
 	test_must_fail git show-ref D >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show-ref -q' '
-	>expect &&
-
 	git show-ref -q A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	git show-ref -q tags/A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	git show-ref -q refs/tags/A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref -q D >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show-ref --verify' '
@@ -54,32 +50,28 @@ test_expect_success 'show-ref --verify' '
 	git show-ref --verify refs/tags/A >actual &&
 	test_cmp expect actual &&
 
-	>expect &&
-
 	test_must_fail git show-ref --verify A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify tags/A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify D >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show-ref --verify -q' '
-	>expect &&
-
 	git show-ref --verify -q refs/tags/A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify -q A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify -q tags/A >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify -q D >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show-ref -d' '
@@ -113,19 +105,17 @@ test_expect_success 'show-ref -d' '
 	git show-ref -d --verify refs/heads/master >actual &&
 	test_cmp expect actual &&
 
-	>expect &&
-
 	test_must_fail git show-ref -d --verify master >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref -d --verify heads/master >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify -d A C >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	test_must_fail git show-ref --verify -d tags/A tags/C >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
@@ -178,10 +168,8 @@ test_expect_success 'show-ref --verify HEAD' '
 	git show-ref --verify HEAD >actual &&
 	test_cmp expect actual &&
 
-	>expect &&
-
 	git show-ref --verify -q HEAD >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show-ref --verify with dangling ref' '
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index 349f6e10af..fa3e499641 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -138,8 +138,7 @@ test_expect_success 'branch -d other@{u}' '
 	git checkout -t -b other master &&
 	git branch -d @{u} &&
 	git for-each-ref refs/heads/master >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'checkout other@{u}' '
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
index 26dc3f1fc0..f79dfbbdd6 100755
--- a/t/t2024-checkout-dwim.sh
+++ b/t/t2024-checkout-dwim.sh
@@ -24,9 +24,8 @@ test_branch_upstream () {
 }
 
 status_uno_is_clean () {
-	>status.expect &&
 	git status -uno --porcelain >status.actual &&
-	test_cmp status.expect status.actual
+	test_must_be_empty status.actual
 }
 
 test_expect_success 'setup' '
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index be6e093142..166942c1bd 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -412,9 +412,8 @@ test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
 		git fetch repo_upstream2 &&
 		test_must_fail git worktree add ../foo foo &&
 		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
-		>status.expect &&
 		git status -uno --porcelain >status.actual &&
-		test_cmp status.expect status.actual
+		test_must_be_empty status.actual
 	) &&
 	(
 		cd foo &&
diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh
index 17744e8c57..9ee659098c 100755
--- a/t/t2202-add-addremove.sh
+++ b/t/t2202-add-addremove.sh
@@ -48,8 +48,7 @@ test_expect_success 'Just "git add" is a no-op' '
 	>will-not-be-added &&
 	git add &&
 	git diff-index --name-status --cached HEAD >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 3fc484e8c3..3b47647ed5 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -210,8 +210,7 @@ test_expect_success 'subdirectory ignore (toplevel)' '
 		cd top &&
 		git ls-files -o --exclude-standard
 	) >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'subdirectory ignore (l1/l2)' '
@@ -219,8 +218,7 @@ test_expect_success 'subdirectory ignore (l1/l2)' '
 		cd top/l1/l2 &&
 		git ls-files -o --exclude-standard
 	) >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'subdirectory ignore (l1)' '
@@ -228,8 +226,7 @@ test_expect_success 'subdirectory ignore (l1)' '
 		cd top/l1 &&
 		git ls-files -o --exclude-standard
 	) >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show/hide empty ignored directory (setup)' '
@@ -251,8 +248,7 @@ test_expect_success 'hide empty ignored directory with --no-empty-directory' '
 		cd top &&
 		git ls-files -o -i --exclude l1 --directory --no-empty-directory
 	) >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'show/hide empty ignored sub-directory (setup)' '
@@ -277,8 +273,7 @@ test_expect_success 'hide empty ignored sub-directory with --no-empty-directory'
 		cd top &&
 		git ls-files -o -i --exclude l1 --directory --no-empty-directory
 	) >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'pattern matches prefix completely' '
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
index dce102130f..46aca0af10 100755
--- a/t/t3070-wildmatch.sh
+++ b/t/t3070-wildmatch.sh
@@ -101,8 +101,7 @@ match_with_ls_files() {
 
 	match_stdout_stderr_cmp="
 		tr -d '\0' <actual.raw >actual &&
-		>expect.err &&
-		test_cmp expect.err actual.err &&
+		test_must_be_empty actual.err &&
 		test_cmp expect actual"
 
 	if test "$match_expect" = 'E'
diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh
index 0ef1b6fdcc..0ea4fc4694 100755
--- a/t/t3201-branch-contains.sh
+++ b/t/t3201-branch-contains.sh
@@ -48,16 +48,14 @@ test_expect_success 'branch --contains master' '
 test_expect_success 'branch --no-contains=master' '
 
 	git branch --no-contains=master >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
 test_expect_success 'branch --no-contains master' '
 
 	git branch --no-contains master >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
@@ -94,8 +92,7 @@ test_expect_success 'branch --contains with pattern implies --list' '
 test_expect_success 'branch --no-contains with pattern implies --list' '
 
 	git branch --no-contains=master master >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
@@ -123,8 +120,7 @@ test_expect_success 'branch --merged with pattern implies --list' '
 test_expect_success 'side: branch --no-merged' '
 
 	git branch --no-merged >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
@@ -152,8 +148,7 @@ test_expect_success 'master: branch --no-merged' '
 test_expect_success 'branch --no-merged with pattern implies --list' '
 
 	git branch --no-merged=master master >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 
 '
 
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 618750167a..37729ba258 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -188,9 +188,8 @@ test_expect_success 'git add --refresh with pathspec' '
 	git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo &&
 	echo "100644 $H 3	foo" | git update-index --index-info &&
 	test-tool chmtime -60 bar baz &&
-	>expect &&
 	git add --refresh bar >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	git diff-files --name-only >actual &&
 	! grep bar actual&&
diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh
index 26dd5b7f78..54ce19e353 100755
--- a/t/t3910-mac-os-precompose.sh
+++ b/t/t3910-mac-os-precompose.sh
@@ -187,9 +187,8 @@ test_expect_failure 'handle existing decomposed filenames' '
 	echo content >"verbatim.$Adiarnfd" &&
 	git -c core.precomposeunicode=false add "verbatim.$Adiarnfd" &&
 	git commit -m "existing decomposed file" &&
-	>expect &&
 	git ls-files --exclude-standard -o "verbatim*" >untracked &&
-	test_cmp expect untracked
+	test_must_be_empty untracked
 '
 
 # Test if the global core.precomposeunicode stops autosensing
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index b7f25071cf..281f8fad0c 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -74,8 +74,7 @@ test_expect_success 'diff-tree pathspec' '
 	tree2=$(git write-tree) &&
 	echo "$tree2" &&
 	git diff-tree -r --name-only $tree $tree2 -- pa path1/a >current &&
-	>expected &&
-	test_cmp expected current
+	test_must_be_empty current
 '
 
 test_expect_success 'diff-tree with wildcard shows dir also matches' '
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 41facf7abf..62ed0232d7 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -93,21 +93,20 @@ test_expect_success 'another test, without options' '
 	git diff >out &&
 	test_cmp expect out &&
 
-	>expect &&
 	git diff -w >out &&
-	test_cmp expect out &&
+	test_must_be_empty out &&
 
 	git diff -w -b >out &&
-	test_cmp expect out &&
+	test_must_be_empty out &&
 
 	git diff -w --ignore-space-at-eol >out &&
-	test_cmp expect out &&
+	test_must_be_empty out &&
 
 	git diff -w -b --ignore-space-at-eol >out &&
-	test_cmp expect out &&
+	test_must_be_empty out &&
 
 	git diff -w --ignore-cr-at-eol >out &&
-	test_cmp expect out &&
+	test_must_be_empty out &&
 
 	tr "Q_" "\015 " <<-\EOF >expect &&
 	diff --git a/x b/x
@@ -182,8 +181,7 @@ test_expect_success 'ignore-blank-lines: only new lines' '
 	test_seq 5 | sed "/3/i\\
 " >x &&
 	git diff --ignore-blank-lines >out &&
-	>expect &&
-	test_cmp expect out
+	test_must_be_empty out
 '
 
 test_expect_success 'ignore-blank-lines: only new lines with space' '
@@ -192,8 +190,7 @@ test_expect_success 'ignore-blank-lines: only new lines with space' '
 	test_seq 5 | sed "/3/i\\
  " >x &&
 	git diff -w --ignore-blank-lines >out &&
-	>expect &&
-	test_cmp expect out
+	test_must_be_empty out
 '
 
 test_expect_success 'ignore-blank-lines: after change' '
diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh
index 23c0e357a7..53ac44b0f0 100755
--- a/t/t4039-diff-assume-unchanged.sh
+++ b/t/t4039-diff-assume-unchanged.sh
@@ -34,9 +34,8 @@ test_expect_success POSIXPERM 'find-copies-harder is not confused by mode bits'
 	git add exec &&
 	git commit -m exec &&
 	git update-index --assume-unchanged exec &&
-	>expect &&
 	git diff-files --find-copies-harder -- exec >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 8417e5a4b1..65da74c766 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -267,8 +267,7 @@ rerere_gc_custom_expiry_test () {
 		git -c "gc.rerereresolved=$right_now" \
 		    -c "gc.rerereunresolved=$right_now" rerere gc &&
 		find .git/rr-cache -type f | sort >actual &&
-		>expect &&
-		test_cmp expect actual
+		test_must_be_empty actual
 	'
 }
 
@@ -536,9 +535,8 @@ test_expect_success 'multiple identical conflicts' '
 
 	# We resolved file1 and file2
 	git rerere &&
-	>expect &&
 	git rerere remaining >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	# We must have recorded both of them
 	count_pre_post 2 2 &&
@@ -548,9 +546,8 @@ test_expect_success 'multiple identical conflicts' '
 	test_must_fail git merge six.1 &&
 	git rerere &&
 
-	>expect &&
 	git rerere remaining >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	concat_insert short 6.1 6.2 >file1.expect &&
 	concat_insert long 6.1 6.2 >file2.expect &&
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 25b1f8cc73..0b1cd3408b 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -340,10 +340,9 @@ test_expect_success PCRE 'log -F -E --perl-regexp --grep=<pcre> uses PCRE' '
 '
 
 test_expect_success 'log with grep.patternType configuration' '
-	>expect &&
 	git -c grep.patterntype=fixed \
 	log -1 --pretty=tformat:%s --grep=s.c.nd >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'log with grep.patternType configuration and command line' '
@@ -1625,9 +1624,8 @@ test_expect_success 'log diagnoses bogus HEAD' '
 '
 
 test_expect_success 'log does not default to HEAD when rev input is given' '
-	>expect &&
 	git log --branches=does-not-exist >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'set up --source tests' '
diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh
index e585fe6129..7c519436ef 100755
--- a/t/t4210-log-i18n.sh
+++ b/t/t4210-log-i18n.sh
@@ -44,15 +44,13 @@ test_expect_success !MINGW 'log --grep searches in log output encoding (latin1)'
 '
 
 test_expect_success !MINGW 'log --grep does not find non-reencoded values (utf8)' '
-	>expect &&
 	git log --encoding=utf8 --format=%s --grep=$latin1_e >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'log --grep does not find non-reencoded values (latin1)' '
-	>expect &&
 	git log --encoding=ISO-8859-1 --format=%s --grep=$utf8_e >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index 2d22a17c4a..6ee4d3f2d9 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -309,9 +309,8 @@ test_expect_success 'pack reuse respects --honor-pack-keep' '
 	done &&
 	reusable_pack --honor-pack-keep >empty.pack &&
 	git index-pack empty.pack &&
-	>expect &&
 	git show-index <empty.idx >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'pack reuse respects --local' '
@@ -319,17 +318,15 @@ test_expect_success 'pack reuse respects --local' '
 	test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" &&
 	reusable_pack --local >empty.pack &&
 	git index-pack empty.pack &&
-	>expect &&
 	git show-index <empty.idx >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'pack reuse respects --incremental' '
 	reusable_pack --incremental >empty.pack &&
 	git index-pack empty.pack &&
-	>expect &&
 	git show-index <empty.idx >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'truncated bitmap fails gracefully' '
diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh
index 4fe4ad9d61..f1708d415e 100755
--- a/t/t5313-pack-bounds-checks.sh
+++ b/t/t5313-pack-bounds-checks.sh
@@ -90,9 +90,8 @@ test_expect_success 'matched bogus object count' '
 
 	# Unlike above, we should notice early that the .idx is totally
 	# bogus, and not even enumerate its contents.
-	>expect &&
 	git cat-file --batch-all-objects --batch-check >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	# But as before, we can do the same object-access checks.
 	test_must_fail git cat-file blob $object &&
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 11e14a1e0f..6edec8fed7 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,8 +74,7 @@ test_expect_success 'add another remote' '
 		git for-each-ref "--format=%(refname)" refs/remotes |
 		sed -e "/^refs\/remotes\/origin\//d" \
 		    -e "/^refs\/remotes\/second\//d" >actual &&
-		>expect &&
-		test_cmp expect actual
+		test_must_be_empty actual
 	)
 '
 
@@ -112,8 +111,7 @@ test_expect_success C_LOCALE_OUTPUT 'remove remote' '
 		check_remote_track origin master side &&
 		git for-each-ref "--format=%(refname)" refs/remotes |
 		sed -e "/^refs\/remotes\/origin\//d" >actual &&
-		>expect &&
-		test_cmp expect actual
+		test_must_be_empty actual
 	)
 '
 
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index ea020040e8..bc5703ff9b 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -155,14 +155,12 @@ test_expect_success 'die with non-2 for wrong repository even with --exit-code'
 
 test_expect_success 'Report success even when nothing matches' '
 	git ls-remote other.git "refs/nsn/*" >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'Report no-match with --exit-code' '
 	test_expect_code 2 git ls-remote --exit-code other.git "refs/nsn/*" >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'Report match with --exit-code' '
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 4b4b6673b8..0030c92e1a 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -152,7 +152,6 @@ test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
 '
 
 test_expect_success 'git fetch --all --no-tags' '
-	>expect &&
 	git clone one test5 &&
 	git clone test5 test6 &&
 	(cd test5 && git tag test-tag) &&
@@ -161,7 +160,7 @@ test_expect_success 'git fetch --all --no-tags' '
 		git fetch --all --no-tags &&
 		git tag >output
 	) &&
-	test_cmp expect test6/output
+	test_must_be_empty test6/output
 '
 
 test_expect_success 'git fetch --all --tags' '
diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh
index d38ecee217..0b0eb1d025 100755
--- a/t/t5533-push-cas.sh
+++ b/t/t5533-push-cas.sh
@@ -142,9 +142,8 @@ test_expect_success 'push to delete (protected, forced)' '
 		cd dst &&
 		git push --force --force-with-lease=master:master^ origin :master
 	) &&
-	>expect &&
 	git ls-remote src refs/heads/master >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'push to delete (allowed)' '
@@ -154,9 +153,8 @@ test_expect_success 'push to delete (allowed)' '
 		git push --force-with-lease=master origin :master 2>err &&
 		grep deleted err
 	) &&
-	>expect &&
 	git ls-remote src refs/heads/master >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'cover everything with default force-with-lease (protected)' '
diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh
index fac5a73851..5582b3d5fd 100755
--- a/t/t5612-clone-refspec.sh
+++ b/t/t5612-clone-refspec.sh
@@ -97,8 +97,7 @@ test_expect_success 'clone with --no-tags' '
 		git fetch &&
 		git for-each-ref refs/tags >../actual
 	) &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success '--single-branch while HEAD pointing at master' '
@@ -140,8 +139,7 @@ test_expect_success '--single-branch while HEAD pointing at master and --no-tags
 		git fetch &&
 		git for-each-ref refs/tags >../actual
 	) &&
-	>expect &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 	test_line_count = 0 actual &&
 	# get tags with --tags overrides tagOpt
 	(
@@ -230,8 +228,7 @@ test_expect_success '--single-branch with detached' '
 		    -e "s|/remotes/origin/|/heads/|" >../actual
 	) &&
 	# nothing
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index 969e4e9e52..fb4d295aa0 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -58,8 +58,7 @@ test_expect_success 'rev-list A..B and rev-list ^A B are the same' '
 
 test_expect_success 'propagate uninteresting flag down correctly' '
 	git rev-list --objects ^HEAD^{tree} HEAD^{tree} >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'symleft flag bit is propagated down from tag' '
diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh
index 20e3e2554a..916d9692bc 100755
--- a/t/t6009-rev-list-parent.sh
+++ b/t/t6009-rev-list-parent.sh
@@ -31,8 +31,7 @@ test_expect_success setup '
 test_expect_success 'one is ancestor of others and should not be shown' '
 
 	git rev-list one --not four >result &&
-	>expect &&
-	test_cmp expect result
+	test_must_be_empty result
 
 '
 
@@ -144,8 +143,7 @@ test_expect_success 'ancestors with the same commit time' '
 		test_commit t$i
 	done &&
 	git rev-list t1^! --not t$i >result &&
-	>expect &&
-	test_cmp expect result
+	test_must_be_empty result
 '
 
 test_done
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index d3453c583c..02936c2f24 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -256,31 +256,27 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
 '
 
 test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
-	>expect &&
 	git rev-list --stdin <expect >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'rev-list should succeed with empty output with all refs excluded' '
-	>expect &&
 	git rev-list --exclude=* --all >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'rev-list should succeed with empty output with empty --all' '
 	(
 		test_create_repo empty &&
 		cd empty &&
-		>expect &&
 		git rev-list --all >actual &&
-		test_cmp expect actual
+		test_must_be_empty actual
 	)
 '
 
 test_expect_success 'rev-list should succeed with empty output with empty glob' '
-	>expect &&
 	git rev-list --glob=does-not-match-anything >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'shortlog accepts --glob/--tags/--remotes' '
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
index dabebaee0b..beadaf6cca 100755
--- a/t/t6019-rev-list-ancestry-path.sh
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -95,10 +95,9 @@ test_expect_success 'rev-list --ancestry-path F...I' '
 
 # G.t is dropped in an "-s ours" merge
 test_expect_success 'rev-list G..M -- G.t' '
-	>expect &&
 	git rev-list --format=%s G..M -- G.t |
 	sed -e "/^commit /d" >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index b760c223c6..53cc9b2ffb 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -893,8 +893,7 @@ test_expect_success 'do not follow renames for empty files' '
 	git mv empty1 empty2 &&
 	git commit -m rename &&
 	test_must_fail git merge empty-base &&
-	>expect &&
-	test_cmp expect empty2
+	test_must_be_empty empty2
 '
 
 test_done
diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh
index debadbd299..ddf34f0115 100755
--- a/t/t6060-merge-index.sh
+++ b/t/t6060-merge-index.sh
@@ -44,8 +44,7 @@ test_expect_success 'read-tree does not resolve content merge' '
 test_expect_success 'git merge-index git-merge-one-file resolves' '
 	git merge-index git-merge-one-file -a &&
 	git diff-files --name-only --diff-filter=U >unmerged &&
-	>expect &&
-	test_cmp expect unmerged &&
+	test_must_be_empty unmerged &&
 	test_cmp expect-merged file &&
 	git cat-file blob :file >file-index &&
 	test_cmp expect-merged file-index
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index e0496da812..024f8c06f7 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -796,9 +796,8 @@ test_expect_success ':remotename and :remoteref' '
 '
 
 test_expect_success 'for-each-ref --ignore-case ignores case' '
-	>expect &&
 	git for-each-ref --format="%(refname)" refs/heads/MASTER >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	echo refs/heads/master >expect &&
 	git for-each-ref --format="%(refname)" --ignore-case \
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index d7b319e919..93a6694f0e 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -693,9 +693,8 @@ test_expect_success \
 '
 
 test_expect_success 'The -n 100 invocation means -n --list 100, not -n100' '
-	>expect &&
 	git tag -n 100 >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	git tag -m "A msg" 100 &&
 	echo "100             A msg" >expect &&
@@ -974,9 +973,8 @@ test_expect_success GPG 'verifying a proper tag with --format pass and format ac
 '
 
 test_expect_success GPG 'verifying a forged tag with --format should fail silently' '
-	>expect &&
 	test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 # blank and empty messages for signed tags:
@@ -1382,9 +1380,8 @@ test_expect_success 'message in editor has initial comment: first line' '
 test_expect_success \
 	'message in editor has initial comment: remainder' '
 	# remove commented lines from the remainder -- should be empty
-	>rest.expect &&
 	sed -e 1d -e "/^#/d" <actual >rest.actual &&
-	test_cmp rest.expect rest.actual
+	test_must_be_empty rest.actual
 '
 
 get_tag_header reuse $commit commit $time >expect
@@ -1466,19 +1463,18 @@ test_expect_success 'checking that first commit is in all tags (relative)' "
 
 # All the --contains tests above, but with --no-contains
 test_expect_success 'checking that first commit is not listed in any tag with --no-contains  (hash)' "
-	>expected &&
 	git tag -l --no-contains $hash1 v* >actual &&
-	test_cmp expected actual
+	test_must_be_empty actual
 "
 
 test_expect_success 'checking that first commit is in all tags (tag)' "
 	git tag -l --no-contains v1.0 v* >actual &&
-	test_cmp expected actual
+	test_must_be_empty actual
 "
 
 test_expect_success 'checking that first commit is in all tags (relative)' "
 	git tag -l --no-contains HEAD~2 v* >actual &&
-	test_cmp expected actual
+	test_must_be_empty actual
 "
 
 cat > expected <<EOF
@@ -1606,9 +1602,8 @@ test_expect_success 'checking that --contains can be used in non-list mode' '
 '
 
 test_expect_success 'checking that initial commit is in all tags with --no-contains' "
-	>expected &&
 	git tag -l --no-contains $hash1 v* >actual &&
-	test_cmp expected actual
+	test_must_be_empty actual
 "
 
 # mixing modes and options:
@@ -1905,7 +1900,6 @@ test_expect_success 'version sort with very long prerelease suffix' '
 '
 
 test_expect_success ULIMIT_STACK_SIZE '--contains and --no-contains work in a deep repo' '
-	>expect &&
 	i=1 &&
 	while test $i -lt 8000
 	do
@@ -1920,7 +1914,7 @@ EOF"
 	git checkout master &&
 	git tag far-far-away HEAD^ &&
 	run_with_limited_stack git tag --contains HEAD >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 	run_with_limited_stack git tag --no-contains HEAD >actual &&
 	test_line_count "-gt" 10 actual
 '
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index 7541ba5edb..00e09a375c 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -626,12 +626,11 @@ test_expect_success TTY 'sub-commands of externals use their own pager' '
 
 test_expect_success TTY 'external command pagers override sub-commands' '
 	sane_unset PAGER GIT_PAGER &&
-	>expect &&
 	>actual &&
 	test_config pager.external false &&
 	test_config pager.log "sed s/^/log:/ >actual" &&
 	test_terminal git --exec-path=. external log --format=%s -1 &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'command with underscores does not complain' '
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 291a1e2b07..1f068714c5 100755
--- a/t/t7030-verify-tag.sh
+++ b/t/t7030-verify-tag.sh
@@ -134,9 +134,8 @@ test_expect_success GPG 'verifying tag with --format' '
 '
 
 test_expect_success GPG 'verifying a forged tag with --format should fail silently' '
-	>expect &&
 	test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
-	test_cmp expect actual-forged
+	test_must_be_empty actual-forged
 '
 
 test_done
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index c61e304e97..6d8256a424 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -26,9 +26,8 @@ avoid_racy() {
 }
 
 status_is_clean() {
-	>../status.expect &&
 	git status --porcelain >../status.actual &&
-	test_cmp ../status.expect ../status.actual
+	test_must_be_empty ../status.actual
 }
 
 test_lazy_prereq UNTRACKED_CACHE '
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index 95653a08ca..97be0d968d 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -549,8 +549,7 @@ test_expect_success 'reset -N keeps removed files as intent-to-add' '
 
 	tree=$(git write-tree) &&
 	git ls-tree $tree new-file >actual &&
-	>expect &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 
 	git diff --name-only >actual &&
 	echo new-file >expect &&
@@ -563,9 +562,8 @@ test_expect_success 'reset --mixed sets up work tree' '
 		cd mixed_worktree &&
 		test_commit dummy
 	) &&
-	: >expect &&
 	git --git-dir=mixed_worktree/.git --work-tree=mixed_worktree reset >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_done
diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh
index 0f95f00477..ecb85c3b82 100755
--- a/t/t7106-reset-unborn-branch.sh
+++ b/t/t7106-reset-unborn-branch.sh
@@ -12,9 +12,8 @@ test_expect_success 'reset' '
 	git add a b &&
 	git reset &&
 
-	>expect &&
 	git ls-files >actual &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'reset HEAD' '
@@ -39,9 +38,8 @@ test_expect_success PERL 'reset -p' '
 	echo y >yes &&
 	git reset -p <yes >output &&
 
-	>expect &&
 	git ls-files >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 	test_i18ngrep "Unstage" output
 '
 
@@ -61,9 +59,8 @@ test_expect_success 'reset --hard' '
 	test_when_finished "echo a >a" &&
 	git reset --hard &&
 
-	>expect &&
 	git ls-files >actual &&
-	test_cmp expect actual &&
+	test_must_be_empty actual &&
 	test_path_is_missing a
 '
 
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index 4e4c455502..1cd12b38c5 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -64,8 +64,7 @@ test_expect_success 'added submodule (subdirectory only)' "
 		cd sub &&
 		git submodule summary . >../actual
 	) &&
-	>expected &&
-	test_cmp expected actual
+	test_must_be_empty actual
 "
 
 test_expect_success 'added submodule (subdirectory with explicit path)' "
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index d33a3cb331..ca4a740da0 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -393,7 +393,6 @@ EOF
 
 test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
 	>.git/result &&
-	>expect &&
 
 	echo >>negative &&
 	(
@@ -403,7 +402,7 @@ test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
 		export GIT_EDITOR &&
 		test_must_fail git commit -e -m sample -a
 	) &&
-	test_cmp expect .git/result
+	test_must_be_empty .git/result
 '
 
 test_expect_success 'do not fire editor if -m <msg> was given' '
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index 047156e9d5..b18503de81 100755
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
@@ -620,8 +620,7 @@ test_expect_success 'file with no base' '
 	git checkout -b test$test_count branch1 &&
 	test_must_fail git merge master &&
 	git mergetool --no-prompt --tool mybase -- both &&
-	>expected &&
-	test_cmp expected both
+	test_must_be_empty both
 '
 
 test_expect_success 'custom commands override built-ins' '
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index dcaab1557b..d826e24b45 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -691,8 +691,7 @@ test_expect_success 'log grep (5)' '
 
 test_expect_success 'log grep (6)' '
 	git log --author=-0700  --pretty=tformat:%s >actual &&
-	>expect &&
-	test_cmp expect actual
+	test_must_be_empty actual
 '
 
 test_expect_success 'log grep (7)' '
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index b8e919e25d..1ef1a19003 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -253,10 +253,9 @@ test_suppress_self () {
 
 	mv msgtxt1 msgtxt1-$3 &&
 	sed -e '/^$/q' msgtxt1-$3 >"msghdr1-$3" &&
-	>"expected-no-cc-$3" &&
 
 	(grep '^Cc:' msghdr1-$3 >"actual-no-cc-$3";
-	 test_cmp expected-no-cc-$3 actual-no-cc-$3)
+	 test_must_be_empty actual-no-cc-$3)
 }
 
 test_suppress_self_unquoted () {
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 9e7f96223d..f8f869e26a 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -2191,12 +2191,11 @@ test_expect_success 'R: --import-marks-if-exists' '
 
 test_expect_success 'R: feature import-marks-if-exists' '
 	rm -f io.marks &&
-	>expect &&
 
 	git fast-import --export-marks=io.marks <<-\EOF &&
 	feature import-marks-if-exists=not_io.marks
 	EOF
-	test_cmp expect io.marks &&
+	test_must_be_empty io.marks &&
 
 	blob=$(echo hi | git hash-object --stdin) &&
 
@@ -2227,13 +2226,11 @@ test_expect_success 'R: feature import-marks-if-exists' '
 	EOF
 	test_cmp expect io.marks &&
 
-	>expect &&
-
 	git fast-import --import-marks-if-exists=not_io.marks \
 			--export-marks=io.marks <<-\EOF &&
 	feature import-marks-if-exists=io.marks
 	EOF
-	test_cmp expect io.marks
+	test_must_be_empty io.marks
 '
 
 test_expect_success 'R: import to output marks works without any content' '
-- 
2.18.0.345.g5c9ce644c3


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] tests: make use of the test_must_be_empty function
  2018-07-27 17:48                         ` [PATCH] tests: make use of the test_must_be_empty function Ævar Arnfjörð Bjarmason
@ 2018-07-27 21:50                           ` Junio C Hamano
  2018-07-31  0:17                           ` SZEDER Gábor
  2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
  2 siblings, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-07-27 21:50 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, SZEDER Gábor

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Change various tests that use an idiom of the form:
>
>     >expect &&
>     test_cmp expect actual
>
> To instead use:
>
>     test_must_be_empty actual

I trust that you eyeballed the result to make sure steps after these
converted parts do *not* depend on (or expect) the presence of an
empty 'expect' file (e.g. ".gitignore" has 'expect' listed, or the
expected output from "status" has expected in the untracked section)?

I'd imagine that kind of sanity checking the result would have taken
10x more time than the actual conversion itself.  

Very much appreciated.

> This patch is on top of "next" since one of the things being fixed up
> is a test in my in-flight ab/checkout-default-remote series. It also
> applies cleanly to "pu", and the only conflicts with "master" are due
> to that series of mine.

A patch that truly depends on all of 'next' won't have a chance to
graduate, but it seems you only need one topic out of it.

If I were you in such a situation I would have prepared two patches,
i.e. one to apply on 'master', and the remainder to apply on tip of
ab/checkout-default-remote, and made sure that applying the former
on 'master' and merging the updated ab/checkout-default-remote would
match the tree you would get by merging ab/checkout-default-remote
to 'master' and applying _this_ patch.

Let's see if I can tease them apart myself first.

Thanks.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] tests: make use of the test_must_be_empty function
  2018-07-27 17:48                         ` [PATCH] tests: make use of the test_must_be_empty function Ævar Arnfjörð Bjarmason
  2018-07-27 21:50                           ` Junio C Hamano
@ 2018-07-31  0:17                           ` SZEDER Gábor
  2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
  2 siblings, 0 replies; 95+ messages in thread
From: SZEDER Gábor @ 2018-07-31  0:17 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Git mailing list, Junio C Hamano

On Fri, Jul 27, 2018 at 7:48 PM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
> Change various tests that use an idiom of the form:
>
>     >expect &&
>     test_cmp expect actual
>
> To instead use:
>
>     test_must_be_empty actual

Thanks for working on this.

> The test_must_be_empty() wrapper was introduced in ca8d148daf ("test:
> test_must_be_empty helper", 2013-06-09). Many of these tests have been
> added after that time. This was mostly found with, and manually pruned
> from:
>
>     git grep '^\s+>.*expect.* &&$' t

This command doesn't output anything as it is, it should be 'git grep
-E ...'.

Furthermore, it doesn't catch the cases when the empty expected file
is written as ': > expect'.  I see that you noticed and converted a
few of these, too, but running

  git grep ': >.*exp.*&&' t/

turns up some more.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-07-27 17:48                         ` [PATCH] tests: make use of the test_must_be_empty function Ævar Arnfjörð Bjarmason
  2018-07-27 21:50                           ` Junio C Hamano
  2018-07-31  0:17                           ` SZEDER Gábor
@ 2018-08-22 17:48                           ` SZEDER Gábor
  2018-08-22 17:53                             ` Eric Sunshine
                                               ` (2 more replies)
  2 siblings, 3 replies; 95+ messages in thread
From: SZEDER Gábor @ 2018-08-22 17:48 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Ævar Arnfjörð Bjarmason, SZEDER Gábor

Prior to d3c6751b18 (tests: make use of the test_must_be_empty
function, 2018-07-27), in the test 'rev-list should succeed with empty
output on empty stdin' in 't6018-rev-list-glob' the empty 'expect'
file served dual purpose: besides specifying the expected output, as
usual, it also served as empty input for 'git rev-list --stdin'.

Then d3c6751b18 came along, and, as part of the conversion to
'test_must_be_empty', removed this empty 'expect' file, not realizing
its secondary purpose.  Redirecting stdin from the now non-existing
file failed the test, but since this test expects failure in the first
place, this issue went unnoticed.

Redirect 'git rev-list's stdin explicitly from /dev/null to provide
empty input.  (Strictly speaking we don't need this redirection,
because the test script's stdin is already redirected from /dev/null
anyway, but I think it's better to be explicit about it.)

Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
---
 t/t6018-rev-list-glob.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 02936c2f24..0bf10d0686 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -256,7 +256,7 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
 '
 
 test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
-	git rev-list --stdin <expect >actual &&
+	git rev-list --stdin </dev/null >actual &&
 	test_must_be_empty actual
 '
 
-- 
2.19.0.rc0.136.gd2dd172e64


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
@ 2018-08-22 17:53                             ` Eric Sunshine
  2018-08-22 18:59                               ` SZEDER Gábor
  2018-08-22 18:01                             ` Junio C Hamano
  2018-08-22 18:50                             ` Junio C Hamano
  2 siblings, 1 reply; 95+ messages in thread
From: Eric Sunshine @ 2018-08-22 17:53 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Junio C Hamano, Git List, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 1:48 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
> Prior to d3c6751b18 (tests: make use of the test_must_be_empty
> function, 2018-07-27), in the test 'rev-list should succeed with empty
> output on empty stdin' in 't6018-rev-list-glob' the empty 'expect'
> file served dual purpose: besides specifying the expected output, as
> usual, it also served as empty input for 'git rev-list --stdin'.
>
> Then d3c6751b18 came along, and, as part of the conversion to
> 'test_must_be_empty', removed this empty 'expect' file, not realizing
> its secondary purpose.  Redirecting stdin from the now non-existing
> file failed the test, but since this test expects failure in the first
> place, this issue went unnoticed.

Can you say a word or two (here in the email thread) about how you're
finding these failures (across the various test fixes you've posted
recently)? Are you instrumenting the code in some fashion? Or, finding
them by visual inspection?

Thanks.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
  2018-08-22 17:53                             ` Eric Sunshine
@ 2018-08-22 18:01                             ` Junio C Hamano
  2018-08-22 18:50                             ` Junio C Hamano
  2 siblings, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-08-22 18:01 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: git, Ævar Arnfjörð Bjarmason

SZEDER Gábor <szeder.dev@gmail.com> writes:

> Prior to d3c6751b18 (tests: make use of the test_must_be_empty
> function, 2018-07-27), in the test 'rev-list should succeed with empty
> output on empty stdin' in 't6018-rev-list-glob' the empty 'expect'
> file served dual purpose: besides specifying the expected output, as
> usual, it also served as empty input for 'git rev-list --stdin'.

Thanks for a correction to a breakage on 'master' that was
introduced during this cycle.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
  2018-08-22 17:53                             ` Eric Sunshine
  2018-08-22 18:01                             ` Junio C Hamano
@ 2018-08-22 18:50                             ` Junio C Hamano
  2018-08-22 19:23                               ` Jeff King
  2 siblings, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-08-22 18:50 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: git, Ævar Arnfjörð Bjarmason, Jeff King

SZEDER Gábor <szeder.dev@gmail.com> writes:

> Redirect 'git rev-list's stdin explicitly from /dev/null to provide
> empty input.  (Strictly speaking we don't need this redirection,
> because the test script's stdin is already redirected from /dev/null
> anyway, but I think it's better to be explicit about it.)

Yes.

>  test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
> -	git rev-list --stdin <expect >actual &&
> +	git rev-list --stdin </dev/null >actual &&
>  	test_must_be_empty actual
>  '

By the way, it may be about time to turn that expect-failure into
expect-success.  It is somewhat unfortunate that 0c5dc743 ("t6018:
flesh out empty input/output rev-list tests", 2017-08-02) removed
the comment that said "we _might_ want to change the behaviour in
these cases" and explained the tests as reminders, anticipating that
the series will change the behaviour for three cases where the
pending list ends up empty to make the discussion moot, but it
changed the behaviour of only two of them, leaving the "--stdin
reads empty" case behind.

It may be just the matter of doing something like the attached
patch.  I won't be committing such a behaviour change during the
pre-release feature freeze, but we may want to consider doing this
early in the next cycle.

 revision.c               | 13 +++++++++++++
 t/t6018-rev-list-glob.sh |  4 ++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/revision.c b/revision.c
index d12e6d8a4a..21fb413511 100644
--- a/revision.c
+++ b/revision.c
@@ -2441,6 +2441,19 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 		object = get_reference(revs, revs->def, &oid, 0);
 		add_pending_object_with_mode(revs, object, revs->def, oc.mode);
 	}
+	/* 
+	 * Even if revs->pending is empty after all the above, if we
+	 * handled "--stdin", then the caller really meant to give us
+	 * an empty commit range.  Just let the traversal give an
+	 * empty result without causing a "no input?  do you know how
+	 * to use this command?" failure.
+	 *
+	 * NOTE!!!  Because "--stdin </dev/null --default HEAD" should
+	 * default to HEAD, this must come _after_ the above block
+	 * that deals with revs->ref fallback.
+	 */
+	if (read_from_stdin)
+		revs->rev_input_given = 1;
 
 	/* Did the user ask for any diff output? Run the diff! */
 	if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 02936c2f24..db8a7834d8 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -255,8 +255,8 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
 	compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
 '
 
-test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
+test_expect_success 'rev-list should succeed with empty output on empty stdin' '
 	git rev-list --stdin </dev/null >actual &&
 	test_must_be_empty actual
 '
 

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 17:53                             ` Eric Sunshine
@ 2018-08-22 18:59                               ` SZEDER Gábor
  2018-08-22 20:30                                 ` Eric Sunshine
  0 siblings, 1 reply; 95+ messages in thread
From: SZEDER Gábor @ 2018-08-22 18:59 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Junio C Hamano, Git mailing list, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 7:53 PM Eric Sunshine <sunshine@sunshineco.com> wrote:

> Can you say a word or two (here in the email thread) about how you're
> finding these failures (across the various test fixes you've posted
> recently)? Are you instrumenting the code in some fashion? Or, finding
> them by visual inspection?

Errors from system commands in our tests look like these:

  grep: file3: No such file or directory
  sed: -e expression #1, char 2: extra characters after command
  diff: sub1/.git: No such file or directory
  tar: rmtlseek not stopped at a record boundary
  tar: Error is not recoverable: exiting now

while errors from the shell running the test like these:

  t0020-crlf.sh: 8: eval: cannot open two: No such file
  t6018-rev-list-glob.sh: 4: eval: cannot open expect: No such file
  t7408-submodule-reference.sh: 615: test: =: unexpected operator

i.e. lines starting with various system commands' or test scripts'
names, followed by ': '.

So I've modified t/Makefile to not remove the 'test-results' directory
after a successful 'make test':

diff --git a/t/Makefile b/t/Makefile
index ea36cf7ac7..c7b1655593 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -54,10 +54,11 @@ pre-clean:
        $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'

 clean-except-prove-cache:
-       $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
+       $(RM) -r 'trash directory'.*
        $(RM) -r valgrind/bin

 clean: clean-except-prove-cache
+       $(RM) '$(TEST_RESULTS_DIRECTORY_SQ)'

 distclean: clean
        $(RM) .prove

And then scanned the results of a '--verbose-log -x' test run with:

  grep -E '^(awk|basename|cat|cd|chmod|cmp|cp|cut|diff|dirname|egrep|find|fgrep|grep|gunzip|gzip|ln|mkdir|mkfifo|mktemp|mv|readlink|rmdir|sed|sort|tar|touch|tr|ulimit|umask|uniq|unzip|wc|zipinfo|t[0-9][0-9][0-9][0-9]-[^:]*\.sh):
' test-results/*.out

and then, for lack of something better to do ;), I started looking at
the simpler looking errors.

I've though about how a check like this could be automated, but
haven't had any workable idea yet.  There are commands that can
legitimately print errors, e.g. when checking for a prereq which the
system doesn't have (e.g. the 'tar' errors above, I think).  And the
list of system commands in the grep pattern above is surely incomplete
and will likely change in the future...

^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 18:50                             ` Junio C Hamano
@ 2018-08-22 19:23                               ` Jeff King
  2018-08-22 19:50                                 ` [PATCH] rev-list: make empty --stdin not an error Jeff King
  0 siblings, 1 reply; 95+ messages in thread
From: Jeff King @ 2018-08-22 19:23 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 11:50:46AM -0700, Junio C Hamano wrote:

> >  test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
> > -	git rev-list --stdin <expect >actual &&
> > +	git rev-list --stdin </dev/null >actual &&
> >  	test_must_be_empty actual
> >  '
> 
> By the way, it may be about time to turn that expect-failure into
> expect-success.  It is somewhat unfortunate that 0c5dc743 ("t6018:
> flesh out empty input/output rev-list tests", 2017-08-02) removed
> the comment that said "we _might_ want to change the behaviour in
> these cases" and explained the tests as reminders, anticipating that
> the series will change the behaviour for three cases where the
> pending list ends up empty to make the discussion moot, but it
> changed the behaviour of only two of them, leaving the "--stdin
> reads empty" case behind.

Yeah, I think I had intended to make --stdin work there, and when I
realized at the end of the series it did not, I failed to go back and
modify that initial commit touching the test.

More discussion in the original thread:

  https://public-inbox.org/git/20170802223416.gwiezhbuxbdmbjzx@sigill.intra.peff.net/

> It may be just the matter of doing something like the attached
> patch.  I won't be committing such a behaviour change during the
> pre-release feature freeze, but we may want to consider doing this
> early in the next cycle.

Yes, definitely this is tricky enough to avoid doing during the freeze.

> diff --git a/revision.c b/revision.c
> index d12e6d8a4a..21fb413511 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -2441,6 +2441,19 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
>  		object = get_reference(revs, revs->def, &oid, 0);
>  		add_pending_object_with_mode(revs, object, revs->def, oc.mode);
>  	}
> +	/* 
> +	 * Even if revs->pending is empty after all the above, if we
> +	 * handled "--stdin", then the caller really meant to give us
> +	 * an empty commit range.  Just let the traversal give an
> +	 * empty result without causing a "no input?  do you know how
> +	 * to use this command?" failure.
> +	 *
> +	 * NOTE!!!  Because "--stdin </dev/null --default HEAD" should
> +	 * default to HEAD, this must come _after_ the above block
> +	 * that deals with revs->ref fallback.
> +	 */
> +	if (read_from_stdin)
> +		revs->rev_input_given = 1;

I think this should do the right thing. All of the issues discussed in
the earlier thread were about using revs->def, and this neatly sidesteps
that by touching the flag afterwards. It's a little funny that the flag
now means two things (earlier in the function it is "we should use the
default" and later it becomes "the caller may complain").

It may be worth splitting it into two flags: rev_input_given and
rev_read_stdin. That puts the responsibility on the caller to check both
flags. But really, rev-list.c is the only caller that checks it anyway.
And it would mean this scary comment can go away. ;)

It also _could_ be useful to other callers to distinguish the two cases
(e.g., if they wanted to know whether they were free to use stdin
themselves). But I don't offhand know of any callers that need that (I
have a vague recollection that it might have come up once over the
years).

-Peff

^ permalink raw reply	[flat|nested] 95+ messages in thread

* [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 19:23                               ` Jeff King
@ 2018-08-22 19:50                                 ` Jeff King
  2018-08-22 20:42                                   ` Junio C Hamano
  0 siblings, 1 reply; 95+ messages in thread
From: Jeff King @ 2018-08-22 19:50 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 03:23:08PM -0400, Jeff King wrote:

> > +	/* 
> > +	 * Even if revs->pending is empty after all the above, if we
> > +	 * handled "--stdin", then the caller really meant to give us
> > +	 * an empty commit range.  Just let the traversal give an
> > +	 * empty result without causing a "no input?  do you know how
> > +	 * to use this command?" failure.
> > +	 *
> > +	 * NOTE!!!  Because "--stdin </dev/null --default HEAD" should
> > +	 * default to HEAD, this must come _after_ the above block
> > +	 * that deals with revs->ref fallback.
> > +	 */
> > +	if (read_from_stdin)
> > +		revs->rev_input_given = 1;
> 
> I think this should do the right thing. All of the issues discussed in
> the earlier thread were about using revs->def, and this neatly sidesteps
> that by touching the flag afterwards. It's a little funny that the flag
> now means two things (earlier in the function it is "we should use the
> default" and later it becomes "the caller may complain").

Here's my "two flags" approach as a real patch. After giving it some
thought, I really do think it's cleaner.

I know we're in a freeze. If you want to pick this up for pu or maybe
next, that's fine. If not, I'll re-send it after the release. It applies
on top of Gábor's text fixup patch.

-- >8 --
Subject: [PATCH] rev-list: make empty --stdin not an error

When we originally did the series that contains 7ba826290a
(revision: add rev_input_given flag, 2017-08-02) the intent
was that "git rev-list --stdin </dev/null" would similarly
become a successful noop. However, an attempt at the time to
do that did not work[1]. The problem is that rev_input_given
serves two roles:

 - it tells rev-list.c that it should not error out

 - it tells revision.c that it should not have the "default"
   ref kick (e.g., "HEAD" in "git log")

We want to trigger the former, but not the latter. This is
technically possible with a single flag, if we set the flag
only after revision.c's revs->def check. But this introduces
a rather subtle ordering dependency.

Instead, let's keep two flags: one to denote when we got
actual input (which triggers both roles) and one for when we
read stdin (which triggers only the first).

This does mean a caller interested in the first role has to
check both flags, but there's only one such caller. And any
future callers might want to make the distinction anyway
(e.g., if they care less about erroring out, and more about
whether revision.c soaked up our stdin).

[1] https://public-inbox.org/git/20170802223416.gwiezhbuxbdmbjzx@sigill.intra.peff.net/

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/rev-list.c       | 2 +-
 revision.c               | 1 +
 revision.h               | 5 +++++
 t/t6018-rev-list-glob.sh | 2 +-
 4 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 5b07f3f4a2..ed0ea7dc5b 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -493,7 +493,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 	if ((!revs.commits && reflog_walk_empty(revs.reflog_info) &&
 	     (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
 	      !revs.pending.nr) &&
-	     !revs.rev_input_given) ||
+	     !revs.rev_input_given && !revs.read_from_stdin) ||
 	    revs.diff)
 		usage(rev_list_usage);
 
diff --git a/revision.c b/revision.c
index de4dce600d..4d53102cf4 100644
--- a/revision.c
+++ b/revision.c
@@ -2369,6 +2369,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 				}
 				if (read_from_stdin++)
 					die("--stdin given twice?");
+				revs->read_from_stdin = 1;
 				read_revisions_from_stdin(revs, &prune_data);
 				continue;
 			}
diff --git a/revision.h b/revision.h
index 007278cc11..1225957927 100644
--- a/revision.h
+++ b/revision.h
@@ -82,6 +82,11 @@ struct rev_info {
 	 */
 	int rev_input_given;
 
+	/*
+	 * Whether we read from stdin due to the --stdin option.
+	 */
+	int read_from_stdin;
+
 	/* topo-sort */
 	enum rev_sort_order sort_order;
 
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 0bf10d0686..db8a7834d8 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -255,7 +255,7 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
 	compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
 '
 
-test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
+test_expect_success 'rev-list should succeed with empty output on empty stdin' '
 	git rev-list --stdin </dev/null >actual &&
 	test_must_be_empty actual
 '
-- 
2.19.0.rc0.412.g7005db4e88


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] t6018-rev-list-glob: fix 'empty stdin' test
  2018-08-22 18:59                               ` SZEDER Gábor
@ 2018-08-22 20:30                                 ` Eric Sunshine
  0 siblings, 0 replies; 95+ messages in thread
From: Eric Sunshine @ 2018-08-22 20:30 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Junio C Hamano, Git List, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 2:59 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
> On Wed, Aug 22, 2018 at 7:53 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > Can you say a word or two (here in the email thread) about how you're
> > finding these failures (across the various test fixes you've posted
> > recently)? Are you instrumenting the code in some fashion? Or, finding
> > them by visual inspection?
>
> Errors from system commands in our tests look like these:
>   grep: file3: No such file or directory
> i.e. lines starting with various system commands' or test scripts'
> names, followed by ': '.
>
> So I've modified t/Makefile to not remove the 'test-results' directory
> after a successful 'make test':
> And then scanned the results of a '--verbose-log -x' test run with:
>   grep -E [...]
> and then, for lack of something better to do ;), I started looking at
> the simpler looking errors.

Thanks for the explanation. That makes a lot of sense.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 19:50                                 ` [PATCH] rev-list: make empty --stdin not an error Jeff King
@ 2018-08-22 20:42                                   ` Junio C Hamano
  2018-08-22 21:37                                     ` Jeff King
  2018-08-22 21:41                                     ` Junio C Hamano
  0 siblings, 2 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-08-22 20:42 UTC (permalink / raw)
  To: Jeff King; +Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

Jeff King <peff@peff.net> writes:

> Instead, let's keep two flags: one to denote when we got
> actual input (which triggers both roles) and one for when we
> read stdin (which triggers only the first).
>
> This does mean a caller interested in the first role has to
> check both flags, but there's only one such caller. And any
> future callers might want to make the distinction anyway
> (e.g., if they care less about erroring out, and more about
> whether revision.c soaked up our stdin).
>
> [1] https://public-inbox.org/git/20170802223416.gwiezhbuxbdmbjzx@sigill.intra.peff.net/
>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Jeff King <peff@peff.net>
> ---
>  builtin/rev-list.c       | 2 +-
>  revision.c               | 1 +
>  revision.h               | 5 +++++
>  t/t6018-rev-list-glob.sh | 2 +-
>  4 files changed, 8 insertions(+), 2 deletions(-)

I think this makes sense, but if we were to give a dedicated field
in the revs structure, can we lose the local variable at the same
time, I wonder?

Thanks.

> diff --git a/revision.c b/revision.c
> index de4dce600d..4d53102cf4 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -2369,6 +2369,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
>  				}
>  				if (read_from_stdin++)
>  					die("--stdin given twice?");
> +				revs->read_from_stdin = 1;
>  				read_revisions_from_stdin(revs, &prune_data);
>  				continue;
>  			}
> diff --git a/revision.h b/revision.h
> index 007278cc11..1225957927 100644
> --- a/revision.h
> +++ b/revision.h
> @@ -82,6 +82,11 @@ struct rev_info {
>  	 */
>  	int rev_input_given;
>  
> +	/*
> +	 * Whether we read from stdin due to the --stdin option.
> +	 */
> +	int read_from_stdin;
> +
>  	/* topo-sort */
>  	enum rev_sort_order sort_order;
>  
> diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
> index 0bf10d0686..db8a7834d8 100755
> --- a/t/t6018-rev-list-glob.sh
> +++ b/t/t6018-rev-list-glob.sh
> @@ -255,7 +255,7 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
>  	compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
>  '
>  
> -test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
> +test_expect_success 'rev-list should succeed with empty output on empty stdin' '
>  	git rev-list --stdin </dev/null >actual &&
>  	test_must_be_empty actual
>  '

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 20:42                                   ` Junio C Hamano
@ 2018-08-22 21:37                                     ` Jeff King
  2018-08-22 21:50                                       ` Junio C Hamano
  2018-08-22 21:41                                     ` Junio C Hamano
  1 sibling, 1 reply; 95+ messages in thread
From: Jeff King @ 2018-08-22 21:37 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 01:42:26PM -0700, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > Instead, let's keep two flags: one to denote when we got
> > actual input (which triggers both roles) and one for when we
> > read stdin (which triggers only the first).
> >
> > This does mean a caller interested in the first role has to
> > check both flags, but there's only one such caller. And any
> > future callers might want to make the distinction anyway
> > (e.g., if they care less about erroring out, and more about
> > whether revision.c soaked up our stdin).
> >
> > [1] https://public-inbox.org/git/20170802223416.gwiezhbuxbdmbjzx@sigill.intra.peff.net/
> >
> > Helped-by: Junio C Hamano <gitster@pobox.com>
> > Signed-off-by: Jeff King <peff@peff.net>
> > ---
> >  builtin/rev-list.c       | 2 +-
> >  revision.c               | 1 +
> >  revision.h               | 5 +++++
> >  t/t6018-rev-list-glob.sh | 2 +-
> >  4 files changed, 8 insertions(+), 2 deletions(-)
> 
> I think this makes sense, but if we were to give a dedicated field
> in the revs structure, can we lose the local variable at the same
> time, I wonder?

Yes. I was thinking it had more purpose than this, but it really is just
a flag to check "did we do this already?". Which is one of the main
purposes I claimed for the new flag in my commit message. :)

Here it is with that squashed in (this is the whole patch, since I
updated the commit message to mention it).

-- >8 --
Subject: [PATCH] rev-list: make empty --stdin not an error

When we originally did the series that contains 7ba826290a
(revision: add rev_input_given flag, 2017-08-02) the intent
was that "git rev-list --stdin </dev/null" would similarly
become a successful noop. However, an attempt at the time to
do that did not work[1]. The problem is that rev_input_given
serves two roles:

 - it tells rev-list.c that it should not error out

 - it tells revision.c that it should not have the "default"
   ref kick (e.g., "HEAD" in "git log")

We want to trigger the former, but not the latter. This is
technically possible with a single flag, if we set the flag
only after revision.c's revs->def check. But this introduces
a rather subtle ordering dependency.

Instead, let's keep two flags: one to denote when we got
actual input (which triggers both roles) and one for when we
read stdin (which triggers only the first).

This does mean a caller interested in the first role has to
check both flags, but there's only one such caller. And any
future callers might want to make the distinction anyway
(e.g., if they care less about erroring out, and more about
whether revision.c soaked up our stdin).

In fact, we already keep such a flag internally in
revision.c for this purpose, so this is really just exposing
that to the caller (and the old function-local flag can go
away in favor of our new one).

[1] https://public-inbox.org/git/20170802223416.gwiezhbuxbdmbjzx@sigill.intra.peff.net/

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/rev-list.c       | 2 +-
 revision.c               | 5 ++---
 revision.h               | 5 +++++
 t/t6018-rev-list-glob.sh | 2 +-
 4 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 5b07f3f4a2..ed0ea7dc5b 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -493,7 +493,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 	if ((!revs.commits && reflog_walk_empty(revs.reflog_info) &&
 	     (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
 	      !revs.pending.nr) &&
-	     !revs.rev_input_given) ||
+	     !revs.rev_input_given && !revs.read_from_stdin) ||
 	    revs.diff)
 		usage(rev_list_usage);
 
diff --git a/revision.c b/revision.c
index de4dce600d..46228f82ee 100644
--- a/revision.c
+++ b/revision.c
@@ -2318,7 +2318,7 @@ static void NORETURN diagnose_missing_default(const char *def)
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
 {
-	int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
+	int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
 	struct argv_array prune_data = ARGV_ARRAY_INIT;
 	const char *submodule = NULL;
 
@@ -2348,7 +2348,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 	revarg_opt = opt ? opt->revarg_opt : 0;
 	if (seen_dashdash)
 		revarg_opt |= REVARG_CANNOT_BE_FILENAME;
-	read_from_stdin = 0;
 	for (left = i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 		if (*arg == '-') {
@@ -2367,7 +2366,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 					argv[left++] = arg;
 					continue;
 				}
-				if (read_from_stdin++)
+				if (revs->read_from_stdin++)
 					die("--stdin given twice?");
 				read_revisions_from_stdin(revs, &prune_data);
 				continue;
diff --git a/revision.h b/revision.h
index 007278cc11..1225957927 100644
--- a/revision.h
+++ b/revision.h
@@ -82,6 +82,11 @@ struct rev_info {
 	 */
 	int rev_input_given;
 
+	/*
+	 * Whether we read from stdin due to the --stdin option.
+	 */
+	int read_from_stdin;
+
 	/* topo-sort */
 	enum rev_sort_order sort_order;
 
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 0bf10d0686..db8a7834d8 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -255,7 +255,7 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
 	compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
 '
 
-test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
+test_expect_success 'rev-list should succeed with empty output on empty stdin' '
 	git rev-list --stdin </dev/null >actual &&
 	test_must_be_empty actual
 '
-- 
2.19.0.rc0.412.g7005db4e88


^ permalink raw reply related	[flat|nested] 95+ messages in thread

* Re: [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 20:42                                   ` Junio C Hamano
  2018-08-22 21:37                                     ` Jeff King
@ 2018-08-22 21:41                                     ` Junio C Hamano
  1 sibling, 0 replies; 95+ messages in thread
From: Junio C Hamano @ 2018-08-22 21:41 UTC (permalink / raw)
  To: Jeff King; +Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

Junio C Hamano <gitster@pobox.com> writes:

> I think this makes sense, but if we were to give a dedicated field
> in the revs structure, can we lose the local variable at the same
> time, I wonder?
>
> Thanks.

Well, the answer to "can we" is always "yes"; what I was truly
wondering was if it makes sense to do so.  I am on the fence.

>
>> diff --git a/revision.c b/revision.c
>> index de4dce600d..4d53102cf4 100644
>> --- a/revision.c
>> +++ b/revision.c
>> @@ -2369,6 +2369,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
>>  				}
>>  				if (read_from_stdin++)
>>  					die("--stdin given twice?");
>> +				revs->read_from_stdin = 1;
>>  				read_revisions_from_stdin(revs, &prune_data);
>>  				continue;

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 21:37                                     ` Jeff King
@ 2018-08-22 21:50                                       ` Junio C Hamano
  2018-08-22 21:55                                         ` Jeff King
  0 siblings, 1 reply; 95+ messages in thread
From: Junio C Hamano @ 2018-08-22 21:50 UTC (permalink / raw)
  To: Jeff King; +Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

Jeff King <peff@peff.net> writes:

> Yes. I was thinking it had more purpose than this, but it really is just
> a flag to check "did we do this already?". Which is one of the main
> purposes I claimed for the new flag in my commit message. :)

OK.  

The reason I was on the fence was primarily because read_from_stdin
field in the structure observable from outside can be a boolean
(that is, "unsigned :1"), but internally this may want to count up
to two.

Or with "unsigned read_from_stdin:1", would this 

	if (revs->read_from_stdin++)
		die("twice???");

still be usable?  As the value of post-increment would be 1 even
when the resulting field would have wrapped-around already, it
should be OK, but it just felt strange to me.

But that is something we do not have to worry about until somebody
tries to shrink the structure by making these flags into bitfields.

Thanks for an updated patch.

^ permalink raw reply	[flat|nested] 95+ messages in thread

* Re: [PATCH] rev-list: make empty --stdin not an error
  2018-08-22 21:50                                       ` Junio C Hamano
@ 2018-08-22 21:55                                         ` Jeff King
  0 siblings, 0 replies; 95+ messages in thread
From: Jeff King @ 2018-08-22 21:55 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: SZEDER Gábor, git, Ævar Arnfjörð Bjarmason

On Wed, Aug 22, 2018 at 02:50:05PM -0700, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > Yes. I was thinking it had more purpose than this, but it really is just
> > a flag to check "did we do this already?". Which is one of the main
> > purposes I claimed for the new flag in my commit message. :)
> 
> OK.  
> 
> The reason I was on the fence was primarily because read_from_stdin
> field in the structure observable from outside can be a boolean
> (that is, "unsigned :1"), but internally this may want to count up
> to two.
> 
> Or with "unsigned read_from_stdin:1", would this 
> 
> 	if (revs->read_from_stdin++)
> 		die("twice???");
> 
> still be usable?  As the value of post-increment would be 1 even
> when the resulting field would have wrapped-around already, it
> should be OK, but it just felt strange to me.

I agree it would work in practice, though I also agree it is funny and
should be avoided.

> But that is something we do not have to worry about until somebody
> tries to shrink the structure by making these flags into bitfields.

Also agreed. I'd probably resolve it then by writing:

  if (revs->read_from_stdin)
	die("twice");
  revs->read_from_stdin = 1;

I guess we could even do that now. Or add a test to make sure "--stdin
--stdin" barfs. But I am perfectly happy to punt until somebody actually
wants to use a bitfield.

-Peff

^ permalink raw reply	[flat|nested] 95+ messages in thread

end of thread, other threads:[~2018-08-22 21:55 UTC | newest]

Thread overview: 95+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-02 10:54 [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Ævar Arnfjörð Bjarmason
2018-05-02 15:21 ` Duy Nguyen
2018-05-02 18:00   ` Eric Sunshine
2018-05-02 18:09     ` Duy Nguyen
2018-05-02 18:25     ` Ævar Arnfjörð Bjarmason
2018-05-03 13:18       ` [PATCH v2] checkout & worktree: introduce checkout.implicitRemote Ævar Arnfjörð Bjarmason
2018-05-03 15:14         ` Duy Nguyen
2018-05-04  7:54           ` Ævar Arnfjörð Bjarmason
2018-05-04 14:58             ` Duy Nguyen
2018-05-04 18:02               ` Ævar Arnfjörð Bjarmason
2018-05-04  9:58         ` Eric Sunshine
2018-05-24 19:47           ` [PATCH v3] " Ævar Arnfjörð Bjarmason
2018-05-25  8:12             ` Junio C Hamano
2018-05-25 14:42               ` Duy Nguyen
2018-05-25 18:38                 ` Ævar Arnfjörð Bjarmason
2018-05-26 12:49                   ` Duy Nguyen
2018-05-31  7:45             ` Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 0/9] ambiguous checkout UI & checkout.defaultRemote Ævar Arnfjörð Bjarmason
2018-06-01 21:10                 ` [PATCH v5 0/8] " Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 " Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 " Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
2018-06-05 15:45                       ` SZEDER Gábor
2018-07-27 17:48                         ` [PATCH] tests: make use of the test_must_be_empty function Ævar Arnfjörð Bjarmason
2018-07-27 21:50                           ` Junio C Hamano
2018-07-31  0:17                           ` SZEDER Gábor
2018-08-22 17:48                           ` [PATCH] t6018-rev-list-glob: fix 'empty stdin' test SZEDER Gábor
2018-08-22 17:53                             ` Eric Sunshine
2018-08-22 18:59                               ` SZEDER Gábor
2018-08-22 20:30                                 ` Eric Sunshine
2018-08-22 18:01                             ` Junio C Hamano
2018-08-22 18:50                             ` Junio C Hamano
2018-08-22 19:23                               ` Jeff King
2018-08-22 19:50                                 ` [PATCH] rev-list: make empty --stdin not an error Jeff King
2018-08-22 20:42                                   ` Junio C Hamano
2018-08-22 21:37                                     ` Jeff King
2018-08-22 21:50                                       ` Junio C Hamano
2018-08-22 21:55                                         ` Jeff King
2018-08-22 21:41                                     ` Junio C Hamano
2018-06-05 14:40                     ` [PATCH v7 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 4/8] checkout.c: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
2018-06-05 14:40                     ` [PATCH v7 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 3/8] checkout.c: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 4/8] checkout.c]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
2018-06-02 11:50                   ` [PATCH v6 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
2018-06-03  7:58                     ` Eric Sunshine
2018-06-01 21:10                 ` [PATCH v5 1/8] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
2018-06-01 21:10                 ` [PATCH v5 2/8] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
2018-06-01 21:10                 ` [PATCH v5 3/8] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
2018-06-01 22:40                   ` Eric Sunshine
2018-06-01 21:10                 ` [PATCH v5 4/8] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
2018-06-01 22:41                   ` Eric Sunshine
2018-06-01 21:10                 ` [PATCH v5 5/8] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
2018-06-01 21:10                 ` [PATCH v5 6/8] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
2018-06-01 21:10                 ` [PATCH v5 7/8] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
2018-06-01 22:52                   ` Eric Sunshine
2018-06-01 21:10                 ` [PATCH v5 8/8] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 1/9] checkout tests: index should be clean after dwim checkout Ævar Arnfjörð Bjarmason
2018-06-01  4:06                 ` Junio C Hamano
2018-06-01 19:43                   ` Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 2/9] checkout.h: wrap the arguments to unique_tracking_name() Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 3/9] checkout.[ch]: move struct declaration into *.h Ævar Arnfjörð Bjarmason
2018-05-31 21:45                 ` Thomas Gummerer
2018-06-01  2:14                   ` Junio C Hamano
2018-06-01  9:56                     ` Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 4/9] checkout.[ch]: introduce an *_INIT macro Ævar Arnfjörð Bjarmason
2018-06-01  4:16                 ` Junio C Hamano
2018-05-31 19:52               ` [PATCH v4 5/9] checkout.[ch]: change "unique" member to "num_matches" Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 6/9] checkout: pass the "num_matches" up to callers Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 7/9] builtin/checkout.c: use "ret" variable for return Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 8/9] checkout: add advice for ambiguous "checkout <branch>" Ævar Arnfjörð Bjarmason
2018-06-01  4:32                 ` Junio C Hamano
2018-06-01  5:11                   ` Junio C Hamano
2018-06-01  9:54                     ` Ævar Arnfjörð Bjarmason
2018-06-04  1:58                       ` Junio C Hamano
2018-06-01  9:50                   ` Ævar Arnfjörð Bjarmason
2018-06-01  7:53                 ` Eric Sunshine
2018-06-01 19:59                   ` Ævar Arnfjörð Bjarmason
2018-05-31 19:52               ` [PATCH v4 9/9] checkout & worktree: introduce checkout.defaultRemote Ævar Arnfjörð Bjarmason
2018-05-31 21:49                 ` Stefan Beller
2018-06-01  8:04                   ` Eric Sunshine
2018-06-01  9:47                   ` Ævar Arnfjörð Bjarmason
2018-05-31 22:22                 ` Thomas Gummerer
2018-06-01  2:17                   ` Junio C Hamano
2018-05-04  7:14       ` [PATCH] checkout & worktree: introduce a core.DWIMRemote setting Eric Sunshine
2018-05-04  7:23         ` Eric Sunshine

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).