The eleventh batch
[git/gitster.git] / builtin / gc.c
blobd52735354c9f87ba4e8acb593dd11aa0482223e1
1 /*
2 * git gc builtin command
4 * Cleanup unreachable files and optimize the repository.
6 * Copyright (c) 2007 James Bowes
8 * Based on git-gc.sh, which is
10 * Copyright (c) 2006 Shawn O. Pearce
12 #define USE_THE_REPOSITORY_VARIABLE
13 #include "builtin.h"
14 #include "abspath.h"
15 #include "date.h"
16 #include "environment.h"
17 #include "hex.h"
18 #include "config.h"
19 #include "tempfile.h"
20 #include "lockfile.h"
21 #include "parse-options.h"
22 #include "run-command.h"
23 #include "sigchain.h"
24 #include "strvec.h"
25 #include "commit.h"
26 #include "commit-graph.h"
27 #include "packfile.h"
28 #include "object-file.h"
29 #include "object-store-ll.h"
30 #include "pack.h"
31 #include "pack-objects.h"
32 #include "path.h"
33 #include "blob.h"
34 #include "tree.h"
35 #include "promisor-remote.h"
36 #include "refs.h"
37 #include "remote.h"
38 #include "exec-cmd.h"
39 #include "gettext.h"
40 #include "hook.h"
41 #include "setup.h"
42 #include "trace2.h"
44 #define FAILED_RUN "failed to run %s"
46 static const char * const builtin_gc_usage[] = {
47 N_("git gc [<options>]"),
48 NULL
51 static timestamp_t gc_log_expire_time;
53 static struct strvec reflog = STRVEC_INIT;
54 static struct strvec repack = STRVEC_INIT;
55 static struct strvec prune = STRVEC_INIT;
56 static struct strvec prune_worktrees = STRVEC_INIT;
57 static struct strvec rerere = STRVEC_INIT;
59 static struct tempfile *pidfile;
60 static struct lock_file log_lock;
62 static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
64 static void clean_pack_garbage(void)
66 int i;
67 for (i = 0; i < pack_garbage.nr; i++)
68 unlink_or_warn(pack_garbage.items[i].string);
69 string_list_clear(&pack_garbage, 0);
72 static void report_pack_garbage(unsigned seen_bits, const char *path)
74 if (seen_bits == PACKDIR_FILE_IDX)
75 string_list_append(&pack_garbage, path);
78 static void process_log_file(void)
80 struct stat st;
81 if (fstat(get_lock_file_fd(&log_lock), &st)) {
83 * Perhaps there was an i/o error or another
84 * unlikely situation. Try to make a note of
85 * this in gc.log along with any existing
86 * messages.
88 int saved_errno = errno;
89 fprintf(stderr, _("Failed to fstat %s: %s"),
90 get_lock_file_path(&log_lock),
91 strerror(saved_errno));
92 fflush(stderr);
93 commit_lock_file(&log_lock);
94 errno = saved_errno;
95 } else if (st.st_size) {
96 /* There was some error recorded in the lock file */
97 commit_lock_file(&log_lock);
98 } else {
99 /* No error, clean up any old gc.log */
100 unlink(git_path("gc.log"));
101 rollback_lock_file(&log_lock);
105 static void process_log_file_at_exit(void)
107 fflush(stderr);
108 process_log_file();
111 static int gc_config_is_timestamp_never(const char *var)
113 const char *value;
114 timestamp_t expire;
116 if (!git_config_get_value(var, &value) && value) {
117 if (parse_expiry_date(value, &expire))
118 die(_("failed to parse '%s' value '%s'"), var, value);
119 return expire == 0;
121 return 0;
124 struct gc_config {
125 int pack_refs;
126 int prune_reflogs;
127 int cruft_packs;
128 unsigned long max_cruft_size;
129 int aggressive_depth;
130 int aggressive_window;
131 int gc_auto_threshold;
132 int gc_auto_pack_limit;
133 int detach_auto;
134 char *gc_log_expire;
135 char *prune_expire;
136 char *prune_worktrees_expire;
137 char *repack_filter;
138 char *repack_filter_to;
139 unsigned long big_pack_threshold;
140 unsigned long max_delta_cache_size;
143 #define GC_CONFIG_INIT { \
144 .pack_refs = 1, \
145 .prune_reflogs = 1, \
146 .cruft_packs = 1, \
147 .aggressive_depth = 50, \
148 .aggressive_window = 250, \
149 .gc_auto_threshold = 6700, \
150 .gc_auto_pack_limit = 50, \
151 .detach_auto = 1, \
152 .gc_log_expire = xstrdup("1.day.ago"), \
153 .prune_expire = xstrdup("2.weeks.ago"), \
154 .prune_worktrees_expire = xstrdup("3.months.ago"), \
155 .max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE, \
158 static void gc_config_release(struct gc_config *cfg)
160 free(cfg->gc_log_expire);
161 free(cfg->prune_expire);
162 free(cfg->prune_worktrees_expire);
163 free(cfg->repack_filter);
164 free(cfg->repack_filter_to);
167 static void gc_config(struct gc_config *cfg)
169 const char *value;
170 char *owned = NULL;
172 if (!git_config_get_value("gc.packrefs", &value)) {
173 if (value && !strcmp(value, "notbare"))
174 cfg->pack_refs = -1;
175 else
176 cfg->pack_refs = git_config_bool("gc.packrefs", value);
179 if (gc_config_is_timestamp_never("gc.reflogexpire") &&
180 gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
181 cfg->prune_reflogs = 0;
183 git_config_get_int("gc.aggressivewindow", &cfg->aggressive_window);
184 git_config_get_int("gc.aggressivedepth", &cfg->aggressive_depth);
185 git_config_get_int("gc.auto", &cfg->gc_auto_threshold);
186 git_config_get_int("gc.autopacklimit", &cfg->gc_auto_pack_limit);
187 git_config_get_bool("gc.autodetach", &cfg->detach_auto);
188 git_config_get_bool("gc.cruftpacks", &cfg->cruft_packs);
189 git_config_get_ulong("gc.maxcruftsize", &cfg->max_cruft_size);
191 if (!repo_config_get_expiry(the_repository, "gc.pruneexpire", &owned)) {
192 free(cfg->prune_expire);
193 cfg->prune_expire = owned;
196 if (!repo_config_get_expiry(the_repository, "gc.worktreepruneexpire", &owned)) {
197 free(cfg->prune_worktrees_expire);
198 cfg->prune_worktrees_expire = owned;
201 if (!repo_config_get_expiry(the_repository, "gc.logexpiry", &owned)) {
202 free(cfg->gc_log_expire);
203 cfg->gc_log_expire = owned;
206 git_config_get_ulong("gc.bigpackthreshold", &cfg->big_pack_threshold);
207 git_config_get_ulong("pack.deltacachesize", &cfg->max_delta_cache_size);
209 if (!git_config_get_string("gc.repackfilter", &owned)) {
210 free(cfg->repack_filter);
211 cfg->repack_filter = owned;
214 if (!git_config_get_string("gc.repackfilterto", &owned)) {
215 free(cfg->repack_filter_to);
216 cfg->repack_filter_to = owned;
219 git_config(git_default_config, NULL);
222 enum schedule_priority {
223 SCHEDULE_NONE = 0,
224 SCHEDULE_WEEKLY = 1,
225 SCHEDULE_DAILY = 2,
226 SCHEDULE_HOURLY = 3,
229 static enum schedule_priority parse_schedule(const char *value)
231 if (!value)
232 return SCHEDULE_NONE;
233 if (!strcasecmp(value, "hourly"))
234 return SCHEDULE_HOURLY;
235 if (!strcasecmp(value, "daily"))
236 return SCHEDULE_DAILY;
237 if (!strcasecmp(value, "weekly"))
238 return SCHEDULE_WEEKLY;
239 return SCHEDULE_NONE;
242 struct maintenance_run_opts {
243 int auto_flag;
244 int detach;
245 int quiet;
246 enum schedule_priority schedule;
248 #define MAINTENANCE_RUN_OPTS_INIT { \
249 .detach = -1, \
252 static int pack_refs_condition(UNUSED struct gc_config *cfg)
255 * The auto-repacking logic for refs is handled by the ref backends and
256 * exposed via `git pack-refs --auto`. We thus always return truish
257 * here and let the backend decide for us.
259 return 1;
262 static int maintenance_task_pack_refs(struct maintenance_run_opts *opts,
263 UNUSED struct gc_config *cfg)
265 struct child_process cmd = CHILD_PROCESS_INIT;
267 cmd.git_cmd = 1;
268 strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
269 if (opts->auto_flag)
270 strvec_push(&cmd.args, "--auto");
272 return run_command(&cmd);
275 static int too_many_loose_objects(struct gc_config *cfg)
278 * Quickly check if a "gc" is needed, by estimating how
279 * many loose objects there are. Because SHA-1 is evenly
280 * distributed, we can check only one and get a reasonable
281 * estimate.
283 DIR *dir;
284 struct dirent *ent;
285 int auto_threshold;
286 int num_loose = 0;
287 int needed = 0;
288 const unsigned hexsz_loose = the_hash_algo->hexsz - 2;
290 dir = opendir(git_path("objects/17"));
291 if (!dir)
292 return 0;
294 auto_threshold = DIV_ROUND_UP(cfg->gc_auto_threshold, 256);
295 while ((ent = readdir(dir)) != NULL) {
296 if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
297 ent->d_name[hexsz_loose] != '\0')
298 continue;
299 if (++num_loose > auto_threshold) {
300 needed = 1;
301 break;
304 closedir(dir);
305 return needed;
308 static struct packed_git *find_base_packs(struct string_list *packs,
309 unsigned long limit)
311 struct packed_git *p, *base = NULL;
313 for (p = get_all_packs(the_repository); p; p = p->next) {
314 if (!p->pack_local || p->is_cruft)
315 continue;
316 if (limit) {
317 if (p->pack_size >= limit)
318 string_list_append(packs, p->pack_name);
319 } else if (!base || base->pack_size < p->pack_size) {
320 base = p;
324 if (base)
325 string_list_append(packs, base->pack_name);
327 return base;
330 static int too_many_packs(struct gc_config *cfg)
332 struct packed_git *p;
333 int cnt;
335 if (cfg->gc_auto_pack_limit <= 0)
336 return 0;
338 for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
339 if (!p->pack_local)
340 continue;
341 if (p->pack_keep)
342 continue;
344 * Perhaps check the size of the pack and count only
345 * very small ones here?
347 cnt++;
349 return cfg->gc_auto_pack_limit < cnt;
352 static uint64_t total_ram(void)
354 #if defined(HAVE_SYSINFO)
355 struct sysinfo si;
357 if (!sysinfo(&si))
358 return si.totalram;
359 #elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM))
360 int64_t physical_memory;
361 int mib[2];
362 size_t length;
364 mib[0] = CTL_HW;
365 # if defined(HW_MEMSIZE)
366 mib[1] = HW_MEMSIZE;
367 # else
368 mib[1] = HW_PHYSMEM;
369 # endif
370 length = sizeof(int64_t);
371 if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0))
372 return physical_memory;
373 #elif defined(GIT_WINDOWS_NATIVE)
374 MEMORYSTATUSEX memInfo;
376 memInfo.dwLength = sizeof(MEMORYSTATUSEX);
377 if (GlobalMemoryStatusEx(&memInfo))
378 return memInfo.ullTotalPhys;
379 #endif
380 return 0;
383 static uint64_t estimate_repack_memory(struct gc_config *cfg,
384 struct packed_git *pack)
386 unsigned long nr_objects = repo_approximate_object_count(the_repository);
387 size_t os_cache, heap;
389 if (!pack || !nr_objects)
390 return 0;
393 * First we have to scan through at least one pack.
394 * Assume enough room in OS file cache to keep the entire pack
395 * or we may accidentally evict data of other processes from
396 * the cache.
398 os_cache = pack->pack_size + pack->index_size;
399 /* then pack-objects needs lots more for book keeping */
400 heap = sizeof(struct object_entry) * nr_objects;
402 * internal rev-list --all --objects takes up some memory too,
403 * let's say half of it is for blobs
405 heap += sizeof(struct blob) * nr_objects / 2;
407 * and the other half is for trees (commits and tags are
408 * usually insignificant)
410 heap += sizeof(struct tree) * nr_objects / 2;
411 /* and then obj_hash[], underestimated in fact */
412 heap += sizeof(struct object *) * nr_objects;
413 /* revindex is used also */
414 heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects;
416 * read_sha1_file() (either at delta calculation phase, or
417 * writing phase) also fills up the delta base cache
419 heap += delta_base_cache_limit;
420 /* and of course pack-objects has its own delta cache */
421 heap += cfg->max_delta_cache_size;
423 return os_cache + heap;
426 static int keep_one_pack(struct string_list_item *item, void *data UNUSED)
428 strvec_pushf(&repack, "--keep-pack=%s", basename(item->string));
429 return 0;
432 static void add_repack_all_option(struct gc_config *cfg,
433 struct string_list *keep_pack)
435 if (cfg->prune_expire && !strcmp(cfg->prune_expire, "now"))
436 strvec_push(&repack, "-a");
437 else if (cfg->cruft_packs) {
438 strvec_push(&repack, "--cruft");
439 if (cfg->prune_expire)
440 strvec_pushf(&repack, "--cruft-expiration=%s", cfg->prune_expire);
441 if (cfg->max_cruft_size)
442 strvec_pushf(&repack, "--max-cruft-size=%lu",
443 cfg->max_cruft_size);
444 } else {
445 strvec_push(&repack, "-A");
446 if (cfg->prune_expire)
447 strvec_pushf(&repack, "--unpack-unreachable=%s", cfg->prune_expire);
450 if (keep_pack)
451 for_each_string_list(keep_pack, keep_one_pack, NULL);
453 if (cfg->repack_filter && *cfg->repack_filter)
454 strvec_pushf(&repack, "--filter=%s", cfg->repack_filter);
455 if (cfg->repack_filter_to && *cfg->repack_filter_to)
456 strvec_pushf(&repack, "--filter-to=%s", cfg->repack_filter_to);
459 static void add_repack_incremental_option(void)
461 strvec_push(&repack, "--no-write-bitmap-index");
464 static int need_to_gc(struct gc_config *cfg)
467 * Setting gc.auto to 0 or negative can disable the
468 * automatic gc.
470 if (cfg->gc_auto_threshold <= 0)
471 return 0;
474 * If there are too many loose objects, but not too many
475 * packs, we run "repack -d -l". If there are too many packs,
476 * we run "repack -A -d -l". Otherwise we tell the caller
477 * there is no need.
479 if (too_many_packs(cfg)) {
480 struct string_list keep_pack = STRING_LIST_INIT_NODUP;
482 if (cfg->big_pack_threshold) {
483 find_base_packs(&keep_pack, cfg->big_pack_threshold);
484 if (keep_pack.nr >= cfg->gc_auto_pack_limit) {
485 cfg->big_pack_threshold = 0;
486 string_list_clear(&keep_pack, 0);
487 find_base_packs(&keep_pack, 0);
489 } else {
490 struct packed_git *p = find_base_packs(&keep_pack, 0);
491 uint64_t mem_have, mem_want;
493 mem_have = total_ram();
494 mem_want = estimate_repack_memory(cfg, p);
497 * Only allow 1/2 of memory for pack-objects, leave
498 * the rest for the OS and other processes in the
499 * system.
501 if (!mem_have || mem_want < mem_have / 2)
502 string_list_clear(&keep_pack, 0);
505 add_repack_all_option(cfg, &keep_pack);
506 string_list_clear(&keep_pack, 0);
507 } else if (too_many_loose_objects(cfg))
508 add_repack_incremental_option();
509 else
510 return 0;
512 if (run_hooks(the_repository, "pre-auto-gc"))
513 return 0;
514 return 1;
517 /* return NULL on success, else hostname running the gc */
518 static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
520 struct lock_file lock = LOCK_INIT;
521 char my_host[HOST_NAME_MAX + 1];
522 struct strbuf sb = STRBUF_INIT;
523 struct stat st;
524 uintmax_t pid;
525 FILE *fp;
526 int fd;
527 char *pidfile_path;
529 if (is_tempfile_active(pidfile))
530 /* already locked */
531 return NULL;
533 if (xgethostname(my_host, sizeof(my_host)))
534 xsnprintf(my_host, sizeof(my_host), "unknown");
536 pidfile_path = git_pathdup("gc.pid");
537 fd = hold_lock_file_for_update(&lock, pidfile_path,
538 LOCK_DIE_ON_ERROR);
539 if (!force) {
540 static char locking_host[HOST_NAME_MAX + 1];
541 static char *scan_fmt;
542 int should_exit;
544 if (!scan_fmt)
545 scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX);
546 fp = fopen(pidfile_path, "r");
547 memset(locking_host, 0, sizeof(locking_host));
548 should_exit =
549 fp != NULL &&
550 !fstat(fileno(fp), &st) &&
552 * 12 hour limit is very generous as gc should
553 * never take that long. On the other hand we
554 * don't really need a strict limit here,
555 * running gc --auto one day late is not a big
556 * problem. --force can be used in manual gc
557 * after the user verifies that no gc is
558 * running.
560 time(NULL) - st.st_mtime <= 12 * 3600 &&
561 fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
562 /* be gentle to concurrent "gc" on remote hosts */
563 (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
564 if (fp)
565 fclose(fp);
566 if (should_exit) {
567 if (fd >= 0)
568 rollback_lock_file(&lock);
569 *ret_pid = pid;
570 free(pidfile_path);
571 return locking_host;
575 strbuf_addf(&sb, "%"PRIuMAX" %s",
576 (uintmax_t) getpid(), my_host);
577 write_in_full(fd, sb.buf, sb.len);
578 strbuf_release(&sb);
579 commit_lock_file(&lock);
580 pidfile = register_tempfile(pidfile_path);
581 free(pidfile_path);
582 return NULL;
586 * Returns 0 if there was no previous error and gc can proceed, 1 if
587 * gc should not proceed due to an error in the last run. Prints a
588 * message and returns with a non-[01] status code if an error occurred
589 * while reading gc.log
591 static int report_last_gc_error(void)
593 struct strbuf sb = STRBUF_INIT;
594 int ret = 0;
595 ssize_t len;
596 struct stat st;
597 char *gc_log_path = git_pathdup("gc.log");
599 if (stat(gc_log_path, &st)) {
600 if (errno == ENOENT)
601 goto done;
603 ret = die_message_errno(_("cannot stat '%s'"), gc_log_path);
604 goto done;
607 if (st.st_mtime < gc_log_expire_time)
608 goto done;
610 len = strbuf_read_file(&sb, gc_log_path, 0);
611 if (len < 0)
612 ret = die_message_errno(_("cannot read '%s'"), gc_log_path);
613 else if (len > 0) {
615 * A previous gc failed. Report the error, and don't
616 * bother with an automatic gc run since it is likely
617 * to fail in the same way.
619 warning(_("The last gc run reported the following. "
620 "Please correct the root cause\n"
621 "and remove %s\n"
622 "Automatic cleanup will not be performed "
623 "until the file is removed.\n\n"
624 "%s"),
625 gc_log_path, sb.buf);
626 ret = 1;
628 strbuf_release(&sb);
629 done:
630 free(gc_log_path);
631 return ret;
634 static void gc_before_repack(struct maintenance_run_opts *opts,
635 struct gc_config *cfg)
638 * We may be called twice, as both the pre- and
639 * post-daemonized phases will call us, but running these
640 * commands more than once is pointless and wasteful.
642 static int done = 0;
643 if (done++)
644 return;
646 if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
647 die(FAILED_RUN, "pack-refs");
649 if (cfg->prune_reflogs) {
650 struct child_process cmd = CHILD_PROCESS_INIT;
652 cmd.git_cmd = 1;
653 strvec_pushv(&cmd.args, reflog.v);
654 if (run_command(&cmd))
655 die(FAILED_RUN, reflog.v[0]);
659 int cmd_gc(int argc,
660 const char **argv,
661 const char *prefix,
662 struct repository *repo UNUSED)
664 int aggressive = 0;
665 int quiet = 0;
666 int force = 0;
667 const char *name;
668 pid_t pid;
669 int daemonized = 0;
670 int keep_largest_pack = -1;
671 timestamp_t dummy;
672 struct child_process rerere_cmd = CHILD_PROCESS_INIT;
673 struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
674 struct gc_config cfg = GC_CONFIG_INIT;
675 const char *prune_expire_sentinel = "sentinel";
676 const char *prune_expire_arg = prune_expire_sentinel;
677 int ret;
679 struct option builtin_gc_options[] = {
680 OPT__QUIET(&quiet, N_("suppress progress reporting")),
681 { OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"),
682 N_("prune unreferenced objects"),
683 PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg },
684 OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")),
685 OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size,
686 N_("with --cruft, limit the size of new cruft packs")),
687 OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
688 OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
689 PARSE_OPT_NOCOMPLETE),
690 OPT_BOOL(0, "detach", &opts.detach,
691 N_("perform garbage collection in the background")),
692 OPT_BOOL_F(0, "force", &force,
693 N_("force running gc even if there may be another gc running"),
694 PARSE_OPT_NOCOMPLETE),
695 OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack,
696 N_("repack all other packs except the largest pack")),
697 OPT_END()
700 if (argc == 2 && !strcmp(argv[1], "-h"))
701 usage_with_options(builtin_gc_usage, builtin_gc_options);
703 strvec_pushl(&reflog, "reflog", "expire", "--all", NULL);
704 strvec_pushl(&repack, "repack", "-d", "-l", NULL);
705 strvec_pushl(&prune, "prune", "--expire", NULL);
706 strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
707 strvec_pushl(&rerere, "rerere", "gc", NULL);
709 gc_config(&cfg);
711 if (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
712 die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
714 if (cfg.pack_refs < 0)
715 cfg.pack_refs = !is_bare_repository();
717 argc = parse_options(argc, argv, prefix, builtin_gc_options,
718 builtin_gc_usage, 0);
719 if (argc > 0)
720 usage_with_options(builtin_gc_usage, builtin_gc_options);
722 if (prune_expire_arg != prune_expire_sentinel) {
723 free(cfg.prune_expire);
724 cfg.prune_expire = xstrdup_or_null(prune_expire_arg);
726 if (cfg.prune_expire && parse_expiry_date(cfg.prune_expire, &dummy))
727 die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
729 if (aggressive) {
730 strvec_push(&repack, "-f");
731 if (cfg.aggressive_depth > 0)
732 strvec_pushf(&repack, "--depth=%d", cfg.aggressive_depth);
733 if (cfg.aggressive_window > 0)
734 strvec_pushf(&repack, "--window=%d", cfg.aggressive_window);
736 if (quiet)
737 strvec_push(&repack, "-q");
739 if (opts.auto_flag) {
740 if (cfg.detach_auto && opts.detach < 0)
741 opts.detach = 1;
744 * Auto-gc should be least intrusive as possible.
746 if (!need_to_gc(&cfg)) {
747 ret = 0;
748 goto out;
751 if (!quiet) {
752 if (opts.detach > 0)
753 fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
754 else
755 fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
756 fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
758 } else {
759 struct string_list keep_pack = STRING_LIST_INIT_NODUP;
761 if (keep_largest_pack != -1) {
762 if (keep_largest_pack)
763 find_base_packs(&keep_pack, 0);
764 } else if (cfg.big_pack_threshold) {
765 find_base_packs(&keep_pack, cfg.big_pack_threshold);
768 add_repack_all_option(&cfg, &keep_pack);
769 string_list_clear(&keep_pack, 0);
772 if (opts.detach > 0) {
773 ret = report_last_gc_error();
774 if (ret == 1) {
775 /* Last gc --auto failed. Skip this one. */
776 ret = 0;
777 goto out;
779 } else if (ret) {
780 /* an I/O error occurred, already reported */
781 goto out;
784 if (lock_repo_for_gc(force, &pid)) {
785 ret = 0;
786 goto out;
789 gc_before_repack(&opts, &cfg); /* dies on failure */
790 delete_tempfile(&pidfile);
793 * failure to daemonize is ok, we'll continue
794 * in foreground
796 daemonized = !daemonize();
799 name = lock_repo_for_gc(force, &pid);
800 if (name) {
801 if (opts.auto_flag) {
802 ret = 0;
803 goto out; /* be quiet on --auto */
806 die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
807 name, (uintmax_t)pid);
810 if (daemonized) {
811 hold_lock_file_for_update(&log_lock,
812 git_path("gc.log"),
813 LOCK_DIE_ON_ERROR);
814 dup2(get_lock_file_fd(&log_lock), 2);
815 atexit(process_log_file_at_exit);
818 gc_before_repack(&opts, &cfg);
820 if (!repository_format_precious_objects) {
821 struct child_process repack_cmd = CHILD_PROCESS_INIT;
823 repack_cmd.git_cmd = 1;
824 repack_cmd.close_object_store = 1;
825 strvec_pushv(&repack_cmd.args, repack.v);
826 if (run_command(&repack_cmd))
827 die(FAILED_RUN, repack.v[0]);
829 if (cfg.prune_expire) {
830 struct child_process prune_cmd = CHILD_PROCESS_INIT;
832 /* run `git prune` even if using cruft packs */
833 strvec_push(&prune, cfg.prune_expire);
834 if (quiet)
835 strvec_push(&prune, "--no-progress");
836 if (repo_has_promisor_remote(the_repository))
837 strvec_push(&prune,
838 "--exclude-promisor-objects");
839 prune_cmd.git_cmd = 1;
840 strvec_pushv(&prune_cmd.args, prune.v);
841 if (run_command(&prune_cmd))
842 die(FAILED_RUN, prune.v[0]);
846 if (cfg.prune_worktrees_expire) {
847 struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
849 strvec_push(&prune_worktrees, cfg.prune_worktrees_expire);
850 prune_worktrees_cmd.git_cmd = 1;
851 strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
852 if (run_command(&prune_worktrees_cmd))
853 die(FAILED_RUN, prune_worktrees.v[0]);
856 rerere_cmd.git_cmd = 1;
857 strvec_pushv(&rerere_cmd.args, rerere.v);
858 if (run_command(&rerere_cmd))
859 die(FAILED_RUN, rerere.v[0]);
861 report_garbage = report_pack_garbage;
862 reprepare_packed_git(the_repository);
863 if (pack_garbage.nr > 0) {
864 close_object_store(the_repository->objects);
865 clean_pack_garbage();
868 if (the_repository->settings.gc_write_commit_graph == 1)
869 write_commit_graph_reachable(the_repository->objects->odb,
870 !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
871 NULL);
873 if (opts.auto_flag && too_many_loose_objects(&cfg))
874 warning(_("There are too many unreachable loose objects; "
875 "run 'git prune' to remove them."));
877 if (!daemonized)
878 unlink(git_path("gc.log"));
880 out:
881 gc_config_release(&cfg);
882 return 0;
885 static const char *const builtin_maintenance_run_usage[] = {
886 N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>] [--schedule]"),
887 NULL
890 static int maintenance_opt_schedule(const struct option *opt, const char *arg,
891 int unset)
893 enum schedule_priority *priority = opt->value;
895 if (unset)
896 die(_("--no-schedule is not allowed"));
898 *priority = parse_schedule(arg);
900 if (!*priority)
901 die(_("unrecognized --schedule argument '%s'"), arg);
903 return 0;
906 /* Remember to update object flag allocation in object.h */
907 #define SEEN (1u<<0)
909 struct cg_auto_data {
910 int num_not_in_graph;
911 int limit;
914 static int dfs_on_ref(const char *refname UNUSED,
915 const char *referent UNUSED,
916 const struct object_id *oid,
917 int flags UNUSED,
918 void *cb_data)
920 struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
921 int result = 0;
922 struct object_id peeled;
923 struct commit_list *stack = NULL;
924 struct commit *commit;
926 if (!peel_iterated_oid(the_repository, oid, &peeled))
927 oid = &peeled;
928 if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
929 return 0;
931 commit = lookup_commit(the_repository, oid);
932 if (!commit)
933 return 0;
934 if (repo_parse_commit(the_repository, commit) ||
935 commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
936 return 0;
938 data->num_not_in_graph++;
940 if (data->num_not_in_graph >= data->limit)
941 return 1;
943 commit_list_append(commit, &stack);
945 while (!result && stack) {
946 struct commit_list *parent;
948 commit = pop_commit(&stack);
950 for (parent = commit->parents; parent; parent = parent->next) {
951 if (repo_parse_commit(the_repository, parent->item) ||
952 commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
953 parent->item->object.flags & SEEN)
954 continue;
956 parent->item->object.flags |= SEEN;
957 data->num_not_in_graph++;
959 if (data->num_not_in_graph >= data->limit) {
960 result = 1;
961 break;
964 commit_list_append(parent->item, &stack);
968 free_commit_list(stack);
969 return result;
972 static int should_write_commit_graph(struct gc_config *cfg UNUSED)
974 int result;
975 struct cg_auto_data data;
977 data.num_not_in_graph = 0;
978 data.limit = 100;
979 git_config_get_int("maintenance.commit-graph.auto",
980 &data.limit);
982 if (!data.limit)
983 return 0;
984 if (data.limit < 0)
985 return 1;
987 result = refs_for_each_ref(get_main_ref_store(the_repository),
988 dfs_on_ref, &data);
990 repo_clear_commit_marks(the_repository, SEEN);
992 return result;
995 static int run_write_commit_graph(struct maintenance_run_opts *opts)
997 struct child_process child = CHILD_PROCESS_INIT;
999 child.git_cmd = child.close_object_store = 1;
1000 strvec_pushl(&child.args, "commit-graph", "write",
1001 "--split", "--reachable", NULL);
1003 if (opts->quiet)
1004 strvec_push(&child.args, "--no-progress");
1006 return !!run_command(&child);
1009 static int maintenance_task_commit_graph(struct maintenance_run_opts *opts,
1010 struct gc_config *cfg UNUSED)
1012 prepare_repo_settings(the_repository);
1013 if (!the_repository->settings.core_commit_graph)
1014 return 0;
1016 if (run_write_commit_graph(opts)) {
1017 error(_("failed to write commit-graph"));
1018 return 1;
1021 return 0;
1024 static int fetch_remote(struct remote *remote, void *cbdata)
1026 struct maintenance_run_opts *opts = cbdata;
1027 struct child_process child = CHILD_PROCESS_INIT;
1029 if (remote->skip_default_update)
1030 return 0;
1032 child.git_cmd = 1;
1033 strvec_pushl(&child.args, "fetch", remote->name,
1034 "--prefetch", "--prune", "--no-tags",
1035 "--no-write-fetch-head", "--recurse-submodules=no",
1036 NULL);
1038 if (opts->quiet)
1039 strvec_push(&child.args, "--quiet");
1041 return !!run_command(&child);
1044 static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
1045 struct gc_config *cfg UNUSED)
1047 if (for_each_remote(fetch_remote, opts)) {
1048 error(_("failed to prefetch remotes"));
1049 return 1;
1052 return 0;
1055 static int maintenance_task_gc(struct maintenance_run_opts *opts,
1056 struct gc_config *cfg UNUSED)
1058 struct child_process child = CHILD_PROCESS_INIT;
1060 child.git_cmd = child.close_object_store = 1;
1061 strvec_push(&child.args, "gc");
1063 if (opts->auto_flag)
1064 strvec_push(&child.args, "--auto");
1065 if (opts->quiet)
1066 strvec_push(&child.args, "--quiet");
1067 else
1068 strvec_push(&child.args, "--no-quiet");
1069 strvec_push(&child.args, "--no-detach");
1071 return run_command(&child);
1074 static int prune_packed(struct maintenance_run_opts *opts)
1076 struct child_process child = CHILD_PROCESS_INIT;
1078 child.git_cmd = 1;
1079 strvec_push(&child.args, "prune-packed");
1081 if (opts->quiet)
1082 strvec_push(&child.args, "--quiet");
1084 return !!run_command(&child);
1087 struct write_loose_object_data {
1088 FILE *in;
1089 int count;
1090 int batch_size;
1093 static int loose_object_auto_limit = 100;
1095 static int loose_object_count(const struct object_id *oid UNUSED,
1096 const char *path UNUSED,
1097 void *data)
1099 int *count = (int*)data;
1100 if (++(*count) >= loose_object_auto_limit)
1101 return 1;
1102 return 0;
1105 static int loose_object_auto_condition(struct gc_config *cfg UNUSED)
1107 int count = 0;
1109 git_config_get_int("maintenance.loose-objects.auto",
1110 &loose_object_auto_limit);
1112 if (!loose_object_auto_limit)
1113 return 0;
1114 if (loose_object_auto_limit < 0)
1115 return 1;
1117 return for_each_loose_file_in_objdir(the_repository->objects->odb->path,
1118 loose_object_count,
1119 NULL, NULL, &count);
1122 static int bail_on_loose(const struct object_id *oid UNUSED,
1123 const char *path UNUSED,
1124 void *data UNUSED)
1126 return 1;
1129 static int write_loose_object_to_stdin(const struct object_id *oid,
1130 const char *path UNUSED,
1131 void *data)
1133 struct write_loose_object_data *d = (struct write_loose_object_data *)data;
1135 fprintf(d->in, "%s\n", oid_to_hex(oid));
1137 return ++(d->count) > d->batch_size;
1140 static int pack_loose(struct maintenance_run_opts *opts)
1142 struct repository *r = the_repository;
1143 int result = 0;
1144 struct write_loose_object_data data;
1145 struct child_process pack_proc = CHILD_PROCESS_INIT;
1148 * Do not start pack-objects process
1149 * if there are no loose objects.
1151 if (!for_each_loose_file_in_objdir(r->objects->odb->path,
1152 bail_on_loose,
1153 NULL, NULL, NULL))
1154 return 0;
1156 pack_proc.git_cmd = 1;
1158 strvec_push(&pack_proc.args, "pack-objects");
1159 if (opts->quiet)
1160 strvec_push(&pack_proc.args, "--quiet");
1161 strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path);
1163 pack_proc.in = -1;
1166 * git-pack-objects(1) ends up writing the pack hash to stdout, which
1167 * we do not care for.
1169 pack_proc.out = -1;
1171 if (start_command(&pack_proc)) {
1172 error(_("failed to start 'git pack-objects' process"));
1173 return 1;
1176 data.in = xfdopen(pack_proc.in, "w");
1177 data.count = 0;
1178 data.batch_size = 50000;
1180 for_each_loose_file_in_objdir(r->objects->odb->path,
1181 write_loose_object_to_stdin,
1182 NULL,
1183 NULL,
1184 &data);
1186 fclose(data.in);
1188 if (finish_command(&pack_proc)) {
1189 error(_("failed to finish 'git pack-objects' process"));
1190 result = 1;
1193 return result;
1196 static int maintenance_task_loose_objects(struct maintenance_run_opts *opts,
1197 struct gc_config *cfg UNUSED)
1199 return prune_packed(opts) || pack_loose(opts);
1202 static int incremental_repack_auto_condition(struct gc_config *cfg UNUSED)
1204 struct packed_git *p;
1205 int incremental_repack_auto_limit = 10;
1206 int count = 0;
1208 prepare_repo_settings(the_repository);
1209 if (!the_repository->settings.core_multi_pack_index)
1210 return 0;
1212 git_config_get_int("maintenance.incremental-repack.auto",
1213 &incremental_repack_auto_limit);
1215 if (!incremental_repack_auto_limit)
1216 return 0;
1217 if (incremental_repack_auto_limit < 0)
1218 return 1;
1220 for (p = get_packed_git(the_repository);
1221 count < incremental_repack_auto_limit && p;
1222 p = p->next) {
1223 if (!p->multi_pack_index)
1224 count++;
1227 return count >= incremental_repack_auto_limit;
1230 static int multi_pack_index_write(struct maintenance_run_opts *opts)
1232 struct child_process child = CHILD_PROCESS_INIT;
1234 child.git_cmd = 1;
1235 strvec_pushl(&child.args, "multi-pack-index", "write", NULL);
1237 if (opts->quiet)
1238 strvec_push(&child.args, "--no-progress");
1240 if (run_command(&child))
1241 return error(_("failed to write multi-pack-index"));
1243 return 0;
1246 static int multi_pack_index_expire(struct maintenance_run_opts *opts)
1248 struct child_process child = CHILD_PROCESS_INIT;
1250 child.git_cmd = child.close_object_store = 1;
1251 strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
1253 if (opts->quiet)
1254 strvec_push(&child.args, "--no-progress");
1256 if (run_command(&child))
1257 return error(_("'git multi-pack-index expire' failed"));
1259 return 0;
1262 #define TWO_GIGABYTES (INT32_MAX)
1264 static off_t get_auto_pack_size(void)
1267 * The "auto" value is special: we optimize for
1268 * one large pack-file (i.e. from a clone) and
1269 * expect the rest to be small and they can be
1270 * repacked quickly.
1272 * The strategy we select here is to select a
1273 * size that is one more than the second largest
1274 * pack-file. This ensures that we will repack
1275 * at least two packs if there are three or more
1276 * packs.
1278 off_t max_size = 0;
1279 off_t second_largest_size = 0;
1280 off_t result_size;
1281 struct packed_git *p;
1282 struct repository *r = the_repository;
1284 reprepare_packed_git(r);
1285 for (p = get_all_packs(r); p; p = p->next) {
1286 if (p->pack_size > max_size) {
1287 second_largest_size = max_size;
1288 max_size = p->pack_size;
1289 } else if (p->pack_size > second_largest_size)
1290 second_largest_size = p->pack_size;
1293 result_size = second_largest_size + 1;
1295 /* But limit ourselves to a batch size of 2g */
1296 if (result_size > TWO_GIGABYTES)
1297 result_size = TWO_GIGABYTES;
1299 return result_size;
1302 static int multi_pack_index_repack(struct maintenance_run_opts *opts)
1304 struct child_process child = CHILD_PROCESS_INIT;
1306 child.git_cmd = child.close_object_store = 1;
1307 strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
1309 if (opts->quiet)
1310 strvec_push(&child.args, "--no-progress");
1312 strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
1313 (uintmax_t)get_auto_pack_size());
1315 if (run_command(&child))
1316 return error(_("'git multi-pack-index repack' failed"));
1318 return 0;
1321 static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts,
1322 struct gc_config *cfg UNUSED)
1324 prepare_repo_settings(the_repository);
1325 if (!the_repository->settings.core_multi_pack_index) {
1326 warning(_("skipping incremental-repack task because core.multiPackIndex is disabled"));
1327 return 0;
1330 if (multi_pack_index_write(opts))
1331 return 1;
1332 if (multi_pack_index_expire(opts))
1333 return 1;
1334 if (multi_pack_index_repack(opts))
1335 return 1;
1336 return 0;
1339 typedef int maintenance_task_fn(struct maintenance_run_opts *opts,
1340 struct gc_config *cfg);
1343 * An auto condition function returns 1 if the task should run
1344 * and 0 if the task should NOT run. See needs_to_gc() for an
1345 * example.
1347 typedef int maintenance_auto_fn(struct gc_config *cfg);
1349 struct maintenance_task {
1350 const char *name;
1351 maintenance_task_fn *fn;
1352 maintenance_auto_fn *auto_condition;
1353 unsigned enabled:1;
1355 enum schedule_priority schedule;
1357 /* -1 if not selected. */
1358 int selected_order;
1361 enum maintenance_task_label {
1362 TASK_PREFETCH,
1363 TASK_LOOSE_OBJECTS,
1364 TASK_INCREMENTAL_REPACK,
1365 TASK_GC,
1366 TASK_COMMIT_GRAPH,
1367 TASK_PACK_REFS,
1369 /* Leave as final value */
1370 TASK__COUNT
1373 static struct maintenance_task tasks[] = {
1374 [TASK_PREFETCH] = {
1375 "prefetch",
1376 maintenance_task_prefetch,
1378 [TASK_LOOSE_OBJECTS] = {
1379 "loose-objects",
1380 maintenance_task_loose_objects,
1381 loose_object_auto_condition,
1383 [TASK_INCREMENTAL_REPACK] = {
1384 "incremental-repack",
1385 maintenance_task_incremental_repack,
1386 incremental_repack_auto_condition,
1388 [TASK_GC] = {
1389 "gc",
1390 maintenance_task_gc,
1391 need_to_gc,
1394 [TASK_COMMIT_GRAPH] = {
1395 "commit-graph",
1396 maintenance_task_commit_graph,
1397 should_write_commit_graph,
1399 [TASK_PACK_REFS] = {
1400 "pack-refs",
1401 maintenance_task_pack_refs,
1402 pack_refs_condition,
1406 static int compare_tasks_by_selection(const void *a_, const void *b_)
1408 const struct maintenance_task *a = a_;
1409 const struct maintenance_task *b = b_;
1411 return b->selected_order - a->selected_order;
1414 static int maintenance_run_tasks(struct maintenance_run_opts *opts,
1415 struct gc_config *cfg)
1417 int i, found_selected = 0;
1418 int result = 0;
1419 struct lock_file lk;
1420 struct repository *r = the_repository;
1421 char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
1423 if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
1425 * Another maintenance command is running.
1427 * If --auto was provided, then it is likely due to a
1428 * recursive process stack. Do not report an error in
1429 * that case.
1431 if (!opts->auto_flag && !opts->quiet)
1432 warning(_("lock file '%s' exists, skipping maintenance"),
1433 lock_path);
1434 free(lock_path);
1435 return 0;
1437 free(lock_path);
1439 /* Failure to daemonize is ok, we'll continue in foreground. */
1440 if (opts->detach > 0) {
1441 trace2_region_enter("maintenance", "detach", the_repository);
1442 daemonize();
1443 trace2_region_leave("maintenance", "detach", the_repository);
1446 for (i = 0; !found_selected && i < TASK__COUNT; i++)
1447 found_selected = tasks[i].selected_order >= 0;
1449 if (found_selected)
1450 QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);
1452 for (i = 0; i < TASK__COUNT; i++) {
1453 if (found_selected && tasks[i].selected_order < 0)
1454 continue;
1456 if (!found_selected && !tasks[i].enabled)
1457 continue;
1459 if (opts->auto_flag &&
1460 (!tasks[i].auto_condition ||
1461 !tasks[i].auto_condition(cfg)))
1462 continue;
1464 if (opts->schedule && tasks[i].schedule < opts->schedule)
1465 continue;
1467 trace2_region_enter("maintenance", tasks[i].name, r);
1468 if (tasks[i].fn(opts, cfg)) {
1469 error(_("task '%s' failed"), tasks[i].name);
1470 result = 1;
1472 trace2_region_leave("maintenance", tasks[i].name, r);
1475 rollback_lock_file(&lk);
1476 return result;
1479 static void initialize_maintenance_strategy(void)
1481 const char *config_str;
1483 if (git_config_get_string_tmp("maintenance.strategy", &config_str))
1484 return;
1486 if (!strcasecmp(config_str, "incremental")) {
1487 tasks[TASK_GC].schedule = SCHEDULE_NONE;
1488 tasks[TASK_COMMIT_GRAPH].enabled = 1;
1489 tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY;
1490 tasks[TASK_PREFETCH].enabled = 1;
1491 tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY;
1492 tasks[TASK_INCREMENTAL_REPACK].enabled = 1;
1493 tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY;
1494 tasks[TASK_LOOSE_OBJECTS].enabled = 1;
1495 tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY;
1496 tasks[TASK_PACK_REFS].enabled = 1;
1497 tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY;
1501 static void initialize_task_config(int schedule)
1503 int i;
1504 struct strbuf config_name = STRBUF_INIT;
1506 if (schedule)
1507 initialize_maintenance_strategy();
1509 for (i = 0; i < TASK__COUNT; i++) {
1510 int config_value;
1511 char *config_str;
1513 strbuf_reset(&config_name);
1514 strbuf_addf(&config_name, "maintenance.%s.enabled",
1515 tasks[i].name);
1517 if (!git_config_get_bool(config_name.buf, &config_value))
1518 tasks[i].enabled = config_value;
1520 strbuf_reset(&config_name);
1521 strbuf_addf(&config_name, "maintenance.%s.schedule",
1522 tasks[i].name);
1524 if (!git_config_get_string(config_name.buf, &config_str)) {
1525 tasks[i].schedule = parse_schedule(config_str);
1526 free(config_str);
1530 strbuf_release(&config_name);
1533 static int task_option_parse(const struct option *opt UNUSED,
1534 const char *arg, int unset)
1536 int i, num_selected = 0;
1537 struct maintenance_task *task = NULL;
1539 BUG_ON_OPT_NEG(unset);
1541 for (i = 0; i < TASK__COUNT; i++) {
1542 if (tasks[i].selected_order >= 0)
1543 num_selected++;
1544 if (!strcasecmp(tasks[i].name, arg)) {
1545 task = &tasks[i];
1549 if (!task) {
1550 error(_("'%s' is not a valid task"), arg);
1551 return 1;
1554 if (task->selected_order >= 0) {
1555 error(_("task '%s' cannot be selected multiple times"), arg);
1556 return 1;
1559 task->selected_order = num_selected + 1;
1561 return 0;
1564 static int maintenance_run(int argc, const char **argv, const char *prefix)
1566 int i;
1567 struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
1568 struct gc_config cfg = GC_CONFIG_INIT;
1569 struct option builtin_maintenance_run_options[] = {
1570 OPT_BOOL(0, "auto", &opts.auto_flag,
1571 N_("run tasks based on the state of the repository")),
1572 OPT_BOOL(0, "detach", &opts.detach,
1573 N_("perform maintenance in the background")),
1574 OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"),
1575 N_("run tasks based on frequency"),
1576 maintenance_opt_schedule),
1577 OPT_BOOL(0, "quiet", &opts.quiet,
1578 N_("do not report progress or other information over stderr")),
1579 OPT_CALLBACK_F(0, "task", NULL, N_("task"),
1580 N_("run a specific task"),
1581 PARSE_OPT_NONEG, task_option_parse),
1582 OPT_END()
1584 int ret;
1586 opts.quiet = !isatty(2);
1588 for (i = 0; i < TASK__COUNT; i++)
1589 tasks[i].selected_order = -1;
1591 argc = parse_options(argc, argv, prefix,
1592 builtin_maintenance_run_options,
1593 builtin_maintenance_run_usage,
1594 PARSE_OPT_STOP_AT_NON_OPTION);
1596 if (opts.auto_flag && opts.schedule)
1597 die(_("use at most one of --auto and --schedule=<frequency>"));
1599 gc_config(&cfg);
1600 initialize_task_config(opts.schedule);
1602 if (argc != 0)
1603 usage_with_options(builtin_maintenance_run_usage,
1604 builtin_maintenance_run_options);
1606 ret = maintenance_run_tasks(&opts, &cfg);
1607 gc_config_release(&cfg);
1608 return ret;
1611 static char *get_maintpath(void)
1613 struct strbuf sb = STRBUF_INIT;
1614 const char *p = the_repository->worktree ?
1615 the_repository->worktree : the_repository->gitdir;
1617 strbuf_realpath(&sb, p, 1);
1618 return strbuf_detach(&sb, NULL);
1621 static char const * const builtin_maintenance_register_usage[] = {
1622 "git maintenance register [--config-file <path>]",
1623 NULL
1626 static int maintenance_register(int argc, const char **argv, const char *prefix)
1628 char *config_file = NULL;
1629 struct option options[] = {
1630 OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1631 OPT_END(),
1633 int found = 0;
1634 const char *key = "maintenance.repo";
1635 char *maintpath = get_maintpath();
1636 struct string_list_item *item;
1637 const struct string_list *list;
1639 argc = parse_options(argc, argv, prefix, options,
1640 builtin_maintenance_register_usage, 0);
1641 if (argc)
1642 usage_with_options(builtin_maintenance_register_usage,
1643 options);
1645 /* Disable foreground maintenance */
1646 git_config_set("maintenance.auto", "false");
1648 /* Set maintenance strategy, if unset */
1649 if (git_config_get("maintenance.strategy"))
1650 git_config_set("maintenance.strategy", "incremental");
1652 if (!git_config_get_string_multi(key, &list)) {
1653 for_each_string_list_item(item, list) {
1654 if (!strcmp(maintpath, item->string)) {
1655 found = 1;
1656 break;
1661 if (!found) {
1662 int rc;
1663 char *global_config_file = NULL;
1665 if (!config_file) {
1666 global_config_file = git_global_config();
1667 config_file = global_config_file;
1669 if (!config_file)
1670 die(_("$HOME not set"));
1671 rc = git_config_set_multivar_in_file_gently(
1672 config_file, "maintenance.repo", maintpath,
1673 CONFIG_REGEX_NONE, NULL, 0);
1674 free(global_config_file);
1676 if (rc)
1677 die(_("unable to add '%s' value of '%s'"),
1678 key, maintpath);
1681 free(maintpath);
1682 return 0;
1685 static char const * const builtin_maintenance_unregister_usage[] = {
1686 "git maintenance unregister [--config-file <path>] [--force]",
1687 NULL
1690 static int maintenance_unregister(int argc, const char **argv, const char *prefix)
1692 int force = 0;
1693 char *config_file = NULL;
1694 struct option options[] = {
1695 OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1696 OPT__FORCE(&force,
1697 N_("return success even if repository was not registered"),
1698 PARSE_OPT_NOCOMPLETE),
1699 OPT_END(),
1701 const char *key = "maintenance.repo";
1702 char *maintpath = get_maintpath();
1703 int found = 0;
1704 struct string_list_item *item;
1705 const struct string_list *list;
1706 struct config_set cs = { { 0 } };
1708 argc = parse_options(argc, argv, prefix, options,
1709 builtin_maintenance_unregister_usage, 0);
1710 if (argc)
1711 usage_with_options(builtin_maintenance_unregister_usage,
1712 options);
1714 if (config_file) {
1715 git_configset_init(&cs);
1716 git_configset_add_file(&cs, config_file);
1718 if (!(config_file
1719 ? git_configset_get_string_multi(&cs, key, &list)
1720 : git_config_get_string_multi(key, &list))) {
1721 for_each_string_list_item(item, list) {
1722 if (!strcmp(maintpath, item->string)) {
1723 found = 1;
1724 break;
1729 if (found) {
1730 int rc;
1731 char *global_config_file = NULL;
1733 if (!config_file) {
1734 global_config_file = git_global_config();
1735 config_file = global_config_file;
1737 if (!config_file)
1738 die(_("$HOME not set"));
1739 rc = git_config_set_multivar_in_file_gently(
1740 config_file, key, NULL, maintpath, NULL,
1741 CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
1742 free(global_config_file);
1744 if (rc &&
1745 (!force || rc == CONFIG_NOTHING_SET))
1746 die(_("unable to unset '%s' value of '%s'"),
1747 key, maintpath);
1748 } else if (!force) {
1749 die(_("repository '%s' is not registered"), maintpath);
1752 git_configset_clear(&cs);
1753 free(maintpath);
1754 return 0;
1757 static const char *get_frequency(enum schedule_priority schedule)
1759 switch (schedule) {
1760 case SCHEDULE_HOURLY:
1761 return "hourly";
1762 case SCHEDULE_DAILY:
1763 return "daily";
1764 case SCHEDULE_WEEKLY:
1765 return "weekly";
1766 default:
1767 BUG("invalid schedule %d", schedule);
1771 static const char *extraconfig[] = {
1772 "credential.interactive=false",
1773 "core.askPass=true", /* 'true' returns success, but no output. */
1774 NULL
1777 static const char *get_extra_config_parameters(void) {
1778 static const char *result = NULL;
1779 struct strbuf builder = STRBUF_INIT;
1781 if (result)
1782 return result;
1784 for (const char **s = extraconfig; s && *s; s++)
1785 strbuf_addf(&builder, "-c %s ", *s);
1787 result = strbuf_detach(&builder, NULL);
1788 return result;
1791 static const char *get_extra_launchctl_strings(void) {
1792 static const char *result = NULL;
1793 struct strbuf builder = STRBUF_INIT;
1795 if (result)
1796 return result;
1798 for (const char **s = extraconfig; s && *s; s++) {
1799 strbuf_addstr(&builder, "<string>-c</string>\n");
1800 strbuf_addf(&builder, "<string>%s</string>\n", *s);
1803 result = strbuf_detach(&builder, NULL);
1804 return result;
1808 * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
1809 * to mock the schedulers that `git maintenance start` rely on.
1811 * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
1812 * list of colon-separated key/value pairs where each pair contains a scheduler
1813 * and its corresponding mock.
1815 * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
1816 * arguments unmodified.
1818 * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
1819 * In this case, the *cmd value is read as input.
1821 * * if the input value cmd is the key of one of the comma-separated list
1822 * item, then *is_available is set to true and *out is set to
1823 * the mock command.
1825 * * if the input value *cmd isn’t the key of any of the comma-separated list
1826 * item, then *is_available is set to false and *out is set to the original
1827 * command.
1829 * Ex.:
1830 * GIT_TEST_MAINT_SCHEDULER not set
1831 * +-------+-------------------------------------------------+
1832 * | Input | Output |
1833 * | *cmd | return code | *out | *is_available |
1834 * +-------+-------------+-------------------+---------------+
1835 * | "foo" | false | "foo" (allocated) | (unchanged) |
1836 * +-------+-------------+-------------------+---------------+
1838 * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
1839 * +-------+-------------------------------------------------+
1840 * | Input | Output |
1841 * | *cmd | return code | *out | *is_available |
1842 * +-------+-------------+-------------------+---------------+
1843 * | "foo" | true | "./mock.foo.sh" | true |
1844 * | "qux" | true | "qux" (allocated) | false |
1845 * +-------+-------------+-------------------+---------------+
1847 static int get_schedule_cmd(const char *cmd, int *is_available, char **out)
1849 char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
1850 struct string_list_item *item;
1851 struct string_list list = STRING_LIST_INIT_NODUP;
1853 if (!testing) {
1854 if (out)
1855 *out = xstrdup(cmd);
1856 return 0;
1859 if (is_available)
1860 *is_available = 0;
1862 string_list_split_in_place(&list, testing, ",", -1);
1863 for_each_string_list_item(item, &list) {
1864 struct string_list pair = STRING_LIST_INIT_NODUP;
1866 if (string_list_split_in_place(&pair, item->string, ":", 2) != 2)
1867 continue;
1869 if (!strcmp(cmd, pair.items[0].string)) {
1870 if (out)
1871 *out = xstrdup(pair.items[1].string);
1872 if (is_available)
1873 *is_available = 1;
1874 string_list_clear(&pair, 0);
1875 goto out;
1878 string_list_clear(&pair, 0);
1881 if (out)
1882 *out = xstrdup(cmd);
1884 out:
1885 string_list_clear(&list, 0);
1886 free(testing);
1887 return 1;
1890 static int get_random_minute(void)
1892 /* Use a static value when under tests. */
1893 if (getenv("GIT_TEST_MAINT_SCHEDULER"))
1894 return 13;
1896 return git_rand() % 60;
1899 static int is_launchctl_available(void)
1901 int is_available;
1902 if (get_schedule_cmd("launchctl", &is_available, NULL))
1903 return is_available;
1905 #ifdef __APPLE__
1906 return 1;
1907 #else
1908 return 0;
1909 #endif
1912 static char *launchctl_service_name(const char *frequency)
1914 struct strbuf label = STRBUF_INIT;
1915 strbuf_addf(&label, "org.git-scm.git.%s", frequency);
1916 return strbuf_detach(&label, NULL);
1919 static char *launchctl_service_filename(const char *name)
1921 char *expanded;
1922 struct strbuf filename = STRBUF_INIT;
1923 strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
1925 expanded = interpolate_path(filename.buf, 1);
1926 if (!expanded)
1927 die(_("failed to expand path '%s'"), filename.buf);
1929 strbuf_release(&filename);
1930 return expanded;
1933 static char *launchctl_get_uid(void)
1935 return xstrfmt("gui/%d", getuid());
1938 static int launchctl_boot_plist(int enable, const char *filename)
1940 char *cmd;
1941 int result;
1942 struct child_process child = CHILD_PROCESS_INIT;
1943 char *uid = launchctl_get_uid();
1945 get_schedule_cmd("launchctl", NULL, &cmd);
1946 strvec_split(&child.args, cmd);
1947 strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
1948 filename, NULL);
1950 child.no_stderr = 1;
1951 child.no_stdout = 1;
1953 if (start_command(&child))
1954 die(_("failed to start launchctl"));
1956 result = finish_command(&child);
1958 free(cmd);
1959 free(uid);
1960 return result;
1963 static int launchctl_remove_plist(enum schedule_priority schedule)
1965 const char *frequency = get_frequency(schedule);
1966 char *name = launchctl_service_name(frequency);
1967 char *filename = launchctl_service_filename(name);
1968 int result = launchctl_boot_plist(0, filename);
1969 unlink(filename);
1970 free(filename);
1971 free(name);
1972 return result;
1975 static int launchctl_remove_plists(void)
1977 return launchctl_remove_plist(SCHEDULE_HOURLY) ||
1978 launchctl_remove_plist(SCHEDULE_DAILY) ||
1979 launchctl_remove_plist(SCHEDULE_WEEKLY);
1982 static int launchctl_list_contains_plist(const char *name, const char *cmd)
1984 struct child_process child = CHILD_PROCESS_INIT;
1986 strvec_split(&child.args, cmd);
1987 strvec_pushl(&child.args, "list", name, NULL);
1989 child.no_stderr = 1;
1990 child.no_stdout = 1;
1992 if (start_command(&child))
1993 die(_("failed to start launchctl"));
1995 /* Returns failure if 'name' doesn't exist. */
1996 return !finish_command(&child);
1999 static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
2001 int i, fd;
2002 const char *preamble, *repeat;
2003 const char *frequency = get_frequency(schedule);
2004 char *name = launchctl_service_name(frequency);
2005 char *filename = launchctl_service_filename(name);
2006 struct lock_file lk = LOCK_INIT;
2007 static unsigned long lock_file_timeout_ms = ULONG_MAX;
2008 struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
2009 struct stat st;
2010 char *cmd;
2011 int minute = get_random_minute();
2013 get_schedule_cmd("launchctl", NULL, &cmd);
2014 preamble = "<?xml version=\"1.0\"?>\n"
2015 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
2016 "<plist version=\"1.0\">"
2017 "<dict>\n"
2018 "<key>Label</key><string>%s</string>\n"
2019 "<key>ProgramArguments</key>\n"
2020 "<array>\n"
2021 "<string>%s/git</string>\n"
2022 "<string>--exec-path=%s</string>\n"
2023 "%s" /* For extra config parameters. */
2024 "<string>for-each-repo</string>\n"
2025 "<string>--keep-going</string>\n"
2026 "<string>--config=maintenance.repo</string>\n"
2027 "<string>maintenance</string>\n"
2028 "<string>run</string>\n"
2029 "<string>--schedule=%s</string>\n"
2030 "</array>\n"
2031 "<key>StartCalendarInterval</key>\n"
2032 "<array>\n";
2033 strbuf_addf(&plist, preamble, name, exec_path, exec_path,
2034 get_extra_launchctl_strings(), frequency);
2036 switch (schedule) {
2037 case SCHEDULE_HOURLY:
2038 repeat = "<dict>\n"
2039 "<key>Hour</key><integer>%d</integer>\n"
2040 "<key>Minute</key><integer>%d</integer>\n"
2041 "</dict>\n";
2042 for (i = 1; i <= 23; i++)
2043 strbuf_addf(&plist, repeat, i, minute);
2044 break;
2046 case SCHEDULE_DAILY:
2047 repeat = "<dict>\n"
2048 "<key>Day</key><integer>%d</integer>\n"
2049 "<key>Hour</key><integer>0</integer>\n"
2050 "<key>Minute</key><integer>%d</integer>\n"
2051 "</dict>\n";
2052 for (i = 1; i <= 6; i++)
2053 strbuf_addf(&plist, repeat, i, minute);
2054 break;
2056 case SCHEDULE_WEEKLY:
2057 strbuf_addf(&plist,
2058 "<dict>\n"
2059 "<key>Day</key><integer>0</integer>\n"
2060 "<key>Hour</key><integer>0</integer>\n"
2061 "<key>Minute</key><integer>%d</integer>\n"
2062 "</dict>\n",
2063 minute);
2064 break;
2066 default:
2067 /* unreachable */
2068 break;
2070 strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
2072 if (safe_create_leading_directories(filename))
2073 die(_("failed to create directories for '%s'"), filename);
2075 if ((long)lock_file_timeout_ms < 0 &&
2076 git_config_get_ulong("gc.launchctlplistlocktimeoutms",
2077 &lock_file_timeout_ms))
2078 lock_file_timeout_ms = 150;
2080 fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
2081 lock_file_timeout_ms);
2084 * Does this file already exist? With the intended contents? Is it
2085 * registered already? Then it does not need to be re-registered.
2087 if (!stat(filename, &st) && st.st_size == plist.len &&
2088 strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
2089 !strbuf_cmp(&plist, &plist2) &&
2090 launchctl_list_contains_plist(name, cmd))
2091 rollback_lock_file(&lk);
2092 else {
2093 if (write_in_full(fd, plist.buf, plist.len) < 0 ||
2094 commit_lock_file(&lk))
2095 die_errno(_("could not write '%s'"), filename);
2097 /* bootout might fail if not already running, so ignore */
2098 launchctl_boot_plist(0, filename);
2099 if (launchctl_boot_plist(1, filename))
2100 die(_("failed to bootstrap service %s"), filename);
2103 free(filename);
2104 free(name);
2105 free(cmd);
2106 strbuf_release(&plist);
2107 strbuf_release(&plist2);
2108 return 0;
2111 static int launchctl_add_plists(void)
2113 const char *exec_path = git_exec_path();
2115 return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
2116 launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
2117 launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
2120 static int launchctl_update_schedule(int run_maintenance, int fd UNUSED)
2122 if (run_maintenance)
2123 return launchctl_add_plists();
2124 else
2125 return launchctl_remove_plists();
2128 static int is_schtasks_available(void)
2130 int is_available;
2131 if (get_schedule_cmd("schtasks", &is_available, NULL))
2132 return is_available;
2134 #ifdef GIT_WINDOWS_NATIVE
2135 return 1;
2136 #else
2137 return 0;
2138 #endif
2141 static char *schtasks_task_name(const char *frequency)
2143 struct strbuf label = STRBUF_INIT;
2144 strbuf_addf(&label, "Git Maintenance (%s)", frequency);
2145 return strbuf_detach(&label, NULL);
2148 static int schtasks_remove_task(enum schedule_priority schedule)
2150 char *cmd;
2151 struct child_process child = CHILD_PROCESS_INIT;
2152 const char *frequency = get_frequency(schedule);
2153 char *name = schtasks_task_name(frequency);
2155 get_schedule_cmd("schtasks", NULL, &cmd);
2156 strvec_split(&child.args, cmd);
2157 strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
2158 free(name);
2159 free(cmd);
2161 return run_command(&child);
2164 static int schtasks_remove_tasks(void)
2166 return schtasks_remove_task(SCHEDULE_HOURLY) ||
2167 schtasks_remove_task(SCHEDULE_DAILY) ||
2168 schtasks_remove_task(SCHEDULE_WEEKLY);
2171 static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
2173 char *cmd;
2174 int result;
2175 struct child_process child = CHILD_PROCESS_INIT;
2176 const char *xml;
2177 struct tempfile *tfile;
2178 const char *frequency = get_frequency(schedule);
2179 char *name = schtasks_task_name(frequency);
2180 struct strbuf tfilename = STRBUF_INIT;
2181 int minute = get_random_minute();
2183 get_schedule_cmd("schtasks", NULL, &cmd);
2185 strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
2186 repo_get_common_dir(the_repository), frequency);
2187 tfile = xmks_tempfile(tfilename.buf);
2188 strbuf_release(&tfilename);
2190 if (!fdopen_tempfile(tfile, "w"))
2191 die(_("failed to create temp xml file"));
2193 xml = "<?xml version=\"1.0\" ?>\n"
2194 "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
2195 "<Triggers>\n"
2196 "<CalendarTrigger>\n";
2197 fputs(xml, tfile->fp);
2199 switch (schedule) {
2200 case SCHEDULE_HOURLY:
2201 fprintf(tfile->fp,
2202 "<StartBoundary>2020-01-01T01:%02d:00</StartBoundary>\n"
2203 "<Enabled>true</Enabled>\n"
2204 "<ScheduleByDay>\n"
2205 "<DaysInterval>1</DaysInterval>\n"
2206 "</ScheduleByDay>\n"
2207 "<Repetition>\n"
2208 "<Interval>PT1H</Interval>\n"
2209 "<Duration>PT23H</Duration>\n"
2210 "<StopAtDurationEnd>false</StopAtDurationEnd>\n"
2211 "</Repetition>\n",
2212 minute);
2213 break;
2215 case SCHEDULE_DAILY:
2216 fprintf(tfile->fp,
2217 "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2218 "<Enabled>true</Enabled>\n"
2219 "<ScheduleByWeek>\n"
2220 "<DaysOfWeek>\n"
2221 "<Monday />\n"
2222 "<Tuesday />\n"
2223 "<Wednesday />\n"
2224 "<Thursday />\n"
2225 "<Friday />\n"
2226 "<Saturday />\n"
2227 "</DaysOfWeek>\n"
2228 "<WeeksInterval>1</WeeksInterval>\n"
2229 "</ScheduleByWeek>\n",
2230 minute);
2231 break;
2233 case SCHEDULE_WEEKLY:
2234 fprintf(tfile->fp,
2235 "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2236 "<Enabled>true</Enabled>\n"
2237 "<ScheduleByWeek>\n"
2238 "<DaysOfWeek>\n"
2239 "<Sunday />\n"
2240 "</DaysOfWeek>\n"
2241 "<WeeksInterval>1</WeeksInterval>\n"
2242 "</ScheduleByWeek>\n",
2243 minute);
2244 break;
2246 default:
2247 break;
2250 xml = "</CalendarTrigger>\n"
2251 "</Triggers>\n"
2252 "<Principals>\n"
2253 "<Principal id=\"Author\">\n"
2254 "<LogonType>InteractiveToken</LogonType>\n"
2255 "<RunLevel>LeastPrivilege</RunLevel>\n"
2256 "</Principal>\n"
2257 "</Principals>\n"
2258 "<Settings>\n"
2259 "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
2260 "<Enabled>true</Enabled>\n"
2261 "<Hidden>true</Hidden>\n"
2262 "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n"
2263 "<WakeToRun>false</WakeToRun>\n"
2264 "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
2265 "<Priority>7</Priority>\n"
2266 "</Settings>\n"
2267 "<Actions Context=\"Author\">\n"
2268 "<Exec>\n"
2269 "<Command>\"%s\\headless-git.exe\"</Command>\n"
2270 "<Arguments>--exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
2271 "</Exec>\n"
2272 "</Actions>\n"
2273 "</Task>\n";
2274 fprintf(tfile->fp, xml, exec_path, exec_path,
2275 get_extra_config_parameters(), frequency);
2276 strvec_split(&child.args, cmd);
2277 strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
2278 get_tempfile_path(tfile), NULL);
2279 close_tempfile_gently(tfile);
2281 child.no_stdout = 1;
2282 child.no_stderr = 1;
2284 if (start_command(&child))
2285 die(_("failed to start schtasks"));
2286 result = finish_command(&child);
2288 delete_tempfile(&tfile);
2289 free(name);
2290 free(cmd);
2291 return result;
2294 static int schtasks_schedule_tasks(void)
2296 const char *exec_path = git_exec_path();
2298 return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
2299 schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
2300 schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
2303 static int schtasks_update_schedule(int run_maintenance, int fd UNUSED)
2305 if (run_maintenance)
2306 return schtasks_schedule_tasks();
2307 else
2308 return schtasks_remove_tasks();
2311 MAYBE_UNUSED
2312 static int check_crontab_process(const char *cmd)
2314 struct child_process child = CHILD_PROCESS_INIT;
2316 strvec_split(&child.args, cmd);
2317 strvec_push(&child.args, "-l");
2318 child.no_stdin = 1;
2319 child.no_stdout = 1;
2320 child.no_stderr = 1;
2321 child.silent_exec_failure = 1;
2323 if (start_command(&child))
2324 return 0;
2325 /* Ignore exit code, as an empty crontab will return error. */
2326 finish_command(&child);
2327 return 1;
2330 static int is_crontab_available(void)
2332 char *cmd;
2333 int is_available;
2334 int ret;
2336 if (get_schedule_cmd("crontab", &is_available, &cmd)) {
2337 ret = is_available;
2338 goto out;
2341 #ifdef __APPLE__
2343 * macOS has cron, but it requires special permissions and will
2344 * create a UI alert when attempting to run this command.
2346 ret = 0;
2347 #else
2348 ret = check_crontab_process(cmd);
2349 #endif
2351 out:
2352 free(cmd);
2353 return ret;
2356 #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
2357 #define END_LINE "# END GIT MAINTENANCE SCHEDULE"
2359 static int crontab_update_schedule(int run_maintenance, int fd)
2361 char *cmd;
2362 int result = 0;
2363 int in_old_region = 0;
2364 struct child_process crontab_list = CHILD_PROCESS_INIT;
2365 struct child_process crontab_edit = CHILD_PROCESS_INIT;
2366 FILE *cron_list, *cron_in;
2367 struct strbuf line = STRBUF_INIT;
2368 struct tempfile *tmpedit = NULL;
2369 int minute = get_random_minute();
2371 get_schedule_cmd("crontab", NULL, &cmd);
2372 strvec_split(&crontab_list.args, cmd);
2373 strvec_push(&crontab_list.args, "-l");
2374 crontab_list.in = -1;
2375 crontab_list.out = dup(fd);
2376 crontab_list.git_cmd = 0;
2378 if (start_command(&crontab_list)) {
2379 result = error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
2380 goto out;
2383 /* Ignore exit code, as an empty crontab will return error. */
2384 finish_command(&crontab_list);
2386 tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX");
2387 if (!tmpedit) {
2388 result = error(_("failed to create crontab temporary file"));
2389 goto out;
2391 cron_in = fdopen_tempfile(tmpedit, "w");
2392 if (!cron_in) {
2393 result = error(_("failed to open temporary file"));
2394 goto out;
2398 * Read from the .lock file, filtering out the old
2399 * schedule while appending the new schedule.
2401 cron_list = fdopen(fd, "r");
2402 rewind(cron_list);
2404 while (!strbuf_getline_lf(&line, cron_list)) {
2405 if (!in_old_region && !strcmp(line.buf, BEGIN_LINE))
2406 in_old_region = 1;
2407 else if (in_old_region && !strcmp(line.buf, END_LINE))
2408 in_old_region = 0;
2409 else if (!in_old_region)
2410 fprintf(cron_in, "%s\n", line.buf);
2412 strbuf_release(&line);
2414 if (run_maintenance) {
2415 struct strbuf line_format = STRBUF_INIT;
2416 const char *exec_path = git_exec_path();
2418 fprintf(cron_in, "%s\n", BEGIN_LINE);
2419 fprintf(cron_in,
2420 "# The following schedule was created by Git\n");
2421 fprintf(cron_in, "# Any edits made in this region might be\n");
2422 fprintf(cron_in,
2423 "# replaced in the future by a Git command.\n\n");
2425 strbuf_addf(&line_format,
2426 "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
2427 exec_path, exec_path, get_extra_config_parameters());
2428 fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
2429 fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
2430 fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
2431 strbuf_release(&line_format);
2433 fprintf(cron_in, "\n%s\n", END_LINE);
2436 fflush(cron_in);
2438 strvec_split(&crontab_edit.args, cmd);
2439 strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit));
2440 crontab_edit.git_cmd = 0;
2442 if (start_command(&crontab_edit)) {
2443 result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
2444 goto out;
2447 if (finish_command(&crontab_edit))
2448 result = error(_("'crontab' died"));
2449 else
2450 fclose(cron_list);
2452 out:
2453 delete_tempfile(&tmpedit);
2454 free(cmd);
2455 return result;
2458 static int real_is_systemd_timer_available(void)
2460 struct child_process child = CHILD_PROCESS_INIT;
2462 strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
2463 child.no_stdin = 1;
2464 child.no_stdout = 1;
2465 child.no_stderr = 1;
2466 child.silent_exec_failure = 1;
2468 if (start_command(&child))
2469 return 0;
2470 if (finish_command(&child))
2471 return 0;
2472 return 1;
2475 static int is_systemd_timer_available(void)
2477 int is_available;
2479 if (get_schedule_cmd("systemctl", &is_available, NULL))
2480 return is_available;
2482 return real_is_systemd_timer_available();
2485 static char *xdg_config_home_systemd(const char *filename)
2487 return xdg_config_home_for("systemd/user", filename);
2490 #define SYSTEMD_UNIT_FORMAT "git-maintenance@%s.%s"
2492 static int systemd_timer_delete_timer_file(enum schedule_priority priority)
2494 int ret = 0;
2495 const char *frequency = get_frequency(priority);
2496 char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2497 char *filename = xdg_config_home_systemd(local_timer_name);
2499 if (unlink(filename) && !is_missing_file_error(errno))
2500 ret = error_errno(_("failed to delete '%s'"), filename);
2502 free(filename);
2503 free(local_timer_name);
2504 return ret;
2507 static int systemd_timer_delete_service_template(void)
2509 int ret = 0;
2510 char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2511 char *filename = xdg_config_home_systemd(local_service_name);
2512 if (unlink(filename) && !is_missing_file_error(errno))
2513 ret = error_errno(_("failed to delete '%s'"), filename);
2515 free(filename);
2516 free(local_service_name);
2517 return ret;
2521 * Write the schedule information into a git-maintenance@<schedule>.timer
2522 * file using a custom minute. This timer file cannot use the templating
2523 * system, so we generate a specific file for each.
2525 static int systemd_timer_write_timer_file(enum schedule_priority schedule,
2526 int minute)
2528 int res = -1;
2529 char *filename;
2530 FILE *file;
2531 const char *unit;
2532 char *schedule_pattern = NULL;
2533 const char *frequency = get_frequency(schedule);
2534 char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2536 filename = xdg_config_home_systemd(local_timer_name);
2538 if (safe_create_leading_directories(filename)) {
2539 error(_("failed to create directories for '%s'"), filename);
2540 goto error;
2542 file = fopen_or_warn(filename, "w");
2543 if (!file)
2544 goto error;
2546 switch (schedule) {
2547 case SCHEDULE_HOURLY:
2548 schedule_pattern = xstrfmt("*-*-* 1..23:%02d:00", minute);
2549 break;
2551 case SCHEDULE_DAILY:
2552 schedule_pattern = xstrfmt("Tue..Sun *-*-* 0:%02d:00", minute);
2553 break;
2555 case SCHEDULE_WEEKLY:
2556 schedule_pattern = xstrfmt("Mon 0:%02d:00", minute);
2557 break;
2559 default:
2560 BUG("Unhandled schedule_priority");
2563 unit = "# This file was created and is maintained by Git.\n"
2564 "# Any edits made in this file might be replaced in the future\n"
2565 "# by a Git command.\n"
2566 "\n"
2567 "[Unit]\n"
2568 "Description=Optimize Git repositories data\n"
2569 "\n"
2570 "[Timer]\n"
2571 "OnCalendar=%s\n"
2572 "Persistent=true\n"
2573 "\n"
2574 "[Install]\n"
2575 "WantedBy=timers.target\n";
2576 if (fprintf(file, unit, schedule_pattern) < 0) {
2577 error(_("failed to write to '%s'"), filename);
2578 fclose(file);
2579 goto error;
2581 if (fclose(file) == EOF) {
2582 error_errno(_("failed to flush '%s'"), filename);
2583 goto error;
2586 res = 0;
2588 error:
2589 free(schedule_pattern);
2590 free(local_timer_name);
2591 free(filename);
2592 return res;
2596 * No matter the schedule, we use the same service and can make use of the
2597 * templating system. When installing git-maintenance@<schedule>.timer,
2598 * systemd will notice that git-maintenance@.service exists as a template
2599 * and will use this file and insert the <schedule> into the template at
2600 * the position of "%i".
2602 static int systemd_timer_write_service_template(const char *exec_path)
2604 int res = -1;
2605 char *filename;
2606 FILE *file;
2607 const char *unit;
2608 char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2610 filename = xdg_config_home_systemd(local_service_name);
2611 if (safe_create_leading_directories(filename)) {
2612 error(_("failed to create directories for '%s'"), filename);
2613 goto error;
2615 file = fopen_or_warn(filename, "w");
2616 if (!file)
2617 goto error;
2619 unit = "# This file was created and is maintained by Git.\n"
2620 "# Any edits made in this file might be replaced in the future\n"
2621 "# by a Git command.\n"
2622 "\n"
2623 "[Unit]\n"
2624 "Description=Optimize Git repositories data\n"
2625 "\n"
2626 "[Service]\n"
2627 "Type=oneshot\n"
2628 "ExecStart=\"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
2629 "LockPersonality=yes\n"
2630 "MemoryDenyWriteExecute=yes\n"
2631 "NoNewPrivileges=yes\n"
2632 "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_VSOCK\n"
2633 "RestrictNamespaces=yes\n"
2634 "RestrictRealtime=yes\n"
2635 "RestrictSUIDSGID=yes\n"
2636 "SystemCallArchitectures=native\n"
2637 "SystemCallFilter=@system-service\n";
2638 if (fprintf(file, unit, exec_path, exec_path, get_extra_config_parameters()) < 0) {
2639 error(_("failed to write to '%s'"), filename);
2640 fclose(file);
2641 goto error;
2643 if (fclose(file) == EOF) {
2644 error_errno(_("failed to flush '%s'"), filename);
2645 goto error;
2648 res = 0;
2650 error:
2651 free(local_service_name);
2652 free(filename);
2653 return res;
2656 static int systemd_timer_enable_unit(int enable,
2657 enum schedule_priority schedule,
2658 int minute)
2660 char *cmd = NULL;
2661 struct child_process child = CHILD_PROCESS_INIT;
2662 const char *frequency = get_frequency(schedule);
2663 int ret;
2666 * Disabling the systemd unit while it is already disabled makes
2667 * systemctl print an error.
2668 * Let's ignore it since it means we already are in the expected state:
2669 * the unit is disabled.
2671 * On the other hand, enabling a systemd unit which is already enabled
2672 * produces no error.
2674 if (!enable) {
2675 child.no_stderr = 1;
2676 } else if (systemd_timer_write_timer_file(schedule, minute)) {
2677 ret = -1;
2678 goto out;
2681 get_schedule_cmd("systemctl", NULL, &cmd);
2682 strvec_split(&child.args, cmd);
2683 strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
2684 "--now", NULL);
2685 strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer");
2687 if (start_command(&child)) {
2688 ret = error(_("failed to start systemctl"));
2689 goto out;
2692 if (finish_command(&child)) {
2694 * Disabling an already disabled systemd unit makes
2695 * systemctl fail.
2696 * Let's ignore this failure.
2698 * Enabling an enabled systemd unit doesn't fail.
2700 if (enable) {
2701 ret = error(_("failed to run systemctl"));
2702 goto out;
2706 ret = 0;
2708 out:
2709 free(cmd);
2710 return ret;
2714 * A previous version of Git wrote the timer units as template files.
2715 * Clean these up, if they exist.
2717 static void systemd_timer_delete_stale_timer_templates(void)
2719 char *timer_template_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "timer");
2720 char *filename = xdg_config_home_systemd(timer_template_name);
2722 if (unlink(filename) && !is_missing_file_error(errno))
2723 warning(_("failed to delete '%s'"), filename);
2725 free(filename);
2726 free(timer_template_name);
2729 static int systemd_timer_delete_unit_files(void)
2731 systemd_timer_delete_stale_timer_templates();
2733 /* Purposefully not short-circuited to make sure all are called. */
2734 return systemd_timer_delete_timer_file(SCHEDULE_HOURLY) |
2735 systemd_timer_delete_timer_file(SCHEDULE_DAILY) |
2736 systemd_timer_delete_timer_file(SCHEDULE_WEEKLY) |
2737 systemd_timer_delete_service_template();
2740 static int systemd_timer_delete_units(void)
2742 int minute = get_random_minute();
2743 /* Purposefully not short-circuited to make sure all are called. */
2744 return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, minute) |
2745 systemd_timer_enable_unit(0, SCHEDULE_DAILY, minute) |
2746 systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, minute) |
2747 systemd_timer_delete_unit_files();
2750 static int systemd_timer_setup_units(void)
2752 int minute = get_random_minute();
2753 const char *exec_path = git_exec_path();
2755 int ret = systemd_timer_write_service_template(exec_path) ||
2756 systemd_timer_enable_unit(1, SCHEDULE_HOURLY, minute) ||
2757 systemd_timer_enable_unit(1, SCHEDULE_DAILY, minute) ||
2758 systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, minute);
2760 if (ret)
2761 systemd_timer_delete_units();
2762 else
2763 systemd_timer_delete_stale_timer_templates();
2765 return ret;
2768 static int systemd_timer_update_schedule(int run_maintenance, int fd UNUSED)
2770 if (run_maintenance)
2771 return systemd_timer_setup_units();
2772 else
2773 return systemd_timer_delete_units();
2776 enum scheduler {
2777 SCHEDULER_INVALID = -1,
2778 SCHEDULER_AUTO,
2779 SCHEDULER_CRON,
2780 SCHEDULER_SYSTEMD,
2781 SCHEDULER_LAUNCHCTL,
2782 SCHEDULER_SCHTASKS,
2785 static const struct {
2786 const char *name;
2787 int (*is_available)(void);
2788 int (*update_schedule)(int run_maintenance, int fd);
2789 } scheduler_fn[] = {
2790 [SCHEDULER_CRON] = {
2791 .name = "crontab",
2792 .is_available = is_crontab_available,
2793 .update_schedule = crontab_update_schedule,
2795 [SCHEDULER_SYSTEMD] = {
2796 .name = "systemctl",
2797 .is_available = is_systemd_timer_available,
2798 .update_schedule = systemd_timer_update_schedule,
2800 [SCHEDULER_LAUNCHCTL] = {
2801 .name = "launchctl",
2802 .is_available = is_launchctl_available,
2803 .update_schedule = launchctl_update_schedule,
2805 [SCHEDULER_SCHTASKS] = {
2806 .name = "schtasks",
2807 .is_available = is_schtasks_available,
2808 .update_schedule = schtasks_update_schedule,
2812 static enum scheduler parse_scheduler(const char *value)
2814 if (!value)
2815 return SCHEDULER_INVALID;
2816 else if (!strcasecmp(value, "auto"))
2817 return SCHEDULER_AUTO;
2818 else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
2819 return SCHEDULER_CRON;
2820 else if (!strcasecmp(value, "systemd") ||
2821 !strcasecmp(value, "systemd-timer"))
2822 return SCHEDULER_SYSTEMD;
2823 else if (!strcasecmp(value, "launchctl"))
2824 return SCHEDULER_LAUNCHCTL;
2825 else if (!strcasecmp(value, "schtasks"))
2826 return SCHEDULER_SCHTASKS;
2827 else
2828 return SCHEDULER_INVALID;
2831 static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
2832 int unset)
2834 enum scheduler *scheduler = opt->value;
2836 BUG_ON_OPT_NEG(unset);
2838 *scheduler = parse_scheduler(arg);
2839 if (*scheduler == SCHEDULER_INVALID)
2840 return error(_("unrecognized --scheduler argument '%s'"), arg);
2841 return 0;
2844 struct maintenance_start_opts {
2845 enum scheduler scheduler;
2848 static enum scheduler resolve_scheduler(enum scheduler scheduler)
2850 if (scheduler != SCHEDULER_AUTO)
2851 return scheduler;
2853 #if defined(__APPLE__)
2854 return SCHEDULER_LAUNCHCTL;
2856 #elif defined(GIT_WINDOWS_NATIVE)
2857 return SCHEDULER_SCHTASKS;
2859 #elif defined(__linux__)
2860 if (is_systemd_timer_available())
2861 return SCHEDULER_SYSTEMD;
2862 else if (is_crontab_available())
2863 return SCHEDULER_CRON;
2864 else
2865 die(_("neither systemd timers nor crontab are available"));
2867 #else
2868 return SCHEDULER_CRON;
2869 #endif
2872 static void validate_scheduler(enum scheduler scheduler)
2874 if (scheduler == SCHEDULER_INVALID)
2875 BUG("invalid scheduler");
2876 if (scheduler == SCHEDULER_AUTO)
2877 BUG("resolve_scheduler should have been called before");
2879 if (!scheduler_fn[scheduler].is_available())
2880 die(_("%s scheduler is not available"),
2881 scheduler_fn[scheduler].name);
2884 static int update_background_schedule(const struct maintenance_start_opts *opts,
2885 int enable)
2887 unsigned int i;
2888 int result = 0;
2889 struct lock_file lk;
2890 char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
2892 if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
2893 free(lock_path);
2894 return error(_("another process is scheduling background maintenance"));
2897 for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
2898 if (enable && opts->scheduler == i)
2899 continue;
2900 if (!scheduler_fn[i].is_available())
2901 continue;
2902 scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
2905 if (enable)
2906 result = scheduler_fn[opts->scheduler].update_schedule(
2907 1, get_lock_file_fd(&lk));
2909 rollback_lock_file(&lk);
2911 free(lock_path);
2912 return result;
2915 static const char *const builtin_maintenance_start_usage[] = {
2916 N_("git maintenance start [--scheduler=<scheduler>]"),
2917 NULL
2920 static int maintenance_start(int argc, const char **argv, const char *prefix)
2922 struct maintenance_start_opts opts = { 0 };
2923 struct option options[] = {
2924 OPT_CALLBACK_F(
2925 0, "scheduler", &opts.scheduler, N_("scheduler"),
2926 N_("scheduler to trigger git maintenance run"),
2927 PARSE_OPT_NONEG, maintenance_opt_scheduler),
2928 OPT_END()
2930 const char *register_args[] = { "register", NULL };
2932 argc = parse_options(argc, argv, prefix, options,
2933 builtin_maintenance_start_usage, 0);
2934 if (argc)
2935 usage_with_options(builtin_maintenance_start_usage, options);
2937 opts.scheduler = resolve_scheduler(opts.scheduler);
2938 validate_scheduler(opts.scheduler);
2940 if (update_background_schedule(&opts, 1))
2941 die(_("failed to set up maintenance schedule"));
2943 if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
2944 warning(_("failed to add repo to global config"));
2945 return 0;
2948 static const char *const builtin_maintenance_stop_usage[] = {
2949 "git maintenance stop",
2950 NULL
2953 static int maintenance_stop(int argc, const char **argv, const char *prefix)
2955 struct option options[] = {
2956 OPT_END()
2958 argc = parse_options(argc, argv, prefix, options,
2959 builtin_maintenance_stop_usage, 0);
2960 if (argc)
2961 usage_with_options(builtin_maintenance_stop_usage, options);
2962 return update_background_schedule(NULL, 0);
2965 static const char * const builtin_maintenance_usage[] = {
2966 N_("git maintenance <subcommand> [<options>]"),
2967 NULL,
2970 int cmd_maintenance(int argc,
2971 const char **argv,
2972 const char *prefix,
2973 struct repository *repo UNUSED)
2975 parse_opt_subcommand_fn *fn = NULL;
2976 struct option builtin_maintenance_options[] = {
2977 OPT_SUBCOMMAND("run", &fn, maintenance_run),
2978 OPT_SUBCOMMAND("start", &fn, maintenance_start),
2979 OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
2980 OPT_SUBCOMMAND("register", &fn, maintenance_register),
2981 OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
2982 OPT_END(),
2985 argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
2986 builtin_maintenance_usage, 0);
2987 return fn(argc, argv, prefix);