From 00c0c4c061a10e64ba20340af0ecdea1e9398453 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 14 Sep 2026 10:20:22 +0200 Subject: [PATCH] Demonstrate that the recent repos menu can't read a submodule's branch The menu shows "Branch unknown" for every submodule, whatever it has checked out. A submodule has no .git directory; its .git is a file naming the directory, and for a submodule git writes that name relative to the submodule. We hand it to os.ReadFile unchanged, so it resolves against lazygit's own working directory and the read fails. A worktree created with --relative-paths (or with worktree.useRelativePaths set) gets a relative name too, and fails the same way. Co-Authored-By: Claude Opus 5 (1M context) --- .../controllers/helpers/repos_helper_test.go | 28 +++++++++++++++++++ .../tests/misc/recent_repos_branch_column.go | 11 +++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/gui/controllers/helpers/repos_helper_test.go b/pkg/gui/controllers/helpers/repos_helper_test.go index be36a7f58..55e0508bd 100644 --- a/pkg/gui/controllers/helpers/repos_helper_test.go +++ b/pkg/gui/controllers/helpers/repos_helper_test.go @@ -46,6 +46,34 @@ func TestReadHeadInfo(t *testing.T) { expected: headInfo{branch: "mybranch"}, expectedOk: true, }, + { + name: "worktree whose .git file names the git dir relatively", + files: map[string]string{ + "repo/.git/worktrees/wt/HEAD": "ref: refs/heads/mybranch\n", + "wt/.git": "gitdir: ../repo/.git/worktrees/wt\n", + }, + /* EXPECTED: + repoPath: "wt", + expected: headInfo{branch: "mybranch"}, + expectedOk: true, + ACTUAL: */ + repoPath: "wt", + expectedOk: false, + }, + { + name: "submodule, whose .git file always names the git dir relatively", + files: map[string]string{ + "repo/.git/modules/sub/HEAD": "ref: refs/heads/mybranch\n", + "repo/sub/.git": "gitdir: ../.git/modules/sub\n", + }, + /* EXPECTED: + repoPath: "repo/sub", + expected: headInfo{branch: "mybranch"}, + expectedOk: true, + ACTUAL: */ + repoPath: "repo/sub", + expectedOk: false, + }, { name: "directory without a .git entry", repoPath: "notarepo", diff --git a/pkg/integration/tests/misc/recent_repos_branch_column.go b/pkg/integration/tests/misc/recent_repos_branch_column.go index 77562102b..9c0257edb 100644 --- a/pkg/integration/tests/misc/recent_repos_branch_column.go +++ b/pkg/integration/tests/misc/recent_repos_branch_column.go @@ -19,13 +19,18 @@ var RecentReposBranchColumn = NewIntegrationTest(NewIntegrationTestArgs{ current, _ := filepath.Abs(".") onBranch, _ := filepath.Abs("../on-branch") detached, _ := filepath.Abs("../detached") - cfg.GetAppState().RecentRepos = []string{current, onBranch, detached} + submodule, _ := filepath.Abs("sub") + cfg.GetAppState().RecentRepos = []string{current, onBranch, detached, submodule} }, SetupRepo: func(shell *Shell) { shell.EmptyCommit("one") shell.CloneNonBare("on-branch") shell.CloneNonBare("detached") shell.RunCommand([]string{"git", "-C", "../detached", "checkout", "--detach"}) + shell.CloneIntoSubmodule("submodule", "sub") + shell.GitAddAll() + shell.Commit("add submodule") + shell.RunCommand([]string{"git", "-C", "sub", "checkout", "--detach"}) }, Run: func(t *TestDriver, keys config.KeybindingConfig) { t.ExpectPopup().Menu(). @@ -33,6 +38,10 @@ var RecentReposBranchColumn = NewIntegrationTest(NewIntegrationTestArgs{ Lines( Contains("on-branch").Contains("master").IsSelected(), Contains("detached").MatchesRegexp(`HEAD detached at [0-9a-f]{8}`), + /* EXPECTED: + Contains("sub").MatchesRegexp(`HEAD detached at [0-9a-f]{8}`), + ACTUAL: */ + Contains("sub").Contains("Branch unknown"), Contains("Cancel"), ). Cancel()