diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/checkout.c | 19 | ||||
-rw-r--r-- | builtin/prune.c | 95 |
2 files changed, 112 insertions, 2 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c index 797e14df41..645135a812 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -826,7 +826,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, const char *path = opts->new_worktree, *name; struct stat st; struct child_process cp; - int counter = 0, len; + int counter = 0, len, ret; if (!new->commit) die(_("no branch specified")); @@ -857,11 +857,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, if (mkdir(sb_repo.buf, 0777)) die_errno(_("could not create directory of '%s'"), sb_repo.buf); + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + strbuf_addf(&sb_git, "%s/.git", path); if (safe_create_leading_directories_const(sb_git.buf)) die_errno(_("could not create leading directories of '%s'"), sb_git.buf); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", real_path(get_git_common_dir()), name); /* @@ -870,6 +880,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, * value would do because this value will be ignored and * replaced at the next (real) checkout. */ + strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); strbuf_reset(&sb); @@ -885,7 +896,11 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, memset(&cp, 0, sizeof(cp)); cp.git_cmd = 1; cp.argv = opts->saved_argv; - return run_command(&cp); + ret = run_command(&cp); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + return ret; } static int git_checkout_config(const char *var, const char *value, void *cb) diff --git a/builtin/prune.c b/builtin/prune.c index 04d3b12ae4..f08670a984 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -76,6 +76,91 @@ static int prune_subdir(int nr, const char *path, void *data) return 0; } +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; + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + return 1; + } + free(path); + return st.st_mtime <= expire; +} + +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); +} + /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other @@ -102,10 +187,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; + int do_prune_worktrees = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), OPT_END() @@ -118,6 +205,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix) init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + + if (do_prune_worktrees) { + if (argc) + die(_("--worktrees does not take extra arguments")); + prune_worktrees(); + return 0; + } + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; |