Skip to content
Closed
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
17 changes: 17 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@
color: c2e0c6
description: Docs site / README

# --- Release / v1 routing ---
- name: backport/v1
color: 0e8a16
description: Merge to master and backport to release/v1
- name: target/v1
color: 1d76db
description: PR targets the release/v1 branch directly
- name: backported
color: c5def5
description: Cherry-picked onto release/v1
- name: backport-failed
color: b60205
description: Cherry-pick conflict — needs manual backport
- name: skip-changelog
color: ededed
description: Exclude from generated release notes

# --- Size ---
- name: size/XS
color: "3cbf00"
Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Backport to release/v1

# Cherry-picks a merged PR's commit straight onto release/v1 and pushes it —
# no intermediate PR. Fires when:
# - a PR labeled `backport/v1` is merged into master, or
# - a maintainer comments `/backport v1` on an already-merged PR.
# On a clean cherry-pick the commit is pushed directly. On conflict nothing is
# pushed; the source PR is labeled `backport-failed` and a comment asks for a
# manual cherry-pick.

on:
pull_request_target:
types: [closed]
issue_comment:
types: [created]

permissions:
contents: write
pull-requests: write
issues: write

jobs:
backport:
name: Backport
runs-on: ubuntu-latest
if: >
(github.event_name == 'pull_request_target'
&& github.event.pull_request.merged == true
&& contains(github.event.pull_request.labels.*.name, 'backport/v1'))
|| (github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& github.event.issue.state == 'closed'
&& startsWith(github.event.comment.body, '/backport v1')
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
steps:
- name: Resolve PR and merge commit
id: pr
uses: actions/github-script@v9
with:
github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const num = context.eventName === 'issue_comment'
? context.payload.issue.number
: context.payload.pull_request.number;

const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: num });

if (!pr.merged || !pr.merge_commit_sha) {
core.notice(`PR #${num} is not merged; nothing to backport.`);
core.setOutput('run', 'false');
return;
}
core.setOutput('run', 'true');
core.setOutput('number', String(num));
core.setOutput('sha', pr.merge_commit_sha);
core.setOutput('title', pr.title);

- name: Checkout
if: steps.pr.outputs.run == 'true'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: release/v1
token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}

- name: Cherry-pick onto release/v1
if: steps.pr.outputs.run == 'true'
id: pick
run: |
set -euo pipefail
SHA="${{ steps.pr.outputs.sha }}"
git config user.name "Floatpane Bot"
git config user.email "us@floatpane.com"
git fetch --no-tags origin "$SHA"

# A true merge commit has >1 parent and needs a mainline (-m 1);
# squash/rebase merges are a single commit cherry-picked as-is.
PARENTS=$(git rev-list --parents -n1 "$SHA" | wc -w)
PICK_ARGS="-x"
if [ "$PARENTS" -gt 2 ]; then PICK_ARGS="-x -m 1"; fi

if git cherry-pick $PICK_ARGS "$SHA"; then
git push origin HEAD:release/v1
echo "status=ok" >> "$GITHUB_OUTPUT"
else
git cherry-pick --abort || true
echo "status=conflict" >> "$GITHUB_OUTPUT"
fi

- name: Report result
if: steps.pr.outputs.run == 'true'
uses: actions/github-script@v9
with:
github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const issue_number = Number('${{ steps.pr.outputs.number }}');
const status = '${{ steps.pick.outputs.status }}';
const sha = '${{ steps.pr.outputs.sha }}';

if (status === 'ok') {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['backported'] });
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'backport-failed' });
} catch (e) { if (e.status !== 404) throw e; }
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `Cherry-picked \`${sha.substring(0, 7)}\` onto \`release/v1\`.`,
});
} else {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['backport-failed'] });
await github.rest.issues.createComment({
owner, repo, issue_number,
body: [
`Backport to \`release/v1\` hit a conflict — cherry-pick it manually:`,
'```bash',
'git fetch origin',
'git checkout release/v1',
`git cherry-pick -x ${sha}`,
'# resolve conflicts, then:',
'git push origin release/v1',
'```',
].join('\n'),
});
core.setFailed('Cherry-pick conflict.');
}
2 changes: 1 addition & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bot-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Set up Zig
uses: goto-bus-stop/setup-zig@v2
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ name: CI

on:
push:
branches: [master]
branches: [master, release/v1]
pull_request:
branches: [master]
branches: [master, release/v1]
merge_group:

jobs:
build:
Expand All @@ -18,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install system dependencies
if: runner.os == 'Linux'
Expand All @@ -41,7 +42,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev
Expand Down Expand Up @@ -73,7 +74,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Check go.mod and go.sum are tidy
run: |
Expand Down Expand Up @@ -229,7 +230,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v7
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ name: Integration

on:
push:
branches: [master]
branches: [master, release/v1]
pull_request:
branches: [master]
branches: [master, release/v1]
merge_group:

jobs:
imap-smtp:
Expand Down Expand Up @@ -34,7 +35,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev
Expand Down Expand Up @@ -68,14 +69,13 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ["1.26.3"]
steps:
- uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
go-version: "1.26.4"

- name: Install system deps (linux)
if: runner.os == 'Linux'
Expand All @@ -94,7 +94,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.26.3"
go-version: "1.26.4"

- name: Set up Zig
uses: goto-bus-stop/setup-zig@v2
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/pr-target-labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: PR Target Labeler

# Decides where a PR is destined and reflects it with labels:
# (no label) -> master only (default)
# backport/v1 -> merge to master AND backport to release/v1 ("both")
# target/v1 -> the PR already targets release/v1 directly (v1 only)
#
# Maintainers steer routing with comment commands on the PR:
# /backport v1 add the backport/v1 label
# /no-backport v1 remove it
# The actual cherry-pick is performed by backport.yml once the PR is merged.

on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
issue_comment:
types: [created]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
# Auto-label based on the PR's base branch and an explicit "backport" hint
# in the title/body. Never removes backport/v1 (a maintainer may have set it).
auto:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
const issue_number = pr.number;
const base = pr.base.ref;
const have = new Set(pr.labels.map(l => l.name));
const add = [];

if (base === 'release/v1') {
if (!have.has('target/v1')) add.push('target/v1');
} else {
const text = `${pr.title}\n${pr.body || ''}`;
if (/\bback[- ]?port\b/i.test(text) && !have.has('backport/v1')) {
add.push('backport/v1');
}
}

if (add.length) {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: add });
console.log(`added: ${add.join(', ')}`);
}

# Comment commands. Restricted to users with write access to the repo.
command:
if: >
github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& (startsWith(github.event.comment.body, '/backport')
|| startsWith(github.event.comment.body, '/no-backport'))
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const body = context.payload.comment.body.trim();
const assoc = context.payload.comment.author_association;
const commenter = context.payload.comment.user.login;

if (!['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc)) {
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `Sorry @${commenter}, only maintainers can change backport routing.`,
});
return;
}

// Only the v1 target is supported for now.
if (!/\bv1\b/.test(body)) {
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `Usage: \`/backport v1\` or \`/no-backport v1\`.`,
});
return;
}

const remove = body.startsWith('/no-backport');
if (remove) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'backport/v1' });
} catch (e) {
if (e.status !== 404) throw e;
}
} else {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['backport/v1'] });
}

await github.rest.reactions.createForIssueComment({
owner, repo, comment_id: context.payload.comment.id, content: 'rocket',
});
Loading
Loading