Skip to content

Commit 043bb4a

Browse files
committed
chore: add script to sync release branches
1 parent dca192e commit 043bb4a

4 files changed

Lines changed: 164 additions & 20 deletions

File tree

admin/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ are used as part of that process:
6565
of the version's upgrade guide with the project space files changed since
6666
the last release.
6767
Usage: `php admin/update-upgrade-guide.php <version> [--dry-run]`
68+
- **sync-release-branches.php** checks out the target branch and merges
69+
upstream and the source branch into it, as done at several points of the
70+
release process. Without `--push`, the result is left unpushed for review.
71+
Usage: `php admin/sync-release-branches.php <target> <source> [--push]`
6872

6973
## Other Stuff
7074

admin/RELEASE.md

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# Release Process
22

3-
> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021.
3+
> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021 (MGatner)
44
>
5-
> Updated for `4.5.0` on April 7, 2024.
6-
> Updated for `4.6.0` on January 19, 2025.
7-
>
8-
> -MGatner, kenjis
5+
> Updated for `4.5.0` on April 7, 2024 (kenjis)
6+
> Updated for `4.6.0` on January 19, 2025 (paulbalandan)
7+
> Updated for `4.7.5` on July 8, 2026 (paulbalandan)
98
109
## Notation
1110

@@ -24,11 +23,7 @@ merging will take time.
2423

2524
* [ ] Merge `develop` into `4.y`:
2625
```console
27-
git fetch upstream
28-
git switch 4.y
29-
git merge upstream/4.y
30-
git merge upstream/develop
31-
git push upstream HEAD
26+
php admin/sync-release-branches.php 4.y develop --push
3227
```
3328

3429
## [Minor version only] Merge minor version branch into `develop`
@@ -183,19 +178,11 @@ existing content.
183178
* If it fails, fix the cause and re-run it via "Run workflow" with the tag `v4.x.x`.
184179
* [ ] Fast-forward `develop` branch to catch the merge commit from `master`
185180
```console
186-
git fetch upstream
187-
git checkout develop
188-
git merge upstream/develop
189-
git merge upstream/master
190-
git push upstream HEAD
181+
php admin/sync-release-branches.php develop master --push
191182
```
192183
* [ ] Update the next minor version branch `4.y`:
193184
```console
194-
git fetch upstream
195-
git switch 4.y
196-
git merge upstream/4.y
197-
git merge upstream/develop
198-
git push upstream HEAD
185+
php admin/sync-release-branches.php 4.y develop --push
199186
```
200187
* [ ] [Minor version only] Create the new next minor version branch `4.z`:
201188
```console

admin/sync-release-branches.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Syncs the target branch with upstream and merges in the given source
7+
* branch, as done at several points of the release process.
8+
*
9+
* Usage: php admin/sync-release-branches.php <target> <source> [--push]
10+
*/
11+
function run_command(string $command): void
12+
{
13+
echo sprintf("$ %s\n", $command);
14+
system($command, $exitCode);
15+
16+
if ($exitCode !== 0) {
17+
exit($exitCode);
18+
}
19+
}
20+
21+
function merge_branch(string $ref): void
22+
{
23+
echo sprintf("$ git merge %s\n", $ref);
24+
system(sprintf('git merge %s', escapeshellarg($ref)), $exitCode);
25+
26+
if ($exitCode !== 0) {
27+
echo sprintf("\nMerging %s failed. Resolve the conflicts and run \"git merge --continue\",\n", $ref);
28+
echo "then push with \"git push upstream HEAD\".\n";
29+
30+
exit(1);
31+
}
32+
}
33+
34+
chdir(__DIR__ . '/..');
35+
36+
$args = array_slice($argv, 1);
37+
$options = array_values(array_filter($args, static fn (string $arg): bool => str_starts_with($arg, '--')));
38+
$params = array_values(array_diff($args, $options));
39+
40+
$isBranch = static fn (string $name): bool => preg_match('/\A(?:develop|master|\d+\.\d+)\z/', $name) === 1;
41+
42+
if (
43+
count($params) !== 2
44+
|| ! $isBranch($params[0])
45+
|| ! $isBranch($params[1])
46+
|| $params[0] === $params[1]
47+
|| array_diff($options, ['--push']) !== []
48+
) {
49+
echo sprintf("Usage: php %s <target> <source> [--push]\n", $argv[0]);
50+
echo sprintf("E.g.,: php %s 4.8 develop --push # merges develop into 4.8\n", $argv[0]);
51+
echo sprintf(" php %s develop master --push # merges master into develop\n", $argv[0]);
52+
echo "Checks out <target>, merges upstream/<target> and then upstream/<source> into it.\n";
53+
echo "Without --push, the result is left unpushed for review.\n";
54+
55+
exit(1);
56+
}
57+
58+
[$target, $source] = $params;
59+
$push = in_array('--push', $options, true);
60+
61+
exec('git remote get-url upstream 2>&1', $remoteOutput, $exitCode);
62+
63+
if ($exitCode !== 0) {
64+
echo "The \"upstream\" remote is not configured.\n";
65+
66+
exit(1);
67+
}
68+
69+
exec('git status --porcelain 2>&1', $statusOutput, $exitCode);
70+
71+
if ($exitCode !== 0 || $statusOutput !== []) {
72+
echo "The working tree is not clean. Commit or stash your changes first.\n";
73+
74+
exit(1);
75+
}
76+
77+
run_command('git fetch upstream');
78+
79+
exec(sprintf('git rev-parse --verify --quiet refs/heads/%s 2>&1', escapeshellarg($target)), $verifyOutput, $exitCode);
80+
81+
if ($exitCode === 0) {
82+
run_command(sprintf('git switch %s', escapeshellarg($target)));
83+
} else {
84+
run_command(sprintf('git switch -c %s upstream/%s', escapeshellarg($target), escapeshellarg($target)));
85+
}
86+
87+
merge_branch("upstream/{$target}");
88+
merge_branch("upstream/{$source}");
89+
90+
if ($push) {
91+
run_command('git push upstream HEAD');
92+
93+
echo sprintf("Merged upstream/%s into %s and pushed to upstream.\n", $source, $target);
94+
} else {
95+
echo sprintf("Merged upstream/%s into %s. Review the result, then run \"git push upstream HEAD\".\n", $source, $target);
96+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\AutoReview;
15+
16+
use PHPUnit\Framework\Attributes\CoversNothing;
17+
use PHPUnit\Framework\Attributes\DataProvider;
18+
use PHPUnit\Framework\Attributes\Group;
19+
use PHPUnit\Framework\TestCase;
20+
21+
/**
22+
* The successful path switches branches and merges, so only the argument
23+
* validation is exercised here.
24+
*
25+
* @internal
26+
*/
27+
#[CoversNothing]
28+
#[Group('AutoReview')]
29+
final class SyncReleaseBranchesTest extends TestCase
30+
{
31+
#[DataProvider('provideUsageErrors')]
32+
public function testUsageErrors(string $arguments): void
33+
{
34+
exec(sprintf('php ./admin/sync-release-branches.php %s 2>&1', $arguments), $output, $exitCode);
35+
36+
$this->assertSame(1, $exitCode);
37+
$this->assertStringContainsString('Usage:', implode("\n", $output));
38+
}
39+
40+
/**
41+
* @return iterable<string, array{0: string}>
42+
*/
43+
public static function provideUsageErrors(): iterable
44+
{
45+
yield 'no arguments' => [''];
46+
47+
yield 'missing source' => ['4.8'];
48+
49+
yield 'invalid branch' => ['release-4.8.0 develop'];
50+
51+
yield 'invalid source' => ['4.8 upstream/develop'];
52+
53+
yield 'same branch and source' => ['develop develop'];
54+
55+
yield 'unknown option' => ['4.8 develop --force'];
56+
}
57+
}

0 commit comments

Comments
 (0)