mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Show sync status in branches list
When pulling/pushing/fast-forwarding a branch, show this state in the branches list for that branch for as long as the operation takes, to make it easier to see when it's done (without having to stare at the status bar in the lower left). This will hopefully help with making these operations feel more predictable, now that we no longer show a loader panel for them.
This commit is contained in:
parent
6da1cf87b1
commit
3ac5ef2d41
|
|
@ -27,6 +27,9 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
|||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
func(branch *models.Branch) types.RefOperation {
|
||||
return c.State().GetRefOperation(branch.FullRefName())
|
||||
},
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Tr,
|
||||
|
|
|
|||
|
|
@ -571,6 +571,9 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
|||
)
|
||||
|
||||
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
|
||||
self.c.State().SetRefOperation(branch.FullRefName(), types.RefOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
defer func() { self.c.State().ClearRefOperation(branch.FullRefName(), context.LOCAL_BRANCHES_CONTEXT_KEY) }()
|
||||
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
if ok {
|
||||
self.c.LogAction(action)
|
||||
|
|
|
|||
|
|
@ -676,7 +676,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.RefOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr)
|
||||
|
||||
self.c.SetViewContent(self.c.Views().Status, status)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.RefOperationNone, self.c.Tr)
|
||||
repoName := self.c.Git().RepoPaths.RepoName()
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -73,13 +74,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 +88,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 +108,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) setCurrentBranchUpstream(upstream string) error {
|
||||
|
|
@ -139,8 +140,13 @@ type PullFilesOptions struct {
|
|||
Action string
|
||||
}
|
||||
|
||||
func (self *SyncController) PullAux(opts PullFilesOptions) error {
|
||||
func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
|
||||
self.c.State().SetRefOperation(currentBranch.FullRefName(), types.RefOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
defer func() {
|
||||
self.c.State().ClearRefOperation(currentBranch.FullRefName(), context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
}()
|
||||
|
||||
return self.pullWithLock(task, opts)
|
||||
})
|
||||
}
|
||||
|
|
@ -167,8 +173,13 @@ type pushOpts struct {
|
|||
setUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
|
||||
self.c.State().SetRefOperation(currentBranch.FullRefName(), types.RefOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
defer func() {
|
||||
self.c.State().ClearRefOperation(currentBranch.FullRefName(), context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
}()
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.Push)
|
||||
err := self.c.Git().Sync.Push(
|
||||
task,
|
||||
|
|
@ -192,7 +203,7 @@ func (self *SyncController) pushAux(opts pushOpts) error {
|
|||
newOpts := opts
|
||||
newOpts.force = true
|
||||
|
||||
return self.pushAux(newOpts)
|
||||
return self.pushAux(currentBranch, newOpts)
|
||||
},
|
||||
})
|
||||
return nil
|
||||
|
|
@ -203,7 +214,7 @@ func (self *SyncController) pushAux(opts pushOpts) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (self *SyncController) requestToForcePush(opts pushOpts) error {
|
||||
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
|
||||
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
|
||||
if forcePushDisabled {
|
||||
return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
|
||||
|
|
@ -214,7 +225,7 @@ func (self *SyncController) requestToForcePush(opts pushOpts) error {
|
|||
Prompt: self.forcePushPrompt(),
|
||||
HandleConfirm: func() error {
|
||||
opts.force = true
|
||||
return self.pushAux(opts)
|
||||
return self.pushAux(currentBranch, opts)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ type Gui struct {
|
|||
// lazygit was opened in, or if we'll retain the one we're currently in.
|
||||
RetainOriginalDir bool
|
||||
|
||||
refOperations map[string]types.RefOperation
|
||||
refOperationsMutex *deadlock.Mutex
|
||||
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
|
||||
|
||||
PrevLayout PrevLayout
|
||||
|
||||
// this is the initial dir we are in upon opening lazygit. We hold onto this
|
||||
|
|
@ -180,6 +184,29 @@ func (self *StateAccessor) SetRetainOriginalDir(value bool) {
|
|||
self.gui.RetainOriginalDir = value
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetRefOperation(ref string) types.RefOperation {
|
||||
self.gui.refOperationsMutex.Lock()
|
||||
defer self.gui.refOperationsMutex.Unlock()
|
||||
|
||||
return self.gui.refOperations[ref]
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetRefOperation(ref string, operation types.RefOperation, contextKey types.ContextKey) {
|
||||
self.gui.refOperationsMutex.Lock()
|
||||
defer self.gui.refOperationsMutex.Unlock()
|
||||
|
||||
self.gui.refOperations[ref] = operation
|
||||
self.gui.startRenderingInlineStatus(contextKey)
|
||||
}
|
||||
|
||||
func (self *StateAccessor) ClearRefOperation(ref string, contextKey types.ContextKey) {
|
||||
self.gui.refOperationsMutex.Lock()
|
||||
defer self.gui.refOperationsMutex.Unlock()
|
||||
|
||||
self.gui.stopRenderingInlineStatus(contextKey)
|
||||
delete(self.gui.refOperations, ref)
|
||||
}
|
||||
|
||||
// we keep track of some stuff from one render to the next to see if certain
|
||||
// things have changed
|
||||
type PrevLayout struct {
|
||||
|
|
@ -473,6 +500,10 @@ func NewGui(
|
|||
},
|
||||
InitialDir: initialDir,
|
||||
afterLayoutFuncs: make(chan func() error, 1000),
|
||||
|
||||
refOperations: make(map[string]types.RefOperation),
|
||||
refOperationsMutex: &deadlock.Mutex{},
|
||||
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
|
||||
}
|
||||
|
||||
gui.PopupHandler = popup.NewPopupHandler(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
|
|
@ -19,6 +20,7 @@ var branchPrefixColorCache = make(map[string]style.TextStyle)
|
|||
|
||||
func GetBranchListDisplayStrings(
|
||||
branches []*models.Branch,
|
||||
getRefOperation func(branch *models.Branch) types.RefOperation,
|
||||
fullDescription bool,
|
||||
diffName string,
|
||||
tr *i18n.TranslationSet,
|
||||
|
|
@ -27,13 +29,14 @@ func GetBranchListDisplayStrings(
|
|||
) [][]string {
|
||||
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
|
||||
diffed := branch.Name == diffName
|
||||
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
|
||||
return getBranchDisplayStrings(branch, getRefOperation(branch), fullDescription, diffed, tr, userConfig, worktrees)
|
||||
})
|
||||
}
|
||||
|
||||
// getBranchDisplayStrings returns the display string of branch
|
||||
func getBranchDisplayStrings(
|
||||
b *models.Branch,
|
||||
refOperation types.RefOperation,
|
||||
fullDescription bool,
|
||||
diffed bool,
|
||||
tr *i18n.TranslationSet,
|
||||
|
|
@ -51,7 +54,7 @@ func getBranchDisplayStrings(
|
|||
}
|
||||
|
||||
coloredName := nameTextStyle.Sprint(displayName)
|
||||
branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft)
|
||||
branchStatus := utils.WithPadding(ColoredBranchStatus(b, refOperation, tr), 2, utils.AlignLeft)
|
||||
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
|
||||
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
|
||||
|
|
@ -109,9 +112,11 @@ func GetBranchTextStyle(name string) style.TextStyle {
|
|||
}
|
||||
}
|
||||
|
||||
func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
|
||||
func ColoredBranchStatus(branch *models.Branch, refOperation types.RefOperation, tr *i18n.TranslationSet) string {
|
||||
colour := style.FgYellow
|
||||
if branch.UpstreamGone {
|
||||
if refOperation != types.RefOperationNone {
|
||||
colour = style.FgCyan
|
||||
} else if branch.UpstreamGone {
|
||||
colour = style.FgRed
|
||||
} else if branch.MatchesUpstream() {
|
||||
colour = style.FgGreen
|
||||
|
|
@ -119,10 +124,15 @@ func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string
|
|||
colour = style.FgMagenta
|
||||
}
|
||||
|
||||
return colour.Sprint(BranchStatus(branch, tr))
|
||||
return colour.Sprint(BranchStatus(branch, refOperation, tr))
|
||||
}
|
||||
|
||||
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
|
||||
func BranchStatus(branch *models.Branch, refOperation types.RefOperation, tr *i18n.TranslationSet) string {
|
||||
refOperationStr := refOperationToString(refOperation, tr)
|
||||
if refOperationStr != "" {
|
||||
return refOperationStr + " " + utils.Loader()
|
||||
}
|
||||
|
||||
if !branch.IsTrackingRemote() {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
21
pkg/gui/presentation/ref_operations.go
Normal file
21
pkg/gui/presentation/ref_operations.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
)
|
||||
|
||||
func refOperationToString(refOperation types.RefOperation, tr *i18n.TranslationSet) string {
|
||||
switch refOperation {
|
||||
case types.RefOperationNone:
|
||||
return ""
|
||||
case types.RefOperationPushing:
|
||||
return tr.PushingStatus
|
||||
case types.RefOperationPulling:
|
||||
return tr.PullingStatus
|
||||
case types.RefOperationFastForwarding:
|
||||
return tr.FastForwardingOperation
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
@ -7,14 +7,15 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
)
|
||||
|
||||
func FormatStatus(repoName string, currentBranch *models.Branch, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
|
||||
func FormatStatus(repoName string, currentBranch *models.Branch, refOperation types.RefOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += ColoredBranchStatus(currentBranch, tr) + " "
|
||||
status += ColoredBranchStatus(currentBranch, refOperation, tr) + " "
|
||||
}
|
||||
|
||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||
|
|
|
|||
|
|
@ -265,6 +265,17 @@ type Mutexes struct {
|
|||
PtyMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type RefOperation int
|
||||
|
||||
// An long-running operation on a ref (branch or tag). We display these in the
|
||||
// list of branches or tags so that there's better feedback of what's happening.
|
||||
const (
|
||||
RefOperationNone RefOperation = iota
|
||||
RefOperationPushing
|
||||
RefOperationPulling
|
||||
RefOperationFastForwarding
|
||||
)
|
||||
|
||||
type IStateAccessor interface {
|
||||
GetRepoPathStack() *utils.StringStack
|
||||
GetRepoState() IRepoStateAccessor
|
||||
|
|
@ -277,6 +288,9 @@ type IStateAccessor interface {
|
|||
SetShowExtrasWindow(bool)
|
||||
GetRetainOriginalDir() bool
|
||||
SetRetainOriginalDir(bool)
|
||||
GetRefOperation(ref string) RefOperation
|
||||
SetRefOperation(ref string, operation RefOperation, contextKey ContextKey)
|
||||
ClearRefOperation(ref string, contextKey ContextKey)
|
||||
}
|
||||
|
||||
type IRepoStateAccessor interface {
|
||||
|
|
|
|||
|
|
@ -148,3 +148,45 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type inlineStatusInfo struct {
|
||||
refCount int
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (gui *Gui) startRenderingInlineStatus(contextKey types.ContextKey) {
|
||||
info := gui.contextsWithInlineStatus[contextKey]
|
||||
if info == nil {
|
||||
info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
|
||||
gui.contextsWithInlineStatus[contextKey] = info
|
||||
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
|
||||
defer ticker.Stop()
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
gui.c.OnUIThread(func() error {
|
||||
_ = gui.c.ContextForKey(contextKey).HandleRender()
|
||||
return nil
|
||||
})
|
||||
case <-info.stop:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
info.refCount++
|
||||
}
|
||||
|
||||
func (gui *Gui) stopRenderingInlineStatus(contextKey types.ContextKey) {
|
||||
if info := gui.contextsWithInlineStatus[contextKey]; info != nil {
|
||||
info.refCount--
|
||||
if info.refCount <= 0 {
|
||||
info.stop <- struct{}{}
|
||||
delete(gui.contextsWithInlineStatus, contextKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ type TranslationSet struct {
|
|||
ReturnToFilesPanel string
|
||||
FastForward string
|
||||
FastForwarding string
|
||||
FastForwardingOperation string
|
||||
FoundConflictsTitle string
|
||||
ViewConflictsMenuItem string
|
||||
AbortMenuItem string
|
||||
|
|
@ -981,6 +982,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||
ReturnToFilesPanel: `Return to files panel`,
|
||||
FastForward: `Fast-forward this branch from its upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}}",
|
||||
FastForwardingOperation: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Conflicts!",
|
||||
ViewConflictsMenuItem: "View conflicts",
|
||||
AbortMenuItem: "Abort the %s",
|
||||
|
|
|
|||
Loading…
Reference in a new issue