Git development
 help / color / mirror / Atom feed
* [PATCH 4/7] pack-bitmap: iterate object sources when opening bitmaps
From: Patrick Steinhardt @ 2026-07-09  8:35 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-odb-for-each-object-filter-v1-0-82fe014b12b3@pks.im>

When opening a bitmap for a repository we perform two steps:

  - We first look for a multi-pack index bitmap in any of the object
    sources connected to the repository.

  - We then look for a packfile bitmap in any of the packfiles of any of
    the object sources.

Both of these steps thus iterate through object sources themselves, one
via `odb_prepare_alternates()` and one via `repo_for_each_pack()`. This
layout makes it hard to introduce a way to open the bitmap of one
specific object source, which is functionality that we'll require in a
subsequent commit.

Reverse the loop so that we instead loop through all sources in the
outer loop, and then for each source we try to load its bitmap via
either the multi-pack index or via a packfile.

Note that this changes the precedence of bitmaps in one specific edge
case: when an earlier object source only has a packfile bitmap, but a
later source has a multi-pack index bitmap, we now pick the packfile
bitmap of the earlier source. Previously, a multi-pack index bitmap from
any source would have taken precedence over all packfile bitmaps. Given
that object sources are ordered such that the local source comes first,
this arguably is an improvement, as we now prefer local bitmaps over
bitmaps in alternates. Furthermore, we already warn about repositories
that have multiple bitmaps, so this setup is broken and thus arguably
not worth worrying about too much.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pack-bitmap.c | 65 ++++++++++++++++++++++++++---------------------------------
 1 file changed, 29 insertions(+), 36 deletions(-)

diff --git a/pack-bitmap.c b/pack-bitmap.c
index eda38a5433..0e3e18a557 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -680,60 +680,53 @@ static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git,
 	return 0;
 }
 
-static int open_pack_bitmap(struct repository *r,
-			    struct bitmap_index *bitmap_git)
+static int open_bitmap_for_source(struct odb_source_packed *source,
+				  struct bitmap_index *bitmap_git)
 {
-	struct packed_git *p;
+	struct multi_pack_index *midx = get_multi_pack_index(source);
+	struct packfile_list_entry *e;
 	int ret = -1;
 
-	repo_for_each_pack(r, p) {
-		if (open_pack_bitmap_1(bitmap_git, p) == 0) {
-			ret = 0;
-			/*
-			 * The only reason to keep looking is to report
-			 * duplicates.
-			 */
-			if (!trace2_is_enabled())
-				break;
-		}
+	if (midx && !open_midx_bitmap_1(bitmap_git, midx))
+		ret = 0;
+
+	for (e = packfile_store_get_packs(source); e; e = e->next) {
+		/*
+		 * When tracing is enabled we want to keep looking to report
+		 * duplicates even if we have already found a bitmap.
+		 */
+		if (!ret && !trace2_is_enabled())
+			break;
+
+		if (open_pack_bitmap_1(bitmap_git, e->pack))
+			continue;
+		ret = 0;
 	}
 
 	return ret;
 }
 
-static int open_midx_bitmap(struct repository *r,
-			    struct bitmap_index *bitmap_git)
+static int open_bitmap(struct repository *r,
+		       struct bitmap_index *bitmap_git)
 {
 	struct odb_source *source;
-	int ret = -1;
+	int found = 0;
 
 	assert(!bitmap_git->map);
 
 	odb_prepare_alternates(r->objects);
 	for (source = r->objects->sources; source; source = source->next) {
 		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct multi_pack_index *midx = get_multi_pack_index(files->packed);
-		if (midx && !open_midx_bitmap_1(bitmap_git, midx))
-			ret = 0;
-	}
-	return ret;
-}
-
-static int open_bitmap(struct repository *r,
-		       struct bitmap_index *bitmap_git)
-{
-	int found;
 
-	assert(!bitmap_git->map);
+		found |= !open_bitmap_for_source(files->packed, bitmap_git);
 
-	found = !open_midx_bitmap(r, bitmap_git);
-
-	/*
-	 * these will all be skipped if we opened a midx bitmap; but run it
-	 * anyway if tracing is enabled to report the duplicates
-	 */
-	if (!found || trace2_is_enabled())
-		found |= !open_pack_bitmap(r, bitmap_git);
+		/*
+		 * The only reason to keep looking after having found a bitmap
+		 * is to report duplicates.
+		 */
+		if (found && !trace2_is_enabled())
+			break;
+	}
 
 	return found ? 0 : -1;
 }

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 3/7] pack-bitmap: allow aborting iteration of bitmapped objects
From: Patrick Steinhardt @ 2026-07-09  8:35 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-odb-for-each-object-filter-v1-0-82fe014b12b3@pks.im>

In a subsequent commit we'll lift iteration of bitmapped objects into
the "packed" backend and make it accessible via `odb_for_each_object()`.
The calling convention for that function is that the callback may return
a non-zero exit code, and if so we'll abort iteration. This is currently
impossible to realize though, as `for_each_bitmapped_object()` will
ignore any return value and just churn through all objects completely.

This doesn't matter to the callers of `for_each_bitmapped_object()`, as
there's only one of them in git-cat-file(1), and the callbacks we pass
always return zero. But once we move the logic into the generic
infrastructure it becomes a latent bug waiting to happen.

Refactor the code so that the return value of the `show_reach` callback
is not ignored anymore. Instead, returning a non-zero value will cause
us to abort iteration in both `show_objects_for_type()` and in
`for_each_bitmapped_object()`.

Note though that there's a second user of `show_objects_for_type()` with
`traverse_bitmap_commit_list()`, and that function does indeed invoke
callbacks that may return non-zero. This non-zero return value never had
any effect at all though, and the callbacks that return non-zero values
are only ever invoked via `traverse_bitmap_commit_list()`. Consequently,
we adapt them to always return 0.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/pack-objects.c |  2 +-
 builtin/rev-list.c     |  2 +-
 pack-bitmap.c          | 31 +++++++++++++++++++++----------
 pack-bitmap.h          |  3 ++-
 4 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index ea5eab4cf8..8ff92c5272 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1909,7 +1909,7 @@ static int add_object_entry_from_bitmap(const struct object_id *oid,
 		return 0;
 
 	create_object_entry(oid, type, name_hash, 0, 0, pack, offset);
-	return 1;
+	return 0;
 }
 
 struct pbase_tree_cache {
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 8f63003709..02818b81c6 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -486,7 +486,7 @@ static int show_object_fast(
 	void *payload UNUSED)
 {
 	fprintf(stdout, "%s\n", oid_to_hex(oid));
-	return 1;
+	return 0;
 }
 
 static void print_disk_usage(off_t size)
diff --git a/pack-bitmap.c b/pack-bitmap.c
index a47c231632..eda38a5433 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -1695,7 +1695,7 @@ static void init_type_iterator(struct ewah_or_iterator *it,
 	}
 }
 
-static void show_objects_for_type(
+static int show_objects_for_type(
 	struct bitmap_index *bitmap_git,
 	struct bitmap *objects,
 	enum object_type object_type,
@@ -1704,6 +1704,7 @@ static void show_objects_for_type(
 {
 	size_t i = 0;
 	uint32_t offset;
+	int ret;
 
 	struct ewah_or_iterator it;
 	eword_t filter;
@@ -1749,11 +1750,17 @@ static void show_objects_for_type(
 
 			hash = bitmap_name_hash(bitmap_git, index_pos);
 
-			show_reach(&oid, object_type, 0, hash, pack, ofs, payload);
+			ret = show_reach(&oid, object_type, 0, hash, pack, ofs, payload);
+			if (ret)
+				goto out;
 		}
 	}
 
+	ret = 0;
+
+out:
 	ewah_or_iterator_release(&it);
+	return ret;
 }
 
 static int in_bitmapped_pack(struct bitmap_index *bitmap_git,
@@ -2062,6 +2069,12 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
 			      show_reachable_fn show_reach,
 			      void *payload)
 {
+	const enum object_type types[] = {
+		OBJ_COMMIT,
+		OBJ_TREE,
+		OBJ_BLOB,
+		OBJ_TAG,
+	};
 	struct bitmap *filtered_bitmap = NULL;
 	uint32_t objects_nr;
 	size_t full_word_count;
@@ -2086,14 +2099,12 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
 		goto out;
 	}
 
-	show_objects_for_type(bitmap_git, filtered_bitmap,
-			      OBJ_COMMIT, show_reach, payload);
-	show_objects_for_type(bitmap_git, filtered_bitmap,
-			      OBJ_TREE, show_reach, payload);
-	show_objects_for_type(bitmap_git, filtered_bitmap,
-			      OBJ_BLOB, show_reach, payload);
-	show_objects_for_type(bitmap_git, filtered_bitmap,
-			      OBJ_TAG, show_reach, payload);
+	for (size_t i = 0; i < ARRAY_SIZE(types); i++) {
+		ret = show_objects_for_type(bitmap_git, filtered_bitmap,
+					    types[i], show_reach, payload);
+		if (ret)
+			goto out;
+	}
 
 	ret = 0;
 out:
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 47935eb24e..ae8dc491ac 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -93,7 +93,8 @@ struct list_objects_filter_options;
 /*
  * Filter bitmapped objects and iterate through all resulting objects,
  * executing `show_reach` for each of them. Returns `-1` in case the filter is
- * not supported, `0` otherwise.
+ * not supported, `0` otherwise. Aborts iteration and bubbles up the return
+ * value in case `show_reach()` returns non-zero.
  */
 int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
 			      const struct list_objects_filter_options *filter,

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 2/7] pack-bitmap: mark object filter as `const`
From: Patrick Steinhardt @ 2026-07-09  8:35 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-odb-for-each-object-filter-v1-0-82fe014b12b3@pks.im>

The function `for_each_bitmapped_object()` accepts an optional object
filter. This filter is never modified by the function, but is not
declared as `const`. Fix this.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pack-bitmap.c | 6 +++---
 pack-bitmap.h | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/pack-bitmap.c b/pack-bitmap.c
index 35774b6f0c..a47c231632 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -1976,7 +1976,7 @@ static void filter_bitmap_object_type(struct bitmap_index *bitmap_git,
 static int filter_bitmap(struct bitmap_index *bitmap_git,
 			 struct object_list *tip_objects,
 			 struct bitmap *to_filter,
-			 struct list_objects_filter_options *filter)
+			 const struct list_objects_filter_options *filter)
 {
 	if (!filter || filter->choice == LOFC_DISABLED)
 		return 0;
@@ -2027,7 +2027,7 @@ static int filter_bitmap(struct bitmap_index *bitmap_git,
 	return -1;
 }
 
-static int can_filter_bitmap(struct list_objects_filter_options *filter)
+static int can_filter_bitmap(const struct list_objects_filter_options *filter)
 {
 	return !filter_bitmap(NULL, NULL, NULL, filter);
 }
@@ -2058,7 +2058,7 @@ static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git,
 }
 
 int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
-			      struct list_objects_filter_options *filter,
+			      const struct list_objects_filter_options *filter,
 			      show_reachable_fn show_reach,
 			      void *payload)
 {
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 19a8655457..47935eb24e 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -96,7 +96,7 @@ struct list_objects_filter_options;
  * not supported, `0` otherwise.
  */
 int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
-			      struct list_objects_filter_options *filter,
+			      const struct list_objects_filter_options *filter,
 			      show_reachable_fn show_reach,
 			      void *payload);
 

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 1/7] odb/source-packed: improve lookup when enumerating objects
From: Patrick Steinhardt @ 2026-07-09  8:35 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-odb-for-each-object-filter-v1-0-82fe014b12b3@pks.im>

When iterating through packed objects via `odb_for_each_object()` we
do so via two different mechanisms:

  - When a multi-pack index is available we use that one to efficiently
    loop through all objects.

  - We then loop through all packfiles that aren't covered by a
    multi-pack index.

Regardless of which mechanism we use, we then iterate through all the
objects indexed by the respective data structure. Curiously though,
while we use the indices for enumerating the objects, we completely
ignore it for the actual object lookup. Instead, we call into the
generic `odb_source_read_object_info()` function, which will itself
consult the indices to figure out where the object in question even
lives.

This has two consequences:

  - It's inefficient, as we basically have to figure out the position of
    the object a second time.

  - It's subtly wrong, as it may now happen that a specific object will
    be looked up via a different pack in case it exists multiple times.

Fix the issue by using `packed_object_info()` directly. While at it,
rename the `store` variable to `source`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/odb/source-packed.c b/odb/source-packed.c
index 0edea5356d..9cfa02b7a2 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -143,7 +143,7 @@ static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_f
 }
 
 static int for_each_prefixed_object_in_midx(
-	struct odb_source_packed *store,
+	struct odb_source_packed *source,
 	struct multi_pack_index *m,
 	const struct odb_for_each_object_options *opts,
 	struct odb_source_packed_for_each_object_wrapper_data *data)
@@ -170,6 +170,7 @@ static int for_each_prefixed_object_in_midx(
 		 */
 		for (i = first; i < num; i++) {
 			const struct object_id *current = NULL;
+			struct packed_git *pack;
 			struct object_id oid;
 
 			current = nth_midxed_object_oid(&oid, m, i);
@@ -177,9 +178,8 @@ static int for_each_prefixed_object_in_midx(
 			if (!match_hash(len, opts->prefix->hash, current->hash))
 				break;
 
-			if (opts->flags) {
+			if (opts->flags || data->request) {
 				uint32_t pack_id = nth_midxed_pack_int_id(m, i);
-				struct packed_git *pack;
 
 				if (prepare_midx_pack(m, pack_id)) {
 					pack_errors = true;
@@ -193,9 +193,9 @@ static int for_each_prefixed_object_in_midx(
 
 			if (data->request) {
 				struct object_info oi = *data->request;
+				off_t offset = nth_midxed_offset(m, i);
 
-				ret = odb_source_read_object_info(&store->base, current,
-								  &oi, 0);
+				ret = packed_object_info(source, pack, offset, &oi);
 				if (ret)
 					goto out;
 
@@ -219,7 +219,7 @@ static int for_each_prefixed_object_in_midx(
 }
 
 static int for_each_prefixed_object_in_pack(
-	struct odb_source_packed *store,
+	struct odb_source_packed *source,
 	struct packed_git *p,
 	const struct odb_for_each_object_options *opts,
 	struct odb_source_packed_for_each_object_wrapper_data *data)
@@ -246,8 +246,9 @@ static int for_each_prefixed_object_in_pack(
 
 		if (data->request) {
 			struct object_info oi = *data->request;
+			off_t offset = nth_packed_object_offset(p, i);
 
-			ret = odb_source_read_object_info(&store->base, &oid, &oi, 0);
+			ret = packed_object_info(source, p, offset, &oi);
 			if (ret)
 				goto out;
 

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 0/7] odb: introduce object filters to `odb_for_each_object()`
From: Patrick Steinhardt @ 2026-07-09  8:35 UTC (permalink / raw)
  To: git

Hi,

this patch series introduces object filters to `odb_for_each_object()`.
The intent of this is to make `git cat-file --batch-all-objects` work
with pluggable object databases. Right now it doesn't because it reaches
into internals of the "packed" backend to efficiently handle bitmapped
objects.

The series is built on top of f85a7e6620 (Start Git 2.56 cycle,
2026-07-06) with ps/odb-drop-whence at 8a7ad23e11 (odb: document object
info fields, 2026-07-02) merged into it.

Thanks!

Patrick

---
Patrick Steinhardt (7):
      odb/source-packed: improve lookup when enumerating objects
      pack-bitmap: mark object filter as `const`
      pack-bitmap: allow aborting iteration of bitmapped objects
      pack-bitmap: iterate object sources when opening bitmaps
      pack-bitmap: introduce function to open bitmap for a single source
      odb: introduce object filters to `odb_for_each_object()`
      builtin/cat-file: filter objects via object database

 builtin/cat-file.c     |  76 +++-----------------------------
 builtin/pack-objects.c |   2 +-
 builtin/rev-list.c     |   2 +-
 odb.h                  |  12 ++++++
 odb/source-packed.c    |  77 ++++++++++++++++++++++++++++++---
 pack-bitmap.c          | 115 ++++++++++++++++++++++++++++---------------------
 pack-bitmap.h          |  10 ++++-
 7 files changed, 164 insertions(+), 130 deletions(-)


---
base-commit: 3c8e2790f2ce15e8b5d4b4e6ced711b12649f32a
change-id: 20260708-pks-odb-for-each-object-filter-13286fa3523d


^ permalink raw reply

* [PATCH 7/7] refs: remove remaining uses of `the_repository`
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

There are still a couple of callsites that use `the_repository`. Convert
these to instead use a repository injected by the caller. This allows us
to remove `USE_THE_REPOSITORY_VARIABLE`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 branch.c                   |  2 +-
 builtin/branch.c           | 14 +++++++++-----
 builtin/check-ref-format.c |  2 +-
 builtin/checkout.c         |  2 +-
 builtin/merge.c            |  2 +-
 builtin/worktree.c         |  8 ++++----
 refs.c                     | 23 +++++++++--------------
 refs.h                     |  5 +++--
 8 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/branch.c b/branch.c
index b2ac403b19..4f38905bad 100644
--- a/branch.c
+++ b/branch.c
@@ -372,7 +372,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
  */
 int validate_branchname(const char *name, struct strbuf *ref)
 {
-	if (check_branch_ref(ref, name)) {
+	if (check_branch_ref(the_repository, ref, name)) {
 		int code = die_message(_("'%s' is not a valid branch name"), name);
 		advise_if_enabled(ADVICE_REF_SYNTAX,
 				  _("See 'git help check-ref-format'"));
diff --git a/builtin/branch.c b/builtin/branch.c
index c8fddf7f94..be26ec0750 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -259,7 +259,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 		char *target = NULL;
 		int flags = 0;
 
-		copy_branchname(&bname, argv[i], allowed_interpret);
+		copy_branchname(the_repository, &bname,
+				argv[i], allowed_interpret);
 		free(name);
 		name = mkpathdup(fmt, bname.buf);
 
@@ -581,7 +582,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 	int recovery = 0, oldref_usage = 0;
 	struct worktree **worktrees = get_worktrees(the_repository);
 
-	if (check_branch_ref(&oldref, oldname)) {
+	if (check_branch_ref(the_repository, &oldref, oldname)) {
 		/*
 		 * Bad name --- this could be an attempt to rename a
 		 * ref that we used to allow to be created by accident.
@@ -898,7 +899,8 @@ int cmd_branch(int argc,
 				die(_("cannot give description to detached HEAD"));
 			branch_name = head;
 		} else if (argc == 1) {
-			copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+			copy_branchname(the_repository, &buf, argv[0],
+					INTERPRET_BRANCH_LOCAL);
 			branch_name = buf.buf;
 		} else {
 			die(_("cannot edit description of more than one branch"));
@@ -941,7 +943,8 @@ int cmd_branch(int argc,
 		if (!argc)
 			branch = branch_get(NULL);
 		else if (argc == 1) {
-			copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+			copy_branchname(the_repository, &buf, argv[0],
+					INTERPRET_BRANCH_LOCAL);
 			branch = branch_get(buf.buf);
 		} else
 			die(_("too many arguments to set new upstream"));
@@ -971,7 +974,8 @@ int cmd_branch(int argc,
 		if (!argc)
 			branch = branch_get(NULL);
 		else if (argc == 1) {
-			copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
+			copy_branchname(the_repository, &buf, argv[0],
+					INTERPRET_BRANCH_LOCAL);
 			branch = branch_get(buf.buf);
 		} else
 			die(_("too many arguments to unset upstream"));
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index e42b0444ea..fd1c9c0e0c 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -45,7 +45,7 @@ static int check_ref_format_branch(const char *arg)
 	int nongit;
 
 	setup_git_directory_gently(the_repository, &nongit);
-	if (check_branch_ref(&sb, arg) ||
+	if (check_branch_ref(the_repository, &sb, arg) ||
 	    !skip_prefix(sb.buf, "refs/heads/", &name))
 		die("'%s' is not a valid branch name", arg);
 	printf("%s\n", name);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index aee84ca897..55e3a89a85 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -805,7 +805,7 @@ static void setup_branch_path(struct branch_info *branch)
 			   &branch->oid, &branch->refname, 0))
 		repo_get_oid_committish(the_repository, branch->name, &branch->oid);
 
-	copy_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
+	copy_branchname(the_repository, &buf, branch->name, INTERPRET_BRANCH_LOCAL);
 	if (strcmp(buf.buf, branch->name)) {
 		free(branch->name);
 		branch->name = xstrdup(buf.buf);
diff --git a/builtin/merge.c b/builtin/merge.c
index 5b46a596f0..58d1b7bb07 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -553,7 +553,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
 	char *found_ref = NULL;
 	int len, early;
 
-	copy_branchname(&bname, remote, 0);
+	copy_branchname(the_repository, &bname, remote, 0);
 	remote = bname.buf;
 
 	oidclr(&branch_head, the_repository->hash_algo);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 0689b3d3e0..6397e149a8 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -481,7 +481,7 @@ static int add_worktree(const char *path, const char *refname,
 	worktrees = NULL;
 
 	/* is 'refname' a branch or commit? */
-	if (!opts->detach && !check_branch_ref(&symref, refname) &&
+	if (!opts->detach && !check_branch_ref(the_repository, &symref, refname) &&
 	    refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) {
 		is_branch = 1;
 		if (!opts->force)
@@ -650,7 +650,7 @@ static void print_preparing_worktree_line(int detach,
 		fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch);
 	} else {
 		struct strbuf s = STRBUF_INIT;
-		if (!detach && !check_branch_ref(&s, branch) &&
+		if (!detach && !check_branch_ref(the_repository, &s, branch) &&
 		    refs_ref_exists(get_main_ref_store(the_repository), s.buf))
 			fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"),
 				  branch);
@@ -772,7 +772,7 @@ static char *dwim_branch(const char *path, char **new_branch)
 	char *branchname = xstrndup(s, n);
 	struct strbuf ref = STRBUF_INIT;
 
-	branch_exists = !check_branch_ref(&ref, branchname) &&
+	branch_exists = !check_branch_ref(the_repository, &ref, branchname) &&
 			refs_ref_exists(get_main_ref_store(the_repository),
 					ref.buf);
 	strbuf_release(&ref);
@@ -869,7 +869,7 @@ static int add(int ac, const char **av, const char *prefix,
 		new_branch = new_branch_force;
 
 		if (!opts.force &&
-		    !check_branch_ref(&symref, new_branch) &&
+		    !check_branch_ref(the_repository, &symref, new_branch) &&
 		    refs_ref_exists(get_main_ref_store(the_repository), symref.buf))
 			die_if_checked_out(symref.buf, 0);
 		strbuf_release(&symref);
diff --git a/refs.c b/refs.c
index d9957a266c..92d5df5b71 100644
--- a/refs.c
+++ b/refs.c
@@ -2,8 +2,6 @@
  * The backend-independent part of the reference module.
  */
 
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "git-compat-util.h"
 #include "abspath.h"
 #include "advice.h"
@@ -744,14 +742,15 @@ static char *substitute_branch_name(struct repository *r,
 	return NULL;
 }
 
-void copy_branchname(struct strbuf *sb, const char *name,
+void copy_branchname(struct repository *repo,
+		     struct strbuf *sb, const char *name,
 		     enum interpret_branch_kind allowed)
 {
 	int len = strlen(name);
 	struct interpret_branch_name_options options = {
 		.allowed = allowed
 	};
-	int used = repo_interpret_branch_name(the_repository, name, len, sb,
+	int used = repo_interpret_branch_name(repo, name, len, sb,
 					      &options);
 
 	if (used < 0)
@@ -759,10 +758,10 @@ void copy_branchname(struct strbuf *sb, const char *name,
 	strbuf_add(sb, name + used, len - used);
 }
 
-int check_branch_ref(struct strbuf *sb, const char *name)
+int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name)
 {
 	if (startup_info->have_repository)
-		copy_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
+		copy_branchname(repo, sb, name, INTERPRET_BRANCH_LOCAL);
 	else
 		strbuf_addstr(sb, name);
 
@@ -3326,9 +3325,9 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
 	return ret;
 }
 
-static int has_worktrees(void)
+static int has_worktrees(struct repository *repo)
 {
-	struct worktree **worktrees = get_worktrees(the_repository);
+	struct worktree **worktrees = get_worktrees(repo);
 	int ret = 0;
 	size_t i;
 
@@ -3373,12 +3372,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	 * Worktrees complicate the migration because every worktree has a
 	 * separate ref storage. While it should be feasible to implement, this
 	 * is pushed out to a future iteration.
-	 *
-	 * TODO: we should really be passing the caller-provided repository to
-	 * `has_worktrees()`, but our worktree subsystem doesn't yet support
-	 * that.
 	 */
-	if (has_worktrees()) {
+	if (has_worktrees(repo)) {
 		strbuf_addstr(errbuf, "migrating repositories with worktrees is not supported yet");
 		ret = -1;
 		goto done;
@@ -3503,7 +3498,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	 * repository format so that clients will use the new ref store.
 	 * We also need to swap out the repository's main ref store.
 	 */
-	initialize_repository_version(the_repository, hash_algo_by_ptr(repo->hash_algo), format, 1);
+	initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format, 1);
 
 	/*
 	 * Unset the old ref store and release it. `get_main_ref_store()` will
diff --git a/refs.h b/refs.h
index a381022c77..9979446d15 100644
--- a/refs.h
+++ b/refs.h
@@ -234,7 +234,8 @@ char *repo_default_branch_name(struct repository *r, int quiet);
  * If "allowed" is non-zero, restrict the set of allowed expansions. See
  * repo_interpret_branch_name() for details.
  */
-void copy_branchname(struct strbuf *sb, const char *name,
+void copy_branchname(struct repository *repo,
+		     struct strbuf *sb, const char *name,
 		     enum interpret_branch_kind allowed);
 
 /*
@@ -243,7 +244,7 @@ void copy_branchname(struct strbuf *sb, const char *name,
  *
  * The return value is "0" if the result is valid, and "-1" otherwise.
  */
-int check_branch_ref(struct strbuf *sb, const char *name);
+int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name);
 
 /*
  * Similar for a tag name in refs/tags/.

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 6/7] worktree: pass repository to public functions
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

Refactor remaining public functions that still depend on
`the_repository` to instead receive a repository as parameter. This
allows us to get rid of `USE_THE_REPOSITORY_VARIABLE`.

Adapt callers accordingly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 branch.c                  |   4 +-
 builtin/branch.c          |   2 +-
 builtin/config.c          |   2 +-
 builtin/fsck.c            |   6 +--
 builtin/gc.c              |   2 +-
 builtin/notes.c           |   2 +-
 builtin/receive-pack.c    |   2 +-
 builtin/reflog.c          |   4 +-
 builtin/refs.c            |   2 +-
 builtin/worktree.c        |  24 +++++-----
 reachable.c               |   4 +-
 ref-filter.c              |   2 +-
 refs.c                    |   2 +-
 revision.c                |   6 +--
 setup.c                   |   7 +--
 submodule.c               |   2 +-
 t/helper/test-ref-store.c |   2 +-
 worktree.c                | 115 ++++++++++++++++++++++++++--------------------
 worktree.h                |  27 +++++++----
 19 files changed, 120 insertions(+), 97 deletions(-)

diff --git a/branch.c b/branch.c
index 243db7d0fc..b2ac403b19 100644
--- a/branch.c
+++ b/branch.c
@@ -394,7 +394,7 @@ static void prepare_checked_out_branches(void)
 		return;
 	initialized_checked_out_branches = 1;
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 
 	while (worktrees[i]) {
 		char *old, *wt_gitdir;
@@ -846,7 +846,7 @@ void remove_branch_state(struct repository *r, int verbose)
 
 void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
-	struct worktree **worktrees = get_worktrees();
+	struct worktree **worktrees = get_worktrees(the_repository);
 
 	for (int i = 0; worktrees[i]; i++) {
 		if (worktrees[i]->is_current && ignore_current_worktree)
diff --git a/builtin/branch.c b/builtin/branch.c
index 1572a4f9ef..c8fddf7f94 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -579,7 +579,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 	const char *interpreted_oldname = NULL;
 	const char *interpreted_newname = NULL;
 	int recovery = 0, oldref_usage = 0;
-	struct worktree **worktrees = get_worktrees();
+	struct worktree **worktrees = get_worktrees(the_repository);
 
 	if (check_branch_ref(&oldref, oldname)) {
 		/*
diff --git a/builtin/config.c b/builtin/config.c
index 8d8ec0beea..0882899c3f 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -974,7 +974,7 @@ static void location_options_init(struct config_location_options *opts,
 		opts->source.file = opts->file_to_free = repo_git_path(the_repository, "config");
 		opts->source.scope = CONFIG_SCOPE_LOCAL;
 	} else if (opts->use_worktree_config) {
-		struct worktree **worktrees = get_worktrees();
+		struct worktree **worktrees = get_worktrees(the_repository);
 		if (the_repository->repository_format_worktree_config)
 			opts->source.file = opts->file_to_free =
 				repo_git_path(the_repository, "config.worktree");
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 76b723f36d..a6c054e45b 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -632,7 +632,7 @@ static void snapshot_refs(struct repository *repo,
 	refs_for_each_ref_ext(get_main_ref_store(repo),
 			      snapshot_ref, &data, &opts);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(repo);
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
 		struct strbuf refname = STRBUF_INIT;
@@ -685,7 +685,7 @@ static void process_refs(struct repository *repo, struct snapshot *snap)
 	}
 
 	if (include_reflogs) {
-		worktrees = get_worktrees();
+		worktrees = get_worktrees(repo);
 		for (p = worktrees; *p; p++) {
 			struct worktree *wt = *p;
 
@@ -1121,7 +1121,7 @@ int cmd_fsck(int argc,
 		verify_index_checksum = 1;
 		verify_ce_order = 1;
 
-		worktrees = get_worktrees();
+		worktrees = get_worktrees(repo);
 		for (p = worktrees; *p; p++) {
 			struct worktree *wt = *p;
 			struct index_state istate =
diff --git a/builtin/gc.c b/builtin/gc.c
index d32af422af..46999a99ab 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -412,7 +412,7 @@ static int worktree_prune_condition(struct gc_config *cfg)
 	while (limit && (d = readdir_skip_dot_and_dotdot(dir))) {
 		char *wtpath;
 		strbuf_reset(&buf);
-		if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date))
+		if (should_prune_worktree(the_repository, d->d_name, &buf, &wtpath, expiry_date))
 			limit--;
 		free(wtpath);
 	}
diff --git a/builtin/notes.c b/builtin/notes.c
index 962df867c8..9f1f0ec840 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -989,7 +989,7 @@ static int merge(int argc, const char **argv, const char *prefix,
 				"NOTES_MERGE_PARTIAL", &result_oid, NULL,
 				0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		worktrees = get_worktrees();
+		worktrees = get_worktrees(the_repository);
 		wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
 					notes_ref);
 		if (wt)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 19eb6a1b61..b246c1ccae 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1503,7 +1503,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	struct object_id *old_oid = &cmd->old_oid;
 	struct object_id *new_oid = &cmd->new_oid;
 	int do_update_worktree = 0;
-	struct worktree **worktrees = get_worktrees();
+	struct worktree **worktrees = get_worktrees(the_repository);
 	const struct worktree *worktree =
 		find_shared_symref(worktrees, "HEAD", name);
 
diff --git a/builtin/reflog.c b/builtin/reflog.c
index dcbfe89339..1211c58fa4 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -250,7 +250,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix,
 		struct string_list_item *item;
 		struct worktree **worktrees, **p;
 
-		worktrees = get_worktrees();
+		worktrees = get_worktrees(the_repository);
 		for (p = worktrees; *p; p++) {
 			if (single_worktree && !(*p)->is_current)
 				continue;
@@ -374,7 +374,7 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix,
 		struct string_list_item *item;
 		struct worktree **worktrees, **p;
 
-		worktrees = get_worktrees();
+		worktrees = get_worktrees(the_repository);
 		for (p = worktrees; *p; p++) {
 			if (single_worktree && !(*p)->is_current)
 				continue;
diff --git a/builtin/refs.c b/builtin/refs.c
index a9ca2058ee..5cd21c25fe 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -113,7 +113,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	repo_config(repo, git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(repo);
 
-	worktrees = get_worktrees_without_reading_head();
+	worktrees = get_worktrees_without_reading_head(repo);
 	for (size_t i = 0; worktrees[i]; i++)
 		ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
 				 &fsck_refs_options, worktrees[i]);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d21c43fde3..0689b3d3e0 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -226,7 +226,7 @@ static void prune_worktrees(void)
 	while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
 		char *path;
 		strbuf_reset(&reason);
-		if (should_prune_worktree(d->d_name, &reason, &path, expire))
+		if (should_prune_worktree(the_repository, d->d_name, &reason, &path, expire))
 			prune_worktree(d->d_name, reason.buf);
 		else if (path)
 			string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name);
@@ -475,7 +475,7 @@ static int add_worktree(const char *path, const char *refname,
 	struct ref_store *wt_refs;
 	struct repo_config_values *cfg = repo_config_values(the_repository);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	check_candidate_path(path, opts->force, worktrees, "add");
 	free_worktrees(worktrees);
 	worktrees = NULL;
@@ -539,7 +539,8 @@ static int add_worktree(const char *path, const char *refname,
 
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-	write_worktree_linking_files(sb_git.buf, sb.buf, opts->relative_paths);
+	write_worktree_linking_files(the_repository, sb_git.buf,
+				     sb.buf, opts->relative_paths);
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
@@ -547,7 +548,7 @@ static int add_worktree(const char *path, const char *refname,
 	/*
 	 * Set up the ref store of the worktree and create the HEAD reference.
 	 */
-	wt = get_linked_worktree(name, 1);
+	wt = get_linked_worktree(the_repository, name, 1);
 	if (!wt) {
 		ret = error(_("could not find created worktree '%s'"), name);
 		goto done;
@@ -1103,7 +1104,7 @@ static int list(int ac, const char **av, const char *prefix,
 	else if (!line_terminator && !porcelain)
 		die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
 	else {
-		struct worktree **worktrees = get_worktrees();
+		struct worktree **worktrees = get_worktrees(the_repository);
 		int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i;
 		struct worktree_display *display = NULL;
 
@@ -1146,7 +1147,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix,
 	if (ac != 1)
 		usage_with_options(git_worktree_lock_usage, options);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
 		die(_("'%s' is not a working tree"), av[0]);
@@ -1183,7 +1184,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix,
 	if (ac != 1)
 		usage_with_options(git_worktree_unlock_usage, options);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
 		die(_("'%s' is not a working tree"), av[0]);
@@ -1269,7 +1270,7 @@ static int move_worktree(int ac, const char **av, const char *prefix,
 	strbuf_addstr(&dst, path);
 	free(path);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
 		die(_("'%s' is not a working tree"), av[0]);
@@ -1394,7 +1395,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix,
 	if (ac != 1)
 		usage_with_options(git_worktree_remove_usage, options);
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
 		die(_("'%s' is not a working tree"), av[0]);
@@ -1456,8 +1457,9 @@ static int repair(int ac, const char **av, const char *prefix,
 	ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
 	p = ac > 0 ? av : self;
 	for (; *p; p++)
-		repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths);
-	repair_worktrees(report_repair, &rc, use_relative_paths);
+		repair_worktree_at_path(the_repository, *p, report_repair,
+					&rc, use_relative_paths);
+	repair_worktrees(the_repository, report_repair, &rc, use_relative_paths);
 	return rc;
 }
 
diff --git a/reachable.c b/reachable.c
index 101cfc2727..be87f487d8 100644
--- a/reachable.c
+++ b/reachable.c
@@ -62,7 +62,7 @@ static void add_rebase_files(struct rev_info *revs)
 		"rebase-merge/autostash",
 		"rebase-merge/orig-head",
 	};
-	struct worktree **worktrees = get_worktrees();
+	struct worktree **worktrees = get_worktrees(the_repository);
 
 	for (struct worktree **wt = worktrees; *wt; wt++) {
 		char *wt_gitdir = get_worktree_git_dir(*wt);
@@ -319,7 +319,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
 
 	/* detached HEAD is not included in the list above */
 	refs_head_ref(get_main_ref_store(the_repository), add_one_ref, revs);
-	other_head_refs(add_one_ref, revs);
+	other_head_refs(the_repository, add_one_ref, revs);
 
 	/* rebase autostash and orig-head */
 	add_rebase_files(revs);
diff --git a/ref-filter.c b/ref-filter.c
index 284796c49b..29aca08ce7 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2402,7 +2402,7 @@ static void lazy_init_worktree_map(void)
 	if (ref_to_worktree_map.worktrees)
 		return;
 
-	ref_to_worktree_map.worktrees = get_worktrees();
+	ref_to_worktree_map.worktrees = get_worktrees(the_repository);
 	hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0);
 	populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees);
 }
diff --git a/refs.c b/refs.c
index 1d24637891..d9957a266c 100644
--- a/refs.c
+++ b/refs.c
@@ -3328,7 +3328,7 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
 
 static int has_worktrees(void)
 {
-	struct worktree **worktrees = get_worktrees();
+	struct worktree **worktrees = get_worktrees(the_repository);
 	int ret = 0;
 	size_t i;
 
diff --git a/revision.c b/revision.c
index 0c95edef59..7dd40a31d3 100644
--- a/revision.c
+++ b/revision.c
@@ -1711,7 +1711,7 @@ static void add_other_reflogs_to_pending(struct all_refs_cb *cb)
 {
 	struct worktree **worktrees, **p;
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
 
@@ -1837,7 +1837,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags)
 	if (revs->single_worktree)
 		return;
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(the_repository);
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
 		struct index_state istate = INDEX_STATE_INIT(revs->repo);
@@ -2813,7 +2813,7 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
 			struct all_refs_cb cb;
 
 			init_all_refs_cb(&cb, revs, *flags);
-			other_head_refs(handle_one_ref, &cb);
+			other_head_refs(the_repository, handle_one_ref, &cb);
 		}
 		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--branches")) {
diff --git a/setup.c b/setup.c
index 0de56a074f..505e8d7bf2 100644
--- a/setup.c
+++ b/setup.c
@@ -2650,7 +2650,8 @@ static void create_object_directory(struct repository *repo)
 	strbuf_release(&path);
 }
 
-static void separate_git_dir(const char *git_dir, const char *git_link)
+static void separate_git_dir(struct repository *repo,
+			     const char *git_dir, const char *git_link)
 {
 	struct stat st;
 
@@ -2666,7 +2667,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
 
 		if (rename(src, git_dir))
 			die_errno(_("unable to move %s to %s"), src, git_dir);
-		repair_worktrees_after_gitdir_move(src);
+		repair_worktrees_after_gitdir_move(repo, src);
 	}
 
 	write_file(git_link, "gitdir: %s", git_dir);
@@ -2823,7 +2824,7 @@ int init_db(struct repository *repo,
 
 		set_git_dir(repo, real_git_dir, 1);
 		git_dir = repo_get_git_dir(repo);
-		separate_git_dir(git_dir, original_git_dir);
+		separate_git_dir(repo, git_dir, original_git_dir);
 	}
 	else {
 		set_git_dir(repo, git_dir, 1);
diff --git a/submodule.c b/submodule.c
index 93d0361072..c6dda4d156 100644
--- a/submodule.c
+++ b/submodule.c
@@ -2494,7 +2494,7 @@ static void relocate_single_git_dir_into_superproject(const char *path,
 	if (validate_submodule_path(path) < 0)
 		exit(128);
 
-	if (submodule_uses_worktrees(path))
+	if (submodule_uses_worktrees(the_repository, path))
 		die(_("relocate_gitdir for submodule '%s' with "
 		      "more than one worktree not supported"), path);
 
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 3866d0aca4..5a9a3053d9 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -84,7 +84,7 @@ static const char **get_store(const char **argv, struct ref_store **refs)
 
 		*refs = repo_get_submodule_ref_store(the_repository, gitdir);
 	} else if (skip_prefix(argv[0], "worktree:", &gitdir)) {
-		struct worktree **p, **worktrees = get_worktrees();
+		struct worktree **p, **worktrees = get_worktrees(the_repository);
 
 		for (p = worktrees; *p; p++) {
 			struct worktree *wt = *p;
diff --git a/worktree.c b/worktree.c
index ebbf9e27e9..cbf95328a3 100644
--- a/worktree.c
+++ b/worktree.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
@@ -139,7 +138,8 @@ static struct worktree *get_main_worktree(struct repository *repo,
 	return worktree;
 }
 
-struct worktree *get_linked_worktree(const char *id,
+struct worktree *get_linked_worktree(struct repository *repo,
+				     const char *id,
 				     int skip_reading_head)
 {
 	struct worktree *worktree = NULL;
@@ -149,7 +149,7 @@ struct worktree *get_linked_worktree(const char *id,
 	if (!id)
 		die("Missing linked worktree name");
 
-	repo_common_path_append(the_repository, &path, "worktrees/%s/gitdir", id);
+	repo_common_path_append(repo, &path, "worktrees/%s/gitdir", id);
 	if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 		/* invalid gitdir file */
 		goto done;
@@ -163,7 +163,7 @@ struct worktree *get_linked_worktree(const char *id,
 	}
 
 	CALLOC_ARRAY(worktree, 1);
-	worktree->repo = the_repository;
+	worktree->repo = repo;
 	worktree->path = strbuf_detach(&worktree_path, NULL);
 	worktree->id = xstrdup(id);
 	worktree->is_current = is_current_worktree(worktree);
@@ -203,7 +203,7 @@ static struct worktree **get_worktrees_internal(struct repository *repo,
 		while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
 			struct worktree *linked = NULL;
 
-			if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) {
+			if ((linked = get_linked_worktree(repo, d->d_name, skip_reading_head))) {
 				ALLOC_GROW(list, counter + 1, alloc);
 				list[counter++] = linked;
 			}
@@ -216,14 +216,14 @@ static struct worktree **get_worktrees_internal(struct repository *repo,
 	return list;
 }
 
-struct worktree **get_worktrees(void)
+struct worktree **get_worktrees(struct repository *repo)
 {
-	return get_worktrees_internal(the_repository, 0);
+	return get_worktrees_internal(repo, 0);
 }
 
-struct worktree **get_worktrees_without_reading_head(void)
+struct worktree **get_worktrees_without_reading_head(struct repository *repo)
 {
-	return get_worktrees_internal(the_repository, 1);
+	return get_worktrees_internal(repo, 1);
 }
 
 char *get_worktree_git_dir(const struct worktree *wt)
@@ -336,7 +336,7 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
 	if (wt->prune_reason_valid)
 		return wt->prune_reason;
 
-	if (should_prune_worktree(wt->id, &reason, &path, expire))
+	if (should_prune_worktree(wt->repo, wt->id, &reason, &path, expire))
 		wt->prune_reason = strbuf_detach(&reason, NULL);
 	wt->prune_reason_valid = 1;
 
@@ -447,7 +447,8 @@ void update_worktree_location(struct worktree *wt, const char *path_,
 	strbuf_realpath(&path, path_, 1);
 	strbuf_addf(&dotgit, "%s/.git", path.buf);
 	if (fspathcmp(wt->path, path.buf)) {
-		write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+		write_worktree_linking_files(wt->repo, dotgit.buf,
+					     gitdir.buf, use_relative_paths);
 
 		free(wt->path);
 		wt->path = strbuf_detach(&path, NULL);
@@ -535,7 +536,8 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
 	return NULL;
 }
 
-int submodule_uses_worktrees(const char *path)
+int submodule_uses_worktrees(struct repository *repo,
+			     const char *path)
 {
 	char *submodule_gitdir;
 	struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
@@ -544,7 +546,7 @@ int submodule_uses_worktrees(const char *path)
 	int ret = 0;
 	struct repository_format format = REPOSITORY_FORMAT_INIT;
 
-	submodule_gitdir = repo_submodule_path(the_repository,
+	submodule_gitdir = repo_submodule_path(repo,
 					       path, "%s", "");
 	if (!submodule_gitdir)
 		return 0;
@@ -597,13 +599,14 @@ void strbuf_worktree_ref(const struct worktree *wt,
 	strbuf_addstr(sb, refname);
 }
 
-int other_head_refs(refs_for_each_cb fn, void *cb_data)
+int other_head_refs(struct repository *repo,
+		    refs_for_each_cb fn, void *cb_data)
 {
 	struct worktree **worktrees, **p;
 	struct strbuf refname = STRBUF_INIT;
 	int ret = 0;
 
-	worktrees = get_worktrees();
+	worktrees = get_worktrees(repo);
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
 		struct object_id oid;
@@ -614,7 +617,7 @@ int other_head_refs(refs_for_each_cb fn, void *cb_data)
 
 		strbuf_reset(&refname);
 		strbuf_worktree_ref(wt, &refname, "HEAD");
-		if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+		if (refs_resolve_ref_unsafe(get_main_ref_store(repo),
 					    refname.buf,
 					    RESOLVE_REF_READING,
 					    &oid, &flag)) {
@@ -687,7 +690,8 @@ static void repair_gitfile(struct worktree *wt,
 
 	if (repair) {
 		fn(0, wt->path, repair, cb_data);
-		write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+		write_worktree_linking_files(wt->repo, dotgit.buf,
+					     gitdir.buf, use_relative_paths);
 	}
 
 done:
@@ -707,9 +711,10 @@ static void repair_noop(int iserr UNUSED,
 	/* nothing */
 }
 
-void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
+void repair_worktrees(struct repository *repo, worktree_repair_fn fn,
+		      void *cb_data, int use_relative_paths)
 {
-	struct worktree **worktrees = get_worktrees_internal(the_repository, 1);
+	struct worktree **worktrees = get_worktrees_internal(repo, 1);
 	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
 
 	if (!fn)
@@ -745,16 +750,17 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
 	if (!file_exists(dotgit.buf))
 		goto done;
 
-	write_worktree_linking_files(dotgit.buf, gitdir.buf, is_relative_path);
+	write_worktree_linking_files(wt->repo, dotgit.buf,
+				     gitdir.buf, is_relative_path);
 done:
 	strbuf_release(&gitdir);
 	strbuf_release(&dotgit);
 	free(path);
 }
 
-void repair_worktrees_after_gitdir_move(const char *old_path)
+void repair_worktrees_after_gitdir_move(struct repository *repo, const char *old_path)
 {
-	struct worktree **worktrees = get_worktrees_internal(the_repository, 1);
+	struct worktree **worktrees = get_worktrees_internal(repo, 1);
 	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
 
 	for (; *wt; wt++)
@@ -762,7 +768,7 @@ void repair_worktrees_after_gitdir_move(const char *old_path)
 	free_worktrees(worktrees);
 }
 
-static int is_main_worktree_path(const char *path)
+static int is_main_worktree_path(struct repository *repo, const char *path)
 {
 	struct strbuf target = STRBUF_INIT;
 	struct strbuf maindir = STRBUF_INIT;
@@ -770,7 +776,7 @@ static int is_main_worktree_path(const char *path)
 
 	strbuf_add_real_path(&target, path);
 	strbuf_strip_suffix(&target, "/.git");
-	strbuf_add_real_path(&maindir, repo_get_common_dir(the_repository));
+	strbuf_add_real_path(&maindir, repo_get_common_dir(repo));
 	strbuf_strip_suffix(&maindir, "/.git");
 	cmp = fspathcmp(maindir.buf, target.buf);
 
@@ -821,7 +827,8 @@ static ssize_t infer_backlink(struct repository *repo,
  * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
  * the worktree's path.
  */
-void repair_worktree_at_path(const char *path,
+void repair_worktree_at_path(struct repository *repo,
+			     const char *path,
 			     worktree_repair_fn fn, void *cb_data,
 			     int use_relative_paths)
 {
@@ -837,7 +844,7 @@ void repair_worktree_at_path(const char *path,
 	if (!fn)
 		fn = repair_noop;
 
-	if (is_main_worktree_path(path))
+	if (is_main_worktree_path(repo, path))
 		goto done;
 
 	strbuf_addf(&dotgit, "%s/.git", path);
@@ -846,7 +853,7 @@ void repair_worktree_at_path(const char *path,
 		goto done;
 	}
 
-	infer_backlink(the_repository, dotgit.buf, &inferred_backlink);
+	infer_backlink(repo, dotgit.buf, &inferred_backlink);
 	strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
 	dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
 	if (dotgit_contents) {
@@ -919,7 +926,8 @@ void repair_worktree_at_path(const char *path,
 
 	if (repair) {
 		fn(0, gitdir.buf, repair, cb_data);
-		write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths);
+		write_worktree_linking_files(repo, dotgit.buf,
+					     gitdir.buf, use_relative_paths);
 	}
 done:
 	free(dotgit_contents);
@@ -930,12 +938,16 @@ void repair_worktree_at_path(const char *path,
 	strbuf_release(&dotgit);
 }
 
-int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
+int should_prune_worktree(struct repository *repo,
+			  const char *id,
+			  struct strbuf *reason,
+			  char **wtpath,
+			  timestamp_t expire)
 {
 	struct stat st;
 	struct strbuf dotgit = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
-	struct strbuf repo = STRBUF_INIT;
+	struct strbuf repo_path = STRBUF_INIT;
 	struct strbuf file = STRBUF_INIT;
 	char *path = NULL;
 	int rc = 0;
@@ -945,17 +957,17 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
 
 	*wtpath = NULL;
 
-	path = repo_common_path(the_repository, "worktrees/%s", id);
-	strbuf_realpath(&repo, path, 1);
+	path = repo_common_path(repo, "worktrees/%s", id);
+	strbuf_realpath(&repo_path, path, 1);
 	FREE_AND_NULL(path);
 
-	strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
-	if (!is_directory(repo.buf)) {
+	strbuf_addf(&gitdir, "%s/gitdir", repo_path.buf);
+	if (!is_directory(repo_path.buf)) {
 		strbuf_addstr(reason, _("not a valid directory"));
 		rc = 1;
 		goto done;
 	}
-	strbuf_addf(&file, "%s/locked", repo.buf);
+	strbuf_addf(&file, "%s/locked", repo_path.buf);
 	if (file_exists(file.buf)) {
 		goto done;
 	}
@@ -999,12 +1011,12 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
 	if (is_absolute_path(path)) {
 		strbuf_addstr(&dotgit, path);
 	} else {
-		strbuf_addf(&dotgit, "%s/%s", repo.buf, path);
+		strbuf_addf(&dotgit, "%s/%s", repo_path.buf, path);
 		strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0);
 	}
 	if (!file_exists(dotgit.buf)) {
 		strbuf_reset(&file);
-		strbuf_addf(&file, "%s/index", repo.buf);
+		strbuf_addf(&file, "%s/index", repo_path.buf);
 		if (stat(file.buf, &st) || st.st_mtime <= expire) {
 			strbuf_addstr(reason, _("gitdir file points to non-existent location"));
 			rc = 1;
@@ -1016,7 +1028,7 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
 	free(path);
 	strbuf_release(&dotgit);
 	strbuf_release(&gitdir);
-	strbuf_release(&repo);
+	strbuf_release(&repo_path);
 	strbuf_release(&file);
 	return rc;
 }
@@ -1094,37 +1106,38 @@ int init_worktree_config(struct repository *r)
 	return res;
 }
 
-void write_worktree_linking_files(const char *dotgit, const char *gitdir,
+void write_worktree_linking_files(struct repository *repo,
+				  const char *dotgit, const char *gitdir,
 				  int use_relative_paths)
 {
 	struct strbuf path = STRBUF_INIT;
-	struct strbuf repo = STRBUF_INIT;
+	struct strbuf repo_path = STRBUF_INIT;
 	struct strbuf tmp = STRBUF_INIT;
 
 	strbuf_addstr(&path, dotgit);
 	strbuf_strip_suffix(&path, "/.git");
 	strbuf_realpath(&path, path.buf, 1);
-	strbuf_addstr(&repo, gitdir);
-	strbuf_strip_suffix(&repo, "/gitdir");
-	strbuf_realpath(&repo, repo.buf, 1);
+	strbuf_addstr(&repo_path, gitdir);
+	strbuf_strip_suffix(&repo_path, "/gitdir");
+	strbuf_realpath(&repo_path, repo_path.buf, 1);
 
-	if (use_relative_paths && !the_repository->repository_format_relative_worktrees) {
-		if (upgrade_repository_format(the_repository, 1) < 0)
+	if (use_relative_paths && !repo->repository_format_relative_worktrees) {
+		if (upgrade_repository_format(repo, 1) < 0)
 			die(_("unable to upgrade repository format to support relative worktrees"));
-		if (repo_config_set_gently(the_repository, "extensions.relativeWorktrees", "true"))
+		if (repo_config_set_gently(repo, "extensions.relativeWorktrees", "true"))
 			die(_("unable to set extensions.relativeWorktrees setting"));
-		the_repository->repository_format_relative_worktrees = 1;
+		repo->repository_format_relative_worktrees = 1;
 	}
 
 	if (use_relative_paths) {
-		write_file(gitdir, "%s/.git", relative_path(path.buf, repo.buf, &tmp));
-		write_file(dotgit, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp));
+		write_file(gitdir, "%s/.git", relative_path(path.buf, repo_path.buf, &tmp));
+		write_file(dotgit, "gitdir: %s", relative_path(repo_path.buf, path.buf, &tmp));
 	} else {
 		write_file(gitdir, "%s/.git", path.buf);
-		write_file(dotgit, "gitdir: %s", repo.buf);
+		write_file(dotgit, "gitdir: %s", repo_path.buf);
 	}
 
 	strbuf_release(&path);
-	strbuf_release(&repo);
+	strbuf_release(&repo_path);
 	strbuf_release(&tmp);
 }
diff --git a/worktree.h b/worktree.h
index 1075409f9a..fbb2757f5b 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,7 +28,7 @@ struct worktree {
  * The caller is responsible for freeing the memory from the returned
  * worktrees by calling free_worktrees().
  */
-struct worktree **get_worktrees(void);
+struct worktree **get_worktrees(struct repository *repo);
 
 /*
  * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
@@ -36,7 +36,7 @@ struct worktree **get_worktrees(void);
  * the HEAD ref. This is useful in contexts where it is assumed that the
  * refdb may not be in a consistent state.
  */
-struct worktree **get_worktrees_without_reading_head(void);
+struct worktree **get_worktrees_without_reading_head(struct repository *repo);
 
 /*
  * Construct a struct worktree corresponding to repo->gitdir and
@@ -47,7 +47,7 @@ struct worktree *get_current_worktree(struct repository *repo);
 /*
  * Returns 1 if linked worktrees exist, 0 otherwise.
  */
-int submodule_uses_worktrees(const char *path);
+int submodule_uses_worktrees(struct repository *repo, const char *path);
 
 /*
  * Return git dir of the worktree. Note that the path may be relative.
@@ -76,7 +76,8 @@ struct worktree *find_worktree(struct worktree **list,
  * Look up the worktree corresponding to `id`, or NULL of no such worktree
  * exists.
  */
-struct worktree *get_linked_worktree(const char *id,
+struct worktree *get_linked_worktree(struct repository *repo,
+				     const char *id,
 				     int skip_reading_head);
 
 /*
@@ -112,7 +113,8 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire);
  * `expire` defines a grace period to prune the worktree when its path
  * does not exist.
  */
-int should_prune_worktree(const char *id,
+int should_prune_worktree(struct repository *repo,
+			  const char *id,
 			  struct strbuf *reason,
 			  char **wtpath,
 			  timestamp_t expire);
@@ -142,12 +144,14 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
  * function, if non-NULL, is called with the path of the worktree and a
  * description of the repair or error, along with the callback user-data.
  */
-void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
+void repair_worktrees(struct repository *repo, worktree_repair_fn,
+		      void *cb_data, int use_relative_paths);
 
 /*
  * Repair the linked worktrees after the gitdir has been moved.
  */
-void repair_worktrees_after_gitdir_move(const char *old_path);
+void repair_worktrees_after_gitdir_move(struct repository *repo,
+					const char *old_path);
 
 /*
  * Repair the linked worktree after the gitdir has been moved.
@@ -164,7 +168,9 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
  * worktree and a description of the repair or error, along with the callback
  * user-data.
  */
-void repair_worktree_at_path(const char *, worktree_repair_fn,
+void repair_worktree_at_path(struct repository *repo,
+			     const char *path,
+			     worktree_repair_fn fn,
 			     void *cb_data, int use_relative_paths);
 
 /*
@@ -196,7 +202,7 @@ int is_shared_symref(const struct worktree *wt,
  * Similar to head_ref() for all HEADs _except_ one from the current
  * worktree, which is covered by head_ref().
  */
-int other_head_refs(refs_for_each_cb fn, void *cb_data);
+int other_head_refs(struct repository *repo, refs_for_each_cb fn, void *cb_data);
 
 int is_worktree_being_rebased(const struct worktree *wt, const char *target);
 int is_worktree_being_bisected(const struct worktree *wt, const char *target);
@@ -239,7 +245,8 @@ int init_worktree_config(struct repository *r);
  *  dotgit: "/path/to/foo/.git"
  *  gitdir: "/path/to/repo/worktrees/foo/gitdir"
  */
-void write_worktree_linking_files(const char *dotgit, const char *gitdir,
+void write_worktree_linking_files(struct repository *repo,
+				  const char *dotgit, const char *gitdir,
 				  int use_relative_paths);
 
 #endif

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 5/7] worktree: pass repository to file-local functions
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

We have a bunch of file-local functions that use `the_repository`.
Adapt them so that the repository is instead passed as a parameter so
that we can get rid of this dependency.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 worktree.c | 47 ++++++++++++++++++++++++++---------------------
 1 file changed, 26 insertions(+), 21 deletions(-)

diff --git a/worktree.c b/worktree.c
index 8b10dea179..ebbf9e27e9 100644
--- a/worktree.c
+++ b/worktree.c
@@ -111,27 +111,28 @@ static int is_main_worktree_bare(struct repository *repo)
 /**
  * get the main worktree
  */
-static struct worktree *get_main_worktree(int skip_reading_head)
+static struct worktree *get_main_worktree(struct repository *repo,
+					  int skip_reading_head)
 {
 	struct worktree *worktree = NULL;
 	struct strbuf worktree_path = STRBUF_INIT;
 
-	strbuf_add_real_path(&worktree_path, repo_get_common_dir(the_repository));
+	strbuf_add_real_path(&worktree_path, repo_get_common_dir(repo));
 	strbuf_strip_suffix(&worktree_path, "/.git");
 
 	CALLOC_ARRAY(worktree, 1);
-	worktree->repo = the_repository;
+	worktree->repo = repo;
 	worktree->path = strbuf_detach(&worktree_path, NULL);
 	worktree->is_current = is_current_worktree(worktree);
-	worktree->is_bare = (the_repository->bare_cfg == 1) ||
-		is_bare_repository(the_repository) ||
+	worktree->is_bare = (repo->bare_cfg == 1) ||
+		is_bare_repository(repo) ||
 		/*
 		 * When in a secondary worktree we have to also verify if the main
 		 * worktree is bare in $commondir/config.worktree.
 		 * This check is unnecessary if we're currently in the main worktree,
 		 * as prior checks already consulted all configs of the current worktree.
 		 */
-		(!worktree->is_current && is_main_worktree_bare(the_repository));
+		(!worktree->is_current && is_main_worktree_bare(repo));
 
 	if (!skip_reading_head)
 		add_head_info(worktree);
@@ -182,7 +183,8 @@ struct worktree *get_linked_worktree(const char *id,
  * retrieving worktree metadata that could be used when the worktree is known
  * to not be in a healthy state, e.g. when creating or repairing it.
  */
-static struct worktree **get_worktrees_internal(int skip_reading_head)
+static struct worktree **get_worktrees_internal(struct repository *repo,
+						int skip_reading_head)
 {
 	struct worktree **list = NULL;
 	struct strbuf path = STRBUF_INIT;
@@ -192,9 +194,9 @@ static struct worktree **get_worktrees_internal(int skip_reading_head)
 
 	ALLOC_ARRAY(list, alloc);
 
-	list[counter++] = get_main_worktree(skip_reading_head);
+	list[counter++] = get_main_worktree(repo, skip_reading_head);
 
-	strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(the_repository));
+	strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(repo));
 	dir = opendir(path.buf);
 	strbuf_release(&path);
 	if (dir) {
@@ -216,12 +218,12 @@ static struct worktree **get_worktrees_internal(int skip_reading_head)
 
 struct worktree **get_worktrees(void)
 {
-	return get_worktrees_internal(0);
+	return get_worktrees_internal(the_repository, 0);
 }
 
 struct worktree **get_worktrees_without_reading_head(void)
 {
-	return get_worktrees_internal(1);
+	return get_worktrees_internal(the_repository, 1);
 }
 
 char *get_worktree_git_dir(const struct worktree *wt)
@@ -707,7 +709,7 @@ static void repair_noop(int iserr UNUSED,
 
 void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
 {
-	struct worktree **worktrees = get_worktrees_internal(1);
+	struct worktree **worktrees = get_worktrees_internal(the_repository, 1);
 	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
 
 	if (!fn)
@@ -752,7 +754,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
 
 void repair_worktrees_after_gitdir_move(const char *old_path)
 {
-	struct worktree **worktrees = get_worktrees_internal(1);
+	struct worktree **worktrees = get_worktrees_internal(the_repository, 1);
 	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
 
 	for (; *wt; wt++)
@@ -786,7 +788,9 @@ static int is_main_worktree_path(const char *path)
  *
  * Returns -1 on failure and strbuf.len on success.
  */
-static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
+static ssize_t infer_backlink(struct repository *repo,
+			      const char *gitfile,
+			      struct strbuf *inferred)
 {
 	struct strbuf actual = STRBUF_INIT;
 	const char *id;
@@ -801,7 +805,7 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
 	id++; /* advance past '/' to point at <id> */
 	if (!*id)
 		goto error;
-	repo_common_path_replace(the_repository, inferred, "worktrees/%s", id);
+	repo_common_path_replace(repo, inferred, "worktrees/%s", id);
 	if (!is_directory(inferred->buf))
 		goto error;
 
@@ -842,7 +846,7 @@ void repair_worktree_at_path(const char *path,
 		goto done;
 	}
 
-	infer_backlink(dotgit.buf, &inferred_backlink);
+	infer_backlink(the_repository, dotgit.buf, &inferred_backlink);
 	strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
 	dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
 	if (dotgit_contents) {
@@ -1017,12 +1021,13 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
 	return rc;
 }
 
-static int move_config_setting(const char *key, const char *value,
+static int move_config_setting(struct repository *repo,
+			       const char *key, const char *value,
 			       const char *from_file, const char *to_file)
 {
-	if (repo_config_set_in_file_gently(the_repository, to_file, key, NULL, value))
+	if (repo_config_set_in_file_gently(repo, to_file, key, NULL, value))
 		return error(_("unable to set %s in '%s'"), key, to_file);
-	if (repo_config_set_in_file_gently(the_repository, from_file, key, NULL, NULL))
+	if (repo_config_set_in_file_gently(repo, from_file, key, NULL, NULL))
 		return error(_("unable to unset %s in '%s'"), key, from_file);
 	return 0;
 }
@@ -1058,7 +1063,7 @@ int init_worktree_config(struct repository *r)
 	 * _could_ be negating a global core.bare=true.
 	 */
 	if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
-		if ((res = move_config_setting("core.bare", "true",
+		if ((res = move_config_setting(r, "core.bare", "true",
 					       common_config_file,
 					       main_worktree_file)))
 			goto cleanup;
@@ -1070,7 +1075,7 @@ int init_worktree_config(struct repository *r)
 	 * upgrade to worktree config.
 	 */
 	if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) {
-		if ((res = move_config_setting("core.worktree", core_worktree,
+		if ((res = move_config_setting(r, "core.worktree", core_worktree,
 					       common_config_file,
 					       main_worktree_file)))
 			goto cleanup;

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 4/7] worktree: refactor code to use available repositories
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

In "worktree.c" we have lots of users of `the_repository` that already
have a repository available to them. Convert all of them to use that
repository instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 worktree.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/worktree.c b/worktree.c
index 30125827fd..8b10dea179 100644
--- a/worktree.c
+++ b/worktree.c
@@ -392,7 +392,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 	if (!is_absolute_path(wt->path)) {
 		strbuf_addf_gently(errmsg,
 				   _("'%s' file does not contain absolute path to the working tree location"),
-				   repo_common_path_replace(the_repository, &buf, "worktrees/%s/gitdir", wt->id));
+				   repo_common_path_replace(wt->repo, &buf, "worktrees/%s/gitdir", wt->id));
 		goto done;
 	}
 
@@ -414,12 +414,12 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 		goto done;
 	}
 
-	strbuf_realpath(&realpath, repo_common_path_replace(the_repository, &buf, "worktrees/%s", wt->id), 1);
+	strbuf_realpath(&realpath, repo_common_path_replace(wt->repo, &buf, "worktrees/%s", wt->id), 1);
 	ret = fspathcmp(path, realpath.buf);
 
 	if (ret)
 		strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
-				   wt->path, repo_common_path_replace(the_repository, &buf,
+				   wt->path, repo_common_path_replace(wt->repo, &buf,
 								      "worktrees/%s", wt->id));
 done:
 	free(path);
@@ -440,7 +440,7 @@ void update_worktree_location(struct worktree *wt, const char *path_,
 	if (is_main_worktree(wt))
 		BUG("can't relocate main worktree");
 
-	wt_gitdir = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id);
+	wt_gitdir = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id);
 	strbuf_realpath(&gitdir, wt_gitdir, 1);
 	strbuf_realpath(&path, path_, 1);
 	strbuf_addf(&dotgit, "%s/.git", path.buf);
@@ -658,7 +658,7 @@ static void repair_gitfile(struct worktree *wt,
 		goto done;
 	}
 
-	path = repo_common_path(the_repository, "worktrees/%s", wt->id);
+	path = repo_common_path(wt->repo, "worktrees/%s", wt->id);
 	strbuf_realpath(&repo, path, 1);
 	strbuf_addf(&dotgit, "%s/.git", wt->path);
 	strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
@@ -727,7 +727,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
 	if (is_main_worktree(wt))
 		goto done;
 
-	path = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id);
+	path = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id);
 	strbuf_realpath(&gitdir, path, 1);
 
 	if (strbuf_read_file(&dotgit, gitdir.buf, 0) < 0)
@@ -1042,7 +1042,7 @@ int init_worktree_config(struct repository *r)
 	 */
 	if (r->repository_format_worktree_config)
 		return 0;
-	if ((res = repo_config_set_gently(the_repository, "extensions.worktreeConfig", "true")))
+	if ((res = repo_config_set_gently(r, "extensions.worktreeConfig", "true")))
 		return error(_("failed to set extensions.worktreeConfig setting"));
 
 	common_config_file = xstrfmt("%s/config", r->commondir);

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 3/7] refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

We have a bunch of users of `the_repository` in the "files" backend, all
of which are trivial to convert to instead use the backend's own repo.
Do so.

There is one more dependency on global state though via `ignore_case`,
and thus we can't trivially remove `USE_THE_REPOSITORY_VARIABLE`. But
this is the only use of global state, and we want to ensure that we
don't unwittingly reintroduce a dependency on `the_repository` going
forward.

Add an extern declaration for `ignore_case` so that it becomes
accessible even without `USE_THE_REPOSITORY_VARIABLE` and drop the
define itself.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/files-backend.c | 31 +++++++++++++++++--------------
 1 file changed, 17 insertions(+), 14 deletions(-)

diff --git a/refs/files-backend.c b/refs/files-backend.c
index 3df56c25c8..09e1be838a 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "../git-compat-util.h"
@@ -29,6 +28,9 @@
 #include "../revision.h"
 #include <wildmatch.h>
 
+/* So that we can drop `USE_THE_REPOSITORY_VARIABLE`. */
+extern int ignore_case;
+
 /*
  * This backend uses the following flags in `ref_update::flags` for
  * internal bookkeeping purposes. Their numerical values must not
@@ -788,7 +790,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 	files_ref_path(refs, &ref_file, refname);
 
 retry:
-	switch (safe_create_leading_directories(the_repository, ref_file.buf)) {
+	switch (safe_create_leading_directories(refs->base.repo, ref_file.buf)) {
 	case SCLD_OK:
 		break; /* success */
 	case SCLD_EXISTS:
@@ -1164,7 +1166,8 @@ typedef int create_file_fn(const char *path, void *cb);
  * recent call of fn. fn is always called at least once, and will be
  * called more than once if it returns ENOENT or EISDIR.
  */
-static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
+static int raceproof_create_file(struct files_ref_store *refs,
+				 const char *path, create_file_fn fn, void *cb)
 {
 	/*
 	 * The number of times we will try to remove empty directories
@@ -1220,7 +1223,7 @@ static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
 			strbuf_addstr(&path_copy, path);
 
 		do {
-			scld_result = safe_create_leading_directories(the_repository, path_copy.buf);
+			scld_result = safe_create_leading_directories(refs->base.repo, path_copy.buf);
 			if (scld_result == SCLD_OK)
 				goto retry_fn;
 		} while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
@@ -1289,7 +1292,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
 	cb_data.lk   = &lock->lk;
 	cb_data.repo = refs->base.repo;
 
-	if (raceproof_create_file(ref_file.buf, create_reflock, &cb_data)) {
+	if (raceproof_create_file(refs, ref_file.buf, create_reflock, &cb_data)) {
 		unable_to_lock_message(ref_file.buf, errno, err);
 		goto error_return;
 	}
@@ -1383,7 +1386,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
 	ref_transaction_add_update(
 			transaction, r->name,
 			REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
-			null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+			null_oid(refs->base.repo->hash_algo), &r->oid, NULL, NULL, NULL,
 			NULL, NULL);
 	if (ref_transaction_commit(transaction, &err))
 		goto cleanup;
@@ -1629,7 +1632,7 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
 	files_reflog_path(refs, &path, newrefname);
 	files_reflog_path(refs, &tmp, TMP_RENAMED_LOG);
 	cb.tmp_renamed_log = tmp.buf;
-	ret = raceproof_create_file(path.buf, rename_tmp_log_callback, &cb);
+	ret = raceproof_create_file(refs, path.buf, rename_tmp_log_callback, &cb);
 	if (ret) {
 		if (errno == EISDIR)
 			error("directory not empty: %s", path.buf);
@@ -1916,13 +1919,13 @@ static int log_ref_setup(struct files_ref_store *refs,
 	char *logfile;
 
 	if (log_refs_cfg == LOG_REFS_UNSET)
-		log_refs_cfg = is_bare_repository(the_repository) ? LOG_REFS_NONE : LOG_REFS_NORMAL;
+		log_refs_cfg = is_bare_repository(refs->base.repo) ? LOG_REFS_NONE : LOG_REFS_NORMAL;
 
 	files_reflog_path(refs, &logfile_sb, refname);
 	logfile = strbuf_detach(&logfile_sb, NULL);
 
 	if (force_create || should_autocreate_reflog(log_refs_cfg, refname)) {
-		if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
+		if (raceproof_create_file(refs, logfile, open_or_create_logfile, logfd)) {
 			if (errno == ENOENT)
 				strbuf_addf(err, "unable to create directory for '%s': "
 					    "%s", logfile, strerror(errno));
@@ -1955,7 +1958,7 @@ static int log_ref_setup(struct files_ref_store *refs,
 	}
 
 	if (*logfd >= 0)
-		adjust_shared_perm(the_repository, logfile);
+		adjust_shared_perm(refs->base.repo, logfile);
 
 	free(logfile);
 	return 0;
@@ -3672,8 +3675,8 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store,
 	 *   they do not understand the reference format extension.
 	 */
 	strbuf_addf(&sb, "%s/refs", ref_store->gitdir);
-	safe_create_dir(the_repository, sb.buf, 1);
-	adjust_shared_perm(the_repository, sb.buf);
+	safe_create_dir(refs->base.repo, sb.buf, 1);
+	adjust_shared_perm(refs->base.repo, sb.buf);
 
 	/*
 	 * There is no need to create directories for common refs when creating
@@ -3685,11 +3688,11 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store,
 		 */
 		strbuf_reset(&sb);
 		files_ref_path(refs, &sb, "refs/heads");
-		safe_create_dir(the_repository, sb.buf, 1);
+		safe_create_dir(refs->base.repo, sb.buf, 1);
 
 		strbuf_reset(&sb);
 		files_ref_path(refs, &sb, "refs/tags");
-		safe_create_dir(the_repository, sb.buf, 1);
+		safe_create_dir(refs->base.repo, sb.buf, 1);
 	}
 
 	strbuf_release(&sb);

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 2/7] refs/packed: drop `USE_THE_REPOSITORY_VARIABLE`
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

There's a single user of `the_repository` in the "packed" reference
backend. Convert it to instead use the backend's repository and drop
`USE_THE_REPOSITORY_VARIABLE`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/packed-backend.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 5c49c06493..7d0a4811fe 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "../git-compat-util.h"

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 1/7] refs/packed: de-globalize handling of "core.packedRefsTimeout"
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>

When locking the "packed-refs" file we allow the user to configure a
timeout for how long we try taking the lock. This is configurable via
"core.packedRefsTimeout", which we parse in `packed_refs_lock()`.

The parsed value is stored in function-static variables though, which of
course has the effect that we'll only ever use the timeout configured in
the first packed reference store that we see. Consequently, if we ever
were to handle stores from different repositories, then we'd use the
same configuration for both stores even if they diverge.

This is of course a somewhat theoretical concern -- we don't typically
handle multiple packed stores, and even if we did it's very unlikely
that the user has configured different timeout values for each of them.
But still, this is a code smell, and an unnecessary one, too.

Fix the issue by moving the value into `struct packed_ref_store` so that
it can be parsed per store.

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

diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 499cb55dfa..5c49c06493 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -162,6 +162,13 @@ struct packed_ref_store {
 	 * `packed_ref_store`) must not be freed.
 	 */
 	struct tempfile *tempfile;
+
+	/*
+	 * Timeout when taking the "packed-refs.lock" file. configurable via
+	 * "core.packedRefsTimeout".
+	 */
+	bool timeout_configured;
+	int timeout_value;
 };
 
 /*
@@ -1233,12 +1240,10 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
 	struct packed_ref_store *refs =
 		packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
 				"packed_refs_lock");
-	static int timeout_configured = 0;
-	static int timeout_value = 1000;
 
-	if (!timeout_configured) {
-		repo_config_get_int(the_repository, "core.packedrefstimeout", &timeout_value);
-		timeout_configured = 1;
+	if (!refs->timeout_configured) {
+		repo_config_get_int(ref_store->repo, "core.packedrefstimeout", &refs->timeout_value);
+		refs->timeout_configured = true;
 	}
 
 	/*
@@ -1249,7 +1254,7 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
 	if (hold_lock_file_for_update_timeout(
 			    &refs->lock,
 			    refs->path,
-			    flags, timeout_value) < 0) {
+			    flags, refs->timeout_value) < 0) {
 		unable_to_lock_message(refs->path, errno, err);
 		return -1;
 	}

-- 
2.55.0.175.ge4962bd3d5.dirty


^ permalink raw reply related

* [PATCH 0/7] refs: remove use of `the_repository`
From: Patrick Steinhardt @ 2026-07-09  8:29 UTC (permalink / raw)
  To: git

Hi,

this patch series refactors the ref subsystem to drop uses of
`the_repository`. These patches were part of a discarded attempt to
make the initialization of the refdb eager. I guess they make sense by
themselves though, so here we go.

Note that these patches contain a slight tangent to also adapt
"worktree.c". This is one of the subsystems that caused problems with
eager refdb initialization because of `has_worktrees()`, so I refactored
this subsystem while at it.

The series is built on top of f85a7e6620 (Start Git 2.56 cycle,
2026-07-06) with ps/refs-writing-subcommands at 002fe677ca
(builtin/refs: add "rename" subcommand, 2026-07-06) merged into it.
Despite that, there's a small set of conflicts with "seen" that can be
merged like this:

diff --cc lib/setup.c
index 505e8d7bf2,d31808130b..0000000000
--- a/lib/setup.c
+++ b/lib/setup.c
@@@ -2822,15 -2847,16 +2848,16 @@@ int init_db(struct repository *repo
  		if (!exist_ok && !stat(real_git_dir, &st))
  			die(_("%s already exists"), real_git_dir);
  
- 		set_git_dir(repo, real_git_dir, 1);
+ 		apply_and_export_relative_gitdir(repo, real_git_dir, 1);
  		git_dir = repo_get_git_dir(repo);
 -		separate_git_dir(git_dir, original_git_dir);
 +		separate_git_dir(repo, git_dir, original_git_dir);
- 	}
- 	else {
- 		set_git_dir(repo, git_dir, 1);
+ 	} else {
+ 		apply_and_export_relative_gitdir(repo, git_dir, 1);
  		git_dir = repo_get_git_dir(repo);
  	}
- 	startup_info->have_repository = 1;
+ 
+ 	if (worktree)
+ 		set_git_work_tree(repo, worktree);
  
  	/*
  	 * Check to see if the repository version is right.
diff --git a/lib/refs/files-backend.c b/lib/refs/files-backend.c
index f672059333..3ba1b4eac4 100644
--- a/lib/refs/files-backend.c
+++ b/lib/refs/files-backend.c
@@ -859,7 +859,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 		} else {
 			unable_to_lock_message(ref_file.buf, myerr, err);
 			if (myerr == EEXIST) {
-				if (repo_ignore_case(the_repository) &&
+				if (repo_ignore_case(refs->base.repo) &&
 				    transaction_has_case_conflicting_update(transaction, update)) {
 					/*
 					 * In case-insensitive filesystems, ensure that conflicts within a
@@ -973,7 +973,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 		 * conflicts between 'foo' and 'Foo/bar'. So let's lowercase
 		 * the refname.
 		 */
-		if (repo_ignore_case(the_repository)) {
+		if (repo_ignore_case(refs->base.repo)) {
 			struct strbuf lower = STRBUF_INIT;
 
 			strbuf_addstr(&lower, refname);

Thanks!

Patrick

---
Patrick Steinhardt (7):
      refs/packed: de-globalize handling of "core.packedRefsTimeout"
      refs/packed: drop `USE_THE_REPOSITORY_VARIABLE`
      refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
      worktree: refactor code to use available repositories
      worktree: pass repository to file-local functions
      worktree: pass repository to public functions
      refs: remove remaining uses of `the_repository`

 branch.c                   |   6 +-
 builtin/branch.c           |  16 +++--
 builtin/check-ref-format.c |   2 +-
 builtin/checkout.c         |   2 +-
 builtin/config.c           |   2 +-
 builtin/fsck.c             |   6 +-
 builtin/gc.c               |   2 +-
 builtin/merge.c            |   2 +-
 builtin/notes.c            |   2 +-
 builtin/receive-pack.c     |   2 +-
 builtin/reflog.c           |   4 +-
 builtin/refs.c             |   2 +-
 builtin/worktree.c         |  32 +++++----
 reachable.c                |   4 +-
 ref-filter.c               |   2 +-
 refs.c                     |  23 +++----
 refs.h                     |   5 +-
 refs/files-backend.c       |  31 +++++----
 refs/packed-backend.c      |  18 +++--
 revision.c                 |   6 +-
 setup.c                    |   7 +-
 submodule.c                |   2 +-
 t/helper/test-ref-store.c  |   2 +-
 worktree.c                 | 166 +++++++++++++++++++++++++--------------------
 worktree.h                 |  27 +++++---
 25 files changed, 204 insertions(+), 169 deletions(-)


---
base-commit: f035246f779167db3506394141b59472d544af65
change-id: 20260618-pks-refs-wo-the-repository-7e43e29371ac


^ permalink raw reply related

* git fetch automatic tag fetching - confusing when on or off
From: Ondra Medek @ 2026-07-09  7:33 UTC (permalink / raw)
  To: git

Hi,
In git fetch documentation https://git-scm.com/docs/git-fetch is:

> By default, any tag that points into the histories being fetched is also fetched; the effect is to fetch tags that point at branches that you are interested in. This default behavior can be changed by using the --tags or --no-tags options or by configuring remote.<name>.tagOpt.

However, sometimes this automatic tag fetching works and sometimes not
and it's mentioned in docs when and why. See the following examples.
(I have no any git config setting regarding tag fetching set).

All these commands DO NOT automatically fetch tags:

git fetch origin master
git fetch origin master:
git fetch origin refs/heads/master
git fetch origin refs/heads/master:

While specifying :dst part of refspec DOES automatic tags fetching:

git fetch origin refs/heads/master:refs/remotes/origin/master

What is even more confusing (or maybe a bug), adding a nonexistent
refs prefix (i.e. with no real refs in local or remote repository)
DOES tags fetching even for "master" branch:

git fetch origin master refs/none/*:refs/none/*

So, it seems to me any refspec with :dst part triggers automatic tag
fetching for all refspecs? Please, document (or fix) this behaviour.

Note: I've prepared tests with
git tag tagTest master
git push --tags origin
And deleted local tag before each test
git tag -d tagTest

Thanks and best regards
Ondřej Medek

^ permalink raw reply

* [PATCH v4] t1410-reflog.sh: avoid suppressing git's exit code in pipelines
From: Gatla Vishweshwar Reddy @ 2026-07-09  5:09 UTC (permalink / raw)
  To: git; +Cc: Gatla Vishweshwar Reddy
In-Reply-To: <xmqqv7aprz8a.fsf@gitster.g>

Piping git commands directly to wc -l suppresses the exit code of
git, hiding potential failures from the test suite. Use
test_stdout_line_count instead, which handles exit code preservation
internally while keeping the test logic clean and readable.

Signed-off-by: Gatla Vishweshwar Reddy <gatlavishweshwarreddy26@gmail.com>
---

Changes in v4:
- Restored blank line between test_expect_success blocks that was
  accidentally removed in v2
- Updated commit message to accurately describe the solution

Thank you for the detailed review!

 t/t1410-reflog.sh | 26 +++++++++-----------------
 1 file changed, 9 insertions(+), 17 deletions(-)

diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index ce71f9a30a..5a40a62ba2 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -244,30 +244,22 @@ test_expect_success 'delete' '
 	test_tick &&
 	git commit -m tiger C &&

-	HEAD_entry_count=$(git reflog | wc -l) &&
-	main_entry_count=$(git reflog show main | wc -l) &&
-
-	test $HEAD_entry_count = 5 &&
-	test $main_entry_count = 5 &&
-
+	test_stdout_line_count = 5 git reflog &&
+	test_stdout_line_count = 5 git reflog show main &&

 	git reflog delete main@{1} &&
+	test_stdout_line_count = 4 git reflog show main &&
+	test_stdout_line_count = 5 git reflog &&
 	git reflog show main > output &&
-	test_line_count = $(($main_entry_count - 1)) output &&
-	test $HEAD_entry_count = $(git reflog | wc -l) &&
 	! grep ox < output &&

-	main_entry_count=$(wc -l < output) &&
-
 	git reflog delete HEAD@{1} &&
-	test $(($HEAD_entry_count -1)) = $(git reflog | wc -l) &&
-	test $main_entry_count = $(git reflog show main | wc -l) &&
-
-	HEAD_entry_count=$(git reflog | wc -l) &&
+	test_stdout_line_count = 4 git reflog &&
+	test_stdout_line_count = 4 git reflog show main &&

 	git reflog delete main@{07.04.2005.15:15:00.-0700} &&
+	test_stdout_line_count = 3 git reflog show main &&
 	git reflog show main > output &&
-	test_line_count = $(($main_entry_count - 1)) output &&
 	! grep dragon < output

 '
@@ -321,11 +313,11 @@ test_expect_success 'git reflog expire unknown reference' '
 '

 test_expect_success 'checkout should not delete log for packed ref' '
-	test $(git reflog main | wc -l) = 4 &&
+	test_stdout_line_count = 4 git reflog main &&
 	git branch foo &&
 	git pack-refs --all &&
 	git checkout foo &&
-	test $(git reflog main | wc -l) = 4
+	test_stdout_line_count = 4 git reflog main
 '

 test_expect_success 'stale dirs do not cause d/f conflicts (reflogs on)' '
--
2.54.0


^ permalink raw reply related

* Re: [PATCH v8 4/9] environment: move pager_program into repo_config_values
From: Junio C Hamano @ 2026-07-09  3:53 UTC (permalink / raw)
  To: Tian Yuchen
  Cc: git, cirnovskyv, szeder.dev, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260708160300.8852-5-cat@malon.dev>

Tian Yuchen <cat@malon.dev> writes:

> On top of that, fix a memory leak in pager.c while we are at it.

Hmph.

> @@ -75,10 +76,12 @@ static void wait_for_pager_signal(int signo)
>  
>  static int core_pager_config(const char *var, const char *value,
>  			     const struct config_context *ctx UNUSED,
> -			     void *data UNUSED)
> +			     void *data)
>  {
> +	struct repository *r = data;
> +
>  	if (!strcmp(var, "core.pager"))
> -		return git_config_string(&pager_program, var, value);
> +		return git_config_string(&repo_config_values(r)->pager_program, var, value);

Isn't this still overwriting what was in the .pager_program member
of the config values struct?  In check_pager_config() below, there
is a free() to avoid such a leak, but wouldn't this have the same
issue?

> @@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)
>  
>  	pager = getenv("GIT_PAGER");
>  	if (!pager) {
> -		if (!pager_program)
> +		if (!repo_config_values(r)->pager_program)
>  			read_early_config(r,
> -					  core_pager_config, NULL);
> -		pager = pager_program;
> +					  core_pager_config, r);
> +		pager = repo_config_values(r)->pager_program;
>  	}
>  	if (!pager)
>  		pager = getenv("PAGER");
> @@ -302,7 +305,9 @@ int check_pager_config(struct repository *r, const char *cmd)
>  
>  	read_early_config(r, pager_command_config, &data);
>  
> -	if (data.value)
> -		pager_program = data.value;
> +	if (data.value) {
> +		free(repo_config_values(r)->pager_program);
> +		repo_config_values(r)->pager_program = data.value;
> +	}
>  	return data.want;
>  }

^ permalink raw reply

* Re: [PATCH v3 11/11] builtin/receive-pack: stage incoming objects via ODB transactions
From: Junio C Hamano @ 2026-07-09  3:49 UTC (permalink / raw)
  To: Justin Tobler; +Cc: git, ps
In-Reply-To: <20260708235925.3992097-12-jltobler@gmail.com>

Justin Tobler <jltobler@gmail.com> writes:

> @@ -2027,6 +2031,7 @@ static void execute_commands_atomic(struct command *commands,
>  static void execute_commands(struct command *commands,
>  			     const char *unpacker_error,
>  			     struct shallow_info *si,
> +			     struct odb_transaction *transaction,
>  			     const struct string_list *push_options)
>  {
>  	struct check_connected_options opt = CHECK_CONNECTED_INIT;
> ...

Hidden in the context early in this function is an error return.
When unpacker_error string is non NULL, we mark all the commands in
the linked commands list as failed, and return early from this
function.

> @@ -2105,14 +2115,13 @@ static void execute_commands(struct command *commands,
>  	 * Now we'll start writing out refs, which means the objects need
>  	 * to be in their final positions so that other processes can see them.
>  	 */
> -	if (tmp_objdir_migrate(tmp_objdir) < 0) {
> +	if (odb_transaction_commit(transaction)) {
>  		for (cmd = commands; cmd; cmd = cmd->next) {
>  			if (!cmd->error_string)
>  				cmd->error_string = "unable to migrate objects to permanent storage";
>  		}
>  		return;
>  	}
> -	tmp_objdir = NULL;
>  
>  	check_aliased_updates(commands);

In the "happy case", execute_commands() would commit the transaction
before going on to do the execute_commands_{atomic,nonatomic}() that
appears later in it.

> @@ -2706,11 +2705,14 @@ int cmd_receive_pack(int argc,
>  		if (!si.nr_ours && !si.nr_theirs)
>  			shallow_update = 0;
>  		if (!delete_only(commands)) {
> -			unpack_status = unpack_with_sideband(&si);
> +			if (odb_transaction_begin(the_repository->objects, &transaction, ODB_TRANSACTION_RECEIVE))

In the "main" program, we start a transaction here, and

> +				unpack_status = "unable to start object transaction";
> +			else
> +				unpack_status = unpack_with_sideband(&si, transaction);

then call unpack_with_sideband().  It may fail.

>  			update_shallow_info(commands, &si, &ref);
>  		}
>  		use_keepalive = KEEPALIVE_ALWAYS;
> -		execute_commands(commands, unpack_status, &si,
> +		execute_commands(commands, unpack_status, &si, transaction,
>  				 &push_options);

And in such a case, execute_commands() returns without committing
the transaction.  Is there a need to add and make an
odb_transaction_abort() call or something in such a case?
Everything should be cleaned up upon process exit, and on file based
backends, we probably let the tempfile/lockfile API do their thing
to clean up, but are there other things we may want to clean up?

>  		delete_tempfile(&pack_lockfile);
>  		sigchain_push(SIGPIPE, SIG_IGN);
> @@ -2719,7 +2721,7 @@ int cmd_receive_pack(int argc,
>  		else if (report_status)
>  			report(commands, unpack_status);
>  		sigchain_pop(SIGPIPE);
> -		run_receive_hook(commands, "post-receive", 1,
> +		run_receive_hook(commands, "post-receive", 1, NULL,
>  				 &push_options);
>  		run_update_post_hook(commands);
>  		free_commands(commands);

^ permalink raw reply

* Re: [PATCH v3 08/11] odb/transaction: add transaction env interface
From: Junio C Hamano @ 2026-07-09  3:36 UTC (permalink / raw)
  To: Justin Tobler; +Cc: git, ps
In-Reply-To: <20260708235925.3992097-9-jltobler@gmail.com>

Justin Tobler <jltobler@gmail.com> writes:

> +static int odb_transaction_files_env(struct odb_transaction *base,
> +				     struct strvec *env)
> +{
> +	struct odb_transaction_files *transaction =
> +		container_of(base, struct odb_transaction_files, base);
> +
> +	odb_transaction_files_prepare(&transaction->base);

Can this fail?  The caller of us would not notice that something
went wrong, and ...

> +	strvec_pushv(env, tmp_objdir_env(transaction->objdir));

... happily ends up using transaction->objdir that may not be
appropriate for it to use if it fails, no?

> +	return 0;
> +}

^ permalink raw reply

* Re: [PATCH v3 06/11] odb/transaction: propagate begin errors
From: Junio C Hamano @ 2026-07-09  3:32 UTC (permalink / raw)
  To: Justin Tobler; +Cc: git, ps
In-Reply-To: <20260708235925.3992097-7-jltobler@gmail.com>

Justin Tobler <jltobler@gmail.com> writes:

> When `odb_transaction_begin()` is invoked, the function returns the
> transaction pointer directly. There is no way for the backend to
> signal that it failed to set up its state, such as when creating the
> temporary object directory backing the transaction.
>
> In a subsequent commit, git-receive-pack(1) starts using ODB
> transactions and needs to be able to report such failures rather
> than silently ignore them. Refactor `odb_transaction_begin()` to
> return an int error code and write the resulting transaction into an
> out parameter. Also introduce `odb_transaction_begin_or_die()` as a
> convenience for callsites that do not need to handle errors
> explicitly.
>
> Note that `odb_transaction_begin()` now returns an error when the ODB
> already has an inflight transaction pending. ODB transaction call sites
> that may encounter an inflight transaction are updated to explicitly
> handle this case.
>
> Signed-off-by: Justin Tobler <jltobler@gmail.com>
> ---
> ...
> diff --git a/odb/transaction.c b/odb/transaction.c
> index b16e07aebf..a5fba7f908 100644
> --- a/odb/transaction.c
> +++ b/odb/transaction.c
> @@ -1,15 +1,20 @@
>  #include "git-compat-util.h"
> +#include "gettext.h"
>  #include "odb/source.h"
>  #include "odb/transaction.h"
>  
> -struct odb_transaction *odb_transaction_begin(struct object_database *odb)
> +int odb_transaction_begin(struct object_database *odb,
> +			  struct odb_transaction **out)
>  {
> +	int ret;
> +
>  	if (odb->transaction)
> -		return NULL;
> +		return error(_("object database transaction already pending"));
>  
> -	odb_source_begin_transaction(odb->sources, &odb->transaction);
> +	ret = odb_source_begin_transaction(odb->sources, out);
> +	odb->transaction = *out;

Can odb_source_begin_transaction() ever fail?  If so, and when it
fails, would *out be left untouched?  I am wondering if we want

	if (!(ret = odb_source_begin_transaction(odb->sources, out)))
        	odb->transaction = *out;

or something like that.

^ permalink raw reply

* [PATCH v7 3/3] config: add "worktree" and "worktree/i" includeIf conditions
From: Chen Linxuan via B4 Relay @ 2026-07-09  2:41 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood
In-Reply-To: <20260709-includeif-worktree-v7-0-e87e705e8df6@black-desk.cn>

From: Chen Linxuan <me@black-desk.cn>

The includeIf mechanism already supports matching on the .git
directory path (gitdir) and the currently checked out branch
(onbranch).  But in multi-worktree setups the .git directory of a
linked worktree points into the main repository's .git/worktrees/
area, which makes gitdir patterns cumbersome when one wants to
include config based on the working tree's checkout path instead.

Introduce two new condition keywords:

  - worktree:<pattern> matches the working directory of the current
    worktree (the path returned by git rev-parse --show-toplevel)
    against a glob pattern.

  - worktree/i:<pattern> is the case-insensitive variant.

The implementation reuses the include_by_path() helper, passing
repo_get_work_tree_original() (added in the previous commit; it keeps
the symlink-preserving spelling of the worktree path) in place of the
gitdir.  As with gitdir, include_by_path() then matches both the
realpath and the original spelling, so a pattern may use either.  The
condition never matches in bare repositories (where there is no
worktree) or during early config reading (where no repository is
available).

Add documentation describing the new conditions, including a comparison
with extensions.worktreeConfig.  Add tests covering bare repositories,
multiple worktrees, symlinked and subdir-of-symlinked worktree paths,
case-sensitive and case-insensitive matching, early config reading,
and non-repository scenarios.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
 Documentation/config.adoc |  48 +++++++++++++
 config.c                  |   6 ++
 t/t1305-config-include.sh | 171 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 225 insertions(+)

diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 15b1a4d59347..c153da986e4a 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:
 	This is the same as `gitdir` except that matching is done
 	case-insensitively (e.g. on case-insensitive file systems)
 
+`worktree`::
+	The data that follows the keyword `worktree` and a colon is used as a
+	glob pattern. If the working directory of the current worktree matches
+	the pattern, the include condition is met.
++
+The worktree location is the path where files are checked out (as returned
+by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
+matches the `.git` directory path. In a linked worktree, the worktree path
+is the directory where that worktree's files are located, not the main
+repository's `.git` directory.
++
+The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,
+`**/`, and trailing-`/` prefix matching). This condition will never match
+in a bare repository (which has no worktree).
++
+This is useful when you want to apply configuration based on where the
+working tree is located on the filesystem. For example, a contributor who
+works on the same project both personally and as an employee can use
+different `user.name` and `user.email` values depending on which directory
+the worktree is checked out under:
++
+----
+[includeIf "worktree:/home/user/work/"]
+    path = ~/.config/git/work.inc
+[includeIf "worktree:/home/user/personal/"]
+    path = ~/.config/git/personal.inc
+----
++
+While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports
+per-worktree configuration, it stores the config inside each repository's
+`.git/config.worktree` file and requires running `git config --worktree`
+inside each worktree individually. In contrast, `includeIf "worktree:..."`
+can be set once in a global or system-level configuration file (e.g.
+`~/.config/git/config`) and applies to all repositories at once based on
+their worktree location.
+
+`worktree/i`::
+	This is the same as `worktree` except that matching is done
+	case-insensitively (e.g. on case-insensitive file systems)
+
 `onbranch`::
 	The data that follows the keyword `onbranch` and a colon is taken to be a
 	pattern with standard globbing wildcards and two additional
@@ -244,6 +284,14 @@ Example
 [includeIf "gitdir:~/to/group/"]
 	path = /path/to/foo.inc
 
+; include if the worktree is at /path/to/project-build
+[includeIf "worktree:/path/to/project-build"]
+	path = build-config.inc
+
+; include for all worktrees inside /path/to/group
+[includeIf "worktree:/path/to/group/"]
+	path = group-config.inc
+
 ; relative paths are always relative to the including
 ; file (if the condition is true); their location is not
 ; affected by the condition
diff --git a/config.c b/config.c
index 00eeeea370c9..652711ec5e0b 100644
--- a/config.c
+++ b/config.c
@@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,
 		return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
+	else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
+		return include_by_path(kvi, inc->repo ? repo_get_work_tree_original(inc->repo) : NULL,
+				       cond, cond_len, 0);
+	else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
+		return include_by_path(kvi, inc->repo ? repo_get_work_tree_original(inc->repo) : NULL,
+				       cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(inc, cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index f3892578e4ff..99eae656a3f7 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -396,4 +396,175 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di
 	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
 '
 
+# worktree: conditional include tests
+
+test_expect_success 'conditional include, worktree bare repo' '
+	git init --bare wt-bare &&
+	(
+		cd wt-bare &&
+		echo "[includeIf \"worktree:/\"]path=bar-bare" >>config &&
+		echo "[test]wtbare=1" >bar-bare &&
+		test_must_fail git config test.wtbare
+	)
+'
+
+test_expect_success 'conditional include, worktree multiple worktrees' '
+	git init wt-multi &&
+	(
+		cd wt-multi &&
+		test_commit initial &&
+		git worktree add -b linked-branch ../wt-linked HEAD &&
+		git worktree add -b prefix-branch ../wt-prefix/linked HEAD
+	) &&
+	wt_main="$(cd wt-multi && pwd)" &&
+	wt_linked="$(cd wt-linked && pwd)" &&
+	wt_prefix_parent="$(cd wt-prefix && pwd)" &&
+	cat >>wt-multi/.git/config <<-EOF &&
+	[includeIf "worktree:$wt_main"]
+		path = main-config
+	[includeIf "worktree:$wt_linked"]
+		path = linked-config
+	[includeIf "worktree:$wt_prefix_parent/"]
+		path = prefix-config
+	EOF
+	echo "[test]mainvar=main" >wt-multi/.git/main-config &&
+	echo "[test]linkedvar=linked" >wt-multi/.git/linked-config &&
+	echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config &&
+	echo main >expect &&
+	git -C wt-multi config test.mainvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-multi config test.linkedvar &&
+	test_must_fail git -C wt-multi config test.prefixvar &&
+	echo linked >expect &&
+	git -C wt-linked config test.linkedvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-linked config test.mainvar &&
+	test_must_fail git -C wt-linked config test.prefixvar &&
+	echo prefix >expect &&
+	git -C wt-prefix/linked config test.prefixvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-prefix/linked config test.mainvar &&
+	test_must_fail git -C wt-prefix/linked config test.linkedvar
+'
+
+test_expect_success SYMLINKS 'conditional include, worktree matching symlink' '
+	mkdir sym-real &&
+	ln -s sym-real sym-link &&
+	git init sym-link/repo &&
+	(
+		cd sym-link/repo &&
+		link_path="$(pwd)" &&
+		real_path="$(test-tool path-utils real_path "$link_path")" &&
+		cat >>.git/config <<-EOF &&
+		[includeIf "gitdir:$link_path/.git"]
+			path = gitdir-link
+		[includeIf "gitdir:$real_path/.git"]
+			path = gitdir-real
+		[includeIf "worktree:$link_path"]
+			path = worktree-link
+		[includeIf "worktree:$real_path"]
+			path = worktree-real
+		EOF
+		echo "[test]gitdirlink=1" >.git/gitdir-link &&
+		echo "[test]gitdirreal=1" >.git/gitdir-real &&
+		echo "[test]worktreelink=1" >.git/worktree-link &&
+		echo "[test]worktreereal=1" >.git/worktree-real &&
+		git config get test.gitdirlink &&
+		git config get test.gitdirreal &&
+		git config get test.worktreelink &&
+		git config get test.worktreereal &&
+		# from a subdirectory, the logical worktree path is recovered by
+		# stripping the below-root suffix, so both spellings still match
+		mkdir d &&
+		cd d &&
+		git config get test.worktreelink &&
+		git config get test.worktreereal
+	)
+'
+
+test_expect_success SYMLINKS 'conditional include, worktree matching symlink of a linked worktree' '
+	git init wt-main &&
+	( cd wt-main && test_commit initial ) &&
+	git -C wt-main worktree add --detach ../wt-real &&
+	ln -s wt-real wt-link &&
+	wt_main="$(cd wt-main && pwd)" &&
+	(
+		cd wt-link &&
+		link_path="$(pwd)" &&
+		real_path="$(test-tool path-utils real_path "$link_path")" &&
+		cat >>"$wt_main/.git/config" <<-EOF &&
+		[includeIf "worktree:$link_path"]
+			path = wt-link
+		[includeIf "worktree:$real_path"]
+			path = wt-real
+		EOF
+		echo "[test]wtlink=1" >"$wt_main/.git/wt-link" &&
+		echo "[test]wtreal=1" >"$wt_main/.git/wt-real" &&
+		test "$(git config get test.wtlink)" = "1" &&
+		test "$(git config get test.wtreal)" = "1"
+	)
+'
+
+test_expect_success !CASE_INSENSITIVE_FS 'conditional include, worktree, case sensitive' '
+	git init wt-case &&
+	(
+		cd wt-case &&
+		test_commit initial &&
+		wt_path="$(pwd)" &&
+		wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+		echo "[includeIf \"worktree:$wt_upper\"]path=case-inc" >>.git/config &&
+		echo "[test]wtcase=1" >.git/case-inc &&
+		test_must_fail git config test.wtcase
+	)
+'
+
+test_expect_success 'conditional include, worktree, icase' '
+	git init wt-icase &&
+	(
+		cd wt-icase &&
+		test_commit initial &&
+		wt_path="$(pwd)" &&
+		wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+		echo "[includeIf \"worktree/i:$wt_upper\"]path=icase-inc" >>.git/config &&
+		echo "[test]wticase=1" >.git/icase-inc &&
+		echo 1 >expect &&
+		git config test.wticase >actual &&
+		test_cmp expect actual
+	)
+'
+
+# The "worktree" condition cannot match during early config reading
+# because the repository object is not yet fully initialized and
+# repo_get_work_tree() returns NULL.
+test_expect_success 'conditional include, worktree does not match in early config' '
+	git init wt-early &&
+	(
+		cd wt-early &&
+		test_commit initial &&
+		wt_path="$(pwd)" &&
+		echo "[includeIf \"worktree:$wt_path\"]path=early-inc" >>.git/config &&
+		echo "[test]wtearly=1" >.git/early-inc &&
+		test-tool config read_early_config test.wtearly >actual &&
+		test_must_be_empty actual
+	)
+'
+
+# Use a loose pattern so the "present in non-worktree cases" check works
+# for Unix-style absolute paths and Windows paths like D:/a/git/...
+test_expect_success 'conditional include, worktree without repository' '
+	test_when_finished "rm -f .gitconfig config.inc" &&
+	git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
+	git config set -f config.inc foo.bar baz &&
+	git config get foo.bar &&
+	test_must_fail nongit git config get foo.bar
+'
+
+test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '
+	test_when_finished "rm -f .gitconfig config.inc" &&
+	git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
+	git config set -f config.inc foo.bar baz &&
+	git config get foo.bar &&
+	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
+'
+
 test_done

-- 
2.53.0



^ permalink raw reply related

* [PATCH v7 1/3] config: refactor include_by_gitdir() into include_by_path()
From: Chen Linxuan via B4 Relay @ 2026-07-09  2:41 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood
In-Reply-To: <20260709-includeif-worktree-v7-0-e87e705e8df6@black-desk.cn>

From: Chen Linxuan <me@black-desk.cn>

The include_by_gitdir() function matches the realpath of a given
path against a glob pattern, but its interface is tightly coupled to
the gitdir condition: it takes a struct config_options *opts and
extracts opts->git_dir internally.

Refactor it into a more generic include_by_path() helper that takes
a const char *path parameter directly, and update the gitdir and
gitdir/i callers to pass opts->git_dir explicitly.  No behavior
change, just preparing for the addition of a new worktree condition
that will reuse the same path-matching logic with a different path.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
 config.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/config.c b/config.c
index 6a0de86e3ae9..00eeeea370c9 100644
--- a/config.c
+++ b/config.c
@@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,
 	return 0;
 }
 
-static int include_by_gitdir(const struct key_value_info *kvi,
-			     const struct config_options *opts,
-			     const char *cond, size_t cond_len, int icase)
+static int include_by_path(const struct key_value_info *kvi,
+			   const char *path,
+			   const char *cond, size_t cond_len, int icase)
 {
 	struct strbuf text = STRBUF_INIT;
 	struct strbuf pattern = STRBUF_INIT;
 	size_t prefix;
 	int ret = 0;
-	const char *git_dir;
 	int already_tried_absolute = 0;
 
-	if (opts->git_dir)
-		git_dir = opts->git_dir;
-	else
+	if (!path)
 		goto done;
 
-	strbuf_realpath(&text, git_dir, 1);
+	strbuf_realpath(&text, path, 1);
 	strbuf_add(&pattern, cond, cond_len);
 	ret = prepare_include_condition_pattern(kvi, &pattern, &prefix);
 	if (ret < 0)
@@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi,
 		 * which'll do the right thing
 		 */
 		strbuf_reset(&text);
-		strbuf_add_absolute_path(&text, git_dir);
+		strbuf_add_absolute_path(&text, path);
 		already_tried_absolute = 1;
 		goto again;
 	}
@@ -400,9 +397,9 @@ static int include_condition_is_true(const struct key_value_info *kvi,
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(kvi, opts, cond, cond_len, 0);
+		return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(kvi, opts, cond, cond_len, 1);
+		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(inc, cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,

-- 
2.53.0



^ permalink raw reply related

* [PATCH v7 0/3] includeIf: add "worktree" condition for matching working tree path
From: Chen Linxuan via B4 Relay @ 2026-07-09  2:41 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood

The `includeIf` mechanism already supports matching on the `.git`
directory path (`gitdir`) and the currently checked out branch
(`onbranch`).  But in multi-worktree setups the `.git` directory of a
linked worktree points into the main repository's `.git/worktrees/`
area, which makes `gitdir` patterns cumbersome when one wants to
include config based on the working tree's checkout path instead.

Introduce two new condition keywords:

  - `worktree:<pattern>` matches the working directory of the current
    worktree against a glob pattern.
  - `worktree/i:<pattern>` is the case-insensitive variant.

Supported pattern features: glob wildcards, `**/` and `/**`, `~`
expansion, `./` relative paths, and trailing-`/` prefix matching.
The condition never matches in a bare repository.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
Changes in v7:
- Preserve the symlinked spelling of the worktree path and match
  includeIf "worktree:" against it, so the condition now matches both
  the symlinked and the real path, consistent with "gitdir:"
  (Patrick Steinhardt, v6 review).
- Split the work into a preparatory commit that stores a non-realpath
  worktree path and a follow-up that wires it into includeIf.
- Extend symlink test coverage to subdirectories and linked worktrees.
- Link to v6: https://lore.kernel.org/r/20260703-includeif-worktree-v6-0-a13893ad9a7f@black-desk.cn

Changes in v6:
- Rebase onto current `master` at Git 2.55.
- Add an in-code comment explaining why the non-repository worktree
  tests use the loose `**.path` pattern (suggested by Junio C Hamano).
- Link to v5: https://lore.kernel.org/r/20260525-includeif-worktree-v5-0-1efe525d025a@black-desk.cn

Changes in v5:
- Fix Windows CI failure: use `**` glob pattern instead of `/` in the
  "worktree without repository" tests, since `/` as a path pattern is
  Unix-specific and does not match Windows paths.
  Github CI pass: https://github.com/black-desk/git/actions/runs/26380466288
- Add a test verifying case-sensitive matching by default, with the
  `!CASE_INSENSITIVE_FS` prerequisite (suggested by Patrick Steinhardt).
- Link to v4: https://lore.kernel.org/r/20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn

Changes in v4:
- Deduplicate the worktree pattern documentation by referencing the
  gitdir syntax instead of repeating the full pattern description
  (suggested by Patrick Steinhardt).
- Add documentation comparing includeIf "worktree:" with
  extensions.worktreeConfig, including a concrete use case example
  (suggested by Phillip Wood, Junio C Hamano).
- Add a test verifying that the worktree condition does not match
  during early config reading (suggested by Patrick Steinhardt).
- Add tests for the non-repository (nongit) scenario (suggested by
  Patrick Steinhardt).
- Add a test for the case-insensitive "worktree/i" variant
- Link to v3: https://lore.kernel.org/r/20260403-includeif-worktree-v3-0-109ce5782b03@black-desk.cn

Changes in v3:
- Apply Junio's suggestion.
- Link to v2: https://lore.kernel.org/r/20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn

Changes in v2:

- Add missing signed-off-by lines.
- Link to v1: https://lore.kernel.org/r/20260401-includeif-worktree-v1-0-906db69f2c79@black-desk.cn

---
Chen Linxuan (3):
      config: refactor include_by_gitdir() into include_by_path()
      repository: keep a symlink-preserving copy of the worktree path
      config: add "worktree" and "worktree/i" includeIf conditions

 Documentation/config.adoc |  48 +++++++++++++
 config.c                  |  25 ++++---
 repository.c              |  26 +++++++
 repository.h              |  10 +++
 setup.c                   |  86 ++++++++++++++++++++++-
 t/t1305-config-include.sh | 171 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 354 insertions(+), 12 deletions(-)

Range-diff versus v6:

1:  faa10baf8deb = 1:  47ee82d6a5bd config: refactor include_by_gitdir() into include_by_path()
-:  ------------ > 2:  367d61f9b55f repository: keep a symlink-preserving copy of the worktree path
2:  ee81d7aeee56 ! 3:  67d9c31d3817 config: add "worktree" and "worktree/i" includeIf conditions
    @@ Commit message
     
         Introduce two new condition keywords:
     
    -      - worktree:<pattern> matches the realpath of the current worktree's
    -        working directory (i.e. repo_get_work_tree()) against a glob
    -        pattern.  This is the path returned by git rev-parse
    -        --show-toplevel.
    +      - worktree:<pattern> matches the working directory of the current
    +        worktree (the path returned by git rev-parse --show-toplevel)
    +        against a glob pattern.
     
           - worktree/i:<pattern> is the case-insensitive variant.
     
    -    The implementation reuses the include_by_path() helper introduced in
    -    the previous commit, passing the worktree path in place of the
    -    gitdir.  The condition never matches in bare repositories (where
    -    there is no worktree) or during early config reading (where no
    -    repository is available).
    +    The implementation reuses the include_by_path() helper, passing
    +    repo_get_work_tree_original() (added in the previous commit; it keeps
    +    the symlink-preserving spelling of the worktree path) in place of the
    +    gitdir.  As with gitdir, include_by_path() then matches both the
    +    realpath and the original spelling, so a pattern may use either.  The
    +    condition never matches in bare repositories (where there is no
    +    worktree) or during early config reading (where no repository is
    +    available).
     
         Add documentation describing the new conditions, including a comparison
         with extensions.worktreeConfig.  Add tests covering bare repositories,
    -    multiple worktrees, symlinked worktree paths, case-sensitive and
    -    case-insensitive matching, early config reading, and non-repository
    -    scenarios.
    +    multiple worktrees, symlinked and subdir-of-symlinked worktree paths,
    +    case-sensitive and case-insensitive matching, early config reading,
    +    and non-repository scenarios.
     
         Signed-off-by: Chen Linxuan <me@black-desk.cn>
     
    @@ config.c: static int include_condition_is_true(const struct key_value_info *kvi,
      	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
      		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
     +	else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
    -+		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
    ++		return include_by_path(kvi, inc->repo ? repo_get_work_tree_original(inc->repo) : NULL,
     +				       cond, cond_len, 0);
     +	else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
    -+		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
    ++		return include_by_path(kvi, inc->repo ? repo_get_work_tree_original(inc->repo) : NULL,
     +				       cond, cond_len, 1);
      	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
      		return include_by_branch(inc, cond, cond_len);
    @@ t/t1305-config-include.sh: test_expect_success 'onbranch without repository but
     +	test_must_fail git -C wt-prefix/linked config test.linkedvar
     +'
     +
    -+test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
    -+	mkdir real-wt &&
    -+	ln -s real-wt link-wt &&
    -+	git init link-wt/repo &&
    ++test_expect_success SYMLINKS 'conditional include, worktree matching symlink' '
    ++	mkdir sym-real &&
    ++	ln -s sym-real sym-link &&
    ++	git init sym-link/repo &&
     +	(
    -+		cd link-wt/repo &&
    -+		# repo->worktree resolves symlinks, so use real path in pattern
    -+		echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config &&
    -+		echo "[test]wtlink=2" >.git/bar-link &&
    -+		echo 2 >expect &&
    -+		git config test.wtlink >actual &&
    -+		test_cmp expect actual
    ++		cd sym-link/repo &&
    ++		link_path="$(pwd)" &&
    ++		real_path="$(test-tool path-utils real_path "$link_path")" &&
    ++		cat >>.git/config <<-EOF &&
    ++		[includeIf "gitdir:$link_path/.git"]
    ++			path = gitdir-link
    ++		[includeIf "gitdir:$real_path/.git"]
    ++			path = gitdir-real
    ++		[includeIf "worktree:$link_path"]
    ++			path = worktree-link
    ++		[includeIf "worktree:$real_path"]
    ++			path = worktree-real
    ++		EOF
    ++		echo "[test]gitdirlink=1" >.git/gitdir-link &&
    ++		echo "[test]gitdirreal=1" >.git/gitdir-real &&
    ++		echo "[test]worktreelink=1" >.git/worktree-link &&
    ++		echo "[test]worktreereal=1" >.git/worktree-real &&
    ++		git config get test.gitdirlink &&
    ++		git config get test.gitdirreal &&
    ++		git config get test.worktreelink &&
    ++		git config get test.worktreereal &&
    ++		# from a subdirectory, the logical worktree path is recovered by
    ++		# stripping the below-root suffix, so both spellings still match
    ++		mkdir d &&
    ++		cd d &&
    ++		git config get test.worktreelink &&
    ++		git config get test.worktreereal
    ++	)
    ++'
    ++
    ++test_expect_success SYMLINKS 'conditional include, worktree matching symlink of a linked worktree' '
    ++	git init wt-main &&
    ++	( cd wt-main && test_commit initial ) &&
    ++	git -C wt-main worktree add --detach ../wt-real &&
    ++	ln -s wt-real wt-link &&
    ++	wt_main="$(cd wt-main && pwd)" &&
    ++	(
    ++		cd wt-link &&
    ++		link_path="$(pwd)" &&
    ++		real_path="$(test-tool path-utils real_path "$link_path")" &&
    ++		cat >>"$wt_main/.git/config" <<-EOF &&
    ++		[includeIf "worktree:$link_path"]
    ++			path = wt-link
    ++		[includeIf "worktree:$real_path"]
    ++			path = wt-real
    ++		EOF
    ++		echo "[test]wtlink=1" >"$wt_main/.git/wt-link" &&
    ++		echo "[test]wtreal=1" >"$wt_main/.git/wt-real" &&
    ++		test "$(git config get test.wtlink)" = "1" &&
    ++		test "$(git config get test.wtreal)" = "1"
     +	)
     +'
     +

---
base-commit: f85a7e662054a7b0d9070e432508831afa214b47



^ permalink raw reply

* [PATCH v7 2/3] repository: keep a symlink-preserving copy of the worktree path
From: Chen Linxuan via B4 Relay @ 2026-07-09  2:41 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood
In-Reply-To: <20260709-includeif-worktree-v7-0-e87e705e8df6@black-desk.cn>

From: Chen Linxuan <me@black-desk.cn>

repo_set_worktree() stores only the realpath-resolved working directory in
repo->worktree, which discards any symlinks the user followed to get
there.  A follow-up commit needs to match that path the way "gitdir:"
does, i.e. against both the real and the symlinked spelling, which
requires the original spelling to still be available.

Add repo->worktree_original, plus a repo_get_work_tree_original()
accessor, to hold that symlink-preserving spelling.  repo_set_worktree()
derives it from the given path; for the discovered-repository case, where
the setup code has already chdir()d to the worktree root by the time
set_git_work_tree(repo, ".") runs, logical_path_from_cwd() recovers it
from $PWD instead.

repo->worktree is unchanged; repo_get_work_tree_original() has no callers
yet and is wired up in the next commit.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
 repository.c | 26 ++++++++++++++++++
 repository.h | 10 +++++++
 setup.c      | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/repository.c b/repository.c
index 73d80bcffdf5..a29d55a6fcd3 100644
--- a/repository.c
+++ b/repository.c
@@ -149,6 +149,11 @@ const char *repo_get_work_tree(struct repository *repo)
 	return repo->worktree;
 }
 
+const char *repo_get_work_tree_original(struct repository *repo)
+{
+	return repo->worktree_original;
+}
+
 static void repo_set_commondir(struct repository *repo,
 			       const char *commondir)
 {
@@ -252,8 +257,28 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)
 
 void repo_set_worktree(struct repository *repo, const char *path)
 {
+	struct strbuf worktree = STRBUF_INIT;
+
+	/*
+	 * Resolve the canonical path first. This preserves the historical
+	 * behaviour for unusable worktree paths (e.g. a bogus GIT_WORK_TREE):
+	 * strbuf_realpath() dies on error before we touch the copy below.
+	 */
 	repo->worktree = real_pathdup(path, 1);
 
+	/*
+	 * Keep a symlink-preserving copy: absolute and normalized, but not
+	 * realpath-resolved. Normalization can only fail for inputs that
+	 * realpath tolerates (the rest already died above); fall back to the
+	 * physical path so callers never see a NULL.
+	 */
+	strbuf_add_absolute_path(&worktree, path);
+	if (strbuf_normalize_path(&worktree) < 0)
+		repo->worktree_original = xstrdup(repo->worktree);
+	else
+		repo->worktree_original = strbuf_detach(&worktree, NULL);
+	strbuf_release(&worktree);
+
 	trace2_def_repo(repo);
 }
 
@@ -379,6 +404,7 @@ void repo_clear(struct repository *repo)
 	FREE_AND_NULL(repo->graft_file);
 	FREE_AND_NULL(repo->index_file);
 	FREE_AND_NULL(repo->worktree);
+	FREE_AND_NULL(repo->worktree_original);
 	FREE_AND_NULL(repo->submodule_prefix);
 	FREE_AND_NULL(repo->ref_storage_payload);
 
diff --git a/repository.h b/repository.h
index 7d649e32e7fa..f08fbfde4a07 100644
--- a/repository.h
+++ b/repository.h
@@ -114,6 +114,15 @@ struct repository {
 	 * A NULL value indicates that there is no working directory.
 	 */
 	char *worktree;
+	/*
+	 * Symlink-preserving spelling of the working directory: absolute and
+	 * normalized, but NOT realpath-resolved (keeps any symlinks the user
+	 * followed to get here). Used by includeIf "worktree:" so it can match
+	 * both the real and the symlinked spelling, the way "gitdir:" does.
+	 * Falls back to the same value as "worktree" when no logical path is
+	 * available.
+	 */
+	char *worktree_original;
 	bool worktree_initialized;
 	bool worktree_config_is_bogus;
 
@@ -221,6 +230,7 @@ const char *repo_get_object_directory(struct repository *repo);
 const char *repo_get_index_file(struct repository *repo);
 const char *repo_get_graft_file(struct repository *repo);
 const char *repo_get_work_tree(struct repository *repo);
+const char *repo_get_work_tree_original(struct repository *repo);
 
 /*
  * Define a custom repository layout. Any field can be NULL, which
diff --git a/setup.c b/setup.c
index 0de56a074f7c..fbbeb95f99db 100644
--- a/setup.c
+++ b/setup.c
@@ -1213,12 +1213,94 @@ static const char *setup_explicit_git_dir(struct repository *repo,
 	return NULL;
 }
 
+/*
+ * Do "a" and "b" refer to the same filesystem entry? Both must report a
+ * nonzero (dev,ino): some filesystems return (0,0) for unrelated paths,
+ * which would otherwise look identical.
+ */
+static int same_entry(const char *a, const char *b)
+{
+	struct stat sa, sb;
+
+	if (stat(a, &sa) || stat(b, &sb))
+		return 0;
+	return (sa.st_dev || sa.st_ino) &&
+	       sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino;
+}
+
+/*
+ * Recover the symlink-preserving spelling of the worktree root.
+ *
+ * strbuf_add_absolute_path() already consults $PWD to keep symlinks when
+ * resolving a relative path, so set_git_work_tree()'s other callers get a
+ * symlink-preserving worktree path for free.  This function exists for the
+ * discovered-repository case: setup_git_directory_gently() chdir()s to the
+ * worktree root *before* set_git_work_tree(repo, ".") runs, so by the time
+ * "." is resolved $PWD still names the caller's original directory and no
+ * longer agrees with the physical cwd, and strbuf_add_absolute_path()
+ * falls back to the realpath.  We close that gap by deriving the logical
+ * root here, from $PWD, while we still have the original physical cwd and
+ * the root offset in hand.
+ *
+ * "cwd" is the physical current directory (getcwd), and "root_len" is the
+ * length of the worktree root within it; cwd->buf[root_len..] is therefore
+ * the part of the path below the root (empty when git ran at the root).
+ *
+ * $PWD, maintained by the shell, may spell that same directory through
+ * symlinks.  If we can confirm $PWD really names cwd's directory (same
+ * device/inode) and that the below-root suffix matches, we swap the
+ * physical root prefix for $PWD's prefix and keep the user's symlinks.
+ * Only symlinks in the root prefix itself are preserved: the below-root
+ * suffix is matched byte-for-byte, so a symlink below the root is not.
+ *
+ * Returns the allocated logical path, or NULL when $PWD is missing, already
+ * physical, or untrustworthy.
+ */
+static char *logical_path_from_cwd(struct strbuf *cwd, int root_len)
+{
+	const char *pwd = getenv("PWD");
+	size_t suffix_len, pwd_len;
+	struct strbuf path = STRBUF_INIT;
+
+	if (!pwd || !is_absolute_path(pwd) || !strcmp(pwd, cwd->buf))
+		return NULL;
+	/*
+	 * $PWD is a plain environment variable: it can be set to anything,
+	 * or left stale after a chdir.  Only borrow its symlink-preserving
+	 * spelling once we prove it still points at the same directory as
+	 * the physical cwd; otherwise give up and return NULL.
+	 */
+	if (!same_entry(cwd->buf, pwd))
+		return NULL;
+
+	/*
+	 * Drop the below-root suffix from $PWD.  It must match the physical
+	 * suffix exactly; the only spelling difference we accept is in the
+	 * root prefix -- i.e. the symlinks we want to preserve.
+	 */
+	suffix_len = cwd->len - root_len;
+	pwd_len = strlen(pwd);
+	if (suffix_len) {
+		const char *suffix = cwd->buf + root_len;
+
+		if (suffix_len > pwd_len ||
+		    fspathcmp(pwd + pwd_len - suffix_len, suffix))
+			return NULL;
+		pwd_len -= suffix_len;
+	}
+
+	strbuf_add(&path, pwd, pwd_len);
+	return strbuf_detach(&path, NULL);
+}
+
 static const char *setup_discovered_git_dir(struct repository *repo,
 					    const char *gitdir,
 					    struct strbuf *cwd, int offset,
 					    struct repository_format *repo_fmt,
 					    int *nongit_ok)
 {
+	char *worktree = NULL;
+
 	if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))
 		return NULL;
 
@@ -1245,7 +1327,9 @@ static const char *setup_discovered_git_dir(struct repository *repo,
 	}
 
 	/* #0, #1, #5, #8, #9, #12, #13 */
-	set_git_work_tree(repo, ".");
+	worktree = logical_path_from_cwd(cwd, offset);
+	set_git_work_tree(repo, worktree ? worktree : ".");
+	free(worktree);
 	if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
 		set_git_dir(repo, gitdir, 0);
 	if (offset >= cwd->len)

-- 
2.53.0



^ permalink raw reply related

* [PATCH] mailmap: map Taylor Blau's work address
From: Taylor Blau @ 2026-07-09  2:26 UTC (permalink / raw)
  To: git

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
---
 .mailmap | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.mailmap b/.mailmap
index c2e3939beb..f8ede075ea 100644
--- a/.mailmap
+++ b/.mailmap
@@ -277,6 +277,7 @@ Sven Verdoolaege <skimo@kotnet.org> <skimo@liacs.nl>
 SZEDER Gábor <szeder.dev@gmail.com> <szeder@ira.uka.de>
 Tao Qingyun <taoqy@ls-a.me> <845767657@qq.com>
 Tay Ray Chuan <rctay89@gmail.com>
+Taylor Blau <me@ttaylorr.com> <ttaylorr@openai.com>
 Ted Percival <ted@midg3t.net> <ted.percival@quest.com>
 Theodore Ts'o <tytso@mit.edu>
 Thomas Ackermann <th.acker@arcor.de> <th.acker66@arcor.de>
-- 
2.55.0

^ permalink raw reply related

* Re: [PATCH 2/2] git-subtree: Bail out if we find output from Rust rewrite (test)
From: Colin Stagner @ 2026-07-09  1:59 UTC (permalink / raw)
  To: Ian Jackson, git
In-Reply-To: <20260706115816.20267-3-ijackson@chiark.greenend.org.uk>

On 7/6/26 06:58, Ian Jackson wrote:

> --- a/contrib/subtree/t/t7900-subtree.sh
> +++ b/contrib/subtree/t/t7900-subtree.sh
> @@ -439,6 +439,24 @@ test_expect_success 'split sub dir/ with --rejoin' '
>   	)
>   '
>   
> +test_expect_success 'split fail on RIIR git subtree data' '
> +	subtree_test_create_repo "$test_count" &&
> +	subtree_test_create_repo "$test_count/sub proj" &&

It may be slightly faster to create only one repo and just make orphan 
branches, like `test_create_subtree_add()` does.

> +		echo "# sabotage" >.git-subtree/config &&
> +		git add .git-subtree/config &&
> +		git commit -m sabotage &&

`test_commit()` from test-lib-functions.sh may be superior to manually 
writing and committing this file.


Colin


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox