mirror of
https://github.com/git/git
synced 2024-08-28 12:09:19 +00:00
![Eric Sunshine](/assets/img/avatar_default.png)
By default, "git worktree add" refuses to create a new worktree when the requested branch is already checked out elsewhere. Add a --force option to override this safeguard. Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
169 lines
4.2 KiB
C
169 lines
4.2 KiB
C
#include "cache.h"
|
|
#include "builtin.h"
|
|
#include "dir.h"
|
|
#include "parse-options.h"
|
|
#include "argv-array.h"
|
|
#include "run-command.h"
|
|
|
|
static const char * const worktree_usage[] = {
|
|
N_("git worktree add [<options>] <path> <branch>"),
|
|
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;
|
|
}
|
|
|
|
static int add(int ac, const char **av, const char *prefix)
|
|
{
|
|
struct child_process c;
|
|
int force = 0;
|
|
const char *path, *branch;
|
|
struct argv_array cmd = ARGV_ARRAY_INIT;
|
|
struct option options[] = {
|
|
OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
|
|
OPT_END()
|
|
};
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
if (ac != 2)
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
|
|
branch = av[1];
|
|
|
|
argv_array_push(&cmd, "checkout");
|
|
argv_array_pushl(&cmd, "--to", path, NULL);
|
|
if (force)
|
|
argv_array_push(&cmd, "--ignore-other-worktrees");
|
|
argv_array_push(&cmd, branch);
|
|
|
|
memset(&c, 0, sizeof(c));
|
|
c.git_cmd = 1;
|
|
c.argv = cmd.argv;
|
|
return run_command(&c);
|
|
}
|
|
|
|
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], "add"))
|
|
return add(ac - 1, av + 1, prefix);
|
|
if (!strcmp(av[1], "prune"))
|
|
return prune(ac - 1, av + 1, prefix);
|
|
usage_with_options(worktree_usage, options);
|
|
}
|