Browse Source
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>
maint
Nguyễn Thái Ngọc Duy
10 years ago
committed by
Junio C Hamano
11 changed files with 198 additions and 114 deletions
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
git-worktree(1) |
||||
=============== |
||||
|
||||
NAME |
||||
---- |
||||
git-worktree - Manage multiple worktrees |
||||
|
||||
|
||||
SYNOPSIS |
||||
-------- |
||||
[verse] |
||||
'git worktree prune' [-n] [-v] [--expire <expire>] |
||||
|
||||
DESCRIPTION |
||||
----------- |
||||
|
||||
Manage multiple worktrees attached to the same repository. These are |
||||
created by the command `git checkout --to`. |
||||
|
||||
COMMANDS |
||||
-------- |
||||
prune:: |
||||
|
||||
Prune working tree information in $GIT_DIR/worktrees. |
||||
|
||||
OPTIONS |
||||
------- |
||||
|
||||
-n:: |
||||
--dry-run:: |
||||
Do not remove anything; just report what it would |
||||
remove. |
||||
|
||||
-v:: |
||||
--verbose:: |
||||
Report all removals. |
||||
|
||||
--expire <time>:: |
||||
Only expire unused worktrees older than <time>. |
||||
|
||||
SEE ALSO |
||||
-------- |
||||
|
||||
linkgit:git-checkout[1] |
||||
|
||||
GIT |
||||
--- |
||||
Part of the linkgit:git[1] suite |
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
#include "cache.h" |
||||
#include "builtin.h" |
||||
#include "dir.h" |
||||
#include "parse-options.h" |
||||
|
||||
static const char * const worktree_usage[] = { |
||||
N_("git worktree prune [<options>]"), |
||||
NULL |
||||
}; |
||||
|
||||
static int show_only; |
||||
static int verbose; |
||||
static unsigned long expire; |
||||
|
||||
static int prune_worktree(const char *id, struct strbuf *reason) |
||||
{ |
||||
struct stat st; |
||||
char *path; |
||||
int fd, len; |
||||
|
||||
if (!is_directory(git_path("worktrees/%s", id))) { |
||||
strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); |
||||
return 1; |
||||
} |
||||
if (file_exists(git_path("worktrees/%s/locked", id))) |
||||
return 0; |
||||
if (stat(git_path("worktrees/%s/gitdir", id), &st)) { |
||||
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); |
||||
return 1; |
||||
} |
||||
fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); |
||||
if (fd < 0) { |
||||
strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), |
||||
id, strerror(errno)); |
||||
return 1; |
||||
} |
||||
len = st.st_size; |
||||
path = xmalloc(len + 1); |
||||
read_in_full(fd, path, len); |
||||
close(fd); |
||||
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) |
||||
len--; |
||||
if (!len) { |
||||
strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); |
||||
free(path); |
||||
return 1; |
||||
} |
||||
path[len] = '\0'; |
||||
if (!file_exists(path)) { |
||||
struct stat st_link; |
||||
free(path); |
||||
/* |
||||
* the repo is moved manually and has not been |
||||
* accessed since? |
||||
*/ |
||||
if (!stat(git_path("worktrees/%s/link", id), &st_link) && |
||||
st_link.st_nlink > 1) |
||||
return 0; |
||||
if (st.st_mtime <= expire) { |
||||
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); |
||||
return 1; |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
free(path); |
||||
return 0; |
||||
} |
||||
|
||||
static void prune_worktrees(void) |
||||
{ |
||||
struct strbuf reason = STRBUF_INIT; |
||||
struct strbuf path = STRBUF_INIT; |
||||
DIR *dir = opendir(git_path("worktrees")); |
||||
struct dirent *d; |
||||
int ret; |
||||
if (!dir) |
||||
return; |
||||
while ((d = readdir(dir)) != NULL) { |
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
||||
continue; |
||||
strbuf_reset(&reason); |
||||
if (!prune_worktree(d->d_name, &reason)) |
||||
continue; |
||||
if (show_only || verbose) |
||||
printf("%s\n", reason.buf); |
||||
if (show_only) |
||||
continue; |
||||
strbuf_reset(&path); |
||||
strbuf_addstr(&path, git_path("worktrees/%s", d->d_name)); |
||||
ret = remove_dir_recursively(&path, 0); |
||||
if (ret < 0 && errno == ENOTDIR) |
||||
ret = unlink(path.buf); |
||||
if (ret) |
||||
error(_("failed to remove: %s"), strerror(errno)); |
||||
} |
||||
closedir(dir); |
||||
if (!show_only) |
||||
rmdir(git_path("worktrees")); |
||||
strbuf_release(&reason); |
||||
strbuf_release(&path); |
||||
} |
||||
|
||||
static int prune(int ac, const char **av, const char *prefix) |
||||
{ |
||||
struct option options[] = { |
||||
OPT__DRY_RUN(&show_only, N_("do not remove, show only")), |
||||
OPT__VERBOSE(&verbose, N_("report pruned objects")), |
||||
OPT_EXPIRY_DATE(0, "expire", &expire, |
||||
N_("expire objects older than <time>")), |
||||
OPT_END() |
||||
}; |
||||
|
||||
expire = ULONG_MAX; |
||||
ac = parse_options(ac, av, prefix, options, worktree_usage, 0); |
||||
if (ac) |
||||
usage_with_options(worktree_usage, options); |
||||
prune_worktrees(); |
||||
return 0; |
||||
} |
||||
|
||||
int cmd_worktree(int ac, const char **av, const char *prefix) |
||||
{ |
||||
struct option options[] = { |
||||
OPT_END() |
||||
}; |
||||
|
||||
if (ac < 2) |
||||
usage_with_options(worktree_usage, options); |
||||
if (!strcmp(av[1], "prune")) |
||||
return prune(ac - 1, av + 1, prefix); |
||||
usage_with_options(worktree_usage, options); |
||||
} |
Loading…
Reference in new issue