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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public function pullRequest(string $repository, int $number): FinalizedPullReque

public function branchHead(string $repository, string $branch): string;

public function isAncestor(string $repository, string $ancestorSha, string $descendantSha): bool;

/** @return list<string> */
public function branches(string $repository): array;

Expand Down
2 changes: 1 addition & 1 deletion src/Application/Release/ReleaseFinalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function finalize(

$finalSha = $pullRequest->mergeCommitSha;
$branchHead = $this->github->branchHead($preparation->repository, $preparation->targetBranch);
if ($branchHead !== $finalSha && !$this->git->isAncestor($finalSha, $branchHead)) {
if ($branchHead !== $finalSha && !$this->github->isAncestor($preparation->repository, $finalSha, $branchHead)) {
throw new DomainException(sprintf(
'Release branch no longer contains merged release %s; current head is %s.',
$finalSha,
Expand Down
20 changes: 20 additions & 0 deletions src/Infrastructure/GitHub/GitHubReleaseFinalizationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,26 @@ public function branchHead(string $repository, string $branch): string
return $sha;
}

public function isAncestor(string $repository, string $ancestorSha, string $descendantSha): bool
{
if ($ancestorSha === $descendantSha) {
return true;
}

$data = $this->request(
'GET',
sprintf(
'/repos/%s/compare/%s...%s',
$repository,
rawurlencode($ancestorSha),
rawurlencode($descendantSha),
),
);
$status = $data['status'] ?? null;

return $status === 'ahead' || $status === 'identical';
}

/** @return list<string> */
public function branches(string $repository): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public function __construct(
private readonly ?HistorySynchronization $publishedHistory = null,
/** @var array<string, string> */
private readonly array $files = [],
/** @var array<string, list<string>> */
private readonly array $ancestors = [],
) {
}

Expand All @@ -36,6 +38,12 @@ public function branchHead(string $repository, string $branch): string
return $this->branchHeads[$branch];
}

public function isAncestor(string $repository, string $ancestorSha, string $descendantSha): bool
{
return $ancestorSha === $descendantSha
|| in_array($ancestorSha, $this->ancestors[$descendantSha] ?? [], true);
}

/** @return list<string> */
public function branches(string $repository): array
{
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Application/Release/ReleaseFinalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public function testAllowsDelayedFinalizationWhenMergedReleaseRemainsAncestor():
array_map(static fn (FileChange $change): string => $change->path, $this->preparation()->fileChanges),
),
['stable35' => $advanced, 'main' => self::MAIN],
ancestors: [$advanced => [self::FINAL]],
);

$prepared = (new ReleaseFinalizer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ public function testReadsMergedPullRequestAndChangedFiles(): void
self::assertSame(['appinfo/info.xml', 'package.json'], $result->changedFiles);
}

public function testChecksAncestryThroughGitHubCompareApi(): void
{
$descendant = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
$client = new MockHttpClient([
$this->json(['status' => 'ahead']),
], 'https://api.github.test');

$result = (new GitHubReleaseFinalizationRepository('token', $client, 'https://api.github.test'))
->isAncestor('LibreSign/libresign', self::SHA, $descendant);

self::assertTrue($result);
}

public function testRejectsDivergedHistoryThroughGitHubCompareApi(): void
{
$descendant = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
$client = new MockHttpClient([
$this->json(['status' => 'diverged']),
], 'https://api.github.test');

$result = (new GitHubReleaseFinalizationRepository('token', $client, 'https://api.github.test'))
->isAncestor('LibreSign/libresign', self::SHA, $descendant);

self::assertFalse($result);
}

public function testReadsFileAtExactCommitThroughGitHubContentsApi(): void
{
$content = "# Changelog\n\n## 13.4.2 - 2026-09-21\n";
Expand Down
Loading