Skip to content

Commit 7783eb2

Browse files
committedJul 13, 2015
Merge branch 'nd/multiple-work-trees'
"git checkout [<tree-ish>] <paths>" spent unnecessary cycles checking if the current branch was checked out elsewhere, when we know we are not switching the branches ourselves. * nd/multiple-work-trees: worktree: new place for "git prune --worktrees" checkout: don't check worktrees when not necessary
2 parents 721f5bb + df0b6cf commit 7783eb2

13 files changed

+217
-126
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
/git-verify-tag
172172
/git-web--browse
173173
/git-whatchanged
174+
/git-worktree
174175
/git-write-tree
175176
/git-core-*/?*
176177
/gitweb/GITWEB-BUILD-OPTIONS

‎Documentation/git-prune.txt

-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ OPTIONS
4848
--expire <time>::
4949
Only expire loose objects older than <time>.
5050

51-
--worktrees::
52-
Prune dead working tree information in $GIT_DIR/worktrees.
53-
5451
<head>...::
5552
In addition to objects
5653
reachable from any of our references, keep objects

‎Documentation/git-worktree.txt

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
git-worktree(1)
2+
===============
3+
4+
NAME
5+
----
6+
git-worktree - Manage multiple worktrees
7+
8+
9+
SYNOPSIS
10+
--------
11+
[verse]
12+
'git worktree prune' [-n] [-v] [--expire <expire>]
13+
14+
DESCRIPTION
15+
-----------
16+
17+
Manage multiple worktrees attached to the same repository. These are
18+
created by the command `git checkout --to`.
19+
20+
COMMANDS
21+
--------
22+
prune::
23+
24+
Prune working tree information in $GIT_DIR/worktrees.
25+
26+
OPTIONS
27+
-------
28+
29+
-n::
30+
--dry-run::
31+
Do not remove anything; just report what it would
32+
remove.
33+
34+
-v::
35+
--verbose::
36+
Report all removals.
37+
38+
--expire <time>::
39+
Only expire unused worktrees older than <time>.
40+
41+
SEE ALSO
42+
--------
43+
44+
linkgit:git-checkout[1]
45+
46+
GIT
47+
---
48+
Part of the linkgit:git[1] suite

‎Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ BUILTIN_OBJS += builtin/var.o
910910
BUILTIN_OBJS += builtin/verify-commit.o
911911
BUILTIN_OBJS += builtin/verify-pack.o
912912
BUILTIN_OBJS += builtin/verify-tag.o
913+
BUILTIN_OBJS += builtin/worktree.o
913914
BUILTIN_OBJS += builtin/write-tree.o
914915

915916
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)

‎builtin.h

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
133133
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
134134
extern int cmd_version(int argc, const char **argv, const char *prefix);
135135
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
136+
extern int cmd_worktree(int argc, const char **argv, const char *prefix);
136137
extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
137138
extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
138139
extern int cmd_show_ref(int argc, const char **argv, const char *prefix);

‎builtin/checkout.c

+11-12
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,6 @@ static int parse_branchname_arg(int argc, const char **argv,
11101110
{
11111111
struct tree **source_tree = &opts->source_tree;
11121112
const char **new_branch = &opts->new_branch;
1113-
int force_detach = opts->force_detach;
11141113
int argcount = 0;
11151114
unsigned char branch_rev[20];
11161115
const char *arg;
@@ -1231,17 +1230,6 @@ static int parse_branchname_arg(int argc, const char **argv,
12311230
else
12321231
new->path = NULL; /* not an existing branch */
12331232

1234-
if (new->path && !force_detach && !*new_branch) {
1235-
unsigned char sha1[20];
1236-
int flag;
1237-
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
1238-
if (head_ref &&
1239-
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
1240-
!opts->ignore_other_worktrees)
1241-
check_linked_checkouts(new);
1242-
free(head_ref);
1243-
}
1244-
12451233
new->commit = lookup_commit_reference_gently(rev, 1);
12461234
if (!new->commit) {
12471235
/* not a commit */
@@ -1321,6 +1309,17 @@ static int checkout_branch(struct checkout_opts *opts,
13211309
die(_("Cannot switch branch to a non-commit '%s'"),
13221310
new->name);
13231311

1312+
if (new->path && !opts->force_detach && !opts->new_branch) {
1313+
unsigned char sha1[20];
1314+
int flag;
1315+
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
1316+
if (head_ref &&
1317+
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
1318+
!opts->ignore_other_worktrees)
1319+
check_linked_checkouts(new);
1320+
free(head_ref);
1321+
}
1322+
13241323
if (opts->new_worktree)
13251324
return prepare_linked_checkout(opts, new);
13261325

‎builtin/gc.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
293293
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
294294
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
295295
argv_array_pushl(&prune, "prune", "--expire", NULL);
296-
argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
296+
argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
297297
argv_array_pushl(&rerere, "rerere", "gc", NULL);
298298

299299
gc_config();

‎builtin/prune.c

-99
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include "reachable.h"
77
#include "parse-options.h"
88
#include "progress.h"
9-
#include "dir.h"
109

1110
static const char * const prune_usage[] = {
1211
N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data)
7675
return 0;
7776
}
7877

79-
static int prune_worktree(const char *id, struct strbuf *reason)
80-
{
81-
struct stat st;
82-
char *path;
83-
int fd, len;
84-
85-
if (!is_directory(git_path("worktrees/%s", id))) {
86-
strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
87-
return 1;
88-
}
89-
if (file_exists(git_path("worktrees/%s/locked", id)))
90-
return 0;
91-
if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
92-
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
93-
return 1;
94-
}
95-
fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
96-
if (fd < 0) {
97-
strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
98-
id, strerror(errno));
99-
return 1;
100-
}
101-
len = st.st_size;
102-
path = xmalloc(len + 1);
103-
read_in_full(fd, path, len);
104-
close(fd);
105-
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
106-
len--;
107-
if (!len) {
108-
strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
109-
free(path);
110-
return 1;
111-
}
112-
path[len] = '\0';
113-
if (!file_exists(path)) {
114-
struct stat st_link;
115-
free(path);
116-
/*
117-
* the repo is moved manually and has not been
118-
* accessed since?
119-
*/
120-
if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
121-
st_link.st_nlink > 1)
122-
return 0;
123-
if (st.st_mtime <= expire) {
124-
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
125-
return 1;
126-
} else {
127-
return 0;
128-
}
129-
}
130-
free(path);
131-
return 0;
132-
}
133-
134-
static void prune_worktrees(void)
135-
{
136-
struct strbuf reason = STRBUF_INIT;
137-
struct strbuf path = STRBUF_INIT;
138-
DIR *dir = opendir(git_path("worktrees"));
139-
struct dirent *d;
140-
int ret;
141-
if (!dir)
142-
return;
143-
while ((d = readdir(dir)) != NULL) {
144-
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
145-
continue;
146-
strbuf_reset(&reason);
147-
if (!prune_worktree(d->d_name, &reason))
148-
continue;
149-
if (show_only || verbose)
150-
printf("%s\n", reason.buf);
151-
if (show_only)
152-
continue;
153-
strbuf_reset(&path);
154-
strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
155-
ret = remove_dir_recursively(&path, 0);
156-
if (ret < 0 && errno == ENOTDIR)
157-
ret = unlink(path.buf);
158-
if (ret)
159-
error(_("failed to remove: %s"), strerror(errno));
160-
}
161-
closedir(dir);
162-
if (!show_only)
163-
rmdir(git_path("worktrees"));
164-
strbuf_release(&reason);
165-
strbuf_release(&path);
166-
}
167-
16878
/*
16979
* Write errors (particularly out of space) can result in
17080
* failed temporary packs (and more rarely indexes and other
@@ -191,12 +101,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
191101
{
192102
struct rev_info revs;
193103
struct progress *progress = NULL;
194-
int do_prune_worktrees = 0;
195104
const struct option options[] = {
196105
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
197106
OPT__VERBOSE(&verbose, N_("report pruned objects")),
198107
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
199-
OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
200108
OPT_EXPIRY_DATE(0, "expire", &expire,
201109
N_("expire objects older than <time>")),
202110
OPT_END()
@@ -211,13 +119,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
211119

212120
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
213121

214-
if (do_prune_worktrees) {
215-
if (argc)
216-
die(_("--worktrees does not take extra arguments"));
217-
prune_worktrees();
218-
return 0;
219-
}
220-
221122
while (argc--) {
222123
unsigned char sha1[20];
223124
const char *name = *argv++;

‎builtin/worktree.c

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "cache.h"
2+
#include "builtin.h"
3+
#include "dir.h"
4+
#include "parse-options.h"
5+
6+
static const char * const worktree_usage[] = {
7+
N_("git worktree prune [<options>]"),
8+
NULL
9+
};
10+
11+
static int show_only;
12+
static int verbose;
13+
static unsigned long expire;
14+
15+
static int prune_worktree(const char *id, struct strbuf *reason)
16+
{
17+
struct stat st;
18+
char *path;
19+
int fd, len;
20+
21+
if (!is_directory(git_path("worktrees/%s", id))) {
22+
strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
23+
return 1;
24+
}
25+
if (file_exists(git_path("worktrees/%s/locked", id)))
26+
return 0;
27+
if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
28+
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
29+
return 1;
30+
}
31+
fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
32+
if (fd < 0) {
33+
strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
34+
id, strerror(errno));
35+
return 1;
36+
}
37+
len = st.st_size;
38+
path = xmalloc(len + 1);
39+
read_in_full(fd, path, len);
40+
close(fd);
41+
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
42+
len--;
43+
if (!len) {
44+
strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
45+
free(path);
46+
return 1;
47+
}
48+
path[len] = '\0';
49+
if (!file_exists(path)) {
50+
struct stat st_link;
51+
free(path);
52+
/*
53+
* the repo is moved manually and has not been
54+
* accessed since?
55+
*/
56+
if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
57+
st_link.st_nlink > 1)
58+
return 0;
59+
if (st.st_mtime <= expire) {
60+
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
61+
return 1;
62+
} else {
63+
return 0;
64+
}
65+
}
66+
free(path);
67+
return 0;
68+
}
69+
70+
static void prune_worktrees(void)
71+
{
72+
struct strbuf reason = STRBUF_INIT;
73+
struct strbuf path = STRBUF_INIT;
74+
DIR *dir = opendir(git_path("worktrees"));
75+
struct dirent *d;
76+
int ret;
77+
if (!dir)
78+
return;
79+
while ((d = readdir(dir)) != NULL) {
80+
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
81+
continue;
82+
strbuf_reset(&reason);
83+
if (!prune_worktree(d->d_name, &reason))
84+
continue;
85+
if (show_only || verbose)
86+
printf("%s\n", reason.buf);
87+
if (show_only)
88+
continue;
89+
strbuf_reset(&path);
90+
strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
91+
ret = remove_dir_recursively(&path, 0);
92+
if (ret < 0 && errno == ENOTDIR)
93+
ret = unlink(path.buf);
94+
if (ret)
95+
error(_("failed to remove: %s"), strerror(errno));
96+
}
97+
closedir(dir);
98+
if (!show_only)
99+
rmdir(git_path("worktrees"));
100+
strbuf_release(&reason);
101+
strbuf_release(&path);
102+
}
103+
104+
static int prune(int ac, const char **av, const char *prefix)
105+
{
106+
struct option options[] = {
107+
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
108+
OPT__VERBOSE(&verbose, N_("report pruned objects")),
109+
OPT_EXPIRY_DATE(0, "expire", &expire,
110+
N_("expire objects older than <time>")),
111+
OPT_END()
112+
};
113+
114+
expire = ULONG_MAX;
115+
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
116+
if (ac)
117+
usage_with_options(worktree_usage, options);
118+
prune_worktrees();
119+
return 0;
120+
}
121+
122+
int cmd_worktree(int ac, const char **av, const char *prefix)
123+
{
124+
struct option options[] = {
125+
OPT_END()
126+
};
127+
128+
if (ac < 2)
129+
usage_with_options(worktree_usage, options);
130+
if (!strcmp(av[1], "prune"))
131+
return prune(ac - 1, av + 1, prefix);
132+
usage_with_options(worktree_usage, options);
133+
}

‎command-list.txt

+1
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,5 @@ git-verify-pack plumbinginterrogators
148148
git-verify-tag ancillaryinterrogators
149149
gitweb ancillaryinterrogators
150150
git-whatchanged ancillaryinterrogators
151+
git-worktree mainporcelain
151152
git-write-tree plumbingmanipulators

‎git.c

+1
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ static struct cmd_struct commands[] = {
483483
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
484484
{ "version", cmd_version },
485485
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
486+
{ "worktree", cmd_worktree, RUN_SETUP },
486487
{ "write-tree", cmd_write_tree, RUN_SETUP },
487488
};
488489

‎t/t2025-checkout-to.sh

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ test_expect_success 'checkout --to refuses to checkout locked branch' '
2828
! test -d .git/worktrees/zere
2929
'
3030

31+
test_expect_success 'checking out paths not complaining about linked checkouts' '
32+
(
33+
cd existing_empty &&
34+
echo dirty >>init.t &&
35+
git checkout master -- init.t
36+
)
37+
'
38+
3139
test_expect_success 'checkout --to a new worktree' '
3240
git rev-parse HEAD >expect &&
3341
git checkout --detach --to here master &&

‎t/t2026-prune-linked-checkouts.sh

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ test_expect_success initialize '
88
git commit --allow-empty -m init
99
'
1010

11-
test_expect_success 'prune --worktrees on normal repo' '
12-
git prune --worktrees &&
13-
test_must_fail git prune --worktrees abc
11+
test_expect_success 'worktree prune on normal repo' '
12+
git worktree prune &&
13+
test_must_fail git worktree prune abc
1414
'
1515

1616
test_expect_success 'prune files inside $GIT_DIR/worktrees' '
1717
mkdir .git/worktrees &&
1818
: >.git/worktrees/abc &&
19-
git prune --worktrees --verbose >actual &&
19+
git worktree prune --verbose >actual &&
2020
cat >expect <<EOF &&
2121
Removing worktrees/abc: not a valid directory
2222
EOF
@@ -31,7 +31,7 @@ test_expect_success 'prune directories without gitdir' '
3131
cat >expect <<EOF &&
3232
Removing worktrees/def: gitdir file does not exist
3333
EOF
34-
git prune --worktrees --verbose >actual &&
34+
git worktree prune --verbose >actual &&
3535
test_i18ncmp expect actual &&
3636
! test -d .git/worktrees/def &&
3737
! test -d .git/worktrees
@@ -42,7 +42,7 @@ test_expect_success SANITY 'prune directories with unreadable gitdir' '
4242
: >.git/worktrees/def/def &&
4343
: >.git/worktrees/def/gitdir &&
4444
chmod u-r .git/worktrees/def/gitdir &&
45-
git prune --worktrees --verbose >actual &&
45+
git worktree prune --verbose >actual &&
4646
test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
4747
! test -d .git/worktrees/def &&
4848
! test -d .git/worktrees
@@ -52,7 +52,7 @@ test_expect_success 'prune directories with invalid gitdir' '
5252
mkdir -p .git/worktrees/def/abc &&
5353
: >.git/worktrees/def/def &&
5454
: >.git/worktrees/def/gitdir &&
55-
git prune --worktrees --verbose >actual &&
55+
git worktree prune --verbose >actual &&
5656
test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
5757
! test -d .git/worktrees/def &&
5858
! test -d .git/worktrees
@@ -62,7 +62,7 @@ test_expect_success 'prune directories with gitdir pointing to nowhere' '
6262
mkdir -p .git/worktrees/def/abc &&
6363
: >.git/worktrees/def/def &&
6464
echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
65-
git prune --worktrees --verbose >actual &&
65+
git worktree prune --verbose >actual &&
6666
test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
6767
! test -d .git/worktrees/def &&
6868
! test -d .git/worktrees
@@ -72,7 +72,7 @@ test_expect_success 'not prune locked checkout' '
7272
test_when_finished rm -r .git/worktrees &&
7373
mkdir -p .git/worktrees/ghi &&
7474
: >.git/worktrees/ghi/locked &&
75-
git prune --worktrees &&
75+
git worktree prune &&
7676
test -d .git/worktrees/ghi
7777
'
7878

@@ -82,14 +82,14 @@ test_expect_success 'not prune recent checkouts' '
8282
mkdir -p .git/worktrees/jlm &&
8383
echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
8484
rmdir zz &&
85-
git prune --worktrees --verbose --expire=2.days.ago &&
85+
git worktree prune --verbose --expire=2.days.ago &&
8686
test -d .git/worktrees/jlm
8787
'
8888

8989
test_expect_success 'not prune proper checkouts' '
9090
test_when_finished rm -r .git/worktrees &&
9191
git checkout "--to=$PWD/nop" --detach master &&
92-
git prune --worktrees &&
92+
git worktree prune &&
9393
test -d .git/worktrees/nop
9494
'
9595

0 commit comments

Comments
 (0)
Please sign in to comment.