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
47 changes: 47 additions & 0 deletions .github/workflows/batch_release_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
repository_dispatch:
types: [batch-release-pr]

# Declare default permissions as read only.
permissions: read-all

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a new global permission setting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github action security scan warned that, if no permission set, using default permission can be too excessive.


env:
HEAD_BRANCH_NAME: ${{ github.event.client_payload.package }}-${{ github.run_id }}-${{ github.run_attempt }}

Expand All @@ -15,6 +18,7 @@ jobs:
outputs:
head_branch_created: ${{ steps.check-branch-exists.outputs.exists }}
release_branch: ${{ steps.create-branch.outputs.release_branch }} # e.g. release-go_router-17.2.2, returned by branches-for-batch-release tool.
blocking_branch: ${{ steps.check-in-flight.outputs.blocking_branch }} # Set only when an earlier release hasn't been merged back to main yet.
steps:
- name: checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
Expand All @@ -26,11 +30,20 @@ jobs:
- name: Set up tools
run: dart pub get
working-directory: ${{ github.workspace }}/script/tool
# A new release can't be cut while an earlier one is still in flight. This
# only reports that; the unblock_sync and report_blocked_release jobs act
# on it.
- name: check for an in-flight release
id: check-in-flight
run: dart ./script/tool/lib/src/main.dart in-flight-release-check --packages=${GITHUB_EVENT_CLIENT_PAYLOAD_PACKAGE} --remote=origin
env:
GITHUB_EVENT_CLIENT_PAYLOAD_PACKAGE: ${{ github.event.client_payload.package }}
# This step is to create branches for a batch release
# Branches may not be created if there is nothing to release.
# In that case, the workflow will exit and complete successfully.
- name: create batch release branch
id: create-branch
if: steps.check-in-flight.outputs.blocking_branch == ''
run: |
gh auth setup-git
git config --global user.name "flutteractionsbot"
Expand Down Expand Up @@ -76,4 +89,38 @@ jobs:
GITHUB_EVENT_CLIENT_PAYLOAD_PACKAGE: ${{ github.event.client_payload.package }}
GH_TOKEN: ${{ secrets.FLUTTERGITHUBBOT_TOKEN }}

# If a previous release hasn't been merged back yet, no new release can be
# cut. Make sure the sync PR for it exists, and nudge it if it does.
unblock_sync:
needs: create_batch_release_branch
if: needs.create_batch_release_branch.outputs.blocking_branch != ''
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/reusable_sync_to_main.yml
with:
branch-name: ${{ needs.create_batch_release_branch.outputs.blocking_branch }}
comment-if-exists: >-
This PR is blocking the next batch release: no new release branch can be
cut until the version bump and changelog on
`${{ needs.create_batch_release_branch.outputs.blocking_branch }}` are on
`main`. That release is already published, so these changes have to
land. If this PR can't be landed as-is, apply the same version bump and
changelog to `main` by hand.
secrets:
BOT_TOKEN: ${{ secrets.FLUTTERGITHUBBOT_TOKEN }}

# Surface the skipped release, since a silently green scheduled run would hide
# it until someone noticed the missing release.
report_blocked_release:
needs: [create_batch_release_branch, unblock_sync]
if: ${{ !cancelled() && needs.create_batch_release_branch.outputs.blocking_branch != '' }}
permissions: {} # Only writes a log annotation.
runs-on: ubuntu-latest
steps:
- name: Report blocked release
run: |
echo "::error::No batch release was created: ${BLOCKING_BRANCH} has not been merged back into main."
exit 1
env:
BLOCKING_BRANCH: ${{ needs.create_batch_release_branch.outputs.blocking_branch }}
52 changes: 6 additions & 46 deletions .github/workflows/release_from_branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,53 +21,13 @@ jobs:

sync_to_main:
needs: release
if: github.repository_owner == 'flutter' && needs.release.result == 'success'
runs-on: ubuntu-latest
if: needs.release.result == 'success'
permissions:
# Need write permission to create PR.
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
fetch-depth: 0 # Fetch history to allow branch comparison
persist-credentials: false

- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.FLUTTERGITHUBBOT_TOKEN }}
run: |
gh auth setup-git
# 1. Fetch main so the runner can see the difference
git fetch origin main

# 2. Verify there are actually new commits to sync
# This prevents the "GraphQL: No commits between main..." error
COMMITS_COUNT=$(git rev-list --count origin/main..HEAD)

if [ "$COMMITS_COUNT" -eq "0" ]; then
echo "No new commits found on ${GITHUB_REF_NAME} compared to main. Nothing to sync."
exit 0
fi

# 3. Extract package name for label
BRANCH_NAME="${GITHUB_REF_NAME}"
TEMP="${BRANCH_NAME#release-}"
PACKAGE_NAME="${TEMP%-*}"

# 4. Check if PR already exists before creating
EXISTING_PR=$(gh pr list --head "${GITHUB_REF_NAME}" --base "main" --json number --jq '.[0].number')
if [ -n "$EXISTING_PR" ]; then
echo "A pull request for ${GITHUB_REF_NAME} already exists (#${EXISTING_PR})."
exit 0
fi

# 5. Create the PR directly
gh pr create \
--base "main" \
--head "${GITHUB_REF_NAME}" \
--title "Sync ${GITHUB_REF_NAME} to main" \
--body "This automated PR syncs the changes from the release branch ${GITHUB_REF_NAME} back to the main branch." \
--label "override: batch-${PACKAGE_NAME}"

uses: ./.github/workflows/reusable_sync_to_main.yml
with:
branch-name: '${{ github.ref_name }}'
secrets:
BOT_TOKEN: ${{ secrets.FLUTTERGITHUBBOT_TOKEN }}
82 changes: 82 additions & 0 deletions .github/workflows/reusable_sync_to_main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Reusable Sync To Main
on:
workflow_call:
inputs:
branch-name:
description: 'The release branch to sync back to main.'
required: true
type: string
comment-if-exists:
description: >-
If non-empty, this is posted as a comment when the sync PR already
exists. The comment is only posted once per PR.
required: false
default: ''
type: string
secrets:
BOT_TOKEN:
required: true
jobs:
sync_to_main:
if: github.repository_owner == 'flutter'
runs-on: ubuntu-latest
permissions:
# Need write permission to create PR.
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
ref: ${{ inputs.branch-name }}
fetch-depth: 0 # Fetch history to allow branch comparison
persist-credentials: false

- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_TOKEN }}
BRANCH_NAME: ${{ inputs.branch-name }}
COMMENT: ${{ inputs.comment-if-exists }}
# Used to avoid posting the same comment on every scheduled run.
COMMENT_MARKER: '<!-- batch-release-sync-nudge -->'
run: |
gh auth setup-git
# 1. Fetch main so the runner can see the difference
git fetch origin main

# 2. Check if PR already exists before creating
EXISTING_PR=$(gh pr list --head "${BRANCH_NAME}" --base "main" --json number --jq '.[0].number')
if [ -n "$EXISTING_PR" ]; then
echo "A pull request for ${BRANCH_NAME} already exists (#${EXISTING_PR})."
if [ -n "$COMMENT" ]; then
if gh pr view "$EXISTING_PR" --json comments --jq '.comments[].body' | grep -qF "$COMMENT_MARKER"; then
echo "Already commented on #${EXISTING_PR}."
else
gh pr comment "$EXISTING_PR" --body "${COMMENT} ${COMMENT_MARKER}"
fi
fi
exit 0
fi

# 3. Verify there are actually new commits to sync.
# This prevents the "GraphQL: No commits between main..." error, and is
# the expected state when the release PR itself has not landed yet.
COMMITS_COUNT=$(git rev-list --count origin/main..HEAD)

if [ "$COMMITS_COUNT" -eq "0" ]; then
echo "No new commits found on ${BRANCH_NAME} compared to main. Nothing to sync."
exit 0
fi

# 4. Extract package name for label. Package names can't contain a
# hyphen, so everything from the first one on is the version.
TEMP="${BRANCH_NAME#release-}"
PACKAGE_NAME="${TEMP%%-*}"

# 5. Create the PR directly
gh pr create \
--base "main" \
--head "${BRANCH_NAME}" \
--title "Sync ${BRANCH_NAME} to main" \
--body "This automated PR syncs the changes from the release branch ${BRANCH_NAME} back to the main branch." \
--label "override: batch-${PACKAGE_NAME}"
5 changes: 5 additions & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.14.4+2

* Adds an `in-flight-release-check` command, which reports whether an earlier
batch release for a package hasn't been merged back yet.

## 0.14.4+1

* Adds support for batch release of pre-1.0 packages.
Expand Down
10 changes: 2 additions & 8 deletions script/tool/lib/src/branches_for_batch_release_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class BranchesForBatchReleaseCommand extends PackageCommand {
return;
}

final pubspec = Pubspec.parse(package.pubspecFile.readAsStringSync());
final Pubspec pubspec = package.parsePubspec();
if (pubspec.version == null) {
printError('The package has no version specified.');
throw ToolExit(_kExitPackageMalformed);
Expand All @@ -96,7 +96,6 @@ class BranchesForBatchReleaseCommand extends PackageCommand {

await _createAndPushReleaseBranch(
git: repository,
package: package,
releaseBranchName: releaseBranchName,
remoteName: remoteName,
);
Expand Down Expand Up @@ -171,7 +170,6 @@ class BranchesForBatchReleaseCommand extends PackageCommand {
/// Throws a [ToolExit] if any of the steps fail.
Future<void> _createAndPushReleaseBranch({
required GitDir git,
required RepositoryPackage package,
required String releaseBranchName,
required String remoteName,
}) async {
Expand All @@ -189,11 +187,7 @@ class BranchesForBatchReleaseCommand extends PackageCommand {

await _pushBranch(git, remoteName, releaseBranchName);

final String? githubOutput = platform.environment['GITHUB_OUTPUT'];
if (githubOutput != null && githubOutput.isNotEmpty) {
final File file = package.directory.fileSystem.file(githubOutput);
file.writeAsStringSync('release_branch=$releaseBranchName\n', mode: io.FileMode.append);
}
writeGitHubActionsOutput('release_branch', releaseBranchName);
}

Future<void> _createHeadBranchAndCommit({
Expand Down
15 changes: 15 additions & 0 deletions script/tool/lib/src/common/package_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,21 @@ abstract class PackageCommand extends Command<void> {
return gitVersionFinder;
}

/// Appends `name=value` to the file that GitHub Actions reads step outputs
/// from, making it available to later steps as `steps.<id>.outputs.<name>`.
///
/// Does nothing when not running in GitHub Actions, where `GITHUB_OUTPUT` is
/// unset.
void writeGitHubActionsOutput(String name, String value) {
final String? githubOutput = platform.environment['GITHUB_OUTPUT'];
if (githubOutput == null || githubOutput.isEmpty) {
return;
}
packagesDir.fileSystem
.file(githubOutput)
.writeAsStringSync('$name=$value\n', mode: io.FileMode.append);
}

// Returns the names of packages that have been changed given a list of
// changed files.
//
Expand Down
Loading
Loading