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