From aab0c99e347cb950a365d40568b4484ff8148d36 Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Mon, 13 Jan 2025 13:19:16 -0500 Subject: [PATCH] BSD fixes #358: Allow creating releases from robo. --- RoboFile.php | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/RoboFile.php b/RoboFile.php index 48a24b8a..8575b7c1 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -1,5 +1,6 @@ comment('This is just a placeholder command, please add your own custom commands here. Please edit : ' . __FILE__); } + /** + * Shared functionality to help create and re-tag a release. + * + * @param string $hotfix_or_release + * Either 'hotfix' or 'release'. + * @param string $semantic_version + * A semantic version number in the form x.y.z. Release must end in 0. + * + * @return array + * An indexed array of [$tag_description, $new_branch_name, $source_branch]. + * + * @throws \Exception + */ + + protected function getVariablesForRelease(string $hotfix_or_release, string $semantic_version): array + { + if (!in_array($hotfix_or_release, ['hotfix', 'release'])) { + throw new InvalidArgumentException("hotfix_or_release must be either 'hotfix' or 'release', '$hotfix_or_release' given."); + } + + // @see https://regex101.com/r/Ly7O1x/3/. + if ($hotfix_or_release === 'hotfix') { + if (!preg_match('/^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P[1-9]\d*)$/', $semantic_version)) { + throw new InvalidArgumentException("semantic_version must be in the form x.y.z, where z is greater than 0, '$semantic_version' given."); + } + } else if (!preg_match('/^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0)$/', $semantic_version)) { + throw new InvalidArgumentException("semantic_version must be in the form x.y.z, where z is 0, '$semantic_version' given."); + } + $this->_exec('git status'); + if (`git status --porcelain`) { + throw new \Exception('Your "git status" must be clean of any changes or untracked files before continuing. Please see the output of "git status" above.'); + } + if ($hotfix_or_release === 'hotfix') { + $source_branch = 'main'; + $tag_description = "Hotfix version $semantic_version"; + } else { + $source_branch = 'develop'; + $tag_description = "Release version $semantic_version"; + } + + $new_branch_name = "$hotfix_or_release/$semantic_version"; + + return [$tag_description, $new_branch_name, $source_branch]; + } + + /** + * Create a release. + * + * @command drupal-project:create-release + * + * @aliases create-release + * + * @param string $hotfix_or_release + * Either 'hotfix' or 'release'. + * @param string $semantic_version + * A semantic version number in the form x.y.z. Release must end in 0. + * + * @return \Robo\ResultData + * + * @throws \Exception + */ + public function createRelease( + InputInterface $input, + OutputInterface $output, + string $hotfix_or_release, + string $semantic_version, + ): ResultData + { + [$tag_description, $new_branch_name, $source_branch] = $this->getVariablesForRelease($hotfix_or_release, $semantic_version); + + `git fetch`; + // Checkout the branch that the release will be created from. + $this->taskGitStack() + ->stopOnFail() + ->checkout($source_branch) + ->run(); + + // If you trying to test this function, you will need to temp change + // $source_branch to whatever branch you are working in, otherwise, + // your changes will get wiped out. + // You will also want to comment the following line out, since it will + // also wipe out your changes. + `git reset --hard origin/$source_branch`; + + // Create the new release branch. + `git checkout -b $new_branch_name`; + + // Create a new release tag and push the release branch and tag. + $this->taskGitStack() + ->stopOnFail() + ->push('origin', $new_branch_name) + ->tag("v$semantic_version", $tag_description) + ->push('origin', "v$semantic_version") + ->run(); + + return new ResultData(); + } + + /** + * Re-creates the tag for a release after updates have been pushed. + * + * The release branch will already be up to date because a feature branch + * should have been pushed to it, but the initial tag will be out of date + * now. This checks out the current version of the release, deletes the tag + * then pushes back up the tag. + * + * @command drupal-project:re-tag-release + * + * @aliases re-tag + * + * @param string $hotfix_or_release + * Either 'hotfix' or 'release'. + * @param string $semantic_version + * A semantic version number in the form x.y.z. Release must end in 0. + * + * @return \Robo\ResultData + * + * @throws \Exception + */ + public function reTagRelease( + InputInterface $input, + OutputInterface $output, + string $hotfix_or_release, + string $semantic_version, + ): ResultData + { + [$tag_description, $new_branch_name] = $this->getVariablesForRelease($hotfix_or_release, $semantic_version); + + `git fetch`; + // Check back out the release branch that has been updated by a feature + // request and is ahead of the source branch. + $this->taskGitStack() + ->stopOnFail() + ->checkout($new_branch_name) + ->run(); + + // Ensure that the release is at the latest. + `git reset --hard origin/$new_branch_name`; + + // Delete the old tag locally and remotely. + `git tag --delete v$semantic_version`; + `git push origin --delete v$semantic_version`; + + // Create a new tag of the same named based on the updated release + // branch. + $this->taskGitStack() + ->stopOnFail() + ->tag("v$semantic_version", $tag_description) + ->push('origin', "v$semantic_version") + ->run(); + + return new ResultData(); + } + }