From d2d5bdc2bcd66d6eba2db9922442d0cf996e0d41 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 7 Aug 2026 22:36:53 +0200 Subject: [PATCH] Identify the current and main worktree by git dir, not by path `git worktree list` reports the main worktree as the common git dir with a trailing "/.git" removed, which equals the working tree only when the git dir sits inside it. In a submodule, a bare repo, or a repo using core.worktree it doesn't, so comparing the reported path against the working tree path matches nothing: no worktree is recognized as current or as main. Most visibly, inside a submodule lazygit claimed we were in a linked worktree named after the submodule, and offered to remove that "worktree". Comparing git dirs identifies a worktree unambiguously, so use that. A worktree whose directory is gone has no git dir to compare, and there we still have nothing better than its path. The submodule tests were asserting the linked-worktree suffix in the status view; it is gone now, and the repo name still says which submodule we're in. Co-Authored-By: Claude Opus 5 (1M context) --- pkg/commands/git_commands/worktree_loader.go | 30 ++++++++++++------- .../git_commands/worktree_loader_test.go | 4 --- pkg/integration/tests/submodule/enter.go | 2 +- .../tests/submodule/enter_nested.go | 2 +- pkg/integration/tests/submodule/reset.go | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pkg/commands/git_commands/worktree_loader.go b/pkg/commands/git_commands/worktree_loader.go index 0e3615f13..adf1e48dc 100644 --- a/pkg/commands/git_commands/worktree_loader.go +++ b/pkg/commands/git_commands/worktree_loader.go @@ -22,9 +22,6 @@ func NewWorktreeLoader(gitCommon *GitCommon) *WorktreeLoader { } func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { - currentRepoPath := self.repoPaths.RepoPath() - worktreePath := self.repoPaths.WorktreePath() - cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv() worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() if err != nil { @@ -54,17 +51,13 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { if strings.HasPrefix(splitLine, "worktree ") { path := strings.SplitN(splitLine, " ", 2)[1] - isMain := path == currentRepoPath - isCurrent := path == worktreePath - isPathMissing := self.pathExists(path) current = &models.Worktree{ - IsMain: isMain, - IsCurrent: isCurrent, - IsPathMissing: isPathMissing, + IsPathMissing: self.pathExists(path), Path: path, // we defer populating GitDir until a loop below so that - // we can parallelize the calls to git rev-parse + // we can parallelize the calls to git rev-parse, and + // IsMain/IsCurrent because they are derived from GitDir GitDir: "", } } else if strings.HasPrefix(splitLine, "HEAD ") { @@ -95,6 +88,23 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { } wg.Wait() + // Identify the current and the main worktree by their git dir rather than by + // their path: `git worktree list` reports the main worktree as the common + // git dir with a trailing "/.git" removed, which is the working tree only + // when the git dir sits inside it. In a submodule, a bare repo or a repo + // using core.worktree it doesn't, and comparing paths then matches nothing. + // A worktree whose directory is gone has no git dir to compare, so there we + // have nothing better than its path. + for _, worktree := range worktrees { + if worktree.GitDir != "" { + worktree.IsCurrent = worktree.GitDir == self.repoPaths.WorktreeGitDirPath() + worktree.IsMain = worktree.GitDir == self.repoPaths.RepoGitDirPath() + } else { + worktree.IsCurrent = worktree.Path == self.repoPaths.WorktreePath() + worktree.IsMain = worktree.Path == self.repoPaths.RepoPath() + } + } + names := getUniqueNamesFromPaths(lo.Map(worktrees, func(worktree *models.Worktree, _ int) string { return worktree.Path })) diff --git a/pkg/commands/git_commands/worktree_loader_test.go b/pkg/commands/git_commands/worktree_loader_test.go index 6f612bbd9..c537c6be4 100644 --- a/pkg/commands/git_commands/worktree_loader_test.go +++ b/pkg/commands/git_commands/worktree_loader_test.go @@ -218,12 +218,8 @@ branch refs/heads/mybranch }, expectedWorktrees: []*models.Worktree{ { - /* EXPECTED: IsMain: true, IsCurrent: true, - ACTUAL: */ - IsMain: false, - IsCurrent: false, Path: "/path/to/repo/.git/modules/mysubmodule", IsPathMissing: false, GitDir: "/path/to/repo/.git/modules/mysubmodule", diff --git a/pkg/integration/tests/submodule/enter.go b/pkg/integration/tests/submodule/enter.go index b768ed40e..67df35276 100644 --- a/pkg/integration/tests/submodule/enter.go +++ b/pkg/integration/tests/submodule/enter.go @@ -29,7 +29,7 @@ var Enter = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Status().Content(Contains("repo")) } assertInSubmodule := func() { - t.Views().Status().Content(Contains("my_submodule_path(my_submodule_name)")) + t.Views().Status().Content(Contains("my_submodule_path")) } assertInParentRepo() diff --git a/pkg/integration/tests/submodule/enter_nested.go b/pkg/integration/tests/submodule/enter_nested.go index 24cdf5261..1fc96425c 100644 --- a/pkg/integration/tests/submodule/enter_nested.go +++ b/pkg/integration/tests/submodule/enter_nested.go @@ -37,7 +37,7 @@ var EnterNested = NewIntegrationTest(NewIntegrationTestArgs{ // enter the nested submodule PressEnter() - t.Views().Status().Content(Contains("innerSubPath(innerSubName)")) + t.Views().Status().Content(Contains("innerSubPath")) t.Views().Commits().ContainsLines( Contains("initial inner commit"), ) diff --git a/pkg/integration/tests/submodule/reset.go b/pkg/integration/tests/submodule/reset.go index d671066a1..6c23bbd85 100644 --- a/pkg/integration/tests/submodule/reset.go +++ b/pkg/integration/tests/submodule/reset.go @@ -31,7 +31,7 @@ var Reset = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Status().Content(Contains("repo")) } assertInSubmodule := func() { - t.Views().Status().Content(Contains("my_submodule_path(my_submodule_name)")) + t.Views().Status().Content(Contains("my_submodule_path")) } assertInParentRepo()