#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 " * 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; } 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.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; }