diff options
author | Stefan Haller <stefan@haller-berlin.de> | 2023-10-10 08:32:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-10 08:32:42 +0200 |
commit | 013cfc77a1e82316681b2a759d90849cd1593dae (patch) | |
tree | ba137ca27bce0ba9f3eb002e22554a0558c9a836 | |
parent | 16f7b01fec5111146bff8c9d5c1eb5a63e78f14d (diff) | |
parent | 235f5bb22100ad5fd7f338680b6d37f55c07005c (diff) |
Show sync status in branches list (#3021)
31 files changed, 377 insertions, 108 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go index f057f8d54..510661034 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/go-errors/errors" - "github.com/sasha-s/go-deadlock" "github.com/spf13/afero" gogit "github.com/jesseduffield/go-git/v5" @@ -63,7 +62,6 @@ func NewGitCommand( version *git_commands.GitVersion, osCommand *oscommands.OSCommand, gitConfig git_config.IGitConfig, - syncMutex *deadlock.Mutex, ) (*GitCommand, error) { currentPath, err := os.Getwd() if err != nil { @@ -118,7 +116,6 @@ func NewGitCommand( gitConfig, repoPaths, repository, - syncMutex, ), nil } @@ -129,7 +126,6 @@ func NewGitCommandAux( gitConfig git_config.IGitConfig, repoPaths *git_commands.RepoPaths, repo *gogit.Repository, - syncMutex *deadlock.Mutex, ) *GitCommand { cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) @@ -140,7 +136,7 @@ func NewGitCommandAux( // common ones are: cmn, osCommand, dotGitDir, configCommands configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo) - gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex) + gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands) fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands) statusCommands := git_commands.NewStatusCommands(gitCommon) diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go index cf8250863..b9537165c 100644 --- a/pkg/commands/git_commands/common.go +++ b/pkg/commands/git_commands/common.go @@ -4,7 +4,6 @@ import ( gogit "github.com/jesseduffield/go-git/v5" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/common" - "github.com/sasha-s/go-deadlock" ) type GitCommon struct { @@ -15,8 +14,6 @@ type GitCommon struct { repoPaths *RepoPaths repo *gogit.Repository config *ConfigCommands - // mutex for doing things like push/pull/fetch - syncMutex *deadlock.Mutex } func NewGitCommon( @@ -27,7 +24,6 @@ func NewGitCommon( repoPaths *RepoPaths, repo *gogit.Repository, config *ConfigCommands, - syncMutex *deadlock.Mutex, ) *GitCommon { return &GitCommon{ Common: cmn, @@ -37,6 +33,5 @@ func NewGitCommon( repoPaths: repoPaths, repo: repo, config: config, - syncMutex: syncMutex, } } diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go index ce8f79442..acfb51dc9 100644 --- a/pkg/commands/git_commands/remote.go +++ b/pkg/commands/git_commands/remote.go @@ -53,7 +53,7 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin Arg(remoteName, "--delete", branchName). ToArgv() - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error { @@ -61,7 +61,7 @@ func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, Arg(remoteName, "--delete", tagName). ToArgv() - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } // CheckRemoteBranchExists Returns remote branch diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go index c32286e6d..fd7584aea 100644 --- a/pkg/commands/git_commands/sync.go +++ b/pkg/commands/git_commands/sync.go @@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch). ToArgv() - cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex) + cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task) return cmdObj, nil } @@ -70,7 +70,6 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj { cmdObj := self.cmd.New(cmdArgs) cmdObj.DontLog().FailOnCredentialRequest() - cmdObj.WithMutex(self.syncMutex) return cmdObj } @@ -96,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error { // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // has 'pull.rebase = interactive' configured. - return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run() } func (self *SyncCommands) FastForward( @@ -110,7 +109,7 @@ func (self *SyncCommands) FastForward( Arg(remoteBranchName + ":" + branchName). ToArgv() - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error { @@ -118,5 +117,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error Arg(remoteName). ToArgv() - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go index 0656e1e19..d2b01ba7e 100644 --- a/pkg/commands/git_commands/tag.go +++ b/pkg/commands/git_commands/tag.go @@ -52,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName). ToArgv() - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } diff --git a/pkg/commands/models/branch.go b/pkg/commands/models/branch.go index 5fc887f87..c5fcfdaed 100644 --- a/pkg/commands/models/branch.go +++ b/pkg/commands/models/branch.go @@ -65,6 +65,10 @@ func (b *Branch) ID() string { return b.RefName() } +func (b *Branch) URN() string { + return "branch-" + b.ID() +} + func (b *Branch) Description() string { return b.RefName() } diff --git a/pkg/commands/models/tag.go b/pkg/commands/models/tag.go index ab6076a7a..24cb83254 100644 --- a/pkg/commands/models/tag.go +++ b/pkg/commands/models/tag.go @@ -24,6 +24,10 @@ func (t *Tag) ID() string { return t.RefName() } +func (t *Tag) URN() string { + return "tag-" + t.ID() +} + func (t *Tag) Description() string { return t.Message } diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go index ac1fae52c..68324d020 100644 --- a/pkg/gui/context/branches_context.go +++ b/pkg/gui/context/branches_context.go @@ -27,6 +27,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext { getDisplayStrings := func(_ int, _ int) [][]string { return presentation.GetBranchListDisplayStrings( viewModel.GetItems(), + c.State().GetItemOperation, c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, c.Modes().Diffing.Ref, c.Tr, diff --git a/pkg/gui/context/tags_context.go b/pkg/gui/context/tags_context.go index 4a9f525f6..3da5a9576 100644 --- a/pkg/gui/context/tags_context.go +++ b/pkg/gui/context/tags_context.go @@ -27,7 +27,10 @@ func NewTagsContext( ) getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref) + return presentation.GetTagListDisplayStrings( + viewModel.GetItems(), + c.State().GetItemOperation, + c.Modes().Diffing.Ref, c.Tr) } return &TagsContext{ diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 40a482f30..43b226b58 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -112,6 +112,7 @@ func (gui *Gui) resetHelpersAndControllers() { Confirmation: helpers.NewConfirmationHelper(helperCommon), Mode: modeHelper, AppStatus: appStatusHelper, + InlineStatus: helpers.NewInlineStatusHelper(helperCommon), WindowArrangement: helpers.NewWindowArrangementHelper( gui.c, windowHelper, diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index 45f950457..403c51b4f 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -34,9 +34,10 @@ func NewBranchesController( func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { return []*types.Binding{ { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.checkSelected(self.press), - Description: self.c.Tr.Checkout, + Key: opts.GetKey(opts.Config.Universal.Select), + Handler: self.checkSelected(self.press), + GetDisabledReason: self.getDisabledReasonForPress, + Description: self.c.Tr.Checkout, }, { Key: opts.GetKey(opts.Config.Universal.New), @@ -299,6 +300,18 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error { return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{}) } +func (self *BranchesController) getDisabledReasonForPress() string { + currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() + if currentBranch != nil { + op := self.c.State().GetItemOperation(currentBranch) + if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling { + return self.c.Tr.CantCheckoutBranchWhilePulling + } + } + + return "" +} + func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) { return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees) } @@ -563,14 +576,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error { action := self.c.Tr.Actions.FastForwardBranch - message := utils.ResolvePlaceholderString( - self.c.Tr.FastForwarding, - map[string]string{ - "branch": branch.Name, - }, - ) - - return self.c.WithWaitingStatus(message, func(task gocui.Task) error { + return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { worktree, ok := self.worktreeForBranch(branch) if ok { self.c.LogAction(action) @@ -590,24 +596,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error { WorktreeGitDir: worktreeGitDir, }, ) - if err != nil { - _ = self.c.Error(err) - } - - return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + return err } else { self.c.LogAction(action) err := self.c.Git().Sync.FastForward( task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch, ) - if err != nil { - _ = self.c.Error(err) - } _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) + return err } - - return nil }) } diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go index 06c5cc973..9e05ba163 100644 --- a/pkg/gui/controllers/helpers/helpers.go +++ b/pkg/gui/controllers/helpers/helpers.go @@ -46,6 +46,7 @@ type Helpers struct { Confirmation *ConfirmationHelper Mode *ModeHelper AppStatus *AppStatusHelper + InlineStatus *InlineStatusHelper WindowArrangement *WindowArrangementHelper Search *SearchHelper Worktree *WorktreeHelper @@ -81,6 +82,7 @@ func NewStubHelpers() *Helpers { Confirmation: &ConfirmationHelper{}, Mode: &ModeHelper{}, AppStatus: &AppStatusHelper{}, + InlineStatus: &InlineStatusHelper{}, WindowArrangement: &WindowArrangementHelper{}, Search: &SearchHelper{}, Worktree: &WorktreeHelper{}, diff --git a/pkg/gui/controllers/helpers/inline_status_helper.go b/pkg/gui/controllers/helpers/inline_status_helper.go new file mode 100644 index 000000000..d4435e5b9 --- /dev/null +++ b/pkg/gui/controllers/helpers/inline_status_helper.go @@ -0,0 +1,129 @@ +package helpers + +import ( + "time" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/sasha-s/go-deadlock" +) + +type InlineStatusHelper struct { + c *HelperCommon + + contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo + mutex *deadlock.Mutex +} + +func NewInlineStatusHelper(c *HelperCommon) *InlineStatusHelper { + return &InlineStatusHelper{ + c: c, + contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo), + mutex: &deadlock.Mutex{}, + } +} + +type InlineStatusOpts struct { + Item types.HasUrn + Operation types.ItemOperation + ContextKey types.ContextKey +} + +type inlineStatusInfo struct { + refCount int + stop chan struct{} +} + +// A custom task for WithInlineStatus calls; it wraps the original one and +// hides the status whenever the task is paused, and shows it again when +// continued. +type inlineStatusHelperTask struct { + gocui.Task + + inlineStatusHelper *InlineStatusHelper + opts InlineStatusOpts +} + +// poor man's version of explicitly saying that struct X implements interface Y +var _ gocui.Task = inlineStatusHelperTask{} + +func (self inlineStatusHelperTask) Pause() { + self.inlineStatusHelper.stop(self.opts) + self.Task.Pause() + + self.inlineStatusHelper.renderContext(self.opts.ContextKey) +} + +func (self inlineStatusHelperTask) Continue() { + self.Task.Continue() + self.inlineStatusHelper.start(self.opts) +} + +func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) { + self.c.OnWorker(func(task gocui.Task) { + self.start(opts) + + err := f(inlineStatusHelperTask{task, self, opts}) + if err != nil { + self.c.OnUIThread(func() error { + return self.c.Error(err) + }) + } + + self.stop(opts) + }) +} + +func (self *InlineStatusHelper) start(opts InlineStatusOpts) { + self.c.State().SetItemOperation(opts.Item, opts.Operation) + + self.mutex.Lock() + defer self.mutex.Unlock() + + info := self.contextsWithInlineStatus[opts.ContextKey] + if info == nil { + info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})} + self.contextsWithInlineStatus[opts.ContextKey] = info + + go utils.Safe(func() { + ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval) + defer ticker.Stop() + outer: + for { + select { + case <-ticker.C: + self.renderContext(opts.ContextKey) + case <-info.stop: + break outer + } + } + }) + } + + info.refCount++ +} + +func (self *InlineStatusHelper) stop(opts InlineStatusOpts) { + self.mutex.Lock() + + if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil { + info.refCount-- + if info.refCount <= 0 { + info.stop <- struct{}{} + delete(self.contextsWithInlineStatus, opts.ContextKey) + } + + } + + self.mutex.Unlock() + + self.c.State().ClearItemOperation(opts.Item) +} + +func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) { + self.c.OnUIThread(func() error { + _ = self.c.ContextForKey(contextKey).HandleRender() + return nil + }) +} diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index f04b102e4..a299ea431 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -115,12 +115,15 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error { } } + includeWorktreesWithBranches := false if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) { // whenever we change commits, we should update branches because the upstream/downstream // counts can change. Whenever we change branches we should also change commits // e.g. in the case of switching branches. refresh("commits and commit files", self.refreshCommitsAndCommitFiles) - refresh("reflog and branches", self.refreshReflogAndBranches) + + includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES) + refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) }) } else if scopeSet.Includes(types.REBASE_COMMITS) { // the above block handles rebase commits so we only need to call this one // if we've asked specifically for rebase commits and not those other things @@ -157,7 +160,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error { refresh("remotes", func() { _ = self.refreshRemotes() }) } - if scopeSet.Includes(types.WORKTREES) { + if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches { refresh("worktrees", func() { _ = self.refreshWorktrees() }) } @@ -242,7 +245,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { case types.INITIAL: self.c.OnWorker(func(_ gocui.Task) { _ = self.refreshReflogCommits() - self.refreshBranches() + self.refreshBranches(false) self.c.State().GetRepoState().SetStartupStage(types.COMPLETE) }) @@ -251,10 +254,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { } } -func (self *RefreshHelper) refreshReflogAndBranches() { +func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) { self.refreshReflogCommitsConsideringStartup() - self.refreshBranches() + self.refreshBranches(refreshWorktrees) } func (self *RefreshHelper) refreshCommitsAndCommitFiles() { @@ -419,7 +422,7 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error { // self.refreshStatus is called at the end of this because that's when we can // be sure there is a State.Model.Branches array to pick the current branch from -func (self *RefreshHelper) refreshBranches() { +func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) { self.c.Mutexes().RefreshingBranchesMutex.Lock() defer self.c.Mutexes().RefreshingBranchesMutex.Unlock() @@ -443,6 +446,13 @@ func (self *RefreshHelper) refreshBranches() { self.c.Model().Branches = branches + if refreshWorktrees { + self.loadWorktrees() + if err := self.c.PostRefreshUpdate(self.c.Contexts().Worktrees); err != nil { + self.c.Log.Error(err) + } + } + if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil { self.c.Log.Error(err) } @@ -636,15 +646,18 @@ func (self *RefreshHelper) refreshRemotes() error { return nil } -func (self *RefreshHelper) refreshWorktrees() error { +func (self *RefreshHelper) loadWorktrees() { worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees() if err != nil { self.c.Log.Error(err) self.c.Model().Worktrees = []*models.Worktree{} - return nil } self.c.Model().Worktrees = worktrees +} + +func (self *RefreshHelper) refreshWorktrees() error { + self.loadWorktrees() // need to refresh branches because the branches view shows worktrees against // branches @@ -678,7 +691,7 @@ func (self *RefreshHelper) refreshStatus() { repoName := self.c.Git().RepoPaths.RepoName() - status := presentation.FormatStatus(repoName, currentBranch, linkedWorktreeName, workingTreeState, self.c.Tr) + status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr) self.c.SetViewContent(self.c.Views().Status, status) } diff --git a/pkg/gui/controllers/helpers/repos_helper.go b/pkg/gui/controllers/helpers/repos_helper.go index 3ebd7767d..59d45e0c1 100644 --- a/pkg/gui/controllers/helpers/repos_helper.go +++ b/pkg/gui/controllers/helpers/repos_helper.go @@ -173,11 +173,6 @@ func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey return err } - // these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to - // switch to a repo while one of these goroutines is in the process of updating something - self.c.Mutexes().SyncMutex.Lock() - defer self.c.Mutexes().SyncMutex.Unlock() - self.c.Mutexes().RefreshingFilesMutex.Lock() defer self.c.Mutexes().RefreshingFilesMutex.Unlock() diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go index 8344dfe76..2a186670b 100644 --- a/pkg/gui/controllers/status_controller.go +++ b/pkg/gui/controllers/status_controller.go @@ -106,7 +106,7 @@ func (self *StatusController) onClick() error { } cx, _ := self.c.Views().Status.Cursor() - upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr) + upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr) repoName := self.c.Git().RepoPaths.RepoName() workingTreeState := self.c.Git().Status.WorkingTreeState() switch workingTreeState { diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go index a77d047a6..4e3369e0a 100644 --- a/pkg/gui/controllers/sync_controller.go +++ b/pkg/gui/controllers/sync_controller.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -30,14 +31,16 @@ func NewSyncController( func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { bindings := []*types.Binding{ { - Key: opts.GetKey(opts.Config.Universal.Push), - Handler: opts.Guards.NoPopupPanel(self.HandlePush), - Description: self.c.Tr.Push, + Key: opts.GetKey(opts.Config.Universal.Push), + Handler: opts.Guards.NoPopupPanel(self.HandlePush), + GetDisabledReason: self.getDisabledReasonForPushOrPull, + Description: self.c.Tr.Push, }, { - Key: opts.GetKey(opts.Config.Universal.Pull), - Handler: opts.Guards.NoPopupPanel(self.HandlePull), - Description: self.c.Tr.Pull, + Key: opts.GetKey(opts.Config.Universal.Pull), + Handler: opts.Guards.NoPopupPanel(self.HandlePull), + GetDisabledReason: self.getDisabledReasonForPushOrPull, + Description: self.c.Tr.Pull, }, } @@ -56,6 +59,18 @@ func (self *SyncController) HandlePull() error { return self.branchCheckedOut(self.pull)() } +func (self *SyncController) getDisabledReasonForPushOrPull() string { + currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() + if currentBranch != nil { + op := self.c.State().GetItemOperation(currentBranch) + if op != types.ItemOperationNone { + return self.c.Tr.CantPullOrPushSameBranchTwice + } + } + + return "" +} + func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error { return func() error { currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() @@ -73,13 +88,13 @@ func (self *SyncController) push(currentBranch *models.Branch) error { if currentBranch.IsTrackingRemote() { opts := pushOpts{} if currentBranch.HasCommitsToPull() { - return self.requestToForcePush(opts) + return self.requestToForcePush(currentBranch, opts) } else { - return self.pushAux(opts) + return self.pushAux(currentBranch, opts) } } else { if self.c.Git().Config.GetPushToCurrent() { - return self.pushAux(pushOpts{setUpstream: true}) + return self.pushAux(currentBranch, pushOpts{setUpstream: true}) } else { return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error { upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) @@ -87,7 +102,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error { return self.c.Error(err) } - return self.pushAux(pushOpts{ + return self.pushAux(currentBranch, pushOpts{ setUpstream: true, upstreamRemote: upstreamRemote, upstreamBranch: upstreamBranch, @@ -107,11 +122,11 @@ func (self *SyncController) pull(currentBranch *models.Branch) error { return self.c.Error(err) } - return self.PullAux(PullFilesOptions{Action: action}) + return self.PullAux(currentBranch, PullFilesOptions{Action: action}) }) } - return self.PullAux(PullFilesOptions{Action: action}) + return self.PullAux(currentBranch, PullFilesOptions{Action: action}) } func (self *SyncController) setCur |