Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use DR\Review\Service\Git\GitRepositoryService;
use DR\Review\Service\Git\Review\ReviewDiffService\CacheableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\LockableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\RecoverableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffServiceInterface;
use DR\Review\Service\Git\Review\Strategy\BasicCherryPickStrategy;
Expand Down Expand Up @@ -191,7 +192,8 @@
$services->set(HesitantCherryPickStrategy::class)->tag('review_diff_strategy', ['priority' => 10]);
$services->set('review.diff.service', ReviewDiffService::class)->arg('$reviewDiffStrategies', tagged_iterator('review_diff_strategy'));

$services->set('lock.review.diff.service', LockableReviewDiffService::class)->arg('$diffService', service('review.diff.service'));
$services->set('recoverable.review.diff.service', RecoverableReviewDiffService::class)->arg('$diffService', service('review.diff.service'));
$services->set('lock.review.diff.service', LockableReviewDiffService::class)->arg('$diffService', service('recoverable.review.diff.service'));
$services->set(ReviewDiffServiceInterface::class, CacheableReviewDiffService::class)->arg('$diffService', service('lock.review.diff.service'));
$services->set(ReviewRouter::class)->decorate('router')->args([service('.inner')]);
$services->set(CodeReviewFileService::class)->arg('$revisionCache', service(CacheInterface::class . ' $revisionCache'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace DR\Review\Service\Git\Review\ReviewDiffService;

use DR\Review\Entity\Repository\Repository;
use DR\Review\Entity\Review\CodeReview;
use DR\Review\Service\Git\Review\FileDiffOptions;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Process\Exception\ProcessFailedException;

class RecoverableReviewDiffService implements ReviewDiffServiceInterface, LoggerAwareInterface
{
use LoggerAwareTrait;

public function __construct(private readonly ReviewDiffServiceInterface $diffService)
{
}

/**
* @inheritDoc
*/
public function getDiffForRevisions(Repository $repository, array $revisions, ?FileDiffOptions $options = null): array
{
return $this->diffService->getDiffForRevisions($repository, $revisions, $options);
}

/**
* The target branch of the review might be stale, try to reset it to the main branch if the diff fails.
* @inheritDoc
*/
public function getDiffForBranch(CodeReview $review, array $revisions, string $branchName, ?FileDiffOptions $options = null): array
{
try {
return $this->diffService->getDiffForBranch($review, $revisions, $branchName, $options);
} catch (ProcessFailedException $exception) {
if ($review->getTargetBranch() === $review->getRepository()->getMainBranchName()) {
throw $exception;
}

$this->logger?->notice('Failed to get diff for branch, trying to reset target branch', ['exception' => $exception]);
$review->setTargetBranch($review->getRepository()->getMainBranchName());
}

return $this->diffService->getDiffForBranch($review, $revisions, $branchName, $options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);

namespace DR\Review\Tests\Unit\Service\Git\Review\ReviewDiffService;

use DR\Review\Entity\Git\Diff\DiffComparePolicy;
use DR\Review\Entity\Repository\Repository;
use DR\Review\Entity\Review\CodeReview;
use DR\Review\Entity\Revision\Revision;
use DR\Review\Service\Git\Review\FileDiffOptions;
use DR\Review\Service\Git\Review\ReviewDiffService\RecoverableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffServiceInterface;
use DR\Review\Tests\AbstractTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use stdClass;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Throwable;

#[CoversClass(RecoverableReviewDiffService::class)]
class RecoverableReviewDiffServiceTest extends AbstractTestCase
{
private ReviewDiffServiceInterface&MockObject $diffService;
private RecoverableReviewDiffService $service;

protected function setUp(): void
{
parent::setUp();
$this->diffService = $this->createMock(ReviewDiffServiceInterface::class);
$this->service = new RecoverableReviewDiffService($this->diffService);
}

/**
* @throws Throwable
*/
public function testGetDiffForRevisions(): void
{
$repository = new Repository();
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);

$this->diffService->expects(self::once())->method('getDiffForRevisions')->with($repository, $revisions, $options);

$this->service->getDiffForRevisions($repository, $revisions, $options);
}

/**
* @throws Throwable
*/
public function testGetDiffForBranchExpectTwoAttempts(): void
{
$callCount = new stdClass();
$callCount->count = 0;
$repository = (new Repository())->setMainBranchName('master');
$review = (new CodeReview())->setTargetBranch('foobar')->setRepository($repository);
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);
$branchName = 'branch';

$exception = $this->createMock(ProcessFailedException::class);

$this->diffService->expects(self::exactly(2))->method('getDiffForBranch')
->with($review, $revisions, $branchName, $options)
->willThrowException($exception);

$this->expectException(ProcessFailedException::class);
$this->service->getDiffForBranch($review, $revisions, $branchName, $options);
}

/**
* @throws Throwable
*/
public function testGetDiffForBranchExpectOneAttemptOnMasterBranch(): void
{
$callCount = new stdClass();
$callCount->count = 0;
$repository = (new Repository())->setMainBranchName('master');
$review = (new CodeReview())->setTargetBranch('master')->setRepository($repository);
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);
$branchName = 'branch';

$exception = $this->createMock(ProcessFailedException::class);

$this->diffService->expects(self::once())->method('getDiffForBranch')
->with($review, $revisions, $branchName, $options)
->willThrowException($exception);

$this->expectException(ProcessFailedException::class);
$this->service->getDiffForBranch($review, $revisions, $branchName, $options);
}
}