Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions cmd/entire/cli/paths/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/<path>. If that submodule
// repository has its own linked worktree, the gitdir ends with
// .git/modules/<path>/worktrees/<id>. 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/<name>
// or /repo/.bare/worktrees/<name> (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
}
44 changes: 44 additions & 0 deletions cmd/entire/cli/paths/worktree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
)

func TestGetWorktreeID(t *testing.T) {
t.Parallel()

tests := []struct {
name string
setupFunc func(dir string) error
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down