mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-28 16:02:01 +03:00
Improve name handling
This commit is contained in:
@ -81,7 +81,7 @@ func TestObtainBranch(t *testing.T) {
|
|||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
branch := obtainBranch(s.input)
|
branch := obtainBranch(s.input, "current-dir")
|
||||||
assert.EqualValues(t, s.expectedBranch, branch)
|
assert.EqualValues(t, s.expectedBranch, branch)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ func NewWorktreeCommands(gitCommon *GitCommon) *WorktreeCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorktreeCommands) New(worktreePath string) error {
|
func (self *WorktreeCommands) New(worktreePath string, committish string) error {
|
||||||
cmdArgs := NewGitCmd("worktree").Arg("add", worktreePath).ToArgv()
|
cmdArgs := NewGitCmd("worktree").Arg("add", worktreePath, committish).ToArgv()
|
||||||
|
|
||||||
return self.cmd.New(cmdArgs).Run()
|
return self.cmd.New(cmdArgs).Run()
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorktreeLoader struct {
|
type WorktreeLoader struct {
|
||||||
@ -49,9 +49,115 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
|
|||||||
}
|
}
|
||||||
} else if strings.HasPrefix(splitLine, "branch ") {
|
} else if strings.HasPrefix(splitLine, "branch ") {
|
||||||
branch := strings.SplitN(splitLine, " ", 2)[1]
|
branch := strings.SplitN(splitLine, " ", 2)[1]
|
||||||
currentWorktree.Branch = filepath.Base(branch)
|
currentWorktree.Branch = strings.TrimPrefix(branch, "refs/heads/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names := getUniqueNamesFromPaths(lo.Map(worktrees, func(worktree *models.Worktree, _ int) string {
|
||||||
|
return worktree.Path
|
||||||
|
}))
|
||||||
|
|
||||||
|
for index, worktree := range worktrees {
|
||||||
|
worktree.NameField = names[index]
|
||||||
|
}
|
||||||
|
|
||||||
return worktrees, nil
|
return worktrees, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pathWithIndexT struct {
|
||||||
|
path string
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
type nameWithIndexT struct {
|
||||||
|
name string
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUniqueNamesFromPaths(paths []string) []string {
|
||||||
|
pathsWithIndex := lo.Map(paths, func(path string, index int) pathWithIndexT {
|
||||||
|
return pathWithIndexT{path, index}
|
||||||
|
})
|
||||||
|
|
||||||
|
namesWithIndex := getUniqueNamesFromPathsAux(pathsWithIndex, 0)
|
||||||
|
|
||||||
|
// now sort based on index
|
||||||
|
result := make([]string, len(namesWithIndex))
|
||||||
|
for _, nameWithIndex := range namesWithIndex {
|
||||||
|
result[nameWithIndex.index] = nameWithIndex.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUniqueNamesFromPathsAux(paths []pathWithIndexT, depth int) []nameWithIndexT {
|
||||||
|
// If we have no paths, return an empty array
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return []nameWithIndexT{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have only one path, return the last segment of the path
|
||||||
|
if len(paths) == 1 {
|
||||||
|
path := paths[0]
|
||||||
|
return []nameWithIndexT{{index: path.index, name: sliceAtDepth(path.path, depth)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// group the paths by their value at the specified depth
|
||||||
|
groups := make(map[string][]pathWithIndexT)
|
||||||
|
for _, path := range paths {
|
||||||
|
value := valueAtDepth(path.path, depth)
|
||||||
|
groups[value] = append(groups[value], path)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []nameWithIndexT{}
|
||||||
|
for _, group := range groups {
|
||||||
|
if len(group) == 1 {
|
||||||
|
path := group[0]
|
||||||
|
result = append(result, nameWithIndexT{index: path.index, name: sliceAtDepth(path.path, depth)})
|
||||||
|
} else {
|
||||||
|
result = append(result, getUniqueNamesFromPathsAux(group, depth+1)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path is /a/b/c/d, and the depth is 0, the value is 'd'. If the depth is 1, the value is 'c', etc
|
||||||
|
func valueAtDepth(path string, depth int) string {
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
|
||||||
|
// Split the path into segments
|
||||||
|
segments := strings.Split(path, "/")
|
||||||
|
|
||||||
|
// Get the length of segments
|
||||||
|
length := len(segments)
|
||||||
|
|
||||||
|
// If the depth is greater than the length of segments, return an empty string
|
||||||
|
if depth >= length {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the segment at the specified depth from the end of the path
|
||||||
|
return segments[length-1-depth]
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path is /a/b/c/d, and the depth is 0, the value is 'd'. If the depth is 1, the value is 'b/c', etc
|
||||||
|
func sliceAtDepth(path string, depth int) string {
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
|
||||||
|
// Split the path into segments
|
||||||
|
segments := strings.Split(path, "/")
|
||||||
|
|
||||||
|
// Get the length of segments
|
||||||
|
length := len(segments)
|
||||||
|
|
||||||
|
// If the depth is greater than or equal to the length of segments, return an empty string
|
||||||
|
if depth >= length {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the segments from the specified depth till end of the path
|
||||||
|
return strings.Join(segments[length-1-depth:], "/")
|
||||||
|
}
|
||||||
|
52
pkg/commands/git_commands/worktree_loader_test.go
Normal file
52
pkg/commands/git_commands/worktree_loader_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetUniqueNamesFromPaths(t *testing.T) {
|
||||||
|
for _, scenario := range []struct {
|
||||||
|
input []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: []string{},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []string{
|
||||||
|
"/my/path/feature/one",
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []string{
|
||||||
|
"/my/path/feature/one/",
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []string{
|
||||||
|
"/a/b/c/d",
|
||||||
|
"/a/b/c/e",
|
||||||
|
"/a/b/f/d",
|
||||||
|
"/a/e/c/d",
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"b/c/d",
|
||||||
|
"e",
|
||||||
|
"f/d",
|
||||||
|
"e/c/d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
actual := getUniqueNamesFromPaths(scenario.input)
|
||||||
|
assert.EqualValues(t, scenario.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,13 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
// A git worktree
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Worktree : A git worktree
|
|
||||||
type Worktree struct {
|
type Worktree struct {
|
||||||
|
// if false, this is a linked worktree
|
||||||
IsMain bool
|
IsMain bool
|
||||||
Path string
|
Path string
|
||||||
Branch string
|
Branch string
|
||||||
|
// based on the path, but uniquified
|
||||||
|
NameField string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) RefName() string {
|
func (w *Worktree) RefName() string {
|
||||||
@ -16,7 +15,7 @@ func (w *Worktree) RefName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) ID() string {
|
func (w *Worktree) ID() string {
|
||||||
return w.RefName()
|
return w.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) Description() string {
|
func (w *Worktree) Description() string {
|
||||||
@ -24,7 +23,7 @@ func (w *Worktree) Description() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) Name() string {
|
func (w *Worktree) Name() string {
|
||||||
return filepath.Base(w.Path)
|
return w.NameField
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) Main() bool {
|
func (w *Worktree) Main() bool {
|
||||||
|
@ -58,12 +58,17 @@ func (self *WorktreeHelper) IsWorktreePathMissing(w *models.Worktree) bool {
|
|||||||
func (self *WorktreeHelper) NewWorktree() error {
|
func (self *WorktreeHelper) NewWorktree() error {
|
||||||
return self.c.Prompt(types.PromptOpts{
|
return self.c.Prompt(types.PromptOpts{
|
||||||
Title: self.c.Tr.NewWorktreePath,
|
Title: self.c.Tr.NewWorktreePath,
|
||||||
HandleConfirm: func(response string) error {
|
HandleConfirm: func(path string) error {
|
||||||
self.c.LogAction(self.c.Tr.Actions.CreateWorktree)
|
return self.c.Prompt(types.PromptOpts{
|
||||||
if err := self.c.Git().Worktree.New(sanitizedBranchName(response)); err != nil {
|
Title: self.c.Tr.NewWorktreePath,
|
||||||
return err
|
HandleConfirm: func(committish string) error {
|
||||||
}
|
self.c.LogAction(self.c.Tr.Actions.CreateWorktree)
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
if err := self.c.Git().Worktree.New(sanitizedBranchName(path), committish); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,12 @@ func GetWorktreeDisplayString(isCurrent bool, isPathMissing bool, worktree *mode
|
|||||||
if icons.IsIconEnabled() {
|
if icons.IsIconEnabled() {
|
||||||
res = append(res, textStyle.Sprint(icon))
|
res = append(res, textStyle.Sprint(icon))
|
||||||
}
|
}
|
||||||
res = append(res, textStyle.Sprint(worktree.Name()))
|
|
||||||
|
name := worktree.Name()
|
||||||
|
if worktree.Main() {
|
||||||
|
// TODO: i18n
|
||||||
|
name += " (main worktree)"
|
||||||
|
}
|
||||||
|
res = append(res, textStyle.Sprint(name))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -557,6 +557,7 @@ type TranslationSet struct {
|
|||||||
MainWorktree string
|
MainWorktree string
|
||||||
CreateWorktree string
|
CreateWorktree string
|
||||||
NewWorktreePath string
|
NewWorktreePath string
|
||||||
|
NewWorktreeBranch string
|
||||||
Name string
|
Name string
|
||||||
Branch string
|
Branch string
|
||||||
Path string
|
Path string
|
||||||
@ -1276,6 +1277,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
MainWorktree: "(main)",
|
MainWorktree: "(main)",
|
||||||
CreateWorktree: "Create worktree",
|
CreateWorktree: "Create worktree",
|
||||||
NewWorktreePath: "New worktree path",
|
NewWorktreePath: "New worktree path",
|
||||||
|
NewWorktreeBranch: "New worktree branch (leave blank to use the current branch)",
|
||||||
Name: "Name",
|
Name: "Name",
|
||||||
Branch: "Branch",
|
Branch: "Branch",
|
||||||
Path: "Path",
|
Path: "Path",
|
||||||
|
Reference in New Issue
Block a user