Problem/Motivation
At the moment core only supports sequential revisioning, however there are any many use cases that would be far better addressed if we had parallel revisioning support in core. The typical example is an entity going through a set of significant changes (either via Workspaces or via an editorial workflow implemented via Content Moderation) and at the same time needing a small typo fix in its default revision.
On top of that, we have no way to make it easy for multiple modules exploiting parallel revisioning to play well together, which means incompatibilities may arise between core and contrib or even in core itself, given that at the moment Workspaces and Content Moderation would not be able to interact with each other.
Supporting parallel revisioning would require us to implement some kind of conflict detection/resolution to be able to merge changes from different revision branches into the default revision.
Proposed resolution
The following proposed solution was originally discussed by @amateescu and @plach and posted at #2942907-24: Entity system does not provide an API for retrieving an entity variant that is safe for editing. Additional information about the thought process behind the proposed solution is available at #2942907-26: Entity system does not provide an API for retrieving an entity variant that is safe for editing. In general all comments between #27 and #34 are somehow relevant to this issue and should be taken into account while working to the final solution.
Use cases
To validate the design it was chosen to use primarily a fairly advanced, although strictly-real life, use case: editing, in the context of a "stage" workspace, a translated and moderated entity, whose edit form has an hypothetical autosave functionality relying on revisions enabled.
Each entity translation goes through an independent editorial workflow. Once all translations reach an approved state, the workspace is ready for deployment to live. While editing the entity, autosave would trigger the creation of revisions that are only visible to their owner, the active user.
Other use cases we took into consideration, assuming the scenario described above, were:
- the display of a multiple inline entity form widget on the entity form;
- the display of the core Revision UI.
Implementation plan
Exploit the API introduced in #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing to provide a way to retrieve the most suitable entity object to populate an entity form and to be displayed with respect to the current context. The specified contexts provide all the required information to perform the revision negotiation.
In the reference scenario above they would be:
- current workspace
- active language
- current user
As mentioned in #5, multiple implementers need to collectively participate in the revision selection process without having to hardcode assumptions about each other. To achieve that, revisions are organized in a hierarchy representing how the implementers handle individual revisions. Each revision has a reference to its parent along with additional metadata about the context the revision was created in.
Each implementer is defined via a "revision type" plugin, which may be tied to one (or more?) context. The definition includes a weight that is used to build a hierarchy between context items, for instance: workspace → active language → user.
The revision graph (a tree allowing two parents for each node, technically a directed acyclic graph) represents the revision branches that may be generated when editing an entity in multiple contexts. In the reference scenario above, an entity might be edited in multiple workspaces, in multiple languages, by multiple users each one triggering their own autosave revisions.
This graph structure is also used for conflict resolution: when needing to merge revision branches a new merged revision having two parents will be created.

As mentioned above, each revision has contextual metadata attached, allowing to look up the fittest revision for the specified context. In fact a revision matching exactly the specified context might not exist, so a fallback logic needs to be available. The context hierarchy defined through weights is used to support that: more and more generic fallback contexts are built until a suitable revision is found. For instance the following situation may happen:
Current context: [ws: stage, lang: it, user: 4]
Fittest revision: [ws: stage, lang: it]
Assigning a sensible weight to each definition is likely to imply some degree of mutual knowledge among revision types, however theoretically this should not require explicitly coding against each other. It should even be possible to alter definitions to change weights and thus the way revision types play with each other.
OTOH altering revision type definitions and changing context info might make some revision contexts invalid, in which case the fallback logic would have to kick in again with possibly unexpected results.
In the reference scenario above, we have three context-defining revision types: Workspace, Content Translation, Autosave. Content Moderation does not require any special kind of context, so it does not need to affect the revision lookup logic. On the contrary, adopting this new API should allow us to simplify/clean-up the current limitations around revision translation and possibly deprecate the revision_translation_affected flag and the logic around it, as mentioned in #2940575-24: Document the scope and purpose of pending revisions.
While the public API mentions variants, since it's dealing with both revisions and translations, under the hood the revision graph will be used to identify the revision most suitable for the specified context and then the entity translation matching it will be instantiated and returned.
Remaining tasks
- Introduce a custom implementation of the tree field to implement the
revision_parentfield with the parent revision reference(s) and the context metadata (parent issue). - Basic implementation of the public API.
- Implementation the solution based on the revision graph (this issue).
- Introduce multiple support for editable revisions (follow-up issue).
- Add displayable support, both individual and multiple (follow-up issue).
User interface changes
None
API changes
None expected
Data model changes
None expected
Release notes snippet
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | bitbucket-committ-branching-example.JPG | 210.48 KB | aaronmchale |
Comments
Comment #2
wim leersWRT parallel revisioning: we discussed this a lot in #2992833, specifically in #2992833-48: Add a version negotiation to revisionable resource types, #2992833-55: Add a version negotiation to revisionable resource types and surrounding comments.
It boils down to this: how do we provide a sensible view of each of the revision branches/parallel revision histories? How do we reliably determine what revision was the starting point ("parent") of a given revision?
I think this issue is closely related to both #2727511: WI: Add revision hash base field to all revisionable entities and #1776796: Provide a better UX for creating, editing & managing draft revisions.. EDIT: saw your "remaining tasks" section explicitly state "tree field" and
revision_parent— so definitely related then :)Music to my ears! See #2933518: The semantics of the "revision_translation_affected" field are unclear to Decoupled Drupal developers (REST/JSON API/GraphQL) users, improve this.
Comment #3
aaronmchaleJust reading through this and had a few thoughts I wanted to share which hopefully could influence the direction here in some way:
Hopefully these thoughts can help to feed into the discussion here.
Thanks
-Aaron
Comment #4
plach@Wim Leers, #2:
I guess it really depends on the use case we are trying to address: as mentioned in the parent issue, @amateescu and I discussed the possibility of returning/displaying all the ancestors of the revision matching the current context.
There could also be cases where the whole picture is needed, that is all available revisions regardless of context, but I think it would be great to start from use cases to validate these approaches.
Well, in our proposal each revision has a parent revision reference. To identify the branch starting point I guess we would traverse parent references until we find one that has multiple children (or we reach the root). This is the key idea, the actual implementation might be smarter/more performant :)
@AaronMcHale, #3:
Yep, I think that could be a good source of inspiration once we are clear on what use cases we are trying to address. We should be careful not to let our developer mindset bias our choices, because a content editor's workflow might be slightly different from the one of a developer reviewing code. This is why I keep mentioning use cases :)
I was thinking about that as well. My initial thoughts around this topic is that concurrent real time editing makes sense when all users share the same (active) context, i.e. they would all see the same revision initially loaded in the entity form. In this case it would be great to be able to leverage the new collaboration capabilities available in CKEditor 5. OTOH if the context is different, e.g. if users are editing different translations or have different active workspaces, concurrent editing would not make sense, so we would still need to deal with merges and conflict resolution.
I agree. Personally I try to always think "entities" rather than "nodes", but as long as we don't have a UI in core supporting all entity types, it is hard to test/validate our efforts from that perspective.
Comment #5
pmelab commentedCreated a child issue on a possible solution for revision negotiation:
https://www.drupal.org/project/drupal/issues/3024775
@plach:
That's our plan too. I've developed an intimate relationship with CKEditor 5 during the last months.
Comment #6
wim leers#4: 👍
#5:
GREAT! I've got a direct connection with them as the maintainer of core's
ckeditor.module. If you're gonna be working on Drupal + CKEditor 5, I could add you to it. We've only used it for coordinating security releases lately, before that it was mostly used for coordinating work to get CKEditor into Drupal core.Comment #7
aaronmchale@plach #4
I haven't seen the new features in CKEditor 5 before, really quite interesting, it'll also be wort looking at how we can potentially integrate revisions with the tracked changes, view/comment modes, and comments features it has.
In this case we might still be able to utilise the new tracked changes features in CKEditor.
There is this issue #2350939: Implement a generic revision UI, currently it's node specific but in #60 I proosed making the issue more generic, and proposed postponing it until #1863906: [PP-1] Replace content revision table with a view is implemented (which is currently node specific but should also be made generic).
Comment #8
matsbla commentedWe do have some challenges when it comes to concurrent editing:
#2465909: Implement per-language locking at entity form level
#2920889: EntityChangedConstraintValidator doesn't take adding, removing and reverting translations into account
#2892132: Entity validation does not always prevent concurrent editing of pending revisions
#2912318: Changing field value in an entity presave hook will not update the entity changed timestamp (About concurrent editing translatable image fields with non-translatable image files)
What implications will this solution have for concurrent editing locks and preventing unintended overriding changes made by others?
Comment #9
amateescu commentedWe decided to postpone the generic tree framework for now and start with a custom
revision_parentfield, which can be reviewed at #2725523: Add a revision_parent field to revisionable entities.Comment #10
plach#2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing was committed.
Comment #12
matsbla commentedIn D8 when you save a translation every translation of the node is re-saved even though it didn't change. This exponentially increase the DB size and can also decrease the performance. I wonder if parallel revisioning support potentially could make us get rid of (or help work around) this behavior so only field data / translations that actually change is update and get a new revision?
Similar to what is suggested in
#2960887: Do not create field revisions when field data hasn't changed
#2875861: Optimize updating data and revision data tables
#1800286: Update, rather than Delete and insert - for significant database performance improvement