* [GSoC Patch 4/7] repo: add path.hooks with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-07-16 1:21 UTC (permalink / raw)
To: git; +Cc: jltobler, lucasseikioshiro, K Jayatheerth
In-Reply-To: <20260716012138.6714-1-jayatheerthkulkarni2005@gmail.com>
External tool integrations and validation systems need a stable way to
identify where the repository hooks are stored. Currently, this involves
relying on `git rev-parse --git-path hooks` or querying `core.hooksPath`
manually.
Introduce `path.hooks.absolute` and `path.hooks.relative` keys to
`git repo info`. This allows tools to discover the active hooks location
natively, ensuring proper resolution regardless of whether Git is using
the standard `.git/hooks` structure or a custom `core.hooksPath` setup.
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Documentation/git-repo.adoc | 8 ++++++++
builtin/repo.c | 22 ++++++++++++++++++++++
t/t1900-repo-info.sh | 6 ++++++
3 files changed, 36 insertions(+)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 8429a44b43..7bc1c51310 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -119,6 +119,14 @@ values that they return:
`path.gitdir.relative`::
The path to the Git repository directory relative to the current working directory.
+`path.hooks.absolute`::
+ The canonical absolute path to the repository's hooks directory.
+ Respects `core.hooksPath` configuration adjustments.
+
+`path.hooks.relative`::
+ The path to the repository's hooks directory relative to the current
+ working directory. Respects `core.hooksPath` configuration adjustments.
+
`path.objects.absolute`::
The canonical absolute path to the repository's object database directory.
Respects the `GIT_OBJECT_DIRECTORY` environment override.
diff --git a/builtin/repo.c b/builtin/repo.c
index d6bdd5bcfa..c921de222d 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -122,6 +122,26 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
return 0;
}
+static int get_path_hooks_absolute(struct repository *repo, struct strbuf *buf)
+{
+ struct strbuf hooks_path = STRBUF_INIT;
+
+ repo_git_path_replace(repo, &hooks_path, "hooks");
+ format_path(buf, hooks_path.buf, startup_info->prefix, PATH_FORMAT_CANONICAL);
+ strbuf_release(&hooks_path);
+ return 0;
+}
+
+static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf)
+{
+ struct strbuf hooks_path = STRBUF_INIT;
+
+ repo_git_path_replace(repo, &hooks_path, "hooks");
+ format_path(buf, hooks_path.buf, startup_info->prefix, PATH_FORMAT_RELATIVE);
+ strbuf_release(&hooks_path);
+ return 0;
+}
+
static int get_path_objects_absolute(struct repository *repo, struct strbuf *buf)
{
const char *obj_dir = repo_get_object_directory(repo);
@@ -216,6 +236,8 @@ static const struct repo_info_field repo_info_field[] = {
{ "path.commondir.relative", get_path_commondir_relative },
{ "path.gitdir.absolute", get_path_gitdir_absolute },
{ "path.gitdir.relative", get_path_gitdir_relative },
+ { "path.hooks.absolute", get_path_hooks_absolute },
+ { "path.hooks.relative", get_path_hooks_relative },
{ "path.objects.absolute", get_path_objects_absolute },
{ "path.objects.relative", get_path_objects_relative },
{ "path.superproject-working-tree.absolute", get_path_superproject_absolute },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 260f4fde43..cd3f856d04 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -213,6 +213,12 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
'.git' \
'GIT_DIR="../.git" && export GIT_DIR'
+test_repo_info_path 'hooks standard fallback' 'hooks' '.git/hooks'
+
+test_repo_info_path 'hooks with core.hooksPath override' 'hooks' \
+ 'custom-hooks' \
+ 'git config core.hooksPath "$ROOT/custom-hooks" && mkdir -p "$ROOT/custom-hooks"'
+
test_repo_info_path 'objects standard' 'objects' '.git/objects'
test_repo_info_path 'objects with GIT_OBJECT_DIRECTORY override' 'objects' \
--
2.55.GIT
^ permalink raw reply related
* [GSoC Patch 5/7] repo: add path.index with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-07-16 1:21 UTC (permalink / raw)
To: git; +Cc: jltobler, lucasseikioshiro, K Jayatheerth
In-Reply-To: <20260716012138.6714-1-jayatheerthkulkarni2005@gmail.com>
External script workflows and formatting layers require straightforward
access to the location of the index staging file. Currently, tracking
this necessitates a legacy call to `git rev-parse --git-path index` or
`--show-toplevel` logic abstractions.
Introduce `path.index.absolute` and `path.index.relative` keys to
`git repo info`. This allows tooling utilities to discover the active
index context cleanly while scaling transparently with localized
`GIT_INDEX_FILE` environment overrides.
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Documentation/git-repo.adoc | 8 ++++++++
builtin/repo.c | 24 ++++++++++++++++++++++++
t/t1900-repo-info.sh | 6 ++++++
3 files changed, 38 insertions(+)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 7bc1c51310..3a837c573e 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -127,6 +127,14 @@ values that they return:
The path to the repository's hooks directory relative to the current
working directory. Respects `core.hooksPath` configuration adjustments.
+`path.index.absolute`::
+ The canonical absolute path to the repository's current index file.
+ Respects the `GIT_INDEX_FILE` environment override.
+
+`path.index.relative`::
+ The path to the repository's current index file relative to the current
+ working directory. Respects the `GIT_INDEX_FILE` environment override.
+
`path.objects.absolute`::
The canonical absolute path to the repository's object database directory.
Respects the `GIT_OBJECT_DIRECTORY` environment override.
diff --git a/builtin/repo.c b/builtin/repo.c
index c921de222d..66bf4c67cc 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -142,6 +142,28 @@ static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf)
return 0;
}
+static int get_path_index_absolute(struct repository *repo, struct strbuf *buf)
+{
+ const char *index_file = repo_get_index_file(repo);
+
+ if (!index_file)
+ return error(_("unable to get index file"));
+
+ format_path(buf, index_file, startup_info->prefix, PATH_FORMAT_CANONICAL);
+ return 0;
+}
+
+static int get_path_index_relative(struct repository *repo, struct strbuf *buf)
+{
+ const char *index_file = repo_get_index_file(repo);
+
+ if (!index_file)
+ return error(_("unable to get index file"));
+
+ format_path(buf, index_file, startup_info->prefix, PATH_FORMAT_RELATIVE);
+ return 0;
+}
+
static int get_path_objects_absolute(struct repository *repo, struct strbuf *buf)
{
const char *obj_dir = repo_get_object_directory(repo);
@@ -238,6 +260,8 @@ static const struct repo_info_field repo_info_field[] = {
{ "path.gitdir.relative", get_path_gitdir_relative },
{ "path.hooks.absolute", get_path_hooks_absolute },
{ "path.hooks.relative", get_path_hooks_relative },
+ { "path.index.absolute", get_path_index_absolute },
+ { "path.index.relative", get_path_index_relative },
{ "path.objects.absolute", get_path_objects_absolute },
{ "path.objects.relative", get_path_objects_relative },
{ "path.superproject-working-tree.absolute", get_path_superproject_absolute },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index cd3f856d04..04e6b8553c 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -219,6 +219,12 @@ test_repo_info_path 'hooks with core.hooksPath override' 'hooks' \
'custom-hooks' \
'git config core.hooksPath "$ROOT/custom-hooks" && mkdir -p "$ROOT/custom-hooks"'
+test_repo_info_path 'index standard' 'index' '.git/index'
+
+test_repo_info_path 'index with GIT_INDEX_FILE override' 'index' \
+ 'custom-index-file' \
+ 'GIT_INDEX_FILE="$ROOT/custom-index-file" && export GIT_INDEX_FILE'
+
test_repo_info_path 'objects standard' 'objects' '.git/objects'
test_repo_info_path 'objects with GIT_OBJECT_DIRECTORY override' 'objects' \
--
2.55.GIT
^ permalink raw reply related
* [GSoC Patch 6/7] repo: add path.grafts with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-07-16 1:21 UTC (permalink / raw)
To: git; +Cc: jltobler, lucasseikioshiro, K Jayatheerth
In-Reply-To: <20260716012138.6714-1-jayatheerthkulkarni2005@gmail.com>
External toolchains managing specialized history rewrites or legacy
history splices require access to the location of the repository grafts
file. Currently, this requires a legacy call to `git rev-parse --git-path info/grafts`.
Introduce `path.grafts.absolute` and `path.grafts.relative` keys to
`git repo info`. This allows scripting layers to query the active grafts
context cleanly while scaling transparently with active `GIT_GRAFT_FILE`
environment variable overrides.
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Documentation/git-repo.adoc | 8 ++++++++
builtin/repo.c | 24 ++++++++++++++++++++++++
t/t1900-repo-info.sh | 6 ++++++
3 files changed, 38 insertions(+)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 3a837c573e..6c962620ec 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -119,6 +119,14 @@ values that they return:
`path.gitdir.relative`::
The path to the Git repository directory relative to the current working directory.
+`path.grafts.absolute`::
+ The canonical absolute path to the repository grafts file.
+ Respects the `GIT_GRAFT_FILE` environment override.
+
+`path.grafts.relative`::
+ The path to the repository grafts file relative to the current working
+ directory. Respects the `GIT_GRAFT_FILE` environment override.
+
`path.hooks.absolute`::
The canonical absolute path to the repository's hooks directory.
Respects `core.hooksPath` configuration adjustments.
diff --git a/builtin/repo.c b/builtin/repo.c
index 66bf4c67cc..a97ad71649 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -122,6 +122,28 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
return 0;
}
+static int get_path_grafts_absolute(struct repository *repo, struct strbuf *buf)
+{
+ const char *graft_file = repo_get_graft_file(repo);
+
+ if (!graft_file)
+ return error(_("unable to get graft file"));
+
+ format_path(buf, graft_file, startup_info->prefix, PATH_FORMAT_CANONICAL);
+ return 0;
+}
+
+static int get_path_grafts_relative(struct repository *repo, struct strbuf *buf)
+{
+ const char *graft_file = repo_get_graft_file(repo);
+
+ if (!graft_file)
+ return error(_("unable to get graft file"));
+
+ format_path(buf, graft_file, startup_info->prefix, PATH_FORMAT_RELATIVE);
+ return 0;
+}
+
static int get_path_hooks_absolute(struct repository *repo, struct strbuf *buf)
{
struct strbuf hooks_path = STRBUF_INIT;
@@ -258,6 +280,8 @@ static const struct repo_info_field repo_info_field[] = {
{ "path.commondir.relative", get_path_commondir_relative },
{ "path.gitdir.absolute", get_path_gitdir_absolute },
{ "path.gitdir.relative", get_path_gitdir_relative },
+ { "path.grafts.absolute", get_path_grafts_absolute },
+ { "path.grafts.relative", get_path_grafts_relative },
{ "path.hooks.absolute", get_path_hooks_absolute },
{ "path.hooks.relative", get_path_hooks_relative },
{ "path.index.absolute", get_path_index_absolute },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 04e6b8553c..6c47989df7 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -213,6 +213,12 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
'.git' \
'GIT_DIR="../.git" && export GIT_DIR'
+test_repo_info_path 'grafts standard' 'grafts' '.git/info/grafts'
+
+test_repo_info_path 'grafts with GIT_GRAFT_FILE override' 'grafts' \
+ 'custom-graft-file' \
+ 'GIT_GRAFT_FILE="$ROOT/custom-graft-file" && export GIT_GRAFT_FILE'
+
test_repo_info_path 'hooks standard fallback' 'hooks' '.git/hooks'
test_repo_info_path 'hooks with core.hooksPath override' 'hooks' \
--
2.55.GIT
^ permalink raw reply related
* [GSoC Patch 7/7] repo: add path.git-prefix path key validation
From: K Jayatheerth @ 2026-07-16 1:21 UTC (permalink / raw)
To: git; +Cc: jltobler, lucasseikioshiro, K Jayatheerth
In-Reply-To: <20260716012138.6714-1-jayatheerthkulkarni2005@gmail.com>
Scripts and command-line prompt integrations frequently need to know their
relative depth inside a repository working tree layout. Currently, this
is retrieved using `git rev-parse --show-prefix`.
Introduce the `path.git-prefix` key to `git repo info`. This mirrors the
prefix location tracking framework as a standalone key, returning the
exact relative path offset complete with a trailing slash, or an empty
string if run directly at the repository working tree root.
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Documentation/git-repo.adoc | 5 +++++
builtin/repo.c | 12 ++++++++++++
t/t1900-repo-info.sh | 19 +++++++++++++++++++
3 files changed, 36 insertions(+)
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 6c962620ec..5ba2ab1612 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -113,6 +113,11 @@ values that they return:
The path to the Git repository's common directory relative to
the current working directory.
+`path.git-prefix`::
+ The relative path from the top-level directory of the working tree to
+ the current working directory (including a trailing slash). Outputs an
+ empty string if executed at the root of the working tree.
+
`path.gitdir.absolute`::
The canonical absolute path to the Git repository directory (the `.git` directory).
diff --git a/builtin/repo.c b/builtin/repo.c
index a97ad71649..00d5064281 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -1,3 +1,4 @@
+#include "compat/posix.h"
#define USE_THE_REPOSITORY_VARIABLE
#include "builtin.h"
@@ -100,6 +101,16 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b
return 0;
}
+static int get_path_git_prefix(struct repository *repo UNUSED, struct strbuf *buf)
+{
+ /*
+ * startup_info->prefix is NULL if we are at the working tree root.
+ * We add an empty string to ensure the buffer is cleanly initialized.
+ */
+ strbuf_addstr(buf, startup_info->prefix ? startup_info->prefix : "");
+ return 0;
+}
+
static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
{
const char *git_dir = repo_get_git_dir(repo);
@@ -278,6 +289,7 @@ static const struct repo_info_field repo_info_field[] = {
{ "object.format", get_object_format },
{ "path.commondir.absolute", get_path_commondir_absolute },
{ "path.commondir.relative", get_path_commondir_relative },
+ { "path.git-prefix", get_path_git_prefix },
{ "path.gitdir.absolute", get_path_gitdir_absolute },
{ "path.gitdir.relative", get_path_gitdir_relative },
{ "path.grafts.absolute", get_path_grafts_absolute },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 6c47989df7..3e5e42f6d3 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -207,6 +207,25 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
'.git' \
'GIT_DIR="../.git" && export GIT_DIR'
+test_expect_success 'path.git-prefix at root and in a subdirectory' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ echo "path.git-prefix=" >expect.root &&
+ git repo info path.git-prefix >actual.root &&
+ test_cmp expect.root actual.root &&
+
+ mkdir -p sub/dir &&
+ cd sub/dir &&
+
+ echo "path.git-prefix=sub/dir/" >expect.sub &&
+ git repo info path.git-prefix >actual.sub &&
+ test_cmp expect.sub actual.sub
+ )
+'
+
test_repo_info_path 'gitdir standard' 'gitdir' '.git'
test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
--
2.55.GIT
^ permalink raw reply related
* Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic
From: Junio C Hamano @ 2026-07-16 1:30 UTC (permalink / raw)
To: Pablo Sabater
Cc: git, chandrapratap3519, chriscool, eric.peijian, jltobler,
karthik.188, peff, toon
In-Reply-To: <DJZH1PLDC08G.1XTK39BO8YOVS@gmail.com>
"Pablo Sabater" <pabloosabaterr@gmail.com> writes:
> We can force "size" when only %(objectname) is requested so the
> server validates the OID, and discard the size on the client side.
>
> Because this is a cheap fix, I'll add a NEEDSWORK for the existence
> check to be done regardless of the attributes requested.
Hmph, why NEEDSWORK? Not doing so would mean that the result
lacks correctness. Why should the first version of this series
deliberately produce an incorrect result?
^ permalink raw reply
* Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic
From: Pablo Sabater @ 2026-07-16 1:47 UTC (permalink / raw)
To: Junio C Hamano, Pablo Sabater
Cc: git, chandrapratap3519, chriscool, eric.peijian, jltobler,
karthik.188, peff, toon
In-Reply-To: <xmqqmrvrk9ge.fsf@gitster.g>
On Thu Jul 16, 2026 at 3:30 AM CEST, Junio C Hamano wrote:
> "Pablo Sabater" <pabloosabaterr@gmail.com> writes:
>
>> We can force "size" when only %(objectname) is requested so the
>> server validates the OID, and discard the size on the client side.
>>
>> Because this is a cheap fix, I'll add a NEEDSWORK for the existence
>> check to be done regardless of the attributes requested.
>
> Hmph, why NEEDSWORK? Not doing so would mean that the result
> lacks correctness. Why should the first version of this series
> deliberately produce an incorrect result?
I was trying to keep the scope of this series to client-side only.
But you are right, I'll add a prep commit in this series that fixes the
behavior. no NEEDSWORK.
Thanks,
Pablo
^ permalink raw reply
* [GSoC] [Blog] week 6 & 7: Improving the new git repo command
From: K Jayatheerth @ 2026-07-16 1:52 UTC (permalink / raw)
To: GIT Mailing-list, Justin Tobler, Lucas Seiki Oshiro
In-Reply-To: <CA+rGoLePg9MHE+OcVtKo5ho8ziNp9NBWAuWc4ZEZ2kevZf5WKg@mail.gmail.com>
Hi!
My Week 6 and Week 7 GSoC blogs are live!
https://jayatheerth.com/#/blogs/gsoc/week-6
https://jayatheerth.com/#/blogs/gsoc/week-7
Feel free to give it a read and share any feedback ; )
Regards,
- K Jayatheerth
^ permalink raw reply
* Re: [GSoC Patch 7/7] repo: add path.git-prefix path key validation
From: Junio C Hamano @ 2026-07-16 3:23 UTC (permalink / raw)
To: K Jayatheerth; +Cc: git, jltobler, lucasseikioshiro
In-Reply-To: <20260716012138.6714-8-jayatheerthkulkarni2005@gmail.com>
K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
> diff --git a/builtin/repo.c b/builtin/repo.c
> index a97ad71649..00d5064281 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -1,3 +1,4 @@
> +#include "compat/posix.h"
> #define USE_THE_REPOSITORY_VARIABLE
> #include "builtin.h"
The first include must be <git-compat-util.h> or common include
files that include <git-compat-util.h> as the first thing, like
<builtin.h>.
As the file already includes <builtin.h>, extra inclusion of
<compat/posix.h> before everything else is an absolute no-no.
By the way, I do not see any "validation" in the patch as the title
claims. Perhaps retitle it to "repo: add path.git-prefix key" or
something simpler like that?
^ permalink raw reply
* Re: [PATCH v7 3/3] replay: offer an option to linearize the commit topology
From: Elijah Newren @ 2026-07-16 3:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Toon Claes, git, Johannes Schindelin
In-Reply-To: <xmqqse5km6lc.fsf@gitster.g>
On Wed, Jul 15, 2026 at 11:49 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Elijah Newren <newren@gmail.com> writes:
>
> > Concretely: I have three branches to rebase onto master; one of them
> > happens to contain a merge I'd like flattened. I add --linearize for
> > that one merge — and now all three branches are silently concatenated
> > into a single chain. That makes no sense to me, and I think won't to
> > most users.
>
> But if that is not the outcome they wanted, I fail to see why they
> would feed all three branches to a single invocation of --linearize
> in the first place. After all, the command is only doing what it
> was asked to do.
Passing several branches isn't the user asking for concatenation; it's
the user asking for replay's core feature: update many branches at
once. Adding --linearize to flatten a merge does have to join the
lines which that merge combined, but it shouldn't also weld together
branches that were never merged in the first place. The user is
combining two intended features, and the concatenation is an emergent
third behavior that neither of them implies.
(Also, please note that I'm aware of the bug you raised earlier about
dropped lines of history; my suggestion(s) don't reintroduce that
bug.)
> If that breaks because by the time you feed branchC to the machinery
> nobody remembers that A1 and A2 were already handled, _that_ is the
> problem the command needs to solve, no? I am confused.
Yes, precisely! That is the problem I want to be able to solve:
updating multiple branches which may have shared history. The current
proposed behavior feels hostile towards that. Concretely, I want to
be able to update this history:
M1 M2 M3 M4 M5
*---*---*---*---* <- main
| \
| \ A1 A2 A4 A6 A7 A8
| \-*---*---*---*---*---* <- branchA
\ \ / \
\ *---* -*---* <- branchC
\ A3 A5 C1 C2
\
\-*---* <- branchB
B1 B2
via `git replay --linearize --onto main branchA branchB branchC` to
(depending on where A4 is ordered relative to A3 & A5):
M1 M2 M3 M4 M5
*---*---*---*---* <- main
|
| A1 A2 A4 A3 A5 A7 A8
|---*---*---*---*---*---*---* <- branchA
| \
| -*---* <- branchC
| C1 C2
|
\-*---* <- branchB
B1 B2
(note that both branchA and branchC become linear with the merge
commit A6 being dropped)
In this graph:
* branchA and branchC cannot easily be replayed with separate
commands (it requires tracking starting and stopping points and
figuring out shared history).
* branchB could be done with a separate command from replaying the
other two, but _only if_ the user first verifies that it has no common
history with the other branches, and I think that's not useful
cognitive load to place on the user.
If concatenation really is the intended behavior for this patch
series, then --linearize seems like the wrong name for it: the
surprising part isn't that each branch becomes linear, it's that the
option also chains together branches that were never related.
> Or do you want to be able to tell "linearlize B, A, and C in this
> turn on top of 'master'" and M1..M5..B1'..B3'..A1'..A4'..C1'..C2' as
> the result?
No, ordered-concatenation is not something I'm interested in. I want
separate branches to stay separate, as in the second graph above. I
almost wish I hadn't even mentioned ordering, even though I labelled
it a "minor" point in my last email, because it seems to have
distracted from the real issue.
As I proposed last time, I'd be fine with erroring on multiple
positive refs as an interim step (plus associated documentation and
commit message updates) so this series lands, with per-branch
linearization as the real fix later.
^ permalink raw reply
* Re: git-last-modified(1) slower than git-log(1)?
From: Jeff King @ 2026-07-16 4:28 UTC (permalink / raw)
To: Gusted; +Cc: git, Toon Claes, Taylor Blau
In-Reply-To: <17f356ff-7bfb-47f5-b714-62a95cc8b821@codeberg.org>
On Tue, Jul 14, 2026 at 08:33:59PM +0200, Gusted wrote:
> The repository I'm currently using to evaluate the performance is
> https://codeberg.org/ziglang/zig
>
> Reproduction steps:
> 1. `git clone https://codeberg.org/ziglang/zig $(mktemp -d)`
> 2. cd to tmp directory.
> 3. `git commit-graph write --changed-paths`. As git-last-modified(1)
> makes good use of the bloom filters.
> 4. `hyperfine 'git last-modified -z -t --max-depth=0
> 80d06578ac66bce3aa0a21e9610cdb782b9a0593 -- doc/langref/' 'git log
> --name-status -c "--format=commit%x00%H %P%x00" --parents --no-renames
> -t -z 80d06578ac66bce3aa0a21e9610cdb782b9a0593 -- ":(literal)doc/langref"'`
Thanks for this concrete reproduction. I can see the same problem here.
Interestingly, if we turn off changed-paths, we get very different
results.
Without a commit graph at all, last-modified wins (this is using the zig
repo and the commands above):
- log: 150ms
- last-modified: 79ms
But with a graph and no changed-paths, they're about equal:
- log: 61ms
- last-modified: 61ms
And then with changed-paths, the log command gets much faster but
last-modified gets slower!
- log: 20ms
- last-modified: 64ms
I think there's a tradeoff in the way that last-modified uses the bloom
filters. It makes a key for every path we're interested in, and then for
each commit, we check each key to say "is this in the commit's filter?".
So if you have a subdirectory with a non-trivial number of entries (like
doc/langref here which has 290), but most commits don't touch that path
at all (only 120 out of ~39k in this case), we'll spend a lot of time
checking each key against each filter. We save ourselves opening the
trees, but at the cost of 290*39k filter comparisons).
Whereas in the git-log case, we make a filter key out of the single
pathspec we're given, and then check each commit against that. So we
only do a single filter check for each commit to narrow it down to those
120 that matter (modulo a few filter false positives).
But I don't see any reason that last-modified couldn't _also_ do that:
pre-filter the commits with a commit matching the original pathspec, and
discard most commits with a single filter check.
The hacky patch below does this, and brings my last-modified runtime
down to 16ms (a 4x improvement, and just a bit faster than git-log).
It tries to reuse the logic from revision.c, so it's doing the exact
same filtering that git-log would do. I think there are other ways to do
it. E.g., we could make our own "root" bloom key that contains all of
the paths and pre-filter with that. But it seemed to be a little slower
when I tried it (~24ms). I'd guess that the problem is that because the
bloom filter is probabilistic, if you shove too many items into a single
key you'll end getting more and more false positives. So putting all 290
entries into one key is too much, and we are better off just considering
the shared prefix.
Anyway, here's the patch. Toon, I'm not planning to take it further
immediately, but you may be interested in poking at it. It probably
needs at least:
- some light refactoring of revision.c
- tests? We don't seem to cover last-modified with changed-paths at
all, and just rely on the test-vars CI job which sets
GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS. It did pass for me with that
flag, so surely I didn't introduce any bugs. :)
- more timing exploration; e.g., might it make things worse if
doc/langref were touched in 99% of the commits? Probably not, but it
might be nice to check timings against a few repo shapes and request
depths.
- Not all pathspecs can support bloom filters (e.g., "*.c" would not).
So in theory:
git last-modified HEAD -- "*.c"
could work, but wouldn't be optimized. I don't think it _does_ work
now, because last-modified's max-depth logic complains. So it might
be a non-issue.
But I think it is solvable if we really wanted. Rather than
traversing looking for "*.c", we actually expand the pathspec in the
tip commit to a set of literal paths, and then as we traverse we
look for those paths. So we could collect all of "*.c" and then
add bloom keys for the shared prefixes. I think this does get tricky
in the general case, though. If you have "a/b/c" and "a/b/d",
looking for "a/b" is reasonable. But what if you also have "a/e"?
Should you just have a key for "a/", or both "a/b" and "a/e"?
There are some tradeoffs between how often uninteresting things in
"a/" will give us a false positive, versus the cost of checking
extra keys.
So maybe an interesting area, but given that in practice most people
will feed a single pathspec to last-modified, it's a lot easier to
just use that.
- I know that last-modified was derived from GitHub's blame-tree
implementation (which I originally wrote, but stopped paying
attention to well before it learned about changed-path filters). I
don't know if the problem was solved separately there, but it would
be worth checking. +cc Taylor
-Peff
---
diff --git a/builtin/last-modified.c b/builtin/last-modified.c
index 5478182f2e..c07169258f 100644
--- a/builtin/last-modified.c
+++ b/builtin/last-modified.c
@@ -254,6 +254,29 @@ static void pass_to_parent(struct bitmap *c,
bitmap_set(p, pos);
}
+/*
+ * revision.c already has this functionality, but it is not public
+ * and it looks up the filter itself. But probably some refactoring
+ * could make it available at the right level?
+ */
+static bool filter_contains_keyvec(const struct bloom_filter *filter,
+ struct rev_info *rev)
+{
+ /*
+ * If we have no keys, we must pessimistically assume a match.
+ */
+ if (!rev->bloom_keyvecs_nr)
+ return true;
+
+ for (int i = 0; i < rev->bloom_keyvecs_nr; i++) {
+ if (bloom_filter_contains_vec(filter,
+ rev->bloom_keyvecs[i],
+ rev->bloom_filter_settings))
+ return true;
+ }
+ return false;
+}
+
static bool maybe_changed_path(struct last_modified *lm,
struct commit *origin,
struct bitmap *active)
@@ -272,6 +295,9 @@ static bool maybe_changed_path(struct last_modified *lm,
if (!filter)
return true;
+ if (!filter_contains_keyvec(filter, &lm->rev))
+ return false;
+
hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) {
if (active && !bitmap_get(active, ent->diff_idx))
continue;
@@ -499,7 +525,22 @@ static int last_modified_init(struct last_modified *lm, struct repository *r,
return argc;
}
- lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo);
+ /*
+ * Load the bloom settings, but also convert our pathspec into
+ * bloom_keyvecs that can be used later. This helper should
+ * probably be factored out, but we don't want to do it ourselves.
+ * There is logic about which pathspecs are allowed or not that
+ * we would not want to duplicate.
+ */
+ prepare_to_use_bloom_filter(&lm->rev);
+
+ /*
+ * Even if our initial pathspecs forbid using bloom filters, we'd still
+ * use them for the literal paths we expand below in
+ * populate_paths_from_revs().
+ */
+ if (!lm->rev.bloom_filter_settings)
+ lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo);
if (populate_paths_from_revs(lm) < 0)
return -1;
diff --git a/revision.c b/revision.c
index 137a86d33b..f5b36ea2cc 100644
--- a/revision.c
+++ b/revision.c
@@ -705,7 +705,7 @@ static int convert_pathspec_to_bloom_keyvec(struct bloom_keyvec **out,
return res;
}
-static void prepare_to_use_bloom_filter(struct rev_info *revs)
+void prepare_to_use_bloom_filter(struct rev_info *revs)
{
if (!revs->commits)
return;
diff --git a/revision.h b/revision.h
index 569b3fa1cb..1f761b85d0 100644
--- a/revision.h
+++ b/revision.h
@@ -576,4 +576,6 @@ int rewrite_parents(struct rev_info *revs,
*/
struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit);
+void prepare_to_use_bloom_filter(struct rev_info *revs);
+
#endif
^ permalink raw reply related
* Re: [PATCH] remote-curl: simplify passing of push specs
From: Patrick Steinhardt @ 2026-07-16 5:27 UTC (permalink / raw)
To: René Scharfe; +Cc: Git List
In-Reply-To: <3b29757e-abcd-4235-a829-ea67c19e71d0@web.de>
On Wed, Jul 15, 2026 at 05:39:51PM +0200, René Scharfe wrote:
> On 7/15/26 8:41 AM, Patrick Steinhardt wrote:
> > On Wed, Jul 15, 2026 at 06:41:17AM +0200, René Scharfe wrote:
> >> diff --git a/remote-curl.c b/remote-curl.c
> >> index 9e614c5567..2c35dd5240 100644
> >> --- a/remote-curl.c
> >> +++ b/remote-curl.c
> >> @@ -1340,10 +1340,9 @@ static void parse_get(const char *arg)
> >> fflush(stdout);
> >> }
> >>
> >> -static int push_dav(int nr_spec, const char **specs)
> >> +static int push_dav(const char **specs)
> >> {
> >> struct child_process child = CHILD_PROCESS_INIT;
> >> - size_t i;
> >>
> >> child.git_cmd = 1;
> >> strvec_push(&child.args, "http-push");
> >
> > I wonder whether the interface would be even better if we simply passed
> > around a `const struct strvec *` directly. That makes it explicit what
> > kind of guarantees we have, and all transitive callers already have one
> > available anyway.
>
> You mean that passing a managed array instead of a plain NULL-terminated
> one would make more places visibly safer at almost no cost?
>
> >> @@ -1353,15 +1352,14 @@ static int push_dav(int nr_spec, const char **specs)
> >> if (options.verbosity > 1)
> >> strvec_push(&child.args, "--verbose");
> >> strvec_push(&child.args, url.buf);
> >> - for (i = 0; i < nr_spec; i++)
> >> - strvec_push(&child.args, specs[i]);
> >> + strvec_pushv(&child.args, specs);
> >
> > I thought that we had something like `strvec_pushvec()` that knew to
> > also optimize for this case so that we don't have to reallocate the
> > vector multiple times. And if we had that function it would even be more
> > efficient to pass it down the stack. But we seemingly don't have it, so
> > that argument is kind of moot.
> We could add one. Not sure it would make a measurable difference; if
> the number of specs is huge there are probably other costs that dwarf
> pushing them to a strvec.
Yeah, I don't expect it to make a difference here, either. But by having
it we could use it in more places going forward, and that might lead to
tiny savings here and there that ultimately add up. So it'd be nudging
folks to "do the right thing".
> I have to admit that the simplicity of strvec_pushv() nudged me towards
> using a NULL-terminated array here, though. So just having a
> strvec_pushvec() available could guide towards using the length-limited
> strvec instead of a simpler NULL-terminated array (which explodes if
> left unterminated).
And that's not a huge issue by itself. I think the version you have here
is totally fine, and I won't insist on a reroll. But I think it gives us
a good opportunity to improve the status quo, if we want to take it.
Thanks!
Patrick
^ permalink raw reply
* Re: [PATCH v2 2/7] refs/packed: drop `USE_THE_REPOSITORY_VARIABLE`
From: Patrick Steinhardt @ 2026-07-16 5:32 UTC (permalink / raw)
To: Toon Claes; +Cc: git, Junio C Hamano
In-Reply-To: <871pd4h1y3.fsf@emacs.iotcl.com>
On Wed, Jul 15, 2026 at 02:28:20PM +0200, Toon Claes wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > 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`.
>
> Well, this was removed in the previous patch. I'm fine keeping this as a
> separate commmit, but the messaging is a bit confusing.
That's fair indeed. I don't quite remember why I split it up -- I assume
that originally I had to do some more changes? Anyway, I think it's
sensible to just merge these two commits.
Patrick
^ permalink raw reply
* [PATCH v3 0/6] refs: remove use of `the_repository`
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im>
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);
Changes in v3:
- Merge the patch that removes `USE_THE_REPOSITORY_VARIABLE` from the
"packed" backend into the patch that removes the last use of
`the_repository`.
- Link to v2: https://patch.msgid.link/20260715-pks-refs-wo-the-repository-v2-0-d00d364f5a3e@pks.im
Changes in v2:
- Fix default value for "core.packedRefsTimeout".
- Link to v1: https://patch.msgid.link/20260709-pks-refs-wo-the-repository-v1-0-1ad6f27529c9@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (6):
refs/packed: de-globalize handling of "core.packedRefsTimeout"
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 | 20 ++++--
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, 206 insertions(+), 169 deletions(-)
Range-diff versus v2:
1: b0f7389eb2 ! 1: 2e2ec63de8 refs/packed: de-globalize handling of "core.packedRefsTimeout"
@@ Commit message
Fix the issue by moving the value into `struct packed_ref_store` so that
it can be parsed per store.
+ This removes the last callsite that still used `the_repository`, so drop
+ the `USE_THE_REPOSITORY_VARIABLE` define.
+
Signed-off-by: Patrick Steinhardt <ps@pks.im>
## refs/packed-backend.c ##
+@@
+-#define USE_THE_REPOSITORY_VARIABLE
+ #define DISABLE_SIGN_COMPARE_WARNINGS
+
+ #include "../git-compat-util.h"
@@ refs/packed-backend.c: struct packed_ref_store {
* `packed_ref_store`) must not be freed.
*/
2: a36af82b03 < -: ---------- refs/packed: drop `USE_THE_REPOSITORY_VARIABLE`
3: 136db475ff = 2: ddba174b0d refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
4: f0c028a2d7 = 3: 69aa7536f6 worktree: refactor code to use available repositories
5: 42df076903 = 4: a75b0b7228 worktree: pass repository to file-local functions
6: 8c7fd80674 = 5: b1a214131d worktree: pass repository to public functions
7: 618fc6497f = 6: 6d83181a30 refs: remove remaining uses of `the_repository`
---
base-commit: f035246f779167db3506394141b59472d544af65
change-id: 20260618-pks-refs-wo-the-repository-7e43e29371ac
^ permalink raw reply related
* [PATCH v3 1/6] refs/packed: de-globalize handling of "core.packedRefsTimeout"
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.
This removes the last callsite that still used `the_repository`, so drop
the `USE_THE_REPOSITORY_VARIABLE` define.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs/packed-backend.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 499cb55dfa..c5d96793fa 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"
@@ -162,6 +161,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 +1239,12 @@ 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) {
+ if (repo_config_get_int(ref_store->repo, "core.packedrefstimeout",
+ &refs->timeout_value))
+ refs->timeout_value = 1000;
+ refs->timeout_configured = true;
}
/*
@@ -1249,7 +1255,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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH v3 2/6] refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH v3 3/6] worktree: refactor code to use available repositories
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH v3 4/6] worktree: pass repository to file-local functions
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH v3 5/6] worktree: pass repository to public functions
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH v3 6/6] refs: remove remaining uses of `the_repository`
From: Patrick Steinhardt @ 2026-07-16 5:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@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.313.g8d093f411d.dirty
^ permalink raw reply related
* [PATCH 0/3] bisect: add --auto-reset to leave when done
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
Add a --auto-reset option to git bisect start and git bisect run that
returns to the commit checked out before git bisect start as soon as the
first bad commit is reported, instead of leaving the session active until
git bisect reset is run by hand.
Harald Nordgren (3):
bisect: read run output from the open descriptor
bisect: let bisect_reset() optionally check out quietly
bisect: add --auto-reset to leave when done
Documentation/git-bisect.adoc | 12 +++++++--
bisect.c | 2 ++
builtin/bisect.c | 51 ++++++++++++++++++++++-------------
t/t6030-bisect-porcelain.sh | 34 +++++++++++++++++++++++
4 files changed, 78 insertions(+), 21 deletions(-)
base-commit: f60db8d575adb79761d363e026fb49bddf330c73
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v1
Pull-Request: https://github.com/git/git/pull/2335
--
gitgitgadget
^ permalink raw reply
* [PATCH 1/3] bisect: read run output from the open descriptor
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2335.git.git.1784180159.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
"git bisect run" redirects each step's output into BISECT_RUN, then
prints it back by reopening the file by name. Read it from the already
open descriptor instead; this behaves the same and no longer needs the
file to be reachable by name.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 798e28f501..69ea14b1b6 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)
return res;
}
-static int print_file_to_stdout(const char *path)
+static int print_fd_to_stdout(int fd)
{
- int fd = open(path, O_RDONLY);
- int ret = 0;
-
- if (fd < 0)
- return error_errno(_("cannot open file '%s' for reading"), path);
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return error_errno(_("failed to rewind BISECT_RUN output"));
if (copy_fd(fd, 1) < 0)
- ret = error_errno(_("failed to read '%s'"), path);
- close(fd);
- return ret;
+ return error_errno(_("failed to read BISECT_RUN output"));
+ return 0;
}
static int check_term_format(const char *term, const char *orig_term)
@@ -1291,7 +1287,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
else
new_state = terms->term_bad;
- temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);
if (temporary_stdout_fd < 0) {
res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
@@ -1307,9 +1303,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
fflush(stdout);
dup2(saved_stdout, 1);
close(saved_stdout);
- close(temporary_stdout_fd);
- print_file_to_stdout(git_path_bisect_run());
+ print_fd_to_stdout(temporary_stdout_fd);
+ close(temporary_stdout_fd);
if (res == BISECT_ONLY_SKIPPED_LEFT)
error(_("bisect run cannot continue any more"));
--
gitgitgadget
^ permalink raw reply related
* [PATCH 2/3] bisect: let bisect_reset() optionally check out quietly
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2335.git.git.1784180159.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
Add a "quiet" parameter to bisect_reset() that passes "--quiet" to the
checkout restoring the original HEAD, suppressing its progress and
branch-status output.
No caller sets the flag yet, so behavior is unchanged.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 69ea14b1b6..27d30b549e 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -230,7 +230,7 @@ static int write_terms(const char *bad, const char *good)
return res;
}
-static int bisect_reset(const char *commit)
+static int bisect_reset(const char *commit, int quiet)
{
struct strbuf branch = STRBUF_INIT;
@@ -251,8 +251,10 @@ static int bisect_reset(const char *commit)
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
- strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees",
- branch.buf, "--", NULL);
+ strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", NULL);
+ if (quiet)
+ strvec_push(&cmd.args, "--quiet");
+ strvec_pushl(&cmd.args, branch.buf, "--", NULL);
if (run_command(&cmd)) {
error(_("could not check out original"
" HEAD '%s'. Try 'git bisect"
@@ -1085,7 +1087,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f
if (is_empty_or_missing_file(filename))
return error(_("cannot read file '%s' for replaying"), filename);
- if (bisect_reset(NULL))
+ if (bisect_reset(NULL, 0))
return BISECT_FAILED;
fp = fopen(filename, "r");
@@ -1334,7 +1336,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU
if (argc > 1)
return error(_("'%s' requires either no argument or a commit"),
"git bisect reset");
- return bisect_reset(argc ? argv[0] : NULL);
+ return bisect_reset(argc ? argv[0] : NULL, 0);
}
static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,
--
gitgitgadget
^ permalink raw reply related
* [PATCH 3/3] bisect: add --auto-reset to leave when done
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2335.git.git.1784180159.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
When a bisection finished, "git bisect" reported the first bad commit
but left the session active until "git bisect reset" was run by hand.
Add an "--auto-reset" option, accepted by both "git bisect start" and
"git bisect run", that resets as soon as the first bad commit is found,
returning to the commit checked out before "git bisect start". The flag
is persisted in a BISECT_AUTO_RESET state file and the restoring
checkout is done quietly.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
Documentation/git-bisect.adoc | 12 ++++++++++--
bisect.c | 2 ++
builtin/bisect.c | 19 +++++++++++++++++--
t/t6030-bisect-porcelain.sh | 34 ++++++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc
index d2115b2990..1b03dbba7a 100644
--- a/Documentation/git-bisect.adoc
+++ b/Documentation/git-bisect.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[synopsis]
git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
+ [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-(good|old) | --term-(bad|new)]
@@ -20,7 +20,7 @@ git bisect reset [<commit>]
git bisect (visualize|view)
git bisect replay <logfile>
git bisect log
-git bisect run <cmd> [<arg>...]
+git bisect run [--auto-reset] <cmd> [<arg>...]
git bisect help
DESCRIPTION
@@ -385,6 +385,14 @@ ignored.
This option is particularly useful in avoiding false positives when a merged
branch contained broken or non-buildable commits, but the merge itself was OK.
+`--auto-reset`::
+ Once the first bad commit is found, clean up the bisection state and
+ return to the commit that was checked out before `git bisect start`,
+ as if `git bisect reset` had been run. The first bad commit is still
+ reported before resetting.
++
+This option may be given to `git bisect start` or to `git bisect run`.
+
EXAMPLES
--------
diff --git a/bisect.c b/bisect.c
index 94c7028d2a..a34309dd35 100644
--- a/bisect.c
+++ b/bisect.c
@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static void read_bisect_paths(struct strvec *array)
{
@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)
unlink_or_warn(git_path_bisect_run());
unlink_or_warn(git_path_bisect_terms());
unlink_or_warn(git_path_bisect_first_parent());
+ unlink_or_warn(git_path_bisect_auto_reset());
/*
* Cleanup BISECT_START last to support the --no-checkout option
* introduced in the commit 4796e823a.
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 27d30b549e..b80eccb635 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_START_USAGE \
N_("git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n" \
- " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]")
+ " [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]")
#define BUILTIN_GIT_BISECT_BAD_USAGE \
N_("git bisect (bad|new|<term-new>) [<rev>]")
#define BUILTIN_GIT_BISECT_GOOD_USAGE \
@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_LOG_USAGE \
"git bisect log"
#define BUILTIN_GIT_BISECT_RUN_USAGE \
- N_("git bisect run <cmd> [<arg>...]")
+ N_("git bisect run [--auto-reset] <cmd> [<arg>...]")
#define BUILTIN_GIT_BISECT_HELP_USAGE \
"git bisect help"
@@ -688,6 +689,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
res = bisect_successful(terms);
+ if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
+ res = bisect_reset(NULL, 1);
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
res = bisect_skipped_commits(terms);
@@ -711,6 +714,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
{
int no_checkout = 0;
int first_parent_only = 0;
+ int auto_reset = 0;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos;
enum bisect_error res = BISECT_OK;
@@ -743,6 +747,8 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
no_checkout = 1;
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
+ } else if (!strcmp(arg, "--auto-reset")) {
+ auto_reset = 1;
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
i++;
@@ -857,6 +863,9 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
if (first_parent_only)
write_file(git_path_bisect_first_parent(), "\n");
+ if (auto_reset)
+ write_file(git_path_bisect_auto_reset(), "\n");
+
if (no_checkout) {
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -1242,6 +1251,12 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
if (bisect_next_check(terms, NULL))
return BISECT_FAILED;
+ if (argc && !strcmp(argv[0], "--auto-reset")) {
+ write_file(git_path_bisect_auto_reset(), "\n");
+ argc--;
+ argv++;
+ }
+
if (!argc) {
error(_("bisect run failed: no command provided."));
return BISECT_FAILED;
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 081116220a..5389ba388c 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -453,6 +453,40 @@ test_expect_success '"git bisect run" simple case' '
git bisect reset
'
+test_expect_success '"git bisect start --auto-reset" leaves the bisection' '
+ test_when_finished "git bisect reset" &&
+ git bisect start --auto-reset $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success '"git bisect run --auto-reset" leaves the bisection' '
+ test_when_finished "git bisect reset" &&
+ write_script test_script.sh <<-\EOF &&
+ ! grep Another hello >/dev/null
+ EOF
+ git bisect start $HASH4 $HASH2 &&
+ git bisect run --auto-reset ./test_script.sh >my_bisect_log.txt &&
+ grep "$HASH3 is the first .bad. commit" my_bisect_log.txt &&
+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success 'without --auto-reset the bisection state is kept' '
+ test_when_finished "git bisect reset" &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success '--auto-reset does not leak into a later bisection' '
+ test_when_finished "git bisect reset" &&
+ git bisect start --auto-reset $HASH4 $HASH2 &&
+ git bisect bad &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
+'
+
# We want to automatically find the commit that
# added "Ciao" into hello.
test_expect_success '"git bisect run" with more complex "git bisect start"' '
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH] rebase: mention --abort alongside --continue
From: Harald Nordgren @ 2026-07-16 6:02 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Phillip Wood, Harald Nordgren via GitGitGadget, git
In-Reply-To: <xmqqa4srnwfa.fsf@gitster.g>
I'll revive this discussion because the 'git rebase --keep-base -x'
case still bothers me.
When getting stuck in the middle of an operation, it just makes sense
to offer a way forward and a way back, why be more obtuse than we need
to?
Harald
^ permalink raw reply
* Re: [PATCH v3 0/6] refs: remove use of `the_repository`
From: Christian Couder @ 2026-07-16 6:53 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Toon Claes
In-Reply-To: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@pks.im>
On Thu, Jul 16, 2026 at 7:33 AM Patrick Steinhardt <ps@pks.im> wrote:
>
> 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 changes in this series look good to me too.
Thanks.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox