diff --git a/RoboFile.php b/RoboFile.php index 367b515335..245fb86245 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -613,6 +613,9 @@ class RoboFile extends \Robo\Tasks { public function releasePublish($version = null) { $version = $this->releaseVersionGetNext($version); return $this->collectionBuilder() + ->addCode(function () use ($version) { + return $this->releaseCheckPullRequest($version); + }) ->addCode(function () { return $this->releaseDownloadZip(); }) @@ -640,6 +643,11 @@ class RoboFile extends \Robo\Tasks { ->run(); } + public function releaseCheckPullRequest($version) { + $this->createGitHubController() + ->checkReleasePullRequestPassed($version); + } + public function releaseVersionGetNext($version = null) { if (!$version) { $version = $this->getReleaseVersionController() diff --git a/tasks/release/GitHubController.php b/tasks/release/GitHubController.php index ef42d65c84..b298b2eece 100644 --- a/tasks/release/GitHubController.php +++ b/tasks/release/GitHubController.php @@ -22,6 +22,9 @@ class GitHubController { /** @var HttpClient */ private $http_client; + /** @var HttpClient */ + private $http_client_no_base; + public function __construct($username, $token, $project) { $this->zip_filename = $project === self::PROJECT_MAILPOET ? self::FREE_ZIP_FILENAME : self::PREMIUM_ZIP_FILENAME; $github_path = $project === self::PROJECT_MAILPOET ? 'mailpoet' : 'mailpoet-premium'; @@ -32,6 +35,12 @@ class GitHubController { ], 'base_uri' => "https://api.github.com/repos/mailpoet/$github_path/", ]); + $this->http_client_no_base = new Client([ + 'auth' => [$username, $token], + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + ], + ]); } public function createReleasePullRequest($version) { @@ -59,6 +68,74 @@ class GitHubController { ]); } + public function checkReleasePullRequestPassed($version) { + $response = $this->http_client->get('pulls', [ + 'query' => [ + 'state' => 'all', + 'head' => self::RELEASE_SOURCE_BRANCH, + 'base' => 'master', + 'direction' => 'desc', + ] + ]); + $response = json_decode($response->getBody()->getContents(), true); + if (sizeof($response) === 0) { + throw new \Exception('Failed to load release pull requests'); + } + $response = array_filter($response, function ($pull_request) use ($version) { + return strpos($pull_request['title'], 'Release ' . $version) !== false; + }); + if (sizeof($response) === 0) { + throw new \Exception('Release pull request not found'); + } + $release_pull_request = reset($response); + if ($release_pull_request['state'] !== 'closed') { + throw new \Exception('Release pull request is still opened.'); + } + if (!$release_pull_request['merged']) { + throw new \Exception('Release pull request has not been merged.'); + } + $this->checkPullRequestChecks($release_pull_request['statuses_url']); + $pull_request_number = $release_pull_request['number']; + $this->checkPullRequestReviews($pull_request_number); + } + + private function checkPullRequestChecks($statuses_url) { + $response = $this->http_client_no_base->get($statuses_url); + $response = json_decode($response->getBody()->getContents(), true); + + // Find checks. Statuses are returned in reverse chronological order. We need to get the first of each type + $latest_statuses = []; + foreach ($response as $status) { + if (!isset($latest_statuses[$status['context']])) { + $latest_statuses[$status['context']] = $status; + } + } + + $failed = []; + foreach ($latest_statuses as $status) { + if ($status['state'] !== 'success') { + $failed[] = $status['context']; + } + } + if (!empty($failed)) { + throw new \Exception('Release pull request build failed. Failed jobs: ' . join(', ', $failed)); + } + } + + private function checkPullRequestReviews($pull_request_number) { + $response = $this->http_client->get("pulls/$pull_request_number/reviews"); + $response = json_decode($response->getBody()->getContents(), true); + $approved = 0; + foreach ($response as $review) { + if (strtolower($review['state']) === 'approved') { + $approved++; + } + } + if ($approved === 0) { + throw new \Exception('Pull Request has not been approved'); + } + } + public function publishRelease($version, $changelog, $release_zip_path) { $this->ensureNoDraftReleaseExists(); $this->ensureReleaseDoesNotExistYet($version);