Closed chindris closed 5 years ago
Hi @amateescu and @pmelab ! I have a couple of questions for this PR:
Question 1: The merged revision of a merge a
to b
should have the revision b
as parent and a
as the merge parent, no matter which revision been copied to create the merge revision.
Question 2: I think it would be fine to use ContentEntityInterface
?
@amateescu please correct me if i'm wrong
For question 1, @pmelab is spot on :)
For question 2, I had to deal with the same problem a few times in the revision tree stuff, so maybe it would be better to open a core issue to make RevisionableInterface
extend EntityInterface
.
I think this is ready for the final review. I implemented point 1) and just left the RevisionableInterface for now as it is.
Here is how the conflict resolution introduced by this PR should work:
There is a service collector: ConflictResolverManager which will collect services tagged with _conflictresolver
The ConflictResolverManager service has the following methods: -- checkConflict(revisionA, revisionB, commonAncestor) -> true | false: checks if the revisionA and revisionB are in conflict. If yes, it returns TRUE, otherwise FALSE. -- resolveConflict(revisionA, revisionB, commonAncestor) -> revisionC | null: tries to automatically resolve the conflict between the two revisions. This method will actually just iterate over a set of the actual conflict resolvers (services tagged with conflict_resolver) and, if the resolver is allowed to be invoked, it will call the resolveConflict() method of it. If that returns a new revision, then this is what the final return of this method will be. If we reach the end of the resolvers set, then we just return null.
The ConflictResolver services will have two methods: -- applies(revisionA, revisionB, commonAncestor) -> true | false: checks if this resolver should be actually used. Returns TRUE if yes. -- resolveConflict(revisionA, revisionB, commonAncestor) -> revisionC | null: tries to resolve the conflict. If successful, will return the new revision, otherwise will just return null.
For the UI part, there is another service collector: ConflictResolverManagerUI which will collect services tagged with _conflict_resolverui.
The service has a method: render(revisionA, revisionB) -> render array. Similar with the ConflictResolverManager::resolveConflict(), it will iterate over the actual services, if the service can be used it will call that service render() method, until the we get a non-null result.
The ConflictResolverUI services have the following methods: -- applies(revisionA, revisionB) -> true | false: checks if this resolver should be actually used. Returns TRUE if yes. -- conflictResolverUI(revisionA, revisionB) -> render array. Returns the UI of the conflict resolution.
There is a route: /conflicts/resolve/{entityType}/{revisionA}/{revisionB} -- If the two revisions are not in conflict, it returns a 404. -- Tries to resolve the conflict automatically by using ConflictResolverManager::resolveConflict(). -- If automatic conflict resolution succeeded, the user is redirected to the preview (edit form ?) of the new revision and a success message is shown. -- If the automatic conflict resolution fails, then the ConflictResolverManagerUI::render() is used to show the UI. -- If ConflictResolverManagerUI::render() return null, the we return a 404.