mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
A commits refresh does its git work on a worker and then reads the model, the contexts, and the modes for that work directly from there: LocalCommits.GetSelectionRangeAndMode/GetLimitCommits/GetShowWholeGitGraph, Model.Commits/MainBranches/HashPool, the filtering path/author. Those are owned by the UI thread, which is concurrently running the cursor and render code, so the reads race it — the dominant, confirmed source of the commits-scope flakes (the startup ClampSelection vs GetSelectionRangeAndMode race, for one). Gather them into an immutable capturedCommitState on the UI thread, before the git work is dispatched, and have refreshCommitsWithLimit compute from that snapshot. UI-thread callers capture inline; worker callers can't (a SYNC/BLOCK_UI refresh parks the UI thread at wg.Wait, so hopping from a scope sub-worker would deadlock), so the capture is lifted out of the scope worker into the refresh orchestration, and worker callers announce themselves with a new RefreshFromWorker entry point that hops the capture to the UI thread and blocks for it (OnUIThreadAndWait). BLOCK_UI runs the whole refresh on the UI thread regardless of the caller, so it captures inline too. Every refresh issued from a worker that reaches the commits (or branches, which pulls in commits) scope is converted: the fast-forward, branch/tag delete, worktree remove/detach, push, reword-via-rebase, author edits, custom-command, hard-reset-with-autostash, reset-to-ref, fetch-and-checkout, gpg-stream, post-fetch, and external-change-poller refreshes, plus the branch checkout and move-commits-to-new-branch refreshes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
package gui
|
|
|
|
import (
|
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/tasks"
|
|
)
|
|
|
|
// hacking this by including the gui struct for now until we split more things out
|
|
type guiCommon struct {
|
|
gui *Gui
|
|
types.IPopupHandler
|
|
}
|
|
|
|
var _ types.IGuiCommon = &guiCommon{}
|
|
|
|
func (self *guiCommon) LogAction(msg string) {
|
|
self.gui.LogAction(msg)
|
|
}
|
|
|
|
func (self *guiCommon) LogCommand(cmdStr string, isCommandLine bool) {
|
|
self.gui.LogCommand(cmdStr, isCommandLine)
|
|
}
|
|
|
|
func (self *guiCommon) Refresh(opts types.RefreshOptions) {
|
|
self.gui.helpers.Refresh.Refresh(opts)
|
|
}
|
|
|
|
func (self *guiCommon) RefreshFromWorker(opts types.RefreshOptions) {
|
|
self.gui.helpers.Refresh.RefreshFromWorker(opts)
|
|
}
|
|
|
|
func (self *guiCommon) PostRefreshUpdate(context types.Context) {
|
|
self.gui.postRefreshUpdate(context)
|
|
}
|
|
|
|
func (self *guiCommon) RunSubprocessAndRefresh(cmdObj *oscommands.CmdObj) error {
|
|
return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
|
|
}
|
|
|
|
func (self *guiCommon) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) {
|
|
return self.gui.runSubprocessWithSuspense(cmdObj)
|
|
}
|
|
|
|
func (self *guiCommon) Suspend() error {
|
|
return self.gui.suspend()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (self *guiCommon) ContextForKey(key types.ContextKey) types.Context {
|
|
return self.gui.State.ContextMgr.ContextForKey(key)
|
|
}
|
|
|
|
func (self *guiCommon) GetAppState() *config.AppState {
|
|
return self.gui.Config.GetAppState()
|
|
}
|
|
|
|
func (self *guiCommon) SaveAppState() error {
|
|
return self.gui.Config.SaveAppState()
|
|
}
|
|
|
|
func (self *guiCommon) SaveAppStateAndLogError() {
|
|
if err := self.gui.Config.SaveAppState(); err != nil {
|
|
self.gui.Log.Errorf("error when saving app state: %v", err)
|
|
}
|
|
}
|
|
|
|
func (self *guiCommon) GetConfig() config.AppConfigurer {
|
|
return self.gui.Config
|
|
}
|
|
|
|
func (self *guiCommon) ResetViewOrigin(view *gocui.View) {
|
|
self.gui.resetViewOrigin(view)
|
|
}
|
|
|
|
func (self *guiCommon) SetViewContent(view *gocui.View, content string) {
|
|
self.gui.setViewContent(view, content)
|
|
}
|
|
|
|
func (self *guiCommon) Render() {
|
|
self.gui.render()
|
|
}
|
|
|
|
func (self *guiCommon) Views() types.Views {
|
|
return self.gui.Views
|
|
}
|
|
|
|
func (self *guiCommon) Git() *commands.GitCommand {
|
|
return self.gui.git
|
|
}
|
|
|
|
func (self *guiCommon) OS() *oscommands.OSCommand {
|
|
return self.gui.os
|
|
}
|
|
|
|
func (self *guiCommon) Modes() *types.Modes {
|
|
return self.gui.State.Modes
|
|
}
|
|
|
|
func (self *guiCommon) Model() *types.Model {
|
|
return self.gui.State.Model
|
|
}
|
|
|
|
func (self *guiCommon) Mutexes() *types.Mutexes {
|
|
return &self.gui.Mutexes
|
|
}
|
|
|
|
func (self *guiCommon) GocuiGui() *gocui.Gui {
|
|
return self.gui.g
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThread(f func() error) {
|
|
self.gui.onUIThread(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadBackground(f func() error) {
|
|
self.gui.onUIThreadBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadContentOnly(f func() error) {
|
|
self.gui.onUIThreadContentOnly(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadContentOnlyBackground(f func() error) {
|
|
self.gui.onUIThreadContentOnlyBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnWorker(f func(gocui.Task) error) {
|
|
self.gui.onWorker(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnWorkerBackground(f func(gocui.Task) error) {
|
|
self.gui.onWorkerBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) {
|
|
self.gui.refreshMainViews(opts)
|
|
}
|
|
|
|
func (self *guiCommon) MainViewPairs() types.MainViewPairs {
|
|
return types.MainViewPairs{
|
|
Normal: self.gui.normalMainContextPair(),
|
|
Staging: self.gui.stagingMainContextPair(),
|
|
PatchBuilding: self.gui.patchBuildingMainContextPair(),
|
|
MergeConflicts: self.gui.mergingMainContextPair(),
|
|
}
|
|
}
|
|
|
|
func (self *guiCommon) GetViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferManager {
|
|
return self.gui.getViewBufferManagerForView(view)
|
|
}
|
|
|
|
func (self *guiCommon) State() types.IStateAccessor {
|
|
return self.gui.stateAccessor
|
|
}
|
|
|
|
func (self *guiCommon) KeybindingsOpts() types.KeybindingsOpts {
|
|
return self.gui.keybindingOpts()
|
|
}
|
|
|
|
func (self *guiCommon) CallKeybindingHandler(binding *types.Binding) error {
|
|
return self.gui.callKeybindingHandler(binding)
|
|
}
|
|
|
|
func (self *guiCommon) ResetKeybindings() error {
|
|
return self.gui.resetKeybindings()
|
|
}
|
|
|
|
func (self *guiCommon) IsAnyModeActive() bool {
|
|
return self.gui.helpers.Mode.IsAnyModeActive()
|
|
}
|
|
|
|
func (self *guiCommon) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) {
|
|
return self.gui.GetInitialKeybindingsWithCustomCommands()
|
|
}
|
|
|
|
func (self *guiCommon) AfterLayout(f func() error) {
|
|
self.gui.afterLayout(f)
|
|
}
|
|
|
|
func (self *guiCommon) RunningIntegrationTest() bool {
|
|
return self.gui.integrationTest != nil
|
|
}
|
|
|
|
func (self *guiCommon) InDemo() bool {
|
|
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
|
|
}
|
|
|
|
func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
|
|
self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
|
|
return nil
|
|
}
|