mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
Initial addition of support for worktrees
This commit is contained in:
committed by
Jesse Duffield
parent
52447e5d46
commit
f8ba899b87
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,3 +39,5 @@ test/results/**
|
|||||||
|
|
||||||
oryxBuildBinary
|
oryxBuildBinary
|
||||||
__debug_bin
|
__debug_bin
|
||||||
|
|
||||||
|
.worktrees
|
@ -116,6 +116,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
|||||||
"stash": tr.StashTitle,
|
"stash": tr.StashTitle,
|
||||||
"suggestions": tr.SuggestionsCheatsheetTitle,
|
"suggestions": tr.SuggestionsCheatsheetTitle,
|
||||||
"extras": tr.ExtrasTitle,
|
"extras": tr.ExtrasTitle,
|
||||||
|
"worktrees": tr.WorktreesTitle,
|
||||||
}
|
}
|
||||||
|
|
||||||
title, ok := contextTitleMap[str]
|
title, ok := contextTitleMap[str]
|
||||||
|
@ -50,6 +50,7 @@ type Loaders struct {
|
|||||||
RemoteLoader *git_commands.RemoteLoader
|
RemoteLoader *git_commands.RemoteLoader
|
||||||
StashLoader *git_commands.StashLoader
|
StashLoader *git_commands.StashLoader
|
||||||
TagLoader *git_commands.TagLoader
|
TagLoader *git_commands.TagLoader
|
||||||
|
Worktrees *git_commands.WorktreeLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitCommand(
|
func NewGitCommand(
|
||||||
@ -133,6 +134,7 @@ func NewGitCommandAux(
|
|||||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode, gitCommon)
|
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode, gitCommon)
|
||||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||||
|
worktreeLoader := git_commands.NewWorktreeLoader(cmn, cmd)
|
||||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||||
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
||||||
|
|
||||||
@ -161,6 +163,7 @@ func NewGitCommandAux(
|
|||||||
FileLoader: fileLoader,
|
FileLoader: fileLoader,
|
||||||
ReflogCommitLoader: reflogCommitLoader,
|
ReflogCommitLoader: reflogCommitLoader,
|
||||||
RemoteLoader: remoteLoader,
|
RemoteLoader: remoteLoader,
|
||||||
|
Worktrees: worktreeLoader,
|
||||||
StashLoader: stashLoader,
|
StashLoader: stashLoader,
|
||||||
TagLoader: tagLoader,
|
TagLoader: tagLoader,
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -117,6 +118,11 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
||||||
|
currentDir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
output, err := self.getRawBranches()
|
output, err := self.getRawBranches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -138,6 +144,11 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(split[6]) > 0 && split[6] != currentDir {
|
||||||
|
// Ignore line because it is a branch checked out in a different worktree
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
return obtainBranch(split), true
|
return obtainBranch(split), true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -166,6 +177,7 @@ var branchFields = []string{
|
|||||||
"upstream:track",
|
"upstream:track",
|
||||||
"subject",
|
"subject",
|
||||||
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
|
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
|
||||||
|
"worktreepath",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain branch information from parsed line output of getRawBranches()
|
// Obtain branch information from parsed line output of getRawBranches()
|
||||||
|
80
pkg/commands/git_commands/worktree_loader.go
Normal file
80
pkg/commands/git_commands/worktree_loader.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorktreeLoader struct {
|
||||||
|
*common.Common
|
||||||
|
cmd oscommands.ICmdObjBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorktreeLoader(
|
||||||
|
common *common.Common,
|
||||||
|
cmd oscommands.ICmdObjBuilder,
|
||||||
|
) *WorktreeLoader {
|
||||||
|
return &WorktreeLoader{
|
||||||
|
Common: common,
|
||||||
|
cmd: cmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
|
||||||
|
currentDir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain", "-z").ToArgv()
|
||||||
|
worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
splitLines := strings.Split(worktreesOutput, "\x00")
|
||||||
|
|
||||||
|
var worktrees []*models.Worktree
|
||||||
|
var currentWorktree *models.Worktree
|
||||||
|
for _, splitLine := range splitLines {
|
||||||
|
if len(splitLine) == 0 && currentWorktree != nil {
|
||||||
|
|
||||||
|
worktrees = append(worktrees, currentWorktree)
|
||||||
|
currentWorktree = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(splitLine, "worktree ") {
|
||||||
|
main := false
|
||||||
|
name := "main"
|
||||||
|
path := strings.SplitN(splitLine, " ", 2)[1]
|
||||||
|
if len(worktrees) == 0 {
|
||||||
|
main = true
|
||||||
|
} else {
|
||||||
|
name = filepath.Base(path)
|
||||||
|
}
|
||||||
|
currentWorktree = &models.Worktree{
|
||||||
|
Name: name,
|
||||||
|
Path: path,
|
||||||
|
Main: main,
|
||||||
|
Current: path == currentDir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
worktree /Users/jbaranick/Source/lazygit
|
||||||
|
HEAD f6d6b5dec0432ffa953611700ab9b1ff0089f948
|
||||||
|
branch refs/heads/worktree_support
|
||||||
|
|
||||||
|
worktree /Users/jbaranick/Source/lazygit/.worktrees/worktree_tests
|
||||||
|
HEAD f6d6b5dec0432ffa953611700ab9b1ff0089f948
|
||||||
|
branch refs/heads/worktree_tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
return worktrees, nil
|
||||||
|
}
|
21
pkg/commands/models/worktree.go
Normal file
21
pkg/commands/models/worktree.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// Worktree : A git worktree
|
||||||
|
type Worktree struct {
|
||||||
|
Name string
|
||||||
|
Main bool
|
||||||
|
Current bool
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worktree) RefName() string {
|
||||||
|
return w.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worktree) ID() string {
|
||||||
|
return w.RefName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worktree) Description() string {
|
||||||
|
return w.RefName()
|
||||||
|
}
|
@ -11,6 +11,7 @@ const (
|
|||||||
FILES_CONTEXT_KEY types.ContextKey = "files"
|
FILES_CONTEXT_KEY types.ContextKey = "files"
|
||||||
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
|
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
|
||||||
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
|
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
|
||||||
|
WORKTREES_CONTEXT_KEY types.ContextKey = "worktrees"
|
||||||
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
|
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
|
||||||
TAGS_CONTEXT_KEY types.ContextKey = "tags"
|
TAGS_CONTEXT_KEY types.ContextKey = "tags"
|
||||||
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
|
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
|
||||||
@ -49,6 +50,7 @@ var AllContextKeys = []types.ContextKey{
|
|||||||
FILES_CONTEXT_KEY,
|
FILES_CONTEXT_KEY,
|
||||||
LOCAL_BRANCHES_CONTEXT_KEY,
|
LOCAL_BRANCHES_CONTEXT_KEY,
|
||||||
REMOTES_CONTEXT_KEY,
|
REMOTES_CONTEXT_KEY,
|
||||||
|
WORKTREES_CONTEXT_KEY,
|
||||||
REMOTE_BRANCHES_CONTEXT_KEY,
|
REMOTE_BRANCHES_CONTEXT_KEY,
|
||||||
TAGS_CONTEXT_KEY,
|
TAGS_CONTEXT_KEY,
|
||||||
LOCAL_COMMITS_CONTEXT_KEY,
|
LOCAL_COMMITS_CONTEXT_KEY,
|
||||||
@ -84,6 +86,7 @@ type ContextTree struct {
|
|||||||
LocalCommits *LocalCommitsContext
|
LocalCommits *LocalCommitsContext
|
||||||
CommitFiles *CommitFilesContext
|
CommitFiles *CommitFilesContext
|
||||||
Remotes *RemotesContext
|
Remotes *RemotesContext
|
||||||
|
Worktrees *WorktreesContext
|
||||||
Submodules *SubmodulesContext
|
Submodules *SubmodulesContext
|
||||||
RemoteBranches *RemoteBranchesContext
|
RemoteBranches *RemoteBranchesContext
|
||||||
ReflogCommits *ReflogCommitsContext
|
ReflogCommits *ReflogCommitsContext
|
||||||
@ -121,6 +124,7 @@ func (self *ContextTree) Flatten() []types.Context {
|
|||||||
self.Files,
|
self.Files,
|
||||||
self.SubCommits,
|
self.SubCommits,
|
||||||
self.Remotes,
|
self.Remotes,
|
||||||
|
self.Worktrees,
|
||||||
self.RemoteBranches,
|
self.RemoteBranches,
|
||||||
self.Tags,
|
self.Tags,
|
||||||
self.Branches,
|
self.Branches,
|
||||||
|
@ -29,6 +29,7 @@ func NewContextTree(c *ContextCommon) *ContextTree {
|
|||||||
Submodules: NewSubmodulesContext(c),
|
Submodules: NewSubmodulesContext(c),
|
||||||
Menu: NewMenuContext(c),
|
Menu: NewMenuContext(c),
|
||||||
Remotes: NewRemotesContext(c),
|
Remotes: NewRemotesContext(c),
|
||||||
|
Worktrees: NewWorktreesContext(c),
|
||||||
RemoteBranches: NewRemoteBranchesContext(c),
|
RemoteBranches: NewRemoteBranchesContext(c),
|
||||||
LocalCommits: NewLocalCommitsContext(c),
|
LocalCommits: NewLocalCommitsContext(c),
|
||||||
CommitFiles: commitFilesContext,
|
CommitFiles: commitFilesContext,
|
||||||
|
52
pkg/gui/context/worktrees_context.go
Normal file
52
pkg/gui/context/worktrees_context.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorktreesContext struct {
|
||||||
|
*FilteredListViewModel[*models.Worktree]
|
||||||
|
*ListContextTrait
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.IListContext = (*WorktreesContext)(nil)
|
||||||
|
|
||||||
|
func NewWorktreesContext(c *ContextCommon) *WorktreesContext {
|
||||||
|
viewModel := NewFilteredListViewModel(
|
||||||
|
func() []*models.Worktree { return c.Model().Worktrees },
|
||||||
|
func(Worktree *models.Worktree) []string {
|
||||||
|
return []string{Worktree.Name}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||||
|
return presentation.GetWorktreeListDisplayStrings(c.Model().Worktrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WorktreesContext{
|
||||||
|
FilteredListViewModel: viewModel,
|
||||||
|
ListContextTrait: &ListContextTrait{
|
||||||
|
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||||
|
View: c.Views().Worktrees,
|
||||||
|
WindowName: "branches",
|
||||||
|
Key: WORKTREES_CONTEXT_KEY,
|
||||||
|
Kind: types.SIDE_CONTEXT,
|
||||||
|
Focusable: true,
|
||||||
|
})),
|
||||||
|
list: viewModel,
|
||||||
|
getDisplayStrings: getDisplayStrings,
|
||||||
|
c: c,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesContext) GetSelectedItemId() string {
|
||||||
|
item := self.GetSelected()
|
||||||
|
if item == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.ID()
|
||||||
|
}
|
@ -138,6 +138,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
|||||||
common,
|
common,
|
||||||
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
|
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
|
||||||
)
|
)
|
||||||
|
worktreesController := controllers.NewWorktreesController(common)
|
||||||
undoController := controllers.NewUndoController(common)
|
undoController := controllers.NewUndoController(common)
|
||||||
globalController := controllers.NewGlobalController(common)
|
globalController := controllers.NewGlobalController(common)
|
||||||
contextLinesController := controllers.NewContextLinesController(common)
|
contextLinesController := controllers.NewContextLinesController(common)
|
||||||
@ -177,6 +178,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
|||||||
for _, context := range []types.Context{
|
for _, context := range []types.Context{
|
||||||
gui.State.Contexts.Status,
|
gui.State.Contexts.Status,
|
||||||
gui.State.Contexts.Remotes,
|
gui.State.Contexts.Remotes,
|
||||||
|
gui.State.Contexts.Worktrees,
|
||||||
gui.State.Contexts.Tags,
|
gui.State.Contexts.Tags,
|
||||||
gui.State.Contexts.Branches,
|
gui.State.Contexts.Branches,
|
||||||
gui.State.Contexts.RemoteBranches,
|
gui.State.Contexts.RemoteBranches,
|
||||||
@ -298,6 +300,10 @@ func (gui *Gui) resetHelpersAndControllers() {
|
|||||||
remotesController,
|
remotesController,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
controllers.AttachControllers(gui.State.Contexts.Worktrees,
|
||||||
|
worktreesController,
|
||||||
|
)
|
||||||
|
|
||||||
controllers.AttachControllers(gui.State.Contexts.Stash,
|
controllers.AttachControllers(gui.State.Contexts.Stash,
|
||||||
stashController,
|
stashController,
|
||||||
)
|
)
|
||||||
|
@ -83,6 +83,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
|||||||
types.REFLOG,
|
types.REFLOG,
|
||||||
types.TAGS,
|
types.TAGS,
|
||||||
types.REMOTES,
|
types.REMOTES,
|
||||||
|
types.WORKTREES,
|
||||||
types.STATUS,
|
types.STATUS,
|
||||||
types.BISECT_INFO,
|
types.BISECT_INFO,
|
||||||
types.STAGING,
|
types.STAGING,
|
||||||
@ -150,6 +151,10 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
|||||||
refresh("remotes", func() { _ = self.refreshRemotes() })
|
refresh("remotes", func() { _ = self.refreshRemotes() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scopeSet.Includes(types.WORKTREES) {
|
||||||
|
refresh("worktrees", func() { _ = self.refreshWorktrees() })
|
||||||
|
}
|
||||||
|
|
||||||
if scopeSet.Includes(types.STAGING) {
|
if scopeSet.Includes(types.STAGING) {
|
||||||
refresh("staging", func() {
|
refresh("staging", func() {
|
||||||
fileWg.Wait()
|
fileWg.Wait()
|
||||||
@ -197,6 +202,7 @@ func getScopeNames(scopes []types.RefreshableView) []string {
|
|||||||
types.REFLOG: "reflog",
|
types.REFLOG: "reflog",
|
||||||
types.TAGS: "tags",
|
types.TAGS: "tags",
|
||||||
types.REMOTES: "remotes",
|
types.REMOTES: "remotes",
|
||||||
|
types.WORKTREES: "worktrees",
|
||||||
types.STATUS: "status",
|
types.STATUS: "status",
|
||||||
types.BISECT_INFO: "bisect",
|
types.BISECT_INFO: "bisect",
|
||||||
types.STAGING: "staging",
|
types.STAGING: "staging",
|
||||||
@ -589,6 +595,17 @@ func (self *RefreshHelper) refreshRemotes() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *RefreshHelper) refreshWorktrees() error {
|
||||||
|
worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees()
|
||||||
|
if err != nil {
|
||||||
|
return self.c.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.Model().Worktrees = worktrees
|
||||||
|
|
||||||
|
return self.c.PostRefreshUpdate(self.c.Contexts().Worktrees)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *RefreshHelper) refreshStashEntries() error {
|
func (self *RefreshHelper) refreshStashEntries() error {
|
||||||
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
|
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
|
||||||
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
||||||
|
186
pkg/gui/controllers/worktrees_controller.go
Normal file
186
pkg/gui/controllers/worktrees_controller.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorktreesController struct {
|
||||||
|
baseController
|
||||||
|
c *ControllerCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.IController = &WorktreesController{}
|
||||||
|
|
||||||
|
func NewWorktreesController(
|
||||||
|
common *ControllerCommon,
|
||||||
|
) *WorktreesController {
|
||||||
|
return &WorktreesController{
|
||||||
|
baseController: baseController{},
|
||||||
|
c: common,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||||
|
bindings := []*types.Binding{
|
||||||
|
{
|
||||||
|
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||||
|
Handler: self.checkSelected(self.enter),
|
||||||
|
Description: self.c.Tr.EnterWorktree,
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||||
|
// Handler: self.withSelectedTag(self.delete),
|
||||||
|
// Description: self.c.Tr.LcDeleteTag,
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// Key: opts.GetKey(opts.Config.Branches.PushTag),
|
||||||
|
// Handler: self.withSelectedTag(self.push),
|
||||||
|
// Description: self.c.Tr.LcPushTag,
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// Key: opts.GetKey(opts.Config.Universal.New),
|
||||||
|
// Handler: self.create,
|
||||||
|
// Description: self.c.Tr.LcCreateTag,
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
|
||||||
|
// Handler: self.withSelectedTag(self.createResetMenu),
|
||||||
|
// Description: self.c.Tr.LcViewResetOptions,
|
||||||
|
// OpensMenu: true,
|
||||||
|
//},
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) GetOnRenderToMain() func() error {
|
||||||
|
return func() error {
|
||||||
|
var task types.UpdateTask
|
||||||
|
worktree := self.context().GetSelected()
|
||||||
|
if worktree == nil {
|
||||||
|
task = types.NewRenderStringTask("No worktrees")
|
||||||
|
} else {
|
||||||
|
task = types.NewRenderStringTask(fmt.Sprintf("%s\nPath: %s", style.FgGreen.Sprint(worktree.Name), worktree.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||||
|
Pair: self.c.MainViewPairs().Normal,
|
||||||
|
Main: &types.ViewUpdateOpts{
|
||||||
|
Title: "Worktree",
|
||||||
|
Task: task,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (self *WorktreesController) switchToWorktree(worktree *models.Worktree) error {
|
||||||
|
// //self.c.LogAction(self.c.Tr.Actions.CheckoutTag)
|
||||||
|
// //if err := self.helpers.Refs.CheckoutRef(tag.Name, types.CheckoutRefOptions{}); err != nil {
|
||||||
|
// // return err
|
||||||
|
// //}
|
||||||
|
// //return self.c.PushContext(self.contexts.Branches)
|
||||||
|
//
|
||||||
|
// wd, err := os.Getwd()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// gui.RepoPathStack.Push(wd)
|
||||||
|
//
|
||||||
|
// return gui.dispatchSwitchToRepo(submodule.Path, true)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// func (self *WorktreesController) delete(tag *models.Tag) error {
|
||||||
|
// prompt := utils.ResolvePlaceholderString(
|
||||||
|
// self.c.Tr.DeleteTagPrompt,
|
||||||
|
// map[string]string{
|
||||||
|
// "tagName": tag.Name,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// return self.c.Confirm(types.ConfirmOpts{
|
||||||
|
// Title: self.c.Tr.DeleteTagTitle,
|
||||||
|
// Prompt: prompt,
|
||||||
|
// HandleConfirm: func() error {
|
||||||
|
// self.c.LogAction(self.c.Tr.Actions.DeleteTag)
|
||||||
|
// if err := self.git.Tag.Delete(tag.Name); err != nil {
|
||||||
|
// return self.c.Error(err)
|
||||||
|
// }
|
||||||
|
// return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (self *WorktreesController) push(tag *models.Tag) error {
|
||||||
|
// title := utils.ResolvePlaceholderString(
|
||||||
|
// self.c.Tr.PushTagTitle,
|
||||||
|
// map[string]string{
|
||||||
|
// "tagName": tag.Name,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// return self.c.Prompt(types.PromptOpts{
|
||||||
|
// Title: title,
|
||||||
|
// InitialContent: "origin",
|
||||||
|
// FindSuggestionsFunc: self.helpers.Suggestions.GetRemoteSuggestionsFunc(),
|
||||||
|
// HandleConfirm: func(response string) error {
|
||||||
|
// return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func() error {
|
||||||
|
// self.c.LogAction(self.c.Tr.Actions.PushTag)
|
||||||
|
// err := self.git.Tag.Push(response, tag.Name)
|
||||||
|
// if err != nil {
|
||||||
|
// _ = self.c.Error(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (self *WorktreesController) createResetMenu(tag *models.Tag) error {
|
||||||
|
// return self.helpers.Refs.CreateGitResetMenu(tag.Name)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (self *WorktreesController) create() error {
|
||||||
|
// // leaving commit SHA blank so that we're just creating the tag for the current commit
|
||||||
|
// return self.helpers.Tags.CreateTagMenu("", func() { self.context().SetSelectedLineIdx(0) })
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (self *WorktreesController) GetOnClick() func() error {
|
||||||
|
return self.checkSelected(self.enter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) enter(worktree *models.Worktree) error {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.State().GetRepoPathStack().Push(wd)
|
||||||
|
|
||||||
|
return self.c.Helpers().Repos.DispatchSwitchToRepo(worktree.Path, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) checkSelected(callback func(worktree *models.Worktree) error) func() error {
|
||||||
|
return func() error {
|
||||||
|
worktree := self.context().GetSelected()
|
||||||
|
if worktree == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(worktree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) Context() types.Context {
|
||||||
|
return self.context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WorktreesController) context() *context.WorktreesContext {
|
||||||
|
return self.c.Contexts().Worktrees
|
||||||
|
}
|
@ -569,6 +569,10 @@ func (gui *Gui) viewTabMap() map[string][]context.TabView {
|
|||||||
Tab: gui.c.Tr.TagsTitle,
|
Tab: gui.c.Tr.TagsTitle,
|
||||||
ViewName: "tags",
|
ViewName: "tags",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Tab: gui.c.Tr.WorktreesTitle,
|
||||||
|
ViewName: "worktrees",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"commits": {
|
"commits": {
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,7 @@ var (
|
|||||||
MERGE_COMMIT_ICON = "\U000f062d" //
|
MERGE_COMMIT_ICON = "\U000f062d" //
|
||||||
DEFAULT_REMOTE_ICON = "\uf02a2" //
|
DEFAULT_REMOTE_ICON = "\uf02a2" //
|
||||||
STASH_ICON = "\uf01c" //
|
STASH_ICON = "\uf01c" //
|
||||||
|
WORKTREE_ICON = "\uf02b" //
|
||||||
)
|
)
|
||||||
|
|
||||||
var remoteIcons = map[string]string{
|
var remoteIcons = map[string]string{
|
||||||
@ -68,3 +69,7 @@ func IconForRemote(remote *models.Remote) string {
|
|||||||
func IconForStash(stash *models.StashEntry) string {
|
func IconForStash(stash *models.StashEntry) string {
|
||||||
return STASH_ICON
|
return STASH_ICON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IconForWorktree(tag *models.Worktree) string {
|
||||||
|
return WORKTREE_ICON
|
||||||
|
}
|
||||||
|
35
pkg/gui/presentation/worktrees.go
Normal file
35
pkg/gui/presentation/worktrees.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package presentation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/generics/slices"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetWorktreeListDisplayStrings(worktrees []*models.Worktree) [][]string {
|
||||||
|
return slices.Map(worktrees, func(worktree *models.Worktree) []string {
|
||||||
|
return getWorktreeDisplayStrings(worktree)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWorktreeDisplayStrings returns the display string of branch
|
||||||
|
func getWorktreeDisplayStrings(w *models.Worktree) []string {
|
||||||
|
textStyle := theme.DefaultTextColor
|
||||||
|
|
||||||
|
current := ""
|
||||||
|
currentColor := style.FgCyan
|
||||||
|
if w.Current {
|
||||||
|
current = " *"
|
||||||
|
currentColor = style.FgGreen
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 3)
|
||||||
|
res = append(res, currentColor.Sprint(current))
|
||||||
|
if icons.IsIconEnabled() {
|
||||||
|
res = append(res, textStyle.Sprint(icons.IconForWorktree(w)))
|
||||||
|
}
|
||||||
|
res = append(res, textStyle.Sprint(w.Name))
|
||||||
|
return res
|
||||||
|
}
|
@ -201,6 +201,7 @@ type Model struct {
|
|||||||
StashEntries []*models.StashEntry
|
StashEntries []*models.StashEntry
|
||||||
SubCommits []*models.Commit
|
SubCommits []*models.Commit
|
||||||
Remotes []*models.Remote
|
Remotes []*models.Remote
|
||||||
|
Worktrees []*models.Worktree
|
||||||
|
|
||||||
// FilteredReflogCommits are the ones that appear in the reflog panel.
|
// FilteredReflogCommits are the ones that appear in the reflog panel.
|
||||||
// when in filtering mode we only include the ones that match the given path
|
// when in filtering mode we only include the ones that match the given path
|
||||||
|
@ -13,6 +13,7 @@ const (
|
|||||||
REFLOG
|
REFLOG
|
||||||
TAGS
|
TAGS
|
||||||
REMOTES
|
REMOTES
|
||||||
|
WORKTREES
|
||||||
STATUS
|
STATUS
|
||||||
SUBMODULES
|
SUBMODULES
|
||||||
STAGING
|
STAGING
|
||||||
|
@ -8,6 +8,7 @@ type Views struct {
|
|||||||
Files *gocui.View
|
Files *gocui.View
|
||||||
Branches *gocui.View
|
Branches *gocui.View
|
||||||
Remotes *gocui.View
|
Remotes *gocui.View
|
||||||
|
Worktrees *gocui.View
|
||||||
Tags *gocui.View
|
Tags *gocui.View
|
||||||
RemoteBranches *gocui.View
|
RemoteBranches *gocui.View
|
||||||
ReflogCommits *gocui.View
|
ReflogCommits *gocui.View
|
||||||
|
@ -29,6 +29,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
|
|||||||
{viewPtr: &gui.Views.Files, name: "files"},
|
{viewPtr: &gui.Views.Files, name: "files"},
|
||||||
{viewPtr: &gui.Views.Tags, name: "tags"},
|
{viewPtr: &gui.Views.Tags, name: "tags"},
|
||||||
{viewPtr: &gui.Views.Remotes, name: "remotes"},
|
{viewPtr: &gui.Views.Remotes, name: "remotes"},
|
||||||
|
{viewPtr: &gui.Views.Worktrees, name: "worktrees"},
|
||||||
{viewPtr: &gui.Views.Branches, name: "localBranches"},
|
{viewPtr: &gui.Views.Branches, name: "localBranches"},
|
||||||
{viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"},
|
{viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"},
|
||||||
{viewPtr: &gui.Views.ReflogCommits, name: "reflogCommits"},
|
{viewPtr: &gui.Views.ReflogCommits, name: "reflogCommits"},
|
||||||
@ -113,6 +114,8 @@ func (gui *Gui) createAllViews() error {
|
|||||||
|
|
||||||
gui.Views.Remotes.Title = gui.c.Tr.RemotesTitle
|
gui.Views.Remotes.Title = gui.c.Tr.RemotesTitle
|
||||||
|
|
||||||
|
gui.Views.Worktrees.Title = gui.c.Tr.WorktreesTitle
|
||||||
|
|
||||||
gui.Views.Tags.Title = gui.c.Tr.TagsTitle
|
gui.Views.Tags.Title = gui.c.Tr.TagsTitle
|
||||||
|
|
||||||
gui.Views.Files.Title = gui.c.Tr.FilesTitle
|
gui.Views.Files.Title = gui.c.Tr.FilesTitle
|
||||||
|
@ -200,6 +200,7 @@ type TranslationSet struct {
|
|||||||
TagsTitle string
|
TagsTitle string
|
||||||
MenuTitle string
|
MenuTitle string
|
||||||
RemotesTitle string
|
RemotesTitle string
|
||||||
|
WorktreesTitle string
|
||||||
RemoteBranchesTitle string
|
RemoteBranchesTitle string
|
||||||
PatchBuildingTitle string
|
PatchBuildingTitle string
|
||||||
InformationTitle string
|
InformationTitle string
|
||||||
@ -541,6 +542,7 @@ type TranslationSet struct {
|
|||||||
FilterPrefix string
|
FilterPrefix string
|
||||||
ExitSearchMode string
|
ExitSearchMode string
|
||||||
ExitTextFilterMode string
|
ExitTextFilterMode string
|
||||||
|
EnterWorktree string
|
||||||
Actions Actions
|
Actions Actions
|
||||||
Bisect Bisect
|
Bisect Bisect
|
||||||
}
|
}
|
||||||
@ -897,6 +899,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
TagsTitle: "Tags",
|
TagsTitle: "Tags",
|
||||||
MenuTitle: "Menu",
|
MenuTitle: "Menu",
|
||||||
RemotesTitle: "Remotes",
|
RemotesTitle: "Remotes",
|
||||||
|
WorktreesTitle: "Worktrees",
|
||||||
RemoteBranchesTitle: "Remote branches",
|
RemoteBranchesTitle: "Remote branches",
|
||||||
PatchBuildingTitle: "Main panel (patch building)",
|
PatchBuildingTitle: "Main panel (patch building)",
|
||||||
InformationTitle: "Information",
|
InformationTitle: "Information",
|
||||||
@ -1239,6 +1242,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
|
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
|
||||||
SearchPrefix: "Search: ",
|
SearchPrefix: "Search: ",
|
||||||
FilterPrefix: "Filter: ",
|
FilterPrefix: "Filter: ",
|
||||||
|
EnterWorktree: "Enter worktree",
|
||||||
Actions: Actions{
|
Actions: Actions{
|
||||||
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
||||||
CheckoutCommit: "Checkout commit",
|
CheckoutCommit: "Checkout commit",
|
||||||
|
Reference in New Issue
Block a user