mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Add helper to compute candidate worktree parent directories
This is the core of "never type a path from scratch": from the repo root, the configured default path, and the parents of existing worktrees, derive the ordered list of directories under which a new worktree could be placed. Pure and unit-tested here; wired into the creation flow in a later commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ef73406a96
commit
b02eca451c
|
|
@ -2,6 +2,7 @@ package helpers
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
|
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type WorktreeHelper struct {
|
||||
|
|
@ -219,6 +221,38 @@ func (self *WorktreeHelper) Detach(worktree *models.Worktree) error {
|
|||
})
|
||||
}
|
||||
|
||||
// worktreeParentDirCandidates returns the candidate parent directories in which
|
||||
// to create a new worktree, in priority order and de-duplicated:
|
||||
//
|
||||
// 1. the parent directory of each existing linked worktree (in worktree order);
|
||||
// 2. the configured default path (relative paths are resolved against repoPath);
|
||||
// 3. the repo's parent directory, if nothing else is available.
|
||||
//
|
||||
// repoPath is RepoPaths.RepoPath(), which is stable regardless of which worktree
|
||||
// we're currently standing in. All returned paths are absolute.
|
||||
func worktreeParentDirCandidates(repoPath string, linkedWorktreePaths []string, defaultPath string) []string {
|
||||
candidates := lo.Map(linkedWorktreePaths, func(path string, _ int) string {
|
||||
return filepath.Dir(path)
|
||||
})
|
||||
|
||||
if defaultPath != "" {
|
||||
if filepath.IsAbs(defaultPath) {
|
||||
defaultPath = filepath.Clean(defaultPath)
|
||||
} else {
|
||||
defaultPath = filepath.Join(repoPath, defaultPath)
|
||||
}
|
||||
candidates = append(candidates, defaultPath)
|
||||
}
|
||||
|
||||
candidates = lo.Uniq(candidates)
|
||||
|
||||
if len(candidates) == 0 {
|
||||
candidates = append(candidates, filepath.Dir(repoPath))
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
func (self *WorktreeHelper) ViewWorktreeOptions(context types.IListContext, ref string) error {
|
||||
currentBranch := self.refsHelper.GetCheckedOutRef()
|
||||
canCheckoutBase := context == self.c.Contexts().Branches && ref != currentBranch.RefName()
|
||||
|
|
|
|||
94
pkg/gui/controllers/helpers/worktree_helper_test.go
Normal file
94
pkg/gui/controllers/helpers/worktree_helper_test.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package helpers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// nativePath rewrites a forward-slash test path into one that is valid on the
|
||||
// host OS, so the scenarios below can be written with readable Unix-style
|
||||
// paths. On Windows a leading slash is not absolute (filepath.IsAbs wants a
|
||||
// drive letter), so we graft one on; relative paths are left untouched.
|
||||
func nativePath(p string) string {
|
||||
if runtime.GOOS == "windows" && strings.HasPrefix(p, "/") {
|
||||
p = "C:" + p
|
||||
}
|
||||
return filepath.FromSlash(p)
|
||||
}
|
||||
|
||||
func TestWorktreeParentDirCandidates(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
repoPath string
|
||||
linkedWorktreePaths []string
|
||||
defaultPath string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "no worktrees and no default path falls back to the repo's parent",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: nil,
|
||||
defaultPath: "",
|
||||
expected: []string{"/code"},
|
||||
},
|
||||
{
|
||||
name: "uses the parent of each linked worktree, in order",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: []string{"/code/worktrees/foo", "/elsewhere/bar"},
|
||||
defaultPath: "",
|
||||
expected: []string{"/code/worktrees", "/elsewhere"},
|
||||
},
|
||||
{
|
||||
name: "de-duplicates parents shared by multiple worktrees",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: []string{"/code/worktrees/foo", "/code/worktrees/bar"},
|
||||
defaultPath: "",
|
||||
expected: []string{"/code/worktrees"},
|
||||
},
|
||||
{
|
||||
name: "appends the default path after the worktree parents",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: []string{"/code/worktrees/foo"},
|
||||
defaultPath: "/somewhere/else",
|
||||
expected: []string{"/code/worktrees", "/somewhere/else"},
|
||||
},
|
||||
{
|
||||
name: "resolves a relative default path against the repo path",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: nil,
|
||||
defaultPath: "../worktrees",
|
||||
expected: []string{"/code/worktrees"},
|
||||
},
|
||||
{
|
||||
name: "resolves a dot-relative default path inside the repo",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: nil,
|
||||
defaultPath: ".worktrees",
|
||||
expected: []string{"/code/myrepo/.worktrees"},
|
||||
},
|
||||
{
|
||||
name: "de-duplicates the default path against a worktree parent",
|
||||
repoPath: "/code/myrepo",
|
||||
linkedWorktreePaths: []string{"/code/worktrees/foo"},
|
||||
defaultPath: "/code/worktrees",
|
||||
expected: []string{"/code/worktrees"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := worktreeParentDirCandidates(
|
||||
nativePath(s.repoPath),
|
||||
lo.Map(s.linkedWorktreePaths, func(p string, _ int) string { return nativePath(p) }),
|
||||
nativePath(s.defaultPath),
|
||||
)
|
||||
expected := lo.Map(s.expected, func(p string, _ int) string { return nativePath(p) })
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue