From c334167a41f63fe6a16090d894d3bb26332bee9b Mon Sep 17 00:00:00 2001 From: "E-Love (Eric Loveland)" Date: Thu, 14 May 2026 21:57:51 -0400 Subject: [PATCH 1/3] fix(worktree): align with Git's gitdir resolution Git only normalizes relative references in `gitdir`[1], so do the same. [1]: https://github.com/git/git/blob/v2.54.0/setup.c#L1012-L1025 --- git/repo/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/repo/base.py b/git/repo/base.py index 7579e326f..08707fc5c 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -295,7 +295,7 @@ def __init__( sm_gitpath = find_worktree_git_dir(dotgit) if sm_gitpath is not None: - git_dir = expand_path(sm_gitpath, expand_vars) + git_dir = osp.normpath(sm_gitpath) self._working_tree_dir = curpath break From e94ccaff11ca7f4388e920bcc2ff617dd9e2a827 Mon Sep 17 00:00:00 2001 From: "E-Love (Eric Loveland)" Date: Tue, 12 May 2026 15:49:14 -0400 Subject: [PATCH 2/3] support relative worktree paths (git 2.48+ worktree.useRelativePaths) git 2.48 introduced the `worktree.useRelativePaths` config option, which causes `git worktree add` to write a relative `gitdir` into the worktree's `.git` file. Resolve the relative `gitdir` against the worktree directory before normalizing it. Absolute paths are unaffected because `os.path.join` ignores the prefix when joined with an absolute path. --- git/repo/base.py | 3 ++- test/test_repo.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index 08707fc5c..2d3cf24f0 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -295,7 +295,8 @@ def __init__( sm_gitpath = find_worktree_git_dir(dotgit) if sm_gitpath is not None: - git_dir = osp.normpath(sm_gitpath) + # worktrees can use relative paths as of Git 2.48, so we join to curpath + git_dir = osp.normpath(osp.join(curpath, sm_gitpath)) self._working_tree_dir = curpath break diff --git a/test/test_repo.py b/test/test_repo.py index fae3dc0b9..13bff52e9 100644 --- a/test/test_repo.py +++ b/test/test_repo.py @@ -1094,7 +1094,7 @@ def test_is_valid_object(self): self.assertFalse(repo.is_valid_object(tag_sha, "commit")) @with_rw_directory - def test_git_work_tree_dotgit(self, rw_dir): + def test_git_work_tree_dotgit(self, rw_dir, use_relative_paths=False): """Check that we find .git as a worktree file and find the worktree based on it.""" git = Git(rw_dir) @@ -1106,7 +1106,11 @@ def test_git_work_tree_dotgit(self, rw_dir): worktree_path = join_path_native(rw_dir, "worktree_repo") if Git.is_cygwin(): worktree_path = cygpath(worktree_path) - rw_master.git.worktree("add", worktree_path, branch.name) + wt_add_kwargs = {"insert_kwargs_after": "add"} + # relative worktree paths introduced in git 2.48.0 + if use_relative_paths and git.version_info[:3] >= (2, 48, 0): + wt_add_kwargs["relative_paths"] = True + rw_master.git.worktree("add", worktree_path, branch.name, **wt_add_kwargs) # This ensures that we can read the repo's gitdir correctly. repo = Repo(worktree_path) @@ -1124,6 +1128,15 @@ def test_git_work_tree_dotgit(self, rw_dir): self.assertIsInstance(repo.heads["aaaaaaaa"], Head) + def test_git_work_tree_dotgit_relative(self): + """Check that we find .git as a worktree file containing a relative path + and find the worktree based on it.""" + if Git().version_info[:3] < (2, 48, 0): + pytest.skip("relative worktree feature unsupported, needs git 2.48.0 or later") + # this class inherits from TestCase so we can't use pytest.mark.parametrize on + # test_git_work_tree_dotgit; delegate instead + self.test_git_work_tree_dotgit(use_relative_paths=True) + @with_rw_directory def test_git_work_tree_env(self, rw_dir): """Check that we yield to GIT_WORK_TREE.""" From 29c98613fd7f286786cf450ffa8a2e314f4317e7 Mon Sep 17 00:00:00 2001 From: "E-Love (Eric Loveland)" Date: Tue, 12 May 2026 16:16:44 -0400 Subject: [PATCH 3/3] doc(_call_process): correct example --- git/cmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/cmd.py b/git/cmd.py index 7f2564d45..637225f0b 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -1595,7 +1595,7 @@ def _call_process( turns into:: - git rev-list --max-count=10 --header=master + git rev-list --max-count=10 --header master :return: Same as :meth:`execute`. If no args are given, used :meth:`execute`'s