summaryrefslogtreecommitdiff
path: root/builtin/gc.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/gc.c')
-rw-r--r--builtin/gc.c426
1 files changed, 328 insertions, 98 deletions
diff --git a/builtin/gc.c b/builtin/gc.c
index 1a80702b3d..3c5eae0edf 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -11,73 +11,122 @@
*/
#include "builtin.h"
-#include "cache.h"
+#include "config.h"
+#include "tempfile.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "run-command.h"
+#include "sigchain.h"
+#include "argv-array.h"
+#include "commit.h"
+#include "packfile.h"
#define FAILED_RUN "failed to run %s"
static const char * const builtin_gc_usage[] = {
- "git gc [options]",
+ N_("git gc [<options>]"),
NULL
};
static int pack_refs = 1;
+static int prune_reflogs = 1;
+static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
+static int detach_auto = 1;
+static timestamp_t gc_log_expire_time;
+static const char *gc_log_expire = "1.day.ago";
static const char *prune_expire = "2.weeks.ago";
+static const char *prune_worktrees_expire = "3.months.ago";
-#define MAX_ADD 10
-static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
-static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
+static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
+static struct argv_array reflog = ARGV_ARRAY_INIT;
+static struct argv_array repack = ARGV_ARRAY_INIT;
+static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
+static struct argv_array rerere = ARGV_ARRAY_INIT;
-static int gc_config(const char *var, const char *value, void *cb)
+static struct tempfile *pidfile;
+static struct lock_file log_lock;
+
+static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
+
+static void clean_pack_garbage(void)
{
- if (!strcmp(var, "gc.packrefs")) {
- if (value && !strcmp(value, "notbare"))
- pack_refs = -1;
- else
- pack_refs = git_config_bool(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.aggressivewindow")) {
- aggressive_window = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.auto")) {
- gc_auto_threshold = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.autopacklimit")) {
- gc_auto_pack_limit = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.pruneexpire")) {
- if (value && strcmp(value, "now")) {
- unsigned long now = approxidate("now");
- if (approxidate(value) >= now)
- return error("Invalid %s: '%s'", var, value);
- }
- return git_config_string(&prune_expire, var, value);
+ int i;
+ for (i = 0; i < pack_garbage.nr; i++)
+ unlink_or_warn(pack_garbage.items[i].string);
+ string_list_clear(&pack_garbage, 0);
+}
+
+static void report_pack_garbage(unsigned seen_bits, const char *path)
+{
+ if (seen_bits == PACKDIR_FILE_IDX)
+ string_list_append(&pack_garbage, path);
+}
+
+static void process_log_file(void)
+{
+ struct stat st;
+ if (fstat(get_lock_file_fd(&log_lock), &st)) {
+ /*
+ * Perhaps there was an i/o error or another
+ * unlikely situation. Try to make a note of
+ * this in gc.log along with any existing
+ * messages.
+ */
+ int saved_errno = errno;
+ fprintf(stderr, _("Failed to fstat %s: %s"),
+ get_tempfile_path(log_lock.tempfile),
+ strerror(saved_errno));
+ fflush(stderr);
+ commit_lock_file(&log_lock);
+ errno = saved_errno;
+ } else if (st.st_size) {
+ /* There was some error recorded in the lock file */
+ commit_lock_file(&log_lock);
+ } else {
+ /* No error, clean up any old gc.log */
+ unlink(git_path("gc.log"));
+ rollback_lock_file(&log_lock);
}
- return git_default_config(var, value, cb);
}
-static void append_option(const char **cmd, const char *opt, int max_length)
+static void process_log_file_at_exit(void)
{
- int i;
+ fflush(stderr);
+ process_log_file();
+}
+
+static void process_log_file_on_signal(int signo)
+{
+ process_log_file();
+ sigchain_pop(signo);
+ raise(signo);
+}
- for (i = 0; cmd[i]; i++)
- ;
+static void gc_config(void)
+{
+ const char *value;
- if (i + 2 >= max_length)
- die("Too many options specified");
- cmd[i++] = opt;
- cmd[i] = NULL;
+ if (!git_config_get_value("gc.packrefs", &value)) {
+ if (value && !strcmp(value, "notbare"))
+ pack_refs = -1;
+ else
+ pack_refs = git_config_bool("gc.packrefs", value);
+ }
+
+ git_config_get_int("gc.aggressivewindow", &aggressive_window);
+ git_config_get_int("gc.aggressivedepth", &aggressive_depth);
+ git_config_get_int("gc.auto", &gc_auto_threshold);
+ git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
+ git_config_get_bool("gc.autodetach", &detach_auto);
+ git_config_get_expiry("gc.pruneexpire", &prune_expire);
+ git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
+ git_config_get_expiry("gc.logexpiry", &gc_log_expire);
+
+ git_config(git_default_config, NULL);
}
static int too_many_loose_objects(void)
@@ -88,8 +137,6 @@ static int too_many_loose_objects(void)
* distributed, we can check only one and get a reasonable
* estimate.
*/
- char path[PATH_MAX];
- const char *objdir = get_object_directory();
DIR *dir;
struct dirent *ent;
int auto_threshold;
@@ -99,15 +146,11 @@ static int too_many_loose_objects(void)
if (gc_auto_threshold <= 0)
return 0;
- if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
- warning("insanely long object directory %.*s", 50, objdir);
- return 0;
- }
- dir = opendir(path);
+ dir = opendir(git_path("objects/17"));
if (!dir)
return 0;
- auto_threshold = (gc_auto_threshold + 255) / 256;
+ auto_threshold = DIV_ROUND_UP(gc_auto_threshold, 256);
while ((ent = readdir(dir)) != NULL) {
if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
ent->d_name[38] != '\0')
@@ -141,7 +184,23 @@ static int too_many_packs(void)
*/
cnt++;
}
- return gc_auto_pack_limit <= cnt;
+ return gc_auto_pack_limit < cnt;
+}
+
+static void add_repack_all_option(void)
+{
+ if (prune_expire && !strcmp(prune_expire, "now"))
+ argv_array_push(&repack, "-a");
+ else {
+ argv_array_push(&repack, "-A");
+ if (prune_expire)
+ argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
+ }
+}
+
+static void add_repack_incremental_option(void)
+{
+ argv_array_push(&repack, "--no-write-bitmap-index");
}
static int need_to_gc(void)
@@ -160,39 +219,166 @@ static int need_to_gc(void)
* there is no need.
*/
if (too_many_packs())
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now") ?
- "-a" : "-A",
- MAX_ADD);
- else if (!too_many_loose_objects())
+ add_repack_all_option();
+ else if (too_many_loose_objects())
+ add_repack_incremental_option();
+ else
return 0;
- if (run_hook(NULL, "pre-auto-gc", NULL))
+ if (run_hook_le(NULL, "pre-auto-gc", NULL))
return 0;
return 1;
}
+/* return NULL on success, else hostname running the gc */
+static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
+{
+ static struct lock_file lock;
+ char my_host[HOST_NAME_MAX + 1];
+ struct strbuf sb = STRBUF_INIT;
+ struct stat st;
+ uintmax_t pid;
+ FILE *fp;
+ int fd;
+ char *pidfile_path;
+
+ if (is_tempfile_active(pidfile))
+ /* already locked */
+ return NULL;
+
+ if (xgethostname(my_host, sizeof(my_host)))
+ xsnprintf(my_host, sizeof(my_host), "unknown");
+
+ pidfile_path = git_pathdup("gc.pid");
+ fd = hold_lock_file_for_update(&lock, pidfile_path,
+ LOCK_DIE_ON_ERROR);
+ if (!force) {
+ static char locking_host[HOST_NAME_MAX + 1];
+ static char *scan_fmt;
+ int should_exit;
+
+ if (!scan_fmt)
+ scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX);
+ fp = fopen(pidfile_path, "r");
+ memset(locking_host, 0, sizeof(locking_host));
+ should_exit =
+ fp != NULL &&
+ !fstat(fileno(fp), &st) &&
+ /*
+ * 12 hour limit is very generous as gc should
+ * never take that long. On the other hand we
+ * don't really need a strict limit here,
+ * running gc --auto one day late is not a big
+ * problem. --force can be used in manual gc
+ * after the user verifies that no gc is
+ * running.
+ */
+ time(NULL) - st.st_mtime <= 12 * 3600 &&
+ fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
+ /* be gentle to concurrent "gc" on remote hosts */
+ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
+ if (fp != NULL)
+ fclose(fp);
+ if (should_exit) {
+ if (fd >= 0)
+ rollback_lock_file(&lock);
+ *ret_pid = pid;
+ free(pidfile_path);
+ return locking_host;
+ }
+ }
+
+ strbuf_addf(&sb, "%"PRIuMAX" %s",
+ (uintmax_t) getpid(), my_host);
+ write_in_full(fd, sb.buf, sb.len);
+ strbuf_release(&sb);
+ commit_lock_file(&lock);
+ pidfile = register_tempfile(pidfile_path);
+ free(pidfile_path);
+ return NULL;
+}
+
+static int report_last_gc_error(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
+ struct stat st;
+ char *gc_log_path = git_pathdup("gc.log");
+
+ if (stat(gc_log_path, &st)) {
+ if (errno == ENOENT)
+ goto done;
+
+ ret = error_errno(_("Can't stat %s"), gc_log_path);
+ goto done;
+ }
+
+ if (st.st_mtime < gc_log_expire_time)
+ goto done;
+
+ ret = strbuf_read_file(&sb, gc_log_path, 0);
+ if (ret > 0)
+ ret = error(_("The last gc run reported the following. "
+ "Please correct the root cause\n"
+ "and remove %s.\n"
+ "Automatic cleanup will not be performed "
+ "until the file is removed.\n\n"
+ "%s"),
+ gc_log_path, sb.buf);
+ strbuf_release(&sb);
+done:
+ free(gc_log_path);
+ return ret;
+}
+
+static int gc_before_repack(void)
+{
+ if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, pack_refs_cmd.argv[0]);
+
+ if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, reflog.argv[0]);
+
+ pack_refs = 0;
+ prune_reflogs = 0;
+ return 0;
+}
+
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
- char buf[80];
+ int force = 0;
+ const char *name;
+ pid_t pid;
+ int daemonized = 0;
struct option builtin_gc_options[] = {
- OPT__QUIET(&quiet, "suppress progress reporting"),
- { OPTION_STRING, 0, "prune", &prune_expire, "date",
- "prune unreferenced objects",
+ OPT__QUIET(&quiet, N_("suppress progress reporting")),
+ { OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
+ N_("prune unreferenced objects"),
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
- OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
- OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+ OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
+ OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")),
+ OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")),
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_gc_usage, builtin_gc_options);
- git_config(gc_config, NULL);
+ argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+ argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
+ argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
+ argv_array_pushl(&prune, "prune", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
+ argv_array_pushl(&rerere, "rerere", "gc", NULL);
+
+ /* default expiry time, overwritten in gc_config */
+ gc_config();
+ if (parse_expiry_date(gc_log_expire, &gc_log_expire_time))
+ die(_("Failed to parse gc.logexpiry value %s"), gc_log_expire);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
@@ -203,15 +389,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_gc_usage, builtin_gc_options);
if (aggressive) {
- append_option(argv_repack, "-f", MAX_ADD);
- append_option(argv_repack, "--depth=250", MAX_ADD);
- if (aggressive_window > 0) {
- sprintf(buf, "--window=%d", aggressive_window);
- append_option(argv_repack, buf, MAX_ADD);
- }
+ argv_array_push(&repack, "-f");
+ if (aggressive_depth > 0)
+ argv_array_pushf(&repack, "--depth=%d", aggressive_depth);
+ if (aggressive_window > 0)
+ argv_array_pushf(&repack, "--window=%d", aggressive_window);
}
if (quiet)
- append_option(argv_repack, "-q", MAX_ADD);
+ argv_array_push(&repack, "-q");
if (auto_gc) {
/*
@@ -219,40 +404,85 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
*/
if (!need_to_gc())
return 0;
- fprintf(stderr,
- "Auto packing the repository for optimum performance.%s\n",
- quiet
- ? ""
- : (" You may also\n"
- "run \"git gc\" manually. See "
- "\"git help gc\" for more information."));
+ if (!quiet) {
+ if (detach_auto)
+ fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
+ else
+ fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
+ fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
+ }
+ if (detach_auto) {
+ if (report_last_gc_error())
+ return -1;
+
+ if (lock_repo_for_gc(force, &pid))
+ return 0;
+ if (gc_before_repack())
+ return -1;
+ delete_tempfile(&pidfile);
+
+ /*
+ * failure to daemonize is ok, we'll continue
+ * in foreground
+ */
+ daemonized = !daemonize();
+ }
} else
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now")
- ? "-a" : "-A",
- MAX_ADD);
+ add_repack_all_option();
+
+ name = lock_repo_for_gc(force, &pid);
+ if (name) {
+ if (auto_gc)
+ return 0; /* be quiet on --auto */
+ die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
+ name, (uintmax_t)pid);
+ }
- if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_pack_refs[0]);
+ if (daemonized) {
+ hold_lock_file_for_update(&log_lock,
+ git_path("gc.log"),
+ LOCK_DIE_ON_ERROR);
+ dup2(get_lock_file_fd(&log_lock), 2);
+ sigchain_push_common(process_log_file_on_signal);
+ atexit(process_log_file_at_exit);
+ }
- if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_reflog[0]);
+ if (gc_before_repack())
+ return -1;
- if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_repack[0]);
+ if (!repository_format_precious_objects) {
+ if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, repack.argv[0]);
- if (prune_expire) {
- argv_prune[2] = prune_expire;
- if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_prune[0]);
+ if (prune_expire) {
+ argv_array_push(&prune, prune_expire);
+ if (quiet)
+ argv_array_push(&prune, "--no-progress");
+ if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune.argv[0]);
+ }
}
- if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_rerere[0]);
+ if (prune_worktrees_expire) {
+ argv_array_push(&prune_worktrees, prune_worktrees_expire);
+ if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune_worktrees.argv[0]);
+ }
+
+ if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, rerere.argv[0]);
+
+ report_garbage = report_pack_garbage;
+ reprepare_packed_git();
+ if (pack_garbage.nr > 0)
+ clean_pack_garbage();
if (auto_gc && too_many_loose_objects())
- warning("There are too many unreachable loose objects; "
- "run 'git prune' to remove them.");
+ warning(_("There are too many unreachable loose objects; "
+ "run 'git prune' to remove them."));
+
+ if (!daemonized)
+ unlink(git_path("gc.log"));
return 0;
}