Motivation
TopLogic serializes all changes in a linear revision sequence. Changes made by different users and different objects are mixed in the global timeline. For a view of a complex object tree (root object R plus all objects of its composition tree), the user is not interested in the changes to individual objects in the global timeline, but only in those changes that affect the displayed subtree. The same focus results in follow-up requirements: specifically undoing a change or a chain of changes, resetting the active status of a subtree to a previous state, and providing suitable undo/redo handlers for the UI.
Scope
ChangeLog filter
- ChangeFilter as interface for selective ChangeLogBuilder results; ChangeLogBuilder.setFilter(...) as minimal connection.
- SubtreeFilter(root, includeSubtree) only accepts changes whose affected object was in the composition subtree of root at the time of the revision (or only the root itself if includeSubtree=false). adjustStartRev clamps the search to the creation revision of root. Results are memoized per (revision, unversioned identity); path prefixes are shared between siblings.
- ChangeSetAnalyzer is extracted from ChangeLogBuilder as a public class so that non-UI code can also use the conversion KB-Event → Model-ChangeSet.
Rollback and undo/redo utility
com.top_logic.element.changelog.ChangeSetReverter provides:
- revertAll(Collection) - Inverse in descending revision order within the caller transaction, without own commit.
- revertAll(kb, Collection, message) - transactional wrapper.
- revertSubtreeTo(root, targetRevision, message) - rolls back the subtree from root to the status at targetRevision. Reads the event stream directly via kb.getDiffReader, without the UI pair dedup; all subtree-relevant changes are undone, including reverts in the time window.
- findUndoCandidate(root, windowSize, includeSubtree) / findRedoCandidate(...) - locate the most recent real change or the most recent revert that has not yet been redone. The redo candidate is only valid if there is no regular forward change between the candidate and HEAD; otherwise TopLogicException(ERROR_CANNOT_REDO_CHAIN_BROKEN).
- undoLast / redoLast - convenient wrappers that search for the candidate, open a transaction with the correct revert message and commit.
CommandHandler
- UndoCommandHandler and RedoCommandHandler(@InApp) analogous to RevertCommandHandler. Target model is the subtree root(null = global). Configuration options: ** check-conflicts (Bool, Default true) - Conflict confirmation dialog. ** window-size (Int, default 50) - upper limit for the inspected window of the change history (bounds search, not result). ** include-subtree (Bool, Default true) - subtree or only object itself.
TL script integration
In the tl-model-search module (package com.top_logic.model.search.expr.config.operations.changelog):
- changeLog([obj [, maxEntries [, includeSubtree]]]) - returns the changes as a list of tl.changelog:ChangeSet. Without object or null: global change history.
- revertChanges(changes) - reverts the transferred ChangeSets within the active transaction (no own commit).
Explicitly not mapped as a TL script function: undo / redo. Reason: A script runs in the surrounding operations transaction and cannot overwrite its commit message, as a result of which the resulting commit would not be recognizable as a revert. These operations are therefore only available as CommandHandlers.
Model extension
On the tl.changelog:ChangeSet type two derived Boolean properties (Migration Ticket_29236_Added_isRevert_isRedo):
- isRevert - true, if the ChangeSet itself reverts another change.
- isRedo - true if the ChangeSet restores a reverted change.
Both are calculated from the existing default methods ChangeSet.isRevert() / isRedo() and can therefore be accessed directly from TL-Script on the attribute.
Dialog connection
ChangeLogListModelBuilder.supportsModel also accepts TLObject; a SubtreeFilter is appended for non-null models. The global opener of the My last changes dialog in the user menu has been configured so that the model of the contained table is explicitly set to null(<target><null/></target> + targetComponent), so that the dialog shows the unfiltered history when called from the user menu regardless of the call context. A variant with a model channel for subtree-specific views is thus possible without further core changes.
Not in this ticket
- Integration of specific undo/redo buttons in existing layouts.
- Configurable impact via individual composition references (opt-in/opt-out of non-composition references).
- Persistent index (revision, ancestorId) for faster subtree queries. Memoization in the filter is sufficient for typical windows.
Tests
- test.com.top_logic.element.changelog.TestSubtreeChangeLog - 5 test cases for subtree filters.
- test.com.top_logic.element.changelog.TestChangeSetReverter - 10 test cases for revertSubtreeTo, undoLast, redoLast including two consecutive undos, redo recovery, redo no-op, subtree isolation and the chain ch1, ch2, undo, ch3, ch4, undo, redo, redo (which detects the broken redo stack via TopLogicException(ERROR_CANNOT_REDO_CHAIN_BROKEN) ).