* [PATCH 13/18] loose: refactor object map to operate on `struct odb_source_loose`
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
While the loose object map functions in "loose.c" accept a generic
`struct odb_source *`, they always expect this to be the "files"
backend. Furthermore, the subsystem doesn't even care about the "files"
backend, but only uses it as a stepping stone to get to the "loose"
backend.
This assumption is implicit and thus not immediately obvious. Refactor
the interfaces to instead operate on a `struct odb_source_loose`
instead, which eliminates the implicit dependency and unnecessary detour
via the "files" source.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
loose.c | 45 ++++++++++++++++++++++-----------------------
loose.h | 4 ++--
object-file.c | 9 ++++++---
3 files changed, 30 insertions(+), 28 deletions(-)
diff --git a/loose.c b/loose.c
index f7a3dd1a72..0b626c1b85 100644
--- a/loose.c
+++ b/loose.c
@@ -46,38 +46,36 @@ static int insert_oid_pair(kh_oid_map_t *map, const struct object_id *key, const
return 1;
}
-static int insert_loose_map(struct odb_source *source,
+static int insert_loose_map(struct odb_source_loose *loose,
const struct object_id *oid,
const struct object_id *compat_oid)
{
- struct odb_source_files *files = odb_source_files_downcast(source);
- struct loose_object_map *map = files->loose->map;
+ struct loose_object_map *map = loose->map;
int inserted = 0;
inserted |= insert_oid_pair(map->to_compat, oid, compat_oid);
inserted |= insert_oid_pair(map->to_storage, compat_oid, oid);
if (inserted)
- oidtree_insert(files->loose->cache, compat_oid, NULL);
+ oidtree_insert(loose->cache, compat_oid, NULL);
return inserted;
}
-static int load_one_loose_object_map(struct repository *repo, struct odb_source *source)
+static int load_one_loose_object_map(struct repository *repo, struct odb_source_loose *loose)
{
- struct odb_source_files *files = odb_source_files_downcast(source);
struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
FILE *fp;
- if (!files->loose->map)
- loose_object_map_init(&files->loose->map);
- if (!files->loose->cache) {
- ALLOC_ARRAY(files->loose->cache, 1);
- oidtree_init(files->loose->cache);
+ if (!loose->map)
+ loose_object_map_init(&loose->map);
+ if (!loose->cache) {
+ ALLOC_ARRAY(loose->cache, 1);
+ oidtree_init(loose->cache);
}
- insert_loose_map(source, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree);
- insert_loose_map(source, repo->hash_algo->empty_blob, repo->compat_hash_algo->empty_blob);
- insert_loose_map(source, repo->hash_algo->null_oid, repo->compat_hash_algo->null_oid);
+ insert_loose_map(loose, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree);
+ insert_loose_map(loose, repo->hash_algo->empty_blob, repo->compat_hash_algo->empty_blob);
+ insert_loose_map(loose, repo->hash_algo->null_oid, repo->compat_hash_algo->null_oid);
repo_common_path_replace(repo, &path, "objects/loose-object-idx");
fp = fopen(path.buf, "rb");
@@ -97,7 +95,7 @@ static int load_one_loose_object_map(struct repository *repo, struct odb_source
parse_oid_hex_algop(p, &compat_oid, &p, repo->compat_hash_algo) ||
p != buf.buf + buf.len)
goto err;
- insert_loose_map(source, &oid, &compat_oid);
+ insert_loose_map(loose, &oid, &compat_oid);
}
strbuf_release(&buf);
@@ -119,7 +117,8 @@ int repo_read_loose_object_map(struct repository *repo)
odb_prepare_alternates(repo->objects);
for (source = repo->objects->sources; source; source = source->next) {
- if (load_one_loose_object_map(repo, source) < 0) {
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ if (load_one_loose_object_map(repo, files->loose) < 0) {
return -1;
}
}
@@ -171,7 +170,7 @@ int repo_write_loose_object_map(struct repository *repo)
return -1;
}
-static int write_one_object(struct odb_source *source,
+static int write_one_object(struct odb_source_loose *loose,
const struct object_id *oid,
const struct object_id *compat_oid)
{
@@ -180,7 +179,7 @@ static int write_one_object(struct odb_source *source,
struct stat st;
struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
- strbuf_addf(&path, "%s/loose-object-idx", source->path);
+ strbuf_addf(&path, "%s/loose-object-idx", loose->base.path);
hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);
fd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666);
@@ -196,7 +195,7 @@ static int write_one_object(struct odb_source *source,
goto errout;
if (close(fd))
goto errout;
- adjust_shared_perm(source->odb->repo, path.buf);
+ adjust_shared_perm(loose->base.odb->repo, path.buf);
rollback_lock_file(&lock);
strbuf_release(&buf);
strbuf_release(&path);
@@ -210,18 +209,18 @@ static int write_one_object(struct odb_source *source,
return -1;
}
-int repo_add_loose_object_map(struct odb_source *source,
+int repo_add_loose_object_map(struct odb_source_loose *loose,
const struct object_id *oid,
const struct object_id *compat_oid)
{
int inserted = 0;
- if (!should_use_loose_object_map(source->odb->repo))
+ if (!should_use_loose_object_map(loose->base.odb->repo))
return 0;
- inserted = insert_loose_map(source, oid, compat_oid);
+ inserted = insert_loose_map(loose, oid, compat_oid);
if (inserted)
- return write_one_object(source, oid, compat_oid);
+ return write_one_object(loose, oid, compat_oid);
return 0;
}
diff --git a/loose.h b/loose.h
index 6af1702973..6c9b3f4571 100644
--- a/loose.h
+++ b/loose.h
@@ -4,7 +4,7 @@
#include "khash.h"
struct repository;
-struct odb_source;
+struct odb_source_loose;
struct loose_object_map {
kh_oid_map_t *to_compat;
@@ -17,7 +17,7 @@ int repo_loose_object_map_oid(struct repository *repo,
const struct object_id *src,
const struct git_hash_algo *dest_algo,
struct object_id *dest);
-int repo_add_loose_object_map(struct odb_source *source,
+int repo_add_loose_object_map(struct odb_source_loose *loose,
const struct object_id *oid,
const struct object_id *compat_oid);
int repo_read_loose_object_map(struct repository *repo);
diff --git a/object-file.c b/object-file.c
index 0689a4e67b..fe24f00d1b 100644
--- a/object-file.c
+++ b/object-file.c
@@ -810,6 +810,7 @@ int odb_source_loose_write_stream(struct odb_source *source,
struct odb_write_stream *in_stream, size_t len,
struct object_id *oid)
{
+ struct odb_source_files *files = odb_source_files_downcast(source);
const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
struct object_id compat_oid;
int fd, ret, err = 0, flush = 0;
@@ -918,7 +919,7 @@ int odb_source_loose_write_stream(struct odb_source *source,
err = finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf,
FOF_SKIP_COLLISION_CHECK);
if (!err && compat)
- err = repo_add_loose_object_map(source, oid, &compat_oid);
+ err = repo_add_loose_object_map(files->loose, oid, &compat_oid);
cleanup:
strbuf_release(&tmp_file);
strbuf_release(&filename);
@@ -931,6 +932,7 @@ int odb_source_loose_write_object(struct odb_source *source,
struct object_id *compat_oid_in,
enum odb_write_object_flags flags)
{
+ struct odb_source_files *files = odb_source_files_downcast(source);
const struct git_hash_algo *algo = source->odb->repo->hash_algo;
const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
struct object_id compat_oid;
@@ -962,13 +964,14 @@ int odb_source_loose_write_object(struct odb_source *source,
if (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags))
return -1;
if (compat)
- return repo_add_loose_object_map(source, oid, &compat_oid);
+ return repo_add_loose_object_map(files->loose, oid, &compat_oid);
return 0;
}
int force_object_loose(struct odb_source *source,
const struct object_id *oid, time_t mtime)
{
+ struct odb_source_files *files = odb_source_files_downcast(source);
const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
void *buf;
unsigned long len;
@@ -998,7 +1001,7 @@ int force_object_loose(struct odb_source *source,
hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
ret = write_loose_object(source, oid, hdr, hdrlen, buf, len, mtime, 0);
if (!ret && compat)
- ret = repo_add_loose_object_map(source, oid, &compat_oid);
+ ret = repo_add_loose_object_map(files->loose, oid, &compat_oid);
free(buf);
return ret;
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 12/18] odb/source-loose: wire up `freshen_object()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_freshen_object()` from "object-file.c" into
"odb/source-loose.c" and wire it up as the `freshen_object()` callback
of the loose source.
As part of the move, `check_and_freshen_source()` is inlined into the
callback function, as it has no other callers anymore.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 15 ---------------
object-file.h | 3 ---
odb/source-files.c | 2 +-
odb/source-loose.c | 9 +++++++++
4 files changed, 10 insertions(+), 19 deletions(-)
diff --git a/object-file.c b/object-file.c
index c83136cf70..0689a4e67b 100644
--- a/object-file.c
+++ b/object-file.c
@@ -87,15 +87,6 @@ int check_and_freshen_file(const char *fn, int freshen)
return 1;
}
-static int check_and_freshen_source(struct odb_source *source,
- const struct object_id *oid,
- int freshen)
-{
- static struct strbuf path = STRBUF_INIT;
- odb_loose_path(source, &path, oid);
- return check_and_freshen_file(path.buf, freshen);
-}
-
int format_object_header(char *str, size_t size, enum object_type type,
size_t objsize)
{
@@ -815,12 +806,6 @@ static int write_loose_object(struct odb_source *source,
FOF_SKIP_COLLISION_CHECK);
}
-int odb_source_loose_freshen_object(struct odb_source *source,
- const struct object_id *oid)
-{
- return !!check_and_freshen_source(source, oid, 1);
-}
-
int odb_source_loose_write_stream(struct odb_source *source,
struct odb_write_stream *in_stream, size_t len,
struct object_id *oid)
diff --git a/object-file.h b/object-file.h
index 506ca6be40..1d90df9d98 100644
--- a/object-file.h
+++ b/object-file.h
@@ -23,9 +23,6 @@ int index_path(struct index_state *istate, struct object_id *oid, const char *pa
struct object_info;
struct odb_source;
-int odb_source_loose_freshen_object(struct odb_source *source,
- const struct object_id *oid);
-
int odb_source_loose_write_object(struct odb_source *source,
const void *buf, unsigned long len,
enum object_type type, struct object_id *oid,
diff --git a/odb/source-files.c b/odb/source-files.c
index d5454e170d..ef548e6fe6 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -152,7 +152,7 @@ static int odb_source_files_freshen_object(struct odb_source *source,
{
struct odb_source_files *files = odb_source_files_downcast(source);
if (packfile_store_freshen_object(files->packed, oid) ||
- odb_source_loose_freshen_object(source, oid))
+ odb_source_freshen_object(&files->loose->base, oid))
return 1;
return 0;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 27be066327..e519365d23 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -580,6 +580,14 @@ static int odb_source_loose_count_objects(struct odb_source *source,
return ret;
}
+static int odb_source_loose_freshen_object(struct odb_source *source,
+ const struct object_id *oid)
+{
+ static struct strbuf path = STRBUF_INIT;
+ odb_loose_path(source, &path, oid);
+ return !!check_and_freshen_file(path.buf, 1);
+}
+
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -638,6 +646,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.for_each_object = odb_source_loose_for_each_object;
loose->base.find_abbrev_len = odb_source_loose_find_abbrev_len;
loose->base.count_objects = odb_source_loose_count_objects;
+ loose->base.freshen_object = odb_source_loose_freshen_object;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 11/18] odb/source-loose: drop `odb_source_loose_has_object()`
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
The function `odb_source_loose_has_object()` checks whether a specific
object exists as a loose object on disk by using lstat(3p). This
interface is somewhat redundant, as we typically check for object
existence in a generic way via `odb_source_read_object_info()`.
In fact, these two calls are redundant in case the latter is called in a
specific way: when called without an object info request and without the
`OBJECT_INFO_QUICK` flag, then we will end up doing the same call to
lstat(3p) in `read_object_info_from_path()`.
Drop the function and adapt callers to instead use the generic
interface so that its calling conventions align with that of other
sources.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/pack-objects.c | 12 ++++++++----
object-file.c | 12 ++++--------
object-file.h | 8 --------
3 files changed, 12 insertions(+), 20 deletions(-)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 480cc0bd8c..a6be3d659f 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1750,9 +1750,11 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
* skip the local object source.
*/
struct odb_source *source = the_repository->objects->sources->next;
- for (; source; source = source->next)
- if (odb_source_loose_has_object(source, oid))
+ for (; source; source = source->next) {
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0))
return 0;
+ }
}
/*
@@ -4135,9 +4137,11 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type
struct odb_source *source = the_repository->objects->sources;
int found = 0;
- for (; !found && source; source = source->next)
- if (odb_source_loose_has_object(source, oid))
+ for (; !found && source; source = source->next) {
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0))
found = 1;
+ }
/*
* If a traversed tree has a missing blob then we want
diff --git a/object-file.c b/object-file.c
index 9b2044de37..c83136cf70 100644
--- a/object-file.c
+++ b/object-file.c
@@ -96,12 +96,6 @@ static int check_and_freshen_source(struct odb_source *source,
return check_and_freshen_file(path.buf, freshen);
}
-int odb_source_loose_has_object(struct odb_source *source,
- const struct object_id *oid)
-{
- return check_and_freshen_source(source, oid, 0);
-}
-
int format_object_header(char *str, size_t size, enum object_type type,
size_t objsize)
{
@@ -1000,9 +994,11 @@ int force_object_loose(struct odb_source *source,
int hdrlen;
int ret;
- for (struct odb_source *s = source->odb->sources; s; s = s->next)
- if (odb_source_loose_has_object(s, oid))
+ for (struct odb_source *s = source->odb->sources; s; s = s->next) {
+ struct odb_source_files *files = odb_source_files_downcast(s);
+ if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0))
return 0;
+ }
oi.typep = &type;
oi.sizep = &len;
diff --git a/object-file.h b/object-file.h
index bc72d89f54..506ca6be40 100644
--- a/object-file.h
+++ b/object-file.h
@@ -23,14 +23,6 @@ int index_path(struct index_state *istate, struct object_id *oid, const char *pa
struct object_info;
struct odb_source;
-/*
- * Return true iff an object database source has a loose object
- * with the specified name. This function does not respect replace
- * references.
- */
-int odb_source_loose_has_object(struct odb_source *source,
- const struct object_id *oid);
-
int odb_source_loose_freshen_object(struct odb_source *source,
const struct object_id *oid);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 10/18] odb/source-loose: wire up `count_objects()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_count_objects()` and its associated helpers from
"object-file.c" into "odb/source-loose.c" and wire it up as the
`count_objects()` callback of the loose source.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/gc.c | 6 +++---
object-file.c | 60 -----------------------------------------------------
object-file.h | 14 -------------
odb/source-files.c | 2 +-
odb/source-loose.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 65 insertions(+), 78 deletions(-)
diff --git a/builtin/gc.c b/builtin/gc.c
index 84a66d3240..c26c93ee0f 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -466,6 +466,7 @@ static int rerere_gc_condition(struct gc_config *cfg UNUSED)
static int too_many_loose_objects(int limit)
{
+ struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
/*
* This is weird, but stems from legacy behaviour: the GC auto
* threshold was always essentially interpreted as if it was rounded up
@@ -474,9 +475,8 @@ static int too_many_loose_objects(int limit)
int auto_threshold = DIV_ROUND_UP(limit, 256) * 256;
unsigned long loose_count;
- if (odb_source_loose_count_objects(the_repository->objects->sources,
- ODB_COUNT_OBJECTS_APPROXIMATE,
- &loose_count) < 0)
+ if (odb_source_count_objects(&files->loose->base, ODB_COUNT_OBJECTS_APPROXIMATE,
+ &loose_count) < 0)
return 0;
return loose_count > auto_threshold;
diff --git a/object-file.c b/object-file.c
index 11957aa44f..9b2044de37 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1602,66 +1602,6 @@ int for_each_loose_file_in_source(struct odb_source *source,
return r;
}
-static int count_loose_object(const struct object_id *oid UNUSED,
- struct object_info *oi UNUSED,
- void *payload)
-{
- unsigned long *count = payload;
- (*count)++;
- return 0;
-}
-
-int odb_source_loose_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);
- const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2;
- char *path = NULL;
- DIR *dir = NULL;
- int ret;
-
- if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) {
- unsigned long count = 0;
- struct dirent *ent;
-
- path = xstrfmt("%s/17", source->path);
-
- dir = opendir(path);
- if (!dir) {
- if (errno == ENOENT) {
- *out = 0;
- ret = 0;
- goto out;
- }
-
- ret = error_errno("cannot open object shard '%s'", path);
- goto out;
- }
-
- while ((ent = readdir(dir)) != NULL) {
- if (strspn(ent->d_name, "0123456789abcdef") != hexsz ||
- ent->d_name[hexsz] != '\0')
- continue;
- count++;
- }
-
- *out = count * 256;
- ret = 0;
- } else {
- struct odb_for_each_object_options opts = { 0 };
- *out = 0;
- ret = odb_source_for_each_object(&files->loose->base, NULL, count_loose_object,
- out, &opts);
- }
-
-out:
- if (dir)
- closedir(dir);
- free(path);
- return ret;
-}
-
static int check_stream_oid(git_zstream *stream,
const char *hdr,
unsigned long size,
diff --git a/object-file.h b/object-file.h
index 96760db0e1..bc72d89f54 100644
--- a/object-file.h
+++ b/object-file.h
@@ -96,20 +96,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
each_loose_subdir_fn subdir_cb,
void *data);
-/*
- * Count the number of loose objects in this source.
- *
- * The object count is approximated by opening a single sharding directory for
- * loose objects and scanning its contents. The result is then extrapolated by
- * 256. This should generally work as a reasonable estimate given that the
- * object hash is supposed to be indistinguishable from random.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-int odb_source_loose_count_objects(struct odb_source *source,
- enum odb_count_objects_flags flags,
- unsigned long *out);
-
/**
* format_object_header() is a thin wrapper around s xsnprintf() that
* writes the initial "<type> <obj-len>" part of the loose object
diff --git a/odb/source-files.c b/odb/source-files.c
index 4a54b10e4a..d5454e170d 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -109,7 +109,7 @@ static int odb_source_files_count_objects(struct odb_source *source,
if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) {
unsigned long loose_count;
- ret = odb_source_loose_count_objects(source, flags, &loose_count);
+ ret = odb_source_count_objects(&files->loose->base, flags, &loose_count);
if (ret < 0)
goto out;
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 4b8d10bc87..27be066327 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -520,6 +520,66 @@ static int odb_source_loose_find_abbrev_len(struct odb_source *source,
return ret;
}
+static int count_loose_object(const struct object_id *oid UNUSED,
+ struct object_info *oi UNUSED,
+ void *payload)
+{
+ unsigned long *count = payload;
+ (*count)++;
+ return 0;
+}
+
+static int odb_source_loose_count_objects(struct odb_source *source,
+ enum odb_count_objects_flags flags,
+ unsigned long *out)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2;
+ char *path = NULL;
+ DIR *dir = NULL;
+ int ret;
+
+ if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) {
+ unsigned long count = 0;
+ struct dirent *ent;
+
+ path = xstrfmt("%s/17", source->path);
+
+ dir = opendir(path);
+ if (!dir) {
+ if (errno == ENOENT) {
+ *out = 0;
+ ret = 0;
+ goto out;
+ }
+
+ ret = error_errno("cannot open object shard '%s'", path);
+ goto out;
+ }
+
+ while ((ent = readdir(dir)) != NULL) {
+ if (strspn(ent->d_name, "0123456789abcdef") != hexsz ||
+ ent->d_name[hexsz] != '\0')
+ continue;
+ count++;
+ }
+
+ *out = count * 256;
+ ret = 0;
+ } else {
+ struct odb_for_each_object_options opts = { 0 };
+ *out = 0;
+ ret = odb_source_for_each_object(&loose->base, NULL, count_loose_object,
+ out, &opts);
+ }
+
+out:
+ if (dir)
+ closedir(dir);
+ free(path);
+ return ret;
+}
+
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -577,6 +637,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.read_object_stream = odb_source_loose_read_object_stream;
loose->base.for_each_object = odb_source_loose_for_each_object;
loose->base.find_abbrev_len = odb_source_loose_find_abbrev_len;
+ loose->base.count_objects = odb_source_loose_count_objects;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 09/18] odb/source-loose: wire up `find_abbrev_len()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_find_abbrev_len()` and its associated helpers
from "object-file.c" into "odb/source-loose.c" and wire it up as the
`find_abbrev_len` callback of the loose source.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 39 ---------------------------------------
object-file.h | 12 ------------
odb/source-files.c | 2 +-
odb/source-loose.c | 40 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 41 insertions(+), 52 deletions(-)
diff --git a/object-file.c b/object-file.c
index 157ecad3ea..11957aa44f 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1662,45 +1662,6 @@ int odb_source_loose_count_objects(struct odb_source *source,
return ret;
}
-struct find_abbrev_len_data {
- const struct object_id *oid;
- unsigned len;
-};
-
-static int find_abbrev_len_cb(const struct object_id *oid,
- struct object_info *oi UNUSED,
- void *cb_data)
-{
- struct find_abbrev_len_data *data = cb_data;
- unsigned len = oid_common_prefix_hexlen(oid, data->oid);
- if (len != hash_algos[oid->algo].hexsz && len >= data->len)
- data->len = len + 1;
- return 0;
-}
-
-int odb_source_loose_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);
- struct odb_for_each_object_options opts = {
- .prefix = oid,
- .prefix_hex_len = min_len,
- };
- struct find_abbrev_len_data data = {
- .oid = oid,
- .len = min_len,
- };
- int ret;
-
- ret = odb_source_for_each_object(&files->loose->base, NULL, find_abbrev_len_cb,
- &data, &opts);
- *out = data.len;
-
- return ret;
-}
-
static int check_stream_oid(git_zstream *stream,
const char *hdr,
unsigned long size,
diff --git a/object-file.h b/object-file.h
index 9ee5649220..96760db0e1 100644
--- a/object-file.h
+++ b/object-file.h
@@ -110,18 +110,6 @@ int odb_source_loose_count_objects(struct odb_source *source,
enum odb_count_objects_flags flags,
unsigned long *out);
-/*
- * Find the shortest unique prefix for the given object ID, where `min_len` is
- * the minimum length that the prefix should have.
- *
- * Returns 0 on success, in which case the computed length will be written to
- * `out`. Otherwise, a negative error code is returned.
- */
-int odb_source_loose_find_abbrev_len(struct odb_source *source,
- const struct object_id *oid,
- unsigned min_len,
- unsigned *out);
-
/**
* format_object_header() is a thin wrapper around s xsnprintf() that
* writes the initial "<type> <obj-len>" part of the loose object
diff --git a/odb/source-files.c b/odb/source-files.c
index 676a641739..4a54b10e4a 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -136,7 +136,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,
if (ret < 0)
goto out;
- ret = odb_source_loose_find_abbrev_len(source, oid, len, &len);
+ ret = odb_source_find_abbrev_len(&files->loose->base, oid, len, &len);
if (ret < 0)
goto out;
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 4e8b923498..4b8d10bc87 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -481,6 +481,45 @@ static int odb_source_loose_for_each_object(struct odb_source *source,
NULL, NULL, &data);
}
+struct find_abbrev_len_data {
+ const struct object_id *oid;
+ unsigned len;
+};
+
+static int find_abbrev_len_cb(const struct object_id *oid,
+ struct object_info *oi UNUSED,
+ void *cb_data)
+{
+ struct find_abbrev_len_data *data = cb_data;
+ unsigned len = oid_common_prefix_hexlen(oid, data->oid);
+ if (len != hash_algos[oid->algo].hexsz && len >= data->len)
+ data->len = len + 1;
+ return 0;
+}
+
+static int odb_source_loose_find_abbrev_len(struct odb_source *source,
+ const struct object_id *oid,
+ unsigned min_len,
+ unsigned *out)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ struct odb_for_each_object_options opts = {
+ .prefix = oid,
+ .prefix_hex_len = min_len,
+ };
+ struct find_abbrev_len_data data = {
+ .oid = oid,
+ .len = min_len,
+ };
+ int ret;
+
+ ret = odb_source_for_each_object(&loose->base, NULL, find_abbrev_len_cb,
+ &data, &opts);
+ *out = data.len;
+
+ return ret;
+}
+
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -537,6 +576,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.read_object_info = odb_source_loose_read_object_info;
loose->base.read_object_stream = odb_source_loose_read_object_stream;
loose->base.for_each_object = odb_source_loose_for_each_object;
+ loose->base.find_abbrev_len = odb_source_loose_find_abbrev_len;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 08/18] odb/source-loose: wire up `for_each_object()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_for_each_object()` and its associated helpers
from "object-file.c" into "odb/source-loose.c" and wire it up as the
`for_each_object()` callback of the loose source.
Again, as in the preceding commit, we are forced to expose a couple of
functions from "object-file.c" that are now used by both subsystems.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/cat-file.c | 5 +-
object-file.c | 299 +++--------------------------------------------------
object-file.h | 32 +++---
odb/source-files.c | 2 +-
odb/source-loose.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 297 insertions(+), 305 deletions(-)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index d9fbad5358..2958fc5357 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -862,8 +862,9 @@ static void batch_each_object(struct batch_options *opt,
*/
odb_prepare_alternates(the_repository->objects);
for (source = the_repository->objects->sources; source; source = source->next) {
- int ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,
- &payload, &opts);
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ int ret = odb_source_for_each_object(&files->loose->base, NULL, batch_one_object_oi,
+ &payload, &opts);
if (ret)
break;
}
diff --git a/object-file.c b/object-file.c
index adfb672493..157ecad3ea 100644
--- a/object-file.c
+++ b/object-file.c
@@ -22,7 +22,6 @@
#include "odb.h"
#include "odb/streaming.h"
#include "odb/transaction.h"
-#include "oidtree.h"
#include "pack.h"
#include "packfile.h"
#include "path.h"
@@ -31,12 +30,6 @@
#include "tempfile.h"
#include "tmp-objdir.h"
-/* The maximum size for an object header. */
-#define MAX_HEADER_LEN 32
-
-static struct oidtree *odb_source_loose_cache(struct odb_source *source,
- const struct object_id *oid);
-
static int get_conv_flags(unsigned flags)
{
if (flags & INDEX_RENORMALIZE)
@@ -164,12 +157,6 @@ int stream_object_signature(struct repository *r,
return !oideq(oid, &real_oid) ? -1 : 0;
}
-static int quick_has_loose(struct odb_source_loose *loose,
- const struct object_id *oid)
-{
- return !!oidtree_contains(odb_source_loose_cache(&loose->files->base, oid), oid);
-}
-
/*
* Map and close the given loose object fd. The path argument is used for
* error reporting.
@@ -227,9 +214,9 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
return ULHR_TOO_LONG;
}
-static void *unpack_loose_rest(git_zstream *stream,
- void *buffer, unsigned long size,
- const struct object_id *oid)
+void *unpack_loose_rest(git_zstream *stream,
+ void *buffer, unsigned long size,
+ const struct object_id *oid)
{
size_t bytes = strlen(buffer) + 1, n;
unsigned char *buf = xmallocz(size);
@@ -343,149 +330,6 @@ int parse_loose_header(const char *hdr, struct object_info *oi)
return 0;
}
-int read_object_info_from_path(struct odb_source_loose *loose,
- const char *path,
- const struct object_id *oid,
- struct object_info *oi,
- enum object_info_flags flags)
-{
- int ret;
- int fd;
- unsigned long mapsize;
- void *map = NULL;
- git_zstream stream, *stream_to_end = NULL;
- char hdr[MAX_HEADER_LEN];
- unsigned long size_scratch;
- enum object_type type_scratch;
- struct stat st;
-
- /*
- * If we don't care about type or size, then we don't
- * need to look inside the object at all. Note that we
- * do not optimize out the stat call, even if the
- * caller doesn't care about the disk-size, since our
- * return value implicitly indicates whether the
- * object even exists.
- */
- if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {
- struct stat st;
-
- if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {
- ret = quick_has_loose(loose, oid) ? 0 : -1;
- goto out;
- }
-
- if (lstat(path, &st) < 0) {
- ret = -1;
- goto out;
- }
-
- if (oi) {
- if (oi->disk_sizep)
- *oi->disk_sizep = st.st_size;
- if (oi->mtimep)
- *oi->mtimep = st.st_mtime;
- }
-
- ret = 0;
- goto out;
- }
-
- fd = git_open(path);
- if (fd < 0) {
- if (errno != ENOENT)
- error_errno(_("unable to open loose object %s"), oid_to_hex(oid));
- ret = -1;
- goto out;
- }
-
- if (fstat(fd, &st)) {
- close(fd);
- ret = -1;
- goto out;
- }
-
- mapsize = xsize_t(st.st_size);
- if (!mapsize) {
- close(fd);
- ret = error(_("object file %s is empty"), path);
- goto out;
- }
-
- map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
- close(fd);
- if (!map) {
- ret = -1;
- goto out;
- }
-
- if (oi->disk_sizep)
- *oi->disk_sizep = mapsize;
- if (oi->mtimep)
- *oi->mtimep = st.st_mtime;
-
- stream_to_end = &stream;
-
- switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) {
- case ULHR_OK:
- if (!oi->sizep)
- oi->sizep = &size_scratch;
- if (!oi->typep)
- oi->typep = &type_scratch;
-
- if (parse_loose_header(hdr, oi) < 0) {
- ret = error(_("unable to parse %s header"), oid_to_hex(oid));
- goto corrupt;
- }
-
- if (*oi->typep < 0)
- die(_("invalid object type"));
-
- if (oi->contentp) {
- *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
- if (!*oi->contentp) {
- ret = -1;
- goto corrupt;
- }
- }
-
- break;
- case ULHR_BAD:
- ret = error(_("unable to unpack %s header"),
- oid_to_hex(oid));
- goto corrupt;
- case ULHR_TOO_LONG:
- ret = error(_("header for %s too long, exceeds %d bytes"),
- oid_to_hex(oid), MAX_HEADER_LEN);
- goto corrupt;
- }
-
- ret = 0;
-
-corrupt:
- if (ret && (flags & OBJECT_INFO_DIE_IF_CORRUPT))
- die(_("loose object %s (stored in %s) is corrupt"),
- oid_to_hex(oid), path);
-
-out:
- if (stream_to_end)
- git_inflate_end(stream_to_end);
- if (map)
- munmap(map, mapsize);
- if (oi) {
- if (oi->sizep == &size_scratch)
- oi->sizep = NULL;
- if (oi->typep == &type_scratch)
- oi->typep = NULL;
- if (oi->delta_base_oid)
- oidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);
- if (!ret)
- oi->whence = OI_LOOSE;
- }
-
- return ret;
-}
-
static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,
const void *buf, unsigned long len,
struct object_id *oid,
@@ -1667,13 +1511,13 @@ int read_pack_header(int fd, struct pack_header *header)
return 0;
}
-static int for_each_file_in_obj_subdir(unsigned int subdir_nr,
- struct strbuf *path,
- const struct git_hash_algo *algop,
- each_loose_object_fn obj_cb,
- each_loose_cruft_fn cruft_cb,
- each_loose_subdir_fn subdir_cb,
- void *data)
+int for_each_file_in_obj_subdir(unsigned int subdir_nr,
+ struct strbuf *path,
+ const struct git_hash_algo *algop,
+ each_loose_object_fn obj_cb,
+ each_loose_cruft_fn cruft_cb,
+ each_loose_subdir_fn subdir_cb,
+ void *data)
{
size_t origlen, baselen;
DIR *dir;
@@ -1758,78 +1602,6 @@ int for_each_loose_file_in_source(struct odb_source *source,
return r;
}
-struct for_each_object_wrapper_data {
- struct odb_source_loose *loose;
- const struct object_info *request;
- odb_for_each_object_cb cb;
- void *cb_data;
-};
-
-static int for_each_object_wrapper_cb(const struct object_id *oid,
- const char *path,
- void *cb_data)
-{
- struct for_each_object_wrapper_data *data = cb_data;
-
- if (data->request) {
- struct object_info oi = *data->request;
-
- if (read_object_info_from_path(data->loose, path, oid, &oi, 0) < 0)
- return -1;
-
- return data->cb(oid, &oi, data->cb_data);
- } else {
- return data->cb(oid, NULL, data->cb_data);
- }
-}
-
-static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid,
- void *node_data UNUSED,
- void *cb_data)
-{
- struct for_each_object_wrapper_data *data = cb_data;
- if (data->request) {
- struct object_info oi = *data->request;
-
- if (odb_source_read_object_info(&data->loose->base,
- oid, &oi, 0) < 0)
- return -1;
-
- return data->cb(oid, &oi, data->cb_data);
- } else {
- return data->cb(oid, NULL, data->cb_data);
- }
-}
-
-int odb_source_loose_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);
- struct for_each_object_wrapper_data data = {
- .loose = files->loose,
- .request = request,
- .cb = cb,
- .cb_data = cb_data,
- };
-
- /* There are no loose promisor objects, so we can return immediately. */
- if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))
- return 0;
- if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)
- return 0;
-
- if (opts->prefix)
- return oidtree_each(odb_source_loose_cache(source, opts->prefix),
- opts->prefix, opts->prefix_hex_len,
- for_each_prefixed_object_wrapper_cb, &data);
-
- return for_each_loose_file_in_source(source, for_each_object_wrapper_cb,
- NULL, NULL, &data);
-}
-
static int count_loose_object(const struct object_id *oid UNUSED,
struct object_info *oi UNUSED,
void *payload)
@@ -1843,6 +1615,7 @@ int odb_source_loose_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);
const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2;
char *path = NULL;
DIR *dir = NULL;
@@ -1878,8 +1651,8 @@ int odb_source_loose_count_objects(struct odb_source *source,
} else {
struct odb_for_each_object_options opts = { 0 };
*out = 0;
- ret = odb_source_loose_for_each_object(source, NULL, count_loose_object,
- out, &opts);
+ ret = odb_source_for_each_object(&files->loose->base, NULL, count_loose_object,
+ out, &opts);
}
out:
@@ -1910,6 +1683,7 @@ int odb_source_loose_find_abbrev_len(struct odb_source *source,
unsigned min_len,
unsigned *out)
{
+ struct odb_source_files *files = odb_source_files_downcast(source);
struct odb_for_each_object_options opts = {
.prefix = oid,
.prefix_hex_len = min_len,
@@ -1920,54 +1694,13 @@ int odb_source_loose_find_abbrev_len(struct odb_source *source,
};
int ret;
- ret = odb_source_loose_for_each_object(source, NULL, find_abbrev_len_cb,
- &data, &opts);
+ ret = odb_source_for_each_object(&files->loose->base, NULL, find_abbrev_len_cb,
+ &data, &opts);
*out = data.len;
return ret;
}
-static int append_loose_object(const struct object_id *oid,
- const char *path UNUSED,
- void *data)
-{
- oidtree_insert(data, oid, NULL);
- return 0;
-}
-
-static struct oidtree *odb_source_loose_cache(struct odb_source *source,
- const struct object_id *oid)
-{
- struct odb_source_files *files = odb_source_files_downcast(source);
- int subdir_nr = oid->hash[0];
- struct strbuf buf = STRBUF_INIT;
- size_t word_bits = bitsizeof(files->loose->subdir_seen[0]);
- size_t word_index = subdir_nr / word_bits;
- size_t mask = (size_t)1u << (subdir_nr % word_bits);
- uint32_t *bitmap;
-
- if (subdir_nr < 0 ||
- (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen))
- BUG("subdir_nr out of range");
-
- bitmap = &files->loose->subdir_seen[word_index];
- if (*bitmap & mask)
- return files->loose->cache;
- if (!files->loose->cache) {
- ALLOC_ARRAY(files->loose->cache, 1);
- oidtree_init(files->loose->cache);
- }
- strbuf_addstr(&buf, source->path);
- for_each_file_in_obj_subdir(subdir_nr, &buf,
- source->odb->repo->hash_algo,
- append_loose_object,
- NULL, NULL,
- files->loose->cache);
- *bitmap |= mask;
- strbuf_release(&buf);
- return files->loose->cache;
-}
-
static int check_stream_oid(git_zstream *stream,
const char *hdr,
unsigned long size,
diff --git a/object-file.h b/object-file.h
index d93b7ffad7..9ee5649220 100644
--- a/object-file.h
+++ b/object-file.h
@@ -6,6 +6,9 @@
#include "odb.h"
#include "odb/source-loose.h"
+/* The maximum size for an object header. */
+#define MAX_HEADER_LEN 32
+
struct index_state;
enum {
@@ -85,19 +88,13 @@ int for_each_loose_file_in_source(struct odb_source *source,
each_loose_cruft_fn cruft_cb,
each_loose_subdir_fn subdir_cb,
void *data);
-
-/*
- * Iterate through all loose objects in the given object database source and
- * invoke the callback function for each of them. If an object info request is
- * given, then the object info will be read for every individual object and
- * passed to the callback as if `odb_source_loose_read_object_info()` was
- * called for the object.
- */
-int odb_source_loose_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);
+int for_each_file_in_obj_subdir(unsigned int subdir_nr,
+ struct strbuf *path,
+ const struct git_hash_algo *algop,
+ each_loose_object_fn obj_cb,
+ each_loose_cruft_fn cruft_cb,
+ each_loose_subdir_fn subdir_cb,
+ void *data);
/*
* Count the number of loose objects in this source.
@@ -188,12 +185,6 @@ int read_loose_object(struct repository *repo,
void **contents,
struct object_info *oi);
-int read_object_info_from_path(struct odb_source_loose *loose,
- const char *path,
- const struct object_id *oid,
- struct object_info *oi,
- enum object_info_flags flags);
-
enum unpack_loose_header_result {
ULHR_OK,
ULHR_BAD,
@@ -217,6 +208,9 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
unsigned long mapsize,
void *buffer,
unsigned long bufsiz);
+void *unpack_loose_rest(git_zstream *stream,
+ void *buffer, unsigned long size,
+ const struct object_id *oid);
int parse_loose_header(const char *hdr, struct object_info *oi);
diff --git a/odb/source-files.c b/odb/source-files.c
index 90806ddf86..676a641739 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -82,7 +82,7 @@ static int odb_source_files_for_each_object(struct odb_source *source,
int ret;
if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {
- ret = odb_source_loose_for_each_object(source, request, cb, cb_data, opts);
+ ret = odb_source_for_each_object(&files->loose->base, request, cb, cb_data, opts);
if (ret)
return ret;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 4b82c6f316..4e8b923498 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -2,6 +2,7 @@
#include "abspath.h"
#include "chdir-notify.h"
#include "gettext.h"
+#include "hex.h"
#include "loose.h"
#include "object-file.h"
#include "odb.h"
@@ -9,8 +10,198 @@
#include "odb/source-loose.h"
#include "odb/streaming.h"
#include "oidtree.h"
+#include "repository.h"
#include "strbuf.h"
+static int append_loose_object(const struct object_id *oid,
+ const char *path UNUSED,
+ void *data)
+{
+ oidtree_insert(data, oid, NULL);
+ return 0;
+}
+
+static struct oidtree *odb_source_loose_cache(struct odb_source_loose *loose,
+ const struct object_id *oid)
+{
+ int subdir_nr = oid->hash[0];
+ struct strbuf buf = STRBUF_INIT;
+ size_t word_bits = bitsizeof(loose->subdir_seen[0]);
+ size_t word_index = subdir_nr / word_bits;
+ size_t mask = (size_t)1u << (subdir_nr % word_bits);
+ uint32_t *bitmap;
+
+ if (subdir_nr < 0 ||
+ (size_t) subdir_nr >= bitsizeof(loose->subdir_seen))
+ BUG("subdir_nr out of range");
+
+ bitmap = &loose->subdir_seen[word_index];
+ if (*bitmap & mask)
+ return loose->cache;
+ if (!loose->cache) {
+ ALLOC_ARRAY(loose->cache, 1);
+ oidtree_init(loose->cache);
+ }
+ strbuf_addstr(&buf, loose->base.path);
+ for_each_file_in_obj_subdir(subdir_nr, &buf,
+ loose->base.odb->repo->hash_algo,
+ append_loose_object,
+ NULL, NULL,
+ loose->cache);
+ *bitmap |= mask;
+ strbuf_release(&buf);
+ return loose->cache;
+}
+
+static int quick_has_loose(struct odb_source_loose *loose,
+ const struct object_id *oid)
+{
+ return !!oidtree_contains(odb_source_loose_cache(loose, oid), oid);
+}
+
+static int read_object_info_from_path(struct odb_source_loose *loose,
+ const char *path,
+ const struct object_id *oid,
+ struct object_info *oi,
+ enum object_info_flags flags)
+{
+ int ret;
+ int fd;
+ unsigned long mapsize;
+ void *map = NULL;
+ git_zstream stream, *stream_to_end = NULL;
+ char hdr[MAX_HEADER_LEN];
+ unsigned long size_scratch;
+ enum object_type type_scratch;
+ struct stat st;
+
+ /*
+ * If we don't care about type or size, then we don't
+ * need to look inside the object at all. Note that we
+ * do not optimize out the stat call, even if the
+ * caller doesn't care about the disk-size, since our
+ * return value implicitly indicates whether the
+ * object even exists.
+ */
+ if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {
+ struct stat st;
+
+ if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {
+ ret = quick_has_loose(loose, oid) ? 0 : -1;
+ goto out;
+ }
+
+ if (lstat(path, &st) < 0) {
+ ret = -1;
+ goto out;
+ }
+
+ if (oi) {
+ if (oi->disk_sizep)
+ *oi->disk_sizep = st.st_size;
+ if (oi->mtimep)
+ *oi->mtimep = st.st_mtime;
+ }
+
+ ret = 0;
+ goto out;
+ }
+
+ fd = git_open(path);
+ if (fd < 0) {
+ if (errno != ENOENT)
+ error_errno(_("unable to open loose object %s"), oid_to_hex(oid));
+ ret = -1;
+ goto out;
+ }
+
+ if (fstat(fd, &st)) {
+ close(fd);
+ ret = -1;
+ goto out;
+ }
+
+ mapsize = xsize_t(st.st_size);
+ if (!mapsize) {
+ close(fd);
+ ret = error(_("object file %s is empty"), path);
+ goto out;
+ }
+
+ map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (!map) {
+ ret = -1;
+ goto out;
+ }
+
+ if (oi->disk_sizep)
+ *oi->disk_sizep = mapsize;
+ if (oi->mtimep)
+ *oi->mtimep = st.st_mtime;
+
+ stream_to_end = &stream;
+
+ switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) {
+ case ULHR_OK:
+ if (!oi->sizep)
+ oi->sizep = &size_scratch;
+ if (!oi->typep)
+ oi->typep = &type_scratch;
+
+ if (parse_loose_header(hdr, oi) < 0) {
+ ret = error(_("unable to parse %s header"), oid_to_hex(oid));
+ goto corrupt;
+ }
+
+ if (*oi->typep < 0)
+ die(_("invalid object type"));
+
+ if (oi->contentp) {
+ *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
+ if (!*oi->contentp) {
+ ret = -1;
+ goto corrupt;
+ }
+ }
+
+ break;
+ case ULHR_BAD:
+ ret = error(_("unable to unpack %s header"),
+ oid_to_hex(oid));
+ goto corrupt;
+ case ULHR_TOO_LONG:
+ ret = error(_("header for %s too long, exceeds %d bytes"),
+ oid_to_hex(oid), MAX_HEADER_LEN);
+ goto corrupt;
+ }
+
+ ret = 0;
+
+corrupt:
+ if (ret && (flags & OBJECT_INFO_DIE_IF_CORRUPT))
+ die(_("loose object %s (stored in %s) is corrupt"),
+ oid_to_hex(oid), path);
+
+out:
+ if (stream_to_end)
+ git_inflate_end(stream_to_end);
+ if (map)
+ munmap(map, mapsize);
+ if (oi) {
+ if (oi->sizep == &size_scratch)
+ oi->sizep = NULL;
+ if (oi->typep == &type_scratch)
+ oi->typep = NULL;
+ if (oi->delta_base_oid)
+ oidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);
+ if (!ret)
+ oi->whence = OI_LOOSE;
+ }
+
+ return ret;
+}
+
static int odb_source_loose_read_object_info(struct odb_source *source,
const struct object_id *oid,
struct object_info *oi,
@@ -218,6 +409,78 @@ static int odb_source_loose_read_object_stream(struct odb_read_stream **out,
return -1;
}
+struct for_each_object_wrapper_data {
+ struct odb_source_loose *loose;
+ const struct object_info *request;
+ odb_for_each_object_cb cb;
+ void *cb_data;
+};
+
+static int for_each_object_wrapper_cb(const struct object_id *oid,
+ const char *path,
+ void *cb_data)
+{
+ struct for_each_object_wrapper_data *data = cb_data;
+
+ if (data->request) {
+ struct object_info oi = *data->request;
+
+ if (read_object_info_from_path(data->loose, path, oid, &oi, 0) < 0)
+ return -1;
+
+ return data->cb(oid, &oi, data->cb_data);
+ } else {
+ return data->cb(oid, NULL, data->cb_data);
+ }
+}
+
+static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid,
+ void *node_data UNUSED,
+ void *cb_data)
+{
+ struct for_each_object_wrapper_data *data = cb_data;
+ if (data->request) {
+ struct object_info oi = *data->request;
+
+ if (odb_source_read_object_info(&data->loose->base,
+ oid, &oi, 0) < 0)
+ return -1;
+
+ return data->cb(oid, &oi, data->cb_data);
+ } else {
+ return data->cb(oid, NULL, data->cb_data);
+ }
+}
+
+static int odb_source_loose_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_loose *loose = odb_source_loose_downcast(source);
+ struct for_each_object_wrapper_data data = {
+ .loose = loose,
+ .request = request,
+ .cb = cb,
+ .cb_data = cb_data,
+ };
+
+ /* There are no loose promisor objects, so we can return immediately. */
+ if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))
+ return 0;
+ if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)
+ return 0;
+
+ if (opts->prefix)
+ return oidtree_each(odb_source_loose_cache(loose, opts->prefix),
+ opts->prefix, opts->prefix_hex_len,
+ for_each_prefixed_object_wrapper_cb, &data);
+
+ return for_each_loose_file_in_source(source, for_each_object_wrapper_cb,
+ NULL, NULL, &data);
+}
+
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -273,6 +536,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.reprepare = odb_source_loose_reprepare;
loose->base.read_object_info = odb_source_loose_read_object_info;
loose->base.read_object_stream = odb_source_loose_read_object_stream;
+ loose->base.for_each_object = odb_source_loose_for_each_object;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 07/18] odb/source-loose: wire up `read_object_stream()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_read_object_stream()` and its associated helpers
from "object-file.c" into "odb/source-loose.c" and wire it up as the
`read_object_stream()` callback of the loose source.
As part of the move we are also forced to expose a couple of functions
from "object-file.h" that parse object headers in a somewhat-generic
way, as those functions are now used by both subsystems.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 200 ++---------------------------------------------------
object-file.h | 31 +++++++--
odb/source-files.c | 2 +-
odb/source-loose.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 222 insertions(+), 200 deletions(-)
diff --git a/object-file.c b/object-file.c
index fa174512a4..adfb672493 100644
--- a/object-file.c
+++ b/object-file.c
@@ -164,28 +164,6 @@ int stream_object_signature(struct repository *r,
return !oideq(oid, &real_oid) ? -1 : 0;
}
-/*
- * Find "oid" as a loose object in given source, open the object and return its
- * file descriptor. Returns the file descriptor on success, negative on failure.
- *
- * The "path" out-parameter will give the path of the object we found (if any).
- * Note that it may point to static storage and is only valid until another
- * call to stat_loose_object().
- */
-static int open_loose_object(struct odb_source_loose *loose,
- const struct object_id *oid, const char **path)
-{
- static struct strbuf buf = STRBUF_INIT;
- int fd;
-
- *path = odb_loose_path(&loose->files->base, &buf, oid);
- fd = git_open(*path);
- if (fd >= 0)
- return fd;
-
- return -1;
-}
-
static int quick_has_loose(struct odb_source_loose *loose,
const struct object_id *oid)
{
@@ -215,42 +193,11 @@ static void *map_fd(int fd, const char *path, unsigned long *size)
return map;
}
-static void *odb_source_loose_map_object(struct odb_source *source,
- const struct object_id *oid,
- unsigned long *size)
-{
- struct odb_source_files *files = odb_source_files_downcast(source);
- const char *p;
- int fd = open_loose_object(files->loose, oid, &p);
-
- if (fd < 0)
- return NULL;
- return map_fd(fd, p, size);
-}
-
-enum unpack_loose_header_result {
- ULHR_OK,
- ULHR_BAD,
- ULHR_TOO_LONG,
-};
-
-/**
- * unpack_loose_header() initializes the data stream needed to unpack
- * a loose object header.
- *
- * Returns:
- *
- * - ULHR_OK on success
- * - ULHR_BAD on error
- * - ULHR_TOO_LONG if the header was too long
- *
- * It will only parse up to MAX_HEADER_LEN bytes.
- */
-static enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
- unsigned char *map,
- unsigned long mapsize,
- void *buffer,
- unsigned long bufsiz)
+enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
+ unsigned char *map,
+ unsigned long mapsize,
+ void *buffer,
+ unsigned long bufsiz)
{
int status;
@@ -340,7 +287,7 @@ static void *unpack_loose_rest(git_zstream *stream,
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-static int parse_loose_header(const char *hdr, struct object_info *oi)
+int parse_loose_header(const char *hdr, struct object_info *oi)
{
const char *type_buf = hdr;
size_t size;
@@ -2170,138 +2117,3 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
return &transaction->base;
}
-
-struct odb_loose_read_stream {
- struct odb_read_stream base;
- git_zstream z;
- enum {
- ODB_LOOSE_READ_STREAM_INUSE,
- ODB_LOOSE_READ_STREAM_DONE,
- ODB_LOOSE_READ_STREAM_ERROR,
- } z_state;
- void *mapped;
- unsigned long mapsize;
- char hdr[32];
- int hdr_avail;
- int hdr_used;
-};
-
-static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz)
-{
- struct odb_loose_read_stream *st =
- container_of(_st, struct odb_loose_read_stream, base);
- size_t total_read = 0;
-
- switch (st->z_state) {
- case ODB_LOOSE_READ_STREAM_DONE:
- return 0;
- case ODB_LOOSE_READ_STREAM_ERROR:
- return -1;
- default:
- break;
- }
-
- if (st->hdr_used < st->hdr_avail) {
- size_t to_copy = st->hdr_avail - st->hdr_used;
- if (sz < to_copy)
- to_copy = sz;
- memcpy(buf, st->hdr + st->hdr_used, to_copy);
- st->hdr_used += to_copy;
- total_read += to_copy;
- }
-
- while (total_read < sz) {
- int status;
-
- st->z.next_out = (unsigned char *)buf + total_read;
- st->z.avail_out = sz - total_read;
- status = git_inflate(&st->z, Z_FINISH);
-
- total_read = st->z.next_out - (unsigned char *)buf;
-
- if (status == Z_STREAM_END) {
- git_inflate_end(&st->z);
- st->z_state = ODB_LOOSE_READ_STREAM_DONE;
- break;
- }
- if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
- git_inflate_end(&st->z);
- st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
- return -1;
- }
- }
- return total_read;
-}
-
-static int close_istream_loose(struct odb_read_stream *_st)
-{
- struct odb_loose_read_stream *st =
- container_of(_st, struct odb_loose_read_stream, base);
-
- if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE)
- git_inflate_end(&st->z);
- munmap(st->mapped, st->mapsize);
- return 0;
-}
-
-int odb_source_loose_read_object_stream(struct odb_read_stream **out,
- struct odb_source *source,
- const struct object_id *oid)
-{
- struct object_info oi = OBJECT_INFO_INIT;
- struct odb_loose_read_stream *st;
- unsigned long mapsize;
- unsigned long size_ul;
- void *mapped;
-
- mapped = odb_source_loose_map_object(source, oid, &mapsize);
- if (!mapped)
- return -1;
-
- /*
- * Note: we must allocate this structure early even though we may still
- * fail. This is because we need to initialize the zlib stream, and it
- * is not possible to copy the stream around after the fact because it
- * has self-referencing pointers.
- */
- CALLOC_ARRAY(st, 1);
-
- switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr,
- sizeof(st->hdr))) {
- case ULHR_OK:
- break;
- case ULHR_BAD:
- case ULHR_TOO_LONG:
- goto error;
- }
-
- /*
- * object_info.sizep is unsigned long* (32-bit on Windows), but
- * st->base.size is size_t (64-bit). Use temporary variable.
- * Note: loose objects >4GB would still truncate here, but such
- * large loose objects are uncommon (they'd normally be packed).
- */
- oi.sizep = &size_ul;
- oi.typep = &st->base.type;
-
- if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
- goto error;
- st->base.size = size_ul;
-
- st->mapped = mapped;
- st->mapsize = mapsize;
- st->hdr_used = strlen(st->hdr) + 1;
- st->hdr_avail = st->z.total_out;
- st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
- st->base.close = close_istream_loose;
- st->base.read = read_istream_loose;
-
- *out = &st->base;
-
- return 0;
-error:
- git_inflate_end(&st->z);
- munmap(mapped, mapsize);
- free(st);
- return -1;
-}
diff --git a/object-file.h b/object-file.h
index 8ac2832dac..d93b7ffad7 100644
--- a/object-file.h
+++ b/object-file.h
@@ -18,13 +18,8 @@ int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct s
int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags);
struct object_info;
-struct odb_read_stream;
struct odb_source;
-int odb_source_loose_read_object_stream(struct odb_read_stream **out,
- struct odb_source *source,
- const struct object_id *oid);
-
/*
* Return true iff an object database source has a loose object
* with the specified name. This function does not respect replace
@@ -199,6 +194,32 @@ int read_object_info_from_path(struct odb_source_loose *loose,
struct object_info *oi,
enum object_info_flags flags);
+enum unpack_loose_header_result {
+ ULHR_OK,
+ ULHR_BAD,
+ ULHR_TOO_LONG,
+};
+
+/**
+ * unpack_loose_header() initializes the data stream needed to unpack
+ * a loose object header.
+ *
+ * Returns:
+ *
+ * - ULHR_OK on success
+ * - ULHR_BAD on error
+ * - ULHR_TOO_LONG if the header was too long
+ *
+ * It will only parse up to MAX_HEADER_LEN bytes.
+ */
+enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
+ unsigned char *map,
+ unsigned long mapsize,
+ void *buffer,
+ unsigned long bufsiz);
+
+int parse_loose_header(const char *hdr, struct object_info *oi);
+
struct odb_transaction;
/*
diff --git a/odb/source-files.c b/odb/source-files.c
index 8d6924755f..90806ddf86 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out,
{
struct odb_source_files *files = odb_source_files_downcast(source);
if (!packfile_store_read_object_stream(out, files->packed, oid) ||
- !odb_source_loose_read_object_stream(out, source, oid))
+ !odb_source_read_object_stream(out, &files->loose->base, oid))
return 0;
return -1;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 50f387ecf3..4b82c6f316 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -1,11 +1,13 @@
#include "git-compat-util.h"
#include "abspath.h"
#include "chdir-notify.h"
+#include "gettext.h"
#include "loose.h"
#include "object-file.h"
#include "odb.h"
#include "odb/source-files.h"
#include "odb/source-loose.h"
+#include "odb/streaming.h"
#include "oidtree.h"
#include "strbuf.h"
@@ -30,6 +32,192 @@ static int odb_source_loose_read_object_info(struct odb_source *source,
return read_object_info_from_path(loose, buf.buf, oid, oi, flags);
}
+/*
+ * Find "oid" as a loose object in given source, open the object and return its
+ * file descriptor. Returns the file descriptor on success, negative on failure.
+ *
+ * The "path" out-parameter will give the path of the object we found (if any).
+ * Note that it may point to static storage and is only valid until another
+ * call to open_loose_object().
+ */
+static int open_loose_object(struct odb_source_loose *loose,
+ const struct object_id *oid, const char **path)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ int fd;
+
+ *path = odb_loose_path(&loose->base, &buf, oid);
+ fd = git_open(*path);
+ if (fd >= 0)
+ return fd;
+
+ return -1;
+}
+
+static void *odb_source_loose_map_object(struct odb_source_loose *loose,
+ const struct object_id *oid,
+ unsigned long *size)
+{
+ const char *p;
+ int fd = open_loose_object(loose, oid, &p);
+ void *map = NULL;
+ struct stat st;
+
+ if (fd < 0)
+ return NULL;
+
+ if (!fstat(fd, &st)) {
+ *size = xsize_t(st.st_size);
+ if (!*size) {
+ /* mmap() is forbidden on empty files */
+ error(_("object file %s is empty"), p);
+ goto out;
+ }
+
+ map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
+ }
+
+out:
+ close(fd);
+ return map;
+}
+
+struct odb_loose_read_stream {
+ struct odb_read_stream base;
+ git_zstream z;
+ enum {
+ ODB_LOOSE_READ_STREAM_INUSE,
+ ODB_LOOSE_READ_STREAM_DONE,
+ ODB_LOOSE_READ_STREAM_ERROR,
+ } z_state;
+ void *mapped;
+ unsigned long mapsize;
+ char hdr[32];
+ int hdr_avail;
+ int hdr_used;
+};
+
+static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz)
+{
+ struct odb_loose_read_stream *st =
+ container_of(_st, struct odb_loose_read_stream, base);
+ size_t total_read = 0;
+
+ switch (st->z_state) {
+ case ODB_LOOSE_READ_STREAM_DONE:
+ return 0;
+ case ODB_LOOSE_READ_STREAM_ERROR:
+ return -1;
+ default:
+ break;
+ }
+
+ if (st->hdr_used < st->hdr_avail) {
+ size_t to_copy = st->hdr_avail - st->hdr_used;
+ if (sz < to_copy)
+ to_copy = sz;
+ memcpy(buf, st->hdr + st->hdr_used, to_copy);
+ st->hdr_used += to_copy;
+ total_read += to_copy;
+ }
+
+ while (total_read < sz) {
+ int status;
+
+ st->z.next_out = (unsigned char *)buf + total_read;
+ st->z.avail_out = sz - total_read;
+ status = git_inflate(&st->z, Z_FINISH);
+
+ total_read = st->z.next_out - (unsigned char *)buf;
+
+ if (status == Z_STREAM_END) {
+ git_inflate_end(&st->z);
+ st->z_state = ODB_LOOSE_READ_STREAM_DONE;
+ break;
+ }
+ if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
+ git_inflate_end(&st->z);
+ st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
+ return -1;
+ }
+ }
+ return total_read;
+}
+
+static int close_istream_loose(struct odb_read_stream *_st)
+{
+ struct odb_loose_read_stream *st =
+ container_of(_st, struct odb_loose_read_stream, base);
+
+ if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE)
+ git_inflate_end(&st->z);
+ munmap(st->mapped, st->mapsize);
+ return 0;
+}
+
+static int odb_source_loose_read_object_stream(struct odb_read_stream **out,
+ struct odb_source *source,
+ const struct object_id *oid)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ struct object_info oi = OBJECT_INFO_INIT;
+ struct odb_loose_read_stream *st;
+ unsigned long mapsize;
+ unsigned long size_ul;
+ void *mapped;
+
+ mapped = odb_source_loose_map_object(loose, oid, &mapsize);
+ if (!mapped)
+ return -1;
+
+ /*
+ * Note: we must allocate this structure early even though we may still
+ * fail. This is because we need to initialize the zlib stream, and it
+ * is not possible to copy the stream around after the fact because it
+ * has self-referencing pointers.
+ */
+ CALLOC_ARRAY(st, 1);
+
+ switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr,
+ sizeof(st->hdr))) {
+ case ULHR_OK:
+ break;
+ case ULHR_BAD:
+ case ULHR_TOO_LONG:
+ goto error;
+ }
+
+ /*
+ * object_info.sizep is unsigned long* (32-bit on Windows), but
+ * st->base.size is size_t (64-bit). Use temporary variable.
+ * Note: loose objects >4GB would still truncate here, but such
+ * large loose objects are uncommon (they'd normally be packed).
+ */
+ oi.sizep = &size_ul;
+ oi.typep = &st->base.type;
+
+ if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
+ goto error;
+ st->base.size = size_ul;
+
+ st->mapped = mapped;
+ st->mapsize = mapsize;
+ st->hdr_used = strlen(st->hdr) + 1;
+ st->hdr_avail = st->z.total_out;
+ st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
+ st->base.close = close_istream_loose;
+ st->base.read = read_istream_loose;
+
+ *out = &st->base;
+
+ return 0;
+error:
+ git_inflate_end(&st->z);
+ munmap(mapped, mapsize);
+ free(st);
+ return -1;
+}
+
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -84,6 +272,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.close = odb_source_loose_close;
loose->base.reprepare = odb_source_loose_reprepare;
loose->base.read_object_info = odb_source_loose_read_object_info;
+ loose->base.read_object_stream = odb_source_loose_read_object_stream;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 06/18] odb/source-loose: wire up `read_object_info()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_read_object_info()` from "object-file.c" into
"odb/source-loose.c" and wire it up as the `read_object_info()` callback
of the loose source. Callers that previously invoked it directly now go
through the generic `odb_source_read_object_info()` interface instead.
The function `read_object_info_from_path()` cannot be moved along with
it because it is still called by `for_each_object_wrapper_cb()`. It is
therefore kept in place, but adjusted to take a loose source to clarify
that it's always operating on this structure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 46 +++++++++++++---------------------------------
object-file.h | 11 ++++++-----
odb/source-files.c | 2 +-
odb/source-loose.c | 24 ++++++++++++++++++++++++
4 files changed, 44 insertions(+), 39 deletions(-)
diff --git a/object-file.c b/object-file.c
index 0f4f1e7bdc..fa174512a4 100644
--- a/object-file.c
+++ b/object-file.c
@@ -396,13 +396,12 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)
return 0;
}
-static int read_object_info_from_path(struct odb_source *source,
- const char *path,
- const struct object_id *oid,
- struct object_info *oi,
- enum object_info_flags flags)
+int read_object_info_from_path(struct odb_source_loose *loose,
+ const char *path,
+ const struct object_id *oid,
+ struct object_info *oi,
+ enum object_info_flags flags)
{
- struct odb_source_files *files = odb_source_files_downcast(source);
int ret;
int fd;
unsigned long mapsize;
@@ -425,7 +424,7 @@ static int read_object_info_from_path(struct odb_source *source,
struct stat st;
if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {
- ret = quick_has_loose(files->loose, oid) ? 0 : -1;
+ ret = quick_has_loose(loose, oid) ? 0 : -1;
goto out;
}
@@ -532,7 +531,7 @@ static int read_object_info_from_path(struct odb_source *source,
if (oi->typep == &type_scratch)
oi->typep = NULL;
if (oi->delta_base_oid)
- oidclr(oi->delta_base_oid, source->odb->repo->hash_algo);
+ oidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);
if (!ret)
oi->whence = OI_LOOSE;
}
@@ -540,26 +539,6 @@ static int read_object_info_from_path(struct odb_source *source,
return ret;
}
-int odb_source_loose_read_object_info(struct odb_source *source,
- const struct object_id *oid,
- struct object_info *oi,
- enum object_info_flags flags)
-{
- static struct strbuf buf = STRBUF_INIT;
-
- /*
- * The second read shouldn't cause new loose objects to show up, unless
- * there was a race condition with a secondary process. We don't care
- * about this case though, so we simply skip reading loose objects a
- * second time.
- */
- if (flags & OBJECT_INFO_SECOND_READ)
- return -1;
-
- odb_loose_path(source, &buf, oid);
- return read_object_info_from_path(source, buf.buf, oid, oi, flags);
-}
-
static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,
const void *buf, unsigned long len,
struct object_id *oid,
@@ -1833,7 +1812,7 @@ int for_each_loose_file_in_source(struct odb_source *source,
}
struct for_each_object_wrapper_data {
- struct odb_source *source;
+ struct odb_source_loose *loose;
const struct object_info *request;
odb_for_each_object_cb cb;
void *cb_data;
@@ -1848,7 +1827,7 @@ static int for_each_object_wrapper_cb(const struct object_id *oid,
if (data->request) {
struct object_info oi = *data->request;
- if (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0)
+ if (read_object_info_from_path(data->loose, path, oid, &oi, 0) < 0)
return -1;
return data->cb(oid, &oi, data->cb_data);
@@ -1865,8 +1844,8 @@ static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid,
if (data->request) {
struct object_info oi = *data->request;
- if (odb_source_loose_read_object_info(data->source,
- oid, &oi, 0) < 0)
+ if (odb_source_read_object_info(&data->loose->base,
+ oid, &oi, 0) < 0)
return -1;
return data->cb(oid, &oi, data->cb_data);
@@ -1881,8 +1860,9 @@ int odb_source_loose_for_each_object(struct odb_source *source,
void *cb_data,
const struct odb_for_each_object_options *opts)
{
+ struct odb_source_files *files = odb_source_files_downcast(source);
struct for_each_object_wrapper_data data = {
- .source = source,
+ .loose = files->loose,
.request = request,
.cb = cb,
.cb_data = cb_data,
diff --git a/object-file.h b/object-file.h
index 420a0fff2e..8ac2832dac 100644
--- a/object-file.h
+++ b/object-file.h
@@ -21,11 +21,6 @@ struct object_info;
struct odb_read_stream;
struct odb_source;
-int odb_source_loose_read_object_info(struct odb_source *source,
- const struct object_id *oid,
- struct object_info *oi,
- enum object_info_flags flags);
-
int odb_source_loose_read_object_stream(struct odb_read_stream **out,
struct odb_source *source,
const struct object_id *oid);
@@ -198,6 +193,12 @@ int read_loose_object(struct repository *repo,
void **contents,
struct object_info *oi);
+int read_object_info_from_path(struct odb_source_loose *loose,
+ const char *path,
+ const struct object_id *oid,
+ struct object_info *oi,
+ enum object_info_flags flags);
+
struct odb_transaction;
/*
diff --git a/odb/source-files.c b/odb/source-files.c
index 59e3a70d80..8d6924755f 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -55,7 +55,7 @@ static int odb_source_files_read_object_info(struct odb_source *source,
struct odb_source_files *files = odb_source_files_downcast(source);
if (!packfile_store_read_object_info(files->packed, oid, oi, flags) ||
- !odb_source_loose_read_object_info(source, oid, oi, flags))
+ !odb_source_read_object_info(&files->loose->base, oid, oi, flags))
return 0;
return -1;
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 65c1076659..50f387ecf3 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -2,10 +2,33 @@
#include "abspath.h"
#include "chdir-notify.h"
#include "loose.h"
+#include "object-file.h"
#include "odb.h"
#include "odb/source-files.h"
#include "odb/source-loose.h"
#include "oidtree.h"
+#include "strbuf.h"
+
+static int odb_source_loose_read_object_info(struct odb_source *source,
+ const struct object_id *oid,
+ struct object_info *oi,
+ enum object_info_flags flags)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ static struct strbuf buf = STRBUF_INIT;
+
+ /*
+ * The second read shouldn't cause new loose objects to show up, unless
+ * there was a race condition with a secondary process. We don't care
+ * about this case though, so we simply skip reading loose objects a
+ * second time.
+ */
+ if (flags & OBJECT_INFO_SECOND_READ)
+ return -1;
+
+ odb_loose_path(source, &buf, oid);
+ return read_object_info_from_path(loose, buf.buf, oid, oi, flags);
+}
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
@@ -60,6 +83,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->base.free = odb_source_loose_free;
loose->base.close = odb_source_loose_close;
loose->base.reprepare = odb_source_loose_reprepare;
+ loose->base.read_object_info = odb_source_loose_read_object_info;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 05/18] odb/source-loose: wire up `close()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Wire up a new `close()` callback for the loose source and call it from
the "files" source via the generic `odb_source_close()` interface. The
callback itself is a no-op as the loose source has no resources that
need to be released on close.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 1 +
odb/source-loose.c | 6 ++++++
2 files changed, 7 insertions(+)
diff --git a/odb/source-files.c b/odb/source-files.c
index 10832e81e4..59e3a70d80 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -36,6 +36,7 @@ static void odb_source_files_free(struct odb_source *source)
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);
packfile_store_close(files->packed);
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index e0fe0d513d..65c1076659 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -21,6 +21,11 @@ static void odb_source_loose_reprepare(struct odb_source *source)
odb_source_loose_clear_cache(loose);
}
+static void odb_source_loose_close(struct odb_source *source UNUSED)
+{
+ /* Nothing to do. */
+}
+
static void odb_source_loose_reparent(const char *name UNUSED,
const char *old_cwd,
const char *new_cwd,
@@ -53,6 +58,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->files = files;
loose->base.free = odb_source_loose_free;
+ loose->base.close = odb_source_loose_close;
loose->base.reprepare = odb_source_loose_reprepare;
if (!is_absolute_path(loose->base.path))
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 04/18] odb/source-loose: wire up `reprepare()` callback
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Move `odb_source_loose_reprepare()` from "object-file.c" into
"odb/source-loose.c" and wire it up as the `reprepare()` callback of the
loose source.
While at it, make `odb_source_loose_clear_cache()` static, as it is no
longer needed outside of its file.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 6 ------
object-file.h | 3 ---
odb/source-files.c | 2 +-
odb/source-loose.c | 9 ++++++++-
odb/source-loose.h | 2 --
5 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/object-file.c b/object-file.c
index 977d959d33..0f4f1e7bdc 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2041,12 +2041,6 @@ static struct oidtree *odb_source_loose_cache(struct odb_source *source,
return files->loose->cache;
}
-void odb_source_loose_reprepare(struct odb_source *source)
-{
- struct odb_source_files *files = odb_source_files_downcast(source);
- odb_source_loose_clear_cache(files->loose);
-}
-
static int check_stream_oid(git_zstream *stream,
const char *hdr,
unsigned long size,
diff --git a/object-file.h b/object-file.h
index 02c9680980..420a0fff2e 100644
--- a/object-file.h
+++ b/object-file.h
@@ -21,9 +21,6 @@ struct object_info;
struct odb_read_stream;
struct odb_source;
-/* Reprepare the loose source by emptying the loose object cache. */
-void odb_source_loose_reprepare(struct odb_source *source);
-
int odb_source_loose_read_object_info(struct odb_source *source,
const struct object_id *oid,
struct object_info *oi,
diff --git a/odb/source-files.c b/odb/source-files.c
index ccc637311b..10832e81e4 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -42,7 +42,7 @@ static void odb_source_files_close(struct odb_source *source)
static void odb_source_files_reprepare(struct odb_source *source)
{
struct odb_source_files *files = odb_source_files_downcast(source);
- odb_source_loose_reprepare(&files->base);
+ odb_source_reprepare(&files->loose->base);
packfile_store_reprepare(files->packed);
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 92e18f5adb..e0fe0d513d 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -7,7 +7,7 @@
#include "odb/source-loose.h"
#include "oidtree.h"
-void odb_source_loose_clear_cache(struct odb_source_loose *loose)
+static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
FREE_AND_NULL(loose->cache);
@@ -15,6 +15,12 @@ void odb_source_loose_clear_cache(struct odb_source_loose *loose)
sizeof(loose->subdir_seen));
}
+static void odb_source_loose_reprepare(struct odb_source *source)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ odb_source_loose_clear_cache(loose);
+}
+
static void odb_source_loose_reparent(const char *name UNUSED,
const char *old_cwd,
const char *new_cwd,
@@ -47,6 +53,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
loose->files = files;
loose->base.free = odb_source_loose_free;
+ loose->base.reprepare = odb_source_loose_reprepare;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-loose.h b/odb/source-loose.h
index 441da9e418..825e703072 100644
--- a/odb/source-loose.h
+++ b/odb/source-loose.h
@@ -44,6 +44,4 @@ static inline struct odb_source_loose *odb_source_loose_downcast(struct odb_sour
return container_of(source, struct odb_source_loose, base);
}
-void odb_source_loose_clear_cache(struct odb_source_loose *loose);
-
#endif
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 03/18] odb/source-loose: start converting to a proper `struct odb_source`
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
Start converting `struct odb_source_loose` into a proper pluggable
`struct odb_source` by embedding the base struct and assigning it the
new `ODB_SOURCE_LOOSE` type. Furthermore, wire up lifecycle management
of this source by implementing the `free` callback and taking ownership
of the chdir notifications.
Note that the loose source is not yet functional as a standalone `struct
odb_source`, as it's missing all of the callback implementations. These
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 17 -----------------
object-file.h | 2 --
odb/source-files.c | 2 +-
odb/source-loose.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
odb/source-loose.h | 14 ++++++++++++++
odb/source.h | 3 +++
6 files changed, 63 insertions(+), 20 deletions(-)
diff --git a/object-file.c b/object-file.c
index 7a1908bfc0..977d959d33 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2041,14 +2041,6 @@ static struct oidtree *odb_source_loose_cache(struct odb_source *source,
return files->loose->cache;
}
-static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
-{
- oidtree_clear(loose->cache);
- FREE_AND_NULL(loose->cache);
- memset(&loose->subdir_seen, 0,
- sizeof(loose->subdir_seen));
-}
-
void odb_source_loose_reprepare(struct odb_source *source)
{
struct odb_source_files *files = odb_source_files_downcast(source);
@@ -2205,15 +2197,6 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
return &transaction->base;
}
-void odb_source_loose_free(struct odb_source_loose *loose)
-{
- if (!loose)
- return;
- odb_source_loose_clear_cache(loose);
- loose_object_map_clear(&loose->map);
- free(loose);
-}
-
struct odb_loose_read_stream {
struct odb_read_stream base;
git_zstream z;
diff --git a/object-file.h b/object-file.h
index 1d8312cf7f..02c9680980 100644
--- a/object-file.h
+++ b/object-file.h
@@ -21,8 +21,6 @@ struct object_info;
struct odb_read_stream;
struct odb_source;
-void odb_source_loose_free(struct odb_source_loose *loose);
-
/* Reprepare the loose source by emptying the loose object cache. */
void odb_source_loose_reprepare(struct odb_source *source);
diff --git a/odb/source-files.c b/odb/source-files.c
index 185cc6903e..ccc637311b 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -27,7 +27,7 @@ static void odb_source_files_free(struct odb_source *source)
{
struct odb_source_files *files = odb_source_files_downcast(source);
chdir_notify_unregister(NULL, odb_source_files_reparent, files);
- odb_source_loose_free(files->loose);
+ odb_source_free(&files->loose->base);
packfile_store_free(files->packed);
odb_source_release(&files->base);
free(files);
diff --git a/odb/source-loose.c b/odb/source-loose.c
index c9e7414814..92e18f5adb 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -1,10 +1,55 @@
#include "git-compat-util.h"
+#include "abspath.h"
+#include "chdir-notify.h"
+#include "loose.h"
+#include "odb.h"
+#include "odb/source-files.h"
#include "odb/source-loose.h"
+#include "oidtree.h"
+
+void odb_source_loose_clear_cache(struct odb_source_loose *loose)
+{
+ oidtree_clear(loose->cache);
+ FREE_AND_NULL(loose->cache);
+ memset(&loose->subdir_seen, 0,
+ sizeof(loose->subdir_seen));
+}
+
+static void odb_source_loose_reparent(const char *name UNUSED,
+ const char *old_cwd,
+ const char *new_cwd,
+ void *cb_data)
+{
+ struct odb_source_loose *loose = cb_data;
+ char *path = reparent_relative_path(old_cwd, new_cwd,
+ loose->base.path);
+ free(loose->base.path);
+ loose->base.path = path;
+}
+
+static void odb_source_loose_free(struct odb_source *source)
+{
+ struct odb_source_loose *loose = odb_source_loose_downcast(source);
+ odb_source_loose_clear_cache(loose);
+ loose_object_map_clear(&loose->map);
+ chdir_notify_unregister(NULL, odb_source_loose_reparent, loose);
+ odb_source_release(&loose->base);
+ free(loose);
+}
struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
{
struct odb_source_loose *loose;
+
CALLOC_ARRAY(loose, 1);
+ odb_source_init(&loose->base, files->base.odb, ODB_SOURCE_LOOSE,
+ files->base.path, files->base.local);
loose->files = files;
+
+ loose->base.free = odb_source_loose_free;
+
+ if (!is_absolute_path(loose->base.path))
+ chdir_notify_register(NULL, odb_source_loose_reparent, loose);
+
return loose;
}
diff --git a/odb/source-loose.h b/odb/source-loose.h
index bf61e767c8..441da9e418 100644
--- a/odb/source-loose.h
+++ b/odb/source-loose.h
@@ -12,6 +12,7 @@ struct oidtree;
* file per object. This source is part of the files source.
*/
struct odb_source_loose {
+ struct odb_source base;
struct odb_source_files *files;
/*
@@ -32,4 +33,17 @@ struct odb_source_loose {
struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files);
+/*
+ * Cast the given object database source to the loose backend. This will cause
+ * a BUG in case the source uses doesn't use this backend.
+ */
+static inline struct odb_source_loose *odb_source_loose_downcast(struct odb_source *source)
+{
+ if (source->type != ODB_SOURCE_LOOSE)
+ BUG("trying to downcast source of type '%d' to loose", source->type);
+ return container_of(source, struct odb_source_loose, base);
+}
+
+void odb_source_loose_clear_cache(struct odb_source_loose *loose);
+
#endif
diff --git a/odb/source.h b/odb/source.h
index 0a440884e4..8bcb67787e 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -14,6 +14,9 @@ enum odb_source_type {
/* The "files" backend that uses loose objects and packfiles. */
ODB_SOURCE_FILES,
+ /* The "loose" backend that uses loose objects, only. */
+ ODB_SOURCE_LOOSE,
+
/* The "in-memory" backend that stores objects in memory. */
ODB_SOURCE_INMEMORY,
};
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 02/18] odb/source-loose: store pointer to "files" instead of generic source
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
The `struct odb_source_loose` holds a pointer to its owning parent
source. The way that Git is currently structured, this parent is always
the "files" source. In subsequent commits we're going to detangle that
so that the "loose" source doesn't have any owning parent source at all
so that it can be used as a completely standalone source.
Detangling this mess is somewhat intricate though, and is made even more
intricate because it's not always clear which kind of source one is
holding at a specific point in time -- either the parent "files" source,
or the child "loose" source.
Make this relationship more explicit by storing a pointer to the "files"
source instead of storing a pointer to a generic `struct odb_source`.
This will help make subsequent steps a bit clearer.
Note that this is a temporary step, only. At the end of this series
we will have dropped the parent pointer completely.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
object-file.c | 4 ++--
odb/source-files.c | 2 +-
odb/source-loose.c | 4 ++--
odb/source-loose.h | 5 +++--
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/object-file.c b/object-file.c
index 641bd9c079..7a1908bfc0 100644
--- a/object-file.c
+++ b/object-file.c
@@ -178,7 +178,7 @@ static int open_loose_object(struct odb_source_loose *loose,
static struct strbuf buf = STRBUF_INIT;
int fd;
- *path = odb_loose_path(loose->source, &buf, oid);
+ *path = odb_loose_path(&loose->files->base, &buf, oid);
fd = git_open(*path);
if (fd >= 0)
return fd;
@@ -189,7 +189,7 @@ static int open_loose_object(struct odb_source_loose *loose,
static int quick_has_loose(struct odb_source_loose *loose,
const struct object_id *oid)
{
- return !!oidtree_contains(odb_source_loose_cache(loose->source, oid), oid);
+ return !!oidtree_contains(odb_source_loose_cache(&loose->files->base, oid), oid);
}
/*
diff --git a/odb/source-files.c b/odb/source-files.c
index b5abd20e97..185cc6903e 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -264,7 +264,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
CALLOC_ARRAY(files, 1);
odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);
- files->loose = odb_source_loose_new(&files->base);
+ files->loose = odb_source_loose_new(files);
files->packed = packfile_store_new(&files->base);
files->base.free = odb_source_files_free;
diff --git a/odb/source-loose.c b/odb/source-loose.c
index b944d21813..c9e7414814 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -1,10 +1,10 @@
#include "git-compat-util.h"
#include "odb/source-loose.h"
-struct odb_source_loose *odb_source_loose_new(struct odb_source *source)
+struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
{
struct odb_source_loose *loose;
CALLOC_ARRAY(loose, 1);
- loose->source = source;
+ loose->files = files;
return loose;
}
diff --git a/odb/source-loose.h b/odb/source-loose.h
index 8b4bac77ea..bf61e767c8 100644
--- a/odb/source-loose.h
+++ b/odb/source-loose.h
@@ -3,6 +3,7 @@
#include "odb/source.h"
+struct odb_source_files;
struct object_database;
struct oidtree;
@@ -11,7 +12,7 @@ struct oidtree;
* file per object. This source is part of the files source.
*/
struct odb_source_loose {
- struct odb_source *source;
+ struct odb_source_files *files;
/*
* Used to store the results of readdir(3) calls when we are OK
@@ -29,6 +30,6 @@ struct odb_source_loose {
struct loose_object_map *map;
};
-struct odb_source_loose *odb_source_loose_new(struct odb_source *source);
+struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files);
#endif
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 01/18] odb/source-loose: move loose source into "odb/" subsystem
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>
In subsequent patches we'll be turning `struct odb_source_loose` into a
proper `struct odb_source`. As a first step towards this goal, move its
struct out of "object-file.c" and into "odb/source-loose.c".
This detaches the implementation of the loose object source from the
generic object file code, following the same convention already used by
the "files" and "in-memory" sources.
No functional changes are intended.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 1 +
meson.build | 1 +
object-file.c | 8 --------
object-file.h | 21 +--------------------
odb/source-loose.c | 10 ++++++++++
odb/source-loose.h | 34 ++++++++++++++++++++++++++++++++++
6 files changed, 47 insertions(+), 28 deletions(-)
diff --git a/Makefile b/Makefile
index a43b8ee067..01356235c3 100644
--- a/Makefile
+++ b/Makefile
@@ -1217,6 +1217,7 @@ LIB_OBJS += odb.o
LIB_OBJS += odb/source.o
LIB_OBJS += odb/source-files.o
LIB_OBJS += odb/source-inmemory.o
+LIB_OBJS += odb/source-loose.o
LIB_OBJS += odb/streaming.o
LIB_OBJS += odb/transaction.o
LIB_OBJS += oid-array.o
diff --git a/meson.build b/meson.build
index 664d831329..c85e598835 100644
--- a/meson.build
+++ b/meson.build
@@ -405,6 +405,7 @@ libgit_sources = [
'odb/source.c',
'odb/source-files.c',
'odb/source-inmemory.c',
+ 'odb/source-loose.c',
'odb/streaming.c',
'odb/transaction.c',
'oid-array.c',
diff --git a/object-file.c b/object-file.c
index 90f995d000..641bd9c079 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2205,14 +2205,6 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
return &transaction->base;
}
-struct odb_source_loose *odb_source_loose_new(struct odb_source *source)
-{
- struct odb_source_loose *loose;
- CALLOC_ARRAY(loose, 1);
- loose->source = source;
- return loose;
-}
-
void odb_source_loose_free(struct odb_source_loose *loose)
{
if (!loose)
diff --git a/object-file.h b/object-file.h
index 5241b8dd5c..1d8312cf7f 100644
--- a/object-file.h
+++ b/object-file.h
@@ -4,6 +4,7 @@
#include "git-zlib.h"
#include "object.h"
#include "odb.h"
+#include "odb/source-loose.h"
struct index_state;
@@ -20,26 +21,6 @@ struct object_info;
struct odb_read_stream;
struct odb_source;
-struct odb_source_loose {
- struct odb_source *source;
-
- /*
- * Used to store the results of readdir(3) calls when we are OK
- * sacrificing accuracy due to races for speed. That includes
- * object existence with OBJECT_INFO_QUICK, as well as
- * our search for unique abbreviated hashes. Don't use it for tasks
- * requiring greater accuracy!
- *
- * Be sure to call odb_load_loose_cache() before using.
- */
- uint32_t subdir_seen[8]; /* 256 bits */
- struct oidtree *cache;
-
- /* Map between object IDs for loose objects. */
- struct loose_object_map *map;
-};
-
-struct odb_source_loose *odb_source_loose_new(struct odb_source *source);
void odb_source_loose_free(struct odb_source_loose *loose);
/* Reprepare the loose source by emptying the loose object cache. */
diff --git a/odb/source-loose.c b/odb/source-loose.c
new file mode 100644
index 0000000000..b944d21813
--- /dev/null
+++ b/odb/source-loose.c
@@ -0,0 +1,10 @@
+#include "git-compat-util.h"
+#include "odb/source-loose.h"
+
+struct odb_source_loose *odb_source_loose_new(struct odb_source *source)
+{
+ struct odb_source_loose *loose;
+ CALLOC_ARRAY(loose, 1);
+ loose->source = source;
+ return loose;
+}
diff --git a/odb/source-loose.h b/odb/source-loose.h
new file mode 100644
index 0000000000..8b4bac77ea
--- /dev/null
+++ b/odb/source-loose.h
@@ -0,0 +1,34 @@
+#ifndef ODB_SOURCE_LOOSE_H
+#define ODB_SOURCE_LOOSE_H
+
+#include "odb/source.h"
+
+struct object_database;
+struct oidtree;
+
+/*
+ * An object database source that stores its objects in loose format, one
+ * file per object. This source is part of the files source.
+ */
+struct odb_source_loose {
+ struct odb_source *source;
+
+ /*
+ * Used to store the results of readdir(3) calls when we are OK
+ * sacrificing accuracy due to races for speed. That includes
+ * object existence with OBJECT_INFO_QUICK, as well as
+ * our search for unique abbreviated hashes. Don't use it for tasks
+ * requiring greater accuracy!
+ *
+ * Be sure to call odb_load_loose_cache() before using.
+ */
+ uint32_t subdir_seen[8]; /* 256 bits */
+ struct oidtree *cache;
+
+ /* Map between object IDs for loose objects. */
+ struct loose_object_map *map;
+};
+
+struct odb_source_loose *odb_source_loose_new(struct odb_source *source);
+
+#endif
--
2.54.0.926.g75ba10bac6.dirty
^ permalink raw reply related
* [PATCH 00/18] odb: make loose object source a proper `struct odb_source`
From: Patrick Steinhardt @ 2026-05-21 8:22 UTC (permalink / raw)
To: git
Hi,
this patch series converts the loose object source into a proper `struct
odb_source` so that it can be used via our generic interfaces.
The patch series is relatively straight-forward, as the source basically
already exists as such and the interfaces already match. So for most of
the part we are just moving around some code and converting functions
that were previously called directly into callbacks.
I guess the only part that needs some attention is that there is some
confusion at first with the `struct odb_source_loose::source` parent
pointer that initially points at the owning `struct odb_source_files`.
This relationship doesn't make much sense, as a loose source can totally
exist standalone without the files source.
We're thus getting rid of this relationship in this series, too. I found
it quite hard to reason about which pointer one is holding at any point
in time though, doubly so because the parent pointer was named "source",
which is rather generic. The second commit thus renames the pointer to
`files` and converts it into `struct odb_source_files` to make the
transition cleaner, but the whole pointer will be dropped at the end of
this series.
The series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)
with ps/odb-in-memory at d2902a4549 (t/unit-tests: add tests for the
in-memory object source, 2026-04-10) merged into it.
Thanks!
Patrick
---
Patrick Steinhardt (18):
odb/source-loose: move loose source into "odb/" subsystem
odb/source-loose: store pointer to "files" instead of generic source
odb/source-loose: start converting to a proper `struct odb_source`
odb/source-loose: wire up `reprepare()` callback
odb/source-loose: wire up `close()` callback
odb/source-loose: wire up `read_object_info()` callback
odb/source-loose: wire up `read_object_stream()` callback
odb/source-loose: wire up `for_each_object()` callback
odb/source-loose: wire up `find_abbrev_len()` callback
odb/source-loose: wire up `count_objects()` callback
odb/source-loose: drop `odb_source_loose_has_object()`
odb/source-loose: wire up `freshen_object()` callback
loose: refactor object map to operate on `struct odb_source_loose`
odb/source-loose: wire up `write_object()` callback
object-file: refactor writing objects to use loose source
odb/source-loose: wire up `write_object_stream()` callback
odb/source-loose: stub out remaining callbacks
odb/source-loose: drop pointer to the "files" source
Makefile | 1 +
builtin/cat-file.c | 5 +-
builtin/gc.c | 6 +-
builtin/pack-objects.c | 12 +-
http-walker.c | 3 +-
http.c | 6 +-
loose.c | 45 ++-
loose.h | 4 +-
meson.build | 1 +
object-file.c | 796 ++++---------------------------------------------
object-file.h | 149 ++++-----
odb/source-files.c | 28 +-
odb/source-loose.c | 736 +++++++++++++++++++++++++++++++++++++++++++++
odb/source-loose.h | 48 +++
odb/source.h | 3 +
15 files changed, 973 insertions(+), 870 deletions(-)
---
base-commit: 072edab49f312c80561b2899f03f361f74fc38e4
change-id: 20260413-b4-pks-odb-source-loose-4900c8ca91db
^ permalink raw reply
* Re: [PATCH] connect: use "service" enum for "name" argument
From: Patrick Steinhardt @ 2026-05-21 8:19 UTC (permalink / raw)
To: Jeff King; +Cc: git
In-Reply-To: <20260519052219.GA1703179@coredump.intra.peff.net>
On Tue, May 19, 2026 at 01:22:19AM -0400, Jeff King wrote:
> diff --git a/connect.h b/connect.h
> index 1645126c17..c56ecddc0e 100644
> --- a/connect.h
> +++ b/connect.h
> @@ -7,7 +7,12 @@
> #define CONNECT_DIAG_URL (1u << 1)
> #define CONNECT_IPV4 (1u << 2)
> #define CONNECT_IPV6 (1u << 3)
> -struct child_process *git_connect(int fd[2], const char *url, const char *name, const char *prog, int flags);
> +enum git_connect_service {
> + GIT_CONNECT_UPLOAD_PACK,
> + GIT_CONNECT_RECEIVE_PACK,
> + GIT_CONNECT_UPLOAD_ARCHIVE,
> +};
> +struct child_process *git_connect(int fd[2], const char *url, enum git_connect_service, const char *prog, int flags);
> int finish_connect(struct child_process *conn);
> int git_connection_is_socket(struct child_process *conn);
> int server_supports(const char *feature);
This is all quite tightly-packed, and the patch would be a good
opportunity to maybe add some documentation. But that's certainly
moving the goalposts quite a bit.
> diff --git a/transport-helper.c b/transport-helper.c
> index 4614036c99..bf37c5280c 100644
> --- a/transport-helper.c
> +++ b/transport-helper.c
> @@ -620,8 +620,22 @@ static int run_connect(struct transport *transport, struct strbuf *cmdbuf)
> return ret;
> }
>
> +static const char *connect_service_cmd(enum git_connect_service service)
> +{
> + switch (service) {
> + case GIT_CONNECT_UPLOAD_PACK:
> + return "git-upload-pack";
> + case GIT_CONNECT_RECEIVE_PACK:
> + return "git-receive-pack";
> + case GIT_CONNECT_UPLOAD_ARCHIVE:
> + return "git-upload-archive";
> + }
> + BUG("unknown git_connect_type: %d", service);
> +}
Shouldn't this say "unknown git_connect_service" instead of "_type"?
Other than that this patch looks good to me, and I agree that this makes
the argument a bit easier to understand.
Patrick
^ permalink raw reply
* Re: [PATCH v3] remote: qualify "git pull" advice for non-upstream compareBranches
From: Junio C Hamano @ 2026-05-21 8:19 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
In-Reply-To: <pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com>
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> + if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
> + if (push_remote_name && push_branch_name)
> + strbuf_addf(sb,
> + _(" (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"),
> + push_remote_name, push_branch_name);
> + else
> + strbuf_addstr(sb,
> + _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
Here is where "git pull" is suggested as a fallback when the history
is diverged (e.g., you pushed and then you rebased).
> + }
> }
> }
>
> @@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> int ours, theirs, cmp;
> int is_upstream, is_push;
> unsigned flags = 0;
> + const char *push_remote_name = NULL;
> + const char *push_branch_name = NULL;
>
> full_ref = resolve_compare_branch(branch,
> branches.items[i].string);
> @@ -2396,13 +2412,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> if (reported)
> strbuf_addstr(sb, "\n");
>
> - if (is_upstream)
> + if (is_upstream || is_push)
> flags |= ENABLE_ADVICE_PULL;
> - if (is_push)
> - flags |= ENABLE_ADVICE_PUSH;
> if (show_divergence_advice && is_upstream)
> flags |= ENABLE_ADVICE_DIVERGENCE;
> + if (is_push) {
> + flags |= ENABLE_ADVICE_PUSH;
> + if (!upstream_ref || strcmp(upstream_ref, full_ref)) {
> + push_remote_name = pushremote_for_branch(branch, NULL);
Here we _know_ that our repository has separate upstream and
push/publish repositories. But we may not be able to "qualify" it
in the following "if" statement, in which case ...
> + if (push_remote_name &&
> + skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
> + skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
> + *push_branch_name == '/')
> + push_branch_name++;
> + else
> + push_remote_name = NULL;
... we assign NULL to push_remote_name to "punt".
> + }
> + }
Which means that this call to the helper function cannot distinguish
between the case where we were in "push" and pushing to the upstream
(i.e., "git pull" without extra arguments is perfectly a sensible
suggestion) and the case where we were in "push", diverged, and
triangular (i.e., "git pull" with or without extra arguments is not
an appropriate thing to suggest) but we cannot exactly tell what is
going on.
Shoudln't the "punt" case refrain from suggesting "git pull"?
> format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
> + push_remote_name, push_branch_name,
> abf, flags);
> reported = 1;
>
> diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
> index 0242b5bf7a..b613aba33a 100755
> --- a/t/t6040-tracking-info.sh
> +++ b/t/t6040-tracking-info.sh
> @@ -646,4 +646,82 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo
> test_cmp expect actual
> '
>
> +test_expect_success 'status.compareBranches behind both upstream and push' '
> + test_config -C test push.default current &&
> + test_config -C test remote.pushDefault origin &&
> + test_config -C test status.compareBranches "@{upstream} @{push}" &&
> + git -C test checkout -b feature13 upstream/main &&
> + (cd test && advance work13) &&
> + git -C test push origin &&
> + git -C test branch --set-upstream-to upstream/ahead &&
> + git -C test reset --hard HEAD^ &&
> + git -C test status >actual &&
> + cat >expect <<-EOF &&
> + On branch feature13
> + Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
> + (use "git pull" to update your local branch)
> +
> + Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.
> + (use "git pull origin feature13" to update your local branch)
> +
> + nothing to commit, working tree clean
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success 'status.compareBranches with remapped push and behind push branch' '
> + test_config -C test remote.pushDefault origin &&
> + test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&
> + test_config -C test status.compareBranches "@{push}" &&
> + git -C test checkout -b feature14 upstream/main &&
> + (cd test && advance work14) &&
> + git -C test push &&
> + git -C test reset --hard HEAD^ &&
> + git -C test status >actual &&
> + cat >expect <<-EOF &&
> + On branch feature14
> + Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.
> + (use "git pull origin remapped14" to update your local branch)
> +
> + nothing to commit, working tree clean
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success 'status.compareBranches with behind push branch and no upstream' '
> + test_config -C test push.default current &&
> + test_config -C test remote.pushDefault origin &&
> + test_config -C test status.compareBranches "@{push}" &&
> + git -C test checkout --no-track -b feature15 upstream/main &&
> + (cd test && advance work15) &&
> + git -C test push origin &&
> + git -C test reset --hard HEAD^ &&
> + git -C test status >actual &&
> + cat >expect <<-EOF &&
> + On branch feature15
> + Your branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.
> + (use "git pull origin feature15" to update your local branch)
> +
> + nothing to commit, working tree clean
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '
> + test_config -C test status.compareBranches "@{upstream} @{push}" &&
> + git -C test checkout -b feature16 origin/main &&
> + (cd test && advance work16) &&
> + git -C test push origin HEAD:main &&
> + git -C test reset --hard HEAD^ &&
> + git -C test status >actual &&
> + cat >expect <<-EOF &&
> + On branch feature16
> + Your branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.
> + (use "git pull" to update your local branch)
> +
> + nothing to commit, working tree clean
> + EOF
> + test_cmp expect actual
> +'
> +
> test_done
>
> base-commit: 7bcaabddcf68bd0702697da5904c3b68c52f94cf
^ permalink raw reply
* Re: [PATCH 0/2] builtin/maintenance: fix locking and respect "gc.auto"
From: Patrick Steinhardt @ 2026-05-21 7:45 UTC (permalink / raw)
To: Junio C Hamano
Cc: Jean-Christophe Manciot, Mikael Magnusson, Jeff King, Taylor Blau,
Derrick Stolee, git
In-Reply-To: <xmqq33zl2tok.fsf@gitster.g>
On Thu, May 21, 2026 at 02:55:07PM +0900, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > On Mon, May 11, 2026 at 02:29:54PM +0200, Patrick Steinhardt wrote:
> >> this patch series addresses the issues reported in [1]. The series is
> >> built on top of Git 2.54.0.
> >
> > Junio: I saw that you are starting to prep for Git 2.54.1, and
> > a89346e34a (Start preparing for 2.54.1, 2026-05-21) explicitly mentions
> > a couple of additional topics that should land in that bugfix release.
> > This topic here isn't mentioned though, but I very much think that these
> > fixes should be included.
>
> Sure. As of https://lore.kernel.org/git/ag1MHje6-C6nmcO4@pks.im/ I
> think it can be merged to 'next', which will allow me to list it in
> there?
Yeah, both Taylor and Peff ACK'd this series, so I think it should be
ready to go.
> Are there other topics that should be fast-tracked?
None that I'm currently aware of. Thanks!
Patrick
^ permalink raw reply
* [PATCH 8/8] setup: construct object database in `apply_repository_format()`
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
With the preceding changes we now always construct the repository's
object database before applying the repository format. Remove this
duplication by constructing it in `apply_repository_format()` instead.
Note that we create the object database _after_ having set up the
repository's hash algorithm, but _before_ setting the compat hash
algorithm. This is intentional:
- Constructing the object database may require knowledge of its
intended object format.
- Setting up the compatibility hash requires the object database to be
initialized already, because we immediately read the loose object
map.
The first point is sensible, the second maybe a little less so. Ideally,
it should be the responsibility of the object database itself to
initialize any data structures required for the compatibility hash. But
this would require further changes, so this is kept as-is for now.
Further note that this requires us to move handling of the environment
variables GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES into
the repository format, as well. This allows the caller more flexibility
around whether or not those environment variables are being honored, as
we do do want to respect them in "setup.c", but not in "repository.c".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
repository.c | 4 +---
setup.c | 45 +++++++++++++++++++++------------------------
setup.h | 10 ++++++++++
3 files changed, 32 insertions(+), 27 deletions(-)
diff --git a/repository.c b/repository.c
index 61dfbb8be6..187dd471c4 100644
--- a/repository.c
+++ b/repository.c
@@ -291,13 +291,11 @@ int repo_init(struct repository *repo,
if (read_repository_format_from_commondir(&format, repo->commondir))
goto error;
- if (apply_repository_format(repo, &format, &err) < 0) {
+ if (apply_repository_format(repo, &format, 0, &err) < 0) {
warning("%s", err.buf);
goto error;
}
- repo->objects = odb_new(repo, NULL, NULL);
-
if (worktree)
repo_set_worktree(repo, worktree);
diff --git a/setup.c b/setup.c
index 4a8d6230b1..513fc88749 100644
--- a/setup.c
+++ b/setup.c
@@ -1752,12 +1752,22 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
int apply_repository_format(struct repository *repo,
const struct repository_format *format,
+ enum apply_repository_format_flags flags,
struct strbuf *err)
{
+ char *object_directory = NULL, *alternate_object_directories = NULL;
+
if (verify_repository_format(format, err) < 0)
return -1;
+ if (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) {
+ object_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT));
+ alternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
+ }
+
repo_set_hash_algo(repo, format->hash_algo);
+ repo->objects = odb_new(repo, object_directory,
+ alternate_object_directories);
repo_set_compat_hash_algo(repo, format->compat_hash_algo);
repo_set_ref_storage_format(repo,
format->ref_storage_format,
@@ -1773,6 +1783,8 @@ int apply_repository_format(struct repository *repo,
repo->repository_format_precious_objects =
format->precious_objects;
+ free(alternate_object_directories);
+ free(object_directory);
return 0;
}
@@ -1785,7 +1797,8 @@ int apply_repository_format(struct repository *repo,
* If successful and fmt is not NULL, fill fmt with data.
*/
static void check_and_apply_repository_format(struct repository *repo,
- struct repository_format *fmt)
+ struct repository_format *fmt,
+ enum apply_repository_format_flags flags)
{
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
struct strbuf err = STRBUF_INIT;
@@ -1794,7 +1807,7 @@ static void check_and_apply_repository_format(struct repository *repo,
fmt = &repo_fmt;
check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);
- if (apply_repository_format(repo, fmt, &err) < 0)
+ if (apply_repository_format(repo, fmt, flags, &err) < 0)
die("%s", err.buf);
startup_info->have_repository = 1;
@@ -1874,15 +1887,9 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags
}
if (is_git_directory(".")) {
- struct strvec to_free = STRVEC_INIT;
-
set_git_dir(repo, ".", 0);
- repo->objects = odb_new(repo,
- getenv_safe(&to_free, DB_ENVIRONMENT),
- getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
- check_and_apply_repository_format(repo, NULL);
-
- strvec_clear(&to_free);
+ check_and_apply_repository_format(repo, NULL,
+ APPLY_REPOSITORY_FORMAT_HONOR_ENV);
return path;
}
@@ -2034,8 +2041,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
startup_info->have_repository ||
/* GIT_DIR_EXPLICIT */
getenv(GIT_DIR_ENVIRONMENT)) {
- struct strvec to_free = STRVEC_INIT;
-
if (!repo->gitdir) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
if (!gitdir)
@@ -2046,17 +2051,13 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
if (startup_info->have_repository) {
struct strbuf err = STRBUF_INIT;
- repo->objects = odb_new(repo,
- getenv_safe(&to_free, DB_ENVIRONMENT),
- getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
- if (apply_repository_format(repo, &repo_fmt, &err) < 0)
+ if (apply_repository_format(repo, &repo_fmt,
+ APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
die("%s", err.buf);
clear_repository_format(&repo_fmt);
strbuf_release(&err);
}
-
- strvec_clear(&to_free);
}
/*
* Since precompose_string_if_needed() needs to look at
@@ -2805,7 +2806,6 @@ int init_db(struct repository *repo,
int exist_ok = flags & INIT_DB_EXIST_OK;
char *original_git_dir = real_pathdup(git_dir, 1);
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
- struct strvec to_free = STRVEC_INIT;
if (real_git_dir) {
struct stat st;
@@ -2826,16 +2826,14 @@ int init_db(struct repository *repo,
}
startup_info->have_repository = 1;
- repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
- getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
-
/*
* Check to see if the repository version is right.
* Note that a newly created repository does not have
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
- check_and_apply_repository_format(repo, &repo_fmt);
+ check_and_apply_repository_format(repo, &repo_fmt,
+ APPLY_REPOSITORY_FORMAT_HONOR_ENV);
repository_format_configure(repo, &repo_fmt, hash, ref_storage_format);
@@ -2892,7 +2890,6 @@ int init_db(struct repository *repo,
}
clear_repository_format(&repo_fmt);
- strvec_clear(&to_free);
free(original_git_dir);
return 0;
}
diff --git a/setup.h b/setup.h
index 5ed92f53fa..821b55aca0 100644
--- a/setup.h
+++ b/setup.h
@@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);
int verify_repository_format(const struct repository_format *format,
struct strbuf *err);
+enum apply_repository_format_flags {
+ /*
+ * Honor environment variables when applying the repository format to
+ * the repository. For now, this only covers environment variables that
+ * relate to the object database.
+ */
+ APPLY_REPOSITORY_FORMAT_HONOR_ENV = (1 << 0),
+};
+
/*
* Apply the given repository format to the repo. This initializes extensions
* and basic data structures required for normal operation. Returns 0 on
@@ -228,6 +237,7 @@ int verify_repository_format(const struct repository_format *format,
*/
int apply_repository_format(struct repository *repo,
const struct repository_format *format,
+ enum apply_repository_format_flags flags,
struct strbuf *err);
const char *get_template_dir(const char *option_template);
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 7/8] repository: stop reading loose object map twice on repo init
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
When initializing a repository via `repo_init()` we end up reading the
loose object map twice:
- `apply_repository_format()` calls `repo_set_compat_hash_algo()`,
which in turn calls `repo_read_loose_object_map()` if we have a
compatibility hash configured.
- `repo_init()` calls `repo_read_loose_object_map()` directly a second
time.
Drop the second read of the loose object map in `repo_init()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
repository.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/repository.c b/repository.c
index 2c2395105f..61dfbb8be6 100644
--- a/repository.c
+++ b/repository.c
@@ -301,9 +301,6 @@ int repo_init(struct repository *repo,
if (worktree)
repo_set_worktree(repo, worktree);
- if (repo->compat_hash_algo)
- repo_read_loose_object_map(repo);
-
clear_repository_format(&format);
strbuf_release(&err);
return 0;
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 6/8] setup: stop initializing object database without repository
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
The function `setup_git_directory_gently()` is responsible for
discovering and setting up a Git repository based on various environment
variables and the current working directory. The result is thus a fully
usable Git repository.
One oddity of this function is that we may set up the object database
even in the case where we don't have a repository, namely in the case
where the `GIT_DIR_EXPLICIT` environment variable is set but points to a
non-existent repository. If so, we call `setup_git_env_internal()` with
the value of the environment variable so that the repository's Git
directory is configured, even if it points to a non-existent directory.
Historically though, this function didn't only configure the repository,
but also initialized the object database. We retained this behaviour
from a preceding commit, even though it really doesn't make much sense
in the first place -- there is no repository, so we don't have an object
database either. There seemingly isn't much of a reason to construct the
object database, as we typically won't try to read objects when we don't
have an object database.
There's one exception though: git-index-pack(1) may run outside of a
repository, which can be used to perform consistency checks for a
packfile. The code path is _almost_ working: we already know to call
`parse_object_buffer()`, which can read objects without an object
database being available. And that works for all object types except for
commits, because `parse_commit_buffer()` calls `parse_commit_graph()`,
and that function doesn't handle the case where we don't have an object
database.
Fix this instance to check for the object database instead of checking
for the Git directory having been initialized. With this fixed, we can
now stop constructing an object database completely.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
commit-graph.c | 4 ++--
setup.c | 7 +++----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/commit-graph.c b/commit-graph.c
index 9abe62bd5a..0820cf5fb8 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -740,13 +740,13 @@ static struct commit_graph *prepare_commit_graph(struct repository *r)
struct odb_source *source;
/*
- * Early return if there is no git dir or if the commit graph is
+ * Early return if there is no object database or if the commit graph is
* disabled.
*
* This must come before the "already attempted?" check below, because
* we want to disable even an already-loaded graph file.
*/
- if (!r->gitdir || r->commit_graph_disabled)
+ if (!r->objects || r->commit_graph_disabled)
return NULL;
if (r->objects->commit_graph_attempted)
diff --git a/setup.c b/setup.c
index 0dc9fe4565..4a8d6230b1 100644
--- a/setup.c
+++ b/setup.c
@@ -2043,13 +2043,12 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
setup_git_env_internal(repo, gitdir);
}
- repo->objects = odb_new(repo,
- getenv_safe(&to_free, DB_ENVIRONMENT),
- getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
-
if (startup_info->have_repository) {
struct strbuf err = STRBUF_INIT;
+ repo->objects = odb_new(repo,
+ getenv_safe(&to_free, DB_ENVIRONMENT),
+ getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
if (apply_repository_format(repo, &repo_fmt, &err) < 0)
die("%s", err.buf);
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 5/8] setup: stop creating the object database in `setup_git_env()`
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
In the preceding commit we have stopped creating the object database in
`repo_set_gitdir()`. But the logic is still somewhat confusing as we
still end up creating it conditionally in `setup_git_dir()`, which is
called multiple times.
Drop the conditional logic and instead create the object database in all
places where we have discovered and configured a repository.
This leads to even more duplication than we already had in the preceding
commit, but an alert reader may notice that we now (almost) always call
`odb_new()` directly before having called `apply_repository_format()`.
The only exception to this is `setup_git_directory_gently()`, where we
also call the function when _not_ applying the repository format. This
will be fixed in the next commit, and once that's done we can then unify
creation of the object database into `apply_repository_format()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
setup.c | 37 ++++++++++++++++++++++++++-----------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/setup.c b/setup.c
index 3bd3f6c592..0dc9fe4565 100644
--- a/setup.c
+++ b/setup.c
@@ -1035,8 +1035,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
}
static void setup_git_env_internal(struct repository *repo,
- const char *git_dir,
- bool skip_initializing_odb)
+ const char *git_dir)
{
char *git_replace_ref_base;
const char *shallow_file;
@@ -1053,10 +1052,6 @@ static void setup_git_env_internal(struct repository *repo,
repo_set_gitdir(repo, git_dir, &args);
strvec_clear(&to_free);
- if (!skip_initializing_odb)
- repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
- getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
-
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
disable_replace_refs();
replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
@@ -1072,10 +1067,10 @@ static void setup_git_env_internal(struct repository *repo,
fetch_if_missing = 0;
}
-static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)
+static void set_git_dir_1(struct repository *repo, const char *path)
{
xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
- setup_git_env_internal(repo, path, skip_initializing_odb);
+ setup_git_env_internal(repo, path);
}
static void update_relative_gitdir(const char *name UNUSED,
@@ -1089,7 +1084,7 @@ static void update_relative_gitdir(const char *name UNUSED,
trace_printf_key(&trace_setup_key,
"setup: move $GIT_DIR to '%s'",
path);
- set_git_dir_1(repo, path, true);
+ set_git_dir_1(repo, path);
free(path);
}
@@ -1102,7 +1097,7 @@ static void set_git_dir(struct repository *repo, const char *path, int make_real
path = realpath.buf;
}
- set_git_dir_1(repo, path, false);
+ set_git_dir_1(repo, path);
if (!is_absolute_path(path))
chdir_notify_register(NULL, update_relative_gitdir, repo);
@@ -1879,8 +1874,15 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags
}
if (is_git_directory(".")) {
+ struct strvec to_free = STRVEC_INIT;
+
set_git_dir(repo, ".", 0);
+ repo->objects = odb_new(repo,
+ getenv_safe(&to_free, DB_ENVIRONMENT),
+ getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
check_and_apply_repository_format(repo, NULL);
+
+ strvec_clear(&to_free);
return path;
}
@@ -2032,13 +2034,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
startup_info->have_repository ||
/* GIT_DIR_EXPLICIT */
getenv(GIT_DIR_ENVIRONMENT)) {
+ struct strvec to_free = STRVEC_INIT;
+
if (!repo->gitdir) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
if (!gitdir)
gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
- setup_git_env_internal(repo, gitdir, false);
+ setup_git_env_internal(repo, gitdir);
}
+ repo->objects = odb_new(repo,
+ getenv_safe(&to_free, DB_ENVIRONMENT),
+ getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
+
if (startup_info->have_repository) {
struct strbuf err = STRBUF_INIT;
@@ -2048,6 +2056,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
clear_repository_format(&repo_fmt);
strbuf_release(&err);
}
+
+ strvec_clear(&to_free);
}
/*
* Since precompose_string_if_needed() needs to look at
@@ -2796,6 +2806,7 @@ int init_db(struct repository *repo,
int exist_ok = flags & INIT_DB_EXIST_OK;
char *original_git_dir = real_pathdup(git_dir, 1);
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
+ struct strvec to_free = STRVEC_INIT;
if (real_git_dir) {
struct stat st;
@@ -2816,6 +2827,9 @@ int init_db(struct repository *repo,
}
startup_info->have_repository = 1;
+ repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
+ getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
+
/*
* Check to see if the repository version is right.
* Note that a newly created repository does not have
@@ -2879,6 +2893,7 @@ int init_db(struct repository *repo,
}
clear_repository_format(&repo_fmt);
+ strvec_clear(&to_free);
free(original_git_dir);
return 0;
}
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 4/8] repository: stop initializing the object database in `repo_set_gitdir()`
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
The function `repo_set_gitdir()` obviously sets the Git directory for a
given repository. Less obviously though, the function also configures a
couple of auxiliary settings.
One such thing is that we create the object database in this function.
This logic only happens conditionally though, as `set_git_dir()` may be
called multiple times during repository setup, and we don't want to
create the object database multiple times. This is somewhat tangled and
hard to follow.
Remove the logic from `repo_set_gitdir()` and instead initialize the
object database outside of it. This leads to some duplication right now,
but that duplication will be removed in a subsequent step where we will
start initializing the object database as part of applying the repo's
format.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
repository.c | 8 ++------
repository.h | 3 ---
setup.c | 7 ++++---
3 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/repository.c b/repository.c
index 58a13f7c4f..2c2395105f 100644
--- a/repository.c
+++ b/repository.c
@@ -181,12 +181,6 @@ void repo_set_gitdir(struct repository *repo,
free(old_gitdir);
repo_set_commondir(repo, o->commondir);
-
- if (!repo->objects)
- repo->objects = odb_new(repo, o->object_dir, o->alternate_db);
- else if (!o->skip_initializing_odb)
- BUG("cannot reinitialize an already-initialized object directory");
-
repo->disable_ref_updates = o->disable_ref_updates;
expand_base_dir(&repo->graft_file, o->graft_file,
@@ -302,6 +296,8 @@ int repo_init(struct repository *repo,
goto error;
}
+ repo->objects = odb_new(repo, NULL, NULL);
+
if (worktree)
repo_set_worktree(repo, worktree);
diff --git a/repository.h b/repository.h
index c3ec0f4b79..36e2db2633 100644
--- a/repository.h
+++ b/repository.h
@@ -221,12 +221,9 @@ const char *repo_get_work_tree(struct repository *repo);
*/
struct set_gitdir_args {
const char *commondir;
- const char *object_dir;
const char *graft_file;
const char *index_file;
- const char *alternate_db;
bool disable_ref_updates;
- bool skip_initializing_odb;
};
void repo_set_gitdir(struct repository *repo, const char *root,
diff --git a/setup.c b/setup.c
index c5015923f1..3bd3f6c592 100644
--- a/setup.c
+++ b/setup.c
@@ -1045,17 +1045,18 @@ static void setup_git_env_internal(struct repository *repo,
struct strvec to_free = STRVEC_INIT;
args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
- args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
- args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
if (getenv(GIT_QUARANTINE_ENVIRONMENT))
args.disable_ref_updates = true;
- args.skip_initializing_odb = skip_initializing_odb;
repo_set_gitdir(repo, git_dir, &args);
strvec_clear(&to_free);
+ if (!skip_initializing_odb)
+ repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
+ getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
+
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
disable_replace_refs();
replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 3/8] setup: deduplicate logic to apply repository format
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
After having discovered the repository format we then apply it to the
repository so that it knows to use the proper repository extensions. The
logic to apply the format is duplicated across three callsites, which
makes it rather painfull to add new extensions.
Introduce a new function `apply_repository_format()` that takes a repo
and applies a given format to it and adapt all callsites to use it.
While at it, rename `check_repository_format()` to clarify that it
doesn't only _check_ the format, but that it also applies it.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
repository.c | 31 +++++++-------------
setup.c | 93 ++++++++++++++++++++++++++++++++----------------------------
setup.h | 9 ++++++
3 files changed, 70 insertions(+), 63 deletions(-)
diff --git a/repository.c b/repository.c
index db57b8308b..58a13f7c4f 100644
--- a/repository.c
+++ b/repository.c
@@ -262,8 +262,8 @@ void repo_set_worktree(struct repository *repo, const char *path)
trace2_def_repo(repo);
}
-static int read_and_verify_repository_format(struct repository_format *format,
- const char *commondir)
+static int read_repository_format_from_commondir(struct repository_format *format,
+ const char *commondir)
{
int ret = 0;
struct strbuf sb = STRBUF_INIT;
@@ -272,11 +272,6 @@ static int read_and_verify_repository_format(struct repository_format *format,
read_repository_format(format, sb.buf);
strbuf_reset(&sb);
- if (verify_repository_format(format, &sb) < 0) {
- warning("%s", sb.buf);
- ret = -1;
- }
-
strbuf_release(&sb);
return ret;
}
@@ -290,6 +285,8 @@ int repo_init(struct repository *repo,
const char *worktree)
{
struct repository_format format = REPOSITORY_FORMAT_INIT;
+ struct strbuf err = STRBUF_INIT;
+
memset(repo, 0, sizeof(*repo));
initialize_repository(repo);
@@ -297,21 +294,13 @@ int repo_init(struct repository *repo,
if (repo_init_gitdir(repo, gitdir))
goto error;
- if (read_and_verify_repository_format(&format, repo->commondir))
+ if (read_repository_format_from_commondir(&format, repo->commondir))
goto error;
- repo_set_hash_algo(repo, format.hash_algo);
- repo_set_compat_hash_algo(repo, format.compat_hash_algo);
- repo_set_ref_storage_format(repo, format.ref_storage_format,
- format.ref_storage_payload);
- repo->repository_format_worktree_config = format.worktree_config;
- repo->repository_format_relative_worktrees = format.relative_worktrees;
- repo->repository_format_precious_objects = format.precious_objects;
- repo->repository_format_submodule_path_cfg = format.submodule_path_cfg;
-
- /* take ownership of format.partial_clone */
- repo->repository_format_partial_clone = format.partial_clone;
- format.partial_clone = NULL;
+ if (apply_repository_format(repo, &format, &err) < 0) {
+ warning("%s", err.buf);
+ goto error;
+ }
if (worktree)
repo_set_worktree(repo, worktree);
@@ -320,10 +309,12 @@ int repo_init(struct repository *repo,
repo_read_loose_object_map(repo);
clear_repository_format(&format);
+ strbuf_release(&err);
return 0;
error:
clear_repository_format(&format);
+ strbuf_release(&err);
repo_clear(repo);
return -1;
}
diff --git a/setup.c b/setup.c
index 252b443117..c5015923f1 100644
--- a/setup.c
+++ b/setup.c
@@ -750,8 +750,7 @@ static int check_repo_format(const char *var, const char *value,
return read_worktree_config(var, value, ctx, vdata);
}
-static int check_repository_format_gently(struct repository *repo,
- const char *gitdir,
+static int check_repository_format_gently(const char *gitdir,
struct repository_format *candidate,
int *nongit_ok)
{
@@ -765,7 +764,7 @@ static int check_repository_format_gently(struct repository *repo,
strbuf_release(&sb);
/*
- * For historical use of check_repository_format() in git-init,
+ * For historical use of check_and_apply_repository_format() in git-init,
* we treat a missing config as a silent "ok", even when nongit_ok
* is unset.
*/
@@ -782,8 +781,6 @@ static int check_repository_format_gently(struct repository *repo,
die("%s", err.buf);
}
- repo->repository_format_precious_objects = candidate->precious_objects;
-
string_list_clear(&candidate->unknown_extensions, 0);
string_list_clear(&candidate->v1_only_extensions, 0);
@@ -1140,7 +1137,7 @@ static const char *setup_explicit_git_dir(struct repository *repo,
die(_("not a git repository: '%s'"), gitdirenv);
}
- if (check_repository_format_gently(repo, gitdirenv, repo_fmt, nongit_ok)) {
+ if (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {
free(gitfile);
return NULL;
}
@@ -1217,7 +1214,7 @@ static const char *setup_discovered_git_dir(struct repository *repo,
struct repository_format *repo_fmt,
int *nongit_ok)
{
- if (check_repository_format_gently(repo, gitdir, repo_fmt, nongit_ok))
+ if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))
return NULL;
/* --work-tree is set without --git-dir; use discovered one */
@@ -1265,7 +1262,7 @@ static const char *setup_bare_git_dir(struct repository *repo,
{
int root_len;
- if (check_repository_format_gently(repo, ".", repo_fmt, nongit_ok))
+ if (check_repository_format_gently(".", repo_fmt, nongit_ok))
return NULL;
setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
@@ -1757,6 +1754,32 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
return result;
}
+int apply_repository_format(struct repository *repo,
+ const struct repository_format *format,
+ struct strbuf *err)
+{
+ if (verify_repository_format(format, err) < 0)
+ return -1;
+
+ repo_set_hash_algo(repo, format->hash_algo);
+ repo_set_compat_hash_algo(repo, format->compat_hash_algo);
+ repo_set_ref_storage_format(repo,
+ format->ref_storage_format,
+ format->ref_storage_payload);
+ repo->repository_format_worktree_config =
+ format->worktree_config;
+ repo->repository_format_submodule_path_cfg =
+ format->submodule_path_cfg;
+ repo->repository_format_relative_worktrees =
+ format->relative_worktrees;
+ repo->repository_format_partial_clone =
+ xstrdup_or_null(format->partial_clone);
+ repo->repository_format_precious_objects =
+ format->precious_objects;
+
+ return 0;
+}
+
/*
* Check the repository format version in the path found in repo_get_git_dir(repo),
* and die if it is a version we don't understand. Generally one would
@@ -1765,26 +1788,20 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
*
* If successful and fmt is not NULL, fill fmt with data.
*/
-static void check_repository_format(struct repository *repo, struct repository_format *fmt)
+static void check_and_apply_repository_format(struct repository *repo,
+ struct repository_format *fmt)
{
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
+ struct strbuf err = STRBUF_INIT;
+
if (!fmt)
fmt = &repo_fmt;
- check_repository_format_gently(repo, repo_get_git_dir(repo), fmt, NULL);
+
+ check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);
+ if (apply_repository_format(repo, fmt, &err) < 0)
+ die("%s", err.buf);
startup_info->have_repository = 1;
- repo_set_hash_algo(repo, fmt->hash_algo);
- repo_set_compat_hash_algo(repo, fmt->compat_hash_algo);
- repo_set_ref_storage_format(repo,
- fmt->ref_storage_format,
- fmt->ref_storage_payload);
- repo->repository_format_worktree_config =
- fmt->worktree_config;
- repo->repository_format_submodule_path_cfg =
- fmt->submodule_path_cfg;
- repo->repository_format_relative_worktrees =
- fmt->relative_worktrees;
- repo->repository_format_partial_clone =
- xstrdup_or_null(fmt->partial_clone);
+
clear_repository_format(&repo_fmt);
}
@@ -1862,7 +1879,7 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags
if (is_git_directory(".")) {
set_git_dir(repo, ".", 0);
- check_repository_format(repo, NULL);
+ check_and_apply_repository_format(repo, NULL);
return path;
}
@@ -2020,25 +2037,15 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
setup_git_env_internal(repo, gitdir, false);
}
+
if (startup_info->have_repository) {
- repo_set_hash_algo(repo, repo_fmt.hash_algo);
- repo_set_compat_hash_algo(repo,
- repo_fmt.compat_hash_algo);
- repo_set_ref_storage_format(repo,
- repo_fmt.ref_storage_format,
- repo_fmt.ref_storage_payload);
- repo->repository_format_worktree_config =
- repo_fmt.worktree_config;
- repo->repository_format_relative_worktrees =
- repo_fmt.relative_worktrees;
- repo->repository_format_submodule_path_cfg =
- repo_fmt.submodule_path_cfg;
- /* take ownership of repo_fmt.partial_clone */
- repo->repository_format_partial_clone =
- repo_fmt.partial_clone;
- repo_fmt.partial_clone = NULL;
- repo->repository_format_precious_objects =
- repo_fmt.precious_objects;
+ struct strbuf err = STRBUF_INIT;
+
+ if (apply_repository_format(repo, &repo_fmt, &err) < 0)
+ die("%s", err.buf);
+
+ clear_repository_format(&repo_fmt);
+ strbuf_release(&err);
}
}
/*
@@ -2814,7 +2821,7 @@ int init_db(struct repository *repo,
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
- check_repository_format(repo, &repo_fmt);
+ check_and_apply_repository_format(repo, &repo_fmt);
repository_format_configure(repo, &repo_fmt, hash, ref_storage_format);
diff --git a/setup.h b/setup.h
index 9409326fe4..5ed92f53fa 100644
--- a/setup.h
+++ b/setup.h
@@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);
int verify_repository_format(const struct repository_format *format,
struct strbuf *err);
+/*
+ * Apply the given repository format to the repo. This initializes extensions
+ * and basic data structures required for normal operation. Returns 0 on
+ * success, a negative error code otherwise.
+ */
+int apply_repository_format(struct repository *repo,
+ const struct repository_format *format,
+ struct strbuf *err);
+
const char *get_template_dir(const char *option_template);
#define INIT_DB_QUIET (1 << 0)
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 2/8] setup: drop `setup_git_env()`
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
The `setup_git_env()` function is a trivial wrapper around
`setup_git_env_internal()` and has a single call site only. Drop the
function.
While at it, drop stale documentation in "environment.h" that points to
this function, even though it hasn't been exposed to callers outside of
"setup.c" since 43ad1047a9 (setup: stop using `the_repository` in
`setup_git_env()`, 2026-03-27) anymore.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
environment.h | 8 +-------
refs.c | 3 ++-
setup.c | 7 +------
3 files changed, 4 insertions(+), 14 deletions(-)
diff --git a/environment.h b/environment.h
index 9eb97b3869..ccfcf37bfb 100644
--- a/environment.h
+++ b/environment.h
@@ -130,13 +130,6 @@ void repo_config_values_init(struct repo_config_values *cfg);
* `the_repository`. We should eventually get rid of these and make the
* dependency on a repository explicit:
*
- * - `setup_git_env()` ideally shouldn't exist as it modifies global state,
- * namely the environment. The current process shouldn't ever access that
- * state via envvars though, but should instead consult a `struct
- * repository`. When spawning new processes, we would ideally also pass a
- * `struct repository` and then set up the environment variables for the
- * child process, only.
- *
* - `have_git_dir()` should not have to exist at all. Instead, we should
* decide on whether or not we have a `struct repository`.
*
@@ -147,6 +140,7 @@ void repo_config_values_init(struct repo_config_values *cfg);
* Please do not add new global config variables here.
*/
# ifdef USE_THE_REPOSITORY_VARIABLE
+
/*
* Returns true iff we have a configured git repository (either via
* setup_git_directory, or in the environment via $GIT_DIR).
diff --git a/refs.c b/refs.c
index 0f3355d2ee..e7070eb743 100644
--- a/refs.c
+++ b/refs.c
@@ -126,7 +126,8 @@ struct ref_namespace_info ref_namespace[] = {
* points to the content of another. Unlike the other
* ref namespaces, this one can be changed by the
* GIT_REPLACE_REF_BASE environment variable. This
- * .namespace value will be overwritten in setup_git_env().
+ * .namespace value will be overwritten during repository
+ * setup.
*/
.ref = "refs/replace/",
.decoration = DECORATION_GRAFTED,
diff --git a/setup.c b/setup.c
index d723306dfe..252b443117 100644
--- a/setup.c
+++ b/setup.c
@@ -1074,11 +1074,6 @@ static void setup_git_env_internal(struct repository *repo,
fetch_if_missing = 0;
}
-static void setup_git_env(struct repository *repo, const char *git_dir)
-{
- setup_git_env_internal(repo, git_dir, false);
-}
-
static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)
{
xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
@@ -2023,7 +2018,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
if (!gitdir)
gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
- setup_git_env(repo, gitdir);
+ setup_git_env_internal(repo, gitdir, false);
}
if (startup_info->have_repository) {
repo_set_hash_algo(repo, repo_fmt.hash_algo);
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
* [PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY
From: Patrick Steinhardt @ 2026-05-21 7:42 UTC (permalink / raw)
To: git
In-Reply-To: <20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im>
In subsequent commits we'll rework how we set up the repository. This is
a somewhat intricate and thus fragile sequence, there's many things that
can go subtly wrong, and there are lots of interesting interactions that
one can discover.
One such discovered edge case was the interaction between git-init(1)
and the "GIT_OBJECT_DIRECTORY" enviroment variable. When set, the
behaviour is that the object directory should be created at the path
that the variable points to. This behaviour is documented as such in
its man page:
If the object storage directory is specified via the
GIT_OBJECT_DIRECTORY environment variable then the sha1 directories
are created underneath; otherwise, the default $GIT_DIR/objects
directory is used.
Curiously enough though we don't seem to have any tests that exercise
this directly, and thus a subsequent commit inadvertently broke this
expectation.
Plug this test gap.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/t0001-init.sh | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index e4d32bb4d2..e89feca544 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -980,4 +980,14 @@ test_expect_success 're-init reads matching includeIf.onbranch' '
test_cmp expect err
'
+test_expect_success 'init honors GIT_OBJECT_DIRECTORY' '
+ test_when_finished "rm -rf init-objdir custom-odb" &&
+ mkdir custom-odb &&
+ env GIT_OBJECT_DIRECTORY="$(pwd)/custom-odb" \
+ git init init-objdir &&
+ test_path_is_missing init-objdir/.git/objects/pack &&
+ test_path_is_dir custom-odb/pack &&
+ test_path_is_dir custom-odb/info
+'
+
test_done
--
2.54.0.771.g3ed373ac14.dirty
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox