worktree: new place for "git prune --worktrees"
Commit 23af91d (prune: strategies for linked checkouts - 2014-11-30) adds "--worktrees" to "git prune" without realizing that "git prune" is for object database only. This patch moves the same functionality to a new command "git worktree". Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Nguyễn Thái Ngọc Duy committed
Jun 29, 2015 at 19:51 UTC
df0b6cfbda88144714541664fb501146d6465a82
11 files changed
+198
-114
.gitignore
+1
@@ -171,6 +171,7 @@
171
/git-verify-tag
172
/git-web--browse
173
/git-whatchanged
174
+/git-worktree
175
/git-write-tree
176
/git-core-*/?*
177
/gitweb/GITWEB-BUILD-OPTIONS
Documentation/git-prune.txt
-3
@@ -48,9 +48,6 @@ OPTIONS
48
--expire <time>::
49
Only expire loose objects older than <time>.
50
51
---worktrees::
52
- Prune dead working tree information in $GIT_DIR/worktrees.
53
-
51
<head>...::
52
In addition to objects
53
reachable from any of our references, keep objects
Documentation/git-worktree.txt
new
+48
@@ -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
@@ -886,6 +886,7 @@ BUILTIN_OBJS += builtin/var.o
886
BUILTIN_OBJS += builtin/verify-commit.o
887
BUILTIN_OBJS += builtin/verify-pack.o
888
BUILTIN_OBJS += builtin/verify-tag.o
889
+BUILTIN_OBJS += builtin/worktree.o
890
BUILTIN_OBJS += builtin/write-tree.o
891
892
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
builtin.h
+1
@@ -133,6 +133,7 @@ extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
133
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
134
extern int cmd_version(int argc, const char **argv, const char *prefix);
135
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);
137
extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
138
extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
139
extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
builtin/gc.c
+1
-1
@@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
293
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
294
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
295
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);
297
argv_array_pushl(&rerere, "rerere", "gc", NULL);
298
299
gc_config();
builtin/prune.c
-99
@@ -6,7 +6,6 @@
6
#include "reachable.h"
7
#include "parse-options.h"
8
#include "progress.h"
9
-#include "dir.h"
9
10
static const char * const prune_usage[] = {
11
N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data)
75
return 0;
76
}
77
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
-
78
/*
79
* Write errors (particularly out of space) can result in
80
* failed temporary packs (and more rarely indexes and other
@@ -191,12 +101,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
101
{
102
struct rev_info revs;
103
struct progress *progress = NULL;
194
- int do_prune_worktrees = 0;
104
const struct option options[] = {
105
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
106
OPT__VERBOSE(&verbose, N_("report pruned objects")),
107
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
199
- OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
108
OPT_EXPIRY_DATE(0, "expire", &expire,
109
N_("expire objects older than <time>")),
110
OPT_END()
@@ -210,13 +118,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
118
119
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
120
213
- if (do_prune_worktrees) {
214
- if (argc)
215
- die(_("--worktrees does not take extra arguments"));
216
- prune_worktrees();
217
- return 0;
218
- }
219
-
121
while (argc--) {
122
unsigned char sha1[20];
123
const char *name = *argv++;
builtin/worktree.c
new
+133
@@ -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
@@ -138,4 +138,5 @@ git-verify-pack plumbinginterrogators
138
git-verify-tag ancillaryinterrogators
139
gitweb ancillaryinterrogators
140
git-whatchanged ancillaryinterrogators
141
+git-worktree mainporcelain
142
git-write-tree plumbingmanipulators
git.c
+1
@@ -484,6 +484,7 @@ static struct cmd_struct commands[] = {
484
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
485
{ "version", cmd_version },
486
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
487
+ { "worktree", cmd_worktree, RUN_SETUP },
488
{ "write-tree", cmd_write_tree, RUN_SETUP },
489
};
490
t/t2026-prune-linked-checkouts.sh
+11
-11
@@ -8,15 +8,15 @@ test_expect_success initialize '
8
git commit --allow-empty -m init
9
'
10
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
14
'
15
16
test_expect_success 'prune files inside $GIT_DIR/worktrees' '
17
mkdir .git/worktrees &&
18
: >.git/worktrees/abc &&
19
- git prune --worktrees --verbose >actual &&
19
+ git worktree prune --verbose >actual &&
20
cat >expect <<EOF &&
21
Removing worktrees/abc: not a valid directory
22
EOF
@@ -31,7 +31,7 @@ test_expect_success 'prune directories without gitdir' '
31
cat >expect <<EOF &&
32
Removing worktrees/def: gitdir file does not exist
33
EOF
34
- git prune --worktrees --verbose >actual &&
34
+ git worktree prune --verbose >actual &&
35
test_i18ncmp expect actual &&
36
! test -d .git/worktrees/def &&
37
! test -d .git/worktrees
@@ -42,7 +42,7 @@ test_expect_success SANITY 'prune directories with unreadable gitdir' '
42
: >.git/worktrees/def/def &&
43
: >.git/worktrees/def/gitdir &&
44
chmod u-r .git/worktrees/def/gitdir &&
45
- git prune --worktrees --verbose >actual &&
45
+ git worktree prune --verbose >actual &&
46
test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
47
! test -d .git/worktrees/def &&
48
! test -d .git/worktrees
@@ -52,7 +52,7 @@ test_expect_success 'prune directories with invalid gitdir' '
52
mkdir -p .git/worktrees/def/abc &&
53
: >.git/worktrees/def/def &&
54
: >.git/worktrees/def/gitdir &&
55
- git prune --worktrees --verbose >actual &&
55
+ git worktree prune --verbose >actual &&
56
test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
57
! test -d .git/worktrees/def &&
58
! test -d .git/worktrees
@@ -62,7 +62,7 @@ test_expect_success 'prune directories with gitdir pointing to nowhere' '
62
mkdir -p .git/worktrees/def/abc &&
63
: >.git/worktrees/def/def &&
64
echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
65
- git prune --worktrees --verbose >actual &&
65
+ git worktree prune --verbose >actual &&
66
test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
67
! test -d .git/worktrees/def &&
68
! test -d .git/worktrees
@@ -72,7 +72,7 @@ test_expect_success 'not prune locked checkout' '
72
test_when_finished rm -r .git/worktrees &&
73
mkdir -p .git/worktrees/ghi &&
74
: >.git/worktrees/ghi/locked &&
75
- git prune --worktrees &&
75
+ git worktree prune &&
76
test -d .git/worktrees/ghi
77
'
78
@@ -82,14 +82,14 @@ test_expect_success 'not prune recent checkouts' '
82
mkdir -p .git/worktrees/jlm &&
83
echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
84
rmdir zz &&
85
- git prune --worktrees --verbose --expire=2.days.ago &&
85
+ git worktree prune --verbose --expire=2.days.ago &&
86
test -d .git/worktrees/jlm
87
'
88
89
test_expect_success 'not prune proper checkouts' '
90
test_when_finished rm -r .git/worktrees &&
91
git checkout "--to=$PWD/nop" --detach master &&
92
- git prune --worktrees &&
92
+ git worktree prune &&
93
test -d .git/worktrees/nop
94
'
95