# [PATCH 00/14] fsck: drop use of `the_repository`

18 messages from 2026-03-20 to 2026-03-23. Participants: Patrick Steinhardt, Junio C Hamano.
Thread: https://gitlist.dev/t/65317

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 00/14] fsck: drop use of `the_repository`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b%40pks.im

```
Hi,

this patch series refactors "{builtin/,}fsck.c" to not depend on
`the_repository` anymore. These refactorings are mostly done to prepare
for upcoming changes where we'll make backend-specific fsck checks
pluggable.

Thanks!

Patrick

---
Patrick Steinhardt (14):
      fsck: drop `the_repository` in `fsck_walk()`
      fsck: drop `the_repository` in `fsck_finish()`
      fsck: refactor interface to parse fsck options
      fsck: drop `the_repository` in `fsck_set_msg_types()`
      fsck: stop relying on global state via `parse_oid_hex()`
      builtin/fsck: fix trivial dependence on `the_repository`
      builtin/fsck: stop using `the_repository` when snapshotting refs
      builtin/fsck: stop using `the_repository` when checking refs
      builtin/fsck: stop using `the_repository` when checking reflogs
      builtin/fsck: stop using `the_repository` with loose objects
      builtin/fsck: stop using `the_repository` when checking packed objects
      builtin/fsck: stop using `the_repository` when marking objects
      fsck: provide repository in `struct fsck_report_object`
      builtin/fsck: stop using `the_repository` in error reporting

 builtin/fsck.c           | 299 ++++++++++++++++-------------
 builtin/index-pack.c     |  15 +-
 builtin/mktag.c          |   7 +-
 builtin/refs.c           |   2 +-
 builtin/unpack-objects.c |   9 +-
 fetch-pack.c             |   4 +-
 fsck.c                   | 485 ++++++++++++++++++++++++++++-------------------
 fsck.h                   |  41 ++--
 object-file.c            |   5 +-
 pack-check.c             |   7 +-
 pack.h                   |   9 +-
 11 files changed, 520 insertions(+), 363 deletions(-)


---
base-commit: 7ff1e8dc1e1680510c96e69965b3fa81372c5037
change-id: 20260320-b4-pks-fsck-without-the-repository-4ddc4c8ed61e


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 01/14] fsck: drop `the_repository` in `fsck_walk()`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-1-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-1-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
The function `fsck_walk()` and its object type specific functions
`fsck_walk_tree()` et al implicitly rely on `the_repository`. Remove
this dependency by injecting the repository as a parameter instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c           |  6 +++---
 builtin/index-pack.c     |  2 +-
 builtin/unpack-objects.c |  2 +-
 fsck.c                   | 40 ++++++++++++++++++++++++++--------------
 fsck.h                   | 15 ++++++++++++---
 5 files changed, 43 insertions(+), 22 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 9bab32effe..15477767c7 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -186,7 +186,7 @@ static void mark_object_reachable(struct object *obj)
 
 static int traverse_one_object(struct object *obj)
 {
-	int result = fsck_walk(obj, obj, &fsck_walk_options);
+	int result = fsck_walk(the_repository, obj, obj, &fsck_walk_options);
 
 	if (obj->type == OBJ_TREE) {
 		struct tree *tree = (struct tree *)obj;
@@ -244,7 +244,7 @@ static int mark_unreachable_referents(const struct object_id *oid,
 	}
 
 	options.walk = mark_used;
-	fsck_walk(obj, NULL, &options);
+	fsck_walk(the_repository, obj, NULL, &options);
 	if (obj->type == OBJ_TREE)
 		free_tree_buffer((struct tree *)obj);
 
@@ -413,7 +413,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
 			   printable_type(&obj->oid, obj->type),
 			   describe_object(&obj->oid));
 
-	if (fsck_walk(obj, NULL, &fsck_obj_options))
+	if (fsck_walk(the_repository, obj, NULL, &fsck_obj_options))
 		objerror(obj, _("broken links"));
 	err = fsck_object(obj, buffer, size, &fsck_obj_options);
 	if (err)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index d1e47279a8..a3d37d34cc 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -954,7 +954,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
 			if (do_fsck_object &&
 			    fsck_object(obj, buf, size, &fsck_options))
 				die(_("fsck error in packed object"));
-			if (strict && fsck_walk(obj, NULL, &fsck_options))
+			if (strict && fsck_walk(the_repository, obj, NULL, &fsck_options))
 				die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid));
 			if (record_outgoing_links)
 				do_record_outgoing_links(obj);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 6fc64e9e4b..52b62ff6d4 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -244,7 +244,7 @@ static int check_object(struct object *obj, enum object_type type,
 	if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options))
 		die("fsck error in packed object");
 	fsck_options.walk = check_object;
-	if (fsck_walk(obj, NULL, &fsck_options))
+	if (fsck_walk(the_repository, obj, NULL, &fsck_options))
 		die("Error on reachable objects of %s", oid_to_hex(&obj->oid));
 	write_cached_object(obj, obj_buf);
 	return 0;
diff --git a/fsck.c b/fsck.c
index 0f02cf8f77..c6b6f533be 100644
--- a/fsck.c
+++ b/fsck.c
@@ -353,14 +353,17 @@ const char *fsck_describe_object(struct fsck_options *options,
 	return buf->buf;
 }
 
-static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options)
+static int fsck_walk_tree(struct repository *repo,
+			  struct tree *tree,
+			  void *data,
+			  struct fsck_options *options)
 {
 	struct tree_desc desc;
 	struct name_entry entry;
 	int res = 0;
 	const char *name;
 
-	if (repo_parse_tree(the_repository, tree))
+	if (repo_parse_tree(repo, tree))
 		return -1;
 
 	name = fsck_get_object_name(options, &tree->object.oid);
@@ -375,14 +378,14 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
 			continue;
 
 		if (S_ISDIR(entry.mode)) {
-			obj = (struct object *)lookup_tree(the_repository, &entry.oid);
+			obj = (struct object *)lookup_tree(repo, &entry.oid);
 			if (name && obj)
 				fsck_put_object_name(options, &entry.oid, "%s%s/",
 						     name, entry.path);
 			result = options->walk(obj, OBJ_TREE, data, options);
 		}
 		else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) {
-			obj = (struct object *)lookup_blob(the_repository, &entry.oid);
+			obj = (struct object *)lookup_blob(repo, &entry.oid);
 			if (name && obj)
 				fsck_put_object_name(options, &entry.oid, "%s%s",
 						     name, entry.path);
@@ -401,7 +404,10 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
 	return res;
 }
 
-static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_options *options)
+static int fsck_walk_commit(struct repository *repo,
+			    struct commit *commit,
+			    void *data,
+			    struct fsck_options *options)
 {
 	int counter = 0, generation = 0, name_prefix_len = 0;
 	struct commit_list *parents;
@@ -409,7 +415,7 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio
 	int result;
 	const char *name;
 
-	if (repo_parse_commit(the_repository, commit))
+	if (repo_parse_commit(repo, commit))
 		return -1;
 
 	name = fsck_get_object_name(options, &commit->object.oid);
@@ -417,7 +423,7 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio
 		fsck_put_object_name(options, get_commit_tree_oid(commit),
 				     "%s:", name);
 
-	result = options->walk((struct object *) repo_get_commit_tree(the_repository, commit),
+	result = options->walk((struct object *) repo_get_commit_tree(repo, commit),
 			       OBJ_TREE, data, options);
 	if (result < 0)
 		return result;
@@ -470,34 +476,40 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio
 	return res;
 }
 
-static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options)
+static int fsck_walk_tag(struct repository *repo,
+			 struct tag *tag,
+			 void *data,
+			 struct fsck_options *options)
 {
 	const char *name = fsck_get_object_name(options, &tag->object.oid);
 
-	if (parse_tag(the_repository, tag))
+	if (parse_tag(repo, tag))
 		return -1;
 	if (name)
 		fsck_put_object_name(options, &tag->tagged->oid, "%s", name);
 	return options->walk(tag->tagged, OBJ_ANY, data, options);
 }
 
-int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
+int fsck_walk(struct repository *repo,
+	      struct object *obj,
+	      void *data,
+	      struct fsck_options *options)
 {
 	if (!obj)
 		return -1;
 
 	if (obj->type == OBJ_NONE)
-		parse_object(the_repository, &obj->oid);
+		parse_object(repo, &obj->oid);
 
 	switch (obj->type) {
 	case OBJ_BLOB:
 		return 0;
 	case OBJ_TREE:
-		return fsck_walk_tree((struct tree *)obj, data, options);
+		return fsck_walk_tree(repo, (struct tree *)obj, data, options);
 	case OBJ_COMMIT:
-		return fsck_walk_commit((struct commit *)obj, data, options);
+		return fsck_walk_commit(repo, (struct commit *)obj, data, options);
 	case OBJ_TAG:
-		return fsck_walk_tag((struct tag *)obj, data, options);
+		return fsck_walk_tag(repo, (struct tag *)obj, data, options);
 	default:
 		error("Unknown object type for %s",
 		      fsck_describe_object(options, &obj->oid));
diff --git a/fsck.h b/fsck.h
index 65ecbb7fe1..4bd54865fe 100644
--- a/fsck.h
+++ b/fsck.h
@@ -208,14 +208,23 @@ struct fsck_options {
 	.error_func = fsck_refs_error_function, \
 }
 
-/* descend in all linked child objects
- * the return value is:
+/*
+ * Perform consistency checks for the given object and all of its decendents.
+ *
+ * If set, the `walk` callback function in the options structure will be called
+ * for every commit. The data parameter will be passed as callback data.
+ *
+ * Returns:
+ *
  *    -1	error in processing the object
  *    <0	return value of the callback, which lead to an abort
  *    >0	return value of the first signaled error >0 (in the case of no other errors)
  *    0		everything OK
  */
-int fsck_walk(struct object *obj, void *data, struct fsck_options *options);
+int fsck_walk(struct repository *repo,
+	      struct object *obj,
+	      void *data,
+	      struct fsck_options *options);
 
 /*
  * Blob objects my pass a NULL data pointer, which indicates they are too large

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 02/14] fsck: drop `the_repository` in `fsck_finish()`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-2-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-2-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
The function `fsck_finish()` and  its transitively called function
`fsck_blobs()` implicitly rely on `the_repository`. Remove this
dependency by injecting the repository as a parameter instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c           |  2 +-
 builtin/index-pack.c     |  2 +-
 builtin/unpack-objects.c |  2 +-
 fetch-pack.c             |  4 ++--
 fsck.c                   | 13 +++++++------
 fsck.h                   |  2 +-
 object-file.c            |  2 +-
 7 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 15477767c7..8f994010da 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -1075,7 +1075,7 @@ int cmd_fsck(int argc,
 			stop_progress(&progress);
 		}
 
-		if (fsck_finish(&fsck_obj_options))
+		if (fsck_finish(the_repository, &fsck_obj_options))
 			errors_found |= ERROR_OBJECT;
 	}
 
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index a3d37d34cc..3d1f7dbd5f 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -2123,7 +2123,7 @@ int cmd_index_pack(int argc,
 			die(_("cannot perform queued object checks outside "
 			      "of a repository"));
 
-		if (fsck_finish(&fsck_options))
+		if (fsck_finish(the_repository, &fsck_options))
 			die(_("fsck error in pack objects"));
 	}
 
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 52b62ff6d4..9596b3ea4f 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -676,7 +676,7 @@ int cmd_unpack_objects(int argc,
 	git_hash_final_oid(&oid, &tmp_ctx);
 	if (strict) {
 		write_rest();
-		if (fsck_finish(&fsck_options))
+		if (fsck_finish(the_repository, &fsck_options))
 			die(_("fsck error in pack objects"));
 	}
 	if (!hasheq(fill(the_hash_algo->rawsz), oid.hash,
diff --git a/fetch-pack.c b/fetch-pack.c
index 6ecd468ef7..6fd534633d 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1231,7 +1231,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
 	if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought,
 		     &fsck_options.gitmodules_found))
 		die(_("git fetch-pack: fetch failed."));
-	if (fsck_finish(&fsck_options))
+	if (fsck_finish(the_repository, &fsck_options))
 		die("fsck failed");
 
  all_done:
@@ -1876,7 +1876,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
 	string_list_clear(&packfile_uris, 0);
 	strvec_clear(&index_pack_args);
 
-	if (fsck_finish(&fsck_options))
+	if (fsck_finish(the_repository, &fsck_options))
 		die("fsck failed");
 
 	if (negotiator)
diff --git a/fsck.c b/fsck.c
index c6b6f533be..1f7d568acf 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1331,7 +1331,8 @@ int fsck_refs_error_function(struct fsck_options *options UNUSED,
 	return ret;
 }
 
-static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done,
+static int fsck_blobs(struct repository *repo,
+		      struct oidset *blobs_found, struct oidset *blobs_done,
 		      enum fsck_msg_id msg_missing, enum fsck_msg_id msg_type,
 		      struct fsck_options *options, const char *blob_type)
 {
@@ -1348,9 +1349,9 @@ static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done,
 		if (oidset_contains(blobs_done, oid))
 			continue;
 
-		buf = odb_read_object(the_repository->objects, oid, &type, &size);
+		buf = odb_read_object(repo->objects, oid, &type, &size);
 		if (!buf) {
-			if (is_promisor_object(the_repository, oid))
+			if (is_promisor_object(repo, oid))
 				continue;
 			ret |= report(options,
 				      oid, OBJ_BLOB, msg_missing,
@@ -1372,14 +1373,14 @@ static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done,
 	return ret;
 }
 
-int fsck_finish(struct fsck_options *options)
+int fsck_finish(struct repository *repo, struct fsck_options *options)
 {
 	int ret = 0;
 
-	ret |= fsck_blobs(&options->gitmodules_found, &options->gitmodules_done,
+	ret |= fsck_blobs(repo, &options->gitmodules_found, &options->gitmodules_done,
 			  FSCK_MSG_GITMODULES_MISSING, FSCK_MSG_GITMODULES_BLOB,
 			  options, ".gitmodules");
-	ret |= fsck_blobs(&options->gitattributes_found, &options->gitattributes_done,
+	ret |= fsck_blobs(repo, &options->gitattributes_found, &options->gitattributes_done,
 			  FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
 			  options, ".gitattributes");
 
diff --git a/fsck.h b/fsck.h
index 4bd54865fe..bfe9b3ffc9 100644
--- a/fsck.h
+++ b/fsck.h
@@ -255,7 +255,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
  * after completing all fsck_object() calls in order to resolve any remaining
  * checks.
  */
-int fsck_finish(struct fsck_options *options);
+int fsck_finish(struct repository *repo, struct fsck_options *options);
 
 /*
  * Check whether there are any checks that have been queued up and that still
diff --git a/object-file.c b/object-file.c
index c62e5496e0..ecbcc501dd 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1285,7 +1285,7 @@ static int index_mem(struct index_state *istate,
 		opts.error_func = hash_format_check_report;
 		if (fsck_buffer(null_oid(istate->repo->hash_algo), type, buf, size, &opts))
 			die(_("refusing to create malformed object"));
-		fsck_finish(&opts);
+		fsck_finish(the_repository, &opts);
 	}
 
 	if (write_object)

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 03/14] fsck: refactor interface to parse fsck options
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-3-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-3-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
The function `git_fsck_config()` is supposed to be used as a callback
function for `repo_config()`. It expects the caller to provide a
repository as callback payload, which is easy to miss. Furthermore, in
the next commit we're about to refactor the function to not rely on
global state anymore, which will require callers to pass a different
payload.

Prepare for this change by refactoring the interface to be fully
self-contained. Like this, callers know exactly which parameters are
expected.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c  |  2 +-
 builtin/mktag.c |  3 ++-
 builtin/refs.c  |  2 +-
 fsck.c          | 11 +++++++++--
 fsck.h          |  9 ++++-----
 5 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 8f994010da..fd689a1e14 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -1023,7 +1023,7 @@ int cmd_fsck(int argc,
 	if (name_objects)
 		fsck_enable_object_names(&fsck_walk_options);
 
-	repo_config(the_repository, git_fsck_config, &fsck_obj_options);
+	fsck_options_parse_config(&fsck_obj_options, the_repository);
 	prepare_repo_settings(the_repository);
 
 	if (check_references)
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 7cf6e1230a..2cf843c195 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -98,7 +98,8 @@ int cmd_mktag(int argc,
 	fsck_set_msg_type_from_ids(&fsck_options, FSCK_MSG_EXTRA_HEADER_ENTRY,
 				   FSCK_WARN);
 	/* config might set fsck.extraHeaderEntry=* again */
-	repo_config(the_repository, git_fsck_config, &fsck_options);
+	fsck_options_parse_config(&fsck_options, the_repository);
+
 	if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options,
 				&tagged_oid, &tagged_type))
 		die(_("tag on stdin did not pass our strict fsck check"));
diff --git a/builtin/refs.c b/builtin/refs.c
index 3064f888b2..c0a3b475e9 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -97,7 +97,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	if (argc)
 		usage(_("'git refs verify' takes no arguments"));
 
-	repo_config(the_repository, git_fsck_config, &fsck_refs_options);
+	fsck_options_parse_config(&fsck_refs_options, the_repository);
 	prepare_repo_settings(the_repository);
 
 	worktrees = get_worktrees_without_reading_head();
diff --git a/fsck.c b/fsck.c
index 1f7d568acf..a7ebab9581 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1404,8 +1404,9 @@ void fsck_options_clear(struct fsck_options *options)
 	kh_clear_oid_map(options->object_names);
 }
 
-int git_fsck_config(const char *var, const char *value,
-		    const struct config_context *ctx, void *cb)
+static int fsck_options_parse_config_key(const char *var, const char *value,
+					 const struct config_context *ctx,
+					 void *cb)
 {
 	struct fsck_options *options = cb;
 	const char *msg_id;
@@ -1435,6 +1436,12 @@ int git_fsck_config(const char *var, const char *value,
 	return git_default_config(var, value, ctx, cb);
 }
 
+void fsck_options_parse_config(struct fsck_options *options,
+			       struct repository *repo)
+{
+	repo_config(repo, fsck_options_parse_config_key, options);
+}
+
 /*
  * Custom error callbacks that are used in more than one place.
  */
diff --git a/fsck.h b/fsck.h
index bfe9b3ffc9..7d0d876103 100644
--- a/fsck.h
+++ b/fsck.h
@@ -304,12 +304,11 @@ void fsck_put_object_name(struct fsck_options *options,
 const char *fsck_describe_object(struct fsck_options *options,
 				 const struct object_id *oid);
 
-struct key_value_info;
 /*
- * repo_config() callback for use by fsck-y tools that want to support
- * fsck.<msg> fsck.skipList etc.
+ * Parse fsck options from the gitconfig. This covers settings like for example
+ * fsck.<msg> fsck.skipList.
  */
-int git_fsck_config(const char *var, const char *value,
-		    const struct config_context *ctx, void *cb);
+void fsck_options_parse_config(struct fsck_options *options,
+			       struct repository *repo);
 
 #endif

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 04/14] fsck: drop `the_repository` in `fsck_set_msg_types()`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-4-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-4-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We implicitly rely on `the_repository` in `fsck_set_msg_types()` because
of our reliance on `the_hash_algo`. Fix this dependency on global state
by requiring the callers to provide the hash function.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/index-pack.c     |  6 ++++--
 builtin/unpack-objects.c |  3 ++-
 fsck.c                   | 27 +++++++++++++++++++--------
 fsck.h                   |  3 ++-
 4 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 3d1f7dbd5f..c4117e5970 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1932,13 +1932,15 @@ int cmd_index_pack(int argc,
 			} else if (skip_to_optional_arg(arg, "--strict", &arg)) {
 				strict = 1;
 				do_fsck_object = 1;
-				fsck_set_msg_types(&fsck_options, arg);
+				fsck_set_msg_types(&fsck_options, arg,
+						   the_hash_algo);
 			} else if (!strcmp(arg, "--check-self-contained-and-connected")) {
 				strict = 1;
 				check_self_contained_and_connected = 1;
 			} else if (skip_to_optional_arg(arg, "--fsck-objects", &arg)) {
 				do_fsck_object = 1;
-				fsck_set_msg_types(&fsck_options, arg);
+				fsck_set_msg_types(&fsck_options, arg,
+						   the_hash_algo);
 			} else if (!strcmp(arg, "--verify")) {
 				verify = 1;
 			} else if (!strcmp(arg, "--verify-stat")) {
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 9596b3ea4f..3e748a55d9 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -649,7 +649,8 @@ int cmd_unpack_objects(int argc,
 			}
 			if (skip_prefix(arg, "--strict=", &arg)) {
 				strict = 1;
-				fsck_set_msg_types(&fsck_options, arg);
+				fsck_set_msg_types(&fsck_options, arg,
+						   the_hash_algo);
 				continue;
 			}
 			if (skip_prefix(arg, "--pack_header=", &arg)) {
diff --git a/fsck.c b/fsck.c
index a7ebab9581..efd987dd2b 100644
--- a/fsck.c
+++ b/fsck.c
@@ -182,7 +182,8 @@ void fsck_set_msg_type(struct fsck_options *options,
 	free(to_free);
 }
 
-void fsck_set_msg_types(struct fsck_options *options, const char *values)
+void fsck_set_msg_types(struct fsck_options *options, const char *values,
+			const struct git_hash_algo *algo)
 {
 	char *buf = xstrdup(values), *to_free = buf;
 	int done = 0;
@@ -207,7 +208,7 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
 			if (equal == len)
 				die("skiplist requires a path");
 			oidset_parse_file(&options->skip_oids, buf + equal + 1,
-					  the_repository->hash_algo);
+					  algo);
 			buf += len + 1;
 			continue;
 		}
@@ -1404,11 +1405,16 @@ void fsck_options_clear(struct fsck_options *options)
 	kh_clear_oid_map(options->object_names);
 }
 
+struct fsck_options_parse_config_key_data {
+	struct repository *repo;
+	struct fsck_options *options;
+};
+
 static int fsck_options_parse_config_key(const char *var, const char *value,
 					 const struct config_context *ctx,
-					 void *cb)
+					 void *cb_data)
 {
-	struct fsck_options *options = cb;
+	struct fsck_options_parse_config_key_data *data = cb_data;
 	const char *msg_id;
 
 	if (strcmp(var, "fsck.skiplist") == 0) {
@@ -1420,7 +1426,8 @@ static int fsck_options_parse_config_key(const char *var, const char *value,
 			struct strbuf sb = STRBUF_INIT;
 			strbuf_addf(&sb, "skiplist=%s", path);
 			free(path);
-			fsck_set_msg_types(options, sb.buf);
+			fsck_set_msg_types(data->options, sb.buf,
+					   data->repo->hash_algo);
 			strbuf_release(&sb);
 		}
 		return 0;
@@ -1429,17 +1436,21 @@ static int fsck_options_parse_config_key(const char *var, const char *value,
 	if (skip_prefix(var, "fsck.", &msg_id)) {
 		if (!value)
 			return config_error_nonbool(var);
-		fsck_set_msg_type(options, msg_id, value);
+		fsck_set_msg_type(data->options, msg_id, value);
 		return 0;
 	}
 
-	return git_default_config(var, value, ctx, cb);
+	return git_default_config(var, value, ctx, cb_data);
 }
 
 void fsck_options_parse_config(struct fsck_options *options,
 			       struct repository *repo)
 {
-	repo_config(repo, fsck_options_parse_config_key, options);
+	struct fsck_options_parse_config_key_data data = {
+		.repo = repo,
+		.options = options,
+	};
+	repo_config(repo, fsck_options_parse_config_key, &data);
 }
 
 /*
diff --git a/fsck.h b/fsck.h
index 7d0d876103..3ca1683f74 100644
--- a/fsck.h
+++ b/fsck.h
@@ -118,7 +118,8 @@ void fsck_set_msg_type_from_ids(struct fsck_options *options,
 				enum fsck_msg_type msg_type);
 void fsck_set_msg_type(struct fsck_options *options,
 		       const char *msg_id, const char *msg_type);
-void fsck_set_msg_types(struct fsck_options *options, const char *values);
+void fsck_set_msg_types(struct fsck_options *options, const char *values,
+			const struct git_hash_algo *algo);
 int is_valid_msg_type(const char *msg_id, const char *msg_type);
 
 /*

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 05/14] fsck: stop relying on global state via `parse_oid_hex()`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-5-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-5-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
The function `parse_oid_hex()` implicitly relies on global state, as it
uses the hash algorithm of `the_repository` to parse the input string.
Refactor "fsck.c" to use `parse_oid_hex_algop()` instead and make the
repository available via parameters as required.

This was the last dependency on `the_repository` in "fsck.c", so we can
now drop `USE_THE_REPOSITORY_VARIABLE`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c           |  2 +-
 builtin/index-pack.c     |  5 +++--
 builtin/mktag.c          |  4 ++--
 builtin/unpack-objects.c |  2 +-
 fsck.c                   | 33 ++++++++++++++++++---------------
 fsck.h                   | 11 +++++++----
 object-file.c            |  3 ++-
 7 files changed, 34 insertions(+), 26 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index fd689a1e14..ff9951c10c 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -415,7 +415,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
 
 	if (fsck_walk(the_repository, obj, NULL, &fsck_obj_options))
 		objerror(obj, _("broken links"));
-	err = fsck_object(obj, buffer, size, &fsck_obj_options);
+	err = fsck_object(the_repository, obj, buffer, size, &fsck_obj_options);
 	if (err)
 		goto out;
 
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index c4117e5970..031e4697d9 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -933,7 +933,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
 			else
 				die(_("invalid blob object %s"), oid_to_hex(oid));
 			if (do_fsck_object &&
-			    fsck_object(&blob->object, (void *)data, size, &fsck_options))
+			    fsck_object(the_repository, &blob->object, (void *)data,
+					size, &fsck_options))
 				die(_("fsck error in packed object"));
 		} else {
 			struct object *obj;
@@ -952,7 +953,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
 			if (!obj)
 				die(_("invalid %s"), type_name(type));
 			if (do_fsck_object &&
-			    fsck_object(obj, buf, size, &fsck_options))
+			    fsck_object(the_repository, obj, buf, size, &fsck_options))
 				die(_("fsck error in packed object"));
 			if (strict && fsck_walk(the_repository, obj, NULL, &fsck_options))
 				die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid));
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 2cf843c195..1925f04321 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -100,8 +100,8 @@ int cmd_mktag(int argc,
 	/* config might set fsck.extraHeaderEntry=* again */
 	fsck_options_parse_config(&fsck_options, the_repository);
 
-	if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options,
-				&tagged_oid, &tagged_type))
+	if (fsck_tag_standalone(the_repository, NULL, buf.buf, buf.len,
+				&fsck_options, &tagged_oid, &tagged_type))
 		die(_("tag on stdin did not pass our strict fsck check"));
 
 	if (verify_object_in_tag(&tagged_oid, &tagged_type) < 0)
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 3e748a55d9..dadc2ba65f 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -241,7 +241,7 @@ static int check_object(struct object *obj, enum object_type type,
 	obj_buf = lookup_object_buffer(obj);
 	if (!obj_buf)
 		die("Whoops! Cannot find object '%s'", oid_to_hex(&obj->oid));
-	if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options))
+	if (fsck_object(the_repository, obj, obj_buf->buffer, obj_buf->size, &fsck_options))
 		die("fsck error in packed object");
 	fsck_options.walk = check_object;
 	if (fsck_walk(the_repository, obj, NULL, &fsck_options))
diff --git a/fsck.c b/fsck.c
index efd987dd2b..edf7a0618f 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "git-compat-util.h"
 #include "date.h"
 #include "dir.h"
@@ -962,7 +960,8 @@ static int fsck_ident(const char **ident, const char *ident_end,
 	return 0;
 }
 
-static int fsck_commit(const struct object_id *oid,
+static int fsck_commit(struct repository *repo,
+		       const struct object_id *oid,
 		       const char *buffer, unsigned long size,
 		       struct fsck_options *options)
 {
@@ -983,14 +982,14 @@ static int fsck_commit(const struct object_id *oid,
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "tree ", &buffer))
 		return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
-	if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') {
+	if (parse_oid_hex_algop(buffer, &tree_oid, &p, repo->hash_algo) || *p != '\n') {
 		err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
 		if (err)
 			return err;
 	}
 	buffer = p + 1;
 	while (buffer < buffer_end && skip_prefix(buffer, "parent ", &buffer)) {
-		if (parse_oid_hex(buffer, &parent_oid, &p) || *p != '\n') {
+		if (parse_oid_hex_algop(buffer, &parent_oid, &p, repo->hash_algo) || *p != '\n') {
 			err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
 			if (err)
 				return err;
@@ -1024,16 +1023,18 @@ static int fsck_commit(const struct object_id *oid,
 	return 0;
 }
 
-static int fsck_tag(const struct object_id *oid, const char *buffer,
+static int fsck_tag(struct repository *repo,
+		    const struct object_id *oid, const char *buffer,
 		    unsigned long size, struct fsck_options *options)
 {
 	struct object_id tagged_oid;
 	int tagged_type;
-	return fsck_tag_standalone(oid, buffer, size, options, &tagged_oid,
-				   &tagged_type);
+	return fsck_tag_standalone(repo, oid, buffer, size, options,
+				   &tagged_oid, &tagged_type);
 }
 
-int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
+int fsck_tag_standalone(struct repository *repo,
+			const struct object_id *oid, const char *buffer,
 			unsigned long size, struct fsck_options *options,
 			struct object_id *tagged_oid,
 			int *tagged_type)
@@ -1057,7 +1058,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
 		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line");
 		goto done;
 	}
-	if (parse_oid_hex(buffer, tagged_oid, &p) || *p != '\n') {
+	if (parse_oid_hex_algop(buffer, tagged_oid, &p, repo->hash_algo) || *p != '\n') {
 		ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1");
 		if (ret)
 			goto done;
@@ -1266,16 +1267,18 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
 	return ret;
 }
 
-int fsck_object(struct object *obj, void *data, unsigned long size,
+int fsck_object(struct repository *repo,
+		struct object *obj, void *data, unsigned long size,
 	struct fsck_options *options)
 {
 	if (!obj)
 		return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
 
-	return fsck_buffer(&obj->oid, obj->type, data, size, options);
+	return fsck_buffer(repo, &obj->oid, obj->type, data, size, options);
 }
 
-int fsck_buffer(const struct object_id *oid, enum object_type type,
+int fsck_buffer(struct repository *repo,
+		const struct object_id *oid, enum object_type type,
 		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
@@ -1284,9 +1287,9 @@ int fsck_buffer(const struct object_id *oid, enum object_type type,
 	if (type == OBJ_TREE)
 		return fsck_tree(oid, data, size, options);
 	if (type == OBJ_COMMIT)
-		return fsck_commit(oid, data, size, options);
+		return fsck_commit(repo, oid, data, size, options);
 	if (type == OBJ_TAG)
-		return fsck_tag(oid, data, size, options);
+		return fsck_tag(repo, oid, data, size, options);
 
 	return report(options, oid, type,
 		      FSCK_MSG_UNKNOWN_TYPE,
diff --git a/fsck.h b/fsck.h
index 3ca1683f74..62b7c3c93a 100644
--- a/fsck.h
+++ b/fsck.h
@@ -231,14 +231,16 @@ int fsck_walk(struct repository *repo,
  * Blob objects my pass a NULL data pointer, which indicates they are too large
  * to fit in memory. All other types must pass a real buffer.
  */
-int fsck_object(struct object *obj, void *data, unsigned long size,
-	struct fsck_options *options);
+int fsck_object(struct repository *repo,
+		struct object *obj, void *data, unsigned long size,
+		struct fsck_options *options);
 
 /*
  * Same as fsck_object(), but for when the caller doesn't have an object
  * struct.
  */
-int fsck_buffer(const struct object_id *oid, enum object_type,
+int fsck_buffer(struct repository *repo,
+		const struct object_id *oid, enum object_type,
 		const void *data, unsigned long size,
 		struct fsck_options *options);
 
@@ -246,7 +248,8 @@ int fsck_buffer(const struct object_id *oid, enum object_type,
  * fsck a tag, and pass info about it back to the caller. This is
  * exposed fsck_object() internals for git-mktag(1).
  */
-int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
+int fsck_tag_standalone(struct repository *repo,
+			const struct object_id *oid, const char *buffer,
 			unsigned long size, struct fsck_options *options,
 			struct object_id *tagged_oid,
 			int *tag_type);
diff --git a/object-file.c b/object-file.c
index ecbcc501dd..f58acaa939 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1283,7 +1283,8 @@ static int index_mem(struct index_state *istate,
 
 		opts.strict = 1;
 		opts.error_func = hash_format_check_report;
-		if (fsck_buffer(null_oid(istate->repo->hash_algo), type, buf, size, &opts))
+		if (fsck_buffer(the_repository, null_oid(istate->repo->hash_algo),
+				type, buf, size, &opts))
 			die(_("refusing to create malformed object"));
 		fsck_finish(the_repository, &opts);
 	}

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 06/14] builtin/fsck: fix trivial dependence on `the_repository`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-6-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-6-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We have a bunch of sites in "builtin/fsck.c" that depend on
`the_repository` even though we already have a repository available, or
in cases where we can trivially make it available.

Refactor such sites to use the context-provided repository instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 116 +++++++++++++++++++++++++++++----------------------------
 1 file changed, 59 insertions(+), 57 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index ff9951c10c..8a52ad02ae 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -184,9 +184,9 @@ static void mark_object_reachable(struct object *obj)
 	mark_object(obj, OBJ_ANY, NULL, NULL);
 }
 
-static int traverse_one_object(struct object *obj)
+static int traverse_one_object(struct repository *repo, struct object *obj)
 {
-	int result = fsck_walk(the_repository, obj, obj, &fsck_walk_options);
+	int result = fsck_walk(repo, obj, obj, &fsck_walk_options);
 
 	if (obj->type == OBJ_TREE) {
 		struct tree *tree = (struct tree *)obj;
@@ -195,16 +195,16 @@ static int traverse_one_object(struct object *obj)
 	return result;
 }
 
-static int traverse_reachable(void)
+static int traverse_reachable(struct repository *repo)
 {
 	struct progress *progress = NULL;
 	unsigned int nr = 0;
 	int result = 0;
 	if (show_progress)
-		progress = start_delayed_progress(the_repository,
+		progress = start_delayed_progress(repo,
 						  _("Checking connectivity"), 0);
 	while (pending.nr) {
-		result |= traverse_one_object(object_array_pop(&pending));
+		result |= traverse_one_object(repo, object_array_pop(&pending));
 		display_progress(progress, ++nr);
 	}
 	stop_progress(&progress);
@@ -254,7 +254,7 @@ static int mark_unreachable_referents(const struct object_id *oid,
 /*
  * Check a single reachable object
  */
-static void check_reachable_object(struct object *obj)
+static void check_reachable_object(struct repository *repo, struct object *obj)
 {
 	/*
 	 * We obviously want the object to be parsed,
@@ -262,9 +262,9 @@ static void check_reachable_object(struct object *obj)
 	 * do a full fsck
 	 */
 	if (!(obj->flags & HAS_OBJ)) {
-		if (is_promisor_object(the_repository, &obj->oid))
+		if (is_promisor_object(repo, &obj->oid))
 			return;
-		if (has_object_pack(the_repository, &obj->oid))
+		if (has_object_pack(repo, &obj->oid))
 			return; /* it is in pack - forget about it */
 		printf_ln(_("missing %s %s"),
 			  printable_type(&obj->oid, obj->type),
@@ -277,7 +277,7 @@ static void check_reachable_object(struct object *obj)
 /*
  * Check a single unreachable object
  */
-static void check_unreachable_object(struct object *obj)
+static void check_unreachable_object(struct repository *repo, struct object *obj)
 {
 	/*
 	 * Missing unreachable object? Ignore it. It's not like
@@ -317,19 +317,19 @@ static void check_unreachable_object(struct object *obj)
 				  printable_type(&obj->oid, obj->type),
 				  describe_object(&obj->oid));
 		if (write_lost_and_found) {
-			char *filename = repo_git_path(the_repository, "lost-found/%s/%s",
+			char *filename = repo_git_path(repo, "lost-found/%s/%s",
 				obj->type == OBJ_COMMIT ? "commit" : "other",
 				describe_object(&obj->oid));
 			FILE *f;
 
-			if (safe_create_leading_directories_const(the_repository, filename)) {
+			if (safe_create_leading_directories_const(repo, filename)) {
 				error(_("could not create lost-found"));
 				free(filename);
 				return;
 			}
 			f = xfopen(filename, "w");
 			if (obj->type == OBJ_BLOB) {
-				if (odb_stream_blob_to_fd(the_repository->objects, fileno(f),
+				if (odb_stream_blob_to_fd(repo->objects, fileno(f),
 							  &obj->oid, NULL, 1))
 					die_errno(_("could not write '%s'"), filename);
 			} else
@@ -349,23 +349,23 @@ static void check_unreachable_object(struct object *obj)
 	 */
 }
 
-static void check_object(struct object *obj)
+static void check_object(struct repository *repo, struct object *obj)
 {
 	if (verbose)
 		fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid));
 
 	if (obj->flags & REACHABLE)
-		check_reachable_object(obj);
+		check_reachable_object(repo, obj);
 	else
-		check_unreachable_object(obj);
+		check_unreachable_object(repo, obj);
 }
 
-static void check_connectivity(void)
+static void check_connectivity(struct repository *repo)
 {
 	int i, max;
 
 	/* Traverse the pending reachable objects */
-	traverse_reachable();
+	traverse_reachable(repo);
 
 	/*
 	 * With --connectivity-only, we won't have actually opened and marked
@@ -383,20 +383,20 @@ static void check_connectivity(void)
 		 * and ignore any that weren't present in our earlier
 		 * traversal.
 		 */
-		odb_for_each_object(the_repository->objects, NULL,
+		odb_for_each_object(repo->objects, NULL,
 				    mark_unreachable_referents, NULL, 0);
 	}
 
 	/* Look up all the requirements, warn about missing objects.. */
-	max = get_max_object_index(the_repository);
+	max = get_max_object_index(repo);
 	if (verbose)
 		fprintf_ln(stderr, _("Checking connectivity (%d objects)"), max);
 
 	for (i = 0; i < max; i++) {
-		struct object *obj = get_indexed_object(the_repository, i);
+		struct object *obj = get_indexed_object(repo, i);
 
 		if (obj)
-			check_object(obj);
+			check_object(repo, obj);
 	}
 }
 
@@ -580,7 +580,8 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
 	return 0;
 }
 
-static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
+static void snapshot_refs(struct repository *repo, struct snapshot *snap,
+			  int argc, const char **argv)
 {
 	struct refs_for_each_ref_options opts = {
 		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
@@ -592,7 +593,7 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
 	for (int i = 0; i < argc; i++) {
 		const char *arg = argv[i];
 		struct object_id oid;
-		if (!repo_get_oid(the_repository, arg, &oid)) {
+		if (!repo_get_oid(repo, arg, &oid)) {
 			struct reference ref = {
 				.name = arg,
 				.oid = &oid,
@@ -610,7 +611,7 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
 		return;
 	}
 
-	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+	refs_for_each_ref_ext(get_main_ref_store(repo),
 			      snapshot_ref, snap, &opts);
 
 	worktrees = get_worktrees();
@@ -620,7 +621,7 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
 
 		strbuf_worktree_ref(wt, &refname, "HEAD");
 
-		head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+		head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(repo),
 							 refname.buf, 0, &head_oid, NULL);
 
 		if (head_points_at && !is_null_oid(&head_oid)) {
@@ -780,7 +781,7 @@ static void fsck_source(struct odb_source *source)
 		fprintf_ln(stderr, _("Checking object directory"));
 
 	if (show_progress)
-		progress = start_progress(the_repository,
+		progress = start_progress(source->odb->repo,
 					  _("Checking object directories"), 256);
 
 	for_each_loose_file_in_source(source, fsck_loose,
@@ -789,7 +790,8 @@ static void fsck_source(struct odb_source *source)
 	stop_progress(&progress);
 }
 
-static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
+static int fsck_cache_tree(struct repository *repo, struct cache_tree *it,
+			   const char *index_path)
 {
 	int i;
 	int err = 0;
@@ -798,7 +800,7 @@ static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
 		fprintf_ln(stderr, _("Checking cache tree of %s"), index_path);
 
 	if (0 <= it->entry_count) {
-		struct object *obj = parse_object(the_repository, &it->oid);
+		struct object *obj = parse_object(repo, &it->oid);
 		if (!obj) {
 			error(_("%s: invalid sha1 pointer in cache-tree of %s"),
 			      oid_to_hex(&it->oid), index_path);
@@ -812,7 +814,7 @@ static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
 			err |= objerror(obj, _("non-tree in cache-tree"));
 	}
 	for (i = 0; i < it->subtree_nr; i++)
-		err |= fsck_cache_tree(it->down[i]->cache_tree, index_path);
+		err |= fsck_cache_tree(repo, it->down[i]->cache_tree, index_path);
 	return err;
 }
 
@@ -838,7 +840,7 @@ static int fsck_resolve_undo(struct index_state *istate,
 			if (!ru->mode[i] || !S_ISREG(ru->mode[i]))
 				continue;
 
-			obj = parse_object(the_repository, &ru->oid[i]);
+			obj = parse_object(istate->repo, &ru->oid[i]);
 			if (!obj) {
 				error(_("%s: invalid sha1 pointer in resolve-undo of %s"),
 				      oid_to_hex(&ru->oid[i]),
@@ -870,7 +872,7 @@ static void fsck_index(struct index_state *istate, const char *index_path,
 		mode = istate->cache[i]->ce_mode;
 		if (S_ISGITLINK(mode))
 			continue;
-		blob = lookup_blob(the_repository,
+		blob = lookup_blob(istate->repo,
 				   &istate->cache[i]->oid);
 		if (!blob)
 			continue;
@@ -883,7 +885,7 @@ static void fsck_index(struct index_state *istate, const char *index_path,
 		mark_object_reachable(obj);
 	}
 	if (istate->cache_tree)
-		fsck_cache_tree(istate->cache_tree, index_path);
+		fsck_cache_tree(istate->repo, istate->cache_tree, index_path);
 	fsck_resolve_undo(istate, index_path);
 }
 
@@ -906,7 +908,7 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
 	if (show_progress) {
 		repo_for_each_pack(r, p)
 			pack_count++;
-		progress = start_delayed_progress(the_repository,
+		progress = start_delayed_progress(r,
 						  "Verifying reverse pack-indexes", pack_count);
 		pack_count = 0;
 	}
@@ -986,7 +988,7 @@ static struct option fsck_opts[] = {
 int cmd_fsck(int argc,
 	     const char **argv,
 	     const char *prefix,
-	     struct repository *repo UNUSED)
+	     struct repository *repo)
 {
 	struct odb_source *source;
 	struct snapshot snap = {
@@ -1023,11 +1025,11 @@ int cmd_fsck(int argc,
 	if (name_objects)
 		fsck_enable_object_names(&fsck_walk_options);
 
-	fsck_options_parse_config(&fsck_obj_options, the_repository);
-	prepare_repo_settings(the_repository);
+	fsck_options_parse_config(&fsck_obj_options, repo);
+	prepare_repo_settings(repo);
 
 	if (check_references)
-		fsck_refs(the_repository);
+		fsck_refs(repo);
 
 	/*
 	 * Take a snapshot of the refs before walking objects to avoid looking
@@ -1035,17 +1037,17 @@ int cmd_fsck(int argc,
 	 * objects. We can still walk over new objects that are added during the
 	 * execution of fsck but won't miss any objects that were reachable.
 	 */
-	snapshot_refs(&snap, argc, argv);
+	snapshot_refs(repo, &snap, argc, argv);
 
 	/* Ensure we get a "fresh" view of the odb */
-	odb_reprepare(the_repository->objects);
+	odb_reprepare(repo->objects);
 
 	if (connectivity_only) {
-		odb_for_each_object(the_repository->objects, NULL,
+		odb_for_each_object(repo->objects, NULL,
 				    mark_object_for_connectivity, NULL, 0);
 	} else {
-		odb_prepare_alternates(the_repository->objects);
-		for (source = the_repository->objects->sources; source; source = source->next)
+		odb_prepare_alternates(repo->objects);
+		for (source = repo->objects->sources; source; source = source->next)
 			fsck_source(source);
 
 		if (check_full) {
@@ -1054,19 +1056,19 @@ int cmd_fsck(int argc,
 			struct progress *progress = NULL;
 
 			if (show_progress) {
-				repo_for_each_pack(the_repository, p) {
+				repo_for_each_pack(repo, p) {
 					if (open_pack_index(p))
 						continue;
 					total += p->num_objects;
 				}
 
-				progress = start_progress(the_repository,
+				progress = start_progress(repo,
 							  _("Checking objects"), total);
 			}
 
-			repo_for_each_pack(the_repository, p) {
+			repo_for_each_pack(repo, p) {
 				/* verify gives error messages itself */
-				if (verify_pack(the_repository,
+				if (verify_pack(repo,
 						p, fsck_obj_buffer,
 						progress, count))
 					errors_found |= ERROR_PACK;
@@ -1075,7 +1077,7 @@ int cmd_fsck(int argc,
 			stop_progress(&progress);
 		}
 
-		if (fsck_finish(the_repository, &fsck_obj_options))
+		if (fsck_finish(repo, &fsck_obj_options))
 			errors_found |= ERROR_OBJECT;
 	}
 
@@ -1100,7 +1102,7 @@ int cmd_fsck(int argc,
 		for (p = worktrees; *p; p++) {
 			struct worktree *wt = *p;
 			struct index_state istate =
-				INDEX_STATE_INIT(the_repository);
+				INDEX_STATE_INIT(repo);
 			char *path, *wt_gitdir;
 
 			/*
@@ -1121,17 +1123,17 @@ int cmd_fsck(int argc,
 		free_worktrees(worktrees);
 	}
 
-	errors_found |= check_pack_rev_indexes(the_repository, show_progress);
-	if (verify_bitmap_files(the_repository))
+	errors_found |= check_pack_rev_indexes(repo, show_progress);
+	if (verify_bitmap_files(repo))
 		errors_found |= ERROR_BITMAP;
 
-	check_connectivity();
+	check_connectivity(repo);
 
-	if (the_repository->settings.core_commit_graph) {
+	if (repo->settings.core_commit_graph) {
 		struct child_process commit_graph_verify = CHILD_PROCESS_INIT;
 
-		odb_prepare_alternates(the_repository->objects);
-		for (source = the_repository->objects->sources; source; source = source->next) {
+		odb_prepare_alternates(repo->objects);
+		for (source = repo->objects->sources; source; source = source->next) {
 			child_process_init(&commit_graph_verify);
 			commit_graph_verify.git_cmd = 1;
 			strvec_pushl(&commit_graph_verify.args, "commit-graph",
@@ -1145,11 +1147,11 @@ int cmd_fsck(int argc,
 		}
 	}
 
-	if (the_repository->settings.core_multi_pack_index) {
+	if (repo->settings.core_multi_pack_index) {
 		struct child_process midx_verify = CHILD_PROCESS_INIT;
 
-		odb_prepare_alternates(the_repository->objects);
-		for (source = the_repository->objects->sources; source; source = source->next) {
+		odb_prepare_alternates(repo->objects);
+		for (source = repo->objects->sources; source; source = source->next) {
 			child_process_init(&midx_verify);
 			midx_verify.git_cmd = 1;
 			strvec_pushl(&midx_verify.args, "multi-pack-index",

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 07/14] builtin/fsck: stop using `the_repository` when snapshotting refs
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-7-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-7-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We depedn on `the_repository` when snapshotting refs. Refactor this to
use a context-provided repository instead that is injected via the
`struct snapshot_ref_data`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 8a52ad02ae..00476bb921 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -532,14 +532,20 @@ struct snapshot {
 	/* TODO: Consider also snapshotting the index of each worktree. */
 };
 
+struct snapshot_ref_data {
+	struct repository *repo;
+	struct snapshot *snap;
+};
+
 static int snapshot_ref(const struct reference *ref, void *cb_data)
 {
-	struct snapshot *snap = cb_data;
+	struct snapshot_ref_data *data = cb_data;
+	struct snapshot *snap = data->snap;
 	struct object *obj;
 
-	obj = parse_object(the_repository, ref->oid);
+	obj = parse_object(data->repo, ref->oid);
 	if (!obj) {
-		if (is_promisor_object(the_repository, ref->oid)) {
+		if (is_promisor_object(data->repo, ref->oid)) {
 			/*
 			 * Increment default_refs anyway, because this is a
 			 * valid ref.
@@ -586,6 +592,10 @@ static void snapshot_refs(struct repository *repo, struct snapshot *snap,
 	struct refs_for_each_ref_options opts = {
 		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
 	};
+	struct snapshot_ref_data data = {
+		.repo = repo,
+		.snap = snap,
+	};
 	struct worktree **worktrees, **p;
 	const char *head_points_at;
 	struct object_id head_oid;
@@ -599,7 +609,7 @@ static void snapshot_refs(struct repository *repo, struct snapshot *snap,
 				.oid = &oid,
 			};
 
-			snapshot_ref(&ref, snap);
+			snapshot_ref(&ref, &data);
 			continue;
 		}
 		error(_("invalid parameter: expected sha1, got '%s'"), arg);
@@ -612,7 +622,7 @@ static void snapshot_refs(struct repository *repo, struct snapshot *snap,
 	}
 
 	refs_for_each_ref_ext(get_main_ref_store(repo),
-			      snapshot_ref, snap, &opts);
+			      snapshot_ref, &data, &opts);
 
 	worktrees = get_worktrees();
 	for (p = worktrees; *p; p++) {
@@ -630,7 +640,7 @@ static void snapshot_refs(struct repository *repo, struct snapshot *snap,
 				.oid = &head_oid,
 			};
 
-			snapshot_ref(&ref, snap);
+			snapshot_ref(&ref, &data);
 		}
 		strbuf_release(&refname);
 

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 08/14] builtin/fsck: stop using `the_repository` when checking refs
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-8-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-8-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We implicitly rely on `the_repository` when checking refs. Refactor this
to instead inject the repository via the callback payload.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 00476bb921..63cd58e58b 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -573,11 +573,12 @@ static int snapshot_ref(const struct reference *ref, void *cb_data)
 	return 0;
 }
 
-static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
+static int fsck_handle_ref(const struct reference *ref, void *cb_data)
 {
+	struct repository *repo = cb_data;
 	struct object *obj;
 
-	obj = parse_object(the_repository, ref->oid);
+	obj = parse_object(repo, ref->oid);
 	obj->flags |= USED;
 	fsck_put_object_name(&fsck_walk_options,
 			     ref->oid, "%s", ref->name);
@@ -664,7 +665,7 @@ static void free_snapshot_refs(struct snapshot *snap)
 	free(snap->ref);
 }
 
-static void process_refs(struct snapshot *snap)
+static void process_refs(struct repository *repo, struct snapshot *snap)
 {
 	struct worktree **worktrees, **p;
 
@@ -673,7 +674,7 @@ static void process_refs(struct snapshot *snap)
 			.name = snap->ref[i].refname,
 			.oid = &snap->ref[i].oid,
 		};
-		fsck_handle_ref(&ref, NULL);
+		fsck_handle_ref(&ref, repo);
 	}
 
 	if (include_reflogs) {
@@ -1092,7 +1093,7 @@ int cmd_fsck(int argc,
 	}
 
 	/* Process the snapshotted refs and the reflogs. */
-	process_refs(&snap);
+	process_refs(repo, &snap);
 
 	/* If not given any explicit objects, process index files too. */
 	if (!argc)

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 09/14] builtin/fsck: stop using `the_repository` when checking reflogs
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-9-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-9-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We implicitly rely on `the_repository` when checking reflogs. Refactor
this to instead inject the repository via the callback payload.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 63cd58e58b..ea441b072e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -467,13 +467,14 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
 
 static int default_refs;
 
-static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
-	timestamp_t timestamp)
+static void fsck_handle_reflog_oid(struct repository *repo,
+				   const char *refname, struct object_id *oid,
+				   timestamp_t timestamp)
 {
 	struct object *obj;
 
 	if (!is_null_oid(oid)) {
-		obj = lookup_object(the_repository, oid);
+		obj = lookup_object(repo, oid);
 		if (obj && (obj->flags & HAS_OBJ)) {
 			if (timestamp)
 				fsck_put_object_name(&fsck_walk_options, oid,
@@ -481,7 +482,7 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
 						     refname, timestamp);
 			obj->flags |= USED;
 			mark_object_reachable(obj);
-		} else if (!is_promisor_object(the_repository, oid)) {
+		} else if (!is_promisor_object(repo, oid)) {
 			error(_("%s: invalid reflog entry %s"),
 			      refname, oid_to_hex(oid));
 			errors_found |= ERROR_REACHABLE;
@@ -493,8 +494,10 @@ static int fsck_handle_reflog_ent(const char *refname,
 				  struct object_id *ooid, struct object_id *noid,
 				  const char *email UNUSED,
 				  timestamp_t timestamp, int tz UNUSED,
-				  const char *message UNUSED, void *cb_data UNUSED)
+				  const char *message UNUSED, void *cb_data)
 {
+	struct repository *repo = cb_data;
+
 	if (now && timestamp > now)
 		return 0;
 
@@ -502,19 +505,20 @@ static int fsck_handle_reflog_ent(const char *refname,
 		fprintf_ln(stderr, _("Checking reflog %s->%s"),
 			   oid_to_hex(ooid), oid_to_hex(noid));
 
-	fsck_handle_reflog_oid(refname, ooid, 0);
-	fsck_handle_reflog_oid(refname, noid, timestamp);
+	fsck_handle_reflog_oid(repo, refname, ooid, 0);
+	fsck_handle_reflog_oid(repo, refname, noid, timestamp);
 	return 0;
 }
 
 static int fsck_handle_reflog(const char *logname, void *cb_data)
 {
 	struct strbuf refname = STRBUF_INIT;
+	struct worktree *wt = cb_data;
 
-	strbuf_worktree_ref(cb_data, &refname, logname);
-	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+	strbuf_worktree_ref(wt, &refname, logname);
+	refs_for_each_reflog_ent(get_main_ref_store(wt->repo),
 				 refname.buf, fsck_handle_reflog_ent,
-				 NULL);
+				 wt->repo);
 	strbuf_release(&refname);
 	return 0;
 }

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 10/14] builtin/fsck: stop using `the_repository` with loose objects
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-10-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-10-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We depend on `the_repository` when performing consistency checks for
loose objects. Refactor this to use a context-provided repository
instead that is injected via the `struct for_each_loose_cb`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index ea441b072e..83299c583e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -400,7 +400,9 @@ static void check_connectivity(struct repository *repo)
 	}
 }
 
-static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
+static int fsck_obj(struct repository *repo,
+		    struct object *obj,
+		    void *buffer, unsigned long size)
 {
 	int err;
 
@@ -413,9 +415,9 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
 			   printable_type(&obj->oid, obj->type),
 			   describe_object(&obj->oid));
 
-	if (fsck_walk(the_repository, obj, NULL, &fsck_obj_options))
+	if (fsck_walk(repo, obj, NULL, &fsck_obj_options))
 		objerror(obj, _("broken links"));
-	err = fsck_object(the_repository, obj, buffer, size, &fsck_obj_options);
+	err = fsck_object(repo, obj, buffer, size, &fsck_obj_options);
 	if (err)
 		goto out;
 
@@ -462,7 +464,7 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
 	}
 	obj->flags &= ~(REACHABLE | SEEN);
 	obj->flags |= HAS_OBJ;
-	return fsck_obj(obj, buffer, size);
+	return fsck_obj(the_repository, obj, buffer, size);
 }
 
 static int default_refs;
@@ -710,27 +712,28 @@ static void process_refs(struct repository *repo, struct snapshot *snap)
 	}
 }
 
-struct for_each_loose_cb
-{
+struct for_each_loose_cb {
+	struct repository *repo;
 	struct progress *progress;
 };
 
 static int fsck_loose(const struct object_id *oid, const char *path,
-		      void *data UNUSED)
+		      void *cb_data)
 {
+	struct for_each_loose_cb *data = cb_data;
 	struct object *obj;
 	enum object_type type = OBJ_NONE;
 	unsigned long size;
 	void *contents = NULL;
 	int eaten;
 	struct object_info oi = OBJECT_INFO_INIT;
-	struct object_id real_oid = *null_oid(the_hash_algo);
+	struct object_id real_oid = *null_oid(data->repo->hash_algo);
 	int err = 0;
 
 	oi.sizep = &size;
 	oi.typep = &type;
 
-	if (read_loose_object(the_repository, path, oid, &real_oid, &contents, &oi) < 0) {
+	if (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {
 		if (contents && !oideq(&real_oid, oid))
 			err = error(_("%s: hash-path mismatch, found at: %s"),
 				    oid_to_hex(&real_oid), path);
@@ -747,7 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,
 	if (!contents && type != OBJ_BLOB)
 		BUG("read_loose_object streamed a non-blob");
 
-	obj = parse_object_buffer(the_repository, oid, type, size,
+	obj = parse_object_buffer(data->repo, oid, type, size,
 				  contents, &eaten);
 
 	if (!obj) {
@@ -761,7 +764,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,
 
 	obj->flags &= ~(REACHABLE | SEEN);
 	obj->flags |= HAS_OBJ;
-	if (fsck_obj(obj, contents, size))
+	if (fsck_obj(data->repo, obj, contents, size))
 		errors_found |= ERROR_OBJECT;
 
 	if (!eaten)
@@ -789,6 +792,7 @@ static void fsck_source(struct odb_source *source)
 {
 	struct progress *progress = NULL;
 	struct for_each_loose_cb cb_data = {
+		.repo = source->odb->repo,
 		.progress = progress,
 	};
 

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 11/14] builtin/fsck: stop using `the_repository` when checking packed objects
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-11-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-11-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We implicitly rely on `the_repository` when checking objects part of a
packfile. These objects are iterated over via `verify_pack()`, which is
provided by the packfile subsystem, and a callback function is then
invoked for each of the objects in that specific pack.

Unfortunately, it is not possible to provide a payload to the callback
function. Refactor `verify_pack()` to accept a payload that is passed
through to the callback so that we can inject the repository and get rid
of the use of `the_repository`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 13 +++++++------
 pack-check.c   |  7 ++++---
 pack.h         |  9 +++++++--
 3 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 83299c583e..874af76e05 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -448,15 +448,16 @@ static int fsck_obj(struct repository *repo,
 }
 
 static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
-			   unsigned long size, void *buffer, int *eaten)
+			   unsigned long size, void *buffer, int *eaten, void *cb_data)
 {
+	struct repository *repo = cb_data;
+	struct object *obj;
+
 	/*
 	 * Note, buffer may be NULL if type is OBJ_BLOB. See
 	 * verify_packfile(), data_valid variable for details.
 	 */
-	struct object *obj;
-	obj = parse_object_buffer(the_repository, oid, type, size, buffer,
-				  eaten);
+	obj = parse_object_buffer(repo, oid, type, size, buffer, eaten);
 	if (!obj) {
 		errors_found |= ERROR_OBJECT;
 		return error(_("%s: object corrupt or missing"),
@@ -464,7 +465,7 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
 	}
 	obj->flags &= ~(REACHABLE | SEEN);
 	obj->flags |= HAS_OBJ;
-	return fsck_obj(the_repository, obj, buffer, size);
+	return fsck_obj(repo, obj, buffer, size);
 }
 
 static int default_refs;
@@ -1088,7 +1089,7 @@ int cmd_fsck(int argc,
 			repo_for_each_pack(repo, p) {
 				/* verify gives error messages itself */
 				if (verify_pack(repo,
-						p, fsck_obj_buffer,
+						p, fsck_obj_buffer, repo,
 						progress, count))
 					errors_found |= ERROR_PACK;
 				count += p->num_objects;
diff --git a/pack-check.c b/pack-check.c
index 7378c80730..79992bb509 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -53,6 +53,7 @@ static int verify_packfile(struct repository *r,
 			   struct packed_git *p,
 			   struct pack_window **w_curs,
 			   verify_fn fn,
+			   void *fn_data,
 			   struct progress *progress, uint32_t base_count)
 
 {
@@ -161,7 +162,7 @@ static int verify_packfile(struct repository *r,
 				    oid_to_hex(&oid), p->pack_name);
 		else if (fn) {
 			int eaten = 0;
-			err |= fn(&oid, type, size, data, &eaten);
+			err |= fn(&oid, type, size, data, &eaten, fn_data);
 			if (eaten)
 				data = NULL;
 		}
@@ -192,7 +193,7 @@ int verify_pack_index(struct packed_git *p)
 	return err;
 }
 
-int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn,
+int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn, void *fn_data,
 		struct progress *progress, uint32_t base_count)
 {
 	int err = 0;
@@ -202,7 +203,7 @@ int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn,
 	if (!p->index_data)
 		return -1;
 
-	err |= verify_packfile(r, p, &w_curs, fn, progress, base_count);
+	err |= verify_packfile(r, p, &w_curs, fn, fn_data, progress, base_count);
 	unuse_pack(&w_curs);
 
 	return err;
diff --git a/pack.h b/pack.h
index ec76472e49..1cde92082b 100644
--- a/pack.h
+++ b/pack.h
@@ -85,7 +85,11 @@ struct pack_idx_entry {
 
 struct progress;
 /* Note, the data argument could be NULL if object type is blob */
-typedef int (*verify_fn)(const struct object_id *, enum object_type, unsigned long, void*, int*);
+typedef int (*verify_fn)(const struct object_id *oid,
+			 enum object_type type,
+			 unsigned long size,
+			 void *buffer, int *eaten,
+			 void *fn_data);
 
 const char *write_idx_file(struct repository *repo,
 			   const char *index_name,
@@ -95,7 +99,8 @@ const char *write_idx_file(struct repository *repo,
 			   const unsigned char *sha1);
 int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 int verify_pack_index(struct packed_git *);
-int verify_pack(struct repository *, struct packed_git *, verify_fn fn, struct progress *, uint32_t);
+int verify_pack(struct repository *, struct packed_git *, verify_fn fn, void *fn_data,
+		struct progress *, uint32_t);
 off_t write_pack_header(struct hashfile *f, uint32_t);
 void fixup_pack_header_footer(const struct git_hash_algo *, int,
 			      unsigned char *, const char *, uint32_t,

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 12/14] builtin/fsck: stop using `the_repository` when marking objects
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-12-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-12-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
We implicitly rely on `the_repository` when marking objects for
connectivity. Refactor this to instead inject the repository via the
callback payload.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 55 +++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 35 insertions(+), 20 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 874af76e05..b15708d155 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -123,10 +123,16 @@ static int fsck_objects_error_func(struct fsck_options *o UNUSED,
 
 static struct object_array pending;
 
+struct mark_object_data {
+	struct repository *repo;
+	struct object *parent;
+};
+
 static int mark_object(struct object *obj, enum object_type type,
-		       void *data, struct fsck_options *options UNUSED)
+		       void *cb_data, struct fsck_options *options UNUSED)
 {
-	struct object *parent = data;
+	struct mark_object_data *data = cb_data;
+	struct object *parent = data->parent;
 
 	/*
 	 * The only case data is NULL or type is OBJ_ANY is when
@@ -153,7 +159,7 @@ static int mark_object(struct object *obj, enum object_type type,
 		return 0;
 	obj->flags |= REACHABLE;
 
-	if (is_promisor_object(the_repository, &obj->oid))
+	if (is_promisor_object(data->repo, &obj->oid))
 		/*
 		 * Further recursion does not need to be performed on this
 		 * object since it is a promisor object (so it does not need to
@@ -162,7 +168,7 @@ static int mark_object(struct object *obj, enum object_type type,
 		return 0;
 
 	if (!(obj->flags & HAS_OBJ)) {
-		if (parent && !odb_has_object(the_repository->objects, &obj->oid,
+		if (parent && !odb_has_object(data->repo->objects, &obj->oid,
 					      HAS_OBJECT_RECHECK_PACKED)) {
 			printf_ln(_("broken link from %7s %s\n"
 				    "              to %7s %s"),
@@ -179,14 +185,21 @@ static int mark_object(struct object *obj, enum object_type type,
 	return 0;
 }
 
-static void mark_object_reachable(struct object *obj)
+static void mark_object_reachable(struct repository *repo, struct object *obj)
 {
-	mark_object(obj, OBJ_ANY, NULL, NULL);
+	struct mark_object_data data = {
+		.repo = repo,
+	};
+	mark_object(obj, OBJ_ANY, &data, NULL);
 }
 
 static int traverse_one_object(struct repository *repo, struct object *obj)
 {
-	int result = fsck_walk(repo, obj, obj, &fsck_walk_options);
+	struct mark_object_data data = {
+		.repo = repo,
+		.parent = obj,
+	};
+	int result = fsck_walk(repo, obj, &data, &fsck_walk_options);
 
 	if (obj->type == OBJ_TREE) {
 		struct tree *tree = (struct tree *)obj;
@@ -222,10 +235,11 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,
 
 static int mark_unreachable_referents(const struct object_id *oid,
 				      struct object_info *oi UNUSED,
-				      void *data UNUSED)
+				      void *data)
 {
+	struct repository *repo = data;
 	struct fsck_options options = FSCK_OPTIONS_DEFAULT;
-	struct object *obj = lookup_object(the_repository, oid);
+	struct object *obj = lookup_object(repo, oid);
 
 	if (!obj || !(obj->flags & HAS_OBJ))
 		return 0; /* not part of our original set */
@@ -237,14 +251,14 @@ static int mark_unreachable_referents(const struct object_id *oid,
 	 * (and we want to avoid parsing blobs).
 	 */
 	if (obj->type == OBJ_NONE) {
-		enum object_type type = odb_read_object_info(the_repository->objects,
+		enum object_type type = odb_read_object_info(repo->objects,
 							     &obj->oid, NULL);
 		if (type > 0)
 			object_as_type(obj, type, 0);
 	}
 
 	options.walk = mark_used;
-	fsck_walk(the_repository, obj, NULL, &options);
+	fsck_walk(repo, obj, NULL, &options);
 	if (obj->type == OBJ_TREE)
 		free_tree_buffer((struct tree *)obj);
 
@@ -384,7 +398,7 @@ static void check_connectivity(struct repository *repo)
 		 * traversal.
 		 */
 		odb_for_each_object(repo->objects, NULL,
-				    mark_unreachable_referents, NULL, 0);
+				    mark_unreachable_referents, repo, 0);
 	}
 
 	/* Look up all the requirements, warn about missing objects.. */
@@ -484,7 +498,7 @@ static void fsck_handle_reflog_oid(struct repository *repo,
 						     "%s@{%"PRItime"}",
 						     refname, timestamp);
 			obj->flags |= USED;
-			mark_object_reachable(obj);
+			mark_object_reachable(repo, obj);
 		} else if (!is_promisor_object(repo, oid)) {
 			error(_("%s: invalid reflog entry %s"),
 			      refname, oid_to_hex(oid));
@@ -589,7 +603,7 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data)
 	obj->flags |= USED;
 	fsck_put_object_name(&fsck_walk_options,
 			     ref->oid, "%s", ref->name);
-	mark_object_reachable(obj);
+	mark_object_reachable(repo, obj);
 
 	return 0;
 }
@@ -829,7 +843,7 @@ static int fsck_cache_tree(struct repository *repo, struct cache_tree *it,
 		}
 		obj->flags |= USED;
 		fsck_put_object_name(&fsck_walk_options, &it->oid, ":");
-		mark_object_reachable(obj);
+		mark_object_reachable(repo, obj);
 		if (obj->type != OBJ_TREE)
 			err |= objerror(obj, _("non-tree in cache-tree"));
 	}
@@ -871,7 +885,7 @@ static int fsck_resolve_undo(struct index_state *istate,
 			obj->flags |= USED;
 			fsck_put_object_name(&fsck_walk_options, &ru->oid[i],
 					     ":(%d):%s", i, path);
-			mark_object_reachable(obj);
+			mark_object_reachable(istate->repo, obj);
 		}
 	}
 	return 0;
@@ -902,7 +916,7 @@ static void fsck_index(struct index_state *istate, const char *index_path,
 				     "%s:%s",
 				     is_current_worktree ? "" : index_path,
 				     istate->cache[i]->name);
-		mark_object_reachable(obj);
+		mark_object_reachable(istate->repo, obj);
 	}
 	if (istate->cache_tree)
 		fsck_cache_tree(istate->repo, istate->cache_tree, index_path);
@@ -911,9 +925,10 @@ static void fsck_index(struct index_state *istate, const char *index_path,
 
 static int mark_object_for_connectivity(const struct object_id *oid,
 					struct object_info *oi UNUSED,
-					void *cb_data UNUSED)
+					void *cb_data)
 {
-	struct object *obj = lookup_unknown_object(the_repository, oid);
+	struct repository *repo = cb_data;
+	struct object *obj = lookup_unknown_object(repo, oid);
 	obj->flags |= HAS_OBJ;
 	return 0;
 }
@@ -1064,7 +1079,7 @@ int cmd_fsck(int argc,
 
 	if (connectivity_only) {
 		odb_for_each_object(repo->objects, NULL,
-				    mark_object_for_connectivity, NULL, 0);
+				    mark_object_for_connectivity, repo, 0);
 	} else {
 		odb_prepare_alternates(repo->objects);
 		for (source = repo->objects->sources; source; source = source->next)

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 13/14] fsck: provide repository in `struct fsck_report_object`
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-13-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-13-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
The `report()` function invokes a callback function with a `struct
fsck_report_object` for every object that failed at least one of our
checks. This report doesn't give the caller enough context though, and
thus they have to rely on global state for `the_repository`.

Refactor the code and inject the repository into the report.

As this requires us to touch up all callsites, rename the `report()`
function to `fsck_report_object()` to align it with `fsck_report_ref()`.
We have already discussed this rename in the past, but we decided to not
do it because it required us to adapt too many callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 fsck.c | 365 ++++++++++++++++++++++++++++++++++++++---------------------------
 fsck.h |   1 +
 2 files changed, 214 insertions(+), 152 deletions(-)

diff --git a/fsck.c b/fsck.c
index edf7a0618f..59b9163598 100644
--- a/fsck.c
+++ b/fsck.c
@@ -258,13 +258,15 @@ static int fsck_vreport(struct fsck_options *options,
 	return result;
 }
 
-__attribute__((format (printf, 5, 6)))
-static int report(struct fsck_options *options,
-		  const struct object_id *oid, enum object_type object_type,
-		  enum fsck_msg_id msg_id, const char *fmt, ...)
+__attribute__((format (printf, 6, 7)))
+static int fsck_report_object(struct repository *repo,
+			      struct fsck_options *options,
+			      const struct object_id *oid, enum object_type object_type,
+			      enum fsck_msg_id msg_id, const char *fmt, ...)
 {
 	va_list ap;
 	struct fsck_object_report report = {
+		.repo = repo,
 		.oid = oid,
 		.object_type = object_type
 	};
@@ -626,7 +628,8 @@ static int verify_ordered(unsigned mode1, const char *name1,
 	return c1 < c2 ? 0 : TREE_UNORDERED;
 }
 
-static int fsck_tree(const struct object_id *tree_oid,
+static int fsck_tree(struct repository *repo,
+		     const struct object_id *tree_oid,
 		     const char *buffer, unsigned long size,
 		     struct fsck_options *options)
 {
@@ -649,9 +652,9 @@ static int fsck_tree(const struct object_id *tree_oid,
 
 	if (init_tree_desc_gently(&desc, tree_oid, buffer, size,
 				  TREE_DESC_RAW_MODES)) {
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_BAD_TREE,
-				 "cannot be parsed as a tree");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_BAD_TREE,
+					     "cannot be parsed as a tree");
 		return retval;
 	}
 
@@ -679,10 +682,10 @@ static int fsck_tree(const struct object_id *tree_oid,
 				oidset_insert(&options->gitmodules_found,
 					      entry_oid);
 			else
-				retval += report(options,
-						 tree_oid, OBJ_TREE,
-						 FSCK_MSG_GITMODULES_SYMLINK,
-						 ".gitmodules is a symbolic link");
+				retval += fsck_report_object(repo, options,
+							     tree_oid, OBJ_TREE,
+							     FSCK_MSG_GITMODULES_SYMLINK,
+							     ".gitmodules is a symbolic link");
 		}
 
 		if (is_hfs_dotgitattributes(name) || is_ntfs_dotgitattributes(name)) {
@@ -690,22 +693,22 @@ static int fsck_tree(const struct object_id *tree_oid,
 				oidset_insert(&options->gitattributes_found,
 					      entry_oid);
 			else
-				retval += report(options, tree_oid, OBJ_TREE,
-						 FSCK_MSG_GITATTRIBUTES_SYMLINK,
-						 ".gitattributes is a symlink");
+				retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+							     FSCK_MSG_GITATTRIBUTES_SYMLINK,
+							     ".gitattributes is a symlink");
 		}
 
 		if (S_ISLNK(mode)) {
 			if (is_hfs_dotgitignore(name) ||
 			    is_ntfs_dotgitignore(name))
-				retval += report(options, tree_oid, OBJ_TREE,
-						 FSCK_MSG_GITIGNORE_SYMLINK,
-						 ".gitignore is a symlink");
+				retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+							     FSCK_MSG_GITIGNORE_SYMLINK,
+							     ".gitignore is a symlink");
 			if (is_hfs_dotmailmap(name) ||
 			    is_ntfs_dotmailmap(name))
-				retval += report(options, tree_oid, OBJ_TREE,
-						 FSCK_MSG_MAILMAP_SYMLINK,
-						 ".mailmap is a symlink");
+				retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+							     FSCK_MSG_MAILMAP_SYMLINK,
+							     ".mailmap is a symlink");
 		}
 
 		if ((backslash = strchr(name, '\\'))) {
@@ -717,18 +720,18 @@ static int fsck_tree(const struct object_id *tree_oid,
 						oidset_insert(&options->gitmodules_found,
 							      entry_oid);
 					else
-						retval += report(options, tree_oid, OBJ_TREE,
-								 FSCK_MSG_GITMODULES_SYMLINK,
-								 ".gitmodules is a symbolic link");
+						retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+									     FSCK_MSG_GITMODULES_SYMLINK,
+									     ".gitmodules is a symbolic link");
 				}
 				backslash = strchr(backslash, '\\');
 			}
 		}
 
 		if (update_tree_entry_gently(&desc)) {
-			retval += report(options, tree_oid, OBJ_TREE,
-					 FSCK_MSG_BAD_TREE,
-					 "cannot be parsed as a tree");
+			retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+						     FSCK_MSG_BAD_TREE,
+						     "cannot be parsed as a tree");
 			break;
 		}
 
@@ -776,49 +779,49 @@ static int fsck_tree(const struct object_id *tree_oid,
 	name_stack_clear(&df_dup_candidates);
 
 	if (has_null_sha1)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_NULL_SHA1,
-				 "contains entries pointing to null sha1");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_NULL_SHA1,
+					     "contains entries pointing to null sha1");
 	if (has_full_path)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_FULL_PATHNAME,
-				 "contains full pathnames");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_FULL_PATHNAME,
+					     "contains full pathnames");
 	if (has_empty_name)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_EMPTY_NAME,
-				 "contains empty pathname");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_EMPTY_NAME,
+					     "contains empty pathname");
 	if (has_dot)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_HAS_DOT,
-				 "contains '.'");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_HAS_DOT,
+					     "contains '.'");
 	if (has_dotdot)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_HAS_DOTDOT,
-				 "contains '..'");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_HAS_DOTDOT,
+					     "contains '..'");
 	if (has_dotgit)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_HAS_DOTGIT,
-				 "contains '.git'");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_HAS_DOTGIT,
+					     "contains '.git'");
 	if (has_zero_pad)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_ZERO_PADDED_FILEMODE,
-				 "contains zero-padded file modes");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_ZERO_PADDED_FILEMODE,
+					     "contains zero-padded file modes");
 	if (has_bad_modes)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_BAD_FILEMODE,
-				 "contains bad file modes");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_BAD_FILEMODE,
+					     "contains bad file modes");
 	if (has_dup_entries)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_DUPLICATE_ENTRIES,
-				 "contains duplicate file entries");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_DUPLICATE_ENTRIES,
+					     "contains duplicate file entries");
 	if (not_properly_sorted)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_TREE_NOT_SORTED,
-				 "not properly sorted");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_TREE_NOT_SORTED,
+					     "not properly sorted");
 	if (has_large_name)
-		retval += report(options, tree_oid, OBJ_TREE,
-				 FSCK_MSG_LARGE_PATHNAME,
-				 "contains excessively large pathname");
+		retval += fsck_report_object(repo, options, tree_oid, OBJ_TREE,
+					     FSCK_MSG_LARGE_PATHNAME,
+					     "contains excessively large pathname");
 	return retval;
 }
 
@@ -839,7 +842,8 @@ static int fsck_tree(const struct object_id *tree_oid,
  *     case hit the newline we found here as the end-of-header. This makes it
  *     OK for them to use helpers like parse_oid_hex(), or even skip_prefix().
  */
-static int verify_headers(const void *data, unsigned long size,
+static int verify_headers(struct repository *repo,
+			  const void *data, unsigned long size,
 			  const struct object_id *oid, enum object_type type,
 			  struct fsck_options *options)
 {
@@ -849,9 +853,9 @@ static int verify_headers(const void *data, unsigned long size,
 	for (i = 0; i < size; i++) {
 		switch (buffer[i]) {
 		case '\0':
-			return report(options, oid, type,
-				FSCK_MSG_NUL_IN_HEADER,
-				"unterminated header: NUL at offset %ld", i);
+			return fsck_report_object(repo, options, oid, type,
+						  FSCK_MSG_NUL_IN_HEADER,
+						  "unterminated header: NUL at offset %ld", i);
 		case '\n':
 			if (i + 1 < size && buffer[i + 1] == '\n')
 				return 0;
@@ -867,8 +871,9 @@ static int verify_headers(const void *data, unsigned long size,
 	if (size && buffer[size - 1] == '\n')
 		return 0;
 
-	return report(options, oid, type,
-		FSCK_MSG_UNTERMINATED_HEADER, "unterminated header");
+	return fsck_report_object(repo, options, oid, type,
+				  FSCK_MSG_UNTERMINATED_HEADER,
+				  "unterminated header");
 }
 
 static timestamp_t parse_timestamp_from_buf(const char **start, const char *end)
@@ -887,7 +892,8 @@ static timestamp_t parse_timestamp_from_buf(const char **start, const char *end)
 	return parse_timestamp(buf, NULL, 10);
 }
 
-static int fsck_ident(const char **ident, const char *ident_end,
+static int fsck_ident(struct repository *repo,
+		      const char **ident, const char *ident_end,
 		      const struct object_id *oid, enum object_type type,
 		      struct fsck_options *options)
 {
@@ -900,12 +906,18 @@ static int fsck_ident(const char **ident, const char *ident_end,
 	*ident = nl + 1;
 
 	if (*p == '<')
-		return report(options, oid, type, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_MISSING_NAME_BEFORE_EMAIL,
+					  "invalid author/committer line - missing space before email");
 	for (;;) {
 		if (p >= ident_end || *p == '\n')
-			return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email");
+			return fsck_report_object(repo, options, oid, type,
+						  FSCK_MSG_MISSING_EMAIL,
+						  "invalid author/committer line - missing email");
 		if (*p == '>')
-			return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name");
+			return fsck_report_object(repo, options, oid, type,
+						  FSCK_MSG_BAD_NAME,
+						  "invalid author/committer line - bad name");
 		if (*p == '<')
 			break; /* end of name, beginning of email */
 
@@ -913,11 +925,15 @@ static int fsck_ident(const char **ident, const char *ident_end,
 		p++;
 	}
 	if (p[-1] != ' ')
-		return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL,
+					  "invalid author/committer line - missing space before email");
 	p++; /* skip past '<' we found */
 	for (;;) {
 		if (p >= ident_end || *p == '<' || *p == '\n')
-			return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email");
+			return fsck_report_object(repo, options, oid, type,
+						  FSCK_MSG_BAD_EMAIL,
+						  "invalid author/committer line - bad email");
 		if (*p == '>')
 			break; /* end of email */
 
@@ -926,7 +942,9 @@ static int fsck_ident(const char **ident, const char *ident_end,
 	}
 	p++; /* skip past '>' we found */
 	if (*p != ' ')
-		return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_MISSING_SPACE_BEFORE_DATE,
+					  "invalid author/committer line - missing space before date");
 	p++;
 	/*
 	 * Our timestamp parser is based on the C strto*() functions, which
@@ -940,14 +958,21 @@ static int fsck_ident(const char **ident, const char *ident_end,
 	while (*p == ' ' || *p == '\t')
 		p++;
 	if (!isdigit(*p))
-		return report(options, oid, type, FSCK_MSG_BAD_DATE,
-			      "invalid author/committer line - bad date");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_BAD_DATE,
+					  "invalid author/committer line - bad date");
 	if (*p == '0' && p[1] != ' ')
-		return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_ZERO_PADDED_DATE,
+					  "invalid author/committer line - zero-padded date");
 	if (date_overflows(parse_timestamp_from_buf(&p, ident_end)))
-		return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_BAD_DATE_OVERFLOW,
+					  "invalid author/committer line - date causes integer overflow");
 	if (*p != ' ')
-		return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_BAD_DATE,
+					  "invalid author/committer line - bad date");
 	p++;
 	if ((*p != '+' && *p != '-') ||
 	    !isdigit(p[1]) ||
@@ -955,7 +980,9 @@ static int fsck_ident(const char **ident, const char *ident_end,
 	    !isdigit(p[3]) ||
 	    !isdigit(p[4]) ||
 	    (p[5] != '\n'))
-		return report(options, oid, type, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone");
+		return fsck_report_object(repo, options, oid, type,
+					  FSCK_MSG_BAD_TIMEZONE,
+					  "invalid author/committer line - bad time zone");
 	p += 6;
 	return 0;
 }
@@ -977,20 +1004,26 @@ static int fsck_commit(struct repository *repo,
 	 * memory safety of the rest of the function depends on it. See the
 	 * comment above the definition of verify_headers() for more details.
 	 */
-	if (verify_headers(buffer, size, oid, OBJ_COMMIT, options))
+	if (verify_headers(repo, buffer, size, oid, OBJ_COMMIT, options))
 		return -1;
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "tree ", &buffer))
-		return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
+		return fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					  FSCK_MSG_MISSING_TREE,
+					  "invalid format - expected 'tree' line");
 	if (parse_oid_hex_algop(buffer, &tree_oid, &p, repo->hash_algo) || *p != '\n') {
-		err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
+		err = fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					 FSCK_MSG_BAD_TREE_SHA1,
+					 "invalid 'tree' line format - bad sha1");
 		if (err)
 			return err;
 	}
 	buffer = p + 1;
 	while (buffer < buffer_end && skip_prefix(buffer, "parent ", &buffer)) {
 		if (parse_oid_hex_algop(buffer, &parent_oid, &p, repo->hash_algo) || *p != '\n') {
-			err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
+			err = fsck_report_object(repo, options, oid, OBJ_COMMIT,
+						 FSCK_MSG_BAD_PARENT_SHA1,
+						 "invalid 'parent' line format - bad sha1");
 			if (err)
 				return err;
 		}
@@ -999,24 +1032,31 @@ static int fsck_commit(struct repository *repo,
 	author_count = 0;
 	while (buffer < buffer_end && skip_prefix(buffer, "author ", &buffer)) {
 		author_count++;
-		err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options);
+		err = fsck_ident(repo, &buffer, buffer_end, oid, OBJ_COMMIT, options);
 		if (err)
 			return err;
 	}
 	if (author_count < 1)
-		err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line");
+		err = fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					 FSCK_MSG_MISSING_AUTHOR,
+					 "invalid format - expected 'author' line");
 	else if (author_count > 1)
-		err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines");
+		err = fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					 FSCK_MSG_MULTIPLE_AUTHORS,
+					 "invalid format - multiple 'author' lines");
 	if (err)
 		return err;
 	if (buffer >= buffer_end || !skip_prefix(buffer, "committer ", &buffer))
-		return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line");
-	err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options);
+		return fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					  FSCK_MSG_MISSING_COMMITTER,
+					  "invalid format - expected 'committer' line");
+	err = fsck_ident(repo, &buffer, buffer_end, oid, OBJ_COMMIT, options);
 	if (err)
 		return err;
 	if (memchr(buffer_begin, '\0', size)) {
-		err = report(options, oid, OBJ_COMMIT, FSCK_MSG_NUL_IN_COMMIT,
-			     "NUL byte in the commit object body");
+		err = fsck_report_object(repo, options, oid, OBJ_COMMIT,
+					 FSCK_MSG_NUL_IN_COMMIT,
+					 "NUL byte in the commit object body");
 		if (err)
 			return err;
 	}
@@ -1050,52 +1090,66 @@ int fsck_tag_standalone(struct repository *repo,
 	 * memory safety of the rest of the function depends on it. See the
 	 * comment above the definition of verify_headers() for more details.
 	 */
-	ret = verify_headers(buffer, size, oid, OBJ_TAG, options);
+	ret = verify_headers(repo, buffer, size, oid, OBJ_TAG, options);
 	if (ret)
 		goto done;
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "object ", &buffer)) {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_OBJECT,
+					 "invalid format - expected 'object' line");
 		goto done;
 	}
 	if (parse_oid_hex_algop(buffer, tagged_oid, &p, repo->hash_algo) || *p != '\n') {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_BAD_OBJECT_SHA1,
+					 "invalid 'object' line format - bad sha1");
 		if (ret)
 			goto done;
 	}
 	buffer = p + 1;
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "type ", &buffer)) {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_TYPE_ENTRY,
+					 "invalid format - expected 'type' line");
 		goto done;
 	}
 	eol = memchr(buffer, '\n', buffer_end - buffer);
 	if (!eol) {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_TYPE,
+					 "invalid format - unexpected end after 'type' line");
 		goto done;
 	}
 	*tagged_type = type_from_string_gently(buffer, eol - buffer, 1);
 	if (*tagged_type < 0)
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_TYPE, "invalid 'type' value");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_BAD_TYPE,
+					 "invalid 'type' value");
 	if (ret)
 		goto done;
 	buffer = eol + 1;
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "tag ", &buffer)) {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_TAG_ENTRY,
+					 "invalid format - expected 'tag' line");
 		goto done;
 	}
 	eol = memchr(buffer, '\n', buffer_end - buffer);
 	if (!eol) {
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_TAG,
+					 "invalid format - unexpected end after 'type' line");
 		goto done;
 	}
 	strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer);
 	if (check_refname_format(sb.buf, 0)) {
-		ret = report(options, oid, OBJ_TAG,
-			     FSCK_MSG_BAD_TAG_NAME,
-			     "invalid 'tag' name: %.*s",
-			     (int)(eol - buffer), buffer);
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_BAD_TAG_NAME,
+					 "invalid 'tag' name: %.*s",
+					 (int)(eol - buffer), buffer);
 		if (ret)
 			goto done;
 	}
@@ -1103,17 +1157,21 @@ int fsck_tag_standalone(struct repository *repo,
 
 	if (buffer >= buffer_end || !skip_prefix(buffer, "tagger ", &buffer)) {
 		/* early tags do not contain 'tagger' lines; warn only */
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_MISSING_TAGGER_ENTRY,
+					 "invalid format - expected 'tagger' line");
 		if (ret)
 			goto done;
 	}
 	else
-		ret = fsck_ident(&buffer, buffer_end, oid, OBJ_TAG, options);
+		ret = fsck_ident(repo, &buffer, buffer_end, oid, OBJ_TAG, options);
 
 	if (buffer < buffer_end && (skip_prefix(buffer, "gpgsig ", &buffer) || skip_prefix(buffer, "gpgsig-sha256 ", &buffer))) {
 		eol = memchr(buffer, '\n', buffer_end - buffer);
 		if (!eol) {
-			ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_GPGSIG, "invalid format - unexpected end after 'gpgsig' or 'gpgsig-sha256' line");
+			ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+						 FSCK_MSG_BAD_GPGSIG,
+						 "invalid format - unexpected end after 'gpgsig' or 'gpgsig-sha256' line");
 			goto done;
 		}
 		buffer = eol + 1;
@@ -1121,7 +1179,9 @@ int fsck_tag_standalone(struct repository *repo,
 		while (buffer < buffer_end && starts_with(buffer, " ")) {
 			eol = memchr(buffer, '\n', buffer_end - buffer);
 			if (!eol) {
-				ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_HEADER_CONTINUATION, "invalid format - unexpected end in 'gpgsig' or 'gpgsig-sha256' continuation line");
+				ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+							 FSCK_MSG_BAD_HEADER_CONTINUATION,
+							 "invalid format - unexpected end in 'gpgsig' or 'gpgsig-sha256' continuation line");
 				goto done;
 			}
 			buffer = eol + 1;
@@ -1136,7 +1196,9 @@ int fsck_tag_standalone(struct repository *repo,
 		 * garbage" could be a custom header. E.g. "mktag"
 		 * doesn't want any unknown headers.
 		 */
-		ret = report(options, oid, OBJ_TAG, FSCK_MSG_EXTRA_HEADER_ENTRY, "invalid format - extra header(s) after 'tagger'");
+		ret = fsck_report_object(repo, options, oid, OBJ_TAG,
+					 FSCK_MSG_EXTRA_HEADER_ENTRY,
+					 "invalid format - extra header(s) after 'tagger'");
 		if (ret)
 			goto done;
 	}
@@ -1147,6 +1209,7 @@ int fsck_tag_standalone(struct repository *repo,
 }
 
 struct fsck_gitmodules_data {
+	struct repository *repo;
 	const struct object_id *oid;
 	struct fsck_options *options;
 	int ret;
@@ -1157,6 +1220,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value,
 			      void *vdata)
 {
 	struct fsck_gitmodules_data *data = vdata;
+	struct repository *repo = data->repo;
 	const char *subsection, *key;
 	size_t subsection_len;
 	char *name;
@@ -1167,37 +1231,31 @@ static int fsck_gitmodules_fn(const char *var, const char *value,
 
 	name = xmemdupz(subsection, subsection_len);
 	if (check_submodule_name(name) < 0)
-		data->ret |= report(data->options,
-				    data->oid, OBJ_BLOB,
-				    FSCK_MSG_GITMODULES_NAME,
-				    "disallowed submodule name: %s",
-				    name);
+		data->ret |= fsck_report_object(repo, data->options, data->oid, OBJ_BLOB,
+						FSCK_MSG_GITMODULES_NAME,
+						"disallowed submodule name: %s", name);
 	if (!strcmp(key, "url") && value &&
 	    check_submodule_url(value) < 0)
-		data->ret |= report(data->options,
-				    data->oid, OBJ_BLOB,
-				    FSCK_MSG_GITMODULES_URL,
-				    "disallowed submodule url: %s",
-				    value);
+		data->ret |= fsck_report_object(repo, data->options, data->oid, OBJ_BLOB,
+						FSCK_MSG_GITMODULES_URL,
+						"disallowed submodule url: %s", value);
 	if (!strcmp(key, "path") && value &&
 	    looks_like_command_line_option(value))
-		data->ret |= report(data->options,
-				    data->oid, OBJ_BLOB,
-				    FSCK_MSG_GITMODULES_PATH,
-				    "disallowed submodule path: %s",
-				    value);
+		data->ret |= fsck_report_object(repo, data->options, data->oid, OBJ_BLOB,
+						FSCK_MSG_GITMODULES_PATH,
+						"disallowed submodule path: %s", value);
 	if (!strcmp(key, "update") && value &&
 	    parse_submodule_update_type(value) == SM_UPDATE_COMMAND)
-		data->ret |= report(data->options, data->oid, OBJ_BLOB,
-				    FSCK_MSG_GITMODULES_UPDATE,
-				    "disallowed submodule update setting: %s",
-				    value);
+		data->ret |= fsck_report_object(repo, data->options, data->oid, OBJ_BLOB,
+						FSCK_MSG_GITMODULES_UPDATE,
+						"disallowed submodule update setting: %s", value);
 	free(name);
 
 	return 0;
 }
 
-static int fsck_blob(const struct object_id *oid, const char *buf,
+static int fsck_blob(struct repository *repo,
+		     const struct object_id *oid, const char *buf,
 		     unsigned long size, struct fsck_options *options)
 {
 	int ret = 0;
@@ -1217,11 +1275,12 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
 			 * blob too gigantic to load into memory. Let's just consider
 			 * that an error.
 			 */
-			return report(options, oid, OBJ_BLOB,
-					FSCK_MSG_GITMODULES_LARGE,
-					".gitmodules too large to parse");
+			return fsck_report_object(repo, options, oid, OBJ_BLOB,
+						  FSCK_MSG_GITMODULES_LARGE,
+						  ".gitmodules too large to parse");
 		}
 
+		data.repo = repo;
 		data.oid = oid;
 		data.options = options;
 		data.ret = 0;
@@ -1229,9 +1288,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
 		if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,
 					".gitmodules", buf, size, &data,
 					CONFIG_SCOPE_UNKNOWN, &config_opts))
-			data.ret |= report(options, oid, OBJ_BLOB,
-					FSCK_MSG_GITMODULES_PARSE,
-					"could not parse gitmodules blob");
+			data.ret |= fsck_report_object(repo, options, oid, OBJ_BLOB,
+						       FSCK_MSG_GITMODULES_PARSE,
+						       "could not parse gitmodules blob");
 		ret |= data.ret;
 	}
 
@@ -1246,17 +1305,17 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
 			 * blob too gigantic to load into memory. Let's just consider
 			 * that an error.
 			 */
-			return report(options, oid, OBJ_BLOB,
-					FSCK_MSG_GITATTRIBUTES_LARGE,
-					".gitattributes too large to parse");
+			return fsck_report_object(repo, options, oid, OBJ_BLOB,
+						  FSCK_MSG_GITATTRIBUTES_LARGE,
+						  ".gitattributes too large to parse");
 		}
 
 		for (ptr = buf; *ptr; ) {
 			const char *eol = strchrnul(ptr, '\n');
 			if (eol - ptr >= ATTR_MAX_LINE_LENGTH) {
-				ret |= report(options, oid, OBJ_BLOB,
-					      FSCK_MSG_GITATTRIBUTES_LINE_LENGTH,
-					      ".gitattributes has too long lines to parse");
+				ret |= fsck_report_object(repo, options, oid, OBJ_BLOB,
+							  FSCK_MSG_GITATTRIBUTES_LINE_LENGTH,
+							  ".gitattributes has too long lines to parse");
 				break;
 			}
 
@@ -1272,7 +1331,9 @@ int fsck_object(struct repository *repo,
 	struct fsck_options *options)
 {
 	if (!obj)
-		return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
+		return fsck_report_object(repo, options, NULL, OBJ_NONE,
+					  FSCK_MSG_BAD_OBJECT_SHA1,
+					  "no valid object to fsck");
 
 	return fsck_buffer(repo, &obj->oid, obj->type, data, size, options);
 }
@@ -1283,18 +1344,18 @@ int fsck_buffer(struct repository *repo,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
-		return fsck_blob(oid, data, size, options);
+		return fsck_blob(repo, oid, data, size, options);
 	if (type == OBJ_TREE)
-		return fsck_tree(oid, data, size, options);
+		return fsck_tree(repo, oid, data, size, options);
 	if (type == OBJ_COMMIT)
 		return fsck_commit(repo, oid, data, size, options);
 	if (type == OBJ_TAG)
 		return fsck_tag(repo, oid, data, size, options);
 
-	return report(options, oid, type,
-		      FSCK_MSG_UNKNOWN_TYPE,
-		      "unknown type '%d' (internal fsck error)",
-		      type);
+	return fsck_report_object(repo, options, oid, type,
+				  FSCK_MSG_UNKNOWN_TYPE,
+				  "unknown type '%d' (internal fsck error)",
+				  type);
 }
 
 int fsck_objects_error_function(struct fsck_options *o,
@@ -1357,17 +1418,17 @@ static int fsck_blobs(struct repository *repo,
 		if (!buf) {
 			if (is_promisor_object(repo, oid))
 				continue;
-			ret |= report(options,
-				      oid, OBJ_BLOB, msg_missing,
-				      "unable to read %s blob", blob_type);
+			ret |= fsck_report_object(repo, options,
+						  oid, OBJ_BLOB, msg_missing,
+						  "unable to read %s blob", blob_type);
 			continue;
 		}
 
 		if (type == OBJ_BLOB)
-			ret |= fsck_blob(oid, buf, size, options);
+			ret |= fsck_blob(repo, oid, buf, size, options);
 		else
-			ret |= report(options, oid, type, msg_type,
-				      "non-blob found at %s", blob_type);
+			ret |= fsck_report_object(repo, options, oid, type, msg_type,
+						  "non-blob found at %s", blob_type);
 		free(buf);
 	}
 
diff --git a/fsck.h b/fsck.h
index 62b7c3c93a..5d05996dd0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -159,6 +159,7 @@ int fsck_refs_error_function(struct fsck_options *options,
 			     const char *message);
 
 struct fsck_object_report {
+	struct repository *repo;
 	const struct object_id *oid;
 	enum object_type object_type;
 };

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Patrick Steinhardt, 2026-03-20 11:47

Subject: [PATCH 14/14] builtin/fsck: stop using `the_repository` in error reporting
Message-ID: <20260320-b4-pks-fsck-without-the-repository-v1-14-6594f997926b@pks.im>
URL: https://gitlist.dev/e/20260320-b4-pks-fsck-without-the-repository-v1-14-6594f997926b%40pks.im
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-0-6594f997926b@pks.im>

```
In the preceding commit we have introduced the repository into `struct
fsck_object_report`. This allows us to drop remaining uses of the global
`the_repository` variable.

Drop them and remove `USE_THE_REPOSITORY_VARIABLE`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/fsck.c | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index b15708d155..24d8ec6d42 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #include "builtin.h"
 #include "gettext.h"
 #include "hex.h"
@@ -66,13 +65,14 @@ static const char *describe_object(const struct object_id *oid)
 	return fsck_describe_object(&fsck_walk_options, oid);
 }
 
-static const char *printable_type(const struct object_id *oid,
+static const char *printable_type(struct repository *repo,
+				  const struct object_id *oid,
 				  enum object_type type)
 {
 	const char *ret;
 
 	if (type == OBJ_NONE)
-		type = odb_read_object_info(the_repository->objects,
+		type = odb_read_object_info(repo->objects,
 					    oid, NULL);
 
 	ret = type_name(type);
@@ -82,12 +82,12 @@ static const char *printable_type(const struct object_id *oid,
 	return ret;
 }
 
-static int objerror(struct object *obj, const char *err)
+static int objerror(struct repository *repo, struct object *obj, const char *err)
 {
 	errors_found |= ERROR_OBJECT;
 	/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
 	fprintf_ln(stderr, _("error in %s %s: %s"),
-		   printable_type(&obj->oid, obj->type),
+		   printable_type(repo, &obj->oid, obj->type),
 		   describe_object(&obj->oid), err);
 	return -1;
 }
@@ -106,13 +106,13 @@ static int fsck_objects_error_func(struct fsck_options *o UNUSED,
 	case FSCK_WARN:
 		/* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */
 		fprintf_ln(stderr, _("warning in %s %s: %s"),
-			   printable_type(oid, object_type),
+			   printable_type(report->repo, oid, object_type),
 			   describe_object(oid), message);
 		return 0;
 	case FSCK_ERROR:
 		/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
 		fprintf_ln(stderr, _("error in %s %s: %s"),
-			   printable_type(oid, object_type),
+			   printable_type(report->repo, oid, object_type),
 			   describe_object(oid), message);
 		return 1;
 	default:
@@ -142,7 +142,7 @@ static int mark_object(struct object *obj, enum object_type type,
 	if (!obj) {
 		/* ... these references to parent->fld are safe here */
 		printf_ln(_("broken link from %7s %s"),
-			  printable_type(&parent->oid, parent->type),
+			  printable_type(data->repo, &parent->oid, parent->type),
 			  describe_object(&parent->oid));
 		printf_ln(_("broken link from %7s %s"),
 			  (type == OBJ_ANY ? _("unknown") : type_name(type)),
@@ -153,7 +153,7 @@ static int mark_object(struct object *obj, enum object_type type,
 
 	if (type != OBJ_ANY && obj->type != type)
 		/* ... and the reference to parent is safe here */
-		objerror(parent, _("wrong object type in link"));
+		objerror(data->repo, parent, _("wrong object type in link"));
 
 	if (obj->flags & REACHABLE)
 		return 0;
@@ -172,9 +172,9 @@ static int mark_object(struct object *obj, enum object_type type,
 					      HAS_OBJECT_RECHECK_PACKED)) {
 			printf_ln(_("broken link from %7s %s\n"
 				    "              to %7s %s"),
-				  printable_type(&parent->oid, parent->type),
+				  printable_type(data->repo, &parent->oid, parent->type),
 				  describe_object(&parent->oid),
-				  printable_type(&obj->oid, obj->type),
+				  printable_type(data->repo, &obj->oid, obj->type),
 				  describe_object(&obj->oid));
 			errors_found |= ERROR_REACHABLE;
 		}
@@ -281,7 +281,7 @@ static void check_reachable_object(struct repository *repo, struct object *obj)
 		if (has_object_pack(repo, &obj->oid))
 			return; /* it is in pack - forget about it */
 		printf_ln(_("missing %s %s"),
-			  printable_type(&obj->oid, obj->type),
+			  printable_type(repo, &obj->oid, obj->type),
 			  describe_object(&obj->oid));
 		errors_found |= ERROR_REACHABLE;
 		return;
@@ -308,7 +308,7 @@ static void check_unreachable_object(struct repository *repo, struct object *obj
 	 */
 	if (show_unreachable) {
 		printf_ln(_("unreachable %s %s"),
-			  printable_type(&obj->oid, obj->type),
+			  printable_type(repo, &obj->oid, obj->type),
 			  describe_object(&obj->oid));
 		return;
 	}
@@ -328,7 +328,7 @@ static void check_unreachable_object(struct repository *repo, struct object *obj
 	if (!(obj->flags & USED)) {
 		if (show_dangling)
 			printf_ln(_("dangling %s %s"),
-				  printable_type(&obj->oid, obj->type),
+				  printable_type(repo, &obj->oid, obj->type),
 				  describe_object(&obj->oid));
 		if (write_lost_and_found) {
 			char *filename = repo_git_path(repo, "lost-found/%s/%s",
@@ -426,11 +426,11 @@ static int fsck_obj(struct repository *repo,
 
 	if (verbose)
 		fprintf_ln(stderr, _("Checking %s %s"),
-			   printable_type(&obj->oid, obj->type),
+			   printable_type(repo, &obj->oid, obj->type),
 			   describe_object(&obj->oid));
 
 	if (fsck_walk(repo, obj, NULL, &fsck_obj_options))
-		objerror(obj, _("broken links"));
+		objerror(repo, obj, _("broken links"));
 	err = fsck_object(repo, obj, buffer, size, &fsck_obj_options);
 	if (err)
 		goto out;
@@ -448,7 +448,7 @@ static int fsck_obj(struct repository *repo,
 
 		if (show_tags && tag->tagged) {
 			printf_ln(_("tagged %s %s (%s) in %s"),
-				  printable_type(&tag->tagged->oid, tag->tagged->type),
+				  printable_type(repo, &tag->tagged->oid, tag->tagged->type),
 				  describe_object(&tag->tagged->oid),
 				  tag->tag,
 				  describe_object(&tag->object.oid));
@@ -845,7 +845,7 @@ static int fsck_cache_tree(struct repository *repo, struct cache_tree *it,
 		fsck_put_object_name(&fsck_walk_options, &it->oid, ":");
 		mark_object_reachable(repo, obj);
 		if (obj->type != OBJ_TREE)
-			err |= objerror(obj, _("non-tree in cache-tree"));
+			err |= objerror(repo, obj, _("non-tree in cache-tree"));
 	}
 	for (i = 0; i < it->subtree_nr; i++)
 		err |= fsck_cache_tree(repo, it->down[i]->cache_tree, index_path);

-- 
2.53.0.1055.ga2ffed1127.dirty


```

## Junio C Hamano, 2026-03-20 23:09

Subject: Re: [PATCH 01/14] fsck: drop `the_repository` in `fsck_walk()`
Message-ID: <xmqq341udrob.fsf@gitster.g>
URL: https://gitlist.dev/e/xmqq341udrob.fsf%40gitster.g
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-1-6594f997926b@pks.im>

```
Patrick Steinhardt <ps@pks.im> writes:

> The function `fsck_walk()` and its object type specific functions
> `fsck_walk_tree()` et al implicitly rely on `the_repository`. Remove
> this dependency by injecting the repository as a parameter instead.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/fsck.c           |  6 +++---
>  builtin/index-pack.c     |  2 +-
>  builtin/unpack-objects.c |  2 +-
>  fsck.c                   | 40 ++++++++++++++++++++++++++--------------
>  fsck.h                   | 15 ++++++++++++---
>  5 files changed, 43 insertions(+), 22 deletions(-)

OK.

While the conversion is cleanly done, the resulting code makes me
wonder if fsck_options struct

        struct fsck_options {
                fsck_walk_func walk;
                fsck_error error_func;
                unsigned strict;
                unsigned verbose;
                enum fsck_msg_type *msg_type;
                struct oidset skip_oids;
                struct oidset gitmodules_found;
                struct oidset gitmodules_done;
                struct oidset gitattributes_found;
                struct oidset gitattributes_done;
                kh_oid_map_t *object_names;
        };

should gain a repository pointer.  After all, these oidset and oidmap
are about objects in one particular repository.


```

## Junio C Hamano, 2026-03-20 23:13

Subject: Re: [PATCH 13/14] fsck: provide repository in `struct fsck_report_object`
Message-ID: <xmqqy0jmccx2.fsf@gitster.g>
URL: https://gitlist.dev/e/xmqqy0jmccx2.fsf%40gitster.g
In-Reply-To: <20260320-b4-pks-fsck-without-the-repository-v1-13-6594f997926b@pks.im>

```
Patrick Steinhardt <ps@pks.im> writes:

> The `report()` function invokes a callback function with a `struct
> fsck_report_object` for every object that failed at least one of our
> checks. This report doesn't give the caller enough context though, and
> thus they have to rely on global state for `the_repository`.
>
> Refactor the code and inject the repository into the report.
>
> As this requires us to touch up all callsites, rename the `report()`
> function to `fsck_report_object()` to align it with `fsck_report_ref()`.
> We have already discussed this rename in the past, but we decided to not
> do it because it required us to adapt too many callsites.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  fsck.c | 365 ++++++++++++++++++++++++++++++++++++++---------------------------
>  fsck.h |   1 +
>  2 files changed, 214 insertions(+), 152 deletions(-)
>
> diff --git a/fsck.c b/fsck.c
> index edf7a0618f..59b9163598 100644
> --- a/fsck.c
> +++ b/fsck.c
> @@ -258,13 +258,15 @@ static int fsck_vreport(struct fsck_options *options,
>  	return result;
>  }
>  
> -__attribute__((format (printf, 5, 6)))
> -static int report(struct fsck_options *options,
> -		  const struct object_id *oid, enum object_type object_type,
> -		  enum fsck_msg_id msg_id, const char *fmt, ...)
> +__attribute__((format (printf, 6, 7)))
> +static int fsck_report_object(struct repository *repo,
> +			      struct fsck_options *options,
> +			      const struct object_id *oid, enum object_type object_type,
> +			      enum fsck_msg_id msg_id, const char *fmt, ...)

Earlier I wondered if struct repository *repo should be added to
fsck_options; I didn't comment on individual patches, but almost all
patches I saw since that remark made me suspect that fsck_options
would be a good place to pass repository throughout the callchain
even more.

```

## Patrick Steinhardt, 2026-03-23 12:22

Subject: Re: [PATCH 01/14] fsck: drop `the_repository` in `fsck_walk()`
Message-ID: <acEwis6mGCaY4WtR@pks.im>
URL: https://gitlist.dev/e/acEwis6mGCaY4WtR%40pks.im
In-Reply-To: <xmqq341udrob.fsf@gitster.g>

```
On Fri, Mar 20, 2026 at 04:09:24PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > The function `fsck_walk()` and its object type specific functions
> > `fsck_walk_tree()` et al implicitly rely on `the_repository`. Remove
> > this dependency by injecting the repository as a parameter instead.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  builtin/fsck.c           |  6 +++---
> >  builtin/index-pack.c     |  2 +-
> >  builtin/unpack-objects.c |  2 +-
> >  fsck.c                   | 40 ++++++++++++++++++++++++++--------------
> >  fsck.h                   | 15 ++++++++++++---
> >  5 files changed, 43 insertions(+), 22 deletions(-)
> 
> OK.
> 
> While the conversion is cleanly done, the resulting code makes me
> wonder if fsck_options struct
> 
>         struct fsck_options {
>                 fsck_walk_func walk;
>                 fsck_error error_func;
>                 unsigned strict;
>                 unsigned verbose;
>                 enum fsck_msg_type *msg_type;
>                 struct oidset skip_oids;
>                 struct oidset gitmodules_found;
>                 struct oidset gitmodules_done;
>                 struct oidset gitattributes_found;
>                 struct oidset gitattributes_done;
>                 kh_oid_map_t *object_names;
>         };
> 
> should gain a repository pointer.  After all, these oidset and oidmap
> are about objects in one particular repository.

That's fair, and indeed this was my first attempt at solving the
dependency on `the_repository`. The reason why I discarded that approach
though is the way that we currenlty initialize `struct fsck_options` via
a set of macros. And as we often do this in file-static scope we don't
have a repository available there.

I had a second look at this though and have now introduced a new
function to initialize the options instead. It's a tiny bit ugly, and
ideally we'd just not have the options in global scope in the first
place. But I guess that overall it's okayish.

Will update and send the revised version soonish.

Thanks!

Patrick

```
