Pause background refreshes while driving a git operation

Several commands (rewording or amending an earlier commit, custom patch
operations, etc.) are implemented by starting an interactive rebase that stops
at a commit, amending it, and continuing. When no conflict occurs, the user
isn't meant to notice a rebase happened at all.

But a background file refresh can fire while the rebase is mid-flight and render
a dirty working copy of whatever the behind-the-scenes rebase is doing (e.g.
applying a custom patch).

To fix this, we pause the background routines for the duration of any
waiting-status operation — exactly the window in which lazygit is driving the
git operation itself and will refresh once at the end. The boundary is also
right for the conflict case: when a rebase stops on a conflict the operation
returns, the pause releases, and background refreshes resume for the interactive
resolution that follows.
This commit is contained in:
Stefan Haller 2026-05-29 18:08:07 +02:00
parent f9c81b655d
commit 3cf890b7d7
4 changed files with 26 additions and 0 deletions

View file

@ -66,12 +66,22 @@ func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task
}
func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task) error {
// A waiting status means lazygit is driving a git operation itself (often
// one that internally runs a rebase and continues it). Pause the background
// routines for its duration so they don't refresh from an intermediate
// state and reveal, say, the half-finished history of a reword.
self.c.PauseBackgroundRefreshes(true)
defer self.c.PauseBackgroundRefreshes(false)
return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
return f(appStatusHelperTask{task, waitingStatusHandle})
})
}
func (self *AppStatusHelper) WithWaitingStatusSync(message string, f func() error) error {
self.c.PauseBackgroundRefreshes(true)
defer self.c.PauseBackgroundRefreshes(false)
return self.statusMgr().WithWaitingStatus(message, func() {}, func(*status.WaitingStatusHandle) error {
stop := make(chan struct{})
defer func() { close(stop) }()

View file

@ -68,6 +68,14 @@ func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(g
visible := view.Visible && self.windowHelper.TopViewInWindow(context.GetWindowName(), false) == view
if visible && context.IsItemVisible(opts.Item) {
self.c.OnWorker(func(task gocui.Task) error {
// An inline status is just a waiting status rendered on the item
// rather than in the bottom line, so it gets the same treatment:
// pause the background routines while we drive the operation. (The
// off-screen branch below goes through WithWaitingStatus, which
// already does this.)
self.c.PauseBackgroundRefreshes(true)
defer self.c.PauseBackgroundRefreshes(false)
self.start(opts)
defer self.stop(opts)

View file

@ -50,6 +50,10 @@ func (self *guiCommon) Resume() error {
return self.gui.resume()
}
func (self *guiCommon) PauseBackgroundRefreshes(pause bool) {
self.gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(pause)
}
func (self *guiCommon) Context() types.IContextMgr {
return self.gui.State.ContextMgr
}

View file

@ -59,6 +59,10 @@ type IGuiCommon interface {
Suspend() error
Resume() error
// Pause or resume the background routines. Calls nest, so every pause must be balanced
// by a resume.
PauseBackgroundRefreshes(pause bool)
Context() IContextMgr
ContextForKey(key ContextKey) Context