Raw
1 /*
2 * git gc builtin command
3 *
4 * Cleanup unreachable files and optimize the repository.
5 *
6 * Copyright (c) 2007 James Bowes
7 *
8 * Based on git-gc.sh, which is
9 *
10 * Copyright (c) 2006 Shawn O. Pearce
11 */
12
13 #define USE_THE_REPOSITORY_VARIABLE
14 #define DISABLE_SIGN_COMPARE_WARNINGS
15
16 #include "builtin.h"
17 #include "abspath.h"
18 #include "date.h"
19 #include "dir.h"
20 #include "environment.h"
21 #include "hex.h"
22 #include "config.h"
23 #include "tempfile.h"
24 #include "lockfile.h"
25 #include "parse-options.h"
26 #include "run-command.h"
27 #include "sigchain.h"
28 #include "strvec.h"
29 #include "commit.h"
30 #include "commit-graph.h"
31 #include "packfile.h"
32 #include "object-file.h"
33 #include "odb.h"
34 #include "path.h"
35 #include "reflog.h"
36 #include "rerere.h"
37 #include "revision.h"
38 #include "refs.h"
39 #include "remote.h"
40 #include "exec-cmd.h"
41 #include "gettext.h"
42 #include "hook.h"
43 #include "setup.h"
44 #include "trace2.h"
45 #include "worktree.h"
46
47 #define FAILED_RUN "failed to run %s"
48
49 static const char * const builtin_gc_usage[] = {
50 N_("git gc [<options>]"),
51 NULL
52 };
53
54 static timestamp_t gc_log_expire_time;
55 static struct tempfile *pidfile;
56 static struct lock_file log_lock;
57 static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
58
59 static void clean_pack_garbage(void)
60 {
61 int i;
62 for (i = 0; i < pack_garbage.nr; i++)
63 unlink_or_warn(pack_garbage.items[i].string);
64 string_list_clear(&pack_garbage, 0);
65 }
66
67 static void report_pack_garbage(unsigned seen_bits, const char *path)
68 {
69 if (seen_bits == PACKDIR_FILE_IDX)
70 string_list_append(&pack_garbage, path);
71 }
72
73 static void process_log_file(void)
74 {
75 struct stat st;
76 if (fstat(get_lock_file_fd(&log_lock), &st)) {
77 /*
78 * Perhaps there was an i/o error or another
79 * unlikely situation. Try to make a note of
80 * this in gc.log along with any existing
81 * messages.
82 */
83 int saved_errno = errno;
84 fprintf(stderr, _("Failed to fstat %s: %s"),
85 get_lock_file_path(&log_lock),
86 strerror(saved_errno));
87 fflush(stderr);
88 commit_lock_file(&log_lock);
89 errno = saved_errno;
90 } else if (st.st_size) {
91 /* There was some error recorded in the lock file */
92 commit_lock_file(&log_lock);
93 } else {
94 char *path = repo_git_path(the_repository, "gc.log");
95 /* No error, clean up any old gc.log */
96 unlink(path);
97 rollback_lock_file(&log_lock);
98 free(path);
99 }
100 }
101
102 static void process_log_file_at_exit(void)
103 {
104 fflush(stderr);
105 process_log_file();
106 }
107
108 static int gc_config_is_timestamp_never(const char *var)
109 {
110 const char *value;
111 timestamp_t expire;
112
113 if (!repo_config_get_value(the_repository, var, &value) && value) {
114 if (parse_expiry_date(value, &expire))
115 die(_("failed to parse '%s' value '%s'"), var, value);
116 return expire == 0;
117 }
118 return 0;
119 }
120
121 struct gc_config {
122 int pack_refs;
123 int prune_reflogs;
124 int cruft_packs;
125 unsigned long max_cruft_size;
126 int aggressive_depth;
127 int aggressive_window;
128 int detach_auto;
129 char *gc_log_expire;
130 char *prune_expire;
131 char *prune_worktrees_expire;
132 char *repack_expire_to;
133 };
134
135 #define GC_CONFIG_INIT { \
136 .pack_refs = 1, \
137 .prune_reflogs = 1, \
138 .cruft_packs = 1, \
139 .aggressive_depth = 50, \
140 .aggressive_window = 250, \
141 .detach_auto = 1, \
142 .gc_log_expire = xstrdup("1.day.ago"), \
143 .prune_expire = xstrdup("2.weeks.ago"), \
144 .prune_worktrees_expire = xstrdup("3.months.ago"), \
145 }
146
147 static void gc_config_release(struct gc_config *cfg)
148 {
149 free(cfg->gc_log_expire);
150 free(cfg->prune_expire);
151 free(cfg->prune_worktrees_expire);
152 }
153
154 static void gc_config(struct gc_config *cfg)
155 {
156 const char *value;
157 char *owned = NULL;
158
159 if (!repo_config_get_value(the_repository, "gc.packrefs", &value)) {
160 if (value && !strcmp(value, "notbare"))
161 cfg->pack_refs = -1;
162 else
163 cfg->pack_refs = git_config_bool("gc.packrefs", value);
164 }
165
166 if (gc_config_is_timestamp_never("gc.reflogexpire") &&
167 gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
168 cfg->prune_reflogs = 0;
169
170 repo_config_get_int(the_repository, "gc.aggressivewindow", &cfg->aggressive_window);
171 repo_config_get_int(the_repository, "gc.aggressivedepth", &cfg->aggressive_depth);
172 repo_config_get_bool(the_repository, "gc.autodetach", &cfg->detach_auto);
173 repo_config_get_bool(the_repository, "gc.cruftpacks", &cfg->cruft_packs);
174 repo_config_get_ulong(the_repository, "gc.maxcruftsize", &cfg->max_cruft_size);
175
176 if (!repo_config_get_expiry(the_repository, "gc.pruneexpire", &owned)) {
177 free(cfg->prune_expire);
178 cfg->prune_expire = owned;
179 }
180
181 if (!repo_config_get_expiry(the_repository, "gc.worktreepruneexpire", &owned)) {
182 free(cfg->prune_worktrees_expire);
183 cfg->prune_worktrees_expire = owned;
184 }
185
186 if (!repo_config_get_expiry(the_repository, "gc.logexpiry", &owned)) {
187 free(cfg->gc_log_expire);
188 cfg->gc_log_expire = owned;
189 }
190
191 repo_config(the_repository, git_default_config, NULL);
192 }
193
194 enum schedule_priority {
195 SCHEDULE_NONE = 0,
196 SCHEDULE_WEEKLY = 1,
197 SCHEDULE_DAILY = 2,
198 SCHEDULE_HOURLY = 3,
199 };
200
201 static enum schedule_priority parse_schedule(const char *value)
202 {
203 if (!value)
204 return SCHEDULE_NONE;
205 if (!strcasecmp(value, "hourly"))
206 return SCHEDULE_HOURLY;
207 if (!strcasecmp(value, "daily"))
208 return SCHEDULE_DAILY;
209 if (!strcasecmp(value, "weekly"))
210 return SCHEDULE_WEEKLY;
211 return SCHEDULE_NONE;
212 }
213
214 enum maintenance_task_label {
215 TASK_PREFETCH,
216 TASK_LOOSE_OBJECTS,
217 TASK_INCREMENTAL_REPACK,
218 TASK_GEOMETRIC_REPACK,
219 TASK_GC,
220 TASK_COMMIT_GRAPH,
221 TASK_PACK_REFS,
222 TASK_REFLOG_EXPIRE,
223 TASK_WORKTREE_PRUNE,
224 TASK_RERERE_GC,
225
226 /* Leave as final value */
227 TASK__COUNT
228 };
229
230 struct maintenance_run_opts {
231 enum maintenance_task_label *tasks;
232 size_t tasks_nr, tasks_alloc;
233 int auto_flag;
234 int detach;
235 int quiet;
236 enum schedule_priority schedule;
237 };
238 #define MAINTENANCE_RUN_OPTS_INIT { \
239 .detach = -1, \
240 }
241
242 static void maintenance_run_opts_release(struct maintenance_run_opts *opts)
243 {
244 free(opts->tasks);
245 }
246
247 static int pack_refs_condition(UNUSED struct gc_config *cfg)
248 {
249 struct string_list included_refs = STRING_LIST_INIT_NODUP;
250 struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
251 struct refs_optimize_opts optimize_opts = {
252 .exclusions = &excludes,
253 .includes = &included_refs,
254 .flags = REFS_OPTIMIZE_PRUNE | REFS_OPTIMIZE_AUTO,
255 };
256 bool required;
257
258 /* Check for all refs, similar to 'git refs optimize --all'. */
259 string_list_append(optimize_opts.includes, "*");
260
261 if (refs_optimize_required(get_main_ref_store(the_repository),
262 &optimize_opts, &required))
263 return 0;
264
265 clear_ref_exclusions(&excludes);
266 string_list_clear(&included_refs, 0);
267
268 return required;
269 }
270
271 static int maintenance_task_pack_refs(struct maintenance_run_opts *opts,
272 UNUSED struct gc_config *cfg)
273 {
274 struct child_process cmd = CHILD_PROCESS_INIT;
275
276 cmd.git_cmd = 1;
277 strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
278 if (opts->auto_flag)
279 strvec_push(&cmd.args, "--auto");
280
281 return run_command(&cmd);
282 }
283
284 struct count_reflog_entries_data {
285 struct expire_reflog_policy_cb policy;
286 size_t count;
287 size_t limit;
288 };
289
290 static int count_reflog_entries(const char *refname UNUSED,
291 struct object_id *old_oid, struct object_id *new_oid,
292 const char *committer, timestamp_t timestamp,
293 int tz, const char *msg, void *cb_data)
294 {
295 struct count_reflog_entries_data *data = cb_data;
296 if (should_expire_reflog_ent(old_oid, new_oid, committer, timestamp, tz, msg, &data->policy))
297 data->count++;
298 return data->count >= data->limit;
299 }
300
301 static int reflog_expire_condition(struct gc_config *cfg UNUSED)
302 {
303 timestamp_t now = time(NULL);
304 struct count_reflog_entries_data data = {
305 .policy = {
306 .opts = REFLOG_EXPIRE_OPTIONS_INIT(now),
307 },
308 };
309 int limit = 100;
310
311 repo_config_get_int(the_repository, "maintenance.reflog-expire.auto", &limit);
312 if (!limit)
313 return 0;
314 if (limit < 0)
315 return 1;
316 data.limit = limit;
317
318 repo_config(the_repository, reflog_expire_config, &data.policy.opts);
319
320 reflog_expire_options_set_refname(&data.policy.opts, "HEAD");
321 refs_for_each_reflog_ent(get_main_ref_store(the_repository), "HEAD",
322 count_reflog_entries, &data);
323
324 reflog_expiry_cleanup(&data.policy);
325 reflog_clear_expire_config(&data.policy.opts);
326 return data.count >= data.limit;
327 }
328
329 static int maintenance_task_reflog_expire(struct maintenance_run_opts *opts UNUSED,
330 struct gc_config *cfg UNUSED)
331 {
332 struct child_process cmd = CHILD_PROCESS_INIT;
333 cmd.git_cmd = 1;
334 strvec_pushl(&cmd.args, "reflog", "expire", "--all", NULL);
335 return run_command(&cmd);
336 }
337
338 static int maintenance_task_worktree_prune(struct maintenance_run_opts *opts UNUSED,
339 struct gc_config *cfg)
340 {
341 struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
342
343 prune_worktrees_cmd.git_cmd = 1;
344 strvec_pushl(&prune_worktrees_cmd.args, "worktree", "prune", "--expire", NULL);
345 strvec_push(&prune_worktrees_cmd.args, cfg->prune_worktrees_expire);
346
347 return run_command(&prune_worktrees_cmd);
348 }
349
350 static int worktree_prune_condition(struct gc_config *cfg)
351 {
352 struct strbuf buf = STRBUF_INIT;
353 int should_prune = 0, limit = 1;
354 timestamp_t expiry_date;
355 struct dirent *d;
356 DIR *dir = NULL;
357
358 repo_config_get_int(the_repository, "maintenance.worktree-prune.auto", &limit);
359 if (limit <= 0) {
360 should_prune = limit < 0;
361 goto out;
362 }
363
364 if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date))
365 goto out;
366
367 dir = opendir(repo_git_path_replace(the_repository, &buf, "worktrees"));
368 if (!dir)
369 goto out;
370
371 while (limit && (d = readdir_skip_dot_and_dotdot(dir))) {
372 char *wtpath;
373 strbuf_reset(&buf);
374 if (should_prune_worktree(the_repository, d->d_name, &buf, &wtpath, expiry_date))
375 limit--;
376 free(wtpath);
377 }
378
379 should_prune = !limit;
380
381 out:
382 if (dir)
383 closedir(dir);
384 strbuf_release(&buf);
385 return should_prune;
386 }
387
388 static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED,
389 struct gc_config *cfg UNUSED)
390 {
391 struct child_process rerere_cmd = CHILD_PROCESS_INIT;
392 rerere_cmd.git_cmd = 1;
393 strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
394 return run_command(&rerere_cmd);
395 }
396
397 static int rerere_gc_condition(struct gc_config *cfg UNUSED)
398 {
399 struct strbuf path = STRBUF_INIT;
400 int should_gc = 0, limit = 1;
401 DIR *dir = NULL;
402
403 repo_config_get_int(the_repository, "maintenance.rerere-gc.auto", &limit);
404 if (limit <= 0) {
405 should_gc = limit < 0;
406 goto out;
407 }
408
409 /*
410 * We skip garbage collection in case we either have no "rr-cache"
411 * directory or when it doesn't contain at least one entry.
412 */
413 repo_git_path_replace(the_repository, &path, "rr-cache");
414 dir = opendir(path.buf);
415 if (!dir)
416 goto out;
417 should_gc = !!readdir_skip_dot_and_dotdot(dir);
418
419 out:
420 strbuf_release(&path);
421 if (dir)
422 closedir(dir);
423 return should_gc;
424 }
425
426 #define OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, aggressive) \
427 .prune_expire = (cfg)->prune_expire, \
428 .expire_to = (cfg)->repack_expire_to, \
429 .cruft_packs = (cfg)->cruft_packs, \
430 .max_cruft_size = (cfg)->max_cruft_size, \
431 .window = (aggressive) ? (cfg)->aggressive_window : 0, \
432 .depth = (aggressive) ? (cfg)->aggressive_depth : 0
433
434 /* return NULL on success, else hostname running the gc */
435 static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
436 {
437 struct lock_file lock = LOCK_INIT;
438 char my_host[HOST_NAME_MAX + 1];
439 struct strbuf sb = STRBUF_INIT;
440 struct stat st;
441 uintmax_t pid;
442 FILE *fp;
443 int fd;
444 char *pidfile_path;
445
446 if (is_tempfile_active(pidfile))
447 /* already locked */
448 return NULL;
449
450 if (xgethostname(my_host, sizeof(my_host)))
451 xsnprintf(my_host, sizeof(my_host), "unknown");
452
453 pidfile_path = repo_git_path(the_repository, "gc.pid");
454 fd = hold_lock_file_for_update(&lock, pidfile_path,
455 LOCK_DIE_ON_ERROR);
456 if (!force) {
457 static char locking_host[HOST_NAME_MAX + 1];
458 static char *scan_fmt;
459 int should_exit;
460
461 if (!scan_fmt)
462 scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX);
463 fp = fopen(pidfile_path, "r");
464 memset(locking_host, 0, sizeof(locking_host));
465 should_exit =
466 fp != NULL &&
467 !fstat(fileno(fp), &st) &&
468 /*
469 * 12 hour limit is very generous as gc should
470 * never take that long. On the other hand we
471 * don't really need a strict limit here,
472 * running gc --auto one day late is not a big
473 * problem. --force can be used in manual gc
474 * after the user verifies that no gc is
475 * running.
476 */
477 time(NULL) - st.st_mtime <= 12 * 3600 &&
478 fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
479 /* be gentle to concurrent "gc" on remote hosts */
480 (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
481 if (fp)
482 fclose(fp);
483 if (should_exit) {
484 if (fd >= 0)
485 rollback_lock_file(&lock);
486 *ret_pid = pid;
487 free(pidfile_path);
488 return locking_host;
489 }
490 }
491
492 strbuf_addf(&sb, "%"PRIuMAX" %s",
493 (uintmax_t) getpid(), my_host);
494 write_in_full(fd, sb.buf, sb.len);
495 strbuf_release(&sb);
496 commit_lock_file(&lock);
497 pidfile = register_tempfile(pidfile_path);
498 free(pidfile_path);
499 return NULL;
500 }
501
502 /*
503 * Returns 0 if there was no previous error and gc can proceed, 1 if
504 * gc should not proceed due to an error in the last run. Prints a
505 * message and returns with a non-[01] status code if an error occurred
506 * while reading gc.log
507 */
508 static int report_last_gc_error(void)
509 {
510 struct strbuf sb = STRBUF_INIT;
511 int ret = 0;
512 ssize_t len;
513 struct stat st;
514 char *gc_log_path = repo_git_path(the_repository, "gc.log");
515
516 if (stat(gc_log_path, &st)) {
517 if (errno == ENOENT)
518 goto done;
519
520 ret = die_message_errno(_("cannot stat '%s'"), gc_log_path);
521 goto done;
522 }
523
524 if (st.st_mtime < gc_log_expire_time)
525 goto done;
526
527 len = strbuf_read_file(&sb, gc_log_path, 0);
528 if (len < 0)
529 ret = die_message_errno(_("cannot read '%s'"), gc_log_path);
530 else if (len > 0) {
531 /*
532 * A previous gc failed. Report the error, and don't
533 * bother with an automatic gc run since it is likely
534 * to fail in the same way.
535 */
536 warning(_("The last gc run reported the following. "
537 "Please correct the root cause\n"
538 "and remove %s\n"
539 "Automatic cleanup will not be performed "
540 "until the file is removed.\n\n"
541 "%s"),
542 gc_log_path, sb.buf);
543 ret = 1;
544 }
545 strbuf_release(&sb);
546 done:
547 free(gc_log_path);
548 return ret;
549 }
550
551 static int gc_foreground_tasks(struct maintenance_run_opts *opts,
552 struct gc_config *cfg)
553 {
554 if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
555 return error(FAILED_RUN, "pack-refs");
556 if (cfg->prune_reflogs && maintenance_task_reflog_expire(opts, cfg))
557 return error(FAILED_RUN, "reflog");
558 return 0;
559 }
560
561 static int maintenance_task_odb(struct maintenance_run_opts *opts,
562 struct gc_config *cfg,
563 int keep_largest_pack,
564 int aggressive)
565 {
566 struct odb_optimize_options odb_opts = {
567 .strategy = ODB_OPTIMIZE_INCREMENTAL,
568 .keep_largest_pack = keep_largest_pack,
569 OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, aggressive),
570 };
571
572 if (opts->auto_flag)
573 odb_opts.flags |= ODB_OPTIMIZE_AUTO;
574 if (!opts->quiet)
575 odb_opts.flags |= ODB_OPTIMIZE_VERBOSE;
576 if (aggressive)
577 odb_opts.flags |= ODB_OPTIMIZE_NO_REUSE_DELTAS;
578
579 return odb_optimize(the_repository->objects, &odb_opts);
580 }
581
582 int cmd_gc(int argc,
583 const char **argv,
584 const char *prefix,
585 struct repository *repo UNUSED)
586 {
587 int aggressive = 0;
588 int force = 0;
589 const char *name;
590 pid_t pid;
591 int daemonized = 0;
592 int keep_largest_pack = -1;
593 int skip_foreground_tasks = 0;
594 timestamp_t dummy;
595 struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
596 struct gc_config cfg = GC_CONFIG_INIT;
597 const char *prune_expire_sentinel = "sentinel";
598 const char *prune_expire_arg = prune_expire_sentinel;
599 int ret;
600 struct option builtin_gc_options[] = {
601 OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
602 {
603 .type = OPTION_STRING,
604 .long_name = "prune",
605 .value = &prune_expire_arg,
606 .argh = N_("date"),
607 .help = N_("prune unreferenced objects"),
608 .flags = PARSE_OPT_OPTARG,
609 .defval = (intptr_t)prune_expire_arg,
610 },
611 OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")),
612 OPT_UNSIGNED(0, "max-cruft-size", &cfg.max_cruft_size,
613 N_("with --cruft, limit the size of new cruft packs")),
614 OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
615 OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
616 PARSE_OPT_NOCOMPLETE),
617 OPT_BOOL(0, "detach", &opts.detach,
618 N_("perform garbage collection in the background")),
619 OPT_BOOL_F(0, "force", &force,
620 N_("force running gc even if there may be another gc running"),
621 PARSE_OPT_NOCOMPLETE),
622 OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack,
623 N_("repack all other packs except the largest pack")),
624 OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"),
625 N_("pack prefix to store a pack containing pruned objects")),
626 OPT_HIDDEN_BOOL(0, "skip-foreground-tasks", &skip_foreground_tasks,
627 N_("skip maintenance tasks typically done in the foreground")),
628 OPT_END()
629 };
630
631 show_usage_with_options_if_asked(argc, argv,
632 builtin_gc_usage, builtin_gc_options);
633
634 gc_config(&cfg);
635
636 if (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
637 die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
638
639 if (cfg.pack_refs < 0)
640 cfg.pack_refs = !is_bare_repository(the_repository);
641
642 argc = parse_options(argc, argv, prefix, builtin_gc_options,
643 builtin_gc_usage, 0);
644 if (argc > 0)
645 usage_with_options(builtin_gc_usage, builtin_gc_options);
646
647 if (prune_expire_arg != prune_expire_sentinel) {
648 free(cfg.prune_expire);
649 cfg.prune_expire = xstrdup_or_null(prune_expire_arg);
650 }
651 if (cfg.prune_expire && parse_expiry_date(cfg.prune_expire, &dummy))
652 die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
653
654 if (opts.auto_flag) {
655 struct odb_optimize_options optimize_opts = {
656 .strategy = ODB_OPTIMIZE_INCREMENTAL,
657 OPTIMIZE_FIELDS_FROM_GC_CONFIG(&cfg, 0),
658 };
659
660 if (cfg.detach_auto && opts.detach < 0)
661 opts.detach = 1;
662
663 /*
664 * Auto-gc should be least intrusive as possible.
665 */
666 if (!odb_optimize_required(the_repository->objects, &optimize_opts) ||
667 run_hooks(the_repository, "pre-auto-gc")) {
668 ret = 0;
669 goto out;
670 }
671
672 if (!opts.quiet) {
673 if (opts.detach > 0)
674 fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
675 else
676 fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
677 fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
678 }
679 }
680
681 if (opts.detach > 0) {
682 ret = report_last_gc_error();
683 if (ret == 1) {
684 /* Last gc --auto failed. Skip this one. */
685 ret = 0;
686 goto out;
687
688 } else if (ret) {
689 /* an I/O error occurred, already reported */
690 goto out;
691 }
692
693 if (!skip_foreground_tasks) {
694 if (lock_repo_for_gc(force, &pid)) {
695 ret = 0;
696 goto out;
697 }
698
699 if (gc_foreground_tasks(&opts, &cfg) < 0)
700 die(NULL);
701 delete_tempfile(&pidfile);
702 }
703
704 /*
705 * failure to daemonize is ok, we'll continue
706 * in foreground
707 */
708 daemonized = !daemonize();
709 }
710
711 name = lock_repo_for_gc(force, &pid);
712 if (name) {
713 if (opts.auto_flag) {
714 ret = 0;
715 goto out; /* be quiet on --auto */
716 }
717
718 die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
719 name, (uintmax_t)pid);
720 }
721
722 if (daemonized) {
723 char *path = repo_git_path(the_repository, "gc.log");
724 hold_lock_file_for_update(&log_lock, path,
725 LOCK_DIE_ON_ERROR);
726 dup2(get_lock_file_fd(&log_lock), 2);
727 atexit(process_log_file_at_exit);
728 free(path);
729 }
730
731 if (opts.detach <= 0 && !skip_foreground_tasks)
732 gc_foreground_tasks(&opts, &cfg);
733
734 if (cfg.prune_worktrees_expire &&
735 maintenance_task_worktree_prune(&opts, &cfg))
736 die(FAILED_RUN, "worktree");
737
738 if (maintenance_task_rerere_gc(&opts, &cfg))
739 die(FAILED_RUN, "rerere");
740
741 if (maintenance_task_odb(&opts, &cfg, keep_largest_pack, aggressive))
742 die(NULL);
743
744 report_garbage = report_pack_garbage;
745 odb_reprepare(the_repository->objects);
746 if (pack_garbage.nr > 0) {
747 odb_close(the_repository->objects);
748 clean_pack_garbage();
749 }
750
751 if (the_repository->settings.gc_write_commit_graph == 1)
752 write_commit_graph_reachable(the_repository->objects->sources,
753 !opts.quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
754 NULL);
755
756 if (!daemonized) {
757 char *path = repo_git_path(the_repository, "gc.log");
758 unlink(path);
759 free(path);
760 }
761
762 out:
763 maintenance_run_opts_release(&opts);
764 gc_config_release(&cfg);
765 return 0;
766 }
767
768 static const char *const builtin_maintenance_run_usage[] = {
769 N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>] [--schedule]"),
770 NULL
771 };
772
773 static int maintenance_opt_schedule(const struct option *opt, const char *arg,
774 int unset)
775 {
776 enum schedule_priority *priority = opt->value;
777
778 if (unset)
779 die(_("--no-schedule is not allowed"));
780
781 *priority = parse_schedule(arg);
782
783 if (!*priority)
784 die(_("unrecognized --schedule argument '%s'"), arg);
785
786 return 0;
787 }
788
789 struct cg_auto_data {
790 int num_not_in_graph;
791 int limit;
792 };
793
794 static int dfs_on_ref(const struct reference *ref, void *cb_data)
795 {
796 struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
797 int result = 0;
798 const struct object_id *maybe_peeled = ref->oid;
799 struct object_id peeled;
800 struct commit_list *stack = NULL;
801 struct commit *commit;
802
803 if (!reference_get_peeled_oid(the_repository, ref, &peeled))
804 maybe_peeled = &peeled;
805 if (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)
806 return 0;
807
808 commit = lookup_commit(the_repository, maybe_peeled);
809 if (!commit || commit->object.flags & SEEN)
810 return 0;
811 commit->object.flags |= SEEN;
812
813 if (repo_parse_commit(the_repository, commit) ||
814 commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
815 return 0;
816
817 data->num_not_in_graph++;
818
819 if (data->num_not_in_graph >= data->limit)
820 return 1;
821
822 commit_list_insert(commit, &stack);
823
824 while (!result && stack) {
825 struct commit_list *parent;
826
827 commit = pop_commit(&stack);
828
829 for (parent = commit->parents; parent; parent = parent->next) {
830 if (repo_parse_commit(the_repository, parent->item) ||
831 commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
832 parent->item->object.flags & SEEN)
833 continue;
834
835 parent->item->object.flags |= SEEN;
836 data->num_not_in_graph++;
837
838 if (data->num_not_in_graph >= data->limit) {
839 result = 1;
840 break;
841 }
842
843 commit_list_insert(parent->item, &stack);
844 }
845 }
846
847 commit_list_free(stack);
848 return result;
849 }
850
851 static int should_write_commit_graph(struct gc_config *cfg UNUSED)
852 {
853 int result;
854 struct cg_auto_data data;
855
856 data.num_not_in_graph = 0;
857 data.limit = 100;
858 repo_config_get_int(the_repository, "maintenance.commit-graph.auto",
859 &data.limit);
860
861 if (!data.limit)
862 return 0;
863 if (data.limit < 0)
864 return 1;
865
866 result = refs_for_each_ref(get_main_ref_store(the_repository),
867 dfs_on_ref, &data);
868
869 repo_clear_commit_marks(the_repository, SEEN);
870
871 return result;
872 }
873
874 static int run_write_commit_graph(struct maintenance_run_opts *opts)
875 {
876 struct child_process child = CHILD_PROCESS_INIT;
877
878 child.git_cmd = 1;
879 child.odb_to_close = the_repository->objects;
880 strvec_pushl(&child.args, "commit-graph", "write",
881 "--split", "--reachable", NULL);
882
883 if (opts->quiet)
884 strvec_push(&child.args, "--no-progress");
885 else
886 strvec_push(&child.args, "--progress");
887
888 return !!run_command(&child);
889 }
890
891 static int maintenance_task_commit_graph(struct maintenance_run_opts *opts,
892 struct gc_config *cfg UNUSED)
893 {
894 prepare_repo_settings(the_repository);
895 if (!the_repository->settings.core_commit_graph)
896 return 0;
897
898 if (run_write_commit_graph(opts)) {
899 error(_("failed to write commit-graph"));
900 return 1;
901 }
902
903 return 0;
904 }
905
906 static int fetch_remote(struct remote *remote, void *cbdata)
907 {
908 struct maintenance_run_opts *opts = cbdata;
909 struct child_process child = CHILD_PROCESS_INIT;
910
911 if (remote->skip_default_update)
912 return 0;
913
914 child.git_cmd = 1;
915 strvec_pushl(&child.args, "fetch", remote->name,
916 "--prefetch", "--prune", "--no-tags",
917 "--no-write-fetch-head", "--recurse-submodules=no",
918 NULL);
919
920 if (opts->quiet)
921 strvec_push(&child.args, "--quiet");
922
923 return !!run_command(&child);
924 }
925
926 static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
927 struct gc_config *cfg UNUSED)
928 {
929 if (for_each_remote(fetch_remote, opts)) {
930 error(_("failed to prefetch remotes"));
931 return 1;
932 }
933
934 return 0;
935 }
936
937 static int maintenance_task_gc_foreground(struct maintenance_run_opts *opts,
938 struct gc_config *cfg)
939 {
940 return gc_foreground_tasks(opts, cfg);
941 }
942
943 static int maintenance_task_gc_background(struct maintenance_run_opts *opts,
944 struct gc_config *cfg UNUSED)
945 {
946 struct child_process child = CHILD_PROCESS_INIT;
947
948 child.git_cmd = 1;
949 child.odb_to_close = the_repository->objects;
950 strvec_push(&child.args, "gc");
951
952 if (opts->auto_flag)
953 strvec_push(&child.args, "--auto");
954 if (opts->quiet)
955 strvec_push(&child.args, "--quiet");
956 else
957 strvec_push(&child.args, "--no-quiet");
958 strvec_push(&child.args, "--no-detach");
959 strvec_push(&child.args, "--skip-foreground-tasks");
960
961 return run_command(&child);
962 }
963
964 static int gc_condition(struct gc_config *cfg)
965 {
966 struct odb_optimize_options opts = {
967 .strategy = ODB_OPTIMIZE_INCREMENTAL,
968 OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
969 };
970 return odb_optimize_required(the_repository->objects, &opts);
971 }
972
973 static int prune_packed(struct maintenance_run_opts *opts)
974 {
975 struct child_process child = CHILD_PROCESS_INIT;
976
977 child.git_cmd = 1;
978 strvec_push(&child.args, "prune-packed");
979
980 if (opts->quiet)
981 strvec_push(&child.args, "--quiet");
982
983 return !!run_command(&child);
984 }
985
986 struct write_loose_object_data {
987 FILE *in;
988 int count;
989 int batch_size;
990 };
991
992 static int loose_object_auto_limit = 100;
993
994 static int loose_object_count(const struct object_id *oid UNUSED,
995 const char *path UNUSED,
996 void *data)
997 {
998 int *count = (int*)data;
999 if (++(*count) >= loose_object_auto_limit)
1000 return 1;
1001 return 0;
1002 }
1003
1004 static int loose_object_auto_condition(struct gc_config *cfg UNUSED)
1005 {
1006 int count = 0;
1007
1008 repo_config_get_int(the_repository, "maintenance.loose-objects.auto",
1009 &loose_object_auto_limit);
1010
1011 if (!loose_object_auto_limit)
1012 return 0;
1013 if (loose_object_auto_limit < 0)
1014 return 1;
1015
1016 return for_each_loose_file_in_source(the_repository->objects->sources,
1017 loose_object_count,
1018 NULL, NULL, &count);
1019 }
1020
1021 static int bail_on_loose(const struct object_id *oid UNUSED,
1022 const char *path UNUSED,
1023 void *data UNUSED)
1024 {
1025 return 1;
1026 }
1027
1028 static int write_loose_object_to_stdin(const struct object_id *oid,
1029 const char *path UNUSED,
1030 void *data)
1031 {
1032 struct write_loose_object_data *d = (struct write_loose_object_data *)data;
1033
1034 fprintf(d->in, "%s\n", oid_to_hex(oid));
1035
1036 /* If batch_size is INT_MAX, then this will return 0 always. */
1037 return ++(d->count) > d->batch_size;
1038 }
1039
1040 static int pack_loose(struct maintenance_run_opts *opts)
1041 {
1042 struct repository *r = the_repository;
1043 int result = 0;
1044 struct write_loose_object_data data;
1045 struct child_process pack_proc = CHILD_PROCESS_INIT;
1046
1047 /*
1048 * Do not start pack-objects process
1049 * if there are no loose objects.
1050 */
1051 if (!for_each_loose_file_in_source(r->objects->sources,
1052 bail_on_loose,
1053 NULL, NULL, NULL))
1054 return 0;
1055
1056 pack_proc.git_cmd = 1;
1057
1058 strvec_push(&pack_proc.args, "pack-objects");
1059 if (opts->quiet)
1060 strvec_push(&pack_proc.args, "--quiet");
1061 else
1062 strvec_push(&pack_proc.args, "--no-quiet");
1063 strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->sources->path);
1064
1065 pack_proc.in = -1;
1066
1067 /*
1068 * git-pack-objects(1) ends up writing the pack hash to stdout, which
1069 * we do not care for.
1070 */
1071 pack_proc.out = -1;
1072
1073 if (start_command(&pack_proc)) {
1074 error(_("failed to start 'git pack-objects' process"));
1075 return 1;
1076 }
1077
1078 data.in = xfdopen(pack_proc.in, "w");
1079 data.count = 0;
1080 data.batch_size = 50000;
1081
1082 repo_config_get_int(r, "maintenance.loose-objects.batchSize",
1083 &data.batch_size);
1084
1085 /* If configured as 0, then remove limit. */
1086 if (!data.batch_size)
1087 data.batch_size = INT_MAX;
1088 else if (data.batch_size > 0)
1089 data.batch_size--; /* Decrease for equality on limit. */
1090
1091 for_each_loose_file_in_source(r->objects->sources,
1092 write_loose_object_to_stdin,
1093 NULL, NULL, &data);
1094
1095 fclose(data.in);
1096
1097 if (finish_command(&pack_proc)) {
1098 error(_("failed to finish 'git pack-objects' process"));
1099 result = 1;
1100 }
1101
1102 return result;
1103 }
1104
1105 static int maintenance_task_loose_objects(struct maintenance_run_opts *opts,
1106 struct gc_config *cfg UNUSED)
1107 {
1108 return prune_packed(opts) || pack_loose(opts);
1109 }
1110
1111 static int incremental_repack_auto_condition(struct gc_config *cfg UNUSED)
1112 {
1113 struct packed_git *p;
1114 int incremental_repack_auto_limit = 10;
1115 int count = 0;
1116
1117 prepare_repo_settings(the_repository);
1118 if (!the_repository->settings.core_multi_pack_index)
1119 return 0;
1120
1121 repo_config_get_int(the_repository, "maintenance.incremental-repack.auto",
1122 &incremental_repack_auto_limit);
1123
1124 if (!incremental_repack_auto_limit)
1125 return 0;
1126 if (incremental_repack_auto_limit < 0)
1127 return 1;
1128
1129 repo_for_each_pack(the_repository, p) {
1130 if (count >= incremental_repack_auto_limit)
1131 break;
1132 if (!p->multi_pack_index)
1133 count++;
1134 }
1135
1136 return count >= incremental_repack_auto_limit;
1137 }
1138
1139 static int multi_pack_index_write(struct maintenance_run_opts *opts)
1140 {
1141 struct child_process child = CHILD_PROCESS_INIT;
1142
1143 child.git_cmd = 1;
1144 strvec_pushl(&child.args, "multi-pack-index", "write", NULL);
1145
1146 if (opts->quiet)
1147 strvec_push(&child.args, "--no-progress");
1148 else
1149 strvec_push(&child.args, "--progress");
1150
1151 if (run_command(&child))
1152 return error(_("failed to write multi-pack-index"));
1153
1154 return 0;
1155 }
1156
1157 static int multi_pack_index_expire(struct maintenance_run_opts *opts)
1158 {
1159 struct child_process child = CHILD_PROCESS_INIT;
1160
1161 child.git_cmd = 1;
1162 child.odb_to_close = the_repository->objects;
1163 strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
1164
1165 if (opts->quiet)
1166 strvec_push(&child.args, "--no-progress");
1167 else
1168 strvec_push(&child.args, "--progress");
1169
1170 if (run_command(&child))
1171 return error(_("'git multi-pack-index expire' failed"));
1172
1173 return 0;
1174 }
1175
1176 #define TWO_GIGABYTES (INT32_MAX)
1177
1178 static off_t get_auto_pack_size(void)
1179 {
1180 /*
1181 * The "auto" value is special: we optimize for
1182 * one large pack-file (i.e. from a clone) and
1183 * expect the rest to be small and they can be
1184 * repacked quickly.
1185 *
1186 * The strategy we select here is to select a
1187 * size that is one more than the second largest
1188 * pack-file. This ensures that we will repack
1189 * at least two packs if there are three or more
1190 * packs.
1191 */
1192 off_t max_size = 0;
1193 off_t second_largest_size = 0;
1194 off_t result_size;
1195 struct packed_git *p;
1196 struct repository *r = the_repository;
1197
1198 odb_reprepare(r->objects);
1199 repo_for_each_pack(r, p) {
1200 if (p->pack_size > max_size) {
1201 second_largest_size = max_size;
1202 max_size = p->pack_size;
1203 } else if (p->pack_size > second_largest_size)
1204 second_largest_size = p->pack_size;
1205 }
1206
1207 result_size = second_largest_size + 1;
1208
1209 /* But limit ourselves to a batch size of 2g */
1210 if (result_size > TWO_GIGABYTES)
1211 result_size = TWO_GIGABYTES;
1212
1213 return result_size;
1214 }
1215
1216 static int multi_pack_index_repack(struct maintenance_run_opts *opts)
1217 {
1218 struct child_process child = CHILD_PROCESS_INIT;
1219
1220 child.git_cmd = 1;
1221 child.odb_to_close = the_repository->objects;
1222 strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
1223
1224 if (opts->quiet)
1225 strvec_push(&child.args, "--no-progress");
1226 else
1227 strvec_push(&child.args, "--progress");
1228
1229 strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
1230 (uintmax_t)get_auto_pack_size());
1231
1232 if (run_command(&child))
1233 return error(_("'git multi-pack-index repack' failed"));
1234
1235 return 0;
1236 }
1237
1238 static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts,
1239 struct gc_config *cfg UNUSED)
1240 {
1241 prepare_repo_settings(the_repository);
1242 if (!the_repository->settings.core_multi_pack_index) {
1243 warning(_("skipping incremental-repack task because core.multiPackIndex is disabled"));
1244 return 0;
1245 }
1246
1247 if (multi_pack_index_write(opts))
1248 return 1;
1249 if (multi_pack_index_expire(opts))
1250 return 1;
1251 if (multi_pack_index_repack(opts))
1252 return 1;
1253 return 0;
1254 }
1255
1256 static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,
1257 struct gc_config *cfg)
1258 {
1259 struct odb_optimize_options odb_opts = {
1260 .strategy = ODB_OPTIMIZE_GEOMETRIC,
1261 OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
1262 };
1263
1264 if (!opts->quiet)
1265 odb_opts.flags |= ODB_OPTIMIZE_VERBOSE;
1266
1267 return odb_optimize(the_repository->objects, &odb_opts);
1268 }
1269
1270 static int geometric_repack_auto_condition(struct gc_config *cfg)
1271 {
1272 struct odb_optimize_options opts = {
1273 .strategy = ODB_OPTIMIZE_GEOMETRIC,
1274 OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
1275 };
1276 return odb_optimize_required(the_repository->objects, &opts);
1277 }
1278
1279 typedef int (*maintenance_task_fn)(struct maintenance_run_opts *opts,
1280 struct gc_config *cfg);
1281 typedef int (*maintenance_auto_fn)(struct gc_config *cfg);
1282
1283 struct maintenance_task {
1284 const char *name;
1285
1286 /*
1287 * Work that will be executed before detaching. This should not include
1288 * tasks that may run for an extended amount of time as it does cause
1289 * auto-maintenance to block until foreground tasks have been run.
1290 */
1291 maintenance_task_fn foreground;
1292
1293 /*
1294 * Work that will be executed after detaching. When not detaching the
1295 * work will be run in the foreground, as well.
1296 */
1297 maintenance_task_fn background;
1298
1299 /*
1300 * An auto condition function returns 1 if the task should run and 0 if
1301 * the task should NOT run. See needs_to_gc() for an example.
1302 */
1303 maintenance_auto_fn auto_condition;
1304 };
1305
1306 static const struct maintenance_task tasks[] = {
1307 [TASK_PREFETCH] = {
1308 .name = "prefetch",
1309 .background = maintenance_task_prefetch,
1310 },
1311 [TASK_LOOSE_OBJECTS] = {
1312 .name = "loose-objects",
1313 .background = maintenance_task_loose_objects,
1314 .auto_condition = loose_object_auto_condition,
1315 },
1316 [TASK_INCREMENTAL_REPACK] = {
1317 .name = "incremental-repack",
1318 .background = maintenance_task_incremental_repack,
1319 .auto_condition = incremental_repack_auto_condition,
1320 },
1321 [TASK_GEOMETRIC_REPACK] = {
1322 .name = "geometric-repack",
1323 .background = maintenance_task_geometric_repack,
1324 .auto_condition = geometric_repack_auto_condition,
1325 },
1326 [TASK_GC] = {
1327 .name = "gc",
1328 .foreground = maintenance_task_gc_foreground,
1329 .background = maintenance_task_gc_background,
1330 .auto_condition = gc_condition,
1331 },
1332 [TASK_COMMIT_GRAPH] = {
1333 .name = "commit-graph",
1334 .background = maintenance_task_commit_graph,
1335 .auto_condition = should_write_commit_graph,
1336 },
1337 [TASK_PACK_REFS] = {
1338 .name = "pack-refs",
1339 .foreground = maintenance_task_pack_refs,
1340 .auto_condition = pack_refs_condition,
1341 },
1342 [TASK_REFLOG_EXPIRE] = {
1343 .name = "reflog-expire",
1344 .foreground = maintenance_task_reflog_expire,
1345 .auto_condition = reflog_expire_condition,
1346 },
1347 [TASK_WORKTREE_PRUNE] = {
1348 .name = "worktree-prune",
1349 .background = maintenance_task_worktree_prune,
1350 .auto_condition = worktree_prune_condition,
1351 },
1352 [TASK_RERERE_GC] = {
1353 .name = "rerere-gc",
1354 .background = maintenance_task_rerere_gc,
1355 .auto_condition = rerere_gc_condition,
1356 },
1357 };
1358
1359 enum task_phase {
1360 TASK_PHASE_FOREGROUND,
1361 TASK_PHASE_BACKGROUND,
1362 };
1363
1364 enum auto_gc_hook_result {
1365 AUTO_GC_HOOK_UNDECIDED = 0,
1366 AUTO_GC_HOOK_RUN = 1,
1367 AUTO_GC_HOOK_SKIP = 2,
1368 };
1369
1370 static int maybe_run_task(const struct maintenance_task *task,
1371 struct repository *repo,
1372 struct maintenance_run_opts *opts,
1373 struct gc_config *cfg,
1374 enum task_phase phase,
1375 enum auto_gc_hook_result *auto_gc_hook_result)
1376 {
1377 int foreground = (phase == TASK_PHASE_FOREGROUND);
1378 maintenance_task_fn fn = foreground ? task->foreground : task->background;
1379 const char *region = foreground ? "maintenance foreground" : "maintenance";
1380 int ret = 0;
1381
1382 if (!fn)
1383 return 0;
1384 if (opts->auto_flag) {
1385 if (*auto_gc_hook_result == AUTO_GC_HOOK_SKIP)
1386 return 0;
1387
1388 if (!task->auto_condition || !task->auto_condition(cfg))
1389 return 0;
1390
1391 if (*auto_gc_hook_result == AUTO_GC_HOOK_UNDECIDED)
1392 *auto_gc_hook_result = run_hooks(repo, "pre-auto-gc") ?
1393 AUTO_GC_HOOK_SKIP : AUTO_GC_HOOK_RUN;
1394 if (*auto_gc_hook_result == AUTO_GC_HOOK_SKIP)
1395 return 0;
1396 }
1397
1398 trace2_region_enter(region, task->name, repo);
1399 if (fn(opts, cfg)) {
1400 error(_("task '%s' failed"), task->name);
1401 ret = 1;
1402 }
1403 trace2_region_leave(region, task->name, repo);
1404
1405 return ret;
1406 }
1407
1408 static int maintenance_run_tasks(struct maintenance_run_opts *opts,
1409 struct gc_config *cfg)
1410 {
1411 int result = 0;
1412 struct lock_file lk;
1413 struct repository *r = the_repository;
1414 char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path);
1415 enum auto_gc_hook_result auto_gc_hook_result = AUTO_GC_HOOK_UNDECIDED;
1416
1417 if (repo_hold_lock_file_for_update(r, &lk, lock_path, LOCK_NO_DEREF) < 0) {
1418 /*
1419 * Another maintenance command is running.
1420 *
1421 * If --auto was provided, then it is likely due to a
1422 * recursive process stack. Do not report an error in
1423 * that case.
1424 */
1425 if (!opts->auto_flag && !opts->quiet)
1426 warning(_("lock file '%s' exists, skipping maintenance"),
1427 lock_path);
1428 free(lock_path);
1429 return 0;
1430 }
1431 free(lock_path);
1432
1433 for (size_t i = 0; i < opts->tasks_nr; i++)
1434 if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
1435 TASK_PHASE_FOREGROUND, &auto_gc_hook_result))
1436 result = 1;
1437
1438 /* Failure to daemonize is ok, we'll continue in foreground. */
1439 if (opts->detach > 0) {
1440 trace2_region_enter("maintenance", "detach", the_repository);
1441 daemonize();
1442 trace2_region_leave("maintenance", "detach", the_repository);
1443 }
1444
1445 for (size_t i = 0; i < opts->tasks_nr; i++)
1446 if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
1447 TASK_PHASE_BACKGROUND, &auto_gc_hook_result))
1448 result = 1;
1449
1450 rollback_lock_file(&lk);
1451 return result;
1452 }
1453
1454 enum maintenance_type {
1455 /* As invoked via `git maintenance run --schedule=`. */
1456 MAINTENANCE_TYPE_SCHEDULED = (1 << 0),
1457 /* As invoked via `git maintenance run` and with `--auto`. */
1458 MAINTENANCE_TYPE_MANUAL = (1 << 1),
1459 };
1460
1461 struct maintenance_strategy {
1462 struct {
1463 unsigned type;
1464 enum schedule_priority schedule;
1465 } tasks[TASK__COUNT];
1466 };
1467
1468 static const struct maintenance_strategy none_strategy = { 0 };
1469
1470 static const struct maintenance_strategy gc_strategy = {
1471 .tasks = {
1472 [TASK_GC] = {
1473 .type = MAINTENANCE_TYPE_MANUAL | MAINTENANCE_TYPE_SCHEDULED,
1474 .schedule = SCHEDULE_DAILY,
1475 },
1476 },
1477 };
1478
1479 static const struct maintenance_strategy incremental_strategy = {
1480 .tasks = {
1481 [TASK_COMMIT_GRAPH] = {
1482 .type = MAINTENANCE_TYPE_SCHEDULED,
1483 .schedule = SCHEDULE_HOURLY,
1484 },
1485 [TASK_PREFETCH] = {
1486 .type = MAINTENANCE_TYPE_SCHEDULED,
1487 .schedule = SCHEDULE_HOURLY,
1488 },
1489 [TASK_INCREMENTAL_REPACK] = {
1490 .type = MAINTENANCE_TYPE_SCHEDULED,
1491 .schedule = SCHEDULE_DAILY,
1492 },
1493 [TASK_LOOSE_OBJECTS] = {
1494 .type = MAINTENANCE_TYPE_SCHEDULED,
1495 .schedule = SCHEDULE_DAILY,
1496 },
1497 [TASK_PACK_REFS] = {
1498 .type = MAINTENANCE_TYPE_SCHEDULED,
1499 .schedule = SCHEDULE_WEEKLY,
1500 },
1501 /*
1502 * Historically, the "incremental" strategy was only available
1503 * in the context of scheduled maintenance when set up via
1504 * "maintenance.strategy". We have later expanded that config
1505 * to also cover manual maintenance.
1506 *
1507 * To retain backwards compatibility with the previous status
1508 * quo we thus run git-gc(1) in case manual maintenance was
1509 * requested. This is the same as the default strategy, which
1510 * would have been in use beforehand.
1511 */
1512 [TASK_GC] = {
1513 .type = MAINTENANCE_TYPE_MANUAL,
1514 },
1515 },
1516 };
1517
1518 static const struct maintenance_strategy geometric_strategy = {
1519 .tasks = {
1520 [TASK_COMMIT_GRAPH] = {
1521 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1522 .schedule = SCHEDULE_HOURLY,
1523 },
1524 [TASK_GEOMETRIC_REPACK] = {
1525 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1526 .schedule = SCHEDULE_DAILY,
1527 },
1528 [TASK_PACK_REFS] = {
1529 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1530 .schedule = SCHEDULE_DAILY,
1531 },
1532 [TASK_RERERE_GC] = {
1533 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1534 .schedule = SCHEDULE_WEEKLY,
1535 },
1536 [TASK_REFLOG_EXPIRE] = {
1537 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1538 .schedule = SCHEDULE_WEEKLY,
1539 },
1540 [TASK_WORKTREE_PRUNE] = {
1541 .type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
1542 .schedule = SCHEDULE_WEEKLY,
1543 },
1544 },
1545 };
1546
1547 static struct maintenance_strategy parse_maintenance_strategy(const char *name)
1548 {
1549 if (!strcasecmp(name, "incremental"))
1550 return incremental_strategy;
1551 if (!strcasecmp(name, "gc"))
1552 return gc_strategy;
1553 if (!strcasecmp(name, "geometric"))
1554 return geometric_strategy;
1555 die(_("unknown maintenance strategy: '%s'"), name);
1556 }
1557
1558 static void initialize_task_config(struct maintenance_run_opts *opts,
1559 const struct string_list *selected_tasks)
1560 {
1561 struct strbuf config_name = STRBUF_INIT;
1562 struct maintenance_strategy strategy;
1563 enum maintenance_type type;
1564 const char *config_str;
1565
1566 /*
1567 * In case the user has asked us to run tasks explicitly we only use
1568 * those specified tasks. Specifically, we do _not_ want to consult the
1569 * config or maintenance strategy.
1570 */
1571 if (selected_tasks->nr) {
1572 for (size_t i = 0; i < selected_tasks->nr; i++) {
1573 enum maintenance_task_label label = (intptr_t)selected_tasks->items[i].util;;
1574 ALLOC_GROW(opts->tasks, opts->tasks_nr + 1, opts->tasks_alloc);
1575 opts->tasks[opts->tasks_nr++] = label;
1576 }
1577
1578 return;
1579 }
1580
1581 /*
1582 * Otherwise, the strategy depends on whether we run as part of a
1583 * scheduled job or not:
1584 *
1585 * - Scheduled maintenance does not perform any housekeeping by
1586 * default, but requires the user to pick a maintenance strategy.
1587 *
1588 * - Unscheduled maintenance uses our default strategy.
1589 *
1590 * Both of these are affected by the gitconfig though, which may
1591 * override specific aspects of our strategy. Furthermore, both
1592 * strategies can be overridden by setting "maintenance.strategy".
1593 */
1594 if (opts->schedule) {
1595 strategy = none_strategy;
1596 type = MAINTENANCE_TYPE_SCHEDULED;
1597 } else {
1598 strategy = geometric_strategy;
1599 type = MAINTENANCE_TYPE_MANUAL;
1600 }
1601
1602 if (!repo_config_get_string_tmp(the_repository, "maintenance.strategy", &config_str))
1603 strategy = parse_maintenance_strategy(config_str);
1604
1605 for (size_t i = 0; i < TASK__COUNT; i++) {
1606 int config_value;
1607
1608 strbuf_reset(&config_name);
1609 strbuf_addf(&config_name, "maintenance.%s.enabled",
1610 tasks[i].name);
1611 if (!repo_config_get_bool(the_repository, config_name.buf, &config_value))
1612 strategy.tasks[i].type = config_value ? type : 0;
1613 if (!(strategy.tasks[i].type & type))
1614 continue;
1615
1616 if (opts->schedule) {
1617 strbuf_reset(&config_name);
1618 strbuf_addf(&config_name, "maintenance.%s.schedule",
1619 tasks[i].name);
1620 if (!repo_config_get_string_tmp(the_repository, config_name.buf, &config_str))
1621 strategy.tasks[i].schedule = parse_schedule(config_str);
1622 if (strategy.tasks[i].schedule < opts->schedule)
1623 continue;
1624 }
1625
1626 ALLOC_GROW(opts->tasks, opts->tasks_nr + 1, opts->tasks_alloc);
1627 opts->tasks[opts->tasks_nr++] = i;
1628 }
1629
1630 strbuf_release(&config_name);
1631 }
1632
1633 static int task_option_parse(const struct option *opt,
1634 const char *arg, int unset)
1635 {
1636 struct string_list *selected_tasks = opt->value;
1637 size_t i;
1638
1639 BUG_ON_OPT_NEG(unset);
1640
1641 for (i = 0; i < TASK__COUNT; i++)
1642 if (!strcasecmp(tasks[i].name, arg))
1643 break;
1644 if (i >= TASK__COUNT) {
1645 error(_("'%s' is not a valid task"), arg);
1646 return 1;
1647 }
1648
1649 if (unsorted_string_list_has_string(selected_tasks, arg)) {
1650 error(_("task '%s' cannot be selected multiple times"), arg);
1651 return 1;
1652 }
1653
1654 string_list_append(selected_tasks, arg)->util = (void *)(intptr_t)i;
1655
1656 return 0;
1657 }
1658
1659 static int maintenance_run(int argc, const char **argv, const char *prefix,
1660 struct repository *repo UNUSED)
1661 {
1662 struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
1663 struct string_list selected_tasks = STRING_LIST_INIT_DUP;
1664 struct gc_config cfg = GC_CONFIG_INIT;
1665 struct option builtin_maintenance_run_options[] = {
1666 OPT_BOOL(0, "auto", &opts.auto_flag,
1667 N_("run tasks based on the state of the repository")),
1668 OPT_BOOL(0, "detach", &opts.detach,
1669 N_("perform maintenance in the background")),
1670 OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"),
1671 N_("run tasks based on frequency"),
1672 maintenance_opt_schedule),
1673 OPT_BOOL(0, "quiet", &opts.quiet,
1674 N_("do not report progress or other information over stderr")),
1675 OPT_CALLBACK_F(0, "task", &selected_tasks, N_("task"),
1676 N_("run a specific task"),
1677 PARSE_OPT_NONEG, task_option_parse),
1678 OPT_END()
1679 };
1680 int ret;
1681
1682 opts.quiet = !isatty(2);
1683
1684 argc = parse_options(argc, argv, prefix,
1685 builtin_maintenance_run_options,
1686 builtin_maintenance_run_usage,
1687 PARSE_OPT_STOP_AT_NON_OPTION);
1688
1689 die_for_incompatible_opt2(opts.auto_flag, "--auto",
1690 opts.schedule, "--schedule=");
1691 die_for_incompatible_opt2(selected_tasks.nr, "--task=",
1692 opts.schedule, "--schedule=");
1693
1694 gc_config(&cfg);
1695 initialize_task_config(&opts, &selected_tasks);
1696
1697 if (argc != 0)
1698 usage_with_options(builtin_maintenance_run_usage,
1699 builtin_maintenance_run_options);
1700
1701 ret = maintenance_run_tasks(&opts, &cfg);
1702
1703 string_list_clear(&selected_tasks, 0);
1704 maintenance_run_opts_release(&opts);
1705 gc_config_release(&cfg);
1706 return ret;
1707 }
1708
1709 static char *get_maintpath(void)
1710 {
1711 struct strbuf sb = STRBUF_INIT;
1712 const char *p = the_repository->worktree ?
1713 the_repository->worktree : the_repository->gitdir;
1714
1715 strbuf_realpath(&sb, p, 1);
1716 return strbuf_detach(&sb, NULL);
1717 }
1718
1719 static char const * const builtin_maintenance_register_usage[] = {
1720 "git maintenance register [--config-file <path>]",
1721 NULL
1722 };
1723
1724 static int maintenance_register(int argc, const char **argv, const char *prefix,
1725 struct repository *repo UNUSED)
1726 {
1727 char *config_file = NULL;
1728 struct option options[] = {
1729 OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1730 OPT_END(),
1731 };
1732 int found = 0;
1733 const char *key = "maintenance.repo";
1734 char *maintpath = get_maintpath();
1735 struct string_list_item *item;
1736 const struct string_list *list;
1737
1738 argc = parse_options(argc, argv, prefix, options,
1739 builtin_maintenance_register_usage, 0);
1740 if (argc)
1741 usage_with_options(builtin_maintenance_register_usage,
1742 options);
1743
1744 /* Disable foreground maintenance */
1745 repo_config_set(the_repository, "maintenance.auto", "false");
1746
1747 /* Set maintenance strategy, if unset */
1748 if (repo_config_get(the_repository, "maintenance.strategy"))
1749 repo_config_set(the_repository, "maintenance.strategy", "incremental");
1750
1751 if (!repo_config_get_string_multi(the_repository, key, &list)) {
1752 for_each_string_list_item(item, list) {
1753 if (!strcmp(maintpath, item->string)) {
1754 found = 1;
1755 break;
1756 }
1757 }
1758 }
1759
1760 if (!found) {
1761 int rc;
1762 char *global_config_file = NULL;
1763
1764 if (!config_file) {
1765 global_config_file = git_global_config();
1766 config_file = global_config_file;
1767 }
1768 if (!config_file)
1769 die(_("$HOME not set"));
1770 rc = repo_config_set_multivar_in_file_gently(the_repository,
1771 config_file, "maintenance.repo", maintpath,
1772 CONFIG_REGEX_NONE, NULL, 0);
1773 free(global_config_file);
1774
1775 if (rc)
1776 die(_("unable to add '%s' value of '%s'"),
1777 key, maintpath);
1778 }
1779
1780 free(maintpath);
1781 return 0;
1782 }
1783
1784 static char const * const builtin_maintenance_unregister_usage[] = {
1785 "git maintenance unregister [--config-file <path>] [--force]",
1786 NULL
1787 };
1788
1789 static int maintenance_unregister(int argc, const char **argv, const char *prefix,
1790 struct repository *repo UNUSED)
1791 {
1792 int force = 0;
1793 char *config_file = NULL;
1794 struct option options[] = {
1795 OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1796 OPT__FORCE(&force,
1797 N_("return success even if repository was not registered"),
1798 PARSE_OPT_NOCOMPLETE),
1799 OPT_END(),
1800 };
1801 const char *key = "maintenance.repo";
1802 char *maintpath = get_maintpath();
1803 int found = 0;
1804 struct string_list_item *item;
1805 const struct string_list *list;
1806 struct config_set cs = { { 0 } };
1807
1808 argc = parse_options(argc, argv, prefix, options,
1809 builtin_maintenance_unregister_usage, 0);
1810 if (argc)
1811 usage_with_options(builtin_maintenance_unregister_usage,
1812 options);
1813
1814 if (config_file) {
1815 git_configset_init(&cs);
1816 git_configset_add_file(&cs, config_file);
1817 }
1818 if (!(config_file
1819 ? git_configset_get_string_multi(&cs, key, &list)
1820 : repo_config_get_string_multi(the_repository, key, &list))) {
1821 for_each_string_list_item(item, list) {
1822 if (!strcmp(maintpath, item->string)) {
1823 found = 1;
1824 break;
1825 }
1826 }
1827 }
1828
1829 if (found) {
1830 int rc;
1831 char *global_config_file = NULL;
1832
1833 if (!config_file) {
1834 global_config_file = git_global_config();
1835 config_file = global_config_file;
1836 }
1837 if (!config_file)
1838 die(_("$HOME not set"));
1839 rc = repo_config_set_multivar_in_file_gently(the_repository,
1840 config_file, key, NULL, maintpath, NULL,
1841 CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
1842 free(global_config_file);
1843
1844 if (rc &&
1845 (!force || rc == CONFIG_NOTHING_SET))
1846 die(_("unable to unset '%s' value of '%s'"),
1847 key, maintpath);
1848 } else if (!force) {
1849 die(_("repository '%s' is not registered"), maintpath);
1850 }
1851
1852 git_configset_clear(&cs);
1853 free(maintpath);
1854 return 0;
1855 }
1856
1857 static const char *get_frequency(enum schedule_priority schedule)
1858 {
1859 switch (schedule) {
1860 case SCHEDULE_HOURLY:
1861 return "hourly";
1862 case SCHEDULE_DAILY:
1863 return "daily";
1864 case SCHEDULE_WEEKLY:
1865 return "weekly";
1866 default:
1867 BUG("invalid schedule %d", schedule);
1868 }
1869 }
1870
1871 static const char *extraconfig[] = {
1872 "credential.interactive=false",
1873 "core.askPass=true", /* 'true' returns success, but no output. */
1874 NULL
1875 };
1876
1877 static const char *get_extra_config_parameters(void) {
1878 static const char *result = NULL;
1879 struct strbuf builder = STRBUF_INIT;
1880
1881 if (result)
1882 return result;
1883
1884 for (const char **s = extraconfig; s && *s; s++)
1885 strbuf_addf(&builder, "-c %s ", *s);
1886
1887 result = strbuf_detach(&builder, NULL);
1888 return result;
1889 }
1890
1891 static const char *get_extra_launchctl_strings(void) {
1892 static const char *result = NULL;
1893 struct strbuf builder = STRBUF_INIT;
1894
1895 if (result)
1896 return result;
1897
1898 for (const char **s = extraconfig; s && *s; s++) {
1899 strbuf_addstr(&builder, "<string>-c</string>\n");
1900 strbuf_addf(&builder, "<string>%s</string>\n", *s);
1901 }
1902
1903 result = strbuf_detach(&builder, NULL);
1904 return result;
1905 }
1906
1907 /*
1908 * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
1909 * to mock the schedulers that `git maintenance start` rely on.
1910 *
1911 * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
1912 * list of colon-separated key/value pairs where each pair contains a scheduler
1913 * and its corresponding mock.
1914 *
1915 * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
1916 * arguments unmodified.
1917 *
1918 * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
1919 * In this case, the *cmd value is read as input.
1920 *
1921 * * if the input value cmd is the key of one of the comma-separated list
1922 * item, then *is_available is set to true and *out is set to
1923 * the mock command.
1924 *
1925 * * if the input value *cmd isn’t the key of any of the comma-separated list
1926 * item, then *is_available is set to false and *out is set to the original
1927 * command.
1928 *
1929 * Ex.:
1930 * GIT_TEST_MAINT_SCHEDULER not set
1931 * +-------+-------------------------------------------------+
1932 * | Input | Output |
1933 * | *cmd | return code | *out | *is_available |
1934 * +-------+-------------+-------------------+---------------+
1935 * | "foo" | false | "foo" (allocated) | (unchanged) |
1936 * +-------+-------------+-------------------+---------------+
1937 *
1938 * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
1939 * +-------+-------------------------------------------------+
1940 * | Input | Output |
1941 * | *cmd | return code | *out | *is_available |
1942 * +-------+-------------+-------------------+---------------+
1943 * | "foo" | true | "./mock.foo.sh" | true |
1944 * | "qux" | true | "qux" (allocated) | false |
1945 * +-------+-------------+-------------------+---------------+
1946 */
1947 static int get_schedule_cmd(const char *cmd, int *is_available, char **out)
1948 {
1949 char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
1950 struct string_list_item *item;
1951 struct string_list list = STRING_LIST_INIT_NODUP;
1952
1953 if (!testing) {
1954 if (out)
1955 *out = xstrdup(cmd);
1956 return 0;
1957 }
1958
1959 if (is_available)
1960 *is_available = 0;
1961
1962 string_list_split_in_place(&list, testing, ",", -1);
1963 for_each_string_list_item(item, &list) {
1964 struct string_list pair = STRING_LIST_INIT_NODUP;
1965
1966 if (string_list_split_in_place(&pair, item->string, ":", 2) != 2)
1967 continue;
1968
1969 if (!strcmp(cmd, pair.items[0].string)) {
1970 if (out)
1971 *out = xstrdup(pair.items[1].string);
1972 if (is_available)
1973 *is_available = 1;
1974 string_list_clear(&pair, 0);
1975 goto out;
1976 }
1977
1978 string_list_clear(&pair, 0);
1979 }
1980
1981 if (out)
1982 *out = xstrdup(cmd);
1983
1984 out:
1985 string_list_clear(&list, 0);
1986 free(testing);
1987 return 1;
1988 }
1989
1990 static int get_random_minute(void)
1991 {
1992 /* Use a static value when under tests. */
1993 if (getenv("GIT_TEST_MAINT_SCHEDULER"))
1994 return 13;
1995
1996 return git_rand(0) % 60;
1997 }
1998
1999 static int is_launchctl_available(void)
2000 {
2001 int is_available;
2002 if (get_schedule_cmd("launchctl", &is_available, NULL))
2003 return is_available;
2004
2005 #ifdef __APPLE__
2006 return 1;
2007 #else
2008 return 0;
2009 #endif
2010 }
2011
2012 static char *launchctl_service_name(const char *frequency)
2013 {
2014 struct strbuf label = STRBUF_INIT;
2015 strbuf_addf(&label, "org.git-scm.git.%s", frequency);
2016 return strbuf_detach(&label, NULL);
2017 }
2018
2019 static char *launchctl_service_filename(const char *name)
2020 {
2021 char *expanded;
2022 struct strbuf filename = STRBUF_INIT;
2023 strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
2024
2025 expanded = interpolate_path(filename.buf, 1);
2026 if (!expanded)
2027 die(_("failed to expand path '%s'"), filename.buf);
2028
2029 strbuf_release(&filename);
2030 return expanded;
2031 }
2032
2033 static char *launchctl_get_uid(void)
2034 {
2035 return xstrfmt("gui/%d", getuid());
2036 }
2037
2038 static int launchctl_boot_plist(int enable, const char *filename)
2039 {
2040 char *cmd;
2041 int result;
2042 struct child_process child = CHILD_PROCESS_INIT;
2043 char *uid = launchctl_get_uid();
2044
2045 get_schedule_cmd("launchctl", NULL, &cmd);
2046 strvec_split(&child.args, cmd);
2047 strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
2048 filename, NULL);
2049
2050 child.no_stderr = 1;
2051 child.no_stdout = 1;
2052
2053 if (start_command(&child))
2054 die(_("failed to start launchctl"));
2055
2056 result = finish_command(&child);
2057
2058 free(cmd);
2059 free(uid);
2060 return result;
2061 }
2062
2063 static int launchctl_remove_plist(enum schedule_priority schedule)
2064 {
2065 const char *frequency = get_frequency(schedule);
2066 char *name = launchctl_service_name(frequency);
2067 char *filename = launchctl_service_filename(name);
2068 int result = launchctl_boot_plist(0, filename);
2069 unlink(filename);
2070 free(filename);
2071 free(name);
2072 return result;
2073 }
2074
2075 static int launchctl_remove_plists(void)
2076 {
2077 return launchctl_remove_plist(SCHEDULE_HOURLY) ||
2078 launchctl_remove_plist(SCHEDULE_DAILY) ||
2079 launchctl_remove_plist(SCHEDULE_WEEKLY);
2080 }
2081
2082 static int launchctl_list_contains_plist(const char *name, const char *cmd)
2083 {
2084 struct child_process child = CHILD_PROCESS_INIT;
2085
2086 strvec_split(&child.args, cmd);
2087 strvec_pushl(&child.args, "list", name, NULL);
2088
2089 child.no_stderr = 1;
2090 child.no_stdout = 1;
2091
2092 if (start_command(&child))
2093 die(_("failed to start launchctl"));
2094
2095 /* Returns failure if 'name' doesn't exist. */
2096 return !finish_command(&child);
2097 }
2098
2099 static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
2100 {
2101 int i, fd;
2102 const char *preamble, *repeat;
2103 const char *frequency = get_frequency(schedule);
2104 char *name = launchctl_service_name(frequency);
2105 char *filename = launchctl_service_filename(name);
2106 struct lock_file lk = LOCK_INIT;
2107 static unsigned long lock_file_timeout_ms = ULONG_MAX;
2108 struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
2109 struct stat st;
2110 char *cmd;
2111 int minute = get_random_minute();
2112
2113 get_schedule_cmd("launchctl", NULL, &cmd);
2114 preamble = "<?xml version=\"1.0\"?>\n"
2115 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
2116 "<plist version=\"1.0\">"
2117 "<dict>\n"
2118 "<key>Label</key><string>%s</string>\n"
2119 "<key>ProgramArguments</key>\n"
2120 "<array>\n"
2121 "<string>%s/git</string>\n"
2122 "<string>--exec-path=%s</string>\n"
2123 "%s" /* For extra config parameters. */
2124 "<string>for-each-repo</string>\n"
2125 "<string>--keep-going</string>\n"
2126 "<string>--config=maintenance.repo</string>\n"
2127 "<string>maintenance</string>\n"
2128 "<string>run</string>\n"
2129 "<string>--schedule=%s</string>\n"
2130 "</array>\n"
2131 "<key>StartCalendarInterval</key>\n"
2132 "<array>\n";
2133 strbuf_addf(&plist, preamble, name, exec_path, exec_path,
2134 get_extra_launchctl_strings(), frequency);
2135
2136 switch (schedule) {
2137 case SCHEDULE_HOURLY:
2138 repeat = "<dict>\n"
2139 "<key>Hour</key><integer>%d</integer>\n"
2140 "<key>Minute</key><integer>%d</integer>\n"
2141 "</dict>\n";
2142 for (i = 1; i <= 23; i++)
2143 strbuf_addf(&plist, repeat, i, minute);
2144 break;
2145
2146 case SCHEDULE_DAILY:
2147 repeat = "<dict>\n"
2148 "<key>Weekday</key><integer>%d</integer>\n"
2149 "<key>Hour</key><integer>0</integer>\n"
2150 "<key>Minute</key><integer>%d</integer>\n"
2151 "</dict>\n";
2152 for (i = 1; i <= 6; i++)
2153 strbuf_addf(&plist, repeat, i, minute);
2154 break;
2155
2156 case SCHEDULE_WEEKLY:
2157 strbuf_addf(&plist,
2158 "<dict>\n"
2159 "<key>Weekday</key><integer>0</integer>\n"
2160 "<key>Hour</key><integer>0</integer>\n"
2161 "<key>Minute</key><integer>%d</integer>\n"
2162 "</dict>\n",
2163 minute);
2164 break;
2165
2166 default:
2167 /* unreachable */
2168 break;
2169 }
2170 strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
2171
2172 if (safe_create_leading_directories(the_repository, filename))
2173 die(_("failed to create directories for '%s'"), filename);
2174
2175 if ((long)lock_file_timeout_ms < 0 &&
2176 repo_config_get_ulong(the_repository, "gc.launchctlplistlocktimeoutms",
2177 &lock_file_timeout_ms))
2178 lock_file_timeout_ms = 150;
2179
2180 fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
2181 lock_file_timeout_ms);
2182
2183 /*
2184 * Does this file already exist? With the intended contents? Is it
2185 * registered already? Then it does not need to be re-registered.
2186 */
2187 if (!stat(filename, &st) && st.st_size == plist.len &&
2188 strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
2189 !strbuf_cmp(&plist, &plist2) &&
2190 launchctl_list_contains_plist(name, cmd))
2191 rollback_lock_file(&lk);
2192 else {
2193 if (write_in_full(fd, plist.buf, plist.len) < 0 ||
2194 commit_lock_file(&lk))
2195 die_errno(_("could not write '%s'"), filename);
2196
2197 /* bootout might fail if not already running, so ignore */
2198 launchctl_boot_plist(0, filename);
2199 if (launchctl_boot_plist(1, filename))
2200 die(_("failed to bootstrap service %s"), filename);
2201 }
2202
2203 free(filename);
2204 free(name);
2205 free(cmd);
2206 strbuf_release(&plist);
2207 strbuf_release(&plist2);
2208 return 0;
2209 }
2210
2211 static int launchctl_add_plists(void)
2212 {
2213 const char *exec_path = git_exec_path();
2214
2215 return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
2216 launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
2217 launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
2218 }
2219
2220 static int launchctl_update_schedule(int run_maintenance, int fd UNUSED)
2221 {
2222 if (run_maintenance)
2223 return launchctl_add_plists();
2224 else
2225 return launchctl_remove_plists();
2226 }
2227
2228 static int is_schtasks_available(void)
2229 {
2230 int is_available;
2231 if (get_schedule_cmd("schtasks", &is_available, NULL))
2232 return is_available;
2233
2234 #ifdef GIT_WINDOWS_NATIVE
2235 return 1;
2236 #else
2237 return 0;
2238 #endif
2239 }
2240
2241 static char *schtasks_task_name(const char *frequency)
2242 {
2243 struct strbuf label = STRBUF_INIT;
2244 strbuf_addf(&label, "Git Maintenance (%s)", frequency);
2245 return strbuf_detach(&label, NULL);
2246 }
2247
2248 static int schtasks_remove_task(enum schedule_priority schedule)
2249 {
2250 char *cmd;
2251 struct child_process child = CHILD_PROCESS_INIT;
2252 const char *frequency = get_frequency(schedule);
2253 char *name = schtasks_task_name(frequency);
2254
2255 get_schedule_cmd("schtasks", NULL, &cmd);
2256 strvec_split(&child.args, cmd);
2257 strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
2258 free(name);
2259 free(cmd);
2260
2261 return run_command(&child);
2262 }
2263
2264 static int schtasks_remove_tasks(void)
2265 {
2266 return schtasks_remove_task(SCHEDULE_HOURLY) ||
2267 schtasks_remove_task(SCHEDULE_DAILY) ||
2268 schtasks_remove_task(SCHEDULE_WEEKLY);
2269 }
2270
2271 static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
2272 {
2273 char *cmd;
2274 int result;
2275 struct child_process child = CHILD_PROCESS_INIT;
2276 const char *xml;
2277 struct tempfile *tfile;
2278 const char *frequency = get_frequency(schedule);
2279 char *name = schtasks_task_name(frequency);
2280 struct strbuf tfilename = STRBUF_INIT;
2281 int minute = get_random_minute();
2282
2283 get_schedule_cmd("schtasks", NULL, &cmd);
2284
2285 strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
2286 repo_get_common_dir(the_repository), frequency);
2287 tfile = xmks_tempfile(tfilename.buf);
2288 strbuf_release(&tfilename);
2289
2290 if (!fdopen_tempfile(tfile, "w"))
2291 die(_("failed to create temp xml file"));
2292
2293 xml = "<?xml version=\"1.0\" ?>\n"
2294 "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
2295 "<Triggers>\n"
2296 "<CalendarTrigger>\n";
2297 fputs(xml, tfile->fp);
2298
2299 switch (schedule) {
2300 case SCHEDULE_HOURLY:
2301 fprintf(tfile->fp,
2302 "<StartBoundary>2020-01-01T01:%02d:00</StartBoundary>\n"
2303 "<Enabled>true</Enabled>\n"
2304 "<ScheduleByDay>\n"
2305 "<DaysInterval>1</DaysInterval>\n"
2306 "</ScheduleByDay>\n"
2307 "<Repetition>\n"
2308 "<Interval>PT1H</Interval>\n"
2309 "<Duration>PT23H</Duration>\n"
2310 "<StopAtDurationEnd>false</StopAtDurationEnd>\n"
2311 "</Repetition>\n",
2312 minute);
2313 break;
2314
2315 case SCHEDULE_DAILY:
2316 fprintf(tfile->fp,
2317 "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2318 "<Enabled>true</Enabled>\n"
2319 "<ScheduleByWeek>\n"
2320 "<DaysOfWeek>\n"
2321 "<Monday />\n"
2322 "<Tuesday />\n"
2323 "<Wednesday />\n"
2324 "<Thursday />\n"
2325 "<Friday />\n"
2326 "<Saturday />\n"
2327 "</DaysOfWeek>\n"
2328 "<WeeksInterval>1</WeeksInterval>\n"
2329 "</ScheduleByWeek>\n",
2330 minute);
2331 break;
2332
2333 case SCHEDULE_WEEKLY:
2334 fprintf(tfile->fp,
2335 "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2336 "<Enabled>true</Enabled>\n"
2337 "<ScheduleByWeek>\n"
2338 "<DaysOfWeek>\n"
2339 "<Sunday />\n"
2340 "</DaysOfWeek>\n"
2341 "<WeeksInterval>1</WeeksInterval>\n"
2342 "</ScheduleByWeek>\n",
2343 minute);
2344 break;
2345
2346 default:
2347 break;
2348 }
2349
2350 xml = "</CalendarTrigger>\n"
2351 "</Triggers>\n"
2352 "<Principals>\n"
2353 "<Principal id=\"Author\">\n"
2354 "<LogonType>InteractiveToken</LogonType>\n"
2355 "<RunLevel>LeastPrivilege</RunLevel>\n"
2356 "</Principal>\n"
2357 "</Principals>\n"
2358 "<Settings>\n"
2359 "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
2360 "<Enabled>true</Enabled>\n"
2361 "<Hidden>true</Hidden>\n"
2362 "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n"
2363 "<WakeToRun>false</WakeToRun>\n"
2364 "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
2365 "<Priority>7</Priority>\n"
2366 "</Settings>\n"
2367 "<Actions Context=\"Author\">\n"
2368 "<Exec>\n"
2369 "<Command>\"%s\\headless-git.exe\"</Command>\n"
2370 "<Arguments>--exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
2371 "</Exec>\n"
2372 "</Actions>\n"
2373 "</Task>\n";
2374 fprintf(tfile->fp, xml, exec_path, exec_path,
2375 get_extra_config_parameters(), frequency);
2376 strvec_split(&child.args, cmd);
2377 strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
2378 get_tempfile_path(tfile), NULL);
2379 close_tempfile_gently(tfile);
2380
2381 child.no_stdout = 1;
2382 child.no_stderr = 1;
2383
2384 if (start_command(&child))
2385 die(_("failed to start schtasks"));
2386 result = finish_command(&child);
2387
2388 delete_tempfile(&tfile);
2389 free(name);
2390 free(cmd);
2391 return result;
2392 }
2393
2394 static int schtasks_schedule_tasks(void)
2395 {
2396 const char *exec_path = git_exec_path();
2397
2398 return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
2399 schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
2400 schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
2401 }
2402
2403 static int schtasks_update_schedule(int run_maintenance, int fd UNUSED)
2404 {
2405 if (run_maintenance)
2406 return schtasks_schedule_tasks();
2407 else
2408 return schtasks_remove_tasks();
2409 }
2410
2411 MAYBE_UNUSED
2412 static int check_crontab_process(const char *cmd)
2413 {
2414 struct child_process child = CHILD_PROCESS_INIT;
2415
2416 strvec_split(&child.args, cmd);
2417 strvec_push(&child.args, "-l");
2418 child.no_stdin = 1;
2419 child.no_stdout = 1;
2420 child.no_stderr = 1;
2421 child.silent_exec_failure = 1;
2422
2423 if (start_command(&child))
2424 return 0;
2425 /* Ignore exit code, as an empty crontab will return error. */
2426 finish_command(&child);
2427 return 1;
2428 }
2429
2430 static int is_crontab_available(void)
2431 {
2432 char *cmd;
2433 int is_available;
2434 int ret;
2435
2436 if (get_schedule_cmd("crontab", &is_available, &cmd)) {
2437 ret = is_available;
2438 goto out;
2439 }
2440
2441 #ifdef __APPLE__
2442 /*
2443 * macOS has cron, but it requires special permissions and will
2444 * create a UI alert when attempting to run this command.
2445 */
2446 ret = 0;
2447 #else
2448 ret = check_crontab_process(cmd);
2449 #endif
2450
2451 out:
2452 free(cmd);
2453 return ret;
2454 }
2455
2456 #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
2457 #define END_LINE "# END GIT MAINTENANCE SCHEDULE"
2458
2459 static int crontab_update_schedule(int run_maintenance, int fd)
2460 {
2461 char *cmd;
2462 int result = 0;
2463 int in_old_region = 0;
2464 struct child_process crontab_list = CHILD_PROCESS_INIT;
2465 struct child_process crontab_edit = CHILD_PROCESS_INIT;
2466 FILE *cron_list, *cron_in;
2467 struct strbuf line = STRBUF_INIT;
2468 struct tempfile *tmpedit = NULL;
2469 int minute = get_random_minute();
2470
2471 get_schedule_cmd("crontab", NULL, &cmd);
2472 strvec_split(&crontab_list.args, cmd);
2473 strvec_push(&crontab_list.args, "-l");
2474 crontab_list.in = -1;
2475 crontab_list.out = dup(fd);
2476 crontab_list.git_cmd = 0;
2477
2478 if (start_command(&crontab_list)) {
2479 result = error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
2480 goto out;
2481 }
2482
2483 /* Ignore exit code, as an empty crontab will return error. */
2484 finish_command(&crontab_list);
2485
2486 tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX");
2487 if (!tmpedit) {
2488 result = error(_("failed to create crontab temporary file"));
2489 goto out;
2490 }
2491 cron_in = fdopen_tempfile(tmpedit, "w");
2492 if (!cron_in) {
2493 result = error(_("failed to open temporary file"));
2494 goto out;
2495 }
2496
2497 /*
2498 * Read from the .lock file, filtering out the old
2499 * schedule while appending the new schedule.
2500 */
2501 cron_list = fdopen(fd, "r");
2502 rewind(cron_list);
2503
2504 while (!strbuf_getline_lf(&line, cron_list)) {
2505 if (!in_old_region && !strcmp(line.buf, BEGIN_LINE))
2506 in_old_region = 1;
2507 else if (in_old_region && !strcmp(line.buf, END_LINE))
2508 in_old_region = 0;
2509 else if (!in_old_region)
2510 fprintf(cron_in, "%s\n", line.buf);
2511 }
2512 strbuf_release(&line);
2513
2514 if (run_maintenance) {
2515 struct strbuf line_format = STRBUF_INIT;
2516 const char *exec_path = git_exec_path();
2517
2518 fprintf(cron_in, "%s\n", BEGIN_LINE);
2519 fprintf(cron_in,
2520 "# The following schedule was created by Git\n");
2521 fprintf(cron_in, "# Any edits made in this region might be\n");
2522 fprintf(cron_in,
2523 "# replaced in the future by a Git command.\n\n");
2524
2525 strbuf_addf(&line_format,
2526 "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
2527 exec_path, exec_path, get_extra_config_parameters());
2528 fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
2529 fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
2530 fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
2531 strbuf_release(&line_format);
2532
2533 fprintf(cron_in, "\n%s\n", END_LINE);
2534 }
2535
2536 fflush(cron_in);
2537
2538 strvec_split(&crontab_edit.args, cmd);
2539 strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit));
2540 crontab_edit.git_cmd = 0;
2541
2542 if (start_command(&crontab_edit)) {
2543 result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
2544 goto out;
2545 }
2546
2547 if (finish_command(&crontab_edit))
2548 result = error(_("'crontab' died"));
2549 else
2550 fclose(cron_list);
2551
2552 out:
2553 delete_tempfile(&tmpedit);
2554 free(cmd);
2555 return result;
2556 }
2557
2558 static int real_is_systemd_timer_available(void)
2559 {
2560 struct child_process child = CHILD_PROCESS_INIT;
2561
2562 strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
2563 child.no_stdin = 1;
2564 child.no_stdout = 1;
2565 child.no_stderr = 1;
2566 child.silent_exec_failure = 1;
2567
2568 if (start_command(&child))
2569 return 0;
2570 if (finish_command(&child))
2571 return 0;
2572 return 1;
2573 }
2574
2575 static int is_systemd_timer_available(void)
2576 {
2577 int is_available;
2578
2579 if (get_schedule_cmd("systemctl", &is_available, NULL))
2580 return is_available;
2581
2582 return real_is_systemd_timer_available();
2583 }
2584
2585 static char *xdg_config_home_systemd(const char *filename)
2586 {
2587 return xdg_config_home_for("systemd/user", filename);
2588 }
2589
2590 #define SYSTEMD_UNIT_FORMAT "git-maintenance@%s.%s"
2591
2592 static int systemd_timer_delete_timer_file(enum schedule_priority priority)
2593 {
2594 int ret = 0;
2595 const char *frequency = get_frequency(priority);
2596 char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2597 char *filename = xdg_config_home_systemd(local_timer_name);
2598
2599 if (unlink(filename) && !is_missing_file_error(errno))
2600 ret = error_errno(_("failed to delete '%s'"), filename);
2601
2602 free(filename);
2603 free(local_timer_name);
2604 return ret;
2605 }
2606
2607 static int systemd_timer_delete_service_template(void)
2608 {
2609 int ret = 0;
2610 char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2611 char *filename = xdg_config_home_systemd(local_service_name);
2612 if (unlink(filename) && !is_missing_file_error(errno))
2613 ret = error_errno(_("failed to delete '%s'"), filename);
2614
2615 free(filename);
2616 free(local_service_name);
2617 return ret;
2618 }
2619
2620 /*
2621 * Write the schedule information into a git-maintenance@<schedule>.timer
2622 * file using a custom minute. This timer file cannot use the templating
2623 * system, so we generate a specific file for each.
2624 */
2625 static int systemd_timer_write_timer_file(enum schedule_priority schedule,
2626 int minute)
2627 {
2628 int res = -1;
2629 char *filename;
2630 FILE *file;
2631 const char *unit;
2632 char *schedule_pattern = NULL;
2633 const char *frequency = get_frequency(schedule);
2634 char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2635
2636 filename = xdg_config_home_systemd(local_timer_name);
2637
2638 if (safe_create_leading_directories(the_repository, filename)) {
2639 error(_("failed to create directories for '%s'"), filename);
2640 goto error;
2641 }
2642 file = fopen_or_warn(filename, "w");
2643 if (!file)
2644 goto error;
2645
2646 switch (schedule) {
2647 case SCHEDULE_HOURLY:
2648 schedule_pattern = xstrfmt("*-*-* 1..23:%02d:00", minute);
2649 break;
2650
2651 case SCHEDULE_DAILY:
2652 schedule_pattern = xstrfmt("Tue..Sun *-*-* 0:%02d:00", minute);
2653 break;
2654
2655 case SCHEDULE_WEEKLY:
2656 schedule_pattern = xstrfmt("Mon 0:%02d:00", minute);
2657 break;
2658
2659 default:
2660 BUG("Unhandled schedule_priority");
2661 }
2662
2663 unit = "# This file was created and is maintained by Git.\n"
2664 "# Any edits made in this file might be replaced in the future\n"
2665 "# by a Git command.\n"
2666 "\n"
2667 "[Unit]\n"
2668 "Description=Optimize Git repositories data\n"
2669 "\n"
2670 "[Timer]\n"
2671 "OnCalendar=%s\n"
2672 "Persistent=true\n"
2673 "\n"
2674 "[Install]\n"
2675 "WantedBy=timers.target\n";
2676 if (fprintf(file, unit, schedule_pattern) < 0) {
2677 error(_("failed to write to '%s'"), filename);
2678 fclose(file);
2679 goto error;
2680 }
2681 if (fclose(file) == EOF) {
2682 error_errno(_("failed to flush '%s'"), filename);
2683 goto error;
2684 }
2685
2686 res = 0;
2687
2688 error:
2689 free(schedule_pattern);
2690 free(local_timer_name);
2691 free(filename);
2692 return res;
2693 }
2694
2695 /*
2696 * No matter the schedule, we use the same service and can make use of the
2697 * templating system. When installing git-maintenance@<schedule>.timer,
2698 * systemd will notice that git-maintenance@.service exists as a template
2699 * and will use this file and insert the <schedule> into the template at
2700 * the position of "%i".
2701 */
2702 static int systemd_timer_write_service_template(const char *exec_path)
2703 {
2704 int res = -1;
2705 char *filename;
2706 FILE *file;
2707 const char *unit;
2708 char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2709
2710 filename = xdg_config_home_systemd(local_service_name);
2711 if (safe_create_leading_directories(the_repository, filename)) {
2712 error(_("failed to create directories for '%s'"), filename);
2713 goto error;
2714 }
2715 file = fopen_or_warn(filename, "w");
2716 if (!file)
2717 goto error;
2718
2719 unit = "# This file was created and is maintained by Git.\n"
2720 "# Any edits made in this file might be replaced in the future\n"
2721 "# by a Git command.\n"
2722 "\n"
2723 "[Unit]\n"
2724 "Description=Optimize Git repositories data\n"
2725 "\n"
2726 "[Service]\n"
2727 "Type=oneshot\n"
2728 "ExecStart=\"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
2729 "LockPersonality=yes\n"
2730 "MemoryDenyWriteExecute=yes\n"
2731 "NoNewPrivileges=yes\n"
2732 "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_VSOCK\n"
2733 "RestrictNamespaces=yes\n"
2734 "RestrictRealtime=yes\n"
2735 "RestrictSUIDSGID=yes\n"
2736 "SystemCallArchitectures=native\n"
2737 "SystemCallFilter=@system-service\n";
2738 if (fprintf(file, unit, exec_path, exec_path, get_extra_config_parameters()) < 0) {
2739 error(_("failed to write to '%s'"), filename);
2740 fclose(file);
2741 goto error;
2742 }
2743 if (fclose(file) == EOF) {
2744 error_errno(_("failed to flush '%s'"), filename);
2745 goto error;
2746 }
2747
2748 res = 0;
2749
2750 error:
2751 free(local_service_name);
2752 free(filename);
2753 return res;
2754 }
2755
2756 static int systemd_timer_enable_unit(int enable,
2757 enum schedule_priority schedule,
2758 int minute)
2759 {
2760 char *cmd = NULL;
2761 struct child_process child = CHILD_PROCESS_INIT;
2762 const char *frequency = get_frequency(schedule);
2763 int ret;
2764
2765 /*
2766 * Disabling the systemd unit while it is already disabled makes
2767 * systemctl print an error.
2768 * Let's ignore it since it means we already are in the expected state:
2769 * the unit is disabled.
2770 *
2771 * On the other hand, enabling a systemd unit which is already enabled
2772 * produces no error.
2773 */
2774 if (!enable) {
2775 child.no_stderr = 1;
2776 } else if (systemd_timer_write_timer_file(schedule, minute)) {
2777 ret = -1;
2778 goto out;
2779 }
2780
2781 get_schedule_cmd("systemctl", NULL, &cmd);
2782 strvec_split(&child.args, cmd);
2783 strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
2784 "--now", NULL);
2785 strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer");
2786
2787 if (start_command(&child)) {
2788 ret = error(_("failed to start systemctl"));
2789 goto out;
2790 }
2791
2792 if (finish_command(&child)) {
2793 /*
2794 * Disabling an already disabled systemd unit makes
2795 * systemctl fail.
2796 * Let's ignore this failure.
2797 *
2798 * Enabling an enabled systemd unit doesn't fail.
2799 */
2800 if (enable) {
2801 ret = error(_("failed to run systemctl"));
2802 goto out;
2803 }
2804 }
2805
2806 ret = 0;
2807
2808 out:
2809 free(cmd);
2810 return ret;
2811 }
2812
2813 /*
2814 * A previous version of Git wrote the timer units as template files.
2815 * Clean these up, if they exist.
2816 */
2817 static void systemd_timer_delete_stale_timer_templates(void)
2818 {
2819 char *timer_template_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "timer");
2820 char *filename = xdg_config_home_systemd(timer_template_name);
2821
2822 if (unlink(filename) && !is_missing_file_error(errno))
2823 warning(_("failed to delete '%s'"), filename);
2824
2825 free(filename);
2826 free(timer_template_name);
2827 }
2828
2829 static int systemd_timer_delete_unit_files(void)
2830 {
2831 systemd_timer_delete_stale_timer_templates();
2832
2833 /* Purposefully not short-circuited to make sure all are called. */
2834 return systemd_timer_delete_timer_file(SCHEDULE_HOURLY) |
2835 systemd_timer_delete_timer_file(SCHEDULE_DAILY) |
2836 systemd_timer_delete_timer_file(SCHEDULE_WEEKLY) |
2837 systemd_timer_delete_service_template();
2838 }
2839
2840 static int systemd_timer_delete_units(void)
2841 {
2842 int minute = get_random_minute();
2843 /* Purposefully not short-circuited to make sure all are called. */
2844 return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, minute) |
2845 systemd_timer_enable_unit(0, SCHEDULE_DAILY, minute) |
2846 systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, minute) |
2847 systemd_timer_delete_unit_files();
2848 }
2849
2850 static int systemd_timer_setup_units(void)
2851 {
2852 int minute = get_random_minute();
2853 const char *exec_path = git_exec_path();
2854
2855 int ret = systemd_timer_write_service_template(exec_path) ||
2856 systemd_timer_enable_unit(1, SCHEDULE_HOURLY, minute) ||
2857 systemd_timer_enable_unit(1, SCHEDULE_DAILY, minute) ||
2858 systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, minute);
2859
2860 if (ret)
2861 systemd_timer_delete_units();
2862 else
2863 systemd_timer_delete_stale_timer_templates();
2864
2865 return ret;
2866 }
2867
2868 static int systemd_timer_update_schedule(int run_maintenance, int fd UNUSED)
2869 {
2870 if (run_maintenance)
2871 return systemd_timer_setup_units();
2872 else
2873 return systemd_timer_delete_units();
2874 }
2875
2876 enum scheduler {
2877 SCHEDULER_INVALID = -1,
2878 SCHEDULER_AUTO,
2879 SCHEDULER_CRON,
2880 SCHEDULER_SYSTEMD,
2881 SCHEDULER_LAUNCHCTL,
2882 SCHEDULER_SCHTASKS,
2883 };
2884
2885 static const struct {
2886 const char *name;
2887 int (*is_available)(void);
2888 int (*update_schedule)(int run_maintenance, int fd);
2889 } scheduler_fn[] = {
2890 [SCHEDULER_CRON] = {
2891 .name = "crontab",
2892 .is_available = is_crontab_available,
2893 .update_schedule = crontab_update_schedule,
2894 },
2895 [SCHEDULER_SYSTEMD] = {
2896 .name = "systemctl",
2897 .is_available = is_systemd_timer_available,
2898 .update_schedule = systemd_timer_update_schedule,
2899 },
2900 [SCHEDULER_LAUNCHCTL] = {
2901 .name = "launchctl",
2902 .is_available = is_launchctl_available,
2903 .update_schedule = launchctl_update_schedule,
2904 },
2905 [SCHEDULER_SCHTASKS] = {
2906 .name = "schtasks",
2907 .is_available = is_schtasks_available,
2908 .update_schedule = schtasks_update_schedule,
2909 },
2910 };
2911
2912 static enum scheduler parse_scheduler(const char *value)
2913 {
2914 if (!value)
2915 return SCHEDULER_INVALID;
2916 else if (!strcasecmp(value, "auto"))
2917 return SCHEDULER_AUTO;
2918 else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
2919 return SCHEDULER_CRON;
2920 else if (!strcasecmp(value, "systemd") ||
2921 !strcasecmp(value, "systemd-timer"))
2922 return SCHEDULER_SYSTEMD;
2923 else if (!strcasecmp(value, "launchctl"))
2924 return SCHEDULER_LAUNCHCTL;
2925 else if (!strcasecmp(value, "schtasks"))
2926 return SCHEDULER_SCHTASKS;
2927 else
2928 return SCHEDULER_INVALID;
2929 }
2930
2931 static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
2932 int unset)
2933 {
2934 enum scheduler *scheduler = opt->value;
2935
2936 BUG_ON_OPT_NEG(unset);
2937
2938 *scheduler = parse_scheduler(arg);
2939 if (*scheduler == SCHEDULER_INVALID)
2940 return error(_("unrecognized --scheduler argument '%s'"), arg);
2941 return 0;
2942 }
2943
2944 struct maintenance_start_opts {
2945 enum scheduler scheduler;
2946 };
2947
2948 static enum scheduler resolve_scheduler(enum scheduler scheduler)
2949 {
2950 if (scheduler != SCHEDULER_AUTO)
2951 return scheduler;
2952
2953 #if defined(__APPLE__)
2954 return SCHEDULER_LAUNCHCTL;
2955
2956 #elif defined(GIT_WINDOWS_NATIVE)
2957 return SCHEDULER_SCHTASKS;
2958
2959 #elif defined(__linux__)
2960 if (is_systemd_timer_available())
2961 return SCHEDULER_SYSTEMD;
2962 else if (is_crontab_available())
2963 return SCHEDULER_CRON;
2964 else
2965 die(_("neither systemd timers nor crontab are available"));
2966
2967 #else
2968 return SCHEDULER_CRON;
2969 #endif
2970 }
2971
2972 static void validate_scheduler(enum scheduler scheduler)
2973 {
2974 if (scheduler == SCHEDULER_INVALID)
2975 BUG("invalid scheduler");
2976 if (scheduler == SCHEDULER_AUTO)
2977 BUG("resolve_scheduler should have been called before");
2978
2979 if (!scheduler_fn[scheduler].is_available())
2980 die(_("%s scheduler is not available"),
2981 scheduler_fn[scheduler].name);
2982 }
2983
2984 static int update_background_schedule(const struct maintenance_start_opts *opts,
2985 int enable)
2986 {
2987 unsigned int i;
2988 int result = 0;
2989 struct lock_file lk;
2990 char *lock_path = xstrfmt("%s/schedule", the_repository->objects->sources->path);
2991
2992 if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
2993 if (errno == EEXIST)
2994 error(_("unable to create '%s.lock': %s.\n\n"
2995 "Another scheduled git-maintenance(1) process seems to be running in this\n"
2996 "repository. Please make sure no other maintenance processes are running and\n"
2997 "then try again. If it still fails, a git-maintenance(1) process may have\n"
2998 "crashed in this repository earlier: remove the file manually to continue."),
2999 absolute_path(lock_path), strerror(errno));
3000 else
3001 error_errno(_("cannot acquire lock for scheduled background maintenance"));
3002 free(lock_path);
3003 return -1;
3004 }
3005
3006 for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
3007 if (enable && opts->scheduler == i)
3008 continue;
3009 if (!scheduler_fn[i].is_available())
3010 continue;
3011 scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
3012 }
3013
3014 if (enable)
3015 result = scheduler_fn[opts->scheduler].update_schedule(
3016 1, get_lock_file_fd(&lk));
3017
3018 rollback_lock_file(&lk);
3019
3020 free(lock_path);
3021 return result;
3022 }
3023
3024 static const char *const builtin_maintenance_start_usage[] = {
3025 N_("git maintenance start [--scheduler=<scheduler>]"),
3026 NULL
3027 };
3028
3029 static int maintenance_start(int argc, const char **argv, const char *prefix,
3030 struct repository *repo)
3031 {
3032 struct maintenance_start_opts opts = { 0 };
3033 struct option options[] = {
3034 OPT_CALLBACK_F(
3035 0, "scheduler", &opts.scheduler, N_("scheduler"),
3036 N_("scheduler to trigger git maintenance run"),
3037 PARSE_OPT_NONEG, maintenance_opt_scheduler),
3038 OPT_END()
3039 };
3040 const char *register_args[] = { "register", NULL };
3041
3042 argc = parse_options(argc, argv, prefix, options,
3043 builtin_maintenance_start_usage, 0);
3044 if (argc)
3045 usage_with_options(builtin_maintenance_start_usage, options);
3046
3047 opts.scheduler = resolve_scheduler(opts.scheduler);
3048 validate_scheduler(opts.scheduler);
3049
3050 if (update_background_schedule(&opts, 1))
3051 die(_("failed to set up maintenance schedule"));
3052
3053 if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL, repo))
3054 warning(_("failed to add repo to global config"));
3055 return 0;
3056 }
3057
3058 static const char *const builtin_maintenance_stop_usage[] = {
3059 "git maintenance stop",
3060 NULL
3061 };
3062
3063 static int maintenance_stop(int argc, const char **argv, const char *prefix,
3064 struct repository *repo UNUSED)
3065 {
3066 struct option options[] = {
3067 OPT_END()
3068 };
3069 argc = parse_options(argc, argv, prefix, options,
3070 builtin_maintenance_stop_usage, 0);
3071 if (argc)
3072 usage_with_options(builtin_maintenance_stop_usage, options);
3073 return update_background_schedule(NULL, 0);
3074 }
3075
3076 static const char *const builtin_maintenance_is_needed_usage[] = {
3077 "git maintenance is-needed [--task=<task>] [--schedule]",
3078 NULL
3079 };
3080
3081 static int maintenance_is_needed(int argc, const char **argv, const char *prefix,
3082 struct repository *repo UNUSED)
3083 {
3084 struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
3085 struct string_list selected_tasks = STRING_LIST_INIT_DUP;
3086 struct gc_config cfg = GC_CONFIG_INIT;
3087 struct option options[] = {
3088 OPT_BOOL(0, "auto", &opts.auto_flag,
3089 N_("run tasks based on the state of the repository")),
3090 OPT_CALLBACK_F(0, "task", &selected_tasks, N_("task"),
3091 N_("check a specific task"),
3092 PARSE_OPT_NONEG, task_option_parse),
3093 OPT_END()
3094 };
3095 bool is_needed = false;
3096
3097 argc = parse_options(argc, argv, prefix, options,
3098 builtin_maintenance_is_needed_usage,
3099 PARSE_OPT_STOP_AT_NON_OPTION);
3100 if (argc)
3101 usage_with_options(builtin_maintenance_is_needed_usage, options);
3102
3103 gc_config(&cfg);
3104 initialize_task_config(&opts, &selected_tasks);
3105
3106 if (opts.auto_flag) {
3107 for (size_t i = 0; i < opts.tasks_nr; i++) {
3108 if (tasks[opts.tasks[i]].auto_condition &&
3109 tasks[opts.tasks[i]].auto_condition(&cfg)) {
3110 is_needed = true;
3111 break;
3112 }
3113 }
3114 } else {
3115 /*
3116 * When not using --auto we always require maintenance right now.
3117 *
3118 * TODO: this certainly is too eager, as some maintenance tasks may
3119 * decide to not do anything because the data structures are already
3120 * fully optimized. We may eventually want to extend the auto
3121 * condition to also cover non-auto runs so that we can detect such
3122 * cases.
3123 */
3124 is_needed = true;
3125 }
3126
3127 string_list_clear(&selected_tasks, 0);
3128 maintenance_run_opts_release(&opts);
3129 gc_config_release(&cfg);
3130
3131 if (is_needed)
3132 return 0;
3133 return 1;
3134 }
3135
3136 static const char *const builtin_maintenance_usage[] = {
3137 N_("git maintenance <subcommand> [<options>]"),
3138 NULL,
3139 };
3140
3141 int cmd_maintenance(int argc,
3142 const char **argv,
3143 const char *prefix,
3144 struct repository *repo)
3145 {
3146 parse_opt_subcommand_fn *fn = NULL;
3147 struct option builtin_maintenance_options[] = {
3148 OPT_SUBCOMMAND("run", &fn, maintenance_run),
3149 OPT_SUBCOMMAND("start", &fn, maintenance_start),
3150 OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
3151 OPT_SUBCOMMAND("register", &fn, maintenance_register),
3152 OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
3153 OPT_SUBCOMMAND("is-needed", &fn, maintenance_is_needed),
3154 OPT_END(),
3155 };
3156
3157 argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
3158 builtin_maintenance_usage, 0);
3159 return fn(argc, argv, prefix, repo);
3160 }