Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions admin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ are used as part of that process:
of the version's upgrade guide with the project space files changed since
the last release.
Usage: `php admin/update-upgrade-guide.php <version> [--dry-run]`
- **sync-release-branches.php** checks out the target branch and merges
upstream and the source branch into it, as done at several points of the
release process. Without `--push`, the result is left unpushed for review.
Usage: `php admin/sync-release-branches.php <target> <source> [--push]`

## Other Stuff

Expand Down
27 changes: 7 additions & 20 deletions admin/RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Release Process

> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021.
> Documentation guide based on the releases of `4.0.5` and `4.1.0` on January 31, 2021 (MGatner)
>
> Updated for `4.5.0` on April 7, 2024.
> Updated for `4.6.0` on January 19, 2025.
>
> -MGatner, kenjis
> Updated for `4.5.0` on April 7, 2024 (kenjis)
> Updated for `4.6.0` on January 19, 2025 (paulbalandan)
> Updated for `4.7.5` on July 8, 2026 (paulbalandan)

## Notation

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

* [ ] Merge `develop` into `4.y`:
```console
git fetch upstream
git switch 4.y
git merge upstream/4.y
git merge upstream/develop
git push upstream HEAD
php admin/sync-release-branches.php 4.y develop --push
```

## [Minor version only] Merge minor version branch into `develop`
Expand Down Expand Up @@ -183,19 +178,11 @@ existing content.
* If it fails, fix the cause and re-run it via "Run workflow" with the tag `v4.x.x`.
* [ ] Fast-forward `develop` branch to catch the merge commit from `master`
```console
git fetch upstream
git checkout develop
git merge upstream/develop
git merge upstream/master
git push upstream HEAD
php admin/sync-release-branches.php develop master --push
```
* [ ] Update the next minor version branch `4.y`:
```console
git fetch upstream
git switch 4.y
git merge upstream/4.y
git merge upstream/develop
git push upstream HEAD
php admin/sync-release-branches.php 4.y develop --push
```
* [ ] [Minor version only] Create the new next minor version branch `4.z`:
```console
Expand Down
96 changes: 96 additions & 0 deletions admin/sync-release-branches.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

/**
* Syncs the target branch with upstream and merges in the given source
* branch, as done at several points of the release process.
*
* Usage: php admin/sync-release-branches.php <target> <source> [--push]
*/
function run_command(string $command): void
{
echo sprintf("$ %s\n", $command);
system($command, $exitCode);

if ($exitCode !== 0) {
exit($exitCode);
}
}

function merge_branch(string $ref): void
{
echo sprintf("$ git merge %s\n", $ref);
system(sprintf('git merge %s', escapeshellarg($ref)), $exitCode);

if ($exitCode !== 0) {
echo sprintf("\nMerging %s failed. Resolve the conflicts and run \"git merge --continue\",\n", $ref);
echo "then push with \"git push upstream HEAD\".\n";

exit(1);
}
}

chdir(__DIR__ . '/..');

$args = array_slice($argv, 1);
$options = array_values(array_filter($args, static fn (string $arg): bool => str_starts_with($arg, '--')));
$params = array_values(array_diff($args, $options));

$isBranch = static fn (string $name): bool => preg_match('/\A(?:develop|master|\d+\.\d+)\z/', $name) === 1;

if (
count($params) !== 2
|| ! $isBranch($params[0])
|| ! $isBranch($params[1])
|| $params[0] === $params[1]
|| array_diff($options, ['--push']) !== []
) {
echo sprintf("Usage: php %s <target> <source> [--push]\n", $argv[0]);
echo sprintf("E.g.,: php %s 4.8 develop --push # merges develop into 4.8\n", $argv[0]);
echo sprintf(" php %s develop master --push # merges master into develop\n", $argv[0]);
echo "Checks out <target>, merges upstream/<target> and then upstream/<source> into it.\n";
echo "Without --push, the result is left unpushed for review.\n";

exit(1);
}

[$target, $source] = $params;
$push = in_array('--push', $options, true);

exec('git remote get-url upstream 2>&1', $remoteOutput, $exitCode);

if ($exitCode !== 0) {
echo "The \"upstream\" remote is not configured.\n";

exit(1);
}

exec('git status --porcelain 2>&1', $statusOutput, $exitCode);

if ($exitCode !== 0 || $statusOutput !== []) {
echo "The working tree is not clean. Commit or stash your changes first.\n";

exit(1);
}

run_command('git fetch upstream');

exec(sprintf('git rev-parse --verify --quiet refs/heads/%s 2>&1', escapeshellarg($target)), $verifyOutput, $exitCode);

if ($exitCode === 0) {
run_command(sprintf('git switch %s', escapeshellarg($target)));
} else {
run_command(sprintf('git switch -c %s upstream/%s', escapeshellarg($target), escapeshellarg($target)));
}

merge_branch("upstream/{$target}");
merge_branch("upstream/{$source}");

if ($push) {
run_command('git push upstream HEAD');

echo sprintf("Merged upstream/%s into %s and pushed to upstream.\n", $source, $target);
} else {
echo sprintf("Merged upstream/%s into %s. Review the result, then run \"git push upstream HEAD\".\n", $source, $target);
}
57 changes: 57 additions & 0 deletions tests/system/AutoReview/SyncReleaseBranchesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\AutoReview;

use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;

/**
* The successful path switches branches and merges, so only the argument
* validation is exercised here.
*
* @internal
*/
#[CoversNothing]
#[Group('AutoReview')]
final class SyncReleaseBranchesTest extends TestCase
{
#[DataProvider('provideUsageErrors')]
public function testUsageErrors(string $arguments): void
{
exec(sprintf('php ./admin/sync-release-branches.php %s 2>&1', $arguments), $output, $exitCode);

$this->assertSame(1, $exitCode);
$this->assertStringContainsString('Usage:', implode("\n", $output));
}

/**
* @return iterable<string, array{0: string}>
*/
public static function provideUsageErrors(): iterable
{
yield 'no arguments' => [''];

yield 'missing source' => ['4.8'];

yield 'invalid branch' => ['release-4.8.0 develop'];

yield 'invalid source' => ['4.8 upstream/develop'];

yield 'same branch and source' => ['develop develop'];

yield 'unknown option' => ['4.8 develop --force'];
}
}
Loading