refs: unregister reference stores from "chdir_notify"

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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jun 25, 2026 at 11:20 UTC 1f43ff2c7e4c1575960ad3b1338922d7fa837c0c
3 files changed +49 -5
refs/files-backend.c
+19 -3
@@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
100 }
101 }
102
103 +static void files_ref_store_reparent(const char *name UNUSED,
104 + const char *old_cwd,
105 + const char *new_cwd,
106 + void *payload)
107 +{
108 + struct files_ref_store *refs = payload;
109 + char *tmp;
110 +
111 + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);
112 + free(refs->base.gitdir);
113 + refs->base.gitdir = tmp;
114 +
115 + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir);
116 + free(refs->gitcommondir);
117 + refs->gitcommondir = tmp;
118 +}
119 +
120 /*
121 * Create a new submodule ref cache and add it to the internal
122 * set of caches.
@@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
145
146 repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
147
131 - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
132 - chdir_notify_reparent("files-backend $GIT_COMMONDIR",
133 - &refs->gitcommondir);
148 + chdir_notify_register(NULL, files_ref_store_reparent, refs);
149
150 strbuf_release(&refdir);
151
@@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store)
197 free(refs->gitcommondir);
198 ref_store_release(refs->packed_ref_store);
199 free(refs->packed_ref_store);
200 + chdir_notify_unregister(NULL, files_ref_store_reparent, refs);
201 }
202
203 static void files_reflog_path(struct files_ref_store *refs,
refs/packed-backend.c
+15 -1
@@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
211 return snapshot->refs->base.repo->hash_algo->hexsz;
212 }
213
214 +static void packed_ref_store_reparent(const char *name UNUSED,
215 + const char *old_cwd,
216 + const char *new_cwd,
217 + void *payload)
218 +{
219 + struct packed_ref_store *refs = payload;
220 + char *tmp;
221 +
222 + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path);
223 + free(refs->path);
224 + refs->path = tmp;
225 +}
226 +
227 /*
228 * Since packed-refs is only stored in the common dir, don't parse the
229 * 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,
242
243 strbuf_addf(&sb, "%s/packed-refs", gitdir);
244 refs->path = strbuf_detach(&sb, NULL);
232 - chdir_notify_reparent("packed-refs", &refs->path);
245 + chdir_notify_register(NULL, packed_ref_store_reparent, refs);
246 return ref_store;
247 }
248
@@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store)
287 clear_snapshot(refs);
288 rollback_lock_file(&refs->lock);
289 delete_tempfile(&refs->tempfile);
290 + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs);
291 free(refs->path);
292 }
293
refs/reftable-backend.c
+15 -1
@@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value,
365 return 0;
366 }
367
368 +static void reftable_be_reparent(const char *name UNUSED,
369 + const char *old_cwd,
370 + const char *new_cwd,
371 + void *payload)
372 +{
373 + struct reftable_ref_store *refs = payload;
374 + char *tmp;
375 +
376 + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);
377 + free(refs->base.gitdir);
378 + refs->base.gitdir = tmp;
379 +}
380 +
381 static struct ref_store *reftable_be_init(struct repository *repo,
382 const char *payload,
383 const char *gitdir,
@@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
460 goto done;
461 }
462
450 - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir);
463 + chdir_notify_register(NULL, reftable_be_reparent, refs);
464
465 done:
466 assert(refs->err != REFTABLE_API_ERROR);
@@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store)
487 free(be);
488 }
489 strmap_clear(&refs->worktree_backends, 0);
490 + chdir_notify_unregister(NULL, reftable_be_reparent, refs);
491 }
492
493 static int reftable_be_create_on_disk(struct ref_store *ref_store,