mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Initial addition of support for worktrees
This commit is contained in:
		
				
					committed by
					
						 Jesse Duffield
						Jesse Duffield
					
				
			
			
				
	
			
			
			
						parent
						
							52447e5d46
						
					
				
				
					commit
					f8ba899b87
				
			
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -39,3 +39,5 @@ test/results/** | ||||
|  | ||||
| oryxBuildBinary | ||||
| __debug_bin | ||||
|  | ||||
| .worktrees | ||||
| @@ -116,6 +116,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string { | ||||
| 		"stash":             tr.StashTitle, | ||||
| 		"suggestions":       tr.SuggestionsCheatsheetTitle, | ||||
| 		"extras":            tr.ExtrasTitle, | ||||
| 		"worktrees":         tr.WorktreesTitle, | ||||
| 	} | ||||
|  | ||||
| 	title, ok := contextTitleMap[str] | ||||
|   | ||||
| @@ -50,6 +50,7 @@ type Loaders struct { | ||||
| 	RemoteLoader       *git_commands.RemoteLoader | ||||
| 	StashLoader        *git_commands.StashLoader | ||||
| 	TagLoader          *git_commands.TagLoader | ||||
| 	Worktrees          *git_commands.WorktreeLoader | ||||
| } | ||||
|  | ||||
| func NewGitCommand( | ||||
| @@ -133,6 +134,7 @@ func NewGitCommandAux( | ||||
| 	commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode, gitCommon) | ||||
| 	reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd) | ||||
| 	remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes) | ||||
| 	worktreeLoader := git_commands.NewWorktreeLoader(cmn, cmd) | ||||
| 	stashLoader := git_commands.NewStashLoader(cmn, cmd) | ||||
| 	tagLoader := git_commands.NewTagLoader(cmn, cmd) | ||||
|  | ||||
| @@ -161,6 +163,7 @@ func NewGitCommandAux( | ||||
| 			FileLoader:         fileLoader, | ||||
| 			ReflogCommitLoader: reflogCommitLoader, | ||||
| 			RemoteLoader:       remoteLoader, | ||||
| 			Worktrees:          worktreeLoader, | ||||
| 			StashLoader:        stashLoader, | ||||
| 			TagLoader:          tagLoader, | ||||
| 		}, | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package git_commands | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -117,6 +118,11 @@ outer: | ||||
| } | ||||
|  | ||||
| func (self *BranchLoader) obtainBranches() []*models.Branch { | ||||
| 	currentDir, err := os.Getwd() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	output, err := self.getRawBranches() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| @@ -138,6 +144,11 @@ func (self *BranchLoader) obtainBranches() []*models.Branch { | ||||
| 			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 | ||||
| 	}) | ||||
| } | ||||
| @@ -166,6 +177,7 @@ var branchFields = []string{ | ||||
| 	"upstream:track", | ||||
| 	"subject", | ||||
| 	fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE), | ||||
| 	"worktreepath", | ||||
| } | ||||
|  | ||||
| // 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" | ||||
| 	LOCAL_BRANCHES_CONTEXT_KEY           types.ContextKey = "localBranches" | ||||
| 	REMOTES_CONTEXT_KEY                  types.ContextKey = "remotes" | ||||
| 	WORKTREES_CONTEXT_KEY                types.ContextKey = "worktrees" | ||||
| 	REMOTE_BRANCHES_CONTEXT_KEY          types.ContextKey = "remoteBranches" | ||||
| 	TAGS_CONTEXT_KEY                     types.ContextKey = "tags" | ||||
| 	LOCAL_COMMITS_CONTEXT_KEY            types.ContextKey = "commits" | ||||
| @@ -49,6 +50,7 @@ var AllContextKeys = []types.ContextKey{ | ||||
| 	FILES_CONTEXT_KEY, | ||||
| 	LOCAL_BRANCHES_CONTEXT_KEY, | ||||
| 	REMOTES_CONTEXT_KEY, | ||||
| 	WORKTREES_CONTEXT_KEY, | ||||
| 	REMOTE_BRANCHES_CONTEXT_KEY, | ||||
| 	TAGS_CONTEXT_KEY, | ||||
| 	LOCAL_COMMITS_CONTEXT_KEY, | ||||
| @@ -84,6 +86,7 @@ type ContextTree struct { | ||||
| 	LocalCommits                *LocalCommitsContext | ||||
| 	CommitFiles                 *CommitFilesContext | ||||
| 	Remotes                     *RemotesContext | ||||
| 	Worktrees                   *WorktreesContext | ||||
| 	Submodules                  *SubmodulesContext | ||||
| 	RemoteBranches              *RemoteBranchesContext | ||||
| 	ReflogCommits               *ReflogCommitsContext | ||||
| @@ -121,6 +124,7 @@ func (self *ContextTree) Flatten() []types.Context { | ||||
| 		self.Files, | ||||
| 		self.SubCommits, | ||||
| 		self.Remotes, | ||||
| 		self.Worktrees, | ||||
| 		self.RemoteBranches, | ||||
| 		self.Tags, | ||||
| 		self.Branches, | ||||
|   | ||||
| @@ -29,6 +29,7 @@ func NewContextTree(c *ContextCommon) *ContextTree { | ||||
| 		Submodules:     NewSubmodulesContext(c), | ||||
| 		Menu:           NewMenuContext(c), | ||||
| 		Remotes:        NewRemotesContext(c), | ||||
| 		Worktrees:      NewWorktreesContext(c), | ||||
| 		RemoteBranches: NewRemoteBranchesContext(c), | ||||
| 		LocalCommits:   NewLocalCommitsContext(c), | ||||
| 		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, | ||||
| 		func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches }, | ||||
| 	) | ||||
| 	worktreesController := controllers.NewWorktreesController(common) | ||||
| 	undoController := controllers.NewUndoController(common) | ||||
| 	globalController := controllers.NewGlobalController(common) | ||||
| 	contextLinesController := controllers.NewContextLinesController(common) | ||||
| @@ -177,6 +178,7 @@ func (gui *Gui) resetHelpersAndControllers() { | ||||
| 	for _, context := range []types.Context{ | ||||
| 		gui.State.Contexts.Status, | ||||
| 		gui.State.Contexts.Remotes, | ||||
| 		gui.State.Contexts.Worktrees, | ||||
| 		gui.State.Contexts.Tags, | ||||
| 		gui.State.Contexts.Branches, | ||||
| 		gui.State.Contexts.RemoteBranches, | ||||
| @@ -298,6 +300,10 @@ func (gui *Gui) resetHelpersAndControllers() { | ||||
| 		remotesController, | ||||
| 	) | ||||
|  | ||||
| 	controllers.AttachControllers(gui.State.Contexts.Worktrees, | ||||
| 		worktreesController, | ||||
| 	) | ||||
|  | ||||
| 	controllers.AttachControllers(gui.State.Contexts.Stash, | ||||
| 		stashController, | ||||
| 	) | ||||
|   | ||||
| @@ -83,6 +83,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error { | ||||
| 				types.REFLOG, | ||||
| 				types.TAGS, | ||||
| 				types.REMOTES, | ||||
| 				types.WORKTREES, | ||||
| 				types.STATUS, | ||||
| 				types.BISECT_INFO, | ||||
| 				types.STAGING, | ||||
| @@ -150,6 +151,10 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error { | ||||
| 			refresh("remotes", func() { _ = self.refreshRemotes() }) | ||||
| 		} | ||||
|  | ||||
| 		if scopeSet.Includes(types.WORKTREES) { | ||||
| 			refresh("worktrees", func() { _ = self.refreshWorktrees() }) | ||||
| 		} | ||||
|  | ||||
| 		if scopeSet.Includes(types.STAGING) { | ||||
| 			refresh("staging", func() { | ||||
| 				fileWg.Wait() | ||||
| @@ -197,6 +202,7 @@ func getScopeNames(scopes []types.RefreshableView) []string { | ||||
| 		types.REFLOG:          "reflog", | ||||
| 		types.TAGS:            "tags", | ||||
| 		types.REMOTES:         "remotes", | ||||
| 		types.WORKTREES:       "worktrees", | ||||
| 		types.STATUS:          "status", | ||||
| 		types.BISECT_INFO:     "bisect", | ||||
| 		types.STAGING:         "staging", | ||||
| @@ -589,6 +595,17 @@ func (self *RefreshHelper) refreshRemotes() error { | ||||
| 	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 { | ||||
| 	self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader. | ||||
| 		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, | ||||
| 				ViewName: "tags", | ||||
| 			}, | ||||
| 			{ | ||||
| 				Tab:      gui.c.Tr.WorktreesTitle, | ||||
| 				ViewName: "worktrees", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"commits": { | ||||
| 			{ | ||||
|   | ||||
| @@ -14,6 +14,7 @@ var ( | ||||
| 	MERGE_COMMIT_ICON   = "\U000f062d" //  | ||||
| 	DEFAULT_REMOTE_ICON = "\uf02a2"    //  | ||||
| 	STASH_ICON          = "\uf01c"     //  | ||||
| 	WORKTREE_ICON       = "\uf02b"     //  | ||||
| ) | ||||
|  | ||||
| var remoteIcons = map[string]string{ | ||||
| @@ -68,3 +69,7 @@ func IconForRemote(remote *models.Remote) string { | ||||
| func IconForStash(stash *models.StashEntry) string { | ||||
| 	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 | ||||
| 	SubCommits   []*models.Commit | ||||
| 	Remotes      []*models.Remote | ||||
| 	Worktrees    []*models.Worktree | ||||
|  | ||||
| 	// FilteredReflogCommits are the ones that appear in the reflog panel. | ||||
| 	// when in filtering mode we only include the ones that match the given path | ||||
|   | ||||
| @@ -13,6 +13,7 @@ const ( | ||||
| 	REFLOG | ||||
| 	TAGS | ||||
| 	REMOTES | ||||
| 	WORKTREES | ||||
| 	STATUS | ||||
| 	SUBMODULES | ||||
| 	STAGING | ||||
|   | ||||
| @@ -8,6 +8,7 @@ type Views struct { | ||||
| 	Files          *gocui.View | ||||
| 	Branches       *gocui.View | ||||
| 	Remotes        *gocui.View | ||||
| 	Worktrees      *gocui.View | ||||
| 	Tags           *gocui.View | ||||
| 	RemoteBranches *gocui.View | ||||
| 	ReflogCommits  *gocui.View | ||||
|   | ||||
| @@ -29,6 +29,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping { | ||||
| 		{viewPtr: &gui.Views.Files, name: "files"}, | ||||
| 		{viewPtr: &gui.Views.Tags, name: "tags"}, | ||||
| 		{viewPtr: &gui.Views.Remotes, name: "remotes"}, | ||||
| 		{viewPtr: &gui.Views.Worktrees, name: "worktrees"}, | ||||
| 		{viewPtr: &gui.Views.Branches, name: "localBranches"}, | ||||
| 		{viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"}, | ||||
| 		{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.Worktrees.Title = gui.c.Tr.WorktreesTitle | ||||
|  | ||||
| 	gui.Views.Tags.Title = gui.c.Tr.TagsTitle | ||||
|  | ||||
| 	gui.Views.Files.Title = gui.c.Tr.FilesTitle | ||||
|   | ||||
| @@ -200,6 +200,7 @@ type TranslationSet struct { | ||||
| 	TagsTitle                           string | ||||
| 	MenuTitle                           string | ||||
| 	RemotesTitle                        string | ||||
| 	WorktreesTitle                      string | ||||
| 	RemoteBranchesTitle                 string | ||||
| 	PatchBuildingTitle                  string | ||||
| 	InformationTitle                    string | ||||
| @@ -541,6 +542,7 @@ type TranslationSet struct { | ||||
| 	FilterPrefix                        string | ||||
| 	ExitSearchMode                      string | ||||
| 	ExitTextFilterMode                  string | ||||
| 	EnterWorktree                       string | ||||
| 	Actions                             Actions | ||||
| 	Bisect                              Bisect | ||||
| } | ||||
| @@ -897,6 +899,7 @@ func EnglishTranslationSet() TranslationSet { | ||||
| 		TagsTitle:                           "Tags", | ||||
| 		MenuTitle:                           "Menu", | ||||
| 		RemotesTitle:                        "Remotes", | ||||
| 		WorktreesTitle:                      "Worktrees", | ||||
| 		RemoteBranchesTitle:                 "Remote branches", | ||||
| 		PatchBuildingTitle:                  "Main panel (patch building)", | ||||
| 		InformationTitle:                    "Information", | ||||
| @@ -1239,6 +1242,7 @@ func EnglishTranslationSet() TranslationSet { | ||||
| 		SearchKeybindings:                   "%s: Next match, %s: Previous match, %s: Exit search mode", | ||||
| 		SearchPrefix:                        "Search: ", | ||||
| 		FilterPrefix:                        "Filter: ", | ||||
| 		EnterWorktree:                       "Enter worktree", | ||||
| 		Actions: Actions{ | ||||
| 			// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) | ||||
| 			CheckoutCommit:                    "Checkout commit", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user