From: Justin Tobler <jltobler@gmail.com>
To: Patrick Steinhardt <ps@pks.im>
Cc: git@vger.kernel.org
Subject: Re: [PATCH 1/4] odb: decouple source path comparisons from `the_repository`
Date: Tue, 11 Aug 2026 17:04:58 -0500 [thread overview]
Message-ID: <anuP0Mh9aBz9VdBK@denethor> (raw)
In-Reply-To: <20260810-pks-odb-eagerly-prepare-alternates-v1-1-f0fa4a4004e1@pks.im>
On 26/08/10 03:33PM, Patrick Steinhardt wrote:
> When registering alternates we deduplicate object database sources by
> their path so that the same source won't be added twice. Ever since
> cf2dc1c238 (speed up alt_odb_usable() with many alternates, 2021-07-07)
> this duplicate check is backed by a map keyed by the source's path,
> using `fspathhash()` and `fspatheq()` as hash and equality functions,
> respectively.
>
> These functions are problematic in this context for two reasons:
>
> - They implicitly depend on `the_repository` instead of the
> repository that owns the object database.
>
> - They derive case-sensitivity from `repo_ignore_case()`, which
> returns a default value in case the repository's configuration has
> not been parsed yet. Object database sources may be registered
> before that is the case, so the answer may flip depending on when a
> source gets registered.
Are alternates currently always registered after repository
configuration has been parsed? Or is this an existing bug?
> Fix this by making the comparison self-contained in the object
> database. Instead of using `fspathhash()` and `fspatheq()` we resolve
> "core.ignoreCase" manually and then use the correct comparison function
> based on the result. This requires us to migrate to a `struct hashmap`,
> as the khash interface does not give us the ability to change these
> functions.
>
> Note that we can unconditionally use `strihash()` to compute entry
> hashes regardless of case sensitivity: a hash function only needs to
> guarantee that equal keys have equal hashes, and a case-insensitive
> hash satisfies this requirement for both case-sensitive and
> case-insensitive equality.
Ok IIUC, even if we want to be case-sensitive, its ok to use
`strihash()` and have hash collisions because the compare function will
still properly distinguish between the cases. Makes sense.
> Overall it's quite debatable whether all of this complexity really is
> worth it, or whether we should just linearly search through all sources
> to find duplicates. But the mentioned commit cares about cases with
> thousands of alternates, and a linear search would of course regress
> performance quite a bit. This doesn't really feel like a reasonable case
> to care about though, but I don't feel comfortable regressing it anyway.
Ya, my first though here was also whether all of this song and dance is
really needed for alternates. There may be someone out there with tons
of alternates I guess though. Probably good to be on the safe side.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> odb.c | 63 ++++++++++++++++++++++++++++++++++++++++--------------------
> odb.h | 15 ++++++++++++++-
> odb/source.h | 7 +++++++
> 3 files changed, 63 insertions(+), 22 deletions(-)
>
> diff --git a/odb.c b/odb.c
> index bd02d8ad54..51da386f22 100644
> --- a/odb.c
> +++ b/odb.c
> @@ -2,11 +2,10 @@
> #include "abspath.h"
> #include "commit-graph.h"
> #include "config.h"
> -#include "dir.h"
> #include "environment.h"
> #include "gettext.h"
> +#include "hashmap.h"
> #include "hex.h"
> -#include "khash.h"
> #include "lockfile.h"
> #include "loose.h"
> #include "midx.h"
> @@ -29,8 +28,32 @@
> #include "trace2.h"
> #include "write-or-die.h"
>
> -KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
> - struct odb_source *, 1, fspathhash, fspatheq)
> +static int odb_source_paths_cmp(struct object_database *o,
> + const char *a, const char *b)
> +{
> + if (o->source_paths_icase < 0) {
> + int icase = 0;
> + repo_config_get_bool(o->repo, "core.ignorecase", &icase);
> + o->source_paths_icase = icase;
> + }
We now parse ignorecase configuration here directly and store the result
in `source_paths_icase`. This ensures configuration is correctly applied
regardless of whether repository configuration has been fully read yet.
> + return o->source_paths_icase ? strcasecmp(a, b) : strcmp(a, b);
> +}
> +
> +static int odb_source_by_path_cmp(const void *cb_data,
> + const struct hashmap_entry *entry,
> + const struct hashmap_entry *entry_or_key,
> + const void *keydata)
> +{
> + struct object_database *o = (struct object_database *)cb_data;
> + const struct odb_source *source = container_of(entry, const struct odb_source, by_path_entry);
> + const char *path = keydata;
> +
> + if (!path)
> + path = container_of(entry_or_key, const struct odb_source, by_path_entry)->path;
> +
> + return odb_source_paths_cmp(o, source->path, path);
> +}
Here is the comparison callback that is used for the hashmap.
> int odb_mkstemp(struct object_database *odb,
> struct strbuf *temp_filename, const char *pattern)
> @@ -58,8 +81,8 @@ int odb_mkstemp(struct object_database *odb,
> */
> static bool odb_is_source_usable(struct object_database *o, const char *path)
> {
> - int r;
> struct strbuf normalized_objdir = STRBUF_INIT;
> + struct hashmap_entry key;
> bool usable = false;
>
> strbuf_realpath(&normalized_objdir, o->sources->path, 1);
> @@ -76,20 +99,18 @@ static bool odb_is_source_usable(struct object_database *o, const char *path)
> * Prevent the common mistake of listing the same
> * thing twice, or object directory itself.
> */
> - if (!o->source_by_path) {
> - khiter_t p;
> -
> - o->source_by_path = kh_init_odb_path_map();
> + if (!hashmap_get_size(&o->source_by_path)) {
> assert(!o->sources->next);
> - p = kh_put_odb_path_map(o->source_by_path, o->sources->path, &r);
> - assert(r == 1); /* never used */
> - kh_value(o->source_by_path, p) = o->sources;
> + hashmap_entry_init(&o->sources->by_path_entry,
> + strihash(o->sources->path));
> + hashmap_add(&o->source_by_path, &o->sources->by_path_entry);
The hashmap is lazily set up with the primary source. I do find some of
the variable names like "source_by_path" a bit vague, but that isn't
really anything new here.
> }
>
> - if (fspatheq(path, normalized_objdir.buf))
> + if (!odb_source_paths_cmp(o, path, normalized_objdir.buf))
> goto out;
If the path matches the first entry in the sources list then we know it
is not an alternate.
>
> - if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path))
> + hashmap_entry_init(&key, strihash(path));
> + if (hashmap_get(&o->source_by_path, &key, path))
> goto out;
If the alternates source cannot be found for the given path, then we
also know it is not a usuable alternate.
> usable = true;
> @@ -172,8 +193,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
> {
> struct odb_source *alternate = NULL;
> struct strvec sources = STRVEC_INIT;
> - khiter_t pos;
> - int ret;
>
> if (!odb_is_source_usable(odb, source))
> goto error;
> @@ -184,10 +203,11 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
> *odb->sources_tail = alternate;
> odb->sources_tail = &(alternate->next);
>
> - pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret);
> - if (!ret)
> + hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->path));
> + if (hashmap_get(&odb->source_by_path, &alternate->by_path_entry,
> + alternate->path))
> BUG("source must not yet exist");
> - kh_value(odb->source_by_path, pos) = alternate;
> + hashmap_add(&odb->source_by_path, &alternate->by_path_entry);
Here is where alternates get registered and added to the hashmap. Makes
sense.
Overall this patch looks good.
-Justin
next prev parent reply other threads:[~2026-08-11 22:05 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-10 13:33 [PATCH 0/4] odb: eagerly load alternates Patrick Steinhardt
2026-08-10 13:33 ` [PATCH 1/4] odb: decouple source path comparisons from `the_repository` Patrick Steinhardt
2026-08-11 22:04 ` Justin Tobler [this message]
2026-08-12 5:39 ` Patrick Steinhardt
2026-08-10 13:33 ` [PATCH 2/4] odb: eagerly initialize alternates Patrick Steinhardt
2026-08-11 22:15 ` Justin Tobler
2026-08-12 5:39 ` Patrick Steinhardt
2026-08-10 13:33 ` [PATCH 3/4] odb: drop `loaded_alternates` field Patrick Steinhardt
2026-08-11 22:22 ` Justin Tobler
2026-08-10 13:33 ` [PATCH 4/4] odb: drop `alternates_db` field Patrick Steinhardt
2026-08-11 22:31 ` Justin Tobler
2026-08-12 5:39 ` Patrick Steinhardt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=anuP0Mh9aBz9VdBK@denethor \
--to=jltobler@gmail.com \
--cc=git@vger.kernel.org \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox