From 58fcd14a99c2bc0a66bb50d651a5a96fe5eb5884 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 23:44:58 +0100 Subject: [PATCH 1/2] Add a test showing that removing a submodule in a linked worktree deletes the wrong git dir Co-Authored-By: Claude Opus 5.5 --- .../submodule/remove_in_linked_worktree.go | 52 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 53 insertions(+) create mode 100644 pkg/integration/tests/submodule/remove_in_linked_worktree.go diff --git a/pkg/integration/tests/submodule/remove_in_linked_worktree.go b/pkg/integration/tests/submodule/remove_in_linked_worktree.go new file mode 100644 index 000000000..438416894 --- /dev/null +++ b/pkg/integration/tests/submodule/remove_in_linked_worktree.go @@ -0,0 +1,52 @@ +package submodule + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var RemoveInLinkedWorktree = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Remove a submodule in a linked worktree", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("first commit") + shell.CloneIntoSubmodule("my_submodule_name", "my_submodule_path") + shell.GitAddAll() + shell.Commit("add submodule") + + shell.AddWorktree("HEAD", "../linked-worktree", "mybranch") + shell.Chdir("../linked-worktree") + shell.RunCommand([]string{"git", "submodule", "update", "--init"}) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // Git keeps the git dirs of submodules separately for each worktree + mainGitDirSubmodulePath := "../repo/.git/modules/my_submodule_name" + linkedGitDirSubmodulePath := "../repo/.git/worktrees/linked-worktree/modules/my_submodule_name" + t.FileSystem(). + PathPresent(mainGitDirSubmodulePath). + PathPresent(linkedGitDirSubmodulePath) + + t.Views().Submodules().Focus(). + Lines( + Contains("my_submodule_name").IsSelected(), + ). + Press(keys.Universal.Remove). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Remove submodule")). + Content(Equals("Are you sure you want to remove submodule 'my_submodule_name' and its corresponding directory? This is irreversible.")). + Confirm() + }). + IsEmpty() + + t.FileSystem(). + /* EXPECTED: + PathPresent(mainGitDirSubmodulePath). + PathNotPresent(linkedGitDirSubmodulePath) + ACTUAL: */ + PathNotPresent(mainGitDirSubmodulePath). + PathPresent(linkedGitDirSubmodulePath) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 36b71ea00..282c2436c 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -579,6 +579,7 @@ var tests = []*components.IntegrationTest{ submodule.EnterFromDotfileBareRepo, submodule.EnterNested, submodule.Remove, + submodule.RemoveInLinkedWorktree, submodule.RemoveNested, submodule.Reset, submodule.ResetFolder, From 98ca24de23f92a6ce161f0a8e004328beb1b89b6 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 23:46:56 +0100 Subject: [PATCH 2/2] Delete the right git dir when removing a submodule in a linked worktree Removing a submodule in a linked worktree deletes the git dir of that submodule in the main worktree. This breaks the submodule there, and any unpushed commits, local branches and stashes in it are lost. The git dir of the submodule in the linked worktree stays behind. Git keeps the git dirs of submodules separately for each worktree. For a linked worktree, they are in .git/worktrees//modules. Lazygit used the worktree's git dir until 3b723282cb switched it to the repo's git dir, so this has been broken since v0.41.0. Use the worktree's git dir again. Co-Authored-By: Claude Opus 5.5 --- pkg/commands/git_commands/submodule.go | 5 ++--- pkg/commands/models/submodule_config.go | 6 +++--- .../tests/submodule/remove_in_linked_worktree.go | 4 ---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pkg/commands/git_commands/submodule.go b/pkg/commands/git_commands/submodule.go index 0fa5de9df..79d3e2318 100644 --- a/pkg/commands/git_commands/submodule.go +++ b/pkg/commands/git_commands/submodule.go @@ -269,9 +269,8 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { self.Log.Error(err) } - // We may in fact want to use the repo's git dir path but git docs say not to - // mix submodules and worktrees anyway. - return os.RemoveAll(submodule.GitDirPath(self.repoPaths.repoGitDirPath)) + // Git keeps the git dirs of submodules separately for each worktree + return os.RemoveAll(submodule.GitDirPath(self.repoPaths.WorktreeGitDirPath())) } func (self *SubmoduleCommands) Add(name string, path string, url string) error { diff --git a/pkg/commands/models/submodule_config.go b/pkg/commands/models/submodule_config.go index 7df0d131a..48d838ff4 100644 --- a/pkg/commands/models/submodule_config.go +++ b/pkg/commands/models/submodule_config.go @@ -38,10 +38,10 @@ func (r *SubmoduleConfig) Description() string { return r.RefName() } -func (r *SubmoduleConfig) GitDirPath(repoGitDirPath string) string { - parentPath := repoGitDirPath +func (r *SubmoduleConfig) GitDirPath(worktreeGitDirPath string) string { + parentPath := worktreeGitDirPath if r.ParentModule != nil { - parentPath = r.ParentModule.GitDirPath(repoGitDirPath) + parentPath = r.ParentModule.GitDirPath(worktreeGitDirPath) } return filepath.Join(parentPath, "modules", r.Name) diff --git a/pkg/integration/tests/submodule/remove_in_linked_worktree.go b/pkg/integration/tests/submodule/remove_in_linked_worktree.go index 438416894..f081faa46 100644 --- a/pkg/integration/tests/submodule/remove_in_linked_worktree.go +++ b/pkg/integration/tests/submodule/remove_in_linked_worktree.go @@ -42,11 +42,7 @@ var RemoveInLinkedWorktree = NewIntegrationTest(NewIntegrationTestArgs{ IsEmpty() t.FileSystem(). - /* EXPECTED: PathPresent(mainGitDirSubmodulePath). PathNotPresent(linkedGitDirSubmodulePath) - ACTUAL: */ - PathNotPresent(mainGitDirSubmodulePath). - PathPresent(linkedGitDirSubmodulePath) }, })