Subject: [PATCH v5 0/6] make git worktree add dwim more

From: Thomas Gummerer <[email-masked]>
Date: Sun, 26 Nov 2017 19:43:50 +0000

The previous rounds can be found at
https://public-inbox.org/git/[email-masked]/,
https://public-inbox.org/git/[email-masked]/,
https://public-inbox.org/git/[email-masked]/ and
https://public-inbox.org/git/[email-masked]/.

Thanks Junio for the review of the previous round and Randall for the
suggestion of documenting that git worktree add can take a commit-ish,
not just a branch.

The main changes in this round are hiding the new behaviour for 'git
worktree <path>' behind a flag, and adding a config option to turn the
new behaviour on by default.  It's also no longer relying on the
--[no]-track flag, but using a new --[no-]guess-remote flag instead.

Interdiff between this and the previous round below:

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5f65fa9234..4966d90ebb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -3425,3 +3425,13 @@ web.browser::
 	Specify a web browser that may be used by some commands.
 	Currently only linkgit:git-instaweb[1] and linkgit:git-help[1]
 	may use it.
+
+worktree.guessRemote::
+	With `add`, if no branch argument, and neither of `-b` nor
+	`-B` nor `--detach` are given, the command defaults to
+	creating a new branch from HEAD.  If `worktree.guessRemote` is
+	set to true, `worktree add` tries to find a remote-tracking
+	branch whose name uniquely matches the new branch name.  If
+	such a branch exists, it is checked out and set as "upstream"
+	for the new branch.  If no such match can be found, it falls
+	back to creating a new branch from the current HEAD.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index abc8f1f50d..fd841886ef 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
 SYNOPSIS
 --------
 [verse]
-'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<branch>]
+'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -45,33 +45,24 @@ specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
-add <path> [<branch>]::
+add <path> [<commit-ish>]::
 
-Create `<path>` and checkout `<branch>` into it. The new working directory
+Create `<path>` and checkout `<commit-ish>` into it. The new working directory
 is linked to the current repository, sharing everything except working
 directory specific files such as HEAD, index, etc. `-` may also be
-specified as `<branch>`; it is synonymous with `@{-1}`.
+specified as `<commit-ish>`; it is synonymous with `@{-1}`.
 +
-If <branch> is not found, and neither `-b` nor `-B` nor `--detach` are
-used, but there does exist a tracking branch in exactly one remote
-(call it <remote>) with a matching name, treat as equivalent to
+If <commit-ish> is a branch name (call it `<branch>` and is not found,
+and neither `-b` nor `-B` nor `--detach` are used, but there does
+exist a tracking branch in exactly one remote (call it `<remote>`)
+with a matching name, treat as equivalent to
 ------------
 $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
-If `<branch>` is omitted and neither `-b` nor `-B` nor `--detach` used,
-then, as a convenience, if there exists a tracking branch in exactly
-one remote (call it `<remote>`) matching the basename of the path
-(call it `<branch>`), treat it as equivalent to
-------------
-$ git worktree add --track -b <branch> <path> <remote>/<branch>
-------------
-If no tracking branch exists in exactly one remote, `<branch>` is
-created based on HEAD, as if `-b $(basename <path>)` was specified.
-+
-To disable the behaviour of trying to match the basename of <path> to
-a remote, and always create a new branch from HEAD, the `--no-track`
-flag can be passed to `git worktree add`.
+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.
 
 list::
 
@@ -101,34 +92,44 @@ OPTIONS
 
 -f::
 --force::
-	By default, `add` refuses to create a new working tree when `<branch>`
+	By default, `add` refuses to create a new working tree when `<commit-ish>` is a branch name and
 	is already checked out by another working tree. This option overrides
 	that safeguard.
 
 -b <new-branch>::
 -B <new-branch>::
 	With `add`, create a new branch named `<new-branch>` starting at
-	`<branch>`, and check out `<new-branch>` into the new working tree.
-	If `<branch>` is omitted, it defaults to HEAD.
+	`<commit-ish>`, and check out `<new-branch>` into the new working tree.
+	If `<commit-ish>` is omitted, it defaults to HEAD.
 	By default, `-b` refuses to create a new branch if it already
 	exists. `-B` overrides this safeguard, resetting `<new-branch>` to
-	`<branch>`.
+	`<commit-ish>`.
 
 --detach::
 	With `add`, detach HEAD in the new working tree. See "DETACHED HEAD"
 	in linkgit:git-checkout[1].
 
 --[no-]checkout::
-	By default, `add` checks out `<branch>`, however, `--no-checkout` can
+	By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can
 	be used to suppress checkout in order to make customizations,
 	such as configuring sparse-checkout. See "Sparse checkout"
 	in linkgit:git-read-tree[1].
 
+--[no-]guess-remote::
+	With `add`, instead of creating a new branch from HEAD when
+	`<commit-ish>` is not given, if there exists a tracking branch
+	in exactly one remote matching the basename of the path, base
+	the new branch on the remote-tracking branch, and mark the
+	remote-tracking branch as "upstream" from the new branch.
++
+This can also be set up as the default behaviour by using the
+`worktree.guessRemote` config option.
+
 --[no-]track::
-	With `--track` `<branch>` is set as "tracking" branch for
-	`<new-branch>`.  This is the default if `<branch>` is a remote
-	tracking branch, and can be suppressed with `--no-track`.  See
-	also linkgit:git-branch[1].
+	When creating a new branch, if `<commit-ish>` is a branch,
+	mark it as "upstream" from the new branch.  This is the
+	default if `<commit-ish>` is a remote-tracking branch.  See
+	"--track" in linkgit:git-branch[1] for details.
 
 --lock::
 	Keep the working tree locked after creation. This is the
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 83c73ecb0d..426aea8761 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -33,8 +33,19 @@ struct add_opts {
 
 static int show_only;
 static int verbose;
+static int guess_remote;
 static timestamp_t expire;
 
+static int git_worktree_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "worktree.guessremote")) {
+		guess_remote = git_config_bool(var, value);
+		return 0;
+	}
+
+	return 0;
+}
+
 static int prune_worktree(const char *id, struct strbuf *reason)
 {
 	struct stat st;
@@ -355,9 +366,13 @@ static int add(int ac, const char **av, const char *prefix)
 		OPT_PASSTHRU(0, "track", &opt_track, NULL,
 			     N_("set up tracking mode (see git-branch(1))"),
 			     PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
+		OPT_BOOL(0, "guess-remote", &guess_remote,
+			 N_("try to match the new branch name with a remote-tracking branch")),
 		OPT_END()
 	};
 
+	git_config(git_worktree_config, NULL);
+
 	memset(&opts, 0, sizeof(opts));
 	opts.checkout = 1;
 	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
@@ -389,7 +404,7 @@ static int add(int ac, const char **av, const char *prefix)
 		int n;
 		const char *s = worktree_basename(path, &n);
 		opts.new_branch = xstrndup(s, n);
-		if (!opt_track || strcmp(opt_track, "--no-track")) {
+		if (guess_remote) {
 			struct object_id oid;
 			const char *remote =
 				unique_tracking_name(opts.new_branch, &oid);
diff --git a/checkout.c b/checkout.c
index b0c744d37a..ac42630f74 100644
--- a/checkout.c
+++ b/checkout.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "remote.h"
+#include "checkout.h"
 
 struct tracking_name_data {
 	/* const */ char *src_ref;
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index 6fd3da4036..6ce9b9c070 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -326,10 +326,7 @@ test_branch_upstream () {
 test_expect_success '--track sets up tracking' '
 	test_when_finished rm -rf track &&
 	git worktree add --track -b track track master &&
-	git config "branch.track.merge" &&
-	(
-		test_branch_upstream track . master
-	)
+	test_branch_upstream track . master
 '
 
 # setup remote repository $1 and repository $2 with $1 set up as
@@ -362,10 +359,9 @@ test_expect_success '--no-track avoids setting up tracking' '
 	) &&
 	(
 		cd foo &&
-		! test_branch_upstream foo repo_upstream foo &&
-		git rev-parse repo_upstream/foo >expect &&
-		git rev-parse foo >actual &&
-		test_cmp expect actual
+		test_must_fail git config "branch.foo.remote" &&
+		test_must_fail git config "branch.foo.merge" &&
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
 	)
 '
 
@@ -384,41 +380,67 @@ test_expect_success '"add" <path> <branch> dwims' '
 	(
 		cd foo &&
 		test_branch_upstream foo repo_upstream foo &&
-		git rev-parse repo_upstream/foo >expect &&
-		git rev-parse foo >actual &&
-		test_cmp expect actual
+		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
 	)
 '
 
-test_expect_success 'git worktree add --no-track does not set up tracking' '
+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 &&
 	(
 		cd repo_b &&
-		git worktree add --no-track ../foo
+		git worktree add ../foo
 	) &&
 	(
 		cd foo &&
-		! test_branch_upstream foo repo_a foo &&
-		git rev-parse repo_a/foo >expect &&
-		git rev-parse foo >actual &&
-		! test_cmp expect actual
+		test_must_fail git config "branch.foo.remote" &&
+		test_must_fail git config "branch.foo.merge" &&
+		! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 	)
 '
 
-test_expect_success 'git worktree add sets up tracking' '
-	test_when_finished rm -rf repo_a repo_b &&
+test_expect_success 'git worktree add --guess-remote sets up tracking' '
+	test_when_finished rm -rf repo_a repo_b foo &&
 	setup_remote_repo repo_a repo_b &&
 	(
 		cd repo_b &&
+		git worktree add --guess-remote ../foo
+	) &&
+	(
+		cd foo &&
+		test_branch_upstream foo repo_a foo &&
+		test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+	)
+'
+
+test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
+	test_when_finished rm -rf repo_a repo_b foo &&
+	setup_remote_repo repo_a repo_b &&
+	(
+		cd repo_b &&
+		git config worktree.guessRemote true &&
 		git worktree add ../foo
 	) &&
 	(
 		cd foo &&
 		test_branch_upstream foo repo_a foo &&
-		git rev-parse repo_a/foo >expect &&
-		git rev-parse foo >actual &&
-		test_cmp expect actual
+		test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+	)
+'
+
+test_expect_success 'git worktree --no-guess-remote option overrides config' '
+	test_when_finished rm -rf repo_a repo_b foo &&
+	setup_remote_repo repo_a repo_b &&
+	(
+		cd repo_b &&
+		git config worktree.guessRemote true &&
+		git worktree add --no-guess-remote ../foo
+	) &&
+	(
+		cd foo &&
+		test_must_fail git config "branch.foo.remote" &&
+		test_must_fail git config "branch.foo.merge" &&
+		! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 	)
 '
 
Thomas Gummerer (6):
  checkout: factor out functions to new lib file
  worktree: add can be created from any commit-ish
  worktree: add --[no-]track option to the add subcommand
  worktree: make add <path> <branch> dwim
  worktree: add --guess-remote flag to add subcommand
  add worktree.guessRemote config option

 Documentation/config.txt       |  10 ++++
 Documentation/git-worktree.txt |  44 ++++++++++----
 Makefile                       |   1 +
 builtin/checkout.c             |  41 +------------
 builtin/worktree.c             |  46 +++++++++++++++
 checkout.c                     |  43 ++++++++++++++
 checkout.h                     |  13 +++++
 t/t2025-worktree-add.sh        | 130 +++++++++++++++++++++++++++++++++++++++++
 8 files changed, 278 insertions(+), 50 deletions(-)
 create mode 100644 checkout.c
 create mode 100644 checkout.h

-- 
2.15.0.426.gb06021eeb