diff options
Diffstat (limited to 'builtin/gc.c')
-rw-r--r-- | builtin/gc.c | 548 |
1 files changed, 472 insertions, 76 deletions
diff --git a/builtin/gc.c b/builtin/gc.c index 64f2b52d6e..f05d2f0a1a 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -54,7 +54,6 @@ static const char *prune_worktrees_expire = "3.months.ago"; static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; -static struct strvec pack_refs_cmd = STRVEC_INIT; static struct strvec reflog = STRVEC_INIT; static struct strvec repack = STRVEC_INIT; static struct strvec prune = STRVEC_INIT; @@ -163,6 +162,15 @@ static void gc_config(void) git_config(git_default_config, NULL); } +struct maintenance_run_opts; +static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) +{ + struct strvec pack_refs_cmd = STRVEC_INIT; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + + return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); +} + static int too_many_loose_objects(void) { /* @@ -301,7 +309,7 @@ static uint64_t estimate_repack_memory(struct packed_git *pack) /* and then obj_hash[], underestimated in fact */ heap += sizeof(struct object *) * nr_objects; /* revindex is used also */ - heap += sizeof(struct revindex_entry) * nr_objects; + heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects; /* * read_sha1_file() (either at delta calculation phase, or * writing phase) also fills up the delta base cache @@ -518,8 +526,8 @@ static void gc_before_repack(void) if (done++) return; - if (pack_refs && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD)) - die(FAILED_RUN, pack_refs_cmd.v[0]); + if (pack_refs && maintenance_task_pack_refs(NULL)) + die(FAILED_RUN, "pack-refs"); if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD)) die(FAILED_RUN, reflog.v[0]); @@ -556,7 +564,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_gc_usage, builtin_gc_options); - strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); strvec_pushl(&repack, "repack", "-d", "-l", NULL); strvec_pushl(&prune, "prune", "--expire", NULL); @@ -769,7 +776,7 @@ static int dfs_on_ref(const char *refname, struct commit_list *stack = NULL; struct commit *commit; - if (!peel_ref(refname, &peeled)) + if (!peel_iterated_oid(oid, &peeled)) oid = &peeled; if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) return 0; @@ -866,49 +873,40 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) return 0; } -static int fetch_remote(const char *remote, struct maintenance_run_opts *opts) +static int fetch_remote(struct remote *remote, void *cbdata) { + struct maintenance_run_opts *opts = cbdata; struct child_process child = CHILD_PROCESS_INIT; + if (remote->skip_default_update) + return 0; + child.git_cmd = 1; - strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags", + strvec_pushl(&child.args, "fetch", remote->name, + "--prefetch", "--prune", "--no-tags", "--no-write-fetch-head", "--recurse-submodules=no", - "--refmap=", NULL); + NULL); if (opts->quiet) strvec_push(&child.args, "--quiet"); - strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote); - return !!run_command(&child); } -static int append_remote(struct remote *remote, void *cbdata) -{ - struct string_list *remotes = (struct string_list *)cbdata; - - string_list_append(remotes, remote->name); - return 0; -} - static int maintenance_task_prefetch(struct maintenance_run_opts *opts) { - int result = 0; - struct string_list_item *item; - struct string_list remotes = STRING_LIST_INIT_DUP; - - if (for_each_remote(append_remote, &remotes)) { - error(_("failed to fill remotes")); - result = 1; - goto cleanup; + git_config_set_multivar_gently("log.excludedecoration", + "refs/prefetch/", + "refs/prefetch/", + CONFIG_FLAGS_FIXED_VALUE | + CONFIG_FLAGS_MULTI_REPLACE); + + if (for_each_remote(fetch_remote, opts)) { + error(_("failed to prefetch remotes")); + return 1; } - for_each_string_list_item(item, &remotes) - result |= fetch_remote(item->string, opts); - -cleanup: - string_list_clear(&remotes, 0); - return result; + return 0; } static int maintenance_task_gc(struct maintenance_run_opts *opts) @@ -1218,6 +1216,7 @@ enum maintenance_task_label { TASK_INCREMENTAL_REPACK, TASK_GC, TASK_COMMIT_GRAPH, + TASK_PACK_REFS, /* Leave as final value */ TASK__COUNT @@ -1249,6 +1248,11 @@ static struct maintenance_task tasks[] = { maintenance_task_commit_graph, should_write_commit_graph, }, + [TASK_PACK_REFS] = { + "pack-refs", + maintenance_task_pack_refs, + NULL, + }, }; static int compare_tasks_by_selection(const void *a_, const void *b_) @@ -1333,6 +1337,8 @@ static void initialize_maintenance_strategy(void) tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY; tasks[TASK_LOOSE_OBJECTS].enabled = 1; tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY; + tasks[TASK_PACK_REFS].enabled = 1; + tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY; } } @@ -1440,11 +1446,23 @@ static int maintenance_run(int argc, const char **argv, const char *prefix) return maintenance_run_tasks(&opts); } +static char *get_maintpath(void) +{ + struct strbuf sb = STRBUF_INIT; + const char *p = the_repository->worktree ? + the_repository->worktree : the_repository->gitdir; + + strbuf_realpath(&sb, p, 1); + return strbuf_detach(&sb, NULL); +} + static int maintenance_register(void) { + int rc; char *config_value; struct child_process config_set = CHILD_PROCESS_INIT; struct child_process config_get = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); /* Disable foreground maintenance */ git_config_set("maintenance.auto", "false"); @@ -1457,74 +1475,408 @@ static int maintenance_register(void) config_get.git_cmd = 1; strvec_pushl(&config_get.args, "config", "--global", "--get", - "--fixed-value", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + "--fixed-value", "maintenance.repo", maintpath, NULL); config_get.out = -1; - if (start_command(&config_get)) - return error(_("failed to run 'git config'")); + if (start_command(&config_get)) { + rc = error(_("failed to run 'git config'")); + goto done; + } /* We already have this value in our config! */ - if (!finish_command(&config_get)) - return 0; + if (!finish_command(&config_get)) { + rc = 0; + goto done; + } config_set.git_cmd = 1; strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + maintpath, NULL); + + rc = run_command(&config_set); - return run_command(&config_set); +done: + free(maintpath); + return rc; } static int maintenance_unregister(void) { + int rc; struct child_process config_unset = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); config_unset.git_cmd = 1; strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + "--fixed-value", "maintenance.repo", maintpath, NULL); + + rc = run_command(&config_unset); + free(maintpath); + return rc; +} + +static const char *get_frequency(enum schedule_priority schedule) +{ + switch (schedule) { + case SCHEDULE_HOURLY: + return "hourly"; + case SCHEDULE_DAILY: + return "daily"; + case SCHEDULE_WEEKLY: + return "weekly"; + default: + BUG("invalid schedule %d", schedule); + } +} + +static char *launchctl_service_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "org.git-scm.git.%s", frequency); + return strbuf_detach(&label, NULL); +} + +static char *launchctl_service_filename(const char *name) +{ + char *expanded; + struct strbuf filename = STRBUF_INIT; + strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name); + + expanded = expand_user_path(filename.buf, 1); + if (!expanded) + die(_("failed to expand path '%s'"), filename.buf); + + strbuf_release(&filename); + return expanded; +} + +static char *launchctl_get_uid(void) +{ + return xstrfmt("gui/%d", getuid()); +} + +static int launchctl_boot_plist(int enable, const char *filename, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + char *uid = launchctl_get_uid(); + + strvec_split(&child.args, cmd); + if (enable) + strvec_push(&child.args, "bootstrap"); + else + strvec_push(&child.args, "bootout"); + strvec_push(&child.args, uid); + strvec_push(&child.args, filename); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + result = finish_command(&child); + + free(uid); + return result; +} + +static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd) +{ + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + int result = launchctl_boot_plist(0, filename, cmd); + unlink(filename); + free(filename); + free(name); + return result; +} + +static int launchctl_remove_plists(const char *cmd) +{ + return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) || + launchctl_remove_plist(SCHEDULE_DAILY, cmd) || + launchctl_remove_plist(SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + FILE *plist; + int i; + const char *preamble, *repeat; + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + + if (safe_create_leading_directories(filename)) + die(_("failed to create directories for '%s'"), filename); + plist = xfopen(filename, "w"); + + preamble = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">" + "<dict>\n" + "<key>Label</key><string>%s</string>\n" + "<key>ProgramArguments</key>\n" + "<array>\n" + "<string>%s/git</string>\n" + "<string>--exec-path=%s</string>\n" + "<string>for-each-repo</string>\n" + "<string>--config=maintenance.repo</string>\n" + "<string>maintenance</string>\n" + "<string>run</string>\n" + "<string>--schedule=%s</string>\n" + "</array>\n" + "<key>StartCalendarInterval</key>\n" + "<array>\n"; + fprintf(plist, preamble, name, exec_path, exec_path, frequency); + + switch (schedule) { + case SCHEDULE_HOURLY: + repeat = "<dict>\n" + "<key>Hour</key><integer>%d</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 23; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_DAILY: + repeat = "<dict>\n" + "<key>Day</key><integer>%d</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 6; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_WEEKLY: + fprintf(plist, + "<dict>\n" + "<key>Day</key><integer>0</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"); + break; + + default: + /* unreachable */ + break; + } + fprintf(plist, "</array>\n</dict>\n</plist>\n"); + fclose(plist); + + /* bootout might fail if not already running, so ignore */ + launchctl_boot_plist(0, filename, cmd); + if (launchctl_boot_plist(1, filename, cmd)) + die(_("failed to bootstrap service %s"), filename); + + free(filename); + free(name); + return 0; +} + +static int launchctl_add_plists(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return launchctl_add_plists(cmd); + else + return launchctl_remove_plists(cmd); +} + +static char *schtasks_task_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "Git Maintenance (%s)", frequency); + return strbuf_detach(&label, NULL); +} + +static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd) +{ + int result; + struct strvec args = STRVEC_INIT; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + + strvec_split(&args, cmd); + strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL); + + result = run_command_v_opt(args.v, 0); + + strvec_clear(&args); + free(name); + return result; +} + +static int schtasks_remove_tasks(const char *cmd) +{ + return schtasks_remove_task(SCHEDULE_HOURLY, cmd) || + schtasks_remove_task(SCHEDULE_DAILY, cmd) || + schtasks_remove_task(SCHEDULE_WEEKLY, cmd); +} + +static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + const char *xml; + struct tempfile *tfile; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + struct strbuf tfilename = STRBUF_INIT; + + strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX", + get_git_common_dir(), frequency); + tfile = xmks_tempfile(tfilename.buf); + strbuf_release(&tfilename); + + if (!fdopen_tempfile(tfile, "w")) + die(_("failed to create temp xml file")); + + xml = "<?xml version=\"1.0\" ?>\n" + "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n" + "<Triggers>\n" + "<CalendarTrigger>\n"; + fputs(xml, tfile->fp); + + switch (schedule) { + case SCHEDULE_HOURLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByDay>\n" + "<DaysInterval>1</DaysInterval>\n" + "</ScheduleByDay>\n" + "<Repetition>\n" + "<Interval>PT1H</Interval>\n" + "<Duration>PT23H</Duration>\n" + "<StopAtDurationEnd>false</StopAtDurationEnd>\n" + "</Repetition>\n"); + break; + + case SCHEDULE_DAILY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Monday />\n" + "<Tuesday />\n" + "<Wednesday />\n" + "<Thursday />\n" + "<Friday />\n" + "<Saturday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + case SCHEDULE_WEEKLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Sunday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + default: + break; + } + + xml = "</CalendarTrigger>\n" + "</Triggers>\n" + "<Principals>\n" + "<Principal id=\"Author\">\n" + "<LogonType>InteractiveToken</LogonType>\n" + "<RunLevel>LeastPrivilege</RunLevel>\n" + "</Principal>\n" + "</Principals>\n" + "<Settings>\n" + "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n" + "<Enabled>true</Enabled>\n" + "<Hidden>true</Hidden>\n" + "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n" + "<WakeToRun>false</WakeToRun>\n" + "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n" + "<Priority>7</Priority>\n" + "</Settings>\n" + "<Actions Context=\"Author\">\n" + "<Exec>\n" + "<Command>\"%s\\git.exe\"</Command>\n" + "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n" + "</Exec>\n" + "</Actions>\n" + "</Task>\n"; + fprintf(tfile->fp, xml, exec_path, exec_path, frequency); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml", + get_tempfile_path(tfile), NULL); + close_tempfile_gently(tfile); + + child.no_stdout = 1; + child.no_stderr = 1; + + if (start_command(&child)) + die(_("failed to start schtasks")); + result = finish_command(&child); + + delete_tempfile(&tfile); + free(name); + return result; +} + +static int schtasks_schedule_tasks(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd); +} - return run_command(&config_unset); +static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return schtasks_schedule_tasks(cmd); + else + return schtasks_remove_tasks(cmd); } #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE" #define END_LINE "# END GIT MAINTENANCE SCHEDULE" -static int update_background_schedule(int run_maintenance) +static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd) { int result = 0; int in_old_region = 0; struct child_process crontab_list = CHILD_PROCESS_INIT; struct child_process crontab_edit = CHILD_PROCESS_INIT; FILE *cron_list, *cron_in; - const char *crontab_name; struct strbuf line = STRBUF_INIT; - struct lock_file lk; - char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); - if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) - return error(_("another process is scheduling background maintenance")); - - crontab_name = getenv("GIT_TEST_CRONTAB"); - if (!crontab_name) - crontab_name = "crontab"; - - strvec_split(&crontab_list.args, crontab_name); + strvec_split(&crontab_list.args, cmd); strvec_push(&crontab_list.args, "-l"); crontab_list.in = -1; - crontab_list.out = dup(get_lock_file_fd(&lk)); + crontab_list.out = dup(fd); crontab_list.git_cmd = 0; - if (start_command(&crontab_list)) { - result = error(_("failed to run 'crontab -l'; your system might not support 'cron'")); - goto cleanup; - } + if (start_command(&crontab_list)) + return error(_("failed to run 'crontab -l'; your system might not support 'cron'")); /* Ignore exit code, as an empty crontab will return error. */ finish_command(&crontab_list); @@ -1533,17 +1885,15 @@ static int update_background_schedule(int run_maintenance) * Read from the .lock file, filtering out the old * schedule while appending the new schedule. */ - cron_list = fdopen(get_lock_file_fd(&lk), "r"); + cron_list = fdopen(fd, "r"); rewind(cron_list); - strvec_split(&crontab_edit.args, crontab_name); + strvec_split(&crontab_edit.args, cmd); crontab_edit.in = -1; crontab_edit.git_cmd = 0; - if (start_command(&crontab_edit)) { - result = error(_("failed to run 'crontab'; your system might not support 'cron'")); - goto cleanup; - } + if (start_command(&crontab_edit)) + return error(_("failed to run 'crontab'; your system might not support 'cron'")); cron_in = fdopen(crontab_edit.in, "w"); if (!cron_in) { @@ -1559,6 +1909,7 @@ static int update_background_schedule(int run_maintenance) else if (!in_old_region) fprintf(cron_in, "%s\n", line.buf); } + strbuf_release(&line); if (run_maintenance) { struct strbuf line_format = STRBUF_INIT; @@ -1587,14 +1938,59 @@ static int update_background_schedule(int run_maintenance) close(crontab_edit.in); done_editing: - if (finish_command(&crontab_edit)) { + if (finish_command(&crontab_edit)) result = error(_("'crontab' died")); + else + fclose(cron_list); + return result; +} + +#if defined(__APPLE__) +static const char platform_scheduler[] = "launchctl"; +#elif defined(GIT_WINDOWS_NATIVE) +static const char platform_scheduler[] = "schtasks"; +#else +static const char platform_scheduler[] = "crontab"; +#endif + +static int update_background_schedule(int enable) +{ + int result; + const char *scheduler = platform_scheduler; + const char *cmd = scheduler; + char *testing; + struct lock_file lk; + char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); + + testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); + if (testing) { + char *sep = strchr(testing, ':'); + if (!sep) + die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing); + *sep = '\0'; + scheduler = testing; + cmd = sep + 1; + } + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + result = error(_("another process is scheduling background maintenance")); goto cleanup; } - fclose(cron_list); -cleanup: + if (!strcmp(scheduler, "launchctl")) + result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "schtasks")) + result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "crontab")) + result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else + die("unknown background scheduler: %s", scheduler); + rollback_lock_file(&lk); + +cleanup: + free(lock_path); + free(testing); return result; } |