Skip to content

Commit fa03849

Browse files
authored
Merge pull request #58 from leviy/include-pre-release-changelogs
Include pre-release changelogs in the changelog of a full release
2 parents d5ac2b7 + 1ffba22 commit fa03849

27 files changed

+715
-198
lines changed

config/github.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ services:
2121
github.release_action.release:
2222
class: Leviy\ReleaseTool\ReleaseAction\GitHubReleaseAction
2323
arguments:
24-
- '@github.client'
25-
- '@vcs.git'
24+
- '@changelog_generator.pull_request'
2625
- '@changelog_formatter.markdown'
26+
- '@vcs.git'
27+
- '@github.client'

config/services.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ services:
1212
arguments:
1313
- '@vcs.git'
1414
- '@versioning_scheme.semantic'
15-
- '@changelog_generator.pull_request'
1615
- ['@github.release_action.release']
1716

1817
versioning_scheme.semantic:
@@ -25,6 +24,11 @@ services:
2524

2625
changelog_formatter.markdown:
2726
class: Leviy\ReleaseTool\Changelog\Formatter\MarkdownFormatter
27+
arguments:
28+
- ['@changelog_formatter_filter.markdown.github_pull_request_link']
29+
30+
changelog_formatter_filter.markdown.github_pull_request_link:
31+
class: Leviy\ReleaseTool\Changelog\Formatter\Filter\GitHubPullRequestUrlFilter
2832
arguments:
2933
- '%github.owner%/%github.repo%'
3034

features/bootstrap/FeatureContext.php

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
declare(strict_types=1);
33

44
use Behat\Behat\Context\Context;
5-
use Leviy\ReleaseTool\Changelog\ChangelogGenerator;
5+
use Behat\Gherkin\Node\PyStringNode;
6+
use Leviy\ReleaseTool\Changelog\Formatter\MarkdownFormatter;
7+
use Leviy\ReleaseTool\Changelog\PullRequestChangelogGenerator;
8+
use Leviy\ReleaseTool\GitHub\GitHubClient;
69
use Leviy\ReleaseTool\Interaction\InformationCollector;
10+
use Leviy\ReleaseTool\ReleaseAction\GitHubReleaseAction;
711
use Leviy\ReleaseTool\ReleaseManager;
12+
use Leviy\ReleaseTool\Vcs\Commit;
13+
use Leviy\ReleaseTool\Vcs\Git;
814
use Leviy\ReleaseTool\Vcs\VersionControlSystem;
915
use Leviy\ReleaseTool\Versioning\SemanticVersioning;
1016
use Mockery\MockInterface;
11-
use PHPUnit\Framework\Assert;
1217

1318
class FeatureContext implements Context
1419
{
@@ -23,29 +28,42 @@ class FeatureContext implements Context
2328
private $releaseManager;
2429

2530
/**
26-
* @var string|null
31+
* @var MockInterface|InformationCollector
2732
*/
28-
private $nextVersion;
33+
private $informationCollector;
2934

3035
/**
31-
* @var MockInterface|InformationCollector
36+
* @var MockInterface|GitHubClient
3237
*/
33-
private $informationCollector;
38+
private $githubClient;
39+
40+
/**
41+
* @var Commit[]
42+
*/
43+
private $mergedPullRequests = [];
3444

3545
public function __construct()
3646
{
3747
$this->informationCollector = Mockery::mock(InformationCollector::class);
38-
$this->versionControlSystem = Mockery::mock(VersionControlSystem::class);
48+
49+
$this->versionControlSystem = Mockery::mock(Git::class);
3950
$this->versionControlSystem->shouldIgnoreMissing();
4051

41-
$changelogGenerator = Mockery::mock(ChangelogGenerator::class);
42-
$changelogGenerator->shouldReceive('getChanges')->andReturn([]);
52+
$changelogGenerator = new PullRequestChangelogGenerator($this->versionControlSystem);
53+
54+
$this->githubClient = Mockery::spy(GitHubClient::class);
55+
56+
$githubReleaseAction = new GitHubReleaseAction(
57+
$changelogGenerator,
58+
new MarkdownFormatter([]),
59+
$this->versionControlSystem,
60+
$this->githubClient
61+
);
4362

4463
$this->releaseManager = new ReleaseManager(
4564
$this->versionControlSystem,
4665
new SemanticVersioning(),
47-
$changelogGenerator,
48-
[]
66+
[$githubReleaseAction]
4967
);
5068
}
5169

@@ -65,7 +83,22 @@ public function iReleaseANewVersion(?string $type = null): void
6583
{
6684
$this->selectVersionType($type);
6785

68-
$this->nextVersion = $this->releaseManager->determineNextVersion($this->informationCollector);
86+
$version = $this->releaseManager->determineNextVersion($this->informationCollector);
87+
$this->releaseManager->release($version, $this->informationCollector);
88+
}
89+
90+
/**
91+
* @When I release version :version
92+
*/
93+
public function iReleaseVersion(string $version): void
94+
{
95+
$this->versionControlSystem->shouldReceive('getCommitsForVersion')
96+
->with($version, Mockery::any())
97+
->andReturn($this->mergedPullRequests);
98+
99+
$this->versionControlSystem->shouldReceive('getTagForVersion')->andReturn($version);
100+
$this->informationCollector->shouldReceive('askConfirmation')->andReturnTrue();
101+
$this->releaseManager->release($version, $this->informationCollector);
69102
}
70103

71104
/**
@@ -92,16 +125,50 @@ public function iReleaseAPreReleaseVersion(string $preReleaseType, ?string $type
92125
return;
93126
}
94127

128+
$this->informationCollector->shouldReceive('askConfirmation')->andReturnTrue();
95129
$this->informationCollector->shouldReceive('askMultipleChoice')->andReturn($answer);
96-
$this->nextVersion = $this->releaseManager->determineNextPreReleaseVersion($this->informationCollector);
130+
131+
$version = $this->releaseManager->determineNextPreReleaseVersion($this->informationCollector);
132+
$this->releaseManager->release($version, $this->informationCollector);
97133
}
98134

99135
/**
100136
* @Then version :version should be released
101137
*/
102138
public function versionShouldBeReleased(string $version): void
103139
{
104-
Assert::assertSame($version, $this->nextVersion);
140+
$this->versionControlSystem->shouldHaveReceived('createVersion', [$version]);
141+
}
142+
143+
/**
144+
* @Given the pre-release :version was created
145+
*/
146+
public function wasAPreRelease(string $version): void
147+
{
148+
$this->versionControlSystem->shouldReceive('getPreReleasesForVersion')->andReturn([$version]);
149+
$this->versionControlSystem->shouldReceive('getCommitsForVersion')
150+
->with($version, Mockery::any())
151+
->andReturn($this->mergedPullRequests);
152+
153+
$this->mergedPullRequests = [];
154+
}
155+
156+
/**
157+
* @Then a release with title :title should be published on GitHub with the following release notes:
158+
*/
159+
public function aReleaseWithTitleShouldBePublishedOnGitHubWithTheFollowingReleaseNotes(
160+
string $version,
161+
PyStringNode $releaseNotes
162+
) {
163+
$this->githubClient->shouldHaveReceived('createRelease', [Mockery::any(), $version, $releaseNotes->getRaw()]);
164+
}
165+
166+
/**
167+
* @Given pull request :title with number :number was merged
168+
*/
169+
public function pullRequestWasMerged(string $title, string $number)
170+
{
171+
$this->mergedPullRequests[] = new Commit('Merge pull request #' . $number . ' from branch', $title);
105172
}
106173

107174
private function selectVersionType(?string $type): void
@@ -121,6 +188,10 @@ private function selectVersionType(?string $type): void
121188
break;
122189
}
123190

191+
// Always respond positive to the question "Do you want to push it to the remote repository and perform
192+
// additional release steps?"
193+
$answers[] = true;
194+
124195
$this->informationCollector->shouldReceive('askConfirmation')->andReturn(...$answers);
125196
}
126197
}

features/changelog.feature

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Feature: Changelog
2+
3+
Scenario: Releasing a version without any pre-releases
4+
Given pull request "Some PR" with number 1 was merged
5+
When I release version "1.1.0"
6+
Then a release with title "1.1.0" should be published on GitHub with the following release notes:
7+
"""
8+
# Changelog for 1.1.0
9+
10+
* Some PR (pull request #1)
11+
12+
"""
13+
14+
Scenario: Releasing a version which is preceded by a pre-release
15+
Given pull request "First pull request" with number 1 was merged
16+
And the pre-release "1.0.0-beta.1" was created
17+
And pull request "Fix bug that came out of beta testing" with number 2 was merged
18+
When I release version "1.0.0"
19+
Then a release with title "1.0.0" should be published on GitHub with the following release notes:
20+
"""
21+
# Changelog for 1.0.0
22+
23+
* Fix bug that came out of beta testing (pull request #2)
24+
25+
# Changelog for 1.0.0-beta.1
26+
27+
* First pull request (pull request #1)
28+
29+
"""

src/Changelog/Changelog.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Leviy\ReleaseTool\Changelog;
5+
6+
use function array_keys;
7+
use function array_merge;
8+
9+
final class Changelog
10+
{
11+
/**
12+
* @var string[][]
13+
*/
14+
private $versions = [];
15+
16+
/**
17+
* @var string[]
18+
*/
19+
private $unreleasedChanges = [];
20+
21+
/**
22+
* @return string[]
23+
*/
24+
public function getVersions(): array
25+
{
26+
return array_keys($this->versions);
27+
}
28+
29+
/**
30+
* @param string[] $changes
31+
*/
32+
public function addVersion(string $version, array $changes): void
33+
{
34+
$this->versions[$version] = $changes;
35+
}
36+
37+
/**
38+
* @return string[]
39+
*/
40+
public function getChangesForVersion(string $version): array
41+
{
42+
return $this->versions[$version];
43+
}
44+
45+
/**
46+
* @param string[] $unreleasedChanges
47+
*/
48+
public function addUnreleasedChanges(array $unreleasedChanges): void
49+
{
50+
$this->unreleasedChanges = array_merge($this->unreleasedChanges, $unreleasedChanges);
51+
}
52+
53+
/**
54+
* @return string[]
55+
*/
56+
public function getUnreleasedChanges(): array
57+
{
58+
return $this->unreleasedChanges;
59+
}
60+
}

src/Changelog/ChangelogGenerator.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
namespace Leviy\ReleaseTool\Changelog;
55

6+
use Leviy\ReleaseTool\Versioning\Version;
7+
68
interface ChangelogGenerator
79
{
8-
/**
9-
* @return string[]
10-
*/
11-
public function getChanges(): array;
10+
public function getUnreleasedChangelog(): Changelog;
11+
12+
public function getChangelogForVersion(Version $version): Changelog;
1213
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Leviy\ReleaseTool\Changelog\Formatter\Filter;
5+
6+
interface Filter
7+
{
8+
public function filter(string $line): string;
9+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Leviy\ReleaseTool\Changelog\Formatter\Filter;
5+
6+
use function preg_replace;
7+
use function sprintf;
8+
9+
final class GitHubPullRequestUrlFilter implements Filter
10+
{
11+
private const PULL_REQUEST_URL = 'https://github.com/%s/pull/$1';
12+
13+
/**
14+
* @var string
15+
*/
16+
private $pullRequestUrl;
17+
18+
public function __construct(string $repositorySlug)
19+
{
20+
$this->pullRequestUrl = sprintf(self::PULL_REQUEST_URL, $repositorySlug);
21+
}
22+
23+
public function filter(string $line): string
24+
{
25+
return preg_replace(
26+
'/pull request #(\d+)/',
27+
'pull request [#$1](' . $this->pullRequestUrl . ')',
28+
$line
29+
);
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Leviy\ReleaseTool\Changelog\Formatter\Filter;
5+
6+
use function preg_replace;
7+
8+
final class IssueLinkFilter implements Filter
9+
{
10+
/**
11+
* @var string
12+
*/
13+
private $pattern;
14+
15+
/**
16+
* @var string
17+
*/
18+
private $url;
19+
20+
/**
21+
* @param string $pattern A regular expression pattern for issue references
22+
* @param string $url A URL replacement string
23+
*/
24+
public function __construct(string $pattern, string $url)
25+
{
26+
$this->pattern = $pattern;
27+
$this->url = $url;
28+
}
29+
30+
public function filter(string $line): string
31+
{
32+
return preg_replace(
33+
$this->pattern,
34+
'[$1](' . $this->url . ')',
35+
$line
36+
);
37+
}
38+
}

src/Changelog/Formatter/Formatter.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33

44
namespace Leviy\ReleaseTool\Changelog\Formatter;
55

6+
use Leviy\ReleaseTool\Changelog\Changelog;
7+
68
interface Formatter
79
{
8-
/**
9-
* @param string[] $changes
10-
*
11-
* @return string[]
12-
*/
13-
public function formatChanges(array $changes): array;
10+
public function format(Changelog $changelog): string;
1411
}

0 commit comments

Comments
 (0)