diff --git a/feature/automated-releases.feature b/feature/automated-releases.feature index 5923fe8a..62f8e733 100644 --- a/feature/automated-releases.feature +++ b/feature/automated-releases.feature @@ -38,7 +38,7 @@ Feature: Automated releases When I close milestone "2.0.0" Then tag "2.0.0" should have been created on branch "2.0.x" - Scenario: If a new major release branch exists, the tool does not create a new minor release + Scenario: If matching minor release branch exists, the tool does not create a new minor release Given following existing branches: | name | | 1.0.x | @@ -66,6 +66,21 @@ Feature: Automated releases Then tag "1.1.0" should have been created on branch "1.1.x" And a new pull request from branch "1.1.x" to "1.2.x" should have been created + Scenario: If a minor release branch exists, when closing the minor release milestone, + the tool tags the minor release from the branch, and creates a pull request + against the next newer minor or major release branch. + Given following existing branches: + | name | + | 1.1.x | + | 2.0.x | + | master | + And following open milestones: + | name | + | 1.1.0 | + When I close milestone "1.1.0" + Then tag "1.1.0" should have been created on branch "1.1.x" + And a new pull request from branch "1.1.x" to "2.0.x" should have been created + Scenario: If no newer release branch exists, the tool will not create any pull requests Given following existing branches: | name | diff --git a/src/Application/Command/SwitchDefaultBranchToNextMinor.php b/src/Application/Command/SwitchDefaultBranchToNextMinor.php index 8db1ce59..358e24a4 100644 --- a/src/Application/Command/SwitchDefaultBranchToNextMinor.php +++ b/src/Application/Command/SwitchDefaultBranchToNextMinor.php @@ -17,6 +17,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use function sprintf; + final class SwitchDefaultBranchToNextMinor extends Command { public function __construct( @@ -58,9 +60,20 @@ public function execute(InputInterface $input, OutputInterface $output): int $nextDefaultBranch = $mergeCandidates->newestFutureReleaseBranchAfter($releaseVersion); if (! $mergeCandidates->contains($nextDefaultBranch)) { + $baseBranch = $mergeCandidates->targetBranchFor($releaseVersion); + if ($baseBranch === null) { + $output->writeln(sprintf( + 'Target branch for release [%s] was not found. Expected [%s] to exist.', + $releaseVersion->fullReleaseName(), + $releaseVersion->targetReleaseBranchName()->name(), + )); + + return 1; + } + $this->push->__invoke( $repositoryPath, - $newestBranch->name(), + $baseBranch->name(), $nextDefaultBranch->name(), ); ($this->bumpChangelogVersion)( diff --git a/src/Git/Value/SemVerVersion.php b/src/Git/Value/SemVerVersion.php index 0423aa44..b6350598 100644 --- a/src/Git/Value/SemVerVersion.php +++ b/src/Git/Value/SemVerVersion.php @@ -11,9 +11,9 @@ final readonly class SemVerVersion { private function __construct( - private readonly int $major, - private readonly int $minor, - private readonly int $patch, + private int $major, + private int $minor, + private int $patch, ) { } diff --git a/test/unit/Application/SwitchDefaultBranchToNextMinorTest.php b/test/unit/Application/SwitchDefaultBranchToNextMinorTest.php index 48b038e3..a126509d 100644 --- a/test/unit/Application/SwitchDefaultBranchToNextMinorTest.php +++ b/test/unit/Application/SwitchDefaultBranchToNextMinorTest.php @@ -145,6 +145,146 @@ public function testWillSwitchToExistingNewestDefaultBranch(): void self::assertSame(0, $this->command->run(new ArrayInput([]), new NullOutput())); } + public function testWillSwitchToExistingNewestDefaultBranchEvenWithNewMajorBranchExist(): void + { + $event = MilestoneClosedEvent::fromEventJson( + <<<'JSON' + { + "milestone": { + "title": "1.3.0", + "number": 123 + }, + "repository": { + "full_name": "foo/bar" + }, + "action": "closed" + } + JSON, + ); + + $workspace = Filesystem\create_temporary_file(Env\temp_dir(), 'workspace'); + + Filesystem\delete_file($workspace); + Filesystem\create_directory($workspace); + Filesystem\create_directory($workspace . '/.git'); + + $this->variables->method('githubWorkspacePath') + ->willReturn($workspace); + + $this->loadEvent->method('__invoke') + ->willReturn($event); + + $this->fetch->expects(self::once()) + ->method('__invoke') + ->with( + 'https://github.com/foo/bar.git', + 'https://github-auth-token:x-oauth-basic@github.com/foo/bar.git', + $workspace, + ); + + $this->getMergeTargets->method('__invoke') + ->with($workspace) + ->willReturn(MergeTargetCandidateBranches::fromAllBranches( + BranchName::fromName('1.1.x'), + BranchName::fromName('1.2.x'), + BranchName::fromName('1.3.x'), + BranchName::fromName('2.0.x'), + BranchName::fromName('master'), + )); + + $this->push->expects(self::once()) + ->method('__invoke') + ->with($workspace, '1.3.x', '1.4.x'); + + $this->bumpChangelogVersion->expects(self::once()) + ->method('__invoke') + ->with( + BumpAndCommitChangelogVersion::BUMP_MINOR, + $workspace, + SemVerVersion::fromMilestoneName('1.3.0'), + BranchName::fromName('1.4.x'), + ); + + $this->setDefaultBranch->expects(self::once()) + ->method('__invoke') + ->with( + self::equalTo(RepositoryName::fromFullName('foo/bar')), + self::equalTo(BranchName::fromName('1.4.x')), + ); + + self::assertSame(0, $this->command->run(new ArrayInput([]), new NullOutput())); + } + + public function testWillNotSwitchToBranchWhenTargetBranchNotFound(): void + { + $event = MilestoneClosedEvent::fromEventJson( + <<<'JSON' + { + "milestone": { + "title": "1.4.0", + "number": 123 + }, + "repository": { + "full_name": "foo/bar" + }, + "action": "closed" + } + JSON, + ); + + $workspace = Filesystem\create_temporary_file(Env\temp_dir(), 'workspace'); + + Filesystem\delete_file($workspace); + Filesystem\create_directory($workspace); + Filesystem\create_directory($workspace . '/.git'); + + $this->variables->method('githubWorkspacePath') + ->willReturn($workspace); + + $this->loadEvent->method('__invoke') + ->willReturn($event); + + $this->fetch->expects(self::once()) + ->method('__invoke') + ->with( + 'https://github.com/foo/bar.git', + 'https://github-auth-token:x-oauth-basic@github.com/foo/bar.git', + $workspace, + ); + + $this->getMergeTargets->method('__invoke') + ->with($workspace) + ->willReturn(MergeTargetCandidateBranches::fromAllBranches( + BranchName::fromName('1.1.x'), + BranchName::fromName('1.2.x'), + BranchName::fromName('1.3.x'), + BranchName::fromName('2.0.x'), + BranchName::fromName('master'), + )); + + $this->push->expects(self::never()) + ->method('__invoke'); + + $this->bumpChangelogVersion->expects(self::never()) + ->method('__invoke'); + + $this->setDefaultBranch->expects(self::never()) + ->method('__invoke'); + + $output = new BufferedOutput(); + + self::assertSame(1, $this->command->run(new ArrayInput([]), $output)); + + self::assertSame( + <<<'OUTPUT' + Target branch for release [1.4.0] was not found. Expected [1.4.x] to exist. + + OUTPUT + , + $output->fetch(), + ); + } + public function testWillSwitchToNewlyCreatedDefaultBranchWhenNoNewerReleaseBranchExists(): void { $workspace = Filesystem\create_temporary_file(Env\temp_dir(), 'workspace');