* [PATCH 0/9] refs: stop using `chdir_notify_reparent()`
@ 2026-06-10 14:57 Patrick Steinhardt
2026-06-10 14:57 ` [PATCH 1/9] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt
` (13 more replies)
0 siblings, 14 replies; 80+ messages in thread
From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Hi,
this patch series is a follow-up of the discussion at [1]. It converts
the reference backends to always use absolute paths internally, which
then allows us to drop the calls to `chdir_notify_reparent()`.
Unfortunately, the series has grown quite a bit larger than anticipated.
This is due to a couple of weirdnesses in how the reference database is
constructed with an "onbranch" condition. We essentially construct the
refdb twice and loose one, but we never noticed because the chdir
notification subsystem kept the pointer to it reachable.
Note that the first couple patches that touch "setup.c" aren't strictly
required. They are a remnant of a previous iteration where I tried to
solve the issue in a different way. But I ultimately figured that these
changes are worth it by themselves as they simplify "setup.c" a bit.
This series is built on top of 1ff279f340 (The 13th batch, 2026-06-09)
with ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct
object database in `apply_repository_format()`, 2026-06-04) merged into
it.
Thanks!
Patrick
[1]: <aifAVpxanV31KUpC@pks.im>
---
Patrick Steinhardt (9):
setup: inline `check_and_apply_repository_format()`
setup: stop applying repository format twice
setup: don't apply "GIT_REFERENCE_BACKEND" without a repository
refs: unregister reference stores from "chdir_notify"
chdir-notify: drop unused `chdir_notify_reparent()`
repository: free main reference database
refs: fix recursing `get_main_ref_store()` with "onbranch" config
refs: drop local buffer in `refs_compute_filesystem_location()`
refs: always use absolute paths for reference stores
chdir-notify.c | 26 ------------
chdir-notify.h | 6 +--
refs.c | 35 ++++++++++++-----
refs/files-backend.c | 6 ---
refs/packed-backend.c | 4 +-
refs/reftable-backend.c | 3 --
repository.c | 5 +++
setup.c | 96 ++++++++++++++++++---------------------------
t/pack-refs-tests.sh | 6 +--
t/t0600-reffiles-backend.sh | 4 +-
t/t1423-ref-backend.sh | 9 +++--
t/t5510-fetch.sh | 2 +-
12 files changed, 83 insertions(+), 119 deletions(-)
---
base-commit: 255322df35357168daefec8523a3cdc849edd6c1
change-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab
^ permalink raw reply [flat|nested] 80+ messages in thread* [PATCH 1/9] setup: inline `check_and_apply_repository_format()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 2/9] setup: stop applying repository format twice Patrick Steinhardt ` (12 subsequent siblings) 13 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak We have two callsites of `check_and_apply_repository_format()`. In a subsequent commit we'll want to adapt one of those callsites to change the order in which we read and apply the repository format, at which point the helper function will not really be a good fit for us anymore. Inline the function to both of the callsites. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/setup.c b/setup.c index b4652651df..a9db1f2c23 100644 --- a/setup.c +++ b/setup.c @@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo, 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 - * set_git_dir() before calling this, and use it only for "are we in a valid - * 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, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -2820,6 +2802,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 strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* @@ -2904,6 +2888,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH 2/9] setup: stop applying repository format twice 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 1/9] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-12 9:00 ` Karthik Nayak 2026-06-10 14:57 ` [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt ` (11 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak When discovering the repository in "setup.c" we apply the final repository format multiple times: - Once via `repository_format_configure()`, where we configure the repository format for both `struct repository_format` and `struct repository`. - And once via `apply_repository_format()`, where we then apply the `struct repository_format` to the `struct repository` again. As the format will be applied to the repository when applying the format it's thus somewhat unnecessary to also apply it to the repository when adapting the discovered format. The only reason we have to do this is because we call `repository_format_configure()` after we have already applied it. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the hash and reference storage format multiple times. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index a9db1f2c23..2748155964 100644 --- a/setup.c +++ b/setup.c @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) die("%s", err.buf); startup_info->have_repository = 1; - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH 2/9] setup: stop applying repository format twice 2026-06-10 14:57 ` [PATCH 2/9] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-12 9:00 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Karthik Nayak @ 2026-06-12 9:00 UTC (permalink / raw) To: Patrick Steinhardt, git [-- Attachment #1: Type: text/plain, Size: 3140 bytes --] Patrick Steinhardt <ps@pks.im> writes: > When discovering the repository in "setup.c" we apply the final > repository format multiple times: > > - Once via `repository_format_configure()`, where we configure the > repository format for both `struct repository_format` and `struct > repository`. > > - And once via `apply_repository_format()`, where we then apply the > `struct repository_format` to the `struct repository` again. > Okay so we're talking applying the repository format to the `struct repository` specifically. > As the format will be applied to the repository when applying the format > it's thus somewhat unnecessary to also apply it to the repository when > adapting the discovered format. This was a bit confusing to read at first. Okay since we already apply the format in the second step, the first is not necessary. > The only reason we have to do this is > because we call `repository_format_configure()` after we have already > applied it. Right, so there is a need to do this. > > Refactor the code so that we first configure the repository format > before applying it to the repository so that we can stop setting the > hash and reference storage format multiple times. > Makse sense. > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > setup.c | 9 ++------- > 1 file changed, 2 insertions(+), 7 deletions(-) > > diff --git a/setup.c b/setup.c > index a9db1f2c23..2748155964 100644 > --- a/setup.c > +++ b/setup.c > @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, > return ret; > } > > -static void repository_format_configure(struct repository *repo, > - struct repository_format *repo_fmt, > +static void repository_format_configure(struct repository_format *repo_fmt, > int hash, enum ref_storage_format ref_format) > { > struct default_format_config cfg = { > @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, > } else if (cfg.hash != GIT_HASH_UNKNOWN) { > repo_fmt->hash_algo = cfg.hash; > } > - repo_set_hash_algo(repo, repo_fmt->hash_algo); > > env = getenv("GIT_DEFAULT_REF_FORMAT"); > if (repo_fmt->version >= 0 && > @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, > > free(backend); > } > - > - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, > - repo_fmt->ref_storage_payload); > } > > int init_db(struct repository *repo, > @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, > * is an attempt to reinitialize new repository with an old tool. > */ > check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); > + repository_format_configure(&repo_fmt, hash, ref_storage_format); > if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) > die("%s", err.buf); > startup_info->have_repository = 1; > - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); > > /* > * Ensure `core.hidedotfiles` is processed. This must happen after we > > -- > 2.54.0.1189.g8c84645362.dirty The patch looks good. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 2/9] setup: stop applying repository format twice 2026-06-12 9:00 ` Karthik Nayak @ 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 12:36 UTC (permalink / raw) To: Karthik Nayak; +Cc: git On Fri, Jun 12, 2026 at 02:00:20AM -0700, Karthik Nayak wrote: > Patrick Steinhardt <ps@pks.im> writes: > > > When discovering the repository in "setup.c" we apply the final > > repository format multiple times: > > > > - Once via `repository_format_configure()`, where we configure the > > repository format for both `struct repository_format` and `struct > > repository`. > > > > - And once via `apply_repository_format()`, where we then apply the > > `struct repository_format` to the `struct repository` again. > > > > Okay so we're talking applying the repository format to the `struct > repository` specifically. > > > As the format will be applied to the repository when applying the format > > it's thus somewhat unnecessary to also apply it to the repository when > > adapting the discovered format. > > This was a bit confusing to read at first. Okay since we already apply > the format in the second step, the first is not necessary. I agree. I'll rephrase this a bit. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 1/9] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 2/9] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-10 17:32 ` Junio C Hamano 2026-06-10 14:57 ` [PATCH 4/9] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt ` (10 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak When discovering a repository we eventually also apply the "GIT_REFERENCE_BACKEND" environment variable to the repository. There's two problems with that: - We do this unconditionally, which is rather pointless: we really only have to configure the repository when we have found one. - We have already applied the repository format at that point in time, so we need to manually reapply it. Move the logic around so that we only apply the environment variable when a repository was discovered. This also allows us to drop the explcit call to `repo_set_ref_storage_format()` because we now adjust the format before we apply it via `apply_repository_format()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/setup.c b/setup.c index 2748155964..7b2e50a8c5 100644 --- a/setup.c +++ b/setup.c @@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2023,6 +2022,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) startup_info->have_repository || /* GIT_DIR_EXPLICIT */ getenv(GIT_DIR_ENVIRONMENT)) { + const char *ref_backend_uri; + if (!repo->gitdir) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); if (!gitdir) @@ -2030,6 +2031,24 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setup_git_env_internal(repo, gitdir); } + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } + if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; @@ -2057,25 +2076,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-10 14:57 ` [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-10 17:32 ` Junio C Hamano 2026-06-12 6:18 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Junio C Hamano @ 2026-06-10 17:32 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak Patrick Steinhardt <ps@pks.im> writes: > When discovering a repository we eventually also apply the > "GIT_REFERENCE_BACKEND" environment variable to the repository. There's > two problems with that: > > - We do this unconditionally, which is rather pointless: we really > only have to configure the repository when we have found one. > > - We have already applied the repository format at that point in time, > so we need to manually reapply it. Does the second point have a small typo, i.e., "if we have a repository, we have already applied the ref backend to it when we discovered it, so NO need to manually reapply"? > Move the logic around so that we only apply the environment variable > when a repository was discovered. This also allows us to drop the > explcit call to `repo_set_ref_storage_format()` because we now adjust > the format before we apply it via `apply_repository_format()`. Makes sense. ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-10 17:32 ` Junio C Hamano @ 2026-06-12 6:18 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-12 6:18 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Karthik Nayak On Wed, Jun 10, 2026 at 10:32:46AM -0700, Junio C Hamano wrote: > Patrick Steinhardt <ps@pks.im> writes: > > > When discovering a repository we eventually also apply the > > "GIT_REFERENCE_BACKEND" environment variable to the repository. There's > > two problems with that: > > > > - We do this unconditionally, which is rather pointless: we really > > only have to configure the repository when we have found one. > > > > - We have already applied the repository format at that point in time, > > so we need to manually reapply it. > > Does the second point have a small typo, i.e., "if we have a > repository, we have already applied the ref backend to it when we > discovered it, so NO need to manually reapply"? No, this is correct as-is. At the point in time where we handle GIT_REFERENCE_BACKEND we have already discovered the repository format, applied it to the repository, configured the reference database format et al. So because we handle GIT_REFERENCE_BACKEND _after_ that whole dance we basically have to re-configure the reference database format, which is awkward. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH 4/9] refs: unregister reference stores from "chdir_notify" 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (2 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-12 9:18 ` Karthik Nayak 2026-06-10 14:57 ` [PATCH 5/9] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt ` (9 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak When creating reference stores we register them with the "chdir_notify" subsystem. This is required because some of the paths we track may be relative paths, so we have to reparent them in case the current working directory changes. But while we register the reference stores, we never unregister them. This can have multiple outcomes: - For a repository's main reference database we essentially keep the pointer alive. We never free that database, either, and our leak checker doesn't notice because it's still registered. - For submodule and worktree reference databases we do eventually free them in `repo_clear()`, so we may keep pointers to free'd memory registered. We never notice though as we don't tend to chdir around in the middle of the process. We never noticed either of these symptoms, but they are obviously bad. Partially fix those issues by unregistering the reference stores when releasing them. The leak of the main reference database will be fixed in a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_parent()`, as there is no infrastructure to unregister the latter. It ultimately doesn't matter much though: in a subsequent commit we'll drop this infrastructure completely. We merely require this step here so that we can fix the memory leaks ahead of time. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 22 +++++++++++++++++++--- refs/packed-backend.c | 16 +++++++++++++++- refs/reftable-backend.c | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..296981584b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c45..499cb55dfa 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de..8c93070677 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, return 0; } +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH 4/9] refs: unregister reference stores from "chdir_notify" 2026-06-10 14:57 ` [PATCH 4/9] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-12 9:18 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Karthik Nayak @ 2026-06-12 9:18 UTC (permalink / raw) To: Patrick Steinhardt, git [-- Attachment #1: Type: text/plain, Size: 6726 bytes --] Patrick Steinhardt <ps@pks.im> writes: > When creating reference stores we register them with the "chdir_notify" > subsystem. This is required because some of the paths we track may be > relative paths, so we have to reparent them in case the current working > directory changes. > > But while we register the reference stores, we never unregister them. > This can have multiple outcomes: > > - For a repository's main reference database we essentially keep the > pointer alive. We never free that database, either, and our leak > checker doesn't notice because it's still registered. > > - For submodule and worktree reference databases we do eventually free > them in `repo_clear()`, so we may keep pointers to free'd memory > registered. We never notice though as we don't tend to chdir around > in the middle of the process. > So `ref_store_release()` is what is called to release a ref_store. So in the former's case, we never release the ref_store even if the repository is closed, wow. > We never noticed either of these symptoms, but they are obviously bad. > > Partially fix those issues by unregistering the reference stores when > releasing them. The leak of the main reference database will be fixed in > a subsequent commit. > > Note that this requires us to use `chdir_notify_register()` instead of > `chdir_notify_parent()`, as there is no infrastructure to unregister the Shouldn't this be s/chdir_notify_parent/chdir_notify_reparent ? > latter. It ultimately doesn't matter much though: in a subsequent commit > we'll drop this infrastructure completely. We merely require this step > here so that we can fix the memory leaks ahead of time. Right, we can't unregister when using `chdir_notify_reparent()` because it internally calls `chdir_notify_register()` with a private cb function, and we need to supply the callback function during un-registering. Makes sense. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs/files-backend.c | 22 +++++++++++++++++++--- > refs/packed-backend.c | 16 +++++++++++++++- > refs/reftable-backend.c | 16 +++++++++++++++- > 3 files changed, 49 insertions(+), 5 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index a4c7858787..296981584b 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) > } > } > > +static void files_ref_store_reparent(const char *name UNUSED, > + const char *old_cwd, > + const char *new_cwd, > + void *payload) > +{ > + struct files_ref_store *refs = payload; > + char *tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); > + free(refs->base.gitdir); > + refs->base.gitdir = tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); > + free(refs->gitcommondir); > + refs->gitcommondir = tmp; > +} > + Looks similar to `void reparent_cb()` but for both the directories. > /* > * Create a new submodule ref cache and add it to the internal > * set of caches. > @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, > > repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); > > - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); > - chdir_notify_reparent("files-backend $GIT_COMMONDIR", > - &refs->gitcommondir); > + chdir_notify_register(NULL, files_ref_store_reparent, refs); > > strbuf_release(&refdir); > > @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) > free(refs->gitcommondir); > ref_store_release(refs->packed_ref_store); > free(refs->packed_ref_store); > + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); > } > > static void files_reflog_path(struct files_ref_store *refs, > diff --git a/refs/packed-backend.c b/refs/packed-backend.c > index 0acde48c45..499cb55dfa 100644 > --- a/refs/packed-backend.c > +++ b/refs/packed-backend.c > @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) > return snapshot->refs->base.repo->hash_algo->hexsz; > } > > +static void packed_ref_store_reparent(const char *name UNUSED, > + const char *old_cwd, > + const char *new_cwd, > + void *payload) > +{ > + struct packed_ref_store *refs = payload; > + char *tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); > + free(refs->path); > + refs->path = tmp; > +} > + > /* > * Since packed-refs is only stored in the common dir, don't parse the > * payload and rely on the files-backend to set 'gitdir' correctly. > @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, > > strbuf_addf(&sb, "%s/packed-refs", gitdir); > refs->path = strbuf_detach(&sb, NULL); > - chdir_notify_reparent("packed-refs", &refs->path); > + chdir_notify_register(NULL, packed_ref_store_reparent, refs); > return ref_store; > } > > @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) > clear_snapshot(refs); > rollback_lock_file(&refs->lock); > delete_tempfile(&refs->tempfile); > + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); > free(refs->path); > } > > diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c > index 4ae22922de..8c93070677 100644 > --- a/refs/reftable-backend.c > +++ b/refs/reftable-backend.c > @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, > return 0; > } > > +static void reftable_be_reparent(const char *name UNUSED, > + const char *old_cwd, > + const char *new_cwd, > + void *payload) > +{ > + struct reftable_ref_store *refs = payload; > + char *tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); > + free(refs->base.gitdir); > + refs->base.gitdir = tmp; > +} > + > static struct ref_store *reftable_be_init(struct repository *repo, > const char *payload, > const char *gitdir, > @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, > goto done; > } > > - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); > + chdir_notify_register(NULL, reftable_be_reparent, refs); > > done: > assert(refs->err != REFTABLE_API_ERROR); > @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) > free(be); > } > strmap_clear(&refs->worktree_backends, 0); > + chdir_notify_unregister(NULL, reftable_be_reparent, refs); > } > > static int reftable_be_create_on_disk(struct ref_store *ref_store, > > -- > 2.54.0.1189.g8c84645362.dirty The changes here look good. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 4/9] refs: unregister reference stores from "chdir_notify" 2026-06-12 9:18 ` Karthik Nayak @ 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 12:36 UTC (permalink / raw) To: Karthik Nayak; +Cc: git On Fri, Jun 12, 2026 at 02:18:28AM -0700, Karthik Nayak wrote: > Patrick Steinhardt <ps@pks.im> writes: [snip] > > We never noticed either of these symptoms, but they are obviously bad. > > > > Partially fix those issues by unregistering the reference stores when > > releasing them. The leak of the main reference database will be fixed in > > a subsequent commit. > > > > Note that this requires us to use `chdir_notify_register()` instead of > > `chdir_notify_parent()`, as there is no infrastructure to unregister the > > Shouldn't this be s/chdir_notify_parent/chdir_notify_reparent ? Yup, good catch. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH 5/9] chdir-notify: drop unused `chdir_notify_reparent()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (3 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 4/9] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 6/9] repository: free main reference database Patrick Steinhardt ` (8 subsequent siblings) 13 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak With the preceding commit we've removed all callers of `chdir_notify_reparent()`, so the function is unused now. Drop it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- chdir-notify.c | 26 -------------------------- chdir-notify.h | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef..1237a45e2e 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846..36b4114472 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH 6/9] repository: free main reference database 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (4 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 5/9] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-12 9:20 ` Karthik Nayak 2026-06-10 14:57 ` [PATCH 7/9] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt ` (7 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak While we release worktree and submodule reference databases when clearing a repository, we don't ever release the main reference database. This memory leak went unnoticed because its pointer is kept alive by the "chdir_notify" subsystem. Fix the memory leak. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- repository.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repository.c b/repository.c index 187dd471c4..e2b5c6712b 100644 --- a/repository.c +++ b/repository.c @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH 6/9] repository: free main reference database 2026-06-10 14:57 ` [PATCH 6/9] repository: free main reference database Patrick Steinhardt @ 2026-06-12 9:20 ` Karthik Nayak 0 siblings, 0 replies; 80+ messages in thread From: Karthik Nayak @ 2026-06-12 9:20 UTC (permalink / raw) To: Patrick Steinhardt, git [-- Attachment #1: Type: text/plain, Size: 1131 bytes --] Patrick Steinhardt <ps@pks.im> writes: > While we release worktree and submodule reference databases when > clearing a repository, we don't ever release the main reference > database. This memory leak went unnoticed because its pointer is > kept alive by the "chdir_notify" subsystem. > > Fix the memory leak. > Funny, cause long ago I looked into this and thought I was clearly missing something and eventually forgot about it. Good to know that I was correct :) > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > repository.c | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/repository.c b/repository.c > index 187dd471c4..e2b5c6712b 100644 > --- a/repository.c > +++ b/repository.c > @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) > FREE_AND_NULL(repo->remote_state); > } > > + if (repo->refs_private) { > + ref_store_release(repo->refs_private); > + FREE_AND_NULL(repo->refs_private); > + } > + > strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) > ref_store_release(e->value); > strmap_clear(&repo->submodule_ref_stores, 1); > > -- > 2.54.0.1189.g8c84645362.dirty [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH 7/9] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (5 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 6/9] repository: free main reference database Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 8/9] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt ` (6 subsequent siblings) 13 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak When we have an "onbranch" condition we need to ask the reference database whether HEAD currently points at the configured branch. This unfortunately creates a chicken-and-egg problem: - The reference database needs to read the configuration so that it can configure itself. - The configuration needs to construct a reference database to fully parse all of its conditionals. The way we handle this is by simply excluding "onbranch" conditionals when we haven't yet configured the reference database. The mechanism for this is broken though: to verify whether or not we have configured the reference database we check whether its format is set to `REF_STORAGE_UNKNOWN` in `include_by_branch()`. But typically, the format _is_ already known at that time because we set it up during repository discovery in "setup.c". The consequence is that we have recursion: 1. We call `get_main_ref_store()`. 2. We don't yet have a reference store, so we call `ref_store_init()`. 3. We parse the configuration required for the reference store. 4. We eventually end up in `include_by_branch()`. 5. We have already configured the reference storage format, so we end up calling `get_main_ref_store()` again. We still haven't finished (1) though, so `get_main_ref_store()` will now call `ref_store_init()` a second time. The end result is that we have constructed the same reference store twice. Of course, as both reference stores would be assigned to `refs_private`, we leak one of those two instances. This never surfaced as an actual leak though because the pointer is kept alive by the "chdir_notify" subsystem. For now, we can fix the issue by explicitly unsetting the reference storage format before constructing it. This makes the mentioned check trigger as expected, and consequently we won't end up constructing a second reference database at all. Ultimately, this means that we consistently stop evaluating "onbranch" conditions when constructing the main reference database. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index d3caa9a633..e69b9b8ac8 100644 --- a/refs.c +++ b/refs.c @@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + enum ref_storage_format format; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); - r->refs_private = ref_store_init(r, r->ref_storage_format, - r->gitdir, REF_STORE_ALL_CAPS); + /* + * When constructing the reference backend we'll end up reading the Git + * configuration. This means we'll also try to evaluate "onbranch" + * conditions. + * + * We cannot read branches when constructing the refdb, so it is not + * possible to evaluate those conditions in the first place. To gate + * their evaluation we check whether or not the reference storage + * format has been configured -- we thus have to temporarily set it to + * UNKNOWN here so that we don't end up recursing. + */ + format = r->ref_storage_format; + r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; + r->refs_private = ref_store_init(r, format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + r->ref_storage_format = format; + return r->refs_private; } -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH 8/9] refs: drop local buffer in `refs_compute_filesystem_location()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (6 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 7/9] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 9/9] refs: always use absolute paths for reference stores Patrick Steinhardt ` (5 subsequent siblings) 13 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak We're using a local buffer in `refs_compute_filesystem_location()` that is only used so that we can fill it and then call `strbuf_realpath()` on its result. This roundtrip isn't necessary though: `strbuf_realpath()` already knows to use a single buffer as both input and output at the same time. So all this does is to add a bit of confusion and an extra memory allocation. Drop the local buffer. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/refs.c b/refs.c index e69b9b8ac8..4912510590 100644 --- a/refs.c +++ b/refs.c @@ -3571,8 +3571,6 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, bool *is_worktree, struct strbuf *refdir, struct strbuf *ref_common_dir) { - struct strbuf sb = STRBUF_INIT; - *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir); if (!payload) { @@ -3586,8 +3584,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, } if (!is_absolute_path(payload)) { - strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload); - strbuf_realpath(ref_common_dir, sb.buf, 1); + strbuf_addf(ref_common_dir, "/%s", payload); + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); } else { strbuf_realpath(ref_common_dir, payload, 1); } @@ -3600,6 +3598,4 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, BUG("worktree path does not contain slash"); strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); } - - strbuf_release(&sb); } -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH 9/9] refs: always use absolute paths for reference stores 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (7 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 8/9] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt @ 2026-06-10 14:57 ` Patrick Steinhardt 2026-06-12 9:58 ` Karthik Nayak 2026-06-11 6:53 ` [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Jeff King ` (4 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-10 14:57 UTC (permalink / raw) To: git; +Cc: Karthik Nayak Both the "files" and "reftable" backends use `refs_compute_filesystem_location()` to figure out the location of both the git and common directories. Depending on how the function is called we may or may not return an absolute path. There isn't really a good reason to use relative paths though. Quite on the contrary, because we sometimes use relative paths we are forced to register for chdir(3p) notifications via `chdir_notify_reparent()`. Adapt the function to always return absolute paths. This results in a user-visible change in behaviour where we now unconditionally print absolute paths in error messages. But arguably, that change in behaviour is acceptable and may even be good in cases where a Git command may end up accessing references across multiple different repositories. Furthermore, drop the calls to `chdir_notify_reparent()`, which aren't required anymore now that the paths are always absolute. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 11 ++++++++--- refs/files-backend.c | 22 ---------------------- refs/packed-backend.c | 18 +----------------- refs/reftable-backend.c | 17 ----------------- t/pack-refs-tests.sh | 6 +++--- t/t0600-reffiles-backend.sh | 4 ++-- t/t1423-ref-backend.sh | 9 ++++++--- t/t5510-fetch.sh | 2 +- 8 files changed, 21 insertions(+), 68 deletions(-) diff --git a/refs.c b/refs.c index 4912510590..8679677bf7 100644 --- a/refs.c +++ b/refs.c @@ -3579,15 +3579,16 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, * worktree path, as the 'gitdir' here is already the worktree * path and is different from 'commondir' denoted by 'ref_common_dir'. */ + strbuf_reset(refdir); strbuf_addstr(refdir, gitdir); - return; + goto out; } if (!is_absolute_path(payload)) { strbuf_addf(ref_common_dir, "/%s", payload); - strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); } else { - strbuf_realpath(ref_common_dir, payload, 1); + strbuf_reset(ref_common_dir); + strbuf_addstr(ref_common_dir, payload); } strbuf_addbuf(refdir, ref_common_dir); @@ -3598,4 +3599,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, BUG("worktree path does not contain slash"); strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); } + +out: + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); + strbuf_realpath(refdir, refdir->buf, 1); } diff --git a/refs/files-backend.c b/refs/files-backend.c index 296981584b..762f392e67 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -21,7 +21,6 @@ #include "../lockfile.h" #include "../path.h" #include "../dir.h" -#include "../chdir-notify.h" #include "../setup.h" #include "../worktree.h" #include "../wrapper.h" @@ -100,23 +99,6 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } -static void files_ref_store_reparent(const char *name UNUSED, - const char *old_cwd, - const char *new_cwd, - void *payload) -{ - struct files_ref_store *refs = payload; - char *tmp; - - tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); - free(refs->base.gitdir); - refs->base.gitdir = tmp; - - tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); - free(refs->gitcommondir); - refs->gitcommondir = tmp; -} - /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -145,10 +127,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_register(NULL, files_ref_store_reparent, refs); - strbuf_release(&refdir); - return ref_store; } @@ -197,7 +176,6 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); - chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 499cb55dfa..89e41a35a3 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -13,7 +13,6 @@ #include "packed-backend.h" #include "../iterator.h" #include "../lockfile.h" -#include "../chdir-notify.h" #include "../statinfo.h" #include "../worktree.h" #include "../wrapper.h" @@ -211,19 +210,6 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } -static void packed_ref_store_reparent(const char *name UNUSED, - const char *old_cwd, - const char *new_cwd, - void *payload) -{ - struct packed_ref_store *refs = payload; - char *tmp; - - tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); - free(refs->path); - refs->path = tmp; -} - /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -239,10 +225,9 @@ struct ref_store *packed_ref_store_init(struct repository *repo, base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed); refs->store_flags = opts->access_flags; - strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_register(NULL, packed_ref_store_reparent, refs); + return ref_store; } @@ -287,7 +272,6 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); - chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 8c93070677..8cc1dbbbdd 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -2,7 +2,6 @@ #include "../git-compat-util.h" #include "../abspath.h" -#include "../chdir-notify.h" #include "../config.h" #include "../dir.h" #include "../environment.h" @@ -365,19 +364,6 @@ static int reftable_be_config(const char *var, const char *value, return 0; } -static void reftable_be_reparent(const char *name UNUSED, - const char *old_cwd, - const char *new_cwd, - void *payload) -{ - struct reftable_ref_store *refs = payload; - char *tmp; - - tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); - free(refs->base.gitdir); - refs->base.gitdir = tmp; -} - static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -460,8 +446,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_register(NULL, reftable_be_reparent, refs); - done: assert(refs->err != REFTABLE_API_ERROR); strbuf_release(&ref_common_dir); @@ -487,7 +471,6 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); - chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, diff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh index d76b087b09..357413ba3c 100644 --- a/t/pack-refs-tests.sh +++ b/t/pack-refs-tests.sh @@ -237,7 +237,7 @@ test_expect_success 'reject packed-refs with unterminated line' ' cp .git/packed-refs .git/packed-refs.bak && test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && printf "%s" "$HEAD refs/zzzzz" >>.git/packed-refs && - echo "fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz" >expected_err && + echo "fatal: unterminated line in $(pwd)/.git/packed-refs: $HEAD refs/zzzzz" >expected_err && test_must_fail git for-each-ref >out 2>err && test_cmp expected_err err ' @@ -246,7 +246,7 @@ test_expect_success 'reject packed-refs containing junk' ' cp .git/packed-refs .git/packed-refs.bak && test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && printf "%s\n" "bogus content" >>.git/packed-refs && - echo "fatal: unexpected line in .git/packed-refs: bogus content" >expected_err && + echo "fatal: unexpected line in $(pwd)/.git/packed-refs: bogus content" >expected_err && test_must_fail git for-each-ref >out 2>err && test_cmp expected_err err ' @@ -255,7 +255,7 @@ test_expect_success 'reject packed-refs with a short SHA-1' ' cp .git/packed-refs .git/packed-refs.bak && test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && printf "%.7s %s\n" $HEAD refs/zzzzz >>.git/packed-refs && - printf "fatal: unexpected line in .git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err && + printf "fatal: unexpected line in $(pwd)/.git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err && test_must_fail git for-each-ref >out 2>err && test_cmp expected_err err ' diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index 74bfa2e9ba..b17f0940c2 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -96,7 +96,7 @@ test_expect_success 'non-empty directory blocks create' - <<\EOT : >.git/$prefix/foo/bar/baz.lock && test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && cat >expected <<-EOF && - fatal: cannot lock ref '$prefix/foo': there is a non-empty directory '.git/$prefix/foo' blocking reference '$prefix/foo' + fatal: cannot lock ref '$prefix/foo': there is a non-empty directory '$(pwd)/.git/$prefix/foo' blocking reference '$prefix/foo' EOF printf "%s\n" "update $prefix/foo $C" | test_must_fail git update-ref --stdin 2>output.err && @@ -135,7 +135,7 @@ test_expect_success 'non-empty directory blocks indirect create' - <<\EOT : >.git/$prefix/foo/bar/baz.lock && test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && cat >expected <<-EOF && - fatal: cannot lock ref '$prefix/symref': there is a non-empty directory '.git/$prefix/foo' blocking reference '$prefix/foo' + fatal: cannot lock ref '$prefix/symref': there is a non-empty directory '$(pwd)/.git/$prefix/foo' blocking reference '$prefix/foo' EOF printf "%s\n" "update $prefix/symref $C" | test_must_fail git update-ref --stdin 2>output.err && diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh index fd47d77e8e..875857f2d0 100755 --- a/t/t1423-ref-backend.sh +++ b/t/t1423-ref-backend.sh @@ -145,7 +145,8 @@ do test_commit 3 && git refs migrate --dry-run --ref-format=$to_format >out && - BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + BACKEND_PATH=$(sed "s/.* the result can be found at ${SQ}\(.*\)${SQ}$/\1/" out) && + test_path_is_dir "$BACKEND_PATH" && test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" ) ' @@ -160,7 +161,8 @@ do test_commit 3 && git refs migrate --dry-run --ref-format=$to_format >out && - BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + BACKEND_PATH=$(sed "s/.* the result can be found at ${SQ}\(.*\)${SQ}$/\1/" out) && + test_path_is_dir "$BACKEND_PATH" && test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" && @@ -187,7 +189,8 @@ do test_commit 3 && git refs migrate --dry-run --ref-format=$to_format >out && - BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + BACKEND_PATH=$(sed "s/.* the result can be found at ${SQ}\(.*\)${SQ}$/\1/" out) && + test_path_is_dir "$BACKEND_PATH" && run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ "worktree add ../wt 2" "$method" && diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index eca9a973b5..d5f84d99df 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -1741,7 +1741,7 @@ test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensiti cd case_insensitive && git remote add origin -- ../case_sensitive_df && test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && - test_grep "cannot lock ref ${SQ}refs/remotes/origin/foo${SQ}: there is a non-empty directory ${SQ}./refs/remotes/origin/foo${SQ} blocking reference ${SQ}refs/remotes/origin/foo${SQ}" err && + test_grep "cannot lock ref ${SQ}refs/remotes/origin/foo${SQ}: there is a non-empty directory ${SQ}$(pwd)/refs/remotes/origin/foo${SQ} blocking reference ${SQ}refs/remotes/origin/foo${SQ}" err && git rev-parse refs/heads/main >expect && git rev-parse refs/heads/Foo/bar >actual && test_cmp expect actual -- 2.54.0.1189.g8c84645362.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH 9/9] refs: always use absolute paths for reference stores 2026-06-10 14:57 ` [PATCH 9/9] refs: always use absolute paths for reference stores Patrick Steinhardt @ 2026-06-12 9:58 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Karthik Nayak @ 2026-06-12 9:58 UTC (permalink / raw) To: Patrick Steinhardt, git [-- Attachment #1: Type: text/plain, Size: 7719 bytes --] Patrick Steinhardt <ps@pks.im> writes: > Both the "files" and "reftable" backends use > `refs_compute_filesystem_location()` to figure out the location of both > the git and common directories. Depending on how the function is called > we may or may not return an absolute path. > > There isn't really a good reason to use relative paths though. Quite on > the contrary, because we sometimes use relative paths we are forced to > register for chdir(3p) notifications via `chdir_notify_reparent()`. > With the previous changes added, we register via `chdir_notify_register()` > Adapt the function to always return absolute paths. This results in a > user-visible change in behaviour where we now unconditionally print > absolute paths in error messages. But arguably, that change in behaviour > is acceptable and may even be good in cases where a Git command may end > up accessing references across multiple different repositories. > > Furthermore, drop the calls to `chdir_notify_reparent()`, which aren't > required anymore now that the paths are always absolute. > Same here, should be `chdir_notify_register()` > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs.c | 11 ++++++++--- > refs/files-backend.c | 22 ---------------------- > refs/packed-backend.c | 18 +----------------- > refs/reftable-backend.c | 17 ----------------- > t/pack-refs-tests.sh | 6 +++--- > t/t0600-reffiles-backend.sh | 4 ++-- > t/t1423-ref-backend.sh | 9 ++++++--- > t/t5510-fetch.sh | 2 +- > 8 files changed, 21 insertions(+), 68 deletions(-) > > diff --git a/refs.c b/refs.c > index 4912510590..8679677bf7 100644 > --- a/refs.c > +++ b/refs.c > @@ -3579,15 +3579,16 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, > * worktree path, as the 'gitdir' here is already the worktree > * path and is different from 'commondir' denoted by 'ref_common_dir'. > */ > + strbuf_reset(refdir); > strbuf_addstr(refdir, gitdir); > - return; > + goto out; > } > > if (!is_absolute_path(payload)) { > strbuf_addf(ref_common_dir, "/%s", payload); > - strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); > } else { > - strbuf_realpath(ref_common_dir, payload, 1); > + strbuf_reset(ref_common_dir); > + strbuf_addstr(ref_common_dir, payload); > } > > strbuf_addbuf(refdir, ref_common_dir); > @@ -3598,4 +3599,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, > BUG("worktree path does not contain slash"); > strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); > } > + > +out: > + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); > + strbuf_realpath(refdir, refdir->buf, 1); > } > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 296981584b..762f392e67 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -21,7 +21,6 @@ > #include "../lockfile.h" > #include "../path.h" > #include "../dir.h" > -#include "../chdir-notify.h" > #include "../setup.h" > #include "../worktree.h" > #include "../wrapper.h" > @@ -100,23 +99,6 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) > } > } > > -static void files_ref_store_reparent(const char *name UNUSED, > - const char *old_cwd, > - const char *new_cwd, > - void *payload) > -{ > - struct files_ref_store *refs = payload; > - char *tmp; > - > - tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); > - free(refs->base.gitdir); > - refs->base.gitdir = tmp; > - > - tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); > - free(refs->gitcommondir); > - refs->gitcommondir = tmp; > -} > - > /* > * Create a new submodule ref cache and add it to the internal > * set of caches. > @@ -145,10 +127,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, > > repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); > > - chdir_notify_register(NULL, files_ref_store_reparent, refs); > - > strbuf_release(&refdir); > - > return ref_store; > } > > @@ -197,7 +176,6 @@ static void files_ref_store_release(struct ref_store *ref_store) > free(refs->gitcommondir); > ref_store_release(refs->packed_ref_store); > free(refs->packed_ref_store); > - chdir_notify_unregister(NULL, files_ref_store_reparent, refs); > } > > static void files_reflog_path(struct files_ref_store *refs, > diff --git a/refs/packed-backend.c b/refs/packed-backend.c > index 499cb55dfa..89e41a35a3 100644 > --- a/refs/packed-backend.c > +++ b/refs/packed-backend.c > @@ -13,7 +13,6 @@ > #include "packed-backend.h" > #include "../iterator.h" > #include "../lockfile.h" > -#include "../chdir-notify.h" > #include "../statinfo.h" > #include "../worktree.h" > #include "../wrapper.h" > @@ -211,19 +210,6 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) > return snapshot->refs->base.repo->hash_algo->hexsz; > } > > -static void packed_ref_store_reparent(const char *name UNUSED, > - const char *old_cwd, > - const char *new_cwd, > - void *payload) > -{ > - struct packed_ref_store *refs = payload; > - char *tmp; > - > - tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); > - free(refs->path); > - refs->path = tmp; > -} > - > /* > * Since packed-refs is only stored in the common dir, don't parse the > * payload and rely on the files-backend to set 'gitdir' correctly. > @@ -239,10 +225,9 @@ struct ref_store *packed_ref_store_init(struct repository *repo, > > base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed); > refs->store_flags = opts->access_flags; > - > strbuf_addf(&sb, "%s/packed-refs", gitdir); > refs->path = strbuf_detach(&sb, NULL); > - chdir_notify_register(NULL, packed_ref_store_reparent, refs); > + > return ref_store; > } > > @@ -287,7 +272,6 @@ static void packed_ref_store_release(struct ref_store *ref_store) > clear_snapshot(refs); > rollback_lock_file(&refs->lock); > delete_tempfile(&refs->tempfile); > - chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); > free(refs->path); > } > > diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c > index 8c93070677..8cc1dbbbdd 100644 > --- a/refs/reftable-backend.c > +++ b/refs/reftable-backend.c > @@ -2,7 +2,6 @@ > > #include "../git-compat-util.h" > #include "../abspath.h" > -#include "../chdir-notify.h" > #include "../config.h" > #include "../dir.h" > #include "../environment.h" > @@ -365,19 +364,6 @@ static int reftable_be_config(const char *var, const char *value, > return 0; > } > > -static void reftable_be_reparent(const char *name UNUSED, > - const char *old_cwd, > - const char *new_cwd, > - void *payload) > -{ > - struct reftable_ref_store *refs = payload; > - char *tmp; > - > - tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); > - free(refs->base.gitdir); > - refs->base.gitdir = tmp; > -} > - > static struct ref_store *reftable_be_init(struct repository *repo, > const char *payload, > const char *gitdir, > @@ -460,8 +446,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, > goto done; > } > > - chdir_notify_register(NULL, reftable_be_reparent, refs); > - > done: > assert(refs->err != REFTABLE_API_ERROR); > strbuf_release(&ref_common_dir); > @@ -487,7 +471,6 @@ static void reftable_be_release(struct ref_store *ref_store) > free(be); > } > strmap_clear(&refs->worktree_backends, 0); > - chdir_notify_unregister(NULL, reftable_be_reparent, refs); > } > > static int reftable_be_create_on_disk(struct ref_store *ref_store, > The changes look good to me. [snip] [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 9/9] refs: always use absolute paths for reference stores 2026-06-12 9:58 ` Karthik Nayak @ 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 12:36 UTC (permalink / raw) To: Karthik Nayak; +Cc: git On Fri, Jun 12, 2026 at 02:58:19AM -0700, Karthik Nayak wrote: > Patrick Steinhardt <ps@pks.im> writes: > > > Both the "files" and "reftable" backends use > > `refs_compute_filesystem_location()` to figure out the location of both > > the git and common directories. Depending on how the function is called > > we may or may not return an absolute path. > > > > There isn't really a good reason to use relative paths though. Quite on > > the contrary, because we sometimes use relative paths we are forced to > > register for chdir(3p) notifications via `chdir_notify_reparent()`. > > > > With the previous changes added, we register via > `chdir_notify_register()` > > > Adapt the function to always return absolute paths. This results in a > > user-visible change in behaviour where we now unconditionally print > > absolute paths in error messages. But arguably, that change in behaviour > > is acceptable and may even be good in cases where a Git command may end > > up accessing references across multiple different repositories. > > > > Furthermore, drop the calls to `chdir_notify_reparent()`, which aren't > > required anymore now that the paths are always absolute. > > > > Same here, should be `chdir_notify_register()` Yes, will fix. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 0/9] refs: stop using `chdir_notify_reparent()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (8 preceding siblings ...) 2026-06-10 14:57 ` [PATCH 9/9] refs: always use absolute paths for reference stores Patrick Steinhardt @ 2026-06-11 6:53 ` Jeff King 2026-06-12 6:18 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (3 subsequent siblings) 13 siblings, 1 reply; 80+ messages in thread From: Jeff King @ 2026-06-11 6:53 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak On Wed, Jun 10, 2026 at 04:57:06PM +0200, Patrick Steinhardt wrote: > this patch series is a follow-up of the discussion at [1]. It converts > the reference backends to always use absolute paths internally, which > then allows us to drop the calls to `chdir_notify_reparent()`. We added chdir-notify to suport set_work_tree(). Commit 8500e0de3f (set_work_tree: use chdir_notify, 2018-03-30) mentions an optimization from 044bbbcb63 (Make git_dir a path relative to work_tree in setup_work_tree(), 2008-06-19). That commit demonstrates some measurable speedup from using relative versus absolute paths. If we move to a world of all absolute paths where chdir-notify is not necessary, will we lose that optimization? I'm not sure how much it matters in practice these days, or if those timings could be repeated. And they weren't all _that_ big to start with. I guess it may depend on how deep your repo is within your filesystem, too. -Peff ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 0/9] refs: stop using `chdir_notify_reparent()` 2026-06-11 6:53 ` [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Jeff King @ 2026-06-12 6:18 ` Patrick Steinhardt 2026-06-13 14:00 ` Jeff King 0 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-12 6:18 UTC (permalink / raw) To: Jeff King; +Cc: git, Karthik Nayak On Thu, Jun 11, 2026 at 02:53:46AM -0400, Jeff King wrote: > On Wed, Jun 10, 2026 at 04:57:06PM +0200, Patrick Steinhardt wrote: > > > this patch series is a follow-up of the discussion at [1]. It converts > > the reference backends to always use absolute paths internally, which > > then allows us to drop the calls to `chdir_notify_reparent()`. > > We added chdir-notify to suport set_work_tree(). Commit 8500e0de3f > (set_work_tree: use chdir_notify, 2018-03-30) mentions an optimization > from 044bbbcb63 (Make git_dir a path relative to work_tree in > setup_work_tree(), 2008-06-19). That commit demonstrates some measurable > speedup from using relative versus absolute paths. Oh, that is context I wasn't aware of. Not much of a surprise though, given that this is from 2008 :) So thanks a lot for the pointer! > If we move to a world of all absolute paths where chdir-notify is not > necessary, will we lose that optimization? Probably. Unfortunately, the commit doesn't have any repeatable benchmarks in there, so it's hard to say whether we could still reproduce those issues or not. > I'm not sure how much it matters in practice these days, or if those > timings could be repeated. And they weren't all _that_ big to start > with. I guess it may depend on how deep your repo is within your > filesystem, too. Ideally, we'd have the best of both worlds: absolute paths everywhere without the performance hit. A while back I had a discussion with Torvalds on the securiy mailing list around this issue, and ultimately the conclusion was that the best way forward would be to use openat(3p). This wouldn't only allow us to optimize cases like this, but it also has the added benefit that we're much less prone to TOCTOU-style issues and we might even be able to use flags like O_BENEATH. So it would basically be win-win. The only problem is of course that Windows doesn't have openat(3p), so we'd have to emulate it, and that's where I always lost the desire to do this. When waking up this morning though I had the thought that we shouldn't try to emulate openat(3p) directly, but instead create a higher-level interface. struct fsroot; /* * Open a new filesystem root at the given directory. All subsequent * calls to open will be relative to this fsroot. */ struct fsroot *fsroot_new(const char *dir); /* * Create a new fsroot from a subdirectory relative to the given * root directory. */ struct fsroot *fsroot_new_subdir(struct fsroot *r, const char *dir); /* * Open a new file relative to the given fsroot. This will use the * equivalent of O_BENEATH so that we only ever open files that are * located below the fsroot. */ int fsroot_open(struct fsroot *r, const char *path, int oflag, ...); This is of course heavily inspired by similar interfaces that exist in Go [1]. By having such a higher-level abstraction it should also be way easier to port this to different platforms, where we can then add safety features like O_BENEATH when available on any given platform. The idea here would be that we can then convert some subsystems to use those structures instead of tracking paths. I'd for example love for the repository's working tree to use this mechanism so that we can squash a whole class of potential security issues when checking out files that end in locations we didn't intend to. Thanks! Patrick [1]: https://pkg.go.dev/io/fs#FS ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 0/9] refs: stop using `chdir_notify_reparent()` 2026-06-12 6:18 ` Patrick Steinhardt @ 2026-06-13 14:00 ` Jeff King 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Jeff King @ 2026-06-13 14:00 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak On Fri, Jun 12, 2026 at 08:18:16AM +0200, Patrick Steinhardt wrote: > > If we move to a world of all absolute paths where chdir-notify is not > > necessary, will we lose that optimization? > > Probably. Unfortunately, the commit doesn't have any repeatable > benchmarks in there, so it's hard to say whether we could still > reproduce those issues or not. Here's an easy-ish reproduction specific to the ref code: rm -rf a/ dir=$(perl -e 'print "a/" x 1024') mkdir -p $dir && cd $dir && git init && git commit --allow-empty -m foo && seq -f 'create refs/heads/foo%05g HEAD' 10000 | git update-ref --stdin && time git show-ref Before your series, I get timings like this: real 0m0.078s user 0m0.020s sys 0m0.057s After, I get: real 0m0.876s user 0m0.004s sys 0m0.872s So it really is measurable (and I did not expect the effect to be nearly so large). Unsurprisingly the extra CPU goes to system time. But obviously that case is quite silly. It's an absurdly deep hierarchy, and 10,000 loose refs is a lot. Just running "git pack-refs --all" brings the before/after to roughly the same timings (around 40ms -- faster even than the before timing). So it _can_ matter, but I think ultimately the better direction is probably "make fewer syscalls". Which we do via packfiles, and via packed-refs, and eventually via reftables, all of which put more data into a single file. I offer the script above more as food for thought, and not necessarily an argument against your series. > Ideally, we'd have the best of both worlds: absolute paths everywhere > without the performance hit. A while back I had a discussion with > Torvalds on the securiy mailing list around this issue, and ultimately > the conclusion was that the best way forward would be to use openat(3p). > > This wouldn't only allow us to optimize cases like this, but it also has > the added benefit that we're much less prone to TOCTOU-style issues and > we might even be able to use flags like O_BENEATH. So it would basically > be win-win. The only problem is of course that Windows doesn't have > openat(3p), so we'd have to emulate it, and that's where I always lost > the desire to do this. > > When waking up this morning though I had the thought that we shouldn't > try to emulate openat(3p) directly, but instead create a higher-level > interface. > [...] Yeah, I think given a decent interface it might not be so bad. It would mean code thinking about filesystem syscalls in a different way, but if done subsystem-by-subsystem it might be OK to do incrementally. Much of the code that would want to switch to this is using repo_git_path() or similar already (and getting rid of those remaining static-buffer functions would be a nice bonus). I do wonder if your series here to move to absolute paths makes the TOCTOU situation a little worse. With a relative path, once we are "inside" the repo then we are only susceptible to changes within it. Whereas with an absolute path, if one of the intermediate paths changes from under us, there may be confusion. Without thinking on it too hard, though, I'd guess if any such case is a security problem, it already was during the "open" part (because it implies that the attacker controls paths below you in the hierarchy, and you had to get to your cwd _somehow_, at which point they could have attacked you then). -Peff ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH 0/9] refs: stop using `chdir_notify_reparent()` 2026-06-13 14:00 ` Jeff King @ 2026-06-15 12:36 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 12:36 UTC (permalink / raw) To: Jeff King; +Cc: git, Karthik Nayak On Sat, Jun 13, 2026 at 10:00:24AM -0400, Jeff King wrote: > On Fri, Jun 12, 2026 at 08:18:16AM +0200, Patrick Steinhardt wrote: > > > > If we move to a world of all absolute paths where chdir-notify is not > > > necessary, will we lose that optimization? > > > > Probably. Unfortunately, the commit doesn't have any repeatable > > benchmarks in there, so it's hard to say whether we could still > > reproduce those issues or not. > > Here's an easy-ish reproduction specific to the ref code: > > rm -rf a/ > dir=$(perl -e 'print "a/" x 1024') > mkdir -p $dir && > cd $dir && > git init && > git commit --allow-empty -m foo && > seq -f 'create refs/heads/foo%05g HEAD' 10000 | > git update-ref --stdin && > time git show-ref > > Before your series, I get timings like this: > > real 0m0.078s > user 0m0.020s > sys 0m0.057s > > After, I get: > > real 0m0.876s > user 0m0.004s > sys 0m0.872s > > So it really is measurable (and I did not expect the effect to be nearly > so large). Unsurprisingly the extra CPU goes to system time. This is indeed surprisingly bad. > But obviously that case is quite silly. It's an absurdly deep hierarchy, > and 10,000 loose refs is a lot. Just running "git pack-refs --all" > brings the before/after to roughly the same timings (around 40ms -- > faster even than the before timing). > > So it _can_ matter, but I think ultimately the better direction is > probably "make fewer syscalls". Which we do via packfiles, and via > packed-refs, and eventually via reftables, all of which put more data > into a single file. > > I offer the script above more as food for thought, and not necessarily > an argument against your series. Hum, yeah. I'm a bit hesitant to just wave your findings away. I mean I agree with you that it's unlikely to really matter in practice. But you never really know, and I'm not sure that I consider dropping the chdir infra important enough to knowingly take that hit. I definitely think that we should merge the remainder of this series though, as these patches simplify "setup.c" and fix a couple of memory leaks. But maybe we drop the last patch for now and... > > Ideally, we'd have the best of both worlds: absolute paths everywhere > > without the performance hit. A while back I had a discussion with > > Torvalds on the securiy mailing list around this issue, and ultimately > > the conclusion was that the best way forward would be to use openat(3p). > > > > This wouldn't only allow us to optimize cases like this, but it also has > > the added benefit that we're much less prone to TOCTOU-style issues and > > we might even be able to use flags like O_BENEATH. So it would basically > > be win-win. The only problem is of course that Windows doesn't have > > openat(3p), so we'd have to emulate it, and that's where I always lost > > the desire to do this. > > > > When waking up this morning though I had the thought that we shouldn't > > try to emulate openat(3p) directly, but instead create a higher-level > > interface. > > [...] > > Yeah, I think given a decent interface it might not be so bad. It would > mean code thinking about filesystem syscalls in a different way, but if > done subsystem-by-subsystem it might be OK to do incrementally. Much of > the code that would want to switch to this is using repo_git_path() or > similar already (and getting rid of those remaining static-buffer > functions would be a nice bonus). > > I do wonder if your series here to move to absolute paths makes the > TOCTOU situation a little worse. With a relative path, once we are > "inside" the repo then we are only susceptible to changes within it. > Whereas with an absolute path, if one of the intermediate paths changes > from under us, there may be confusion. > > Without thinking on it too hard, though, I'd guess if any such case is a > security problem, it already was during the "open" part (because it > implies that the attacker controls paths below you in the hierarchy, and > you had to get to your cwd _somehow_, at which point they could have > attacked you then). ... eventually give this idea here a test? Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 0/8] refs: stop using `chdir_notify_reparent()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (9 preceding siblings ...) 2026-06-11 6:53 ` [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Jeff King @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt ` (7 more replies) 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (2 subsequent siblings) 13 siblings, 8 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King Hi, this patch series is a follow-up of the discussion at [1]. It converts the reference backends to always use absolute paths internally, which then allows us to drop the calls to `chdir_notify_reparent()`. Unfortunately, the series has grown quite a bit larger than anticipated. This is due to a couple of weirdnesses in how the reference database is constructed with an "onbranch" condition. We essentially construct the refdb twice and loose one, but we never noticed because the chdir notification subsystem kept the pointer to it reachable. Note that the first couple patches that touch "setup.c" aren't strictly required. They are a remnant of a previous iteration where I tried to solve the issue in a different way. But I ultimately figured that these changes are worth it by themselves as they simplify "setup.c" a bit. This series is built on top of 1ff279f340 (The 13th batch, 2026-06-09) with ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct object database in `apply_repository_format()`, 2026-06-04) merged into it. Changes in v2: - Drop the last patch. This seemingly destroys the whole purpose of the patch series, but after Peff's hint that this is actually a performance optimization I'm less inclined to drop the chdir_notify infra. I still think that the remainder of the patches make sense standalone, as they simplify "setup.c" and clean memory leaks. Going forward I'd like to investigate the idea of introducing a `struct fsroot` infrastructure that uses the platform-equivalent of openat et al. - Improve a couple of commit messages. - Link to v1: https://patch.msgid.link/20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im Thanks! Patrick [1]: <aifAVpxanV31KUpC@pks.im> --- Patrick Steinhardt (8): setup: inline `check_and_apply_repository_format()` setup: stop applying repository format twice setup: don't apply "GIT_REFERENCE_BACKEND" without a repository refs: unregister reference stores from "chdir_notify" chdir-notify: drop unused `chdir_notify_reparent()` repository: free main reference database refs: fix recursing `get_main_ref_store()` with "onbranch" config refs: drop local buffer in `refs_compute_filesystem_location()` chdir-notify.c | 26 -------------- chdir-notify.h | 6 +--- refs.c | 28 ++++++++++----- refs/files-backend.c | 22 ++++++++++-- refs/packed-backend.c | 16 ++++++++- refs/reftable-backend.c | 16 ++++++++- repository.c | 5 +++ setup.c | 96 ++++++++++++++++++++----------------------------- 8 files changed, 113 insertions(+), 102 deletions(-) Range-diff versus v1: 1: ef72346c7d = 1: 3902fecdb9 setup: inline `check_and_apply_repository_format()` 2: 157fc098b3 ! 2: 9479ffc370 setup: stop applying repository format twice @@ Commit message When discovering the repository in "setup.c" we apply the final repository format multiple times: - - Once via `repository_format_configure()`, where we configure the - repository format for both `struct repository_format` and `struct - repository`. + - Once via `repository_format_configure()`, where we apply the hash + algorithm and ref storage format to both `struct repository_format` + and `struct repository`. - - And once via `apply_repository_format()`, where we then apply the - `struct repository_format` to the `struct repository` again. + - And once via `apply_repository_format()`, where we apply these two + settings from `struct repository_format` to `struct repository`. - As the format will be applied to the repository when applying the format - it's thus somewhat unnecessary to also apply it to the repository when - adapting the discovered format. The only reason we have to do this is - because we call `repository_format_configure()` after we have already - applied it. + With the current flow both of these are in fact necessary. But this is + only because we call `repository_format_configure()` after we have + called `apply_repository_format()`. Consequently, if we only changed the + repository format in `repository_format_configure()` it would never + propagate to the repository. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the 3: f1429ae8c9 = 3: 09299c488d setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 4: 4137f0f083 ! 4: dff1bfec7a refs: unregister reference stores from "chdir_notify" @@ Commit message a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of - `chdir_notify_parent()`, as there is no infrastructure to unregister the + `chdir_notify_reparent()`, as there is no infrastructure to unregister the latter. It ultimately doesn't matter much though: in a subsequent commit we'll drop this infrastructure completely. We merely require this step here so that we can fix the memory leaks ahead of time. 5: dbda87ab6a = 5: 367806c5ba chdir-notify: drop unused `chdir_notify_reparent()` 6: b1d2f39def = 6: e8eb346876 repository: free main reference database 7: f7f5028a10 = 7: 090f80707c refs: fix recursing `get_main_ref_store()` with "onbranch" config 8: 818c0878f9 = 8: 14b12a8f10 refs: drop local buffer in `refs_compute_filesystem_location()` 9: 7408f9b69f < -: ---------- refs: always use absolute paths for reference stores --- base-commit: 255322df35357168daefec8523a3cdc849edd6c1 change-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 1/8] setup: inline `check_and_apply_repository_format()` 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 2/8] setup: stop applying repository format twice Patrick Steinhardt ` (6 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King We have two callsites of `check_and_apply_repository_format()`. In a subsequent commit we'll want to adapt one of those callsites to change the order in which we read and apply the repository format, at which point the helper function will not really be a good fit for us anymore. Inline the function to both of the callsites. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/setup.c b/setup.c index b4652651df..a9db1f2c23 100644 --- a/setup.c +++ b/setup.c @@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo, 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 - * set_git_dir() before calling this, and use it only for "are we in a valid - * 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, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -2820,6 +2802,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 strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* @@ -2904,6 +2888,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v2 2/8] setup: stop applying repository format twice 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-17 17:22 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt ` (5 subsequent siblings) 7 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King When discovering the repository in "setup.c" we apply the final repository format multiple times: - Once via `repository_format_configure()`, where we apply the hash algorithm and ref storage format to both `struct repository_format` and `struct repository`. - And once via `apply_repository_format()`, where we apply these two settings from `struct repository_format` to `struct repository`. With the current flow both of these are in fact necessary. But this is only because we call `repository_format_configure()` after we have called `apply_repository_format()`. Consequently, if we only changed the repository format in `repository_format_configure()` it would never propagate to the repository. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the hash and reference storage format multiple times. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index a9db1f2c23..2748155964 100644 --- a/setup.c +++ b/setup.c @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) die("%s", err.buf); startup_info->have_repository = 1; - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 2/8] setup: stop applying repository format twice 2026-06-15 13:56 ` [PATCH v2 2/8] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-17 17:22 ` Justin Tobler 0 siblings, 0 replies; 80+ messages in thread From: Justin Tobler @ 2026-06-17 17:22 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/15 03:56PM, Patrick Steinhardt wrote: > When discovering the repository in "setup.c" we apply the final > repository format multiple times: > > - Once via `repository_format_configure()`, where we apply the hash > algorithm and ref storage format to both `struct repository_format` > and `struct repository`. > > - And once via `apply_repository_format()`, where we apply these two > settings from `struct repository_format` to `struct repository`. > > With the current flow both of these are in fact necessary. But this is > only because we call `repository_format_configure()` after we have > called `apply_repository_format()`. Consequently, if we only changed the > repository format in `repository_format_configure()` it would never > propagate to the repository. Ok, so because `repository_format_configure()` is invoked after the repository format was already applied, it had to explictly configure the repository as well. > Refactor the code so that we first configure the repository format > before applying it to the repository so that we can stop setting the > hash and reference storage format multiple times. Makes sense. Sounds like a good change. > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > setup.c | 9 ++------- > 1 file changed, 2 insertions(+), 7 deletions(-) > > diff --git a/setup.c b/setup.c > index a9db1f2c23..2748155964 100644 > --- a/setup.c > +++ b/setup.c > @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, > return ret; > } > > -static void repository_format_configure(struct repository *repo, > - struct repository_format *repo_fmt, > +static void repository_format_configure(struct repository_format *repo_fmt, > int hash, enum ref_storage_format ref_format) We now only care about configuring the repository format and will let `apply_repository_format()` handle setting the repository. Looks good. [snip] > @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, > * is an attempt to reinitialize new repository with an old tool. > */ > check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); > + repository_format_configure(&repo_fmt, hash, ref_storage_format); > if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) > die("%s", err.buf); > startup_info->have_repository = 1; > - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); `apply_repository_format()` already has the logic to set the hash algo and ref storage format from the repository format, so change changing the order here is ok and a good change. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 2/8] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-17 17:43 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt ` (4 subsequent siblings) 7 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King When discovering a repository we eventually also apply the "GIT_REFERENCE_BACKEND" environment variable to the repository. There's two problems with that: - We do this unconditionally, which is rather pointless: we really only have to configure the repository when we have found one. - We have already applied the repository format at that point in time, so we need to manually reapply it. Move the logic around so that we only apply the environment variable when a repository was discovered. This also allows us to drop the explcit call to `repo_set_ref_storage_format()` because we now adjust the format before we apply it via `apply_repository_format()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/setup.c b/setup.c index 2748155964..7b2e50a8c5 100644 --- a/setup.c +++ b/setup.c @@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2023,6 +2022,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) startup_info->have_repository || /* GIT_DIR_EXPLICIT */ getenv(GIT_DIR_ENVIRONMENT)) { + const char *ref_backend_uri; + if (!repo->gitdir) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); if (!gitdir) @@ -2030,6 +2031,24 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setup_git_env_internal(repo, gitdir); } + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } + if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; @@ -2057,25 +2076,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-15 13:56 ` [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-17 17:43 ` Justin Tobler 2026-06-18 6:53 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Justin Tobler @ 2026-06-17 17:43 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/15 03:56PM, Patrick Steinhardt wrote: > When discovering a repository we eventually also apply the > "GIT_REFERENCE_BACKEND" environment variable to the repository. There's > two problems with that: > > - We do this unconditionally, which is rather pointless: we really > only have to configure the repository when we have found one. I agree that configuring the repository reference format when there isn't a repository to begin doesn't sound very useful. > - We have already applied the repository format at that point in time, > so we need to manually reapply it. > > Move the logic around so that we only apply the environment variable > when a repository was discovered. This also allows us to drop the > explcit call to `repo_set_ref_storage_format()` because we now adjust > the format before we apply it via `apply_repository_format()`. Make sense. > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- [snip] > @@ -2023,6 +2022,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) > startup_info->have_repository || > /* GIT_DIR_EXPLICIT */ > getenv(GIT_DIR_ENVIRONMENT)) { > + const char *ref_backend_uri; > + > if (!repo->gitdir) { > const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); > if (!gitdir) > @@ -2030,6 +2031,24 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) > setup_git_env_internal(repo, gitdir); > } > > + /* > + * The env variable should override the repository config > + * for 'extensions.refStorage'. > + */ > + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); > + if (ref_backend_uri) { > + char *format; > + > + free(repo_fmt.ref_storage_payload); > + > + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); > + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); > + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) > + die(_("unknown ref storage format: '%s'"), format); > + > + free(format); > + } > + > if (startup_info->have_repository) { > struct strbuf err = STRBUF_INIT; Hmmm, we only invoke `apply_repository_format()` if we indeed have a repository (having just GIT_DIR_ENVIRONMENT set isn't enough). Should we instead nest this logic right above `apply_repository_format()` in the same block? -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-17 17:43 ` Justin Tobler @ 2026-06-18 6:53 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:53 UTC (permalink / raw) To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King On Wed, Jun 17, 2026 at 12:43:02PM -0500, Justin Tobler wrote: > On 26/06/15 03:56PM, Patrick Steinhardt wrote: > > @@ -2030,6 +2031,24 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) > > setup_git_env_internal(repo, gitdir); > > } > > > > + /* > > + * The env variable should override the repository config > > + * for 'extensions.refStorage'. > > + */ > > + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); > > + if (ref_backend_uri) { > > + char *format; > > + > > + free(repo_fmt.ref_storage_payload); > > + > > + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); > > + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); > > + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) > > + die(_("unknown ref storage format: '%s'"), format); > > + > > + free(format); > > + } > > + > > if (startup_info->have_repository) { > > struct strbuf err = STRBUF_INIT; > > Hmmm, we only invoke `apply_repository_format()` if we indeed have a > repository (having just GIT_DIR_ENVIRONMENT set isn't enough). Should we > instead nest this logic right above `apply_repository_format()` in the > same block? Yup, that makes sense indeed. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (2 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-17 18:02 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt ` (3 subsequent siblings) 7 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King When creating reference stores we register them with the "chdir_notify" subsystem. This is required because some of the paths we track may be relative paths, so we have to reparent them in case the current working directory changes. But while we register the reference stores, we never unregister them. This can have multiple outcomes: - For a repository's main reference database we essentially keep the pointer alive. We never free that database, either, and our leak checker doesn't notice because it's still registered. - For submodule and worktree reference databases we do eventually free them in `repo_clear()`, so we may keep pointers to free'd memory registered. We never notice though as we don't tend to chdir around in the middle of the process. We never noticed either of these symptoms, but they are obviously bad. Partially fix those issues by unregistering the reference stores when releasing them. The leak of the main reference database will be fixed in a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_reparent()`, as there is no infrastructure to unregister the latter. It ultimately doesn't matter much though: in a subsequent commit we'll drop this infrastructure completely. We merely require this step here so that we can fix the memory leaks ahead of time. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 22 +++++++++++++++++++--- refs/packed-backend.c | 16 +++++++++++++++- refs/reftable-backend.c | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..296981584b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c45..499cb55dfa 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de..8c93070677 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, return 0; } +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" 2026-06-15 13:56 ` [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-17 18:02 ` Justin Tobler 2026-06-17 18:07 ` Justin Tobler 2026-06-18 6:54 ` Patrick Steinhardt 0 siblings, 2 replies; 80+ messages in thread From: Justin Tobler @ 2026-06-17 18:02 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/15 03:56PM, Patrick Steinhardt wrote: > When creating reference stores we register them with the "chdir_notify" > subsystem. This is required because some of the paths we track may be > relative paths, so we have to reparent them in case the current working > directory changes. > > But while we register the reference stores, we never unregister them. > This can have multiple outcomes: > > - For a repository's main reference database we essentially keep the > pointer alive. We never free that database, either, and our leak > checker doesn't notice because it's still registered. > > - For submodule and worktree reference databases we do eventually free > them in `repo_clear()`, so we may keep pointers to free'd memory > registered. We never notice though as we don't tend to chdir around > in the middle of the process. > > We never noticed either of these symptoms, but they are obviously bad. > > Partially fix those issues by unregistering the reference stores when > releasing them. The leak of the main reference database will be fixed in > a subsequent commit. > > Note that this requires us to use `chdir_notify_register()` instead of > `chdir_notify_reparent()`, as there is no infrastructure to unregister the > latter. It ultimately doesn't matter much though: in a subsequent commit > we'll drop this infrastructure completely. We merely require this step > here so that we can fix the memory leaks ahead of time. Since this version of the series dropped the last patch which stopped using `chdir_notify_reparent()`, does the log message here need to be updated? > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs/files-backend.c | 22 +++++++++++++++++++--- > refs/packed-backend.c | 16 +++++++++++++++- > refs/reftable-backend.c | 16 +++++++++++++++- > 3 files changed, 49 insertions(+), 5 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index a4c7858787..296981584b 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) > } > } > > +static void files_ref_store_reparent(const char *name UNUSED, > + const char *old_cwd, > + const char *new_cwd, > + void *payload) > +{ > + struct files_ref_store *refs = payload; > + char *tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); > + free(refs->base.gitdir); > + refs->base.gitdir = tmp; > + > + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); > + free(refs->gitcommondir); > + refs->gitcommondir = tmp; > +} Ok, here is introduce a callback specific to the file ref store to handle reparenting both the gitdir and commondir. > /* > * Create a new submodule ref cache and add it to the internal > * set of caches. > @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, > > repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); > > - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); > - chdir_notify_reparent("files-backend $GIT_COMMONDIR", > - &refs->gitcommondir); > + chdir_notify_register(NULL, files_ref_store_reparent, refs); We use the new callback here instead of relying on the generic callback used by `chdir_notify_reparent()`. > strbuf_release(&refdir); > > @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) > free(refs->gitcommondir); > ref_store_release(refs->packed_ref_store); > free(refs->packed_ref_store); > + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); This allows us to unregister the callback and avoid holding on references which may have been free'd. Makes sense. The rest of the patch does the exact same for the packed ref store and reftable BE which look correct too. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" 2026-06-17 18:02 ` Justin Tobler @ 2026-06-17 18:07 ` Justin Tobler 2026-06-18 6:54 ` Patrick Steinhardt 1 sibling, 0 replies; 80+ messages in thread From: Justin Tobler @ 2026-06-17 18:07 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/17 01:02PM, Justin Tobler wrote: > On 26/06/15 03:56PM, Patrick Steinhardt wrote: > > Note that this requires us to use `chdir_notify_register()` instead of > > `chdir_notify_reparent()`, as there is no infrastructure to unregister the > > latter. It ultimately doesn't matter much though: in a subsequent commit > > we'll drop this infrastructure completely. We merely require this step > > here so that we can fix the memory leaks ahead of time. > > Since this version of the series dropped the last patch which stopped > using `chdir_notify_reparent()`, does the log message here need to be > updated? After looking at the next patch, I realized we are referring to just the `chdir_notify_reparent()` function here which is no longer used. The current log message makes sense. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" 2026-06-17 18:02 ` Justin Tobler 2026-06-17 18:07 ` Justin Tobler @ 2026-06-18 6:54 ` Patrick Steinhardt 1 sibling, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King On Wed, Jun 17, 2026 at 01:02:23PM -0500, Justin Tobler wrote: > On 26/06/15 03:56PM, Patrick Steinhardt wrote: > > When creating reference stores we register them with the "chdir_notify" > > subsystem. This is required because some of the paths we track may be > > relative paths, so we have to reparent them in case the current working > > directory changes. > > > > But while we register the reference stores, we never unregister them. > > This can have multiple outcomes: > > > > - For a repository's main reference database we essentially keep the > > pointer alive. We never free that database, either, and our leak > > checker doesn't notice because it's still registered. > > > > - For submodule and worktree reference databases we do eventually free > > them in `repo_clear()`, so we may keep pointers to free'd memory > > registered. We never notice though as we don't tend to chdir around > > in the middle of the process. > > > > We never noticed either of these symptoms, but they are obviously bad. > > > > Partially fix those issues by unregistering the reference stores when > > releasing them. The leak of the main reference database will be fixed in > > a subsequent commit. > > > > Note that this requires us to use `chdir_notify_register()` instead of > > `chdir_notify_reparent()`, as there is no infrastructure to unregister the > > latter. It ultimately doesn't matter much though: in a subsequent commit > > we'll drop this infrastructure completely. We merely require this step > > here so that we can fix the memory leaks ahead of time. > > Since this version of the series dropped the last patch which stopped > using `chdir_notify_reparent()`, does the log message here need to be > updated? True, will do. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 5/8] chdir-notify: drop unused `chdir_notify_reparent()` 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (3 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 6/8] repository: free main reference database Patrick Steinhardt ` (2 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King With the preceding commit we've removed all callers of `chdir_notify_reparent()`, so the function is unused now. Drop it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- chdir-notify.c | 26 -------------------------- chdir-notify.h | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef..1237a45e2e 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846..36b4114472 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v2 6/8] repository: free main reference database 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (4 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-17 18:09 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 7 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King While we release worktree and submodule reference databases when clearing a repository, we don't ever release the main reference database. This memory leak went unnoticed because its pointer is kept alive by the "chdir_notify" subsystem. Fix the memory leak. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- repository.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repository.c b/repository.c index 187dd471c4..e2b5c6712b 100644 --- a/repository.c +++ b/repository.c @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 6/8] repository: free main reference database 2026-06-15 13:56 ` [PATCH v2 6/8] repository: free main reference database Patrick Steinhardt @ 2026-06-17 18:09 ` Justin Tobler 0 siblings, 0 replies; 80+ messages in thread From: Justin Tobler @ 2026-06-17 18:09 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/15 03:56PM, Patrick Steinhardt wrote: > While we release worktree and submodule reference databases when > clearing a repository, we don't ever release the main reference > database. This memory leak went unnoticed because its pointer is > kept alive by the "chdir_notify" subsystem. > > Fix the memory leak. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > repository.c | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/repository.c b/repository.c > index 187dd471c4..e2b5c6712b 100644 > --- a/repository.c > +++ b/repository.c > @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) > FREE_AND_NULL(repo->remote_state); > } > > + if (repo->refs_private) { > + ref_store_release(repo->refs_private); > + FREE_AND_NULL(repo->refs_private); > + } Nice fix. :) ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (5 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 6/8] repository: free main reference database Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 2026-06-17 18:41 ` Justin Tobler 2026-06-18 16:40 ` Jeff King 2026-06-15 13:56 ` [PATCH v2 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 7 siblings, 2 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King When we have an "onbranch" condition we need to ask the reference database whether HEAD currently points at the configured branch. This unfortunately creates a chicken-and-egg problem: - The reference database needs to read the configuration so that it can configure itself. - The configuration needs to construct a reference database to fully parse all of its conditionals. The way we handle this is by simply excluding "onbranch" conditionals when we haven't yet configured the reference database. The mechanism for this is broken though: to verify whether or not we have configured the reference database we check whether its format is set to `REF_STORAGE_UNKNOWN` in `include_by_branch()`. But typically, the format _is_ already known at that time because we set it up during repository discovery in "setup.c". The consequence is that we have recursion: 1. We call `get_main_ref_store()`. 2. We don't yet have a reference store, so we call `ref_store_init()`. 3. We parse the configuration required for the reference store. 4. We eventually end up in `include_by_branch()`. 5. We have already configured the reference storage format, so we end up calling `get_main_ref_store()` again. We still haven't finished (1) though, so `get_main_ref_store()` will now call `ref_store_init()` a second time. The end result is that we have constructed the same reference store twice. Of course, as both reference stores would be assigned to `refs_private`, we leak one of those two instances. This never surfaced as an actual leak though because the pointer is kept alive by the "chdir_notify" subsystem. For now, we can fix the issue by explicitly unsetting the reference storage format before constructing it. This makes the mentioned check trigger as expected, and consequently we won't end up constructing a second reference database at all. Ultimately, this means that we consistently stop evaluating "onbranch" conditions when constructing the main reference database. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index d3caa9a633..e69b9b8ac8 100644 --- a/refs.c +++ b/refs.c @@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + enum ref_storage_format format; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); - r->refs_private = ref_store_init(r, r->ref_storage_format, - r->gitdir, REF_STORE_ALL_CAPS); + /* + * When constructing the reference backend we'll end up reading the Git + * configuration. This means we'll also try to evaluate "onbranch" + * conditions. + * + * We cannot read branches when constructing the refdb, so it is not + * possible to evaluate those conditions in the first place. To gate + * their evaluation we check whether or not the reference storage + * format has been configured -- we thus have to temporarily set it to + * UNKNOWN here so that we don't end up recursing. + */ + format = r->ref_storage_format; + r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; + r->refs_private = ref_store_init(r, format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + r->ref_storage_format = format; + return r->refs_private; } -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-15 13:56 ` [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt @ 2026-06-17 18:41 ` Justin Tobler 2026-06-18 5:59 ` Patrick Steinhardt 2026-06-18 16:40 ` Jeff King 1 sibling, 1 reply; 80+ messages in thread From: Justin Tobler @ 2026-06-17 18:41 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/15 03:56PM, Patrick Steinhardt wrote: [snip] > diff --git a/refs.c b/refs.c > index d3caa9a633..e69b9b8ac8 100644 > --- a/refs.c > +++ b/refs.c > @@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store) > > struct ref_store *get_main_ref_store(struct repository *r) > { > + enum ref_storage_format format; > + > if (r->refs_private) > return r->refs_private; > > if (!r->gitdir) > BUG("attempting to get main_ref_store outside of repository"); > > - r->refs_private = ref_store_init(r, r->ref_storage_format, > - r->gitdir, REF_STORE_ALL_CAPS); > + /* > + * When constructing the reference backend we'll end up reading the Git > + * configuration. This means we'll also try to evaluate "onbranch" > + * conditions. > + * > + * We cannot read branches when constructing the refdb, so it is not > + * possible to evaluate those conditions in the first place. To gate > + * their evaluation we check whether or not the reference storage > + * format has been configured -- we thus have to temporarily set it to > + * UNKNOWN here so that we don't end up recursing. > + */ > + format = r->ref_storage_format; > + r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; Is this really the best signal to indicate that a repository ref store has not been initialized? Temporarily setting the storage format to REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that `include_by_branch()` probably shouldn't be using it to begin with if its not reliable. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-17 18:41 ` Justin Tobler @ 2026-06-18 5:59 ` Patrick Steinhardt 2026-06-18 14:15 ` Justin Tobler 0 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 5:59 UTC (permalink / raw) To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King On Wed, Jun 17, 2026 at 01:41:40PM -0500, Justin Tobler wrote: > On 26/06/15 03:56PM, Patrick Steinhardt wrote: > [snip] > > diff --git a/refs.c b/refs.c > > index d3caa9a633..e69b9b8ac8 100644 > > --- a/refs.c > > +++ b/refs.c > > @@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store) > > > > struct ref_store *get_main_ref_store(struct repository *r) > > { > > + enum ref_storage_format format; > > + > > if (r->refs_private) > > return r->refs_private; > > > > if (!r->gitdir) > > BUG("attempting to get main_ref_store outside of repository"); > > > > - r->refs_private = ref_store_init(r, r->ref_storage_format, > > - r->gitdir, REF_STORE_ALL_CAPS); > > + /* > > + * When constructing the reference backend we'll end up reading the Git > > + * configuration. This means we'll also try to evaluate "onbranch" > > + * conditions. > > + * > > + * We cannot read branches when constructing the refdb, so it is not > > + * possible to evaluate those conditions in the first place. To gate > > + * their evaluation we check whether or not the reference storage > > + * format has been configured -- we thus have to temporarily set it to > > + * UNKNOWN here so that we don't end up recursing. > > + */ > > + format = r->ref_storage_format; > > + r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; > > Is this really the best signal to indicate that a repository ref store > has not been initialized? Temporarily setting the storage format to > REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that > `include_by_branch()` probably shouldn't be using it to begin with if > its not reliable. True, but we don't really have a better signal to the best of my knowledge. Ideally, we'd be able to use the existence `r->refs_private` as signal. But that doesn't really work as the reference database is lazily constructed, and the recursion happens in the exact function that would construct it in the first place. And there indeed are cases where reading the configuration is the first caller of `get_main_ref_store()`. My first internal iteration tried to make this non-lazily constructed so that we can use it as a proper signal. But that led to a bunch of problems where we now parsed configuration way earlier than we currently do, and that in turn led to all kinds of errors. I was able to fix all of those errors except one: we expect `git config set` to work in a misconfigured repository so that the user can fix the misconfig without having to manually edit the Git configuration files. But when constructing the refdb eagerly we will die early in such cases. We could again work around that issue, but that unfortunately evolved into a proper mess that I eventually discarded as unworkable. I think this is an inherent design flaw: constructing the refdb requires us to be able to parse the configuration, but constructing the configuration may require us to construct the refdb. So this awkwardness is built into Git's design, unfortunately. So I'd really love to have a better signal, as I fully agree that the above workaround is nothing more but a hack. But I'm just not sure what that signal would be. And this version here does exactly what we want: we honor "onbranch" conditionals in all cases, except when constructing the main reference store. Even if it's ugly. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-18 5:59 ` Patrick Steinhardt @ 2026-06-18 14:15 ` Justin Tobler 2026-06-18 14:51 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Justin Tobler @ 2026-06-18 14:15 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/18 07:59AM, Patrick Steinhardt wrote: > On Wed, Jun 17, 2026 at 01:41:40PM -0500, Justin Tobler wrote: > > Is this really the best signal to indicate that a repository ref store > > has not been initialized? Temporarily setting the storage format to > > REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that > > `include_by_branch()` probably shouldn't be using it to begin with if > > its not reliable. > > True, but we don't really have a better signal to the best of my > knowledge. Ideally, we'd be able to use the existence `r->refs_private` > as signal. But that doesn't really work as the reference database is > lazily constructed, and the recursion happens in the exact function that > would construct it in the first place. And there indeed are cases where > reading the configuration is the first caller of `get_main_ref_store()`. Ok, my first thought was also whether we could use the existence of the ref store as a signal, but I guess that won't work here. > My first internal iteration tried to make this non-lazily constructed so > that we can use it as a proper signal. But that led to a bunch of > problems where we now parsed configuration way earlier than we currently > do, and that in turn led to all kinds of errors. I was able to fix all > of those errors except one: we expect `git config set` to work in a > misconfigured repository so that the user can fix the misconfig without > having to manually edit the Git configuration files. But when > constructing the refdb eagerly we will die early in such cases. > > We could again work around that issue, but that unfortunately evolved > into a proper mess that I eventually discarded as unworkable. I think > this is an inherent design flaw: constructing the refdb requires us to > be able to parse the configuration, but constructing the configuration > may require us to construct the refdb. So this awkwardness is built into > Git's design, unfortunately. > > So I'd really love to have a better signal, as I fully agree that the > above workaround is nothing more but a hack. But I'm just not sure what > that signal would be. And this version here does exactly what we want: > we honor "onbranch" conditionals in all cases, except when constructing > the main reference store. Even if it's ugly. Could we embed an `initialized` boolean in `struct ref_store` that gets set when the ref store is properly initialized and use that as a signal instead? I'm not sure how complex introducing this would be though. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-18 14:15 ` Justin Tobler @ 2026-06-18 14:51 ` Patrick Steinhardt 2026-06-18 15:53 ` Justin Tobler 0 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 14:51 UTC (permalink / raw) To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King On Thu, Jun 18, 2026 at 09:15:00AM -0500, Justin Tobler wrote: > On 26/06/18 07:59AM, Patrick Steinhardt wrote: > > On Wed, Jun 17, 2026 at 01:41:40PM -0500, Justin Tobler wrote: > > > Is this really the best signal to indicate that a repository ref store > > > has not been initialized? Temporarily setting the storage format to > > > REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that > > > `include_by_branch()` probably shouldn't be using it to begin with if > > > its not reliable. > > > > True, but we don't really have a better signal to the best of my > > knowledge. Ideally, we'd be able to use the existence `r->refs_private` > > as signal. But that doesn't really work as the reference database is > > lazily constructed, and the recursion happens in the exact function that > > would construct it in the first place. And there indeed are cases where > > reading the configuration is the first caller of `get_main_ref_store()`. > > Ok, my first thought was also whether we could use the existence of the > ref store as a signal, but I guess that won't work here. > > > My first internal iteration tried to make this non-lazily constructed so > > that we can use it as a proper signal. But that led to a bunch of > > problems where we now parsed configuration way earlier than we currently > > do, and that in turn led to all kinds of errors. I was able to fix all > > of those errors except one: we expect `git config set` to work in a > > misconfigured repository so that the user can fix the misconfig without > > having to manually edit the Git configuration files. But when > > constructing the refdb eagerly we will die early in such cases. > > > > We could again work around that issue, but that unfortunately evolved > > into a proper mess that I eventually discarded as unworkable. I think > > this is an inherent design flaw: constructing the refdb requires us to > > be able to parse the configuration, but constructing the configuration > > may require us to construct the refdb. So this awkwardness is built into > > Git's design, unfortunately. > > > > So I'd really love to have a better signal, as I fully agree that the > > above workaround is nothing more but a hack. But I'm just not sure what > > that signal would be. And this version here does exactly what we want: > > we honor "onbranch" conditionals in all cases, except when constructing > > the main reference store. Even if it's ugly. > > Could we embed an `initialized` boolean in `struct ref_store` that gets > set when the ref store is properly initialized and use that as a signal > instead? I'm not sure how complex introducing this would be though. We could, but I'm not sure what that would really buy us. It would basically be one more bit of state that we have to track going forward, and thus one more source of inconsistencies. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-18 14:51 ` Patrick Steinhardt @ 2026-06-18 15:53 ` Justin Tobler 0 siblings, 0 replies; 80+ messages in thread From: Justin Tobler @ 2026-06-18 15:53 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King On 26/06/18 04:51PM, Patrick Steinhardt wrote: > On Thu, Jun 18, 2026 at 09:15:00AM -0500, Justin Tobler wrote: > > Could we embed an `initialized` boolean in `struct ref_store` that gets > > set when the ref store is properly initialized and use that as a signal > > instead? I'm not sure how complex introducing this would be though. > > We could, but I'm not sure what that would really buy us. It would > basically be one more bit of state that we have to track going forward, > and thus one more source of inconsistencies. My naive thought here is that if the ref store knows when it is initialized, this could be used as a more reliable signal by `include_by_branch()`. I guess the problem though would be that, at that point in time, we are still inside `ref_store_init()` and thus the ref store is not fully initialized anyways. I was hoping we could avoid the hack of temporarily setting the ref format here, but introducing state specific to tracking whether its ok to parse onbranch conditions in the config is probably not worth it I guess. -Justin ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-15 13:56 ` [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-17 18:41 ` Justin Tobler @ 2026-06-18 16:40 ` Jeff King 2026-06-19 6:25 ` Patrick Steinhardt 1 sibling, 1 reply; 80+ messages in thread From: Jeff King @ 2026-06-18 16:40 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak On Mon, Jun 15, 2026 at 03:56:53PM +0200, Patrick Steinhardt wrote: > When we have an "onbranch" condition we need to ask the reference > database whether HEAD currently points at the configured branch. This > unfortunately creates a chicken-and-egg problem: > > - The reference database needs to read the configuration so that it > can configure itself. > > - The configuration needs to construct a reference database to fully > parse all of its conditionals. > > The way we handle this is by simply excluding "onbranch" conditionals > when we haven't yet configured the reference database. My gut feeling upon reading this is that some part of the config reading is being done wrong to create this chicken-and-egg situation. I'd expect the ref database config (like the ref format) to be read not through the regular config subsystem, but via read_repository_format() and friends. And while that does build on the regular config code, it should never enable includes at all. So includeIf.onbranch:foo.path is just another uninteresting config key to it. In other words, there should be two passes over the config file: one to load basic repository information (and not respect includes), and one to actually load what we think of as user-visible config[1]. And it seems to work. If I do this: diff --git a/config.c b/config.c index 45144f73c5..343af2cf9a 100644 --- a/config.c +++ b/config.c @@ -303,7 +303,7 @@ static int include_by_branch(struct config_include_data *data, const char *refname, *shortname; if (!data->repo || data->repo->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) - return 0; + BUG("chicken and egg"); refname = refs_resolve_ref_unsafe(get_main_ref_store(data->repo), "HEAD", 0, NULL, &flags); and then: git config includeIf.onbranch:main.path alt-config git config -f .git/alt-config foo.bar baz git config foo.bar then we correctly read the value without triggering this code path. Looking back at the last commit that touched include_by_branch(), the problem does not appear to be about a chicken-and-egg at all, though. It is about reading config with includes when there is _no_ repository at all. I.e., this: git config -f main-config includeIf.onbranch:main.path alt-config git config -f alt-config foo.bar baz GIT_DIR=/does/not/exist git.compile config --include -f main-config foo.bar will trigger that BUG() marker, and quietly returning "no match" (like the current code does) is the right thing. Looking below... > The consequence is that we have recursion: > > 1. We call `get_main_ref_store()`. > > 2. We don't yet have a reference store, so we call `ref_store_init()`. > > 3. We parse the configuration required for the reference store. > > 4. We eventually end up in `include_by_branch()`. > > 5. We have already configured the reference storage format, so we end > up calling `get_main_ref_store()` again. Ah, the culprit seems to be ref_store_init() calling into the regular config parser via repo_settings_get_log_all_ref_updates(). But that feels weird to me. Either: 1. It is application config that should not be something we need to load in order to initialize the backend. We could lazy-load it later, or rely on higher level code to set the option. 2. It is crucial to the ref backend functioning, in which case we ought to be reading it alongside core.repositoryFormatVersion, etc. -Peff ^ permalink raw reply related [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-18 16:40 ` Jeff King @ 2026-06-19 6:25 ` Patrick Steinhardt 2026-06-21 21:12 ` Jeff King 0 siblings, 1 reply; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 6:25 UTC (permalink / raw) To: Jeff King; +Cc: git, Karthik Nayak On Thu, Jun 18, 2026 at 12:40:35PM -0400, Jeff King wrote: > On Mon, Jun 15, 2026 at 03:56:53PM +0200, Patrick Steinhardt wrote: [snip] > I'd expect the ref database config (like the ref format) to be read not > through the regular config subsystem, but via read_repository_format() > and friends. And while that does build on the regular config code, it > should never enable includes at all. So includeIf.onbranch:foo.path is > just another uninteresting config key to it. This feels rather painful though, as we'd now have to do this for every single backend that we know about. Also, I think not enabling includes is an overly broad fix: there isn't any reason why "includeif.gitdir" and all the other conditions shouldn't apply. We really only want to disable "onbranch". [snip] > > The consequence is that we have recursion: > > > > 1. We call `get_main_ref_store()`. > > > > 2. We don't yet have a reference store, so we call `ref_store_init()`. > > > > 3. We parse the configuration required for the reference store. > > > > 4. We eventually end up in `include_by_branch()`. > > > > 5. We have already configured the reference storage format, so we end > > up calling `get_main_ref_store()` again. > > Ah, the culprit seems to be ref_store_init() calling into the regular > config parser via repo_settings_get_log_all_ref_updates(). But that > feels weird to me. Either: > > 1. It is application config that should not be something we need to > load in order to initialize the backend. We could lazy-load it > later, or rely on higher level code to set the option. I actually tried lazy-loading, but I found it to be quite painful overall, as the above setting isn't the only one we use. The reftable backend for example has a bunch of additional settings that it reads. We could of course start lazy-loading all of these. But that may not work for future backends that really _need_ to parse some configuration at initiation time. > 2. It is crucial to the ref backend functioning, in which case we > ought to be reading it alongside core.repositoryFormatVersion, etc. I think ideally, we'd have a way to read the repository configuration that explicitly disables parsing includes. We could for example extend `struct config_options` to have a new "ignore_refdb" toggle then explicitly use that in the reference backends. I'll give that a try. Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-19 6:25 ` Patrick Steinhardt @ 2026-06-21 21:12 ` Jeff King 2026-06-22 5:15 ` Patrick Steinhardt 0 siblings, 1 reply; 80+ messages in thread From: Jeff King @ 2026-06-21 21:12 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Karthik Nayak On Fri, Jun 19, 2026 at 08:25:42AM +0200, Patrick Steinhardt wrote: > On Thu, Jun 18, 2026 at 12:40:35PM -0400, Jeff King wrote: > > On Mon, Jun 15, 2026 at 03:56:53PM +0200, Patrick Steinhardt wrote: > [snip] > > I'd expect the ref database config (like the ref format) to be read not > > through the regular config subsystem, but via read_repository_format() > > and friends. And while that does build on the regular config code, it > > should never enable includes at all. So includeIf.onbranch:foo.path is > > just another uninteresting config key to it. > > This feels rather painful though, as we'd now have to do this for every > single backend that we know about. Also, I think not enabling includes > is an overly broad fix: there isn't any reason why "includeif.gitdir" > and all the other conditions shouldn't apply. We really only want to > disable "onbranch". Sorry, I should probably gone back and edited my email after finishing it. I was thinking that you meant not general config, but the specific extensions.refStorage key. Which is not really config, but repo metadata we happen to store in the .git/config file. And obviously you cannot read any refs until you know what's in that key. And that _is_ read separately while loading the repo config, which I think is right. Other options, like core.logallrefupdates, are handled separately. And I realized halfway through my reply that was probably what you meant. I agree those are user-facing config options that should generally respect includes in the normal way. I thinks are a bit funny there, though. See below. > I actually tried lazy-loading, but I found it to be quite painful > overall, as the above setting isn't the only one we use. The reftable > backend for example has a bunch of additional settings that it reads. > > We could of course start lazy-loading all of these. But that may not > work for future backends that really _need_ to parse some configuration > at initiation time. Yes, obviously there's some true chicken-and-egg issues if there are config keys that are needed to initialize the backend. But I think there are many that are not needed immediately (e.g., because they relate only to writes, not reads) but still block loading. For example, try this: git init git config core.logallrefupdates false git config includeIf.onbranch:main.path alt-config git config -f .git/alt-config core.logallrefupdates true git commit --allow-empty -qm foo echo "git-config => $(git config core.logallrefupdates)" echo "reflog => $(git reflog show)" git-config will report the value as true, but git-commit will not respect it. But this used to work! Back when onbranch was added, we'd create the reflog. Bisecting turns up eafb126456 (environment: stop storing "core.logAllRefUpdates" globally, 2024-09-12), which makes sense. That commit pushed the config read down into the ref initialization function, which created the chicken-and-egg. Now the config shown above is a bit silly, and I don't expect anybody to do it in real life. But what worries me is two-fold: 1. There are some magic variables that just won't work with onbranch includes, but the user doesn't necessarily know what they are. 2. We try to cache the results of config reads. Is it possible for an "early" request like this to cache a state that skipped the onbranch include, and then we use that state to look up other unrelated variables? Or could we see a partially completed state in the cache when we lookup a ref variable? I'm not sure. The actual backend lookups use the uncached repo_config() interface (and in your series here, explicitly disables the use of refs during that read). But the core.logallrefupdates lookup uses the cached version, and I think there are others (some of which happen deep under the hood through library calls, like calc_shared_perm()). I tried to construct a few cases that might tickle this behavior, but couldn't come up with one. But I have a nagging feeling that we are mostly getting lucky on some of the ordering, and a seemingly unrelated change could have bad effects. Sorry, I know that's kind of vague and hand-wavy. I'm not sure I have a specific recommendation for a direction. It just feels like we're piling up hacks to avoid infinite recursion without a clear model of what config is read when. I guess if I could suggest anything, it would be that ref backends initialize themselves to do reads while loading as little config as possible, and then perhaps load additional config through the non-caching repo_config() path. -Peff ^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-21 21:12 ` Jeff King @ 2026-06-22 5:15 ` Patrick Steinhardt 0 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 5:15 UTC (permalink / raw) To: Jeff King; +Cc: git, Karthik Nayak On Sun, Jun 21, 2026 at 05:12:11PM -0400, Jeff King wrote: > On Fri, Jun 19, 2026 at 08:25:42AM +0200, Patrick Steinhardt wrote: > > On Thu, Jun 18, 2026 at 12:40:35PM -0400, Jeff King wrote: > > > On Mon, Jun 15, 2026 at 03:56:53PM +0200, Patrick Steinhardt wrote: > > I actually tried lazy-loading, but I found it to be quite painful > > overall, as the above setting isn't the only one we use. The reftable > > backend for example has a bunch of additional settings that it reads. > > > > We could of course start lazy-loading all of these. But that may not > > work for future backends that really _need_ to parse some configuration > > at initiation time. > > Yes, obviously there's some true chicken-and-egg issues if there are > config keys that are needed to initialize the backend. But I think there > are many that are not needed immediately (e.g., because they relate only > to writes, not reads) but still block loading. > > For example, try this: > > git init > git config core.logallrefupdates false > git config includeIf.onbranch:main.path alt-config > git config -f .git/alt-config core.logallrefupdates true > git commit --allow-empty -qm foo > > echo "git-config => $(git config core.logallrefupdates)" > echo "reflog => $(git reflog show)" > > git-config will report the value as true, but git-commit will not > respect it. But this used to work! Back when onbranch was added, we'd > create the reflog. Bisecting turns up eafb126456 (environment: stop > storing "core.logAllRefUpdates" globally, 2024-09-12), which makes > sense. That commit pushed the config read down into the ref > initialization function, which created the chicken-and-egg. > > Now the config shown above is a bit silly, and I don't expect anybody to > do it in real life. But what worries me is two-fold: > > 1. There are some magic variables that just won't work with onbranch > includes, but the user doesn't necessarily know what they are. > > 2. We try to cache the results of config reads. Is it possible for an > "early" request like this to cache a state that skipped the > onbranch include, and then we use that state to look up other > unrelated variables? Or could we see a partially completed state in > the cache when we lookup a ref variable? > > I'm not sure. The actual backend lookups use the uncached > repo_config() interface (and in your series here, explicitly > disables the use of refs during that read). But the > core.logallrefupdates lookup uses the cached version, and I think > there are others (some of which happen deep under the hood > through library calls, like calc_shared_perm()). > > I tried to construct a few cases that might tickle this behavior, but > couldn't come up with one. But I have a nagging feeling that we are > mostly getting lucky on some of the ordering, and a seemingly unrelated > change could have bad effects. > > Sorry, I know that's kind of vague and hand-wavy. > > I'm not sure I have a specific recommendation for a direction. It just > feels like we're piling up hacks to avoid infinite recursion without a > clear model of what config is read when. I guess if I could suggest > anything, it would be that ref backends initialize themselves to do > reads while loading as little config as possible, and then perhaps load > additional config through the non-caching repo_config() path. Yeah, I thought more about this issue over the weekend and kind of got to the same conclusion. Sure, the current version where we explicitly handle the exclusion of "onbranch" conditions is at least less awkward. But I have to agree that it's still not the right fix, as it doesn't really solve the root issue. Taking a step back: all the values that we currently parse are only relevant when writing new refs. So in theory it should be possible to lazy-load all of them on the first write. This should be rather easy to do for the "files" backend. But for the "reftable" backend this will result in a large refactoring because we require the configuration when constructing the reftable stack. That's kind of misdesigned though: the reftable stack shouldn't really care about write options when being constructed. What it needs to know about is the expected hash ID, and any optional stuff like the onreload callback. The write options should then be passed by the caller when we actually perform a write. I'll iterate a bit on this idea and will see where I get. I really shouldn't have opened this can of worms. Thanks! Patrick ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v2 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt ` (6 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt @ 2026-06-15 13:56 ` Patrick Steinhardt 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-15 13:56 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King We're using a local buffer in `refs_compute_filesystem_location()` that is only used so that we can fill it and then call `strbuf_realpath()` on its result. This roundtrip isn't necessary though: `strbuf_realpath()` already knows to use a single buffer as both input and output at the same time. So all this does is to add a bit of confusion and an extra memory allocation. Drop the local buffer. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/refs.c b/refs.c index e69b9b8ac8..4912510590 100644 --- a/refs.c +++ b/refs.c @@ -3571,8 +3571,6 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, bool *is_worktree, struct strbuf *refdir, struct strbuf *ref_common_dir) { - struct strbuf sb = STRBUF_INIT; - *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir); if (!payload) { @@ -3586,8 +3584,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, } if (!is_absolute_path(payload)) { - strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload); - strbuf_realpath(ref_common_dir, sb.buf, 1); + strbuf_addf(ref_common_dir, "/%s", payload); + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); } else { strbuf_realpath(ref_common_dir, payload, 1); } @@ -3600,6 +3598,4 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, BUG("worktree path does not contain slash"); strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); } - - strbuf_release(&sb); } -- 2.55.0.rc0.738.g0c8ab3ebcc.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (10 preceding siblings ...) 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt ` (7 more replies) 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt 13 siblings, 8 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler Hi, this patch series is a follow-up of the discussion at [1]. It converts the reference backends to always use absolute paths internally, which then allows us to drop the calls to `chdir_notify_reparent()`. Unfortunately, the series has grown quite a bit larger than anticipated. This is due to a couple of weirdnesses in how the reference database is constructed with an "onbranch" condition. We essentially construct the refdb twice and loose one, but we never noticed because the chdir notification subsystem kept the pointer to it reachable. Note that the first couple patches that touch "setup.c" aren't strictly required. They are a remnant of a previous iteration where I tried to solve the issue in a different way. But I ultimately figured that these changes are worth it by themselves as they simplify "setup.c" a bit. This series is built on top of 1ff279f340 (The 13th batch, 2026-06-09) with ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct object database in `apply_repository_format()`, 2026-06-04) merged into it. Changes in v3: - Reduce the scope of applying the GIT_REFERENCE_BACKEND environment variable even further so that we really only do this when we end up applying the reference format. - Fix a commit message that still referred to the dropped last commit. - Link to v2: https://patch.msgid.link/20260615-b4-pks-refs-avoid-chdir-notify-reparent-v2-0-f4854aa99859@pks.im Changes in v2: - Drop the last patch. This seemingly destroys the whole purpose of the patch series, but after Peff's hint that this is actually a performance optimization I'm less inclined to drop the chdir_notify infra. I still think that the remainder of the patches make sense standalone, as they simplify "setup.c" and clean memory leaks. Going forward I'd like to investigate the idea of introducing a `struct fsroot` infrastructure that uses the platform-equivalent of openat et al. - Improve a couple of commit messages. - Link to v1: https://patch.msgid.link/20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im Thanks! Patrick [1]: <aifAVpxanV31KUpC@pks.im> --- Patrick Steinhardt (8): setup: inline `check_and_apply_repository_format()` setup: stop applying repository format twice setup: don't apply "GIT_REFERENCE_BACKEND" without a repository refs: unregister reference stores from "chdir_notify" chdir-notify: drop unused `chdir_notify_reparent()` repository: free main reference database refs: fix recursing `get_main_ref_store()` with "onbranch" config refs: drop local buffer in `refs_compute_filesystem_location()` chdir-notify.c | 26 -------------- chdir-notify.h | 6 +--- refs.c | 28 ++++++++++----- refs/files-backend.c | 22 ++++++++++-- refs/packed-backend.c | 16 ++++++++- refs/reftable-backend.c | 16 ++++++++- repository.c | 5 +++ setup.c | 95 +++++++++++++++++++------------------------------ 8 files changed, 112 insertions(+), 102 deletions(-) Range-diff versus v2: 1: ea89bedaa2 = 1: 2be38c1e02 setup: inline `check_and_apply_repository_format()` 2: b87f1db13b = 2: 7fdcd81bb7 setup: stop applying repository format twice 3: f72a8dc251 ! 3: 2162480668 setup: don't apply "GIT_REFERENCE_BACKEND" without a repository @@ setup.c: const char *setup_git_directory_gently(struct repository *repo, int *no /* @@ setup.c: const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) - startup_info->have_repository || - /* GIT_DIR_EXPLICIT */ - getenv(GIT_DIR_ENVIRONMENT)) { -+ const char *ref_backend_uri; -+ - if (!repo->gitdir) { - const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); - if (!gitdir) -@@ setup.c: const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) - setup_git_env_internal(repo, gitdir); - } -+ /* -+ * The env variable should override the repository config -+ * for 'extensions.refStorage'. -+ */ -+ ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); -+ if (ref_backend_uri) { -+ char *format; + if (startup_info->have_repository) { + struct strbuf err = STRBUF_INIT; ++ const char *ref_backend_uri; + -+ free(repo_fmt.ref_storage_payload); ++ /* ++ * The env variable should override the repository config ++ * for 'extensions.refStorage'. ++ */ ++ ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); ++ if (ref_backend_uri) { ++ char *format; + -+ parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); -+ repo_fmt.ref_storage_format = ref_storage_format_by_name(format); -+ if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) -+ die(_("unknown ref storage format: '%s'"), format); ++ free(repo_fmt.ref_storage_payload); + -+ free(format); -+ } ++ parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); ++ repo_fmt.ref_storage_format = ref_storage_format_by_name(format); ++ if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) ++ die(_("unknown ref storage format: '%s'"), format); + - if (startup_info->have_repository) { - struct strbuf err = STRBUF_INIT; ++ free(format); ++ } + if (apply_repository_format(repo, &repo_fmt, + APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) @@ setup.c: const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } 4: 17bdcdb4c5 ! 4: 14daa680b1 refs: unregister reference stores from "chdir_notify" @@ Commit message Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_reparent()`, as there is no infrastructure to unregister the - latter. It ultimately doesn't matter much though: in a subsequent commit - we'll drop this infrastructure completely. We merely require this step - here so that we can fix the memory leaks ahead of time. + latter. Signed-off-by: Patrick Steinhardt <ps@pks.im> 5: c2f13a487e = 5: 89fe37ebe1 chdir-notify: drop unused `chdir_notify_reparent()` 6: 730e4caeda = 6: 4a96b70db4 repository: free main reference database 7: 1dda77cd19 = 7: e48fc2d69d refs: fix recursing `get_main_ref_store()` with "onbranch" config 8: 6d969bb023 = 8: 37935d50c8 refs: drop local buffer in `refs_compute_filesystem_location()` --- base-commit: 255322df35357168daefec8523a3cdc849edd6c1 change-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v3 1/8] setup: inline `check_and_apply_repository_format()` 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 2/8] setup: stop applying repository format twice Patrick Steinhardt ` (6 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We have two callsites of `check_and_apply_repository_format()`. In a subsequent commit we'll want to adapt one of those callsites to change the order in which we read and apply the repository format, at which point the helper function will not really be a good fit for us anymore. Inline the function to both of the callsites. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/setup.c b/setup.c index b4652651df..a9db1f2c23 100644 --- a/setup.c +++ b/setup.c @@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo, 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 - * set_git_dir() before calling this, and use it only for "are we in a valid - * 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, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -2820,6 +2802,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 strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* @@ -2904,6 +2888,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 2/8] setup: stop applying repository format twice 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt ` (5 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering the repository in "setup.c" we apply the final repository format multiple times: - Once via `repository_format_configure()`, where we apply the hash algorithm and ref storage format to both `struct repository_format` and `struct repository`. - And once via `apply_repository_format()`, where we apply these two settings from `struct repository_format` to `struct repository`. With the current flow both of these are in fact necessary. But this is only because we call `repository_format_configure()` after we have called `apply_repository_format()`. Consequently, if we only changed the repository format in `repository_format_configure()` it would never propagate to the repository. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the hash and reference storage format multiple times. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index a9db1f2c23..2748155964 100644 --- a/setup.c +++ b/setup.c @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) die("%s", err.buf); startup_info->have_repository = 1; - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 2/8] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt ` (4 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering a repository we eventually also apply the "GIT_REFERENCE_BACKEND" environment variable to the repository. There's two problems with that: - We do this unconditionally, which is rather pointless: we really only have to configure the repository when we have found one. - We have already applied the repository format at that point in time, so we need to manually reapply it. Move the logic around so that we only apply the environment variable when a repository was discovered. This also allows us to drop the explcit call to `repo_set_ref_storage_format()` because we now adjust the format before we apply it via `apply_repository_format()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/setup.c b/setup.c index 2748155964..79125db565 100644 --- a/setup.c +++ b/setup.c @@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2032,6 +2031,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; + const char *ref_backend_uri; + + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) @@ -2057,25 +2075,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 4/8] refs: unregister reference stores from "chdir_notify" 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (2 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt ` (3 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When creating reference stores we register them with the "chdir_notify" subsystem. This is required because some of the paths we track may be relative paths, so we have to reparent them in case the current working directory changes. But while we register the reference stores, we never unregister them. This can have multiple outcomes: - For a repository's main reference database we essentially keep the pointer alive. We never free that database, either, and our leak checker doesn't notice because it's still registered. - For submodule and worktree reference databases we do eventually free them in `repo_clear()`, so we may keep pointers to free'd memory registered. We never notice though as we don't tend to chdir around in the middle of the process. We never noticed either of these symptoms, but they are obviously bad. Partially fix those issues by unregistering the reference stores when releasing them. The leak of the main reference database will be fixed in a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_reparent()`, as there is no infrastructure to unregister the latter. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 22 +++++++++++++++++++--- refs/packed-backend.c | 16 +++++++++++++++- refs/reftable-backend.c | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..296981584b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c45..499cb55dfa 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de..8c93070677 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, return 0; } +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 5/8] chdir-notify: drop unused `chdir_notify_reparent()` 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (3 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 6/8] repository: free main reference database Patrick Steinhardt ` (2 subsequent siblings) 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler With the preceding commit we've removed all callers of `chdir_notify_reparent()`, so the function is unused now. Drop it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- chdir-notify.c | 26 -------------------------- chdir-notify.h | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef..1237a45e2e 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846..36b4114472 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 6/8] repository: free main reference database 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (4 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler While we release worktree and submodule reference databases when clearing a repository, we don't ever release the main reference database. This memory leak went unnoticed because its pointer is kept alive by the "chdir_notify" subsystem. Fix the memory leak. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- repository.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repository.c b/repository.c index 187dd471c4..e2b5c6712b 100644 --- a/repository.c +++ b/repository.c @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (5 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 6/8] repository: free main reference database Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When we have an "onbranch" condition we need to ask the reference database whether HEAD currently points at the configured branch. This unfortunately creates a chicken-and-egg problem: - The reference database needs to read the configuration so that it can configure itself. - The configuration needs to construct a reference database to fully parse all of its conditionals. The way we handle this is by simply excluding "onbranch" conditionals when we haven't yet configured the reference database. The mechanism for this is broken though: to verify whether or not we have configured the reference database we check whether its format is set to `REF_STORAGE_UNKNOWN` in `include_by_branch()`. But typically, the format _is_ already known at that time because we set it up during repository discovery in "setup.c". The consequence is that we have recursion: 1. We call `get_main_ref_store()`. 2. We don't yet have a reference store, so we call `ref_store_init()`. 3. We parse the configuration required for the reference store. 4. We eventually end up in `include_by_branch()`. 5. We have already configured the reference storage format, so we end up calling `get_main_ref_store()` again. We still haven't finished (1) though, so `get_main_ref_store()` will now call `ref_store_init()` a second time. The end result is that we have constructed the same reference store twice. Of course, as both reference stores would be assigned to `refs_private`, we leak one of those two instances. This never surfaced as an actual leak though because the pointer is kept alive by the "chdir_notify" subsystem. For now, we can fix the issue by explicitly unsetting the reference storage format before constructing it. This makes the mentioned check trigger as expected, and consequently we won't end up constructing a second reference database at all. Ultimately, this means that we consistently stop evaluating "onbranch" conditions when constructing the main reference database. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index d3caa9a633..e69b9b8ac8 100644 --- a/refs.c +++ b/refs.c @@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + enum ref_storage_format format; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); - r->refs_private = ref_store_init(r, r->ref_storage_format, - r->gitdir, REF_STORE_ALL_CAPS); + /* + * When constructing the reference backend we'll end up reading the Git + * configuration. This means we'll also try to evaluate "onbranch" + * conditions. + * + * We cannot read branches when constructing the refdb, so it is not + * possible to evaluate those conditions in the first place. To gate + * their evaluation we check whether or not the reference storage + * format has been configured -- we thus have to temporarily set it to + * UNKNOWN here so that we don't end up recursing. + */ + format = r->ref_storage_format; + r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; + r->refs_private = ref_store_init(r, format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + r->ref_storage_format = format; + return r->refs_private; } -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v3 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (6 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt @ 2026-06-18 6:54 ` Patrick Steinhardt 7 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We're using a local buffer in `refs_compute_filesystem_location()` that is only used so that we can fill it and then call `strbuf_realpath()` on its result. This roundtrip isn't necessary though: `strbuf_realpath()` already knows to use a single buffer as both input and output at the same time. So all this does is to add a bit of confusion and an extra memory allocation. Drop the local buffer. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/refs.c b/refs.c index e69b9b8ac8..4912510590 100644 --- a/refs.c +++ b/refs.c @@ -3571,8 +3571,6 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, bool *is_worktree, struct strbuf *refdir, struct strbuf *ref_common_dir) { - struct strbuf sb = STRBUF_INIT; - *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir); if (!payload) { @@ -3586,8 +3584,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, } if (!is_absolute_path(payload)) { - strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload); - strbuf_realpath(ref_common_dir, sb.buf, 1); + strbuf_addf(ref_common_dir, "/%s", payload); + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); } else { strbuf_realpath(ref_common_dir, payload, 1); } @@ -3600,6 +3598,4 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, BUG("worktree path does not contain slash"); strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); } - - strbuf_release(&sb); } -- 2.55.0.rc0.786.g65d90a0328.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (11 preceding siblings ...) 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 01/10] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt ` (9 more replies) 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt 13 siblings, 10 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler Hi, this patch series is a follow-up of the discussion at [1]. It converts the reference backends to always use absolute paths internally, which then allows us to drop the calls to `chdir_notify_reparent()`. Unfortunately, the series has grown quite a bit larger than anticipated. This is due to a couple of weirdnesses in how the reference database is constructed with an "onbranch" condition. We essentially construct the refdb twice and loose one, but we never noticed because the chdir notification subsystem kept the pointer to it reachable. Note that the first couple patches that touch "setup.c" aren't strictly required. They are a remnant of a previous iteration where I tried to solve the issue in a different way. But I ultimately figured that these changes are worth it by themselves as they simplify "setup.c" a bit. This series is built on top of 1ff279f340 (The 13th batch, 2026-06-09) with ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct object database in `apply_repository_format()`, 2026-06-04) merged into it. Changes in v4: - Fix the "onbranch" recursion at the root of the problem by explicitly disabling the use of the ref store when parsing configuration at ref store initialization time. - Link to v3: https://patch.msgid.link/20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im Changes in v3: - Reduce the scope of applying the GIT_REFERENCE_BACKEND environment variable even further so that we really only do this when we end up applying the reference format. - Fix a commit message that still referred to the dropped last commit. - Link to v2: https://patch.msgid.link/20260615-b4-pks-refs-avoid-chdir-notify-reparent-v2-0-f4854aa99859@pks.im Changes in v2: - Drop the last patch. This seemingly destroys the whole purpose of the patch series, but after Peff's hint that this is actually a performance optimization I'm less inclined to drop the chdir_notify infra. I still think that the remainder of the patches make sense standalone, as they simplify "setup.c" and clean memory leaks. Going forward I'd like to investigate the idea of introducing a `struct fsroot` infrastructure that uses the platform-equivalent of openat et al. - Improve a couple of commit messages. - Link to v1: https://patch.msgid.link/20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im Thanks! Patrick [1]: <aifAVpxanV31KUpC@pks.im> --- Patrick Steinhardt (10): setup: inline `check_and_apply_repository_format()` setup: stop applying repository format twice setup: don't apply "GIT_REFERENCE_BACKEND" without a repository refs: unregister reference stores from "chdir_notify" chdir-notify: drop unused `chdir_notify_reparent()` repository: free main reference database refs: move parsing of "core.logAllRefUpdates" back into ref stores refs/reftable-backend: manually parse "core.sharedRepository" refs: fix recursing `get_main_ref_store()` with "onbranch" config refs: drop local buffer in `refs_compute_filesystem_location()` builtin/checkout.c | 7 ++- chdir-notify.c | 26 ------------ chdir-notify.h | 6 +-- config.c | 4 +- config.h | 1 + path.c | 11 ++--- path.h | 2 +- refs.c | 25 ++++++++--- refs.h | 9 ++++ refs/files-backend.c | 48 ++++++++++++++++++--- refs/packed-backend.c | 16 ++++++- refs/refs-internal.h | 6 --- refs/reftable-backend.c | 50 +++++++++++++++++----- repo-settings.c | 16 ------- repo-settings.h | 9 ---- repository.c | 5 +++ setup.c | 110 +++++++++++++++++++++--------------------------- 17 files changed, 192 insertions(+), 159 deletions(-) Range-diff versus v3: 1: 3ac83ba983 = 1: 3ae112f84b setup: inline `check_and_apply_repository_format()` 2: b6b15770eb = 2: d03fb25a01 setup: stop applying repository format twice 3: 5850f0602d = 3: f437af7ce6 setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 4: e4b12483b4 = 4: 7704b7e5db refs: unregister reference stores from "chdir_notify" 5: 4a78c5080a = 5: 545fe82dda chdir-notify: drop unused `chdir_notify_reparent()` 6: 3f8ae36acc = 6: 5ac9f8c2b3 repository: free main reference database 7: 2a22f9a2e0 < -: ---------- refs: fix recursing `get_main_ref_store()` with "onbranch" config -: ---------- > 7: 0482470af1 refs: move parsing of "core.logAllRefUpdates" back into ref stores -: ---------- > 8: 1b2f9d4ff9 refs/reftable-backend: manually parse "core.sharedRepository" -: ---------- > 9: c7ec7d887f refs: fix recursing `get_main_ref_store()` with "onbranch" config 8: 6bc943659d = 10: 5fb782268b refs: drop local buffer in `refs_compute_filesystem_location()` --- base-commit: 255322df35357168daefec8523a3cdc849edd6c1 change-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v4 01/10] setup: inline `check_and_apply_repository_format()` 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 02/10] setup: stop applying repository format twice Patrick Steinhardt ` (8 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We have two callsites of `check_and_apply_repository_format()`. In a subsequent commit we'll want to adapt one of those callsites to change the order in which we read and apply the repository format, at which point the helper function will not really be a good fit for us anymore. Inline the function to both of the callsites. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/setup.c b/setup.c index b4652651df..a9db1f2c23 100644 --- a/setup.c +++ b/setup.c @@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo, 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 - * set_git_dir() before calling this, and use it only for "are we in a valid - * 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, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -2820,6 +2802,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 strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* @@ -2904,6 +2888,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 02/10] setup: stop applying repository format twice 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 01/10] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 03/10] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt ` (7 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering the repository in "setup.c" we apply the final repository format multiple times: - Once via `repository_format_configure()`, where we apply the hash algorithm and ref storage format to both `struct repository_format` and `struct repository`. - And once via `apply_repository_format()`, where we apply these two settings from `struct repository_format` to `struct repository`. With the current flow both of these are in fact necessary. But this is only because we call `repository_format_configure()` after we have called `apply_repository_format()`. Consequently, if we only changed the repository format in `repository_format_configure()` it would never propagate to the repository. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the hash and reference storage format multiple times. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index a9db1f2c23..2748155964 100644 --- a/setup.c +++ b/setup.c @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) die("%s", err.buf); startup_info->have_repository = 1; - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 03/10] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 01/10] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 02/10] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 04/10] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt ` (6 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering a repository we eventually also apply the "GIT_REFERENCE_BACKEND" environment variable to the repository. There's two problems with that: - We do this unconditionally, which is rather pointless: we really only have to configure the repository when we have found one. - We have already applied the repository format at that point in time, so we need to manually reapply it. Move the logic around so that we only apply the environment variable when a repository was discovered. This also allows us to drop the explcit call to `repo_set_ref_storage_format()` because we now adjust the format before we apply it via `apply_repository_format()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/setup.c b/setup.c index 2748155964..79125db565 100644 --- a/setup.c +++ b/setup.c @@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2032,6 +2031,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; + const char *ref_backend_uri; + + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) @@ -2057,25 +2075,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 04/10] refs: unregister reference stores from "chdir_notify" 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (2 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 03/10] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 05/10] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt ` (5 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When creating reference stores we register them with the "chdir_notify" subsystem. This is required because some of the paths we track may be relative paths, so we have to reparent them in case the current working directory changes. But while we register the reference stores, we never unregister them. This can have multiple outcomes: - For a repository's main reference database we essentially keep the pointer alive. We never free that database, either, and our leak checker doesn't notice because it's still registered. - For submodule and worktree reference databases we do eventually free them in `repo_clear()`, so we may keep pointers to free'd memory registered. We never notice though as we don't tend to chdir around in the middle of the process. We never noticed either of these symptoms, but they are obviously bad. Partially fix those issues by unregistering the reference stores when releasing them. The leak of the main reference database will be fixed in a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_reparent()`, as there is no infrastructure to unregister the latter. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 22 +++++++++++++++++++--- refs/packed-backend.c | 16 +++++++++++++++- refs/reftable-backend.c | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..296981584b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c45..499cb55dfa 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de..8c93070677 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, return 0; } +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 05/10] chdir-notify: drop unused `chdir_notify_reparent()` 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (3 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 04/10] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 06/10] repository: free main reference database Patrick Steinhardt ` (4 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler With the preceding commit we've removed all callers of `chdir_notify_reparent()`, so the function is unused now. Drop it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- chdir-notify.c | 26 -------------------------- chdir-notify.h | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef..1237a45e2e 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846..36b4114472 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 06/10] repository: free main reference database 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (4 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 05/10] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 07/10] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt ` (3 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler While we release worktree and submodule reference databases when clearing a repository, we don't ever release the main reference database. This memory leak went unnoticed because its pointer is kept alive by the "chdir_notify" subsystem. Fix the memory leak. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- repository.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repository.c b/repository.c index 187dd471c4..e2b5c6712b 100644 --- a/repository.c +++ b/repository.c @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 07/10] refs: move parsing of "core.logAllRefUpdates" back into ref stores 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (5 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 06/10] repository: free main reference database Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 08/10] refs/reftable-backend: manually parse "core.sharedRepository" Patrick Steinhardt ` (2 subsequent siblings) 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler In cc42c88945 (refs: extract out reflog config to generic layer, 2026-05-04) we have refactored how we parse "core.logAllRefUpdates" so that it happens in the generic layer. Unfortunately, this has worsened a preexisting issue where we may recurse when creating the reference store because of a chicken-and-egg problem between parsing the configuration and evaluating "onbranch" conditions. Prepare for a fix by essentially reverting that change so that we handle this setting in the respective backends again. The backends are already parsing other configuration anyway, so by moving the logic back in there we can ensure that all backend configuration is parsed the same way. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- builtin/checkout.c | 7 +++++-- refs.c | 10 +++++++++- refs.h | 9 +++++++++ refs/files-backend.c | 20 +++++++++++++++++--- refs/refs-internal.h | 6 ------ refs/reftable-backend.c | 20 +++++++++++--------- repo-settings.c | 16 ---------------- repo-settings.h | 9 --------- setup.c | 7 ++++++- 9 files changed, 57 insertions(+), 47 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index b78b3a1d16..aee84ca897 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -952,10 +952,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - enum log_refs_config log_all_ref_updates = - repo_settings_get_log_all_ref_updates(the_repository); + enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; + const char *value; char *refname; + if (!repo_config_get_string_tmp(the_repository, "core.logallrefupdates", &value)) + log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); if (opts->new_branch_log && !should_autocreate_reflog(log_all_ref_updates, refname)) { diff --git a/refs.c b/refs.c index d3caa9a633..5b773b1c15 100644 --- a/refs.c +++ b/refs.c @@ -1053,6 +1053,15 @@ static char *normalize_reflog_message(const char *msg) return strbuf_detach(&sb, NULL); } +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value) +{ + if (value && !strcasecmp(value, "always")) + return LOG_REFS_ALWAYS; + else if (git_config_bool("core.logallrefupdates", value)) + return LOG_REFS_NORMAL; + return LOG_REFS_NONE; +} + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname) { @@ -2327,7 +2336,6 @@ static struct ref_store *ref_store_init(struct repository *repo, struct ref_store *refs; struct ref_store_init_options opts = { .access_flags = flags, - .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo), }; be = find_ref_storage_backend(format); diff --git a/refs.h b/refs.h index 71d5c186d0..a381022c77 100644 --- a/refs.h +++ b/refs.h @@ -146,6 +146,15 @@ enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, int refs_ref_exists(struct ref_store *refs, const char *refname); +enum log_refs_config { + LOG_REFS_UNSET = -1, + LOG_REFS_NONE = 0, + LOG_REFS_NORMAL, + LOG_REFS_ALWAYS +}; + +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value); + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname); diff --git a/refs/files-backend.c b/refs/files-backend.c index 296981584b..79fb6735e1 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -117,6 +117,21 @@ static void files_ref_store_reparent(const char *name UNUSED, refs->gitcommondir = tmp; } +static int files_ref_store_config(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *payload) +{ + struct files_ref_store *refs = payload; + + if (!strcmp(var, "core.prefersymlinkrefs")) { + refs->prefer_symlink_refs = git_config_bool(var, value); + } else if (!strcmp(var, "core.logallrefupdates")) { + refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + } + + return 0; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -141,10 +156,9 @@ static struct ref_store *files_ref_store_init(struct repository *repo, refs->packed_ref_store = packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); refs->store_flags = opts->access_flags; - refs->log_all_ref_updates = opts->log_all_ref_updates; - - repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); + refs->log_all_ref_updates = LOG_REFS_UNSET; + repo_config(repo, files_ref_store_config, refs); chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index a08d58900e..c3ac7b556f 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -406,12 +406,6 @@ struct ref_store; struct ref_store_init_options { /* The kind of operations that the ref_store is allowed to perform. */ unsigned int access_flags; - - /* - * Denotes under what conditions reflogs should be created when updating - * references. - */ - enum log_refs_config log_all_ref_updates; }; /* diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 8c93070677..5115a3f4ce 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -332,34 +332,36 @@ static void fill_reftable_log_record(struct reftable_log_record *log, const stru static int reftable_be_config(const char *var, const char *value, const struct config_context *ctx, - void *_opts) + void *payload) { - struct reftable_write_options *opts = _opts; + struct reftable_ref_store *refs = payload; if (!strcmp(var, "reftable.blocksize")) { unsigned long block_size = git_config_ulong(var, value, ctx->kvi); if (block_size > 16777215) die("reftable block size cannot exceed 16MB"); - opts->block_size = block_size; + refs->write_options.block_size = block_size; } else if (!strcmp(var, "reftable.restartinterval")) { unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi); if (restart_interval > UINT16_MAX) die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX); - opts->restart_interval = restart_interval; + refs->write_options.restart_interval = restart_interval; } else if (!strcmp(var, "reftable.indexobjects")) { - opts->skip_index_objects = !git_config_bool(var, value); + refs->write_options.skip_index_objects = !git_config_bool(var, value); } else if (!strcmp(var, "reftable.geometricfactor")) { unsigned long factor = git_config_ulong(var, value, ctx->kvi); if (factor > UINT8_MAX) die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX); - opts->auto_compaction_factor = factor; + refs->write_options.auto_compaction_factor = factor; } else if (!strcmp(var, "reftable.locktimeout")) { int64_t lock_timeout = git_config_int64(var, value, ctx->kvi); if (lock_timeout > LONG_MAX) die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX); if (lock_timeout < 0 && lock_timeout != -1) die("reftable lock timeout does not support negative values other than -1"); - opts->lock_timeout_ms = lock_timeout; + refs->write_options.lock_timeout_ms = lock_timeout; + } else if (!strcmp(var, "core.logallrefupdates")) { + refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); } return 0; @@ -398,7 +400,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); - refs->log_all_ref_updates = opts->log_all_ref_updates; refs->store_flags = opts->access_flags; switch (repo->hash_algo->format_id) { @@ -415,8 +416,9 @@ static struct ref_store *reftable_be_init(struct repository *repo, refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); refs->write_options.lock_timeout_ms = 100; + refs->log_all_ref_updates = LOG_REFS_UNSET; - repo_config(repo, reftable_be_config, &refs->write_options); + repo_config(repo, reftable_be_config, refs); /* * It is somewhat unfortunate that we have to mirror the default block diff --git a/repo-settings.c b/repo-settings.c index 208e09ff17..f3be3b8c5a 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -177,22 +177,6 @@ void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long repo->settings.big_file_threshold = value; } -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo) -{ - const char *value; - - if (!repo_config_get_string_tmp(repo, "core.logallrefupdates", &value)) { - if (value && !strcasecmp(value, "always")) - return LOG_REFS_ALWAYS; - else if (git_config_bool("core.logallrefupdates", value)) - return LOG_REFS_NORMAL; - else - return LOG_REFS_NONE; - } - - return LOG_REFS_UNSET; -} - int repo_settings_get_warn_ambiguous_refs(struct repository *repo) { prepare_repo_settings(repo); diff --git a/repo-settings.h b/repo-settings.h index cad9c3f0cc..e5253ead02 100644 --- a/repo-settings.h +++ b/repo-settings.h @@ -16,13 +16,6 @@ enum fetch_negotiation_setting { FETCH_NEGOTIATION_NOOP, }; -enum log_refs_config { - LOG_REFS_UNSET = -1, - LOG_REFS_NONE = 0, - LOG_REFS_NORMAL, - LOG_REFS_ALWAYS -}; - struct repo_settings { int initialized; @@ -86,8 +79,6 @@ struct repo_settings { void prepare_repo_settings(struct repository *r); void repo_settings_clear(struct repository *r); -/* Read the value for "core.logAllRefUpdates". */ -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo); /* Read the value for "core.warnAmbiguousRefs". */ int repo_settings_get_warn_ambiguous_refs(struct repository *repo); /* Read the value for "core.hooksPath". */ diff --git a/setup.c b/setup.c index 79125db565..0c6efb0560 100644 --- a/setup.c +++ b/setup.c @@ -2584,10 +2584,15 @@ static int create_default_files(struct repository *repo, if (is_bare_repository()) repo_config_set(repo, "core.bare", "true"); else { + const char *value; + repo_config_set(repo, "core.bare", "false"); + /* allow template config file to override the default */ - if (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET) + if (repo_config_get_string_tmp(repo, "core.logallrefupdates", &value) || + refs_parse_log_all_ref_updates_config(value) == LOG_REFS_UNSET) repo_config_set(repo, "core.logallrefupdates", "true"); + if (needs_work_tree_config(original_git_dir, work_tree)) repo_config_set(repo, "core.worktree", work_tree); } -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 08/10] refs/reftable-backend: manually parse "core.sharedRepository" 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (6 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 07/10] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 09/10] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 10/10] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We're using `calc_shared_perm()` when creating a reftable repository. This function internally uses `repo_settings_get_shared_repository()`, which results in the same chicken-and-egg problem as mentioned in the preceding commit. Prepare for a fix by handling parsing of "core.sharedRepository" manually in `reftable_be_config()` so that we have full control over how exactly this configuration is read. Note that this change requires a small reording in "setup.c" when creating the repositroy, as we only write "core.sharedRepository" into the configuration after we've already created the reference database. This is too late though now that we parse the value directly from the configuration, so we have to reverse the order. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- path.c | 11 ++++++----- path.h | 2 +- refs/reftable-backend.c | 8 +++++++- setup.c | 8 ++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/path.c b/path.c index d7e17bf174..c28b057374 100644 --- a/path.c +++ b/path.c @@ -736,11 +736,10 @@ char *interpolate_path(const char *path, int real_home) return NULL; } -int calc_shared_perm(struct repository *repo, - int mode) +int calc_shared_perm(int shared_repo, int mode) { int tweak; - int shared_repo = repo_settings_get_shared_repository(repo); + if (shared_repo < 0) tweak = -shared_repo; else @@ -763,13 +762,15 @@ int adjust_shared_perm(struct repository *repo, const char *path) { int old_mode, new_mode; + int shared_repository; - if (!repo_settings_get_shared_repository(repo)) + shared_repository = repo_settings_get_shared_repository(repo); + if (!shared_repository) return 0; if (get_st_mode_bits(path, &old_mode) < 0) return -1; - new_mode = calc_shared_perm(repo, old_mode); + new_mode = calc_shared_perm(shared_repository, old_mode); if (S_ISDIR(old_mode)) { /* Copy read bits to execute bits */ new_mode |= (new_mode & 0444) >> 2; diff --git a/path.h b/path.h index 0434ba5e07..1188dc4729 100644 --- a/path.h +++ b/path.h @@ -145,7 +145,7 @@ const char *git_path_shallow(struct repository *r); int ends_with_path_components(const char *path, const char *components); -int calc_shared_perm(struct repository *repo, int mode); +int calc_shared_perm(int shared_repository, int mode); int adjust_shared_perm(struct repository *repo, const char *path); char *interpolate_path(const char *path, int real_home); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 5115a3f4ce..ee92bd9c70 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -362,6 +362,11 @@ static int reftable_be_config(const char *var, const char *value, refs->write_options.lock_timeout_ms = lock_timeout; } else if (!strcmp(var, "core.logallrefupdates")) { refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + } else if (!strcmp(var, "core.sharedrepository")) { + mode_t mask = umask(0); + umask(mask); + refs->write_options.default_permissions = calc_shared_perm(git_config_perm(var, value), + 0666 & ~mask); } return 0; @@ -412,7 +417,8 @@ static struct ref_store *reftable_be_init(struct repository *repo, default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); } - refs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask); + + refs->write_options.default_permissions = 0666 & ~mask; refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); refs->write_options.lock_timeout_ms = 100; diff --git a/setup.c b/setup.c index 0c6efb0560..03ff359070 100644 --- a/setup.c +++ b/setup.c @@ -2846,10 +2846,6 @@ int init_db(struct repository *repo, reinit = create_default_files(repo, template_dir, original_git_dir, &repo_fmt, init_shared_repository); - if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); - create_object_directory(repo); - if (repo_settings_get_shared_repository(repo)) { char buf[10]; /* We do not spell "group" and such, so that @@ -2871,6 +2867,10 @@ int init_db(struct repository *repo, repo_config_set(repo, "receive.denyNonFastforwards", "true"); } + if (!(flags & INIT_DB_SKIP_REFDB)) + create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); + create_object_directory(repo); + if (!(flags & INIT_DB_QUIET)) { int len = strlen(git_dir); -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 09/10] refs: fix recursing `get_main_ref_store()` with "onbranch" config 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (7 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 08/10] refs/reftable-backend: manually parse "core.sharedRepository" Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 10/10] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When we have an "onbranch" condition we need to ask the reference database whether HEAD currently points at the configured branch. This unfortunately creates a chicken-and-egg problem: - The reference database needs to read the configuration so that it can configure itself. - The configuration needs to construct a reference database to fully parse all of its conditionals. The way we handle this is by simply excluding "onbranch" conditionals when we haven't yet configured the reference database. The mechanism for this is broken though: to verify whether or not we have configured the reference database we check whether its format is set to `REF_STORAGE_UNKNOWN` in `include_by_branch()`. But typically, the format _is_ already known at that time because we set it up during repository discovery in "setup.c". The consequence is that we recurse: 1. We call `get_main_ref_store()`. 2. We don't yet have a reference store, so we call `ref_store_init()`. 3. We parse the configuration required for the reference store. 4. We eventually end up in `include_by_branch()`. 5. We have already configured the reference storage format, so we end up calling `get_main_ref_store()` again. We still haven't finished (1) though, so `get_main_ref_store()` will now call `ref_store_init()` a second time. The end result is that we have constructed the same reference store twice. Of course, as both reference stores would be assigned to `refs_private`, we leak one of those two instances. This never surfaced as an actual leak though because the pointer is kept alive by the "chdir_notify" subsystem. The mechanism to use the configured reference format is quite fragile in the first place. Introduce a new mechanism that allows us to explicitly skip evaluation of "onbranch" conditions and use it to fix the issue. Add a sanity check in `get_main_ref_store()` to make sure we aren't recursing, which would have failed before the fix. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- config.c | 4 +++- config.h | 1 + refs.c | 7 +++++++ refs/files-backend.c | 8 +++++++- refs/reftable-backend.c | 8 +++++++- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/config.c b/config.c index a1b92fe083..223c252236 100644 --- a/config.c +++ b/config.c @@ -302,7 +302,9 @@ static int include_by_branch(struct config_include_data *data, struct strbuf pattern = STRBUF_INIT; const char *refname, *shortname; - if (!data->repo || data->repo->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + if (!data->repo || + data->opts->ignore_refs || + data->repo->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) return 0; refname = refs_resolve_ref_unsafe(get_main_ref_store(data->repo), diff --git a/config.h b/config.h index bf47fb3afc..42aedde878 100644 --- a/config.h +++ b/config.h @@ -88,6 +88,7 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type, struct config_options { unsigned int respect_includes : 1; unsigned int ignore_repo : 1; + unsigned int ignore_refs : 1; unsigned int ignore_worktree : 1; unsigned int ignore_cmdline : 1; unsigned int system_gently : 1; diff --git a/refs.c b/refs.c index 5b773b1c15..f242e6ca96 100644 --- a/refs.c +++ b/refs.c @@ -2359,15 +2359,22 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + static bool initializing; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); + if (initializing) + BUG("main reference store creation is recursing"); + initializing = true; r->refs_private = ref_store_init(r, r->ref_storage_format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + initializing = false; + return r->refs_private; } diff --git a/refs/files-backend.c b/refs/files-backend.c index 79fb6735e1..ce29875cdd 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -141,6 +141,12 @@ static struct ref_store *files_ref_store_init(struct repository *repo, const char *gitdir, const struct ref_store_init_options *opts) { + struct config_options config_opts = { + .respect_includes = 1, + .ignore_refs = 1, + .commondir = repo->commondir, + .git_dir = repo->gitdir, + }; struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; struct strbuf ref_common_dir = STRBUF_INIT; @@ -158,7 +164,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, refs->store_flags = opts->access_flags; refs->log_all_ref_updates = LOG_REFS_UNSET; - repo_config(repo, files_ref_store_config, refs); + config_with_options(files_ref_store_config, refs, NULL, repo, &config_opts); chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index ee92bd9c70..05d4edc6fd 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -390,6 +390,12 @@ static struct ref_store *reftable_be_init(struct repository *repo, const char *gitdir, const struct ref_store_init_options *opts) { + struct config_options config_opts = { + .respect_includes = 1, + .ignore_refs = 1, + .commondir = repo->commondir, + .git_dir = repo->gitdir, + }; struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); struct strbuf ref_common_dir = STRBUF_INIT; struct strbuf refdir = STRBUF_INIT; @@ -424,7 +430,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, refs->write_options.lock_timeout_ms = 100; refs->log_all_ref_updates = LOG_REFS_UNSET; - repo_config(repo, reftable_be_config, refs); + config_with_options(reftable_be_config, refs, NULL, repo, &config_opts); /* * It is somewhat unfortunate that we have to mirror the default block -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v4 10/10] refs: drop local buffer in `refs_compute_filesystem_location()` 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (8 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 09/10] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt @ 2026-06-19 11:27 ` Patrick Steinhardt 9 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-19 11:27 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We're using a local buffer in `refs_compute_filesystem_location()` that is only used so that we can fill it and then call `strbuf_realpath()` on its result. This roundtrip isn't necessary though: `strbuf_realpath()` already knows to use a single buffer as both input and output at the same time. So all this does is to add a bit of confusion and an extra memory allocation. Drop the local buffer. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/refs.c b/refs.c index f242e6ca96..582dbeff0a 100644 --- a/refs.c +++ b/refs.c @@ -3570,8 +3570,6 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, bool *is_worktree, struct strbuf *refdir, struct strbuf *ref_common_dir) { - struct strbuf sb = STRBUF_INIT; - *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir); if (!payload) { @@ -3585,8 +3583,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, } if (!is_absolute_path(payload)) { - strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload); - strbuf_realpath(ref_common_dir, sb.buf, 1); + strbuf_addf(ref_common_dir, "/%s", payload); + strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1); } else { strbuf_realpath(ref_common_dir, payload, 1); } @@ -3599,6 +3597,4 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload, BUG("worktree path does not contain slash"); strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); } - - strbuf_release(&sb); } -- 2.55.0.rc1.722.g2b3ac350e6.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 00/11] refs: fix "onbranch" conditions 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt ` (12 preceding siblings ...) 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 01/11] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt ` (10 more replies) 13 siblings, 11 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler Hi, originally, this patch series was a follow-up of the discussion at [1], where it converted the reference backends to always use absolute paths internally so that we could drop the `chdir_notify_reparent()` machinery. But this focus shifted as we discovered that this led to quite a sizeable performance regression. Instead, the series now focusses on fixing handling of the "onbranch" conditions. As part of the above work I discovered that we recurse when creating the main reference database in case we have "onbranch" conditions, and that recursion caused us to construct an ad-hoc reference store that we essentially discarded. The leak wasn't ever catched though because the store is kept alive by the `chdir_notify` infrastructure. This is a deeper-running issue though: the reference backends respect some configuration guarded by "onbranch" conditions, but not all of them. This issue is fixed by this series by lazy-loading all configuration so that we don't need to read any configuration when we initialize the reference store. This fixes the recursion and makes us consistently honor those "onbranch" conditions. This series is built on top of 1ff279f340 (The 13th batch, 2026-06-09) with ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct object database in `apply_repository_format()`, 2026-06-04) merged into it. Changes in v5: - Fix the "onbranch" recursion properly: instead of papering over the issue, this series now refactors reference store initialization to not read any configuration at all anymore. Instead, the config is now parsed lazily. This fixes the recursion, but also makes us respect configuration guarded by "onbranch" conditions properly. - Link to v4: https://patch.msgid.link/20260619-b4-pks-refs-avoid-chdir-notify-reparent-v4-0-a6472be7acc4@pks.im Changes in v4: - Fix the "onbranch" recursion at the root of the problem by explicitly disabling the use of the ref store when parsing configuration at ref store initialization time. - Link to v3: https://patch.msgid.link/20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im Changes in v3: - Reduce the scope of applying the GIT_REFERENCE_BACKEND environment variable even further so that we really only do this when we end up applying the reference format. - Fix a commit message that still referred to the dropped last commit. - Link to v2: https://patch.msgid.link/20260615-b4-pks-refs-avoid-chdir-notify-reparent-v2-0-f4854aa99859@pks.im Changes in v2: - Drop the last patch. This seemingly destroys the whole purpose of the patch series, but after Peff's hint that this is actually a performance optimization I'm less inclined to drop the chdir_notify infra. I still think that the remainder of the patches make sense standalone, as they simplify "setup.c" and clean memory leaks. Going forward I'd like to investigate the idea of introducing a `struct fsroot` infrastructure that uses the platform-equivalent of openat et al. - Improve a couple of commit messages. - Link to v1: https://patch.msgid.link/20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im Thanks! Patrick [1]: <aifAVpxanV31KUpC@pks.im> --- Patrick Steinhardt (11): setup: inline `check_and_apply_repository_format()` setup: stop applying repository format twice setup: don't apply "GIT_REFERENCE_BACKEND" without a repository refs: unregister reference stores from "chdir_notify" chdir-notify: drop unused `chdir_notify_reparent()` repository: free main reference database refs: move parsing of "core.logAllRefUpdates" back into ref stores refs/files: lazy-load configuration to fix chicken-and-egg reftable: split up write options refs/reftable: lazy-load configuration to fix chicken-and-egg refs: protect against chicken-and-egg recursion builtin/checkout.c | 7 +- chdir-notify.c | 26 ----- chdir-notify.h | 6 +- refs.c | 17 +++- refs.h | 9 ++ refs/files-backend.c | 69 ++++++++++--- refs/packed-backend.c | 16 ++- refs/refs-internal.h | 6 -- refs/reftable-backend.c | 177 ++++++++++++++++++++------------- reftable/reftable-stack.h | 30 +++++- reftable/reftable-writer.h | 17 +--- reftable/stack.c | 100 ++++++++++++------- reftable/stack.h | 2 +- reftable/writer.c | 21 ++-- reftable/writer.h | 1 + repo-settings.c | 16 --- repo-settings.h | 9 -- repository.c | 5 + setup.c | 102 ++++++++----------- t/helper/test-reftable.c | 2 +- t/t0600-reffiles-backend.sh | 21 ++++ t/t0613-reftable-write-options.sh | 19 ++++ t/t1400-update-ref.sh | 12 +++ t/unit-tests/lib-reftable.c | 8 +- t/unit-tests/lib-reftable.h | 2 + t/unit-tests/u-reftable-merged.c | 9 +- t/unit-tests/u-reftable-readwrite.c | 38 ++++++-- t/unit-tests/u-reftable-stack.c | 189 ++++++++++++++++-------------------- t/unit-tests/u-reftable-table.c | 8 +- 29 files changed, 555 insertions(+), 389 deletions(-) Range-diff versus v4: 1: a70b0f44b2 = 1: 1a3e7849fb setup: inline `check_and_apply_repository_format()` 2: b33b51748b = 2: 9fee5b6ac2 setup: stop applying repository format twice 3: a22755337a = 3: 8eeaaa2359 setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 4: 848645c3e8 = 4: 19f0e381aa refs: unregister reference stores from "chdir_notify" 5: 489e274577 = 5: 5b1ec8f62a chdir-notify: drop unused `chdir_notify_reparent()` 6: a9811da5c8 = 6: 44abab07fa repository: free main reference database 7: 8de1023c6b = 7: 21d46ff924 refs: move parsing of "core.logAllRefUpdates" back into ref stores 8: cb3cf159d2 < -: ---------- refs/reftable-backend: manually parse "core.sharedRepository" 9: 1a7c195c03 < -: ---------- refs: fix recursing `get_main_ref_store()` with "onbranch" config 10: c9b019a1a5 < -: ---------- refs: drop local buffer in `refs_compute_filesystem_location()` -: ---------- > 8: 22d65ada3d refs/files: lazy-load configuration to fix chicken-and-egg -: ---------- > 9: 715b090f40 reftable: split up write options -: ---------- > 10: a941049373 refs/reftable: lazy-load configuration to fix chicken-and-egg -: ---------- > 11: 7ca965fe73 refs: protect against chicken-and-egg recursion --- base-commit: 255322df35357168daefec8523a3cdc849edd6c1 change-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab ^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v5 01/11] setup: inline `check_and_apply_repository_format()` 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 02/11] setup: stop applying repository format twice Patrick Steinhardt ` (9 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler We have two callsites of `check_and_apply_repository_format()`. In a subsequent commit we'll want to adapt one of those callsites to change the order in which we read and apply the repository format, at which point the helper function will not really be a good fit for us anymore. Inline the function to both of the callsites. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/setup.c b/setup.c index b4652651df..a9db1f2c23 100644 --- a/setup.c +++ b/setup.c @@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo, 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 - * set_git_dir() before calling this, and use it only for "are we in a valid - * 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, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -2820,6 +2802,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 strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* @@ -2904,6 +2888,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 02/11] setup: stop applying repository format twice 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 01/11] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 03/11] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt ` (8 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering the repository in "setup.c" we apply the final repository format multiple times: - Once via `repository_format_configure()`, where we apply the hash algorithm and ref storage format to both `struct repository_format` and `struct repository`. - And once via `apply_repository_format()`, where we apply these two settings from `struct repository_format` to `struct repository`. With the current flow both of these are in fact necessary. But this is only because we call `repository_format_configure()` after we have called `apply_repository_format()`. Consequently, if we only changed the repository format in `repository_format_configure()` it would never propagate to the repository. Refactor the code so that we first configure the repository format before applying it to the repository so that we can stop setting the hash and reference storage format multiple times. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index a9db1f2c23..2748155964 100644 --- a/setup.c +++ b/setup.c @@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2830,10 +2825,10 @@ int init_db(struct repository *repo, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) die("%s", err.buf); startup_info->have_repository = 1; - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 03/11] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 01/11] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 02/11] setup: stop applying repository format twice Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 04/11] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt ` (7 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When discovering a repository we eventually also apply the "GIT_REFERENCE_BACKEND" environment variable to the repository. There's two problems with that: - We do this unconditionally, which is rather pointless: we really only have to configure the repository when we have found one. - We have already applied the repository format at that point in time, so we need to manually reapply it. Move the logic around so that we only apply the environment variable when a repository was discovered. This also allows us to drop the explcit call to `repo_set_ref_storage_format()` because we now adjust the format before we apply it via `apply_repository_format()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- setup.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/setup.c b/setup.c index 2748155964..79125db565 100644 --- a/setup.c +++ b/setup.c @@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2032,6 +2031,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; + const char *ref_backend_uri; + + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) @@ -2057,25 +2075,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 04/11] refs: unregister reference stores from "chdir_notify" 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (2 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 03/11] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 05/11] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt ` (6 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When creating reference stores we register them with the "chdir_notify" subsystem. This is required because some of the paths we track may be relative paths, so we have to reparent them in case the current working directory changes. But while we register the reference stores, we never unregister them. This can have multiple outcomes: - For a repository's main reference database we essentially keep the pointer alive. We never free that database, either, and our leak checker doesn't notice because it's still registered. - For submodule and worktree reference databases we do eventually free them in `repo_clear()`, so we may keep pointers to free'd memory registered. We never notice though as we don't tend to chdir around in the middle of the process. We never noticed either of these symptoms, but they are obviously bad. Partially fix those issues by unregistering the reference stores when releasing them. The leak of the main reference database will be fixed in a subsequent commit. Note that this requires us to use `chdir_notify_register()` instead of `chdir_notify_reparent()`, as there is no infrastructure to unregister the latter. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 22 +++++++++++++++++++--- refs/packed-backend.c | 16 +++++++++++++++- refs/reftable-backend.c | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..296981584b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c45..499cb55dfa 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de..8c93070677 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value, return 0; } +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 05/11] chdir-notify: drop unused `chdir_notify_reparent()` 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (3 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 04/11] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 06/11] repository: free main reference database Patrick Steinhardt ` (5 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler With the preceding commit we've removed all callers of `chdir_notify_reparent()`, so the function is unused now. Drop it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- chdir-notify.c | 26 -------------------------- chdir-notify.h | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef..1237a45e2e 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846..36b4114472 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 06/11] repository: free main reference database 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (4 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 05/11] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 07/11] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt ` (4 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler While we release worktree and submodule reference databases when clearing a repository, we don't ever release the main reference database. This memory leak went unnoticed because its pointer is kept alive by the "chdir_notify" subsystem. Fix the memory leak. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- repository.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repository.c b/repository.c index 187dd471c4..e2b5c6712b 100644 --- a/repository.c +++ b/repository.c @@ -421,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 07/11] refs: move parsing of "core.logAllRefUpdates" back into ref stores 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (5 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 06/11] repository: free main reference database Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 08/11] refs/files: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt ` (3 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler In cc42c88945 (refs: extract out reflog config to generic layer, 2026-05-04) we have refactored how we parse "core.logAllRefUpdates" so that it happens in the generic layer. Unfortunately, this has worsened a preexisting issue where we may recurse when creating the reference store because of a chicken-and-egg problem between parsing the configuration and evaluating "onbranch" conditions. Prepare for a fix by essentially reverting that change so that we handle this setting in the respective backends again. The backends are already parsing other configuration anyway, so by moving the logic back in there we can ensure that all backend configuration is parsed the same way. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- builtin/checkout.c | 7 +++++-- refs.c | 10 +++++++++- refs.h | 9 +++++++++ refs/files-backend.c | 20 +++++++++++++++++--- refs/refs-internal.h | 6 ------ refs/reftable-backend.c | 20 +++++++++++--------- repo-settings.c | 16 ---------------- repo-settings.h | 9 --------- setup.c | 7 ++++++- 9 files changed, 57 insertions(+), 47 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index b78b3a1d16..aee84ca897 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -952,10 +952,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - enum log_refs_config log_all_ref_updates = - repo_settings_get_log_all_ref_updates(the_repository); + enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; + const char *value; char *refname; + if (!repo_config_get_string_tmp(the_repository, "core.logallrefupdates", &value)) + log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); if (opts->new_branch_log && !should_autocreate_reflog(log_all_ref_updates, refname)) { diff --git a/refs.c b/refs.c index d3caa9a633..5b773b1c15 100644 --- a/refs.c +++ b/refs.c @@ -1053,6 +1053,15 @@ static char *normalize_reflog_message(const char *msg) return strbuf_detach(&sb, NULL); } +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value) +{ + if (value && !strcasecmp(value, "always")) + return LOG_REFS_ALWAYS; + else if (git_config_bool("core.logallrefupdates", value)) + return LOG_REFS_NORMAL; + return LOG_REFS_NONE; +} + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname) { @@ -2327,7 +2336,6 @@ static struct ref_store *ref_store_init(struct repository *repo, struct ref_store *refs; struct ref_store_init_options opts = { .access_flags = flags, - .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo), }; be = find_ref_storage_backend(format); diff --git a/refs.h b/refs.h index 71d5c186d0..a381022c77 100644 --- a/refs.h +++ b/refs.h @@ -146,6 +146,15 @@ enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, int refs_ref_exists(struct ref_store *refs, const char *refname); +enum log_refs_config { + LOG_REFS_UNSET = -1, + LOG_REFS_NONE = 0, + LOG_REFS_NORMAL, + LOG_REFS_ALWAYS +}; + +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value); + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname); diff --git a/refs/files-backend.c b/refs/files-backend.c index 296981584b..79fb6735e1 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -117,6 +117,21 @@ static void files_ref_store_reparent(const char *name UNUSED, refs->gitcommondir = tmp; } +static int files_ref_store_config(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *payload) +{ + struct files_ref_store *refs = payload; + + if (!strcmp(var, "core.prefersymlinkrefs")) { + refs->prefer_symlink_refs = git_config_bool(var, value); + } else if (!strcmp(var, "core.logallrefupdates")) { + refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + } + + return 0; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -141,10 +156,9 @@ static struct ref_store *files_ref_store_init(struct repository *repo, refs->packed_ref_store = packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); refs->store_flags = opts->access_flags; - refs->log_all_ref_updates = opts->log_all_ref_updates; - - repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); + refs->log_all_ref_updates = LOG_REFS_UNSET; + repo_config(repo, files_ref_store_config, refs); chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index a08d58900e..c3ac7b556f 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -406,12 +406,6 @@ struct ref_store; struct ref_store_init_options { /* The kind of operations that the ref_store is allowed to perform. */ unsigned int access_flags; - - /* - * Denotes under what conditions reflogs should be created when updating - * references. - */ - enum log_refs_config log_all_ref_updates; }; /* diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 8c93070677..5115a3f4ce 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -332,34 +332,36 @@ static void fill_reftable_log_record(struct reftable_log_record *log, const stru static int reftable_be_config(const char *var, const char *value, const struct config_context *ctx, - void *_opts) + void *payload) { - struct reftable_write_options *opts = _opts; + struct reftable_ref_store *refs = payload; if (!strcmp(var, "reftable.blocksize")) { unsigned long block_size = git_config_ulong(var, value, ctx->kvi); if (block_size > 16777215) die("reftable block size cannot exceed 16MB"); - opts->block_size = block_size; + refs->write_options.block_size = block_size; } else if (!strcmp(var, "reftable.restartinterval")) { unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi); if (restart_interval > UINT16_MAX) die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX); - opts->restart_interval = restart_interval; + refs->write_options.restart_interval = restart_interval; } else if (!strcmp(var, "reftable.indexobjects")) { - opts->skip_index_objects = !git_config_bool(var, value); + refs->write_options.skip_index_objects = !git_config_bool(var, value); } else if (!strcmp(var, "reftable.geometricfactor")) { unsigned long factor = git_config_ulong(var, value, ctx->kvi); if (factor > UINT8_MAX) die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX); - opts->auto_compaction_factor = factor; + refs->write_options.auto_compaction_factor = factor; } else if (!strcmp(var, "reftable.locktimeout")) { int64_t lock_timeout = git_config_int64(var, value, ctx->kvi); if (lock_timeout > LONG_MAX) die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX); if (lock_timeout < 0 && lock_timeout != -1) die("reftable lock timeout does not support negative values other than -1"); - opts->lock_timeout_ms = lock_timeout; + refs->write_options.lock_timeout_ms = lock_timeout; + } else if (!strcmp(var, "core.logallrefupdates")) { + refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); } return 0; @@ -398,7 +400,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); - refs->log_all_ref_updates = opts->log_all_ref_updates; refs->store_flags = opts->access_flags; switch (repo->hash_algo->format_id) { @@ -415,8 +416,9 @@ static struct ref_store *reftable_be_init(struct repository *repo, refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); refs->write_options.lock_timeout_ms = 100; + refs->log_all_ref_updates = LOG_REFS_UNSET; - repo_config(repo, reftable_be_config, &refs->write_options); + repo_config(repo, reftable_be_config, refs); /* * It is somewhat unfortunate that we have to mirror the default block diff --git a/repo-settings.c b/repo-settings.c index 208e09ff17..f3be3b8c5a 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -177,22 +177,6 @@ void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long repo->settings.big_file_threshold = value; } -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo) -{ - const char *value; - - if (!repo_config_get_string_tmp(repo, "core.logallrefupdates", &value)) { - if (value && !strcasecmp(value, "always")) - return LOG_REFS_ALWAYS; - else if (git_config_bool("core.logallrefupdates", value)) - return LOG_REFS_NORMAL; - else - return LOG_REFS_NONE; - } - - return LOG_REFS_UNSET; -} - int repo_settings_get_warn_ambiguous_refs(struct repository *repo) { prepare_repo_settings(repo); diff --git a/repo-settings.h b/repo-settings.h index cad9c3f0cc..e5253ead02 100644 --- a/repo-settings.h +++ b/repo-settings.h @@ -16,13 +16,6 @@ enum fetch_negotiation_setting { FETCH_NEGOTIATION_NOOP, }; -enum log_refs_config { - LOG_REFS_UNSET = -1, - LOG_REFS_NONE = 0, - LOG_REFS_NORMAL, - LOG_REFS_ALWAYS -}; - struct repo_settings { int initialized; @@ -86,8 +79,6 @@ struct repo_settings { void prepare_repo_settings(struct repository *r); void repo_settings_clear(struct repository *r); -/* Read the value for "core.logAllRefUpdates". */ -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo); /* Read the value for "core.warnAmbiguousRefs". */ int repo_settings_get_warn_ambiguous_refs(struct repository *repo); /* Read the value for "core.hooksPath". */ diff --git a/setup.c b/setup.c index 79125db565..0c6efb0560 100644 --- a/setup.c +++ b/setup.c @@ -2584,10 +2584,15 @@ static int create_default_files(struct repository *repo, if (is_bare_repository()) repo_config_set(repo, "core.bare", "true"); else { + const char *value; + repo_config_set(repo, "core.bare", "false"); + /* allow template config file to override the default */ - if (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET) + if (repo_config_get_string_tmp(repo, "core.logallrefupdates", &value) || + refs_parse_log_all_ref_updates_config(value) == LOG_REFS_UNSET) repo_config_set(repo, "core.logallrefupdates", "true"); + if (needs_work_tree_config(original_git_dir, work_tree)) repo_config_set(repo, "core.worktree", work_tree); } -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 08/11] refs/files: lazy-load configuration to fix chicken-and-egg 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (6 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 07/11] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 09/11] reftable: split up write options Patrick Steinhardt ` (2 subsequent siblings) 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When initializing the "files" reference backend we read the repository's config to parse "core.preferSymlinkRefs" and "core.logAllRefUpdates". This results in a chicken-and-egg problem though, because parsing the configuration may require us to have access to the reference store already when an "onbranch" condition exists. Luckily, all the configuration that we honor only relates to writing references. Consequently, we don't strictly need that configuration to be readily available at initialization time, and we can easiliy defer parsing it to a later point in time. Implement this fix and add tests that verify that we can indeed properly parse these config knobs via an "onbranch" condition. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 37 ++++++++++++++++++++++++++----------- t/t0600-reffiles-backend.sh | 21 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 79fb6735e1..d0f379dcd6 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -84,12 +84,14 @@ struct files_ref_store { unsigned int store_flags; char *gitcommondir; - enum log_refs_config log_all_ref_updates; - int prefer_symlink_refs; - struct ref_cache *loose; - struct ref_store *packed_ref_store; + + struct files_ref_store_write_options { + enum log_refs_config log_all_ref_updates; + int prefer_symlink_refs; + bool initialized; + } write_opts_lazy_loaded; }; static void clear_loose_ref_cache(struct files_ref_store *refs) @@ -121,17 +123,31 @@ static int files_ref_store_config(const char *var, const char *value, const struct config_context *ctx UNUSED, void *payload) { - struct files_ref_store *refs = payload; + struct files_ref_store_write_options *opts = payload; if (!strcmp(var, "core.prefersymlinkrefs")) { - refs->prefer_symlink_refs = git_config_bool(var, value); + opts->prefer_symlink_refs = git_config_bool(var, value); } else if (!strcmp(var, "core.logallrefupdates")) { - refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); } return 0; } +static const struct files_ref_store_write_options *files_ref_store_write_options(struct files_ref_store *refs) +{ + struct files_ref_store_write_options *opts = &refs->write_opts_lazy_loaded; + + if (opts->initialized) + return opts; + + opts->log_all_ref_updates = LOG_REFS_UNSET; + repo_config(refs->base.repo, files_ref_store_config, opts); + + opts->initialized = true; + return opts; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -156,9 +172,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo, refs->packed_ref_store = packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); refs->store_flags = opts->access_flags; - refs->log_all_ref_updates = LOG_REFS_UNSET; - repo_config(repo, files_ref_store_config, refs); chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -1890,7 +1904,7 @@ static int log_ref_setup(struct files_ref_store *refs, const char *refname, int force_create, int *logfd, struct strbuf *err) { - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates; + enum log_refs_config log_refs_cfg = files_ref_store_write_options(refs)->log_all_ref_updates; struct strbuf logfile_sb = STRBUF_INIT; char *logfile; @@ -3301,6 +3315,7 @@ static int files_transaction_finish(struct ref_store *ref_store, { struct files_ref_store *refs = files_downcast(ref_store, 0, "ref_transaction_finish"); + const struct files_ref_store_write_options *write_opts = files_ref_store_write_options(refs); size_t i; int ret = 0; struct strbuf sb = STRBUF_INIT; @@ -3340,7 +3355,7 @@ static int files_transaction_finish(struct ref_store *ref_store, * We try creating a symlink, if that succeeds we continue to the * next update. If not, we try and create a regular symref. */ - if (update->new_target && refs->prefer_symlink_refs) + if (update->new_target && write_opts->prefer_symlink_refs) /* * By using the `NOT_CONSTANT()` trick, we can avoid * errors by `clang`'s `-Wunreachable` logic that would diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index 74bfa2e9ba..bbbf6fa422 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -519,4 +519,25 @@ test_expect_success 'symref transaction supports false symlink config' ' test_cmp expect actual ' +test_expect_success SYMLINKS,!MINGW,!WITH_BREAKING_CHANGES 'core.preferSymlinkRefs can be set up via onbranch condition' ' + test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" && + test_when_finished "rm -f .git/include" && + git update-ref refs/heads/new @ && + cat >.git/include <<-\EOF && + [core] + preferSymlinkRefs = true + EOF + test_config includeIf.onbranch:"$(git branch --show-current)".path \ + "$(pwd)/.git/include" && + cat >stdin <<-EOF && + start + symref-create TEST_SYMREF_HEAD refs/heads/new + prepare + commit + EOF + git update-ref --no-deref --stdin <stdin && + test_path_is_symlink .git/TEST_SYMREF_HEAD && + test "$(test_readlink .git/TEST_SYMREF_HEAD)" = refs/heads/new +' + test_done -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 09/11] reftable: split up write options 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (7 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 08/11] refs/files: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 10/11] refs/reftable: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 11/11] refs: protect against chicken-and-egg recursion Patrick Steinhardt 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler When initializing the reftable stack the caller may optionally pass some write options. These write options mix up two different concerns though: - Of course, they allow the caller to configure how new reftables are being written. - But they also allow the caller to configure the stack itself, like its hash ID and the `on_reload` callback. This is somewhat awkward, as it doesn't easily give the caller the flexibility to for example write multiple reftables with different options. Furthermore, this requires us to eagerly parse relevant configuration when initializing the reftable backend. Refactor the code by splitting out those options that configure the stack itself. Creating a new stack will thus only require this limited set of options, whereas the caller is expected to pass write options to all functions that end up writing tables. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/reftable-backend.c | 29 +++--- reftable/reftable-stack.h | 30 +++++- reftable/reftable-writer.h | 17 +--- reftable/stack.c | 100 ++++++++++++------- reftable/stack.h | 2 +- reftable/writer.c | 21 ++-- reftable/writer.h | 1 + t/helper/test-reftable.c | 2 +- t/unit-tests/lib-reftable.c | 8 +- t/unit-tests/lib-reftable.h | 2 + t/unit-tests/u-reftable-merged.c | 9 +- t/unit-tests/u-reftable-readwrite.c | 38 ++++++-- t/unit-tests/u-reftable-stack.c | 189 ++++++++++++++++-------------------- t/unit-tests/u-reftable-table.c | 8 +- 14 files changed, 258 insertions(+), 198 deletions(-) diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 5115a3f4ce..608d71cf10 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -48,9 +48,9 @@ static void reftable_backend_on_reload(void *payload) static int reftable_backend_init(struct reftable_backend *be, const char *path, - const struct reftable_write_options *_opts) + const struct reftable_stack_options *_opts) { - struct reftable_write_options opts = *_opts; + struct reftable_stack_options opts = *_opts; opts.on_reload = reftable_backend_on_reload; opts.on_reload_payload = be; return reftable_new_stack(&be->stack, path, &opts); @@ -140,6 +140,7 @@ struct reftable_ref_store { * is populated lazily when we try to resolve `worktrees/$worktree` refs. */ struct strmap worktree_backends; + struct reftable_stack_options stack_options; struct reftable_write_options write_options; unsigned int store_flags; @@ -190,7 +191,7 @@ static int backend_for_worktree(struct reftable_backend **out, CALLOC_ARRAY(*out, 1); store->err = ret = reftable_backend_init(*out, worktree_dir.buf, - &store->write_options); + &store->stack_options); if (ret < 0) { free(*out); goto out; @@ -404,10 +405,10 @@ static struct ref_store *reftable_be_init(struct repository *repo, switch (repo->hash_algo->format_id) { case GIT_SHA1_FORMAT_ID: - refs->write_options.hash_id = REFTABLE_HASH_SHA1; + refs->stack_options.hash_id = REFTABLE_HASH_SHA1; break; case GIT_SHA256_FORMAT_ID: - refs->write_options.hash_id = REFTABLE_HASH_SHA256; + refs->stack_options.hash_id = REFTABLE_HASH_SHA256; break; default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); @@ -441,7 +442,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, } strbuf_addstr(&path, "/reftable"); refs->err = reftable_backend_init(&refs->main_backend, path.buf, - &refs->write_options); + &refs->stack_options); if (refs->err) goto done; @@ -457,7 +458,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, strbuf_addstr(&refdir, "/reftable"); refs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf, - &refs->write_options); + &refs->stack_options); if (refs->err) goto done; } @@ -997,6 +998,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out, struct reftable_addition *addition; ret = reftable_stack_new_addition(&addition, be->stack, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret) { if (ret == REFTABLE_LOCK_ERROR) @@ -1685,9 +1687,9 @@ static int reftable_be_optimize(struct ref_store *ref_store, stack = refs->main_backend.stack; if (opts->flags & REFS_OPTIMIZE_AUTO) - ret = reftable_stack_auto_compact(stack); + ret = reftable_stack_auto_compact(stack, &refs->write_options); else - ret = reftable_stack_compact_all(stack, NULL); + ret = reftable_stack_compact_all(stack, &refs->write_options, NULL); if (ret < 0) { ret = error(_("unable to compact stack: %s"), reftable_error_str(ret)); @@ -1721,8 +1723,8 @@ static int reftable_be_optimize_required(struct ref_store *ref_store, if (opts->flags & REFS_OPTIMIZE_AUTO) use_heuristics = true; - return reftable_stack_compaction_required(stack, use_heuristics, - required); + return reftable_stack_compaction_required(stack, &refs->write_options, + use_heuristics, required); } struct write_create_symref_arg { @@ -1979,6 +1981,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2009,6 +2012,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2374,6 +2378,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2446,6 +2451,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); assert(ret != REFTABLE_API_ERROR); @@ -2568,6 +2574,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, goto done; ret = reftable_stack_new_addition(&add, be->stack, + &refs->write_options, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret < 0) goto done; diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index 5f7be573fa..11f9963f4f 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -26,11 +26,29 @@ */ struct reftable_stack; +/* Options related to opening a stack. */ +struct reftable_stack_options { + /* + * 4-byte identifier ("sha1", "s256") of the hash. Defaults to SHA1 if + * unset. + */ + enum reftable_hash hash_id; + + /* + * Callback function to execute whenever the stack is being reloaded. + * This can be used e.g. to discard cached information that relies on + * the old stack's data. The payload data will be passed as argument to + * the callback. + */ + void (*on_reload)(void *payload); + void *on_reload_payload; +}; + /* open a new reftable stack. The tables along with the table list will be * stored in 'dir'. Typically, this should be .git/reftables. */ int reftable_new_stack(struct reftable_stack **dest, const char *dir, - const struct reftable_write_options *opts); + const struct reftable_stack_options *opts); /* returns the update_index at which a next table should be written. */ uint64_t reftable_stack_next_update_index(struct reftable_stack *st); @@ -52,6 +70,7 @@ enum { */ int reftable_stack_new_addition(struct reftable_addition **dest, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags); /* Adds a reftable to transaction. */ @@ -77,7 +96,9 @@ void reftable_addition_destroy(struct reftable_addition *add); int reftable_stack_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *write_arg), - void *write_arg, unsigned flags); + void *write_arg, + const struct reftable_write_options *opts, + unsigned flags); struct reftable_iterator; @@ -122,6 +143,7 @@ struct reftable_log_expiry_config { /* compacts all reftables into a giant table. Expire reflog entries if config is * non-NULL */ int reftable_stack_compact_all(struct reftable_stack *st, + const struct reftable_write_options *opts, struct reftable_log_expiry_config *config); /* @@ -132,11 +154,13 @@ int reftable_stack_compact_all(struct reftable_stack *st, * compacted to maintain geometric progression. */ int reftable_stack_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, bool use_heuristics, bool *required); /* heuristically compact unbalanced table stack. */ -int reftable_stack_auto_compact(struct reftable_stack *st); +int reftable_stack_auto_compact(struct reftable_stack *st, + const struct reftable_write_options *opts); /* delete stale .ref tables. */ int reftable_stack_clean(struct reftable_stack *st); diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index a66db415c8..6ff4ddfc60 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -28,11 +28,6 @@ struct reftable_write_options { /* how often to write complete keys in each block. */ uint16_t restart_interval; - /* 4-byte identifier ("sha1", "s256") of the hash. - * Defaults to SHA1 if unset - */ - enum reftable_hash hash_id; - /* Default mode for creating files. If unset, use 0666 (+umask) */ unsigned int default_permissions; @@ -60,15 +55,6 @@ struct reftable_write_options { * negative value will cause us to block indefinitely. */ long lock_timeout_ms; - - /* - * Callback function to execute whenever the stack is being reloaded. - * This can be used e.g. to discard cached information that relies on - * the old stack's data. The payload data will be passed as argument to - * the callback. - */ - void (*on_reload)(void *payload); - void *on_reload_payload; }; /* reftable_block_stats holds statistics for a single block type */ @@ -114,7 +100,8 @@ struct reftable_writer; int reftable_writer_new(struct reftable_writer **out, ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, const struct reftable_write_options *opts); + void *writer_arg, enum reftable_hash hash_id, + const struct reftable_write_options *opts); /* * Set the range of update indices for the records we will add. When writing a diff --git a/reftable/stack.c b/reftable/stack.c index 1fba96ddb3..ab12926708 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -501,10 +501,10 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, } int reftable_new_stack(struct reftable_stack **dest, const char *dir, - const struct reftable_write_options *_opts) + const struct reftable_stack_options *_opts) { struct reftable_buf list_file_name = REFTABLE_BUF_INIT; - struct reftable_write_options opts = { 0 }; + struct reftable_stack_options opts = { 0 }; struct reftable_stack *p; int err; @@ -629,6 +629,7 @@ int reftable_stack_reload(struct reftable_stack *st) struct reftable_addition { struct reftable_flock tables_list_lock; struct reftable_stack *stack; + struct reftable_write_options opts; char **new_tables; size_t new_tables_len, new_tables_cap; @@ -657,6 +658,7 @@ static void reftable_addition_close(struct reftable_addition *add) static int reftable_stack_init_addition(struct reftable_addition *add, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags) { struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; @@ -664,15 +666,17 @@ static int reftable_stack_init_addition(struct reftable_addition *add, memset(add, 0, sizeof(*add)); add->stack = st; + if (opts) + add->opts = *opts; err = flock_acquire(&add->tables_list_lock, st->list_file, - st->opts.lock_timeout_ms); + add->opts.lock_timeout_ms); if (err < 0) goto done; - if (st->opts.default_permissions) { + if (add->opts.default_permissions) { if (chmod(add->tables_list_lock.path, - st->opts.default_permissions) < 0) { + add->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -702,12 +706,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add, static int stack_try_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *arg), - void *arg, unsigned flags) + void *arg, + const struct reftable_write_options *opts, + unsigned flags) { struct reftable_addition add; int err; - err = reftable_stack_init_addition(&add, st, flags); + err = reftable_stack_init_addition(&add, st, opts, flags); if (err < 0) goto done; @@ -723,9 +729,11 @@ static int stack_try_add(struct reftable_stack *st, int reftable_stack_add(struct reftable_stack *st, int (*write)(struct reftable_writer *wr, void *arg), - void *arg, unsigned flags) + void *arg, + const struct reftable_write_options *opts, + unsigned flags) { - int err = stack_try_add(st, write, arg, flags); + int err = stack_try_add(st, write, arg, opts, flags); if (err < 0) { if (err == REFTABLE_OUTDATED_ERROR) { /* Ignore error return, we want to propagate @@ -810,7 +818,7 @@ int reftable_addition_commit(struct reftable_addition *add) if (err) goto done; - if (!add->stack->opts.disable_auto_compact) { + if (!add->opts.disable_auto_compact) { /* * Auto-compact the stack to keep the number of tables in * control. It is possible that a concurrent writer is already @@ -820,7 +828,7 @@ int reftable_addition_commit(struct reftable_addition *add) * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`. * Both of these errors are benign, so we simply ignore them. */ - err = reftable_stack_auto_compact(add->stack); + err = reftable_stack_auto_compact(add->stack, &add->opts); if (err < 0 && err != REFTABLE_LOCK_ERROR && err != REFTABLE_OUTDATED_ERROR) goto done; @@ -834,6 +842,7 @@ int reftable_addition_commit(struct reftable_addition *add) int reftable_stack_new_addition(struct reftable_addition **dest, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags) { int err; @@ -842,7 +851,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest, if (!*dest) return REFTABLE_OUT_OF_MEMORY_ERROR; - err = reftable_stack_init_addition(*dest, st, flags); + err = reftable_stack_init_addition(*dest, st, opts, flags); if (err) { reftable_free(*dest); *dest = NULL; @@ -862,7 +871,7 @@ int reftable_addition_add(struct reftable_addition *add, struct reftable_writer *wr = NULL; struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; struct fd_writer writer = { - .opts = &add->stack->opts, + .opts = &add->opts, }; int err = 0; @@ -883,9 +892,9 @@ int reftable_addition_add(struct reftable_addition *add, err = tmpfile_from_pattern(&tab_file, temp_tab_file_name.buf); if (err < 0) goto done; - if (add->stack->opts.default_permissions) { + if (add->opts.default_permissions) { if (chmod(tab_file.path, - add->stack->opts.default_permissions)) { + add->opts.default_permissions)) { err = REFTABLE_IO_ERROR; goto done; } @@ -893,7 +902,7 @@ int reftable_addition_add(struct reftable_addition *add, writer.fd = tab_file.fd; err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, - &writer, &add->stack->opts); + &writer, add->stack->opts.hash_id, &add->opts); if (err < 0) goto done; @@ -1066,13 +1075,14 @@ static int stack_write_compact(struct reftable_stack *st, static int stack_compact_locked(struct reftable_stack *st, size_t first, size_t last, struct reftable_log_expiry_config *config, + const struct reftable_write_options *opts, struct reftable_tmpfile *tab_file_out) { struct reftable_buf next_name = REFTABLE_BUF_INIT; struct reftable_buf tab_file_path = REFTABLE_BUF_INIT; struct reftable_writer *wr = NULL; struct fd_writer writer= { - .opts = &st->opts, + .opts = opts, }; struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; int err = 0; @@ -1094,15 +1104,15 @@ static int stack_compact_locked(struct reftable_stack *st, if (err < 0) goto done; - if (st->opts.default_permissions && - chmod(tab_file.path, st->opts.default_permissions) < 0) { + if (opts->default_permissions && + chmod(tab_file.path, opts->default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } writer.fd = tab_file.fd; err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, - &writer, &st->opts); + &writer, st->opts.hash_id, opts); if (err < 0) goto done; @@ -1150,6 +1160,7 @@ enum stack_compact_range_flags { static int stack_compact_range(struct reftable_stack *st, size_t first, size_t last, struct reftable_log_expiry_config *expiry, + const struct reftable_write_options *opts, unsigned int flags) { struct reftable_buf tables_list_buf = REFTABLE_BUF_INIT; @@ -1175,7 +1186,7 @@ static int stack_compact_range(struct reftable_stack *st, * Hold the lock so that we can read "tables.list" and lock all tables * which are part of the user-specified range. */ - err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); + err = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms); if (err < 0) goto done; @@ -1274,7 +1285,7 @@ static int stack_compact_range(struct reftable_stack *st, * these tables may end up with an empty new table in case tombstones * end up cancelling out all refs in that range. */ - err = stack_compact_locked(st, first, last, expiry, &new_table); + err = stack_compact_locked(st, first, last, expiry, opts, &new_table); if (err < 0) { if (err != REFTABLE_EMPTY_TABLE_ERROR) goto done; @@ -1286,13 +1297,13 @@ static int stack_compact_range(struct reftable_stack *st, * "tables.list". We'll then replace the compacted range of tables with * the new table. */ - err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); + err = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms); if (err < 0) goto done; - if (st->opts.default_permissions) { + if (opts->default_permissions) { if (chmod(tables_list_lock.path, - st->opts.default_permissions) < 0) { + opts->default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -1513,10 +1524,16 @@ static int stack_compact_range(struct reftable_stack *st, } int reftable_stack_compact_all(struct reftable_stack *st, + const struct reftable_write_options *opts, struct reftable_log_expiry_config *config) { + struct reftable_write_options opts_default = { 0 }; size_t last = st->merged->tables_len ? st->merged->tables_len - 1 : 0; - return stack_compact_range(st, 0, last, config, 0); + + if (!opts) + opts = &opts_default; + + return stack_compact_range(st, 0, last, config, opts, 0); } static int segment_size(struct segment *s) @@ -1601,6 +1618,7 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, } static int stack_segments_for_compaction(struct reftable_stack *st, + const struct reftable_write_options *opts, struct segment *seg) { int version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; @@ -1615,13 +1633,14 @@ static int stack_segments_for_compaction(struct reftable_stack *st, sizes[i] = st->tables[i]->size - overhead; *seg = suggest_compaction_segment(sizes, st->merged->tables_len, - st->opts.auto_compaction_factor); + opts->auto_compaction_factor); reftable_free(sizes); return 0; } static int update_segment_if_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, struct segment *seg, bool use_geometric, bool *required) @@ -1638,7 +1657,7 @@ static int update_segment_if_compaction_required(struct reftable_stack *st, return 0; } - err = stack_segments_for_compaction(st, seg); + err = stack_segments_for_compaction(st, opts, seg); if (err) return err; @@ -1647,27 +1666,40 @@ static int update_segment_if_compaction_required(struct reftable_stack *st, } int reftable_stack_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, bool use_heuristics, bool *required) { + struct reftable_write_options opts_default = { 0 }; struct segment seg; - return update_segment_if_compaction_required(st, &seg, use_heuristics, - required); + + if (!opts) + opts = &opts_default; + + return update_segment_if_compaction_required(st, opts, &seg, + use_heuristics, required); } -int reftable_stack_auto_compact(struct reftable_stack *st) +int reftable_stack_auto_compact(struct reftable_stack *st, + const struct reftable_write_options *opts) { + struct reftable_write_options opts_default = { 0 }; struct segment seg; bool required; int err; - err = update_segment_if_compaction_required(st, &seg, true, &required); + if (!opts) + opts = &opts_default; + + err = update_segment_if_compaction_required(st, opts, &seg, true, + &required); if (err) return err; if (required) return stack_compact_range(st, seg.start, seg.end - 1, - NULL, STACK_COMPACT_RANGE_BEST_EFFORT); + NULL, opts, + STACK_COMPACT_RANGE_BEST_EFFORT); return 0; } @@ -1807,7 +1839,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st) int reftable_stack_clean(struct reftable_stack *st) { struct reftable_addition *add = NULL; - int err = reftable_stack_new_addition(&add, st, 0); + int err = reftable_stack_new_addition(&add, st, NULL, 0); if (err < 0) { goto done; } diff --git a/reftable/stack.h b/reftable/stack.h index bc28f2998a..f7901e6c6f 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -20,7 +20,7 @@ struct reftable_stack { char *reftable_dir; - struct reftable_write_options opts; + struct reftable_stack_options opts; struct reftable_table **tables; size_t tables_len; diff --git a/reftable/writer.c b/reftable/writer.c index 0133b64975..f850e9d599 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -80,9 +80,6 @@ static void options_set_defaults(struct reftable_write_options *opts) opts->restart_interval = 16; } - if (opts->hash_id == 0) { - opts->hash_id = REFTABLE_HASH_SHA1; - } if (opts->block_size == 0) { opts->block_size = DEFAULT_BLOCK_SIZE; } @@ -90,7 +87,7 @@ static void options_set_defaults(struct reftable_write_options *opts) static int writer_version(struct reftable_writer *w) { - return (w->opts.hash_id == 0 || w->opts.hash_id == REFTABLE_HASH_SHA1) ? + return (w->hash_id == 0 || w->hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; } @@ -107,7 +104,7 @@ static int writer_write_header(struct reftable_writer *w, uint8_t *dest) if (writer_version(w) == 2) { uint32_t hash_id; - switch (w->opts.hash_id) { + switch (w->hash_id) { case REFTABLE_HASH_SHA1: hash_id = REFTABLE_FORMAT_ID_SHA1; break; @@ -134,7 +131,7 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) reftable_buf_reset(&w->last_key); ret = block_writer_init(&w->block_writer_data, typ, w->block, w->opts.block_size, block_start, - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (ret < 0) return ret; @@ -147,7 +144,9 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) int reftable_writer_new(struct reftable_writer **out, ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, const struct reftable_write_options *_opts) + void *writer_arg, + enum reftable_hash hash_id, + const struct reftable_write_options *_opts) { struct reftable_write_options opts = {0}; struct reftable_writer *wp; @@ -162,6 +161,9 @@ int reftable_writer_new(struct reftable_writer **out, if (opts.block_size >= (1 << 24)) return REFTABLE_API_ERROR; + if (!hash_id) + hash_id = REFTABLE_HASH_SHA1; + reftable_buf_init(&wp->block_writer_data.last_key); reftable_buf_init(&wp->last_key); reftable_buf_init(&wp->scratch); @@ -173,6 +175,7 @@ int reftable_writer_new(struct reftable_writer **out, wp->write = writer_func; wp->write_arg = writer_arg; wp->opts = opts; + wp->hash_id = hash_id; wp->flush = flush_func; writer_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF); @@ -367,7 +370,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, if (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) { reftable_buf_reset(&w->scratch); err = reftable_buf_add(&w->scratch, (char *)reftable_ref_record_val1(ref), - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (err < 0) goto out; @@ -379,7 +382,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, if (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) { reftable_buf_reset(&w->scratch); err = reftable_buf_add(&w->scratch, reftable_ref_record_val2(ref), - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (err < 0) goto out; diff --git a/reftable/writer.h b/reftable/writer.h index 9f53610b27..c08fc413e1 100644 --- a/reftable/writer.h +++ b/reftable/writer.h @@ -27,6 +27,7 @@ struct reftable_writer { uint64_t next; uint64_t min_update_index, max_update_index; struct reftable_write_options opts; + enum reftable_hash hash_id; /* memory buffer for writing */ uint8_t *block; diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index b16c0722c8..fc49fafc34 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -174,7 +174,7 @@ static int dump_table(struct reftable_merged_table *mt) static int dump_stack(const char *stackdir, uint32_t hash_id) { struct reftable_stack *stack = NULL; - struct reftable_write_options opts = { .hash_id = hash_id }; + struct reftable_stack_options opts = { .hash_id = hash_id }; struct reftable_merged_table *merged = NULL; int err = reftable_new_stack(&stack, stackdir, &opts); diff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c index fdb5b11a20..19a3ac8b80 100644 --- a/t/unit-tests/lib-reftable.c +++ b/t/unit-tests/lib-reftable.c @@ -25,11 +25,12 @@ static int strbuf_writer_flush(void *arg UNUSED) } struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf, + enum reftable_hash hash_id, struct reftable_write_options *opts) { struct reftable_writer *writer; int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush, - buf, opts); + buf, hash_id, opts); cl_assert(!ret); return writer; } @@ -39,6 +40,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t nrefs, struct reftable_log_record *logs, size_t nlogs, + enum reftable_hash hash_id, struct reftable_write_options *_opts) { struct reftable_write_options opts = { 0 }; @@ -66,7 +68,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, min = ui; } - writer = cl_reftable_strbuf_writer(buf, &opts); + writer = cl_reftable_strbuf_writer(buf, hash_id, &opts); ret = reftable_writer_set_limits(writer, min, max); cl_assert(!ret); @@ -88,7 +90,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t off = i * (opts.block_size ? opts.block_size : DEFAULT_BLOCK_SIZE); if (!off) - off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1); + off = header_size(hash_id == REFTABLE_HASH_SHA256 ? 2 : 1); cl_assert(buf->buf[off] == 'r'); } diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h index d7e6d3136f..caf443d147 100644 --- a/t/unit-tests/lib-reftable.h +++ b/t/unit-tests/lib-reftable.h @@ -10,6 +10,7 @@ struct reftable_buf; void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id); struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf, + enum reftable_hash hash_id, struct reftable_write_options *opts); void cl_reftable_write_to_buf(struct reftable_buf *buf, @@ -17,4 +18,5 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t nrecords, struct reftable_log_record *logs, size_t nlogs, + enum reftable_hash hash_id, struct reftable_write_options *opts); diff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c index 54cb7fc2a7..21232c1e4f 100644 --- a/t/unit-tests/u-reftable-merged.c +++ b/t/unit-tests/u-reftable-merged.c @@ -34,7 +34,8 @@ merged_table_from_records(struct reftable_ref_record **refs, cl_assert(*source != NULL); for (size_t i = 0; i < n; i++) { - cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts); + cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, + REFTABLE_HASH_SHA1, &opts); block_source_from_buf(&(*source)[i], &buf[i]); err = reftable_table_new(&(*tables)[i], &(*source)[i], @@ -357,7 +358,8 @@ merged_table_from_log_records(struct reftable_log_record **logs, cl_assert(*source != NULL); for (size_t i = 0; i < n; i++) { - cl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts); + cl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], + REFTABLE_HASH_SHA1, &opts); block_source_from_buf(&(*source)[i], &buf[i]); err = reftable_table_new(&(*tables)[i], &(*source)[i], @@ -487,7 +489,8 @@ void test_reftable_merged__default_write_opts(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_ref_record rec = { .refname = (char *) "master", .update_index = 1, diff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c index 4d8c4be5f1..5794b460c6 100644 --- a/t/unit-tests/u-reftable-readwrite.c +++ b/t/unit-tests/u-reftable-readwrite.c @@ -48,7 +48,6 @@ static void write_table(char ***names, struct reftable_buf *buf, int N, { struct reftable_write_options opts = { .block_size = block_size, - .hash_id = hash_id, }; struct reftable_ref_record *refs; struct reftable_log_record *logs; @@ -78,7 +77,7 @@ static void write_table(char ***names, struct reftable_buf *buf, int N, logs[i].value.update.message = (char *) "message"; } - cl_reftable_write_to_buf(buf, refs, N, logs, N, &opts); + cl_reftable_write_to_buf(buf, refs, N, logs, N, hash_id, &opts); reftable_free(refs); reftable_free(logs); @@ -103,6 +102,7 @@ void test_reftable_readwrite__log_buffer_size(void) .message = (char *) "commit: 9\n", } } }; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); /* This tests buffer extension for log compression. Must use a random @@ -143,6 +143,7 @@ void test_reftable_readwrite__log_overflow(void) }, }; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); memset(msg, 'x', sizeof(msg) - 1); @@ -157,6 +158,7 @@ void test_reftable_readwrite__log_write_limits(void) struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_log_record log = { .refname = (char *)"refs/head/master", @@ -202,7 +204,9 @@ void test_reftable_readwrite__log_write_read(void) struct reftable_table *table; struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); const struct reftable_stats *stats = NULL; int N = 2, i; char **names; @@ -299,6 +303,7 @@ void test_reftable_readwrite__log_zlib_corruption(void) struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); const struct reftable_stats *stats = NULL; char message[100] = { 0 }; @@ -531,6 +536,7 @@ static void t_table_refs_for(int indexed) struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_iterator it = { 0 }; int N = 50, j, i; @@ -622,7 +628,9 @@ void test_reftable_readwrite__write_empty_table(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_block_source source = { 0 }; struct reftable_table *table = NULL; struct reftable_ref_record rec = { 0 }; @@ -660,7 +668,9 @@ void test_reftable_readwrite__write_object_id_min_length(void) .block_size = 75, }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -691,7 +701,9 @@ void test_reftable_readwrite__write_object_id_length(void) .block_size = 75, }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -721,7 +733,9 @@ void test_reftable_readwrite__write_empty_key(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .refname = (char *) "", .update_index = 1, @@ -740,7 +754,9 @@ void test_reftable_readwrite__write_key_order(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record refs[2] = { { .refname = (char *) "b", @@ -787,7 +803,8 @@ void test_reftable_readwrite__write_multiple_indices(void) int i; int err; - writer = cl_reftable_strbuf_writer(&writer_buf, &opts); + writer = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1, + &opts); reftable_writer_set_limits(writer, 1, 1); for (i = 0; i < 100; i++) { struct reftable_ref_record ref = { @@ -861,7 +878,8 @@ void test_reftable_readwrite__write_multi_level_index(void) struct reftable_table *table; int err; - writer = cl_reftable_strbuf_writer(&writer_buf, &opts); + writer = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1, + &opts); reftable_writer_set_limits(writer, 1, 1); for (size_t i = 0; i < 200; i++) { struct reftable_ref_record ref = { diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c index b8110cdeee..e6c1635940 100644 --- a/t/unit-tests/u-reftable-stack.c +++ b/t/unit-tests/u-reftable-stack.c @@ -111,10 +111,9 @@ static int write_test_ref(struct reftable_writer *wr, void *arg) static void write_n_ref_tables(struct reftable_stack *st, size_t n) { - int disable_auto_compact; - - disable_auto_compact = st->opts.disable_auto_compact; - st->opts.disable_auto_compact = 1; + struct reftable_write_options opts = { + .disable_auto_compact = 1, + }; for (size_t i = 0; i < n; i++) { struct reftable_ref_record ref = { @@ -128,10 +127,8 @@ static void write_n_ref_tables(struct reftable_stack *st, cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1); cl_assert_equal_i(reftable_stack_add(st, - &write_test_ref, &ref, 0), 0); + &write_test_ref, &ref, &opts, 0), 0); } - - st->opts.disable_auto_compact = disable_auto_compact; } struct write_log_arg { @@ -168,10 +165,10 @@ void test_reftable_stack__add_one(void) struct stat stat_result = { 0 }; int err; - err = reftable_new_stack(&st, dir, &opts); + err = reftable_new_stack(&st, dir, NULL); cl_assert(!err); - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); cl_assert(!err); err = reftable_stack_read_ref(st, ref.refname, &dest); @@ -210,7 +207,6 @@ void test_reftable_stack__add_one(void) void test_reftable_stack__uptodate(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL; struct reftable_stack *st2 = NULL; char *dir = get_tmp_dir(__LINE__); @@ -232,15 +228,15 @@ void test_reftable_stack__uptodate(void) /* simulate multi-process access to the same stack by creating two stacks for the same directory. */ - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st1, write_test_ref, - &ref1, 0), 0); + &ref1, NULL, 0), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, 0), REFTABLE_OUTDATED_ERROR); + &ref2, NULL, 0), REFTABLE_OUTDATED_ERROR); cl_assert_equal_i(reftable_stack_reload(st2), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, 0), 0); + &ref2, NULL, 0), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); clear_dir(dir); @@ -249,7 +245,6 @@ void test_reftable_stack__uptodate(void) void test_reftable_stack__transaction_api(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_addition *add = NULL; @@ -261,11 +256,11 @@ void test_reftable_stack__transaction_api(void) }; struct reftable_ref_record dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -306,7 +301,7 @@ void test_reftable_stack__transaction_with_reload(void) cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); - cl_assert_equal_i(reftable_stack_new_addition(&add, st1, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[0]), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -317,9 +312,9 @@ void test_reftable_stack__transaction_with_reload(void) * create the addition and lock the stack by default, but allow the * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set. */ - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, 0), + cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0), REFTABLE_OUTDATED_ERROR); - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, + cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, REFTABLE_STACK_NEW_ADDITION_RELOAD), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[1]), 0); @@ -342,12 +337,11 @@ void test_reftable_stack__transaction_with_reload(void) void test_reftable_stack__transaction_api_performs_auto_compaction(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = {0}; struct reftable_addition *add = NULL; struct reftable_stack *st = NULL; size_t n = 20; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (size_t i = 0; i <= n; i++) { struct reftable_ref_record ref = { @@ -356,6 +350,9 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void) .value.symref = (char *) "master", }; char name[100]; + struct reftable_write_options write_opts = { + .disable_auto_compact = (i != n), + }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); ref.refname = name; @@ -365,10 +362,8 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void) * we can ensure that we indeed honor this setting and have * better control over when exactly auto compaction runs. */ - st->opts.disable_auto_compact = i != n; - cl_assert_equal_i(reftable_stack_new_addition(&add, - st, 0), 0); + st, &write_opts, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -398,15 +393,14 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) .value_type = REFTABLE_REF_VAL1, .value.val1 = {0x01}, }; - struct reftable_write_options opts = { 0 }; struct reftable_stack *st; struct reftable_buf table_path = REFTABLE_BUF_INIT; char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, NULL, 0), 0); cl_assert_equal_i(st->merged->tables_len, 1); cl_assert_equal_i(st->stats.attempts, 0); cl_assert_equal_i(st->stats.failures, 0); @@ -424,7 +418,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) write_file_buf(table_path.buf, "", 0); ref.update_index = 2; - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, NULL, 0); cl_assert(!err); cl_assert_equal_i(st->merged->tables_len, 2); cl_assert_equal_i(st->stats.attempts, 1); @@ -443,7 +437,6 @@ static int write_error(struct reftable_writer *wr UNUSED, void *arg) void test_reftable_stack__update_index_check(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record ref1 = { .refname = (char *) "name1", @@ -458,11 +451,11 @@ void test_reftable_stack__update_index_check(void) .value.symref = (char *) "master", }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref1, 0), 0); + &ref1, NULL, 0), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref2, 0), REFTABLE_API_ERROR); + &ref2, NULL, 0), REFTABLE_API_ERROR); reftable_stack_destroy(st); clear_dir(dir); } @@ -470,14 +463,13 @@ void test_reftable_stack__update_index_check(void) void test_reftable_stack__lock_failure(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int i; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) cl_assert_equal_i(reftable_stack_add(st, write_error, - &i, 0), i); + &i, NULL, 0), i); reftable_stack_destroy(st); clear_dir(dir); @@ -499,7 +491,7 @@ void test_reftable_stack__add(void) size_t i, N = ARRAY_SIZE(refs); int err = 0; - err = reftable_new_stack(&st, dir, &opts); + err = reftable_new_stack(&st, dir, NULL); cl_assert(!err); for (i = 0; i < N; i++) { @@ -521,7 +513,7 @@ void test_reftable_stack__add(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], &opts, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -529,10 +521,10 @@ void test_reftable_stack__add(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, &opts, 0), 0); } - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0); for (i = 0; i < N; i++) { struct reftable_ref_record dest = { 0 }; @@ -584,7 +576,6 @@ void test_reftable_stack__add(void) void test_reftable_stack__iterator(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_ref_record refs[10] = { 0 }; @@ -593,7 +584,7 @@ void test_reftable_stack__iterator(void) size_t N = ARRAY_SIZE(refs), i; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i < N; i++) { refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i); @@ -613,7 +604,7 @@ void test_reftable_stack__iterator(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], NULL, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -622,7 +613,7 @@ void test_reftable_stack__iterator(void) }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } reftable_stack_init_ref_iterator(st, &it); @@ -669,9 +660,6 @@ void test_reftable_stack__iterator(void) void test_reftable_stack__log_normalize(void) { - struct reftable_write_options opts = { - 0, - }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_log_record input = { @@ -693,15 +681,15 @@ void test_reftable_stack__log_normalize(void) .update_index = 1, }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); input.value.update.message = (char *) "one\ntwo"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), REFTABLE_API_ERROR); + &arg, NULL, 0), REFTABLE_API_ERROR); input.value.update.message = (char *) "one"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "one\n"); @@ -709,7 +697,7 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "two\n"; arg.update_index = 2; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "two\n"); @@ -723,7 +711,6 @@ void test_reftable_stack__log_normalize(void) void test_reftable_stack__tombstone(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record refs[2] = { 0 }; struct reftable_log_record logs[2] = { 0 }; @@ -731,7 +718,7 @@ void test_reftable_stack__tombstone(void) struct reftable_ref_record dest = { 0 }; struct reftable_log_record log_dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); /* even entries add the refs, odd entries delete them. */ for (i = 0; i < N; i++) { @@ -760,7 +747,7 @@ void test_reftable_stack__tombstone(void) } for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], NULL, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -768,7 +755,7 @@ void test_reftable_stack__tombstone(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } cl_assert_equal_i(reftable_stack_read_ref(st, "branch", @@ -779,7 +766,7 @@ void test_reftable_stack__tombstone(void) &log_dest), 1); reftable_log_record_release(&log_dest); - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0); cl_assert_equal_i(reftable_stack_read_ref(st, "branch", &dest), 1); cl_assert_equal_i(reftable_stack_read_log(st, "branch", @@ -799,7 +786,6 @@ void test_reftable_stack__tombstone(void) void test_reftable_stack__hash_id(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record ref = { @@ -808,15 +794,14 @@ void test_reftable_stack__hash_id(void) .value.symref = (char *) "target", .update_index = 1, }; - struct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 }; + struct reftable_stack_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 }; struct reftable_stack *st32 = NULL; - struct reftable_write_options opts_default = { 0 }; struct reftable_stack *st_default = NULL; struct reftable_ref_record dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, NULL, 0), 0); /* can't read it with the wrong hash ID. */ cl_assert_equal_i(reftable_new_stack(&st32, dir, @@ -824,7 +809,7 @@ void test_reftable_stack__hash_id(void) /* check that we can read it back with default opts too. */ cl_assert_equal_i(reftable_new_stack(&st_default, dir, - &opts_default), 0); + NULL), 0); cl_assert_equal_i(reftable_stack_read_ref(st_default, "master", &dest), 0); cl_assert(reftable_ref_record_equal(&ref, &dest, @@ -855,7 +840,6 @@ void test_reftable_stack__suggest_compaction_segment_nothing(void) void test_reftable_stack__reflog_expire(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_log_record logs[20] = { 0 }; size_t i, N = ARRAY_SIZE(logs) - 1; @@ -864,7 +848,7 @@ void test_reftable_stack__reflog_expire(void) }; struct reftable_log_record log = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 1; i <= N; i++) { char buf[256]; @@ -885,18 +869,18 @@ void test_reftable_stack__reflog_expire(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); - cl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0); cl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname, &log), 1); cl_assert_equal_i(reftable_stack_read_log(st, logs[11].refname, &log), 0); expiry.min_update_index = 15; - cl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0); cl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname, &log), 1); cl_assert_equal_i(reftable_stack_read_log(st, logs[16].refname, @@ -918,15 +902,14 @@ static int write_nothing(struct reftable_writer *wr, void *arg UNUSED) void test_reftable_stack__empty_add(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_stack *st2 = NULL; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_nothing, - NULL, 0), 0); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); + NULL, NULL, 0), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); clear_dir(dir); reftable_stack_destroy(st); reftable_stack_destroy(st2); @@ -952,7 +935,7 @@ void test_reftable_stack__auto_compaction(void) size_t i, N = 100; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i < N; i++) { char name[100]; @@ -964,10 +947,10 @@ void test_reftable_stack__auto_compaction(void) }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); cl_assert(!err); - err = reftable_stack_auto_compact(st); + err = reftable_stack_auto_compact(st, &opts); cl_assert(!err); cl_assert(i < 2 || st->merged->tables_len < 2 * fastlogN(i, 2)); } @@ -989,7 +972,7 @@ void test_reftable_stack__auto_compaction_factor(void) size_t N = 100; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (size_t i = 0; i < N; i++) { char name[20]; @@ -1000,7 +983,7 @@ void test_reftable_stack__auto_compaction_factor(void) }; xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, &write_test_ref, &ref, 0); + err = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0); cl_assert(!err); cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); @@ -1020,7 +1003,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 5); cl_assert_equal_i(st->merged->tables_len, 5); @@ -1042,7 +1025,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) * would in theory compact all tables, due to the preexisting lock we * only compact the newest two tables. */ - err = reftable_stack_auto_compact(st); + err = reftable_stack_auto_compact(st, &opts); cl_assert(!err); cl_assert_equal_i(st->stats.failures, 0); cl_assert_equal_i(st->merged->tables_len, 4); @@ -1054,12 +1037,11 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) void test_reftable_stack__add_performs_auto_compaction(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); size_t i, n = 20; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i <= n; i++) { struct reftable_ref_record ref = { @@ -1067,6 +1049,9 @@ void test_reftable_stack__add_performs_auto_compaction(void) .value_type = REFTABLE_REF_SYMREF, .value.symref = (char *) "master", }; + struct reftable_write_options write_opts = { + .disable_auto_compact = (i != n), + }; bool required = false; char buf[128]; @@ -1075,20 +1060,18 @@ void test_reftable_stack__add_performs_auto_compaction(void) * we can ensure that we indeed honor this setting and have * better control over when exactly auto compaction runs. */ - st->opts.disable_auto_compact = i != n; - snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i); ref.refname = buf; cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, &write_opts, 0), 0); /* * The stack length should grow continuously for all runs where * auto compaction is disabled. When enabled, we should merge * all tables in the stack. */ - cl_assert_equal_i(reftable_stack_compaction_required(st, true, &required), 0); + cl_assert_equal_i(reftable_stack_compaction_required(st, NULL, true, &required), 0); if (i != n) { cl_assert_equal_i(st->merged->tables_len, i + 1); if (i < 1) @@ -1115,7 +1098,7 @@ void test_reftable_stack__compaction_with_locked_tables(void) char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 3); cl_assert_equal_i(st->merged->tables_len, 3); @@ -1131,7 +1114,7 @@ void test_reftable_stack__compaction_with_locked_tables(void) * Compaction is expected to fail given that we were not able to * compact all tables. */ - err = reftable_stack_compact_all(st, NULL); + err = reftable_stack_compact_all(st, &opts, NULL); cl_assert_equal_i(err, REFTABLE_LOCK_ERROR); cl_assert_equal_i(st->stats.failures, 1); cl_assert_equal_i(st->merged->tables_len, 3); @@ -1143,15 +1126,14 @@ void test_reftable_stack__compaction_with_locked_tables(void) void test_reftable_stack__compaction_concurrent(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL; char *dir = get_tmp_dir(__LINE__); - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 3); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); - cl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); @@ -1171,20 +1153,19 @@ static void unclean_stack_close(struct reftable_stack *st) void test_reftable_stack__compaction_concurrent_clean(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL; char *dir = get_tmp_dir(__LINE__); - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 3); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); - cl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0); unclean_stack_close(st1); unclean_stack_close(st2); - cl_assert_equal_i(reftable_new_stack(&st3, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st3, dir, NULL), 0); cl_assert_equal_i(reftable_stack_clean(st3), 0); cl_assert_equal_i(count_dir_entries(dir), 2); @@ -1197,7 +1178,6 @@ void test_reftable_stack__compaction_concurrent_clean(void) void test_reftable_stack__read_across_reload(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; @@ -1205,17 +1185,17 @@ void test_reftable_stack__read_across_reload(void) int err; /* Create a first stack and set up an iterator for it. */ - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 2); cl_assert_equal_i(st1->merged->tables_len, 2); reftable_stack_init_ref_iterator(st1, &it); cl_assert_equal_i(reftable_iterator_seek_ref(&it, ""), 0); /* Set up a second stack for the same directory and compact it. */ - err = reftable_new_stack(&st2, dir, &opts); + err = reftable_new_stack(&st2, dir, NULL); cl_assert(!err); cl_assert_equal_i(st2->merged->tables_len, 2); - err = reftable_stack_compact_all(st2, NULL); + err = reftable_stack_compact_all(st2, NULL, NULL); cl_assert(!err); cl_assert_equal_i(st2->merged->tables_len, 1); @@ -1244,7 +1224,6 @@ void test_reftable_stack__read_across_reload(void) void test_reftable_stack__reload_with_missing_table(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; @@ -1253,7 +1232,7 @@ void test_reftable_stack__reload_with_missing_table(void) int err; /* Create a first stack and set up an iterator for it. */ - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 2); cl_assert_equal_i(st->merged->tables_len, 2); reftable_stack_init_ref_iterator(st, &it); @@ -1320,11 +1299,11 @@ void test_reftable_stack__invalid_limit_updates(void) char *dir = get_tmp_dir(__LINE__); struct reftable_stack *st = NULL; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0); /* * write_limits_after_ref also updates the update indexes after adding diff --git a/t/unit-tests/u-reftable-table.c b/t/unit-tests/u-reftable-table.c index 14fae8b199..fae478ee04 100644 --- a/t/unit-tests/u-reftable-table.c +++ b/t/unit-tests/u-reftable-table.c @@ -22,7 +22,8 @@ void test_reftable_table__seek_once(void) struct reftable_buf buf = REFTABLE_BUF_INIT; int ret; - cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); + cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, + REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); @@ -64,7 +65,7 @@ void test_reftable_table__reseek(void) int ret; cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), - NULL, 0, NULL); + NULL, 0, REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); @@ -147,7 +148,8 @@ void test_reftable_table__block_iterator(void) (uintmax_t) i); } - cl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL); + cl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, + REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 10/11] refs/reftable: lazy-load configuration to fix chicken-and-egg 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (8 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 09/11] reftable: split up write options Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 11/11] refs: protect against chicken-and-egg recursion Patrick Steinhardt 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler Same as with the "files" backend, the "reftable" backend also has a chicken-and-egg problem with "onbranch" conditions. Fix this issue the same as we did with the "files" backend by lazy-loading configuration. Now that both the "files" and the "reftable" backend handle this properly, add a generic test to t1400 that verifies that the user can configure "core.logAllRefUpdates" via an "onbranch" condition. This is mostly a nonsensical thing to do in the first place, but it serves as a good sanity chekc. Note that we had to move `should_write_log()` around so that it can access the new `reftable_be_write_options()` function. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/reftable-backend.c | 146 ++++++++++++++++++++++---------------- t/t0613-reftable-write-options.sh | 19 +++++ t/t1400-update-ref.sh | 12 ++++ 3 files changed, 116 insertions(+), 61 deletions(-) diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 608d71cf10..d74131a5ae 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -141,10 +141,21 @@ struct reftable_ref_store { */ struct strmap worktree_backends; struct reftable_stack_options stack_options; - struct reftable_write_options write_options; + + /* + * Options used when writing to or compacting the reftable stacks. + * These are parsed from the configuration lazily on first use via + * `reftable_be_write_options()` so that we don't have to access the + * configuration when initializing the ref store. Do not access these + * fields directly, but use the accessor instead. + */ + struct reftable_be_write_options { + struct reftable_write_options opts; + enum log_refs_config log_all_ref_updates; + bool initialized; + } write_opts_lazy_loaded; unsigned int store_flags; - enum log_refs_config log_all_ref_updates; int err; }; @@ -285,26 +296,6 @@ static int backend_for(struct reftable_backend **out, return ret; } -static int should_write_log(struct reftable_ref_store *refs, const char *refname) -{ - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates; - if (log_refs_cfg == LOG_REFS_UNSET) - log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; - - switch (log_refs_cfg) { - case LOG_REFS_NONE: - return refs_reflog_exists(&refs->base, refname); - case LOG_REFS_ALWAYS: - return 1; - case LOG_REFS_NORMAL: - if (should_autocreate_reflog(log_refs_cfg, refname)) - return 1; - return refs_reflog_exists(&refs->base, refname); - default: - BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg); - } -} - static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split) { const char *tz_begin; @@ -336,38 +327,72 @@ static int reftable_be_config(const char *var, const char *value, void *payload) { struct reftable_ref_store *refs = payload; + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded; if (!strcmp(var, "reftable.blocksize")) { unsigned long block_size = git_config_ulong(var, value, ctx->kvi); if (block_size > 16777215) die("reftable block size cannot exceed 16MB"); - refs->write_options.block_size = block_size; + opts->opts.block_size = block_size; } else if (!strcmp(var, "reftable.restartinterval")) { unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi); if (restart_interval > UINT16_MAX) die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX); - refs->write_options.restart_interval = restart_interval; + opts->opts.restart_interval = restart_interval; } else if (!strcmp(var, "reftable.indexobjects")) { - refs->write_options.skip_index_objects = !git_config_bool(var, value); + opts->opts.skip_index_objects = !git_config_bool(var, value); } else if (!strcmp(var, "reftable.geometricfactor")) { unsigned long factor = git_config_ulong(var, value, ctx->kvi); if (factor > UINT8_MAX) die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX); - refs->write_options.auto_compaction_factor = factor; + opts->opts.auto_compaction_factor = factor; } else if (!strcmp(var, "reftable.locktimeout")) { int64_t lock_timeout = git_config_int64(var, value, ctx->kvi); if (lock_timeout > LONG_MAX) die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX); if (lock_timeout < 0 && lock_timeout != -1) die("reftable lock timeout does not support negative values other than -1"); - refs->write_options.lock_timeout_ms = lock_timeout; + opts->opts.lock_timeout_ms = lock_timeout; } else if (!strcmp(var, "core.logallrefupdates")) { - refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); } return 0; } +static const struct reftable_be_write_options *reftable_be_write_options(struct reftable_ref_store *refs) +{ + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded; + mode_t mask; + + if (opts->initialized) + return opts; + + mask = umask(0); + umask(mask); + + opts->opts.default_permissions = calc_shared_perm(refs->base.repo, 0666 & ~mask); + opts->opts.disable_auto_compact = + !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); + opts->opts.lock_timeout_ms = 100; + opts->log_all_ref_updates = LOG_REFS_UNSET; + + repo_config(refs->base.repo, reftable_be_config, refs); + + /* + * It is somewhat unfortunate that we have to mirror the default block + * size of the reftable library here. But given that the write options + * wouldn't be updated by the library here, and given that we require + * the proper block size to trim reflog message so that they fit, we + * must set up a proper value here. + */ + if (!opts->opts.block_size) + opts->opts.block_size = 4096; + + opts->initialized = true; + return opts; +} + static void reftable_be_reparent(const char *name UNUSED, const char *old_cwd, const char *new_cwd, @@ -391,10 +416,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, struct strbuf refdir = STRBUF_INIT; struct strbuf path = STRBUF_INIT; bool is_worktree; - mode_t mask; - - mask = umask(0); - umask(mask); refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir, &ref_common_dir); @@ -413,23 +434,6 @@ static struct ref_store *reftable_be_init(struct repository *repo, default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); } - refs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask); - refs->write_options.disable_auto_compact = - !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); - refs->write_options.lock_timeout_ms = 100; - refs->log_all_ref_updates = LOG_REFS_UNSET; - - repo_config(repo, reftable_be_config, refs); - - /* - * It is somewhat unfortunate that we have to mirror the default block - * size of the reftable library here. But given that the write options - * wouldn't be updated by the library here, and given that we require - * the proper block size to trim reflog message so that they fit, we - * must set up a proper value here. - */ - if (!refs->write_options.block_size) - refs->write_options.block_size = 4096; /* * Set up the main reftable stack that is hosted in GIT_COMMON_DIR. @@ -998,7 +1002,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out, struct reftable_addition *addition; ret = reftable_stack_new_addition(&addition, be->stack, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret) { if (ret == REFTABLE_LOCK_ERROR) @@ -1437,6 +1441,26 @@ static int transaction_update_cmp(const void *a, const void *b) return strcmp(update_a->update->refname, update_b->update->refname); } +static int should_write_log(struct reftable_ref_store *refs, const char *refname) +{ + enum log_refs_config log_refs_cfg = reftable_be_write_options(refs)->log_all_ref_updates; + if (log_refs_cfg == LOG_REFS_UNSET) + log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; + + switch (log_refs_cfg) { + case LOG_REFS_NONE: + return refs_reflog_exists(&refs->base, refname); + case LOG_REFS_ALWAYS: + return 1; + case LOG_REFS_NORMAL: + if (should_autocreate_reflog(log_refs_cfg, refname)) + return 1; + return refs_reflog_exists(&refs->base, refname); + default: + BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg); + } +} + static int write_transaction_table(struct reftable_writer *writer, void *cb_data) { struct write_transaction_table_arg *arg = cb_data; @@ -1571,7 +1595,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ); log->value.update.message = - xstrndup(u->msg, arg->refs->write_options.block_size / 2); + xstrndup(u->msg, reftable_be_write_options(arg->refs)->opts.block_size / 2); } } @@ -1687,9 +1711,9 @@ static int reftable_be_optimize(struct ref_store *ref_store, stack = refs->main_backend.stack; if (opts->flags & REFS_OPTIMIZE_AUTO) - ret = reftable_stack_auto_compact(stack, &refs->write_options); + ret = reftable_stack_auto_compact(stack, &reftable_be_write_options(refs)->opts); else - ret = reftable_stack_compact_all(stack, &refs->write_options, NULL); + ret = reftable_stack_compact_all(stack, &reftable_be_write_options(refs)->opts, NULL); if (ret < 0) { ret = error(_("unable to compact stack: %s"), reftable_error_str(ret)); @@ -1723,7 +1747,7 @@ static int reftable_be_optimize_required(struct ref_store *ref_store, if (opts->flags & REFS_OPTIMIZE_AUTO) use_heuristics = true; - return reftable_stack_compaction_required(stack, &refs->write_options, + return reftable_stack_compaction_required(stack, &reftable_be_write_options(refs)->opts, use_heuristics, required); } @@ -1843,7 +1867,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = deletion_ts; logs[logs_nr].value.update.message = - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2); memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; @@ -1882,7 +1906,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = creation_ts; logs[logs_nr].value.update.message = - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2); memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; @@ -1981,7 +2005,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2012,7 +2036,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2378,7 +2402,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2451,7 +2475,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); assert(ret != REFTABLE_API_ERROR); @@ -2574,7 +2598,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, goto done; ret = reftable_stack_new_addition(&add, be->stack, - &refs->write_options, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret < 0) goto done; diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index 26b716c75f..a65960d048 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh @@ -278,4 +278,23 @@ test_expect_success 'object index can be disabled' ' ) ' +test_expect_success 'write options can be set up via onbranch condition' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + init_repo && + ( + cd repo && + test_commit A && + test_commit B && + cat >.git/include <<-\EOF && + [reftable] + blockSize = 123 + EOF + git config includeIf.onbranch:master.path "$(pwd)/.git/include" && + git refs optimize && + test-tool dump-reftable -b .git/reftable/*.ref >stats && + test_grep "block_size: 123" stats + ) +' + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 1015f335e3..b8c3be6631 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -178,6 +178,18 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' test_must_fail git reflog exists $outside ' +test_expect_success 'core.logAllRefUpdates can be set up via onbranch condition' ' + test_when_finished "git update-ref -d $outside" && + test_when_finished "rm -f .git/include" && + cat >.git/include <<-\EOF && + [core] + logAllRefUpdates = always + EOF + test_config includeIf.onbranch:main.path "$(pwd)/.git/include" && + git update-ref $outside $A && + git reflog exists $outside +' + test_expect_success "create $m (by HEAD)" ' git update-ref HEAD $A && test $A = $(git show-ref -s --verify $m) -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v5 11/11] refs: protect against chicken-and-egg recursion 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt ` (9 preceding siblings ...) 2026-06-22 8:28 ` [PATCH v5 10/11] refs/reftable: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt @ 2026-06-22 8:28 ` Patrick Steinhardt 10 siblings, 0 replies; 80+ messages in thread From: Patrick Steinhardt @ 2026-06-22 8:28 UTC (permalink / raw) To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler In the preceding commits we have fixed recursion when creating the reference backends due to a chicken-and-egg situation with "onbranch" conditions. Unfortunately, this issue has existed for a while, and we didn't really have a good mechanism to detect this recursion. Improve the status quo by detecting the recursion when creating the main reference store. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/refs.c b/refs.c index 5b773b1c15..1d24637891 100644 --- a/refs.c +++ b/refs.c @@ -2359,15 +2359,22 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + static bool initializing; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); + if (initializing) + BUG("initialization of main ref store is recursing"); + initializing = true; r->refs_private = ref_store_init(r, r->ref_storage_format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + initializing = false; + return r->refs_private; } -- 2.55.0.rc1.745.g43192e7977.dirty ^ permalink raw reply related [flat|nested] 80+ messages in thread
end of thread, other threads:[~2026-06-22 8:28 UTC | newest] Thread overview: 80+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-10 14:57 [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 1/9] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 2/9] setup: stop applying repository format twice Patrick Steinhardt 2026-06-12 9:00 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 3/9] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt 2026-06-10 17:32 ` Junio C Hamano 2026-06-12 6:18 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 4/9] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt 2026-06-12 9:18 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 5/9] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 6/9] repository: free main reference database Patrick Steinhardt 2026-06-12 9:20 ` Karthik Nayak 2026-06-10 14:57 ` [PATCH 7/9] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 8/9] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 2026-06-10 14:57 ` [PATCH 9/9] refs: always use absolute paths for reference stores Patrick Steinhardt 2026-06-12 9:58 ` Karthik Nayak 2026-06-15 12:36 ` Patrick Steinhardt 2026-06-11 6:53 ` [PATCH 0/9] refs: stop using `chdir_notify_reparent()` Jeff King 2026-06-12 6:18 ` Patrick Steinhardt 2026-06-13 14:00 ` Jeff King 2026-06-15 12:36 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 0/8] " Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 2/8] setup: stop applying repository format twice Patrick Steinhardt 2026-06-17 17:22 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt 2026-06-17 17:43 ` Justin Tobler 2026-06-18 6:53 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt 2026-06-17 18:02 ` Justin Tobler 2026-06-17 18:07 ` Justin Tobler 2026-06-18 6:54 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 6/8] repository: free main reference database Patrick Steinhardt 2026-06-17 18:09 ` Justin Tobler 2026-06-15 13:56 ` [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-17 18:41 ` Justin Tobler 2026-06-18 5:59 ` Patrick Steinhardt 2026-06-18 14:15 ` Justin Tobler 2026-06-18 14:51 ` Patrick Steinhardt 2026-06-18 15:53 ` Justin Tobler 2026-06-18 16:40 ` Jeff King 2026-06-19 6:25 ` Patrick Steinhardt 2026-06-21 21:12 ` Jeff King 2026-06-22 5:15 ` Patrick Steinhardt 2026-06-15 13:56 ` [PATCH v2 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 0/8] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 1/8] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 2/8] setup: stop applying repository format twice Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 4/8] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 5/8] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 6/8] repository: free main reference database Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-18 6:54 ` [PATCH v3 8/8] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 00/10] refs: stop using `chdir_notify_reparent()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 01/10] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 02/10] setup: stop applying repository format twice Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 03/10] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 04/10] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 05/10] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 06/10] repository: free main reference database Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 07/10] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 08/10] refs/reftable-backend: manually parse "core.sharedRepository" Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 09/10] refs: fix recursing `get_main_ref_store()` with "onbranch" config Patrick Steinhardt 2026-06-19 11:27 ` [PATCH v4 10/10] refs: drop local buffer in `refs_compute_filesystem_location()` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 00/11] refs: fix "onbranch" conditions Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 01/11] setup: inline `check_and_apply_repository_format()` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 02/11] setup: stop applying repository format twice Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 03/11] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 04/11] refs: unregister reference stores from "chdir_notify" Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 05/11] chdir-notify: drop unused `chdir_notify_reparent()` Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 06/11] repository: free main reference database Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 07/11] refs: move parsing of "core.logAllRefUpdates" back into ref stores Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 08/11] refs/files: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 09/11] reftable: split up write options Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 10/11] refs/reftable: lazy-load configuration to fix chicken-and-egg Patrick Steinhardt 2026-06-22 8:28 ` [PATCH v5 11/11] refs: protect against chicken-and-egg recursion Patrick Steinhardt
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox