Escape the merge conflicts view before prompting to continue the rebase (#5822)

When the last conflict of a file is resolved, a files refresh both
offers to continue the rebase/merge (if we started it ourselves) and,
via its merge-conflicts scope, escapes from the merge conflicts view
back to the files context. The two race: the prompt is bounced onto the
UI thread by the files worker, while the escape's context push is queued
separately by EscapeMerge, and it deliberately refuses to push the files
context over a popup. So if the prompt opens first, the escape does
nothing, and closing the prompt lands the user in the stale merge
conflicts view — usually already emptied by the escape's state reset —
instead of the files panel. No later refresh rescues this.

Fix this by escaping from the merge conflicts view right before opening
the prompt. This runs on the UI thread and doesn't hold the merge
conflicts mutex, so it can reset the state and push the files context
synchronously; whichever side runs first, the prompt now always opens on
top of the files context, and EscapeMerge's guarded push still does
nothing only when that's the right thing to do.

This is a timing race with no deterministic regression test; it showed
up as a rare flake in tests that cancel the continue prompt (e.g.
commit/amend_when_there_are_conflicts_and_continue) when looping the
integration tests under the race detector.
This commit is contained in:
Stefan Haller 2026-07-16 14:44:20 +02:00 committed by GitHub
commit 07745afc57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1207,6 +1207,19 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re
// for operations we started ourselves; prompting for one that was
// started outside lazygit (e.g. by a coding agent) would be confusing.
self.onUIThreadUnlessRepoChanged(env, func() error {
// The merge-conflicts scope of this refresh also notices that
// the conflicts are gone and escapes from the merge conflicts
// view to the files context (see RefreshMergeState), but it
// runs concurrently with us, and its escape refuses to push
// the files context over a popup. So if our prompt opens
// first, the escape does nothing, and closing the prompt
// would land the user in the dead merge conflicts view.
// Escape it ourselves before opening the prompt, so that the
// prompt always opens on top of the files context.
if self.c.Context().IsCurrent(self.c.Contexts().MergeConflicts) {
self.mergeConflictsHelper.ResetMergeState()
self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{})
}
return self.mergeAndRebaseHelper.PromptToContinueRebase()
})
}