| #include "git-compat-util.h" | |
| #include "abspath.h" | |
| #include "blob.h" | |
| #include "chdir-notify.h" | |
| #include "config.h" | |
| #include "gettext.h" | |
| #include "hex.h" | |
| #include "lockfile.h" | |
| #include "object-file.h" | |
| #include "odb.h" | |
| #include "odb/source.h" | |
| #include "odb/source-files.h" | |
| #include "odb/source-loose.h" | |
| #include "pack-objects.h" | |
| #include "packfile.h" | |
| #include "path.h" | |
| #include "promisor-remote.h" | |
| #include "repack.h" | |
| #include "run-command.h" | |
| #include "strbuf.h" | |
| #include "string-list.h" | |
| #include "strvec.h" | |
| #include "tree.h" | |
| #include "write-or-die.h" | |
| static void odb_source_files_reparent(const char *old_cwd, | |
| const char *new_cwd, | |
| void *cb_data) | |
| { | |
| struct odb_source_files *files = cb_data; | |
| char *path = reparent_relative_path(old_cwd, new_cwd, | |
| files->base.path); | |
| free(files->base.path); | |
| files->base.path = path; | |
| } | |
| static void odb_source_files_free(struct odb_source *source) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| chdir_notify_unregister(odb_source_files_reparent, files); | |
| odb_source_free(&files->loose->base); | |
| odb_source_free(&files->packed->base); | |
| odb_source_release(&files->base); | |
| free(files); | |
| } | |
| static void odb_source_files_close(struct odb_source *source) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| odb_source_close(&files->loose->base); | |
| odb_source_close(&files->packed->base); | |
| } | |
| static int odb_source_files_create_on_disk(struct odb_source *source) | |
| { | |
| struct strbuf path = STRBUF_INIT; | |
| safe_create_dir(source->odb->repo, source->path, 1); | |
| strbuf_addf(&path, "%s/pack", source->path); | |
| safe_create_dir(source->odb->repo, path.buf, 1); | |
| strbuf_reset(&path); | |
| strbuf_addf(&path, "%s/info", source->path); | |
| safe_create_dir(source->odb->repo, path.buf, 1); | |
| strbuf_release(&path); | |
| return 0; | |
| } | |
| static void odb_source_files_prepare(struct odb_source *source, | |
| enum odb_prepare_flags flags) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| odb_source_prepare(&files->loose->base, flags); | |
| odb_source_prepare(&files->packed->base, flags); | |
| } | |
| static enum odb_read_status odb_source_files_read_object_info(struct odb_source *source, | |
| const struct object_id *oid, | |
| struct object_info *oi, | |
| enum object_info_flags flags, | |
| struct strbuf *errmsg) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| enum odb_read_status ret_packed, ret_loose; | |
| ret_packed = odb_source_read_object_info(&files->packed->base, oid, oi, | |
| flags, errmsg); | |
| if (!ret_packed) | |
| return 0; | |
| ret_loose = odb_source_read_object_info(&files->loose->base, oid, oi, flags, | |
| ret_packed == ODB_READ_NOT_FOUND ? errmsg : NULL); | |
| if (!ret_loose) | |
| return 0; | |
| /* | |
| * Reading the packed object may have failed even though the object | |
| * exists, for example because it is corrupt. Report this failure to | |
| * the caller in case neither of the sources was able to read the | |
| * object, and prefer the error of the packed source in case both | |
| * reads have failed. | |
| */ | |
| if (ret_packed != ODB_READ_NOT_FOUND) | |
| return ret_packed; | |
| return ret_loose; | |
| } | |
| static int odb_source_files_read_object_stream(struct odb_stream **out, | |
| struct odb_source *source, | |
| const struct object_id *oid) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| if (!odb_source_read_object_stream(out, &files->packed->base, oid) || | |
| !odb_source_read_object_stream(out, &files->loose->base, oid)) | |
| return 0; | |
| return -1; | |
| } | |
| static int odb_source_files_for_each_object(struct odb_source *source, | |
| const struct object_info *request, | |
| odb_for_each_object_cb cb, | |
| void *cb_data, | |
| const struct odb_for_each_object_options *opts) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| int ret; | |
| if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) { | |
| ret = odb_source_for_each_object(&files->loose->base, request, cb, cb_data, opts); | |
| if (ret) | |
| return ret; | |
| } | |
| ret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static int odb_source_files_count_objects(struct odb_source *source, | |
| enum odb_count_objects_flags flags, | |
| unsigned long *out) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| unsigned long count; | |
| int ret; | |
| ret = odb_source_count_objects(&files->packed->base, flags, &count); | |
| if (ret < 0) | |
| goto out; | |
| if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) { | |
| unsigned long loose_count; | |
| ret = odb_source_count_objects(&files->loose->base, flags, &loose_count); | |
| if (ret < 0) | |
| goto out; | |
| count += loose_count; | |
| } | |
| *out = count; | |
| ret = 0; | |
| out: | |
| return ret; | |
| } | |
| static int odb_source_files_find_abbrev_len(struct odb_source *source, | |
| const struct object_id *oid, | |
| unsigned min_len, | |
| unsigned *out) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| unsigned len = min_len; | |
| int ret; | |
| ret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len); | |
| if (ret < 0) | |
| goto out; | |
| ret = odb_source_find_abbrev_len(&files->loose->base, oid, len, &len); | |
| if (ret < 0) | |
| goto out; | |
| *out = len; | |
| ret = 0; | |
| out: | |
| return ret; | |
| } | |
| static int odb_source_files_freshen_object(struct odb_source *source, | |
| const struct object_id *oid, | |
| const time_t *mtime) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| if (odb_source_freshen_object(&files->packed->base, oid, mtime) || | |
| odb_source_freshen_object(&files->loose->base, oid, mtime)) | |
| return 1; | |
| return 0; | |
| } | |
| static int odb_source_files_write_object(struct odb_source *source, | |
| const void *buf, size_t len, | |
| enum object_type type, | |
| const struct object_id *oid, | |
| const struct object_id *compat_oid, | |
| const time_t *mtime, | |
| enum odb_write_object_flags flags) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| return odb_source_write_object(&files->loose->base, buf, len, type, | |
| oid, compat_oid, mtime, flags); | |
| } | |
| static int odb_source_files_write_object_stream(struct odb_source *source, | |
| struct odb_stream *stream, | |
| struct object_id *oid) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| return odb_source_write_object_stream(&files->loose->base, stream, oid); | |
| } | |
| static int odb_source_files_begin_transaction(struct odb_source *source, | |
| struct odb_transaction **out, | |
| enum odb_transaction_flags flags) | |
| { | |
| return odb_transaction_files_begin(source, out, flags); | |
| } | |
| static int odb_source_files_read_alternates(struct odb_source *source, | |
| struct strvec *out) | |
| { | |
| struct strbuf buf = STRBUF_INIT; | |
| char *path; | |
| path = xstrfmt("%s/info/alternates", source->path); | |
| if (strbuf_read_file(&buf, path, 1024) < 0) { | |
| warn_on_fopen_errors(path); | |
| free(path); | |
| return 0; | |
| } | |
| parse_alternates(buf.buf, '\n', source->path, out); | |
| strbuf_release(&buf); | |
| free(path); | |
| return 0; | |
| } | |
| static int odb_source_files_write_alternate(struct odb_source *source, | |
| const char *alternate) | |
| { | |
| struct lock_file lock = LOCK_INIT; | |
| char *path = xstrfmt("%s/%s", source->path, "info/alternates"); | |
| FILE *in, *out; | |
| int found = 0; | |
| int ret; | |
| repo_hold_lock_file_for_update(source->odb->repo, &lock, path, | |
| LOCK_DIE_ON_ERROR); | |
| out = fdopen_lock_file(&lock, "w"); | |
| if (!out) { | |
| ret = error_errno(_("unable to fdopen alternates lockfile")); | |
| goto out; | |
| } | |
| in = fopen(path, "r"); | |
| if (in) { | |
| struct strbuf line = STRBUF_INIT; | |
| while (strbuf_getline(&line, in) != EOF) { | |
| if (!strcmp(alternate, line.buf)) { | |
| found = 1; | |
| break; | |
| } | |
| fprintf_or_die(out, "%s\n", line.buf); | |
| } | |
| strbuf_release(&line); | |
| fclose(in); | |
| } else if (errno != ENOENT) { | |
| ret = error_errno(_("unable to read alternates file")); | |
| goto out; | |
| } | |
| if (found) { | |
| rollback_lock_file(&lock); | |
| } else { | |
| fprintf_or_die(out, "%s\n", alternate); | |
| if (commit_lock_file(&lock)) { | |
| ret = error_errno(_("unable to move new alternates file into place")); | |
| goto out; | |
| } | |
| } | |
| ret = 0; | |
| out: | |
| free(path); | |
| return ret; | |
| } | |
| static int too_many_loose_objects(struct odb_source_files *files, int limit) | |
| { | |
| unsigned long loose_count; | |
| if (limit <= 0) | |
| return 0; | |
| if (odb_source_count_objects(&files->loose->base, ODB_COUNT_OBJECTS_APPROXIMATE, | |
| &loose_count) < 0) | |
| return 0; | |
| /* | |
| * This is weird, but stems from legacy behaviour: the GC auto | |
| * threshold was always essentially interpreted as if it was rounded up | |
| * to the next multiple 256 of, so we retain this behaviour for now. | |
| */ | |
| return loose_count > (DIV_ROUND_UP(((unsigned long) limit), 256) * 256); | |
| } | |
| static struct packed_git *find_base_packs(struct odb_source_files *files, | |
| struct string_list *packs, | |
| unsigned long limit) | |
| { | |
| struct packfile_list_entry *e; | |
| struct packed_git *base = NULL; | |
| for (e = packfile_store_get_packs(files->packed); e; e = e->next) { | |
| if (e->pack->is_cruft) | |
| continue; | |
| if (limit) { | |
| if ((uintmax_t) e->pack->pack_size >= limit) | |
| string_list_append(packs, e->pack->pack_name); | |
| } else if (!base || base->pack_size < e->pack->pack_size) { | |
| base = e->pack; | |
| } | |
| } | |
| if (base) | |
| string_list_append(packs, base->pack_name); | |
| return base; | |
| } | |
| static int too_many_packs(struct odb_source_files *files, int gc_auto_pack_limit) | |
| { | |
| struct packfile_list_entry *e; | |
| int cnt = 0; | |
| if (gc_auto_pack_limit <= 0) | |
| return 0; | |
| for (e = packfile_store_get_packs(files->packed); e; e = e->next) { | |
| if (e->pack->pack_keep) | |
| continue; | |
| /* | |
| * Perhaps check the size of the pack and count only | |
| * very small ones here? | |
| */ | |
| cnt++; | |
| } | |
| return gc_auto_pack_limit < cnt; | |
| } | |
| static uint64_t total_ram(void) | |
| { | |
| #if defined(HAVE_SYSINFO) | |
| struct sysinfo si; | |
| if (!sysinfo(&si)) { | |
| uint64_t total = si.totalram; | |
| if (si.mem_unit > 1) | |
| total *= (uint64_t)si.mem_unit; | |
| return total; | |
| } | |
| #elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM) || defined(HW_PHYSMEM64)) | |
| uint64_t physical_memory; | |
| int mib[2]; | |
| size_t length; | |
| mib[0] = CTL_HW; | |
| # if defined(HW_MEMSIZE) | |
| mib[1] = HW_MEMSIZE; | |
| # elif defined(HW_PHYSMEM64) | |
| mib[1] = HW_PHYSMEM64; | |
| # else | |
| mib[1] = HW_PHYSMEM; | |
| # endif | |
| length = sizeof(physical_memory); | |
| if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0)) { | |
| if (length == 4) { | |
| uint32_t mem; | |
| if (!sysctl(mib, 2, &mem, &length, NULL, 0)) | |
| physical_memory = mem; | |
| } | |
| return physical_memory; | |
| } | |
| #elif defined(GIT_WINDOWS_NATIVE) | |
| MEMORYSTATUSEX memInfo; | |
| memInfo.dwLength = sizeof(MEMORYSTATUSEX); | |
| if (GlobalMemoryStatusEx(&memInfo)) | |
| return memInfo.ullTotalPhys; | |
| #endif | |
| return 0; | |
| } | |
| static uint64_t estimate_repack_memory(struct odb_source_files *files, | |
| struct packed_git *pack) | |
| { | |
| unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; | |
| unsigned long delta_base_cache_limit = DEFAULT_DELTA_BASE_CACHE_LIMIT; | |
| unsigned long nr_objects; | |
| size_t os_cache, heap; | |
| if (odb_source_count_objects(&files->base, ODB_COUNT_OBJECTS_APPROXIMATE, | |
| &nr_objects) < 0) | |
| return 0; | |
| if (!pack || !nr_objects) | |
| return 0; | |
| repo_config_get_ulong(files->base.odb->repo, "pack.deltacachesize", | |
| &max_delta_cache_size); | |
| repo_config_get_ulong(files->base.odb->repo, "core.deltabasecachelimit", | |
| &delta_base_cache_limit); | |
| /* | |
| * First we have to scan through at least one pack. | |
| * Assume enough room in OS file cache to keep the entire pack | |
| * or we may accidentally evict data of other processes from | |
| * the cache. | |
| */ | |
| os_cache = pack->pack_size + pack->index_size; | |
| /* then pack-objects needs lots more for book keeping */ | |
| heap = sizeof(struct object_entry) * nr_objects; | |
| /* | |
| * internal rev-list --all --objects takes up some memory too, | |
| * let's say half of it is for blobs | |
| */ | |
| heap += sizeof(struct blob) * nr_objects / 2; | |
| /* | |
| * and the other half is for trees (commits and tags are | |
| * usually insignificant) | |
| */ | |
| heap += sizeof(struct tree) * nr_objects / 2; | |
| /* and then obj_hash[], underestimated in fact */ | |
| heap += sizeof(struct object *) * nr_objects; | |
| /* revindex is used also */ | |
| 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 | |
| */ | |
| heap += delta_base_cache_limit; | |
| /* and of course pack-objects has its own delta cache */ | |
| heap += max_delta_cache_size; | |
| return os_cache + heap; | |
| } | |
| static int keep_one_pack(struct string_list_item *item, void *data) | |
| { | |
| struct strvec *args = data; | |
| strvec_pushf(args, "--keep-pack=%s", basename(item->string)); | |
| return 0; | |
| } | |
| static void add_repack_all_option(struct repository *repo, | |
| const struct odb_optimize_options *opts, | |
| struct string_list *keep_pack, | |
| struct strvec *args) | |
| { | |
| char *repack_filter = NULL; | |
| char *repack_filter_to = NULL; | |
| repo_config_get_string(repo, "gc.repackfilter", &repack_filter); | |
| repo_config_get_string(repo, "gc.repackfilterto", &repack_filter_to); | |
| if (opts->prune_expire && !strcmp(opts->prune_expire, "now") && | |
| !(opts->cruft_packs && opts->expire_to)) | |
| strvec_push(args, "-a"); | |
| else if (opts->cruft_packs) { | |
| strvec_push(args, "--cruft"); | |
| if (opts->prune_expire) | |
| strvec_pushf(args, "--cruft-expiration=%s", opts->prune_expire); | |
| if (opts->max_cruft_size) | |
| strvec_pushf(args, "--max-cruft-size=%lu", | |
| opts->max_cruft_size); | |
| if (opts->expire_to) | |
| strvec_pushf(args, "--expire-to=%s", opts->expire_to); | |
| } else { | |
| strvec_push(args, "-A"); | |
| if (opts->prune_expire) | |
| strvec_pushf(args, "--unpack-unreachable=%s", opts->prune_expire); | |
| } | |
| if (keep_pack) | |
| for_each_string_list(keep_pack, keep_one_pack, args); | |
| if (repack_filter && *repack_filter) | |
| strvec_pushf(args, "--filter=%s", repack_filter); | |
| if (repack_filter_to && *repack_filter_to) | |
| strvec_pushf(args, "--filter-to=%s", repack_filter_to); | |
| free(repack_filter); | |
| free(repack_filter_to); | |
| } | |
| static void add_repack_incremental_option(struct strvec *args) | |
| { | |
| strvec_push(args, "--no-write-bitmap-index"); | |
| } | |
| bool odb_source_files_optimize_required(struct odb_source *source, | |
| const struct odb_optimize_options *opts) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| struct repository *repo = source->odb->repo; | |
| switch (opts->strategy) { | |
| case ODB_OPTIMIZE_INCREMENTAL: { | |
| int gc_auto_threshold = 6700; | |
| int gc_auto_pack_limit = 50; | |
| repo_config_get_int(repo, "gc.auto", &gc_auto_threshold); | |
| repo_config_get_int(repo, "gc.autopacklimit", &gc_auto_pack_limit); | |
| /* | |
| * Setting gc.auto to 0 or negative can disable the | |
| * automatic gc. | |
| */ | |
| if (gc_auto_threshold <= 0) | |
| return false; | |
| if (!too_many_packs(files, gc_auto_pack_limit) && | |
| !too_many_loose_objects(files, gc_auto_threshold)) | |
| return false; | |
| return true; | |
| } | |
| case ODB_OPTIMIZE_GEOMETRIC: { | |
| struct pack_geometry geometry = { | |
| .split_factor = 2, | |
| }; | |
| struct pack_objects_args po_args = { | |
| .local = 1, | |
| }; | |
| struct existing_packs existing_packs = EXISTING_PACKS_INIT; | |
| struct string_list kept_packs = STRING_LIST_INIT_DUP; | |
| int auto_value = 6700; | |
| bool ret; | |
| repo_config_get_int(repo, "maintenance.geometric-repack.auto", | |
| &auto_value); | |
| if (!auto_value) | |
| return false; | |
| if (auto_value < 0) | |
| return true; | |
| repo_config_get_int(repo, "maintenance.geometric-repack.splitFactor", | |
| &geometry.split_factor); | |
| existing_packs.repo = repo; | |
| existing_packs_collect(&existing_packs, &kept_packs); | |
| pack_geometry_init(&geometry, &existing_packs, &po_args); | |
| pack_geometry_split(&geometry); | |
| /* | |
| * When we'd merge at least two packs with one another we always | |
| * perform the repack. | |
| */ | |
| if (geometry.split) { | |
| ret = true; | |
| goto out; | |
| } | |
| /* | |
| * Otherwise, we estimate the number of loose objects to determine | |
| * whether we want to create a new packfile or not. | |
| */ | |
| if (too_many_loose_objects(files, auto_value)) { | |
| ret = true; | |
| goto out; | |
| } | |
| ret = false; | |
| out: | |
| existing_packs_release(&existing_packs); | |
| pack_geometry_release(&geometry); | |
| return ret; | |
| } | |
| default: | |
| BUG("unknown maintenance strategy '%d'", opts->strategy); | |
| } | |
| } | |
| int odb_source_files_optimize(struct odb_source *source, | |
| const struct odb_optimize_options *opts) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| struct repository *repo = source->odb->repo; | |
| struct child_process repack_cmd = CHILD_PROCESS_INIT; | |
| unsigned long big_pack_threshold = 0; | |
| int gc_auto_threshold = 6700; | |
| int gc_auto_pack_limit = 50; | |
| int ret; | |
| repo_config_get_int(repo, "gc.auto", &gc_auto_threshold); | |
| repo_config_get_int(repo, "gc.autopacklimit", &gc_auto_pack_limit); | |
| repo_config_get_ulong(repo, "gc.bigpackthreshold", &big_pack_threshold); | |
| if (repo->repository_format_precious_objects) | |
| return 0; | |
| repack_cmd.git_cmd = 1; | |
| repack_cmd.odb_to_close = repo->objects; | |
| strvec_pushl(&repack_cmd.args, "repack", "-d", "-l", NULL); | |
| if (opts->flags & ODB_OPTIMIZE_NO_REUSE_DELTAS) | |
| strvec_push(&repack_cmd.args, "-f"); | |
| if (opts->depth > 0) | |
| strvec_pushf(&repack_cmd.args, "--depth=%d", opts->depth); | |
| if (opts->window > 0) | |
| strvec_pushf(&repack_cmd.args, "--window=%d", opts->window); | |
| if (!(opts->flags & ODB_OPTIMIZE_VERBOSE)) | |
| strvec_push(&repack_cmd.args, "-q"); | |
| /* | |
| * There's three cases we need to consider: | |
| * | |
| * - If we're invoked without `--auto` we'll need to perform a full | |
| * repack. | |
| * | |
| * - If we're invoked with `--auto` and there's too many packs, then | |
| * we perform a full repack, as well. | |
| * | |
| * - Otherwise we perform an incremental repack. | |
| */ | |
| switch (opts->strategy) { | |
| case ODB_OPTIMIZE_INCREMENTAL: | |
| if (!(opts->flags & ODB_OPTIMIZE_AUTO)) { | |
| struct string_list keep_pack = STRING_LIST_INIT_NODUP; | |
| if (opts->keep_largest_pack != -1) { | |
| if (opts->keep_largest_pack) | |
| find_base_packs(files, &keep_pack, 0); | |
| } else if (big_pack_threshold) { | |
| find_base_packs(files, &keep_pack, big_pack_threshold); | |
| } | |
| add_repack_all_option(repo, opts, &keep_pack, &repack_cmd.args); | |
| string_list_clear(&keep_pack, 0); | |
| } else { | |
| if (too_many_packs(files, gc_auto_pack_limit)) { | |
| struct string_list keep_pack = STRING_LIST_INIT_NODUP; | |
| if (big_pack_threshold) { | |
| find_base_packs(files, &keep_pack, big_pack_threshold); | |
| if (keep_pack.nr >= (unsigned long) gc_auto_pack_limit) { | |
| string_list_clear(&keep_pack, 0); | |
| find_base_packs(files, &keep_pack, 0); | |
| } | |
| } else { | |
| struct packed_git *p = find_base_packs(files, &keep_pack, 0); | |
| uint64_t mem_have, mem_want; | |
| mem_have = total_ram(); | |
| mem_want = estimate_repack_memory(files, p); | |
| /* | |
| * Only allow 1/2 of memory for pack-objects, leave | |
| * the rest for the OS and other processes in the | |
| * system. | |
| */ | |
| if (!mem_have || mem_want < mem_have / 2) | |
| string_list_clear(&keep_pack, 0); | |
| } | |
| add_repack_all_option(repo, opts, &keep_pack, &repack_cmd.args); | |
| string_list_clear(&keep_pack, 0); | |
| } else { | |
| add_repack_incremental_option(&repack_cmd.args); | |
| } | |
| } | |
| break; | |
| case ODB_OPTIMIZE_GEOMETRIC: { | |
| struct pack_geometry geometry = { | |
| .split_factor = 2, | |
| }; | |
| struct pack_objects_args po_args = { | |
| .local = 1, | |
| }; | |
| struct existing_packs existing_packs = EXISTING_PACKS_INIT; | |
| struct string_list kept_packs = STRING_LIST_INIT_DUP; | |
| repo_config_get_int(repo, "maintenance.geometric-repack.splitFactor", | |
| &geometry.split_factor); | |
| existing_packs.repo = repo; | |
| existing_packs_collect(&existing_packs, &kept_packs); | |
| pack_geometry_init(&geometry, &existing_packs, &po_args); | |
| pack_geometry_split(&geometry); | |
| if (geometry.split < geometry.pack_nr) { | |
| strvec_pushf(&repack_cmd.args, "--geometric=%d", | |
| geometry.split_factor); | |
| } else { | |
| add_repack_all_option(repo, opts, NULL, &repack_cmd.args); | |
| } | |
| if (repo->settings.core_multi_pack_index) | |
| strvec_push(&repack_cmd.args, "--write-midx"); | |
| existing_packs_release(&existing_packs); | |
| pack_geometry_release(&geometry); | |
| break; | |
| } | |
| default: | |
| die("unknown maintenance strategy '%d'", opts->strategy); | |
| } | |
| if (run_command(&repack_cmd)) { | |
| ret = error("failed to run %s", repack_cmd.args.v[0]); | |
| goto out; | |
| } | |
| /* Geometric repacking uses cruft packs, so we don't have to prune separately. */ | |
| if (opts->strategy != ODB_OPTIMIZE_GEOMETRIC && opts->prune_expire) { | |
| struct child_process prune_cmd = CHILD_PROCESS_INIT; | |
| strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL); | |
| /* run `git prune` even if using cruft packs */ | |
| strvec_push(&prune_cmd.args, opts->prune_expire); | |
| if (!(opts->flags & ODB_OPTIMIZE_VERBOSE)) | |
| strvec_push(&prune_cmd.args, "--no-progress"); | |
| if (repo_has_promisor_remote(repo)) | |
| strvec_push(&prune_cmd.args, | |
| "--exclude-promisor-objects"); | |
| prune_cmd.git_cmd = 1; | |
| if (run_command(&prune_cmd)) { | |
| ret = error("failed to run %s", prune_cmd.args.v[0]); | |
| goto out; | |
| } | |
| } | |
| if (opts->flags & ODB_OPTIMIZE_AUTO && too_many_loose_objects(files, gc_auto_threshold)) | |
| warning(_("There are too many unreachable loose objects; " | |
| "run 'git prune' to remove them.")); | |
| ret = 0; | |
| out: | |
| return ret; | |
| } | |
| struct odb_pack_generator_files { | |
| struct odb_pack_generator base; | |
| struct child_process cp; | |
| }; | |
| static int odb_pack_generator_files_finish(struct odb_pack_generator *_generator) | |
| { | |
| struct odb_pack_generator_files *generator = | |
| (struct odb_pack_generator_files *)_generator; | |
| int ret; | |
| ret = finish_command(&generator->cp); | |
| free(generator); | |
| if (ret) { | |
| /* | |
| * On failure, pack-objects is expected to have written a | |
| * useful error message to its standard error stream already. | |
| * Death by signal is worth mentioning, though, with the | |
| * exception of SIGPIPE: that is a normal occurrence when the | |
| * consumer of the pack hangs up. | |
| */ | |
| if (ret > 128 && ret - 128 == SIGPIPE) | |
| return -1; | |
| if (ret > 128) | |
| error(_("pack-objects died of signal %d"), ret - 128); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int odb_source_files_generate_pack(struct odb_source *source UNUSED, | |
| struct odb_pack_generator **out, | |
| const struct odb_generate_pack_options *opts) | |
| { | |
| struct odb_pack_generator_files *generator; | |
| struct child_process *cp; | |
| FILE *in; | |
| CALLOC_ARRAY(generator, 1); | |
| child_process_init(&generator->cp); | |
| cp = &generator->cp; | |
| /* | |
| * The hook is expected to spawn "$hook git pack-objects <args...>" | |
| * and to behave like git-pack-objects(1) would have. This can for | |
| * example be used to serve precomputed packfiles. | |
| */ | |
| if (opts->pack_objects_hook) { | |
| strvec_push(&cp->args, opts->pack_objects_hook); | |
| strvec_push(&cp->args, "git"); | |
| cp->use_shell = 1; | |
| } else { | |
| cp->git_cmd = 1; | |
| } | |
| /* | |
| * The caller-provided shallow boundary overrides any shallow state | |
| * that the repository itself may have, so the shallow file needs to | |
| * be neutralized. | |
| */ | |
| if (opts->shallows.nr) { | |
| strvec_push(&cp->args, "--shallow-file"); | |
| strvec_push(&cp->args, ""); | |
| } | |
| strvec_push(&cp->args, "pack-objects"); | |
| strvec_push(&cp->args, "--revs"); | |
| strvec_push(&cp->args, "--stdout"); | |
| if (opts->thin) | |
| strvec_push(&cp->args, "--thin"); | |
| if (opts->shallow) | |
| strvec_push(&cp->args, "--shallow"); | |
| if (opts->ofs_delta) | |
| strvec_push(&cp->args, "--delta-base-offset"); | |
| if (opts->include_tag) | |
| strvec_push(&cp->args, "--include-tag"); | |
| if (opts->missing_allow_promisor) | |
| strvec_push(&cp->args, "--missing=allow-promisor"); | |
| if (opts->disable_bitmaps) | |
| strvec_push(&cp->args, "--no-use-bitmap-index"); | |
| switch (opts->progress) { | |
| case ODB_GENERATE_PACK_PROGRESS_NONE: | |
| strvec_push(&cp->args, "--quiet"); | |
| break; | |
| case ODB_GENERATE_PACK_PROGRESS_STANDARD: | |
| strvec_push(&cp->args, "--progress"); | |
| break; | |
| case ODB_GENERATE_PACK_PROGRESS_VERBOSE: | |
| strvec_push(&cp->args, "--all-progress"); | |
| break; | |
| default: | |
| BUG("unknown progress option %d", opts->progress); | |
| } | |
| if (opts->filter_spec) | |
| strvec_pushf(&cp->args, "--filter=%s", opts->filter_spec); | |
| if (opts->uri_protocols) | |
| for (size_t i = 0; i < opts->uri_protocols->nr; i++) | |
| strvec_pushf(&cp->args, "--uri-protocol=%s", | |
| opts->uri_protocols->items[i].string); | |
| cp->in = -1; | |
| cp->out = opts->pack_fd; | |
| cp->err = opts->progress_fd; | |
| cp->clean_on_exit = 1; | |
| if (start_command(cp)) { | |
| free(generator); | |
| return error(_("could not spawn pack-objects")); | |
| } | |
| /* | |
| * Feed the objects to pack-objects. This is safe to do synchronously | |
| * because pack-objects consumes all of its standard input before it | |
| * starts to generate the pack. | |
| */ | |
| in = xfdopen(cp->in, "w"); | |
| for (size_t i = 0; i < opts->shallows.nr; i++) | |
| fprintf(in, "--shallow %s\n", oid_to_hex(&opts->shallows.oid[i])); | |
| for (size_t i = 0; i < opts->wants.nr; i++) | |
| fprintf(in, "%s\n", oid_to_hex(&opts->wants.oid[i])); | |
| fprintf(in, "--not\n"); | |
| for (size_t i = 0; i < opts->haves.nr; i++) | |
| fprintf(in, "%s\n", oid_to_hex(&opts->haves.oid[i])); | |
| fprintf(in, "\n"); | |
| fflush(in); | |
| if (ferror(in)) { | |
| error(_("error writing to pack-objects")); | |
| fclose(in); | |
| if (opts->pack_fd < 0) | |
| close(cp->out); | |
| if (opts->progress_fd < 0) | |
| close(cp->err); | |
| finish_command(cp); | |
| free(generator); | |
| return -1; | |
| } | |
| fclose(in); | |
| generator->base.out = opts->pack_fd < 0 ? cp->out : -1; | |
| generator->base.err = opts->progress_fd < 0 ? cp->err : -1; | |
| generator->base.finish = odb_pack_generator_files_finish; | |
| *out = &generator->base; | |
| return 0; | |
| } | |
| static int odb_source_files_fsck(struct odb_source *source, | |
| struct odb_fsck_options *opts) | |
| { | |
| struct odb_source_files *files = odb_source_files_downcast(source); | |
| int ret = 0; | |
| if (!(opts->flags & ODB_FSCK_FULL) && !source->local) | |
| return 0; | |
| ret |= odb_source_fsck(&files->loose->base, opts); | |
| ret |= odb_source_fsck(&files->packed->base, opts); | |
| return ret; | |
| } | |
| struct odb_source_files *odb_source_files_new(struct object_database *odb, | |
| const char *path, | |
| bool local) | |
| { | |
| struct odb_source_files *files; | |
| CALLOC_ARRAY(files, 1); | |
| odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local); | |
| files->loose = odb_source_loose_new(odb, path, local); | |
| files->packed = odb_source_packed_new(odb, path, local); | |
| files->base.free = odb_source_files_free; | |
| files->base.close = odb_source_files_close; | |
| files->base.create_on_disk = odb_source_files_create_on_disk; | |
| files->base.prepare = odb_source_files_prepare; | |
| files->base.fsck = odb_source_files_fsck; | |
| files->base.read_object_info = odb_source_files_read_object_info; | |
| files->base.read_object_stream = odb_source_files_read_object_stream; | |
| files->base.for_each_object = odb_source_files_for_each_object; | |
| files->base.count_objects = odb_source_files_count_objects; | |
| files->base.find_abbrev_len = odb_source_files_find_abbrev_len; | |
| files->base.freshen_object = odb_source_files_freshen_object; | |
| files->base.write_object = odb_source_files_write_object; | |
| files->base.write_object_stream = odb_source_files_write_object_stream; | |
| files->base.begin_transaction = odb_source_files_begin_transaction; | |
| files->base.read_alternates = odb_source_files_read_alternates; | |
| files->base.write_alternate = odb_source_files_write_alternate; | |
| files->base.optimize = odb_source_files_optimize; | |
| files->base.optimize_required = odb_source_files_optimize_required; | |
| files->base.generate_pack = odb_source_files_generate_pack; | |
| /* | |
| * Ideally, we would only ever store absolute paths in the source. This | |
| * is not (yet) possible though because we access and assume relative | |
| * paths in the primary ODB source in some user-facing functionality. | |
| */ | |
| if (!is_absolute_path(path)) | |
| chdir_notify_register(odb_source_files_reparent, files); | |
| return files; | |
| } |