fix: make the non-merge path in Execute() exception-safe [patch] - #72
Merged
matt-edmondson merged 2 commits intoSep 23, 2026
Merged
Conversation
Execute() cleared forward history and cleaned up save boundaries before calling command.Execute(). When Execute() threw, the exception propagated but the redo history and any save boundaries inside it had already been discarded, even though the new command was never applied. Move command.Execute() ahead of ClearForward() and CleanupInvalidBoundaries(), mirroring the auto-merge path above and CompositeCommand.Execute(). Behaviour on success is unchanged: CurrentPosition is untouched by Execute(), so CleanupInvalidBoundaries() still receives the same position, and AddCommand() already clears forward history itself when branching. Adds two tests, each failing before the change: - Execute_CommandThrowsException_PreservesRedoHistory - Execute_CommandThrowsException_PreservesSaveBoundariesInForwardHistory Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1ntzPwg732TrH7D5vWpSV
MSTEST0037, reported by SonarQube Cloud on this PR: use Assert.HasCount rather than Assert.AreEqual on a collection's Count. The rest of the suite already asserts save boundaries this way. Assertion strength is unchanged: both new tests still fail against the pre-fix UndoRedoService with the same messages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1ntzPwg732TrH7D5vWpSV
|
matt-edmondson
deleted the
claude/undoredo-70-execute-preserves-redo-on-throw
branch
September 23, 2026 23:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #70.
UndoRedoService.Execute(ICommand)cleared forward history and cleaned up save boundaries before callingcommand.Execute():When
command.Execute()threw, the exception propagated correctly — but the pre-existing redo history, and any save boundaries inside it, had already been permanently discarded for a command that was never applied.The comment on the auto-merge branch just above claimed this path already mirrored its exception safety ("This mirrors the non-merge path below"). It didn't; the non-merge path was evidently missed when the merge path and
CompositeCommandwere hardened.The change
command.Execute()moves ahead of the two destructive calls. Nothing else moves.Behaviour on success is identical, for two reasons worth stating because they are what makes the reorder safe rather than merely later:
CurrentPositionis not touched bycommand.Execute(), soCleanupInvalidBoundaries()still receives exactly the position it received before.StackManager.AddCommand()already removes anything afterCurrentPositionitself when branching, so the explicitClearForward()was never what made the stack correct — it only mattered for the boundary cleanup's position argument, which is unchanged.Tests
Two tests added next to the existing exception-safety tests, both verified to fail before the change and pass after it (by stashing the
UndoRedoService.cschange and re-running):Execute_CommandThrowsException_PreservesRedoHistoryCommandCountis 1, not 3 — B and C silently lostExecute_CommandThrowsException_PreservesSaveBoundariesInForwardHistorySaveBoundaries.Countis 0, not 1The first also asserts the preserved history is still replayable — both
Redo()calls reapply B and C — rather than only that the count survived. The existingExecute_CommandThrowsException_DoesNotCorruptStackmissed this bug because it exercises a stack with no redo history to lose.Full suite: 48 passed, 0 failed, 0 skipped.
🤖 Generated with Claude Code
https://claude.ai/code/session_01T1ntzPwg732TrH7D5vWpSV
Generated by Claude Code