Skip to content

Commit 66d3f19

Browse files
committed
Merge branch 'tg/worktree-create-tracking'
The way "git worktree add" determines what branch to create from where and checkout in the new worktree has been updated a bit. * tg/worktree-create-tracking: add worktree.guessRemote config option worktree: add --guess-remote flag to add subcommand worktree: make add <path> <branch> dwim worktree: add --[no-]track option to the add subcommand worktree: add can be created from any commit-ish checkout: factor out functions to new lib file
2 parents 1974f47 + e92445a commit 66d3f19

File tree

8 files changed

+277
-51
lines changed

8 files changed

+277
-51
lines changed

Documentation/config.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3468,3 +3468,13 @@ web.browser::
34683468
Specify a web browser that may be used by some commands.
34693469
Currently only linkgit:git-instaweb[1] and linkgit:git-help[1]
34703470
may use it.
3471+
3472+
worktree.guessRemote::
3473+
With `add`, if no branch argument, and neither of `-b` nor
3474+
`-B` nor `--detach` are given, the command defaults to
3475+
creating a new branch from HEAD. If `worktree.guessRemote` is
3476+
set to true, `worktree add` tries to find a remote-tracking
3477+
branch whose name uniquely matches the new branch name. If
3478+
such a branch exists, it is checked out and set as "upstream"
3479+
for the new branch. If no such match can be found, it falls
3480+
back to creating a new branch from the current HEAD.

Documentation/git-worktree.txt

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<branch>]
12+
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
1313
'git worktree list' [--porcelain]
1414
'git worktree lock' [--reason <string>] <worktree>
1515
'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -45,14 +45,22 @@ specifying `--reason` to explain why the working tree is locked.
4545

4646
COMMANDS
4747
--------
48-
add <path> [<branch>]::
48+
add <path> [<commit-ish>]::
4949

50-
Create `<path>` and checkout `<branch>` into it. The new working directory
50+
Create `<path>` and checkout `<commit-ish>` into it. The new working directory
5151
is linked to the current repository, sharing everything except working
5252
directory specific files such as HEAD, index, etc. `-` may also be
53-
specified as `<branch>`; it is synonymous with `@{-1}`.
53+
specified as `<commit-ish>`; it is synonymous with `@{-1}`.
5454
+
55-
If `<branch>` is omitted and neither `-b` nor `-B` nor `--detach` used,
55+
If <commit-ish> is a branch name (call it `<branch>` and is not found,
56+
and neither `-b` nor `-B` nor `--detach` are used, but there does
57+
exist a tracking branch in exactly one remote (call it `<remote>`)
58+
with a matching name, treat as equivalent to
59+
------------
60+
$ git worktree add --track -b <branch> <path> <remote>/<branch>
61+
------------
62+
+
63+
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
5664
then, as a convenience, a new branch based at HEAD is created automatically,
5765
as if `-b $(basename <path>)` was specified.
5866

@@ -84,29 +92,45 @@ OPTIONS
8492

8593
-f::
8694
--force::
87-
By default, `add` refuses to create a new working tree when `<branch>`
95+
By default, `add` refuses to create a new working tree when `<commit-ish>` is a branch name and
8896
is already checked out by another working tree. This option overrides
8997
that safeguard.
9098

9199
-b <new-branch>::
92100
-B <new-branch>::
93101
With `add`, create a new branch named `<new-branch>` starting at
94-
`<branch>`, and check out `<new-branch>` into the new working tree.
95-
If `<branch>` is omitted, it defaults to HEAD.
102+
`<commit-ish>`, and check out `<new-branch>` into the new working tree.
103+
If `<commit-ish>` is omitted, it defaults to HEAD.
96104
By default, `-b` refuses to create a new branch if it already
97105
exists. `-B` overrides this safeguard, resetting `<new-branch>` to
98-
`<branch>`.
106+
`<commit-ish>`.
99107

100108
--detach::
101109
With `add`, detach HEAD in the new working tree. See "DETACHED HEAD"
102110
in linkgit:git-checkout[1].
103111

104112
--[no-]checkout::
105-
By default, `add` checks out `<branch>`, however, `--no-checkout` can
113+
By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can
106114
be used to suppress checkout in order to make customizations,
107115
such as configuring sparse-checkout. See "Sparse checkout"
108116
in linkgit:git-read-tree[1].
109117

118+
--[no-]guess-remote::
119+
With `worktree add <path>`, without `<commit-ish>`, instead
120+
of creating a new branch from HEAD, if there exists a tracking
121+
branch in exactly one remote matching the basename of `<path>,
122+
base the new branch on the remote-tracking branch, and mark
123+
the remote-tracking branch as "upstream" from the new branch.
124+
+
125+
This can also be set up as the default behaviour by using the
126+
`worktree.guessRemote` config option.
127+
128+
--[no-]track::
129+
When creating a new branch, if `<commit-ish>` is a branch,
130+
mark it as "upstream" from the new branch. This is the
131+
default if `<commit-ish>` is a remote-tracking branch. See
132+
"--track" in linkgit:git-branch[1] for details.
133+
110134
--lock::
111135
Keep the working tree locked after creation. This is the
112136
equivalent of `git worktree lock` after `git worktree add`,

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ LIB_OBJS += branch.o
759759
LIB_OBJS += bulk-checkin.o
760760
LIB_OBJS += bundle.o
761761
LIB_OBJS += cache-tree.o
762+
LIB_OBJS += checkout.o
762763
LIB_OBJS += color.o
763764
LIB_OBJS += column.o
764765
LIB_OBJS += combine-diff.o

builtin/checkout.c

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "builtin.h"
22
#include "config.h"
3+
#include "checkout.h"
34
#include "lockfile.h"
45
#include "parse-options.h"
56
#include "refs.h"
@@ -872,46 +873,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
872873
return git_xmerge_config(var, value, NULL);
873874
}
874875

875-
struct tracking_name_data {
876-
/* const */ char *src_ref;
877-
char *dst_ref;
878-
struct object_id *dst_oid;
879-
int unique;
880-
};
881-
882-
static int check_tracking_name(struct remote *remote, void *cb_data)
883-
{
884-
struct tracking_name_data *cb = cb_data;
885-
struct refspec query;
886-
memset(&query, 0, sizeof(struct refspec));
887-
query.src = cb->src_ref;
888-
if (remote_find_tracking(remote, &query) ||
889-
get_oid(query.dst, cb->dst_oid)) {
890-
free(query.dst);
891-
return 0;
892-
}
893-
if (cb->dst_ref) {
894-
free(query.dst);
895-
cb->unique = 0;
896-
return 0;
897-
}
898-
cb->dst_ref = query.dst;
899-
return 0;
900-
}
901-
902-
static const char *unique_tracking_name(const char *name, struct object_id *oid)
903-
{
904-
struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
905-
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
906-
cb_data.dst_oid = oid;
907-
for_each_remote(check_tracking_name, &cb_data);
908-
free(cb_data.src_ref);
909-
if (cb_data.unique)
910-
return cb_data.dst_ref;
911-
free(cb_data.dst_ref);
912-
return NULL;
913-
}
914-
915876
static int parse_branchname_arg(int argc, const char **argv,
916877
int dwim_new_local_branch_ok,
917878
struct branch_info *new,

builtin/worktree.c

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "cache.h"
2+
#include "checkout.h"
23
#include "config.h"
34
#include "builtin.h"
45
#include "dir.h"
@@ -32,8 +33,19 @@ struct add_opts {
3233

3334
static int show_only;
3435
static int verbose;
36+
static int guess_remote;
3537
static timestamp_t expire;
3638

39+
static int git_worktree_config(const char *var, const char *value, void *cb)
40+
{
41+
if (!strcmp(var, "worktree.guessremote")) {
42+
guess_remote = git_config_bool(var, value);
43+
return 0;
44+
}
45+
46+
return git_default_config(var, value, cb);
47+
}
48+
3749
static int prune_worktree(const char *id, struct strbuf *reason)
3850
{
3951
struct stat st;
@@ -341,6 +353,7 @@ static int add(int ac, const char **av, const char *prefix)
341353
const char *new_branch_force = NULL;
342354
char *path;
343355
const char *branch;
356+
const char *opt_track = NULL;
344357
struct option options[] = {
345358
OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
346359
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -350,6 +363,11 @@ static int add(int ac, const char **av, const char *prefix)
350363
OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
351364
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
352365
OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
366+
OPT_PASSTHRU(0, "track", &opt_track, NULL,
367+
N_("set up tracking mode (see git-branch(1))"),
368+
PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
369+
OPT_BOOL(0, "guess-remote", &guess_remote,
370+
N_("try to match the new branch name with a remote-tracking branch")),
353371
OPT_END()
354372
};
355373

@@ -384,6 +402,28 @@ static int add(int ac, const char **av, const char *prefix)
384402
int n;
385403
const char *s = worktree_basename(path, &n);
386404
opts.new_branch = xstrndup(s, n);
405+
if (guess_remote) {
406+
struct object_id oid;
407+
const char *remote =
408+
unique_tracking_name(opts.new_branch, &oid);
409+
if (remote)
410+
branch = remote;
411+
}
412+
}
413+
414+
if (ac == 2 && !opts.new_branch && !opts.detach) {
415+
struct object_id oid;
416+
struct commit *commit;
417+
const char *remote;
418+
419+
commit = lookup_commit_reference_by_name(branch);
420+
if (!commit) {
421+
remote = unique_tracking_name(branch, &oid);
422+
if (remote) {
423+
opts.new_branch = branch;
424+
branch = remote;
425+
}
426+
}
387427
}
388428

389429
if (opts.new_branch) {
@@ -394,9 +434,13 @@ static int add(int ac, const char **av, const char *prefix)
394434
argv_array_push(&cp.args, "--force");
395435
argv_array_push(&cp.args, opts.new_branch);
396436
argv_array_push(&cp.args, branch);
437+
if (opt_track)
438+
argv_array_push(&cp.args, opt_track);
397439
if (run_command(&cp))
398440
return -1;
399441
branch = opts.new_branch;
442+
} else if (opt_track) {
443+
die(_("--[no-]track can only be used if a new branch is created"));
400444
}
401445

402446
UNLEAK(path);
@@ -557,7 +601,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
557601
OPT_END()
558602
};
559603

560-
git_config(git_default_config, NULL);
604+
git_config(git_worktree_config, NULL);
561605

562606
if (ac < 2)
563607
usage_with_options(worktree_usage, options);

checkout.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "cache.h"
2+
#include "remote.h"
3+
#include "checkout.h"
4+
5+
struct tracking_name_data {
6+
/* const */ char *src_ref;
7+
char *dst_ref;
8+
struct object_id *dst_oid;
9+
int unique;
10+
};
11+
12+
static int check_tracking_name(struct remote *remote, void *cb_data)
13+
{
14+
struct tracking_name_data *cb = cb_data;
15+
struct refspec query;
16+
memset(&query, 0, sizeof(struct refspec));
17+
query.src = cb->src_ref;
18+
if (remote_find_tracking(remote, &query) ||
19+
get_oid(query.dst, cb->dst_oid)) {
20+
free(query.dst);
21+
return 0;
22+
}
23+
if (cb->dst_ref) {
24+
free(query.dst);
25+
cb->unique = 0;
26+
return 0;
27+
}
28+
cb->dst_ref = query.dst;
29+
return 0;
30+
}
31+
32+
const char *unique_tracking_name(const char *name, struct object_id *oid)
33+
{
34+
struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
35+
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
36+
cb_data.dst_oid = oid;
37+
for_each_remote(check_tracking_name, &cb_data);
38+
free(cb_data.src_ref);
39+
if (cb_data.unique)
40+
return cb_data.dst_ref;
41+
free(cb_data.dst_ref);
42+
return NULL;
43+
}

checkout.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef CHECKOUT_H
2+
#define CHECKOUT_H
3+
4+
#include "cache.h"
5+
6+
/*
7+
* Check if the branch name uniquely matches a branch name on a remote
8+
* tracking branch. Return the name of the remote if such a branch
9+
* exists, NULL otherwise.
10+
*/
11+
extern const char *unique_tracking_name(const char *name, struct object_id *oid);
12+
13+
#endif /* CHECKOUT_H */

0 commit comments

Comments
 (0)