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.

Example of a revision graph representing multiple branches

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_parent field 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

Comments

plach created an issue. See original summary.

wim leers’s picture

WRT 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 :)

possibly deprecate the revision_translation_affected flag

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.

aaronmchale’s picture

StatusFileSize
new210.48 KB

Just reading through this and had a few thoughts I wanted to share which hopefully could influence the direction here in some way:

  • Essentially what we're talking about here is bringing some of the capabilities of Git to Drupal Revisioning. So looking at the problem in that context perhaps we can learn a lot about logic, workflows and UI. For example the way BitBucket displays commits along with which branches they are in and how those branches merge together is quite nice, perhpas we can take insperation there (notice the area I highlighted with a red border in the image below):
    BitBuket committ history with multiple Git bracnhes intersecting and merging together
  • As I was reading this I was thinking that this really has the potential to lay the groundwork for a contrib module (or even a core module) to come along which could allow for multiple people editing the same WYSIWYG field to see those updates in real time, essentially what Google Docs and Microsoft Word Online can do with realtime multi-user document editing.
  • Sometimes I think it's too easy to forget that any Content Entity Type can have revision support, nodes shouldn't be the centre of the "revbision universe". So I'm always mindful going forward that (as I've said in other similar issues) when we make changes to the revision system, espeically UI changes, we should try to bring as much of it into the core Entity Revision APIs and make it easilly resuible for any Content Entity Type. I work with Custom Content Entities in modules a lot and the amount of custom code that is needed to generate the revision UIs is a little scary, so where possible we should try to reduce that.

Hopefully these thoughts can help to feed into the discussion here.

Thanks
-Aaron

plach’s picture

@Wim Leers, #2:

[...] how do we provide a sensible view of each of the revision branches/parallel revision histories?

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.

[...] this way we can always have a straight sequence of revisions going from root to leaf. With this logic, if the default revision matches the current context, we would replicate core's behavior (without CM enabled). If we have a single main branch with pending revisions generated by CM, again we would replicate the current core behavior. So this approach seems to be "backwards-compatible" with respect to what we have now.

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.

How do we reliably determine what revision was the starting point ("parent") of a given revision?

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:

[...] So looking at the problem in that context perhaps we can learn a lot about logic, workflows and UI. [...]

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 :)

[...] allow for multiple people editing the same WYSIWYG field to see those updates in real time [...]

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.

Sometimes I think it's too easy to forget that any Content Entity Type can have revision support, nodes shouldn't be the centre of the "revbision universe". [...]

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.

pmelab’s picture

Created a child issue on a possible solution for revision negotiation:
https://www.drupal.org/project/drupal/issues/3024775


@plach:

In this case it would be great to be able to leverage the new collaboration capabilities available in CKEditor 5.

That's our plan too. I've developed an intimate relationship with CKEditor 5 during the last months.

wim leers’s picture

#4: 👍

#5:

I've developed an intimate relationship with CKEditor 5 during the last months.

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.

aaronmchale’s picture

@plach #4

In this case it would be great to be able to leverage the new collaboration capabilities available in CKEditor 5

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.

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.

In this case we might still be able to utilise the new tracked changes features in CKEditor.

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.

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).

matsbla’s picture

We 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?

amateescu’s picture

Issue summary: View changes

We decided to postpone the generic tree framework for now and start with a custom revision_parent field, which can be reviewed at #2725523: Add a revision_parent field to revisionable entities.

plach’s picture

Title: [PP-3] Add parallel revisioning support » [PP-2] Add parallel revisioning support

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

matsbla’s picture

In 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

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.