Files
Patrick Steinhardt 0ba59c8453 odb/source: drop read_alternates callback
Now that alternates are an implementation detail of the "files" backend
there is no reason anymore to expose them anywhere else. The callback to
read alternates is thus no longer required. Remove it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-10-08 07:31:20 -07:00

1292 lines
36 KiB
C

#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 "quote.h"
#include "repack.h"
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
struct odb_files_dir *odb_files_dir_new(struct object_database *odb,
const char *path, bool local)
{
struct odb_files_dir *dir;
CALLOC_ARRAY(dir, 1);
dir->abspath = absolute_pathdup(path);
dir->local = local;
dir->loose = odb_source_loose_new(odb, path, local);
dir->packed = odb_source_packed_new(odb, path, local);
return dir;
}
void odb_files_dir_free(struct odb_files_dir *dir)
{
if (!dir)
return;
odb_source_free(&dir->loose->base);
odb_source_free(&dir->packed->base);
free(dir->abspath);
free(dir);
}
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);
while (files->dirs) {
struct odb_files_dir *next = files->dirs->next;
odb_files_dir_free(files->dirs);
files->dirs = next;
}
hashmap_clear(&files->dirs_by_path);
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);
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
odb_source_close(&dir->loose->base);
odb_source_close(&dir->packed->base);
}
}
static int odb_source_files_create_on_disk(struct odb_source *source,
const struct odb_create_on_disk_options *opts)
{
struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
struct strset seen = STRSET_INIT;
struct strbuf line = STRBUF_INIT;
int ret;
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);
if (opts->alternates && opts->alternates->nr) {
FILE *alternates, *orig;
strbuf_reset(&path);
strbuf_addf(&path, "%s/info/alternates", source->path);
repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
path.buf, LOCK_DIE_ON_ERROR);
alternates = fdopen_lock_file(&alternates_lock, "w");
if (!alternates) {
ret = error_errno(_("unable to fdopen alternates lockfile"));
goto out;
}
/*
* The alternates file may already exist, e.g. when it has been
* seeded from a template directory. Read any preexisting
* entries so that we don't end up writing duplicates.
*/
orig = fopen(path.buf, "r");
if (orig) {
while (strbuf_getline(&line, orig) != EOF) {
strset_add(&seen, line.buf);
fprintf(alternates, "%s\n", line.buf);
}
if (ferror(orig)) {
ret = error_errno(_("unable to read alternates file"));
fclose(orig);
goto out;
}
fclose(orig);
} else if (errno != ENOENT) {
ret = error_errno(_("unable to read alternates file"));
goto out;
}
for (size_t i = 0; i < opts->alternates->nr; i++) {
const char *alternate = opts->alternates->v[i];
if (!strset_add(&seen, alternate))
continue;
fprintf(alternates, "%s\n", alternate);
}
if (ferror(alternates)) {
ret = error_errno(_("unable to write alternates file"));
goto out;
}
if (commit_lock_file(&alternates_lock)) {
ret = error_errno(_("unable to commit alternates file"));
goto out;
}
}
/* Reprepare the object database to activate alternates. */
odb_reprepare(source->odb);
ret = 0;
out:
rollback_lock_file(&alternates_lock);
strbuf_release(&line);
strbuf_release(&path);
strset_clear(&seen);
return ret;
}
/*
* NEEDSWORK: we're using "core.ignoreCase" to deduplicate alternates that
* _may_ be the same. This requires quite a bit of boilerplate for dubious
* benefit:
*
* - Duplicating alternates should really only lead to regressed performance.
*
* - We don't properly resolve symlinks or mointpoints, so we may still end
* up duplicating alternates.
*
* - The value may be lying, in which case we might deduplicate alternates
* that are in fact not mapping to the same directory.
*
* We should investigate whether we can remove this whole mechanism outright.
*/
static int odb_files_dir_paths_cmp(struct odb_source_files *files,
const char *a, const char *b)
{
if (files->dirs_paths_icase < 0) {
int icase = 0;
repo_config_get_bool(files->base.odb->repo, "core.ignorecase", &icase);
files->dirs_paths_icase = icase;
}
return files->dirs_paths_icase ? strcasecmp(a, b) : strcmp(a, b);
}
static int odb_files_dir_by_path_cmp(const void *cb_data,
const struct hashmap_entry *entry,
const struct hashmap_entry *entry_or_key,
const void *keydata)
{
struct odb_source_files *files = (struct odb_source_files *)cb_data;
const struct odb_files_dir *dir = container_of(entry, const struct odb_files_dir, by_path_entry);
const char *path = keydata;
if (!path)
path = container_of(entry_or_key, const struct odb_files_dir, by_path_entry)->abspath;
return odb_files_dir_paths_cmp(files, dir->abspath, path);
}
/*
* Return non-zero iff the path is usable as an alternate object directory.
*/
static bool odb_files_dir_is_usable(struct odb_source_files *files,
const char *path)
{
struct strbuf normalized_objdir = STRBUF_INIT;
struct hashmap_entry key;
bool usable = false;
strbuf_realpath(&normalized_objdir, files->dirs->abspath, 1);
/* Detect cases where alternate disappeared */
if (!is_directory(path)) {
error(_("object directory %s does not exist; "
"check .git/objects/info/alternates"),
path);
goto out;
}
/*
* Prevent the common mistake of listing the same
* thing twice, or object directory itself.
*/
if (!hashmap_get_size(&files->dirs_by_path)) {
assert(!files->dirs->next);
hashmap_entry_init(&files->dirs->by_path_entry,
strihash(files->dirs->abspath));
hashmap_add(&files->dirs_by_path, &files->dirs->by_path_entry);
}
if (!odb_files_dir_paths_cmp(files, path, normalized_objdir.buf))
goto out;
hashmap_entry_init(&key, strihash(path));
if (hashmap_get(&files->dirs_by_path, &key, path))
goto out;
usable = true;
out:
strbuf_release(&normalized_objdir);
return usable;
}
static void parse_alternates(const char *string,
int sep,
const char *relative_base,
struct strvec *out)
{
struct strbuf pathbuf = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;
if (!string || !*string)
return;
while (*string) {
const char *end;
strbuf_reset(&buf);
strbuf_reset(&pathbuf);
if (*string == '#') {
/* comment; consume up to next separator */
end = strchrnul(string, sep);
} else if (*string == '"' && !unquote_c_style(&buf, string, &end)) {
/*
* quoted path; unquote_c_style has copied the
* data for us and set "end". Broken quoting (e.g.,
* an entry that doesn't end with a quote) falls
* back to the unquoted case below.
*/
} else {
/* normal, unquoted path */
end = strchrnul(string, sep);
strbuf_add(&buf, string, end - string);
}
if (*end)
end++;
string = end;
if (!buf.len)
continue;
if (!is_absolute_path(buf.buf) && relative_base) {
strbuf_realpath(&pathbuf, relative_base, 1);
strbuf_addch(&pathbuf, '/');
}
strbuf_addbuf(&pathbuf, &buf);
strbuf_reset(&buf);
if (!strbuf_realpath(&buf, pathbuf.buf, 0)) {
error(_("unable to normalize alternate object path: %s"),
pathbuf.buf);
continue;
}
/*
* The trailing slash after the directory name is given by
* this function at the end. Remove duplicates.
*/
while (buf.len && buf.buf[buf.len - 1] == '/')
strbuf_setlen(&buf, buf.len - 1);
strvec_push(out, buf.buf);
}
strbuf_release(&pathbuf);
strbuf_release(&buf);
}
static int read_alternates(const char *object_dir, struct strvec *out)
{
struct strbuf buf = STRBUF_INIT;
char *path;
path = xstrfmt("%s/info/alternates", object_dir);
if (strbuf_read_file(&buf, path, 1024) < 0) {
warn_on_fopen_errors(path);
free(path);
return 0;
}
parse_alternates(buf.buf, '\n', object_dir, out);
strbuf_release(&buf);
free(path);
return 0;
}
static void odb_add_alternate_recursively(struct odb_source_files *files,
const char *path,
int depth)
{
struct odb_files_dir *alternate;
struct strvec alternates = STRVEC_INIT;
if (!odb_files_dir_is_usable(files, path))
goto out;
alternate = odb_files_dir_new(files->base.odb, path, false);
/* add the alternate entry */
*files->dirs_tail = alternate;
files->dirs_tail = &(alternate->next);
hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->abspath));
if (hashmap_get(&files->dirs_by_path, &alternate->by_path_entry,
alternate->abspath))
BUG("object directory must not yet exist");
hashmap_add(&files->dirs_by_path, &alternate->by_path_entry);
/* recursively add alternates */
read_alternates(alternate->abspath, &alternates);
if (alternates.nr && depth + 1 > 5) {
error(_("%s: ignoring alternate object stores, nesting too deep"),
path);
} else {
for (size_t i = 0; i < alternates.nr; i++)
odb_add_alternate_recursively(files, alternates.v[i], depth + 1);
}
out:
strvec_clear(&alternates);
}
static void odb_prepare_alternates(struct odb_source_files *files,
const char *alternate_db)
{
struct strvec alternates = STRVEC_INIT;
parse_alternates(alternate_db, PATH_SEP, NULL, &alternates);
read_alternates(files->dirs->abspath, &alternates);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_alternate_recursively(files, alternates.v[i], 0);
strvec_clear(&alternates);
}
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);
/*
* Reprepare alternates, in case the alternates file was modified
* during the course of this process. This only _adds_ directories to
* the linked list, so existing directories will continue to exist
* for the lifetime of the process.
*/
if (flags & ODB_PREPARE_FLUSH_CACHES)
odb_prepare_alternates(files, NULL);
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
odb_source_prepare(&dir->loose->base, flags);
odb_source_prepare(&dir->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 status = ODB_READ_NOT_FOUND;
/*
* Reading an object may fail even though the object exists, for
* example because it is corrupt. Report this failure to the caller in
* case none of the directories was able to read the object, and
* prefer the first such error in case multiple reads have failed.
*/
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
enum odb_read_status ret;
ret = odb_source_read_object_info(&dir->packed->base, oid, oi, flags,
status == ODB_READ_NOT_FOUND ? errmsg : NULL);
if (!ret)
return 0;
if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND)
status = ret;
ret = odb_source_read_object_info(&dir->loose->base, oid, oi, flags,
status == ODB_READ_NOT_FOUND ? errmsg : NULL);
if (!ret)
return 0;
if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND)
status = ret;
}
return status;
}
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);
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next)
if (!odb_source_read_object_stream(out, &dir->packed->base, oid) ||
!odb_source_read_object_stream(out, &dir->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;
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
if (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !dir->local)
continue;
if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {
ret = odb_source_for_each_object(&dir->loose->base, request, cb, cb_data, opts);
if (ret)
return ret;
}
ret = odb_source_for_each_object(&dir->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 = 0;
int ret;
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
unsigned long dir_count;
ret = odb_source_count_objects(&dir->packed->base, flags, &dir_count);
if (ret < 0)
goto out;
count += dir_count;
if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) {
ret = odb_source_count_objects(&dir->loose->base, flags, &dir_count);
if (ret < 0)
goto out;
count += dir_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 = 0;
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
ret = odb_source_find_abbrev_len(&dir->packed->base, oid, len, &len);
if (ret < 0)
goto out;
ret = odb_source_find_abbrev_len(&dir->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);
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next)
if (odb_source_freshen_object(&dir->packed->base, oid, mtime) ||
odb_source_freshen_object(&dir->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->dirs->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->dirs->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 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->dirs->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->dirs->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->dirs->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;
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
if (!(opts->flags & ODB_FSCK_FULL) && !dir->local)
continue;
ret |= odb_source_fsck(&dir->loose->base, opts);
ret |= odb_source_fsck(&dir->packed->base, opts);
}
return ret;
}
struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, const char *obj_dir)
{
struct odb_source_files *files = odb_source_files_downcast(odb->source);
char *obj_dir_real = real_pathdup(obj_dir, 1);
struct strbuf odb_path_real = STRBUF_INIT;
struct odb_files_dir *dir;
for (dir = files->dirs; dir; dir = dir->next) {
strbuf_realpath(&odb_path_real, dir->abspath, 1);
if (!strcmp(obj_dir_real, odb_path_real.buf))
break;
}
free(obj_dir_real);
strbuf_release(&odb_path_real);
return dir;
}
struct odb_source_files *odb_source_files_new(struct object_database *odb,
enum odb_new_flags flags)
{
struct odb_source_files *files;
char *object_dir = NULL;
char *alternates = NULL;
if (flags & ODB_NEW_HONOR_ENV) {
object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
}
if (!object_dir)
object_dir = xstrfmt("%s/objects", odb->repo->commondir);
CALLOC_ARRAY(files, 1);
odb_source_init(&files->base, odb, ODB_SOURCE_FILES, object_dir, true);
hashmap_init(&files->dirs_by_path, odb_files_dir_by_path_cmp, files, 0);
files->dirs_paths_icase = -1;
files->dirs = odb_files_dir_new(odb, object_dir, true);
files->dirs_tail = &files->dirs->next;
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.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(object_dir))
chdir_notify_register(odb_source_files_reparent, files);
odb_prepare_alternates(files, alternates);
free(object_dir);
free(alternates);
return files;
}