Skip to content

Commit

Permalink
TASK: Add force strategy to the rebase workspace command
Browse files Browse the repository at this point in the history
  • Loading branch information
mficzel committed Mar 13, 2024
1 parent c561b48 commit bc6e8f8
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,70 @@ Feature: Workspace based content publishing

Then workspace user-ws-two has status OUTDATED

Scenario: Conflicting changes lead to OUTDATED_CONFLICT which can be recovered from via forced rebase

When the command CreateWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-ws-one" |
| baseWorkspaceName | "live" |
| newContentStreamId | "user-cs-one" |
| workspaceOwner | "owner-identifier" |
And the graph projection is fully up to date
And the command CreateWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-ws-two" |
| baseWorkspaceName | "live" |
| newContentStreamId | "user-cs-two" |
| workspaceOwner | "owner-identifier" |
And the graph projection is fully up to date

When the command RemoveNodeAggregate is executed with payload:
| Key | Value |
| nodeAggregateId | "nody-mc-nodeface" |
| nodeVariantSelectionStrategy | "allVariants" |
| coveredDimensionSpacePoint | {} |
| contentStreamId | "user-cs-one" |
And the graph projection is fully up to date

When the command SetNodeProperties is executed with payload:
| Key | Value |
| contentStreamId | "user-cs-two" |
| nodeAggregateId | "nody-mc-nodeface" |
| originDimensionSpacePoint | {} |
| propertyValues | {"text": "Modified"} |
And the graph projection is fully up to date

And the command CreateNodeAggregateWithNode is executed with payload:
| Key | Value |
| nodeAggregateId | "noderus-secundus" |
| nodeTypeName | "Neos.ContentRepository.Testing:Content" |
| parentNodeAggregateId | "lady-eleonode-rootford" |
| originDimensionSpacePoint | {} |
| contentStreamId | "user-cs-two" |
And the graph projection is fully up to date

And the command SetNodeProperties is executed with payload:
| Key | Value |
| contentStreamId | "user-cs-two" |
| nodeAggregateId | "noderus-secundus" |
| originDimensionSpacePoint | {} |
| propertyValues | {"text": "The other node"} |
And the graph projection is fully up to date

And the command PublishWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-ws-one" |
And the graph projection is fully up to date

Then workspace user-ws-two has status OUTDATED

When the command RebaseWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-ws-two" |
| rebasedContentStreamId | "user-cs-two-rebased" |
| rebaseErrorHandlingStrategy | "force" |
And the graph projection is fully up to date

Then workspace user-ws-two has status UP_TO_DATE
And I expect a node identified by user-cs-two-rebased;noderus-secundus;{} to exist in the content graph

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardIndividualNodesFromWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishIndividualNodesFromWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\WorkspaceRebaseStatistics;
use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Command\CreateContentStream;
use Neos\ContentRepository\Core\Feature\ContentStreamForking\Command\ForkContentStream;
Expand Down Expand Up @@ -354,7 +355,7 @@ private function publishContentStream(
*/
private function handleRebaseWorkspace(
RebaseWorkspace $command,
ContentRepository $contentRepository,
ContentRepository $contentRepository
): EventsToPublish {
$workspace = $this->requireWorkspace($command->workspaceName, $contentRepository);
$baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository);
Expand Down Expand Up @@ -417,7 +418,7 @@ private function handleRebaseWorkspace(
$streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName();

// if we got so far without an Exception, we can switch the Workspace's active Content stream.
if (!$rebaseStatistics->hasErrors()) {
if ($command->rebaseErrorHandlingStrategy === RebaseErrorHandlingStrategy::STRATEGY_FORCE || $rebaseStatistics->hasErrors() === false) {
$events = Events::with(
new WorkspaceWasRebased(
$command->workspaceName,
Expand All @@ -426,6 +427,8 @@ private function handleRebaseWorkspace(
),
);

//@todo persist errors in case force is used $somehow

return new EventsToPublish(
$streamName,
$events,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command;

use Neos\ContentRepository\Core\CommandHandler\CommandInterface;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;

Expand All @@ -31,20 +32,29 @@ final class RebaseWorkspace implements CommandInterface
*/
private function __construct(
public readonly WorkspaceName $workspaceName,
public readonly ContentStreamId $rebasedContentStreamId
public readonly ContentStreamId $rebasedContentStreamId,
public readonly RebaseErrorHandlingStrategy $rebaseErrorHandlingStrategy
) {
}

public static function create(WorkspaceName $workspaceName): self
{
return new self($workspaceName, ContentStreamId::create());
return new self($workspaceName, ContentStreamId::create(), RebaseErrorHandlingStrategy::STRATEGY_FAIL);
}

/**
* Call this method if you want to run this command fully deterministically, f.e. during test cases
*/
public function withRebasedContentStreamId(ContentStreamId $newContentStreamId): self
{
return new self($this->workspaceName, $newContentStreamId);
return new self($this->workspaceName, $newContentStreamId, $this->rebaseErrorHandlingStrategy);
}

/**
* Call this method if you want to run this command with a specific error handling strategy like force
*/
public function withErrorHandlingStrategy(RebaseErrorHandlingStrategy $rebaseErrorHandlingStrategy): self
{
return new self($this->workspaceName, $this->rebasedContentStreamId, $rebaseErrorHandlingStrategy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the Neos.ContentRepository package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto;

/**
* The strategy how to handle errors during workspace rebase
*
* - fail (default) ensures conflicts are not ignored but reported
* - force will rebase even if some conflicting events could have to be rebased
*
* @api DTO of {@see RebaseWorkspace} command
*/
enum RebaseErrorHandlingStrategy: string implements \JsonSerializable
{
/**
* This strategy rebasing will fail if conflicts are detected and the "WorkspaceRebaseFailed" event is added.
*/
case STRATEGY_FAIL = 'fail';

/**
* This strategy means all events that can be applied are rebased and conflicting events are ignored
*/
case STRATEGY_FORCE = 'force';

/**
* @return string
*/
public function jsonSerialize(): string
{
return $this->value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Features;

use Behat\Gherkin\Node\TableNode;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
Expand Down Expand Up @@ -105,6 +106,9 @@ public function theCommandRebaseWorkspaceIsExecutedWithPayload(TableNode $payloa
if (isset($commandArguments['rebasedContentStreamId'])) {
$command = $command->withRebasedContentStreamId(ContentStreamId::fromString($commandArguments['rebasedContentStreamId']));
}
if (isset($commandArguments['rebaseErrorHandlingStrategy'])) {
$command = $command->withErrorHandlingStrategy(RebaseErrorHandlingStrategy::from($commandArguments['rebaseErrorHandlingStrategy']));
}

$this->lastCommandOrEventResult = $this->currentContentRepository->handle($command);
}
Expand Down
16 changes: 10 additions & 6 deletions Neos.Neos/Classes/Command/WorkspaceCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\Projection\Workspace\Workspace;
use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory;
use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
Expand Down Expand Up @@ -112,19 +113,22 @@ public function discardCommand(string $workspace, string $contentRepositoryIdent
*
* @param string $workspace Name of the workspace, for example "user-john"
* @param string $contentRepositoryIdentifier
* @param bool $force Rebase all events that do not conflict
* @throws StopCommandException
*/
public function rebaseCommand(string $workspace, string $contentRepositoryIdentifier = 'default'): void
public function rebaseCommand(string $workspace, string $contentRepositoryIdentifier = 'default', bool $force = false): void
{
$contentRepositoryId = ContentRepositoryId::fromString($contentRepositoryIdentifier);
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);

try {
$contentRepository->handle(
RebaseWorkspace::create(
WorkspaceName::fromString($workspace),
)
)->block();
$rebaseCommand = RebaseWorkspace::create(
WorkspaceName::fromString($workspace),
);
if ($force) {
$rebaseCommand = $rebaseCommand->withErrorHandlingStrategy(RebaseErrorHandlingStrategy::STRATEGY_FORCE);
}
$contentRepository->handle($rebaseCommand)->block();
} catch (WorkspaceDoesNotExist $exception) {
$this->outputLine('Workspace "%s" does not exist', [$workspace]);
$this->quit(1);
Expand Down

0 comments on commit bc6e8f8

Please sign in to comment.