The initial stab at Conflict module for Drupal 8 will only do a few simple things.

The goal is to keep the module pluggable and to not to depend on Multiversion or Replication module.

Here are the main things we should start with:

Step 1: Implement the ConflictManager service

  • Provide a basic manager called ConflictManager that can do two main things; resolve lowest common ancestor (LCA) for two revision (in a pluggable way), and do a merge between three revisions of an entity (in a pluggable way).
  • The way we should make these things pluggable is by using tagged services, similar to how ReplicatorManager works in the Workspace module
  • The ConflictManagerInterface should therefor have four public methods:
    1. ::addAncestorResolver(ConflictAncestorResolverInterface $resolver) - Used by the container system
    2. ::addConflictResolver(ConflictResolverInterface $resolver) - Used by the container system
    3. ::resolveLowestCommonAncestor($revision1, $revision2) - This method will be responsible for finding the lowest common ancestor for two given revisions objects of an entity. All this method should do is to pass off the responsibility to the first tagged ancestor resolver service, as per above.
    4. ::resolveConflict($revision1, $revision2, $revision3) - This method will be responsible for fixing the conflict between three revisions. All this method should do is to pass off the responsibility to the first tagged conflict resolver service, as per above.

Step 2: Implement two different ancestor resolvers

Since the LCA resolution is pluggable inside ConflictManager::resolveLowestCommonAncestor() we should provide two different implementations for this:

  1. A simple resolver which find the LCA for revisions that use the default linear revision system provided by core. This resolver will work with any Drupal 8 site.
  2. A more complicated resolver that uses the relaxedws/lca library and RevisionTreeIndex from Multiversion module. This step depends on #2748403: Add getGraph() to RevisionTreeIndex . This resolver will only work with sites that uses Multiversion module.

Step 3: Implement two conflict resolvers

  1. A simple resolver that just returns the revision with the largest revision ID. This resolver will work with any Drupal 8 site.
  2. A more complex resolver that uses the Serialization module to normalize the given revisions into arrays and then use the relaxedws/merge library to perform the merge. This resolver will only work with sites that uses Multiversion module.

The merge process would roughly work like the below (please note this is only example code and might not be fully accurate).

Note that this is a test case inside Conflict module without Multiversion module, so revisions are not stored as trees. We should also add tests in Multiversion module with full revision trees.

$storage = $this->entityManager->getStorage('entity_test');
$serializer = \Drupal::service('serializer');
$conflict = \Drupal::service('conflict.manager');

// Create a few revisions.
$entity = $storage->create(['name' => 'rev 1']);
$entity->save();
$entity->name = 'rev 2';
$entity->save();
$entity->name = 'rev 3';
$entity->save();

$rev1 = $storage->loadRevision(1);
$rev2 = $storage->loadRevision(2);
$rev3 = $storage->loadRevision(3);

// Resolve the conflict and return a merged version of the revision object.
$merged = $conflict->resolveConflict($rev1, $rev2, $rev3);
$this->assertEqual($merged->name, 'rev 3');

Step 4: Tests, tests, tests, and more tests!

The heading says it all :)

Comments

dixon_ created an issue. See original summary.

dixon_’s picture

Title: Create basic tests for the initial D8 version » Create a basic and pluggable ConflictManager
dixon_’s picture

Issue summary: View changes
dixon_’s picture

Issue summary: View changes
dixon_’s picture

Issue summary: View changes
rakesh_verma’s picture

@dixon_ , I get it what we are trying to do. I would start with the implementation after our call. Meanwhile, I would be studying about dependency Injection and other d8 module development stuff. would that be fine?

dixon_’s picture

@rakesh_verma Feel free to start messing around with the implementation already. At least you can create a new branch in out Github repo and prepare the basic module files so that there's an empty module you can install on a Drupal 8 site :) https://github.com/drupaldeploy/drupal-conflict

dixon_’s picture

Issue summary: View changes
AjitS’s picture

Issue summary: View changes

Corrected function names of a couple of functions from ConflictManagerInterface.
Let me know if this was not supposed to be done :)

rakesh_verma’s picture

@AjitS Thanks for fixing them :D

dixon_’s picture

Thanks @AjitS :)

dixon_’s picture

Issue summary: View changes
dixon_’s picture

A test case can look like this:

  class MyTest {

    public function basicTest() {
      $entity = EntityTest::create(['label' => 'revision 1']);
      $entity->save();
      $entity->label = 'revision 2';
      $entity->save();
      $entity->label = 'revision 3';
      $entity->save();

      $revision2 = EntityTest::loadRevision(2);
      $revision3 = EntityTest::loadRevision(3);

      $manager = Drupal::service('conflict.manager');
      $revisionLca = $manager->resolveLowestCommonAncestor($revision1, $revision2);
      $this->assertTrue($revisionLca->label() == 'revision 1');
    }
  }

jeqq’s picture

Instead of:

$revision2 = EntityTest::loadRevision(2);
$revision3 = EntityTest::loadRevision(3);

use:

$revision2 = entity_revision_load('entity_test', 2);
$revision3 = entity_revision_load('entity_test', 3);
rakesh_verma’s picture

can Node::getRevisionCreationTime be used for implementation of simple merge functionality? It'd get the entity which was created last and we can use that entity as our final revision.

rakesh_verma’s picture

What would the LCA return if there is only one revision?

jeqq’s picture

@rakesh_verma Node::getRevisionCreationTime can be used just for node entity type, but the merge functionality should work with all supported entity types.

LCA for one revision would be the current revision.

rakesh_verma’s picture

@jeqq: `$revision2 = entity_revision_load('entity_test', 2);
$revision3 = entity_revision_load('entity_test', 3);`

are deprecated and doesn't work anymore I believe.

alexej_d’s picture

@rakesh_verma it still works but you need a revisionable entity eg 'entity_test_mulrev' you could then do $this->entityManager->getStorage('entity_test_mulrev')->loadRevision(4)

dixon_’s picture

Issue summary: View changes
rakesh_verma’s picture

Issue summary: View changes

Fixed a typo.

rakesh_verma’s picture

Issue summary: View changes

Changed `$rev3 = $storage->loadRevision(1);` to `$rev3 = $storage->loadRevision(3);`