From 49f9478298d841bedb89bd8b3fda97dae5d11246 Mon Sep 17 00:00:00 2001 From: Muskan Paliwal Date: Wed, 3 Jun 2026 21:42:06 +0530 Subject: [PATCH] fix(paths): handle submodule gitdirs in worktree IDs Entire-Checkpoint: 803ea1872413 --- cmd/entire/cli/paths/worktree.go | 35 ++++++++++++++------- cmd/entire/cli/paths/worktree_test.go | 44 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/cmd/entire/cli/paths/worktree.go b/cmd/entire/cli/paths/worktree.go index 6e0f3c3e6..faf48bc24 100644 --- a/cmd/entire/cli/paths/worktree.go +++ b/cmd/entire/cli/paths/worktree.go @@ -36,23 +36,36 @@ func GetWorktreeID(worktreePath string) (string, error) { } gitdir := strings.TrimPrefix(line, "gitdir: ") + if worktreeID, found := parseWorktreeID(gitdir); found { + return worktreeID, nil + } + + return "", fmt.Errorf("unexpected gitdir format (no worktrees): %s", gitdir) +} + +func parseWorktreeID(gitdir string) (string, bool) { + gitdir = strings.TrimSuffix(strings.ReplaceAll(gitdir, "\\", "/"), "/") + + // Submodule gitdirs live under .git/modules/. If that submodule + // repository has its own linked worktree, the gitdir ends with + // .git/modules//worktrees/. A /worktrees/ segment before the + // final /modules/ belongs to the superproject's worktree, not the submodule. + if modulesIndex := strings.LastIndex(gitdir, "/modules/"); modulesIndex >= 0 { + afterModules := gitdir[modulesIndex+len("/modules/"):] + if _, worktreeID, found := strings.Cut(afterModules, "/worktrees/"); found { + return strings.TrimSuffix(worktreeID, "/"), true + } + return "", true + } // Extract worktree name from path like /repo/.git/worktrees/ // or /repo/.bare/worktrees/ (bare repo + worktree layout). // The path after the marker is the worktree identifier. - var worktreeID string - var found bool for _, marker := range []string{".git/worktrees/", ".bare/worktrees/"} { - _, worktreeID, found = strings.Cut(gitdir, marker) - if found { - break + if _, worktreeID, found := strings.Cut(gitdir, marker); found { + return strings.TrimSuffix(worktreeID, "/"), true } } - if !found { - return "", fmt.Errorf("unexpected gitdir format (no worktrees): %s", gitdir) - } - // Remove trailing slashes if any - worktreeID = strings.TrimSuffix(worktreeID, "/") - return worktreeID, nil + return "", false } diff --git a/cmd/entire/cli/paths/worktree_test.go b/cmd/entire/cli/paths/worktree_test.go index 1ccf45f79..60c308878 100644 --- a/cmd/entire/cli/paths/worktree_test.go +++ b/cmd/entire/cli/paths/worktree_test.go @@ -8,6 +8,8 @@ import ( ) func TestGetWorktreeID(t *testing.T) { + t.Parallel() + tests := []struct { name string setupFunc func(dir string) error @@ -22,6 +24,46 @@ func TestGetWorktreeID(t *testing.T) { }, wantID: "", }, + { + name: "ordinary submodule relative gitdir", + setupFunc: func(dir string) error { + content := "gitdir: ../../.git/modules/deps/go-git\n" + return os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0o644) + }, + wantID: "", + }, + { + name: "ordinary submodule absolute gitdir", + setupFunc: func(dir string) error { + content := "gitdir: /repo/.git/modules/deps/go-git\n" + return os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0o644) + }, + wantID: "", + }, + { + name: "nested ordinary submodule gitdir", + setupFunc: func(dir string) error { + content := "gitdir: /repo/.git/modules/libs/go-git/modules/vendor/crypto\n" + return os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0o644) + }, + wantID: "", + }, + { + name: "linked worktree of submodule", + setupFunc: func(dir string) error { + content := "gitdir: /repo/.git/modules/deps/go-git/worktrees/sub-linked\n" + return os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0o644) + }, + wantID: "sub-linked", + }, + { + name: "ordinary submodule inside linked superproject worktree", + setupFunc: func(dir string) error { + content := "gitdir: /repo/.git/worktrees/super-linked/modules/deps/go-git\n" + return os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0o644) + }, + wantID: "", + }, { name: "linked worktree simple name", setupFunc: func(dir string) error { @@ -83,6 +125,8 @@ func TestGetWorktreeID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + dir := t.TempDir() if err := tt.setupFunc(dir); err != nil { t.Fatalf("setup failed: %v", err)