Skip to content

Commit 4e60dd3

Browse files
vdemeestertekton-robot
authored andcommitted
feat(slash): add /rebase command to rebase PRs against base branch
Add a new /rebase slash command that rebases a PR's head branch against its base branch and force pushes the result. This is useful for keeping PRs up to date without manual intervention. Features: - Validates PR is open and not from a fork - Rebases head branch onto base branch - Uses --force-with-lease for safer force push - Reports conflicts if rebase fails - Indicates if branch is already up to date The workflow is designed to be reusable by other Tekton projects. Signed-off-by: Vincent Demeester <vdemeest@redhat.com>
1 parent 01346a0 commit 4e60dd3

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Rebase Command Workflow
2+
#
3+
# This workflow is triggered by the /rebase slash command from the slash.yml workflow.
4+
# It rebases a pull request branch against its base branch and force pushes the result.
5+
#
6+
# Usage: Comment `/rebase` on a pull request
7+
#
8+
# Security Notes:
9+
# - Only users with "write" permission can trigger this command (enforced in slash.yml)
10+
# - Uses CHATOPS_TOKEN to push to branches
11+
# - The rebase is performed on the PR's head branch against its base branch
12+
#
13+
# To use this in a repo:
14+
#
15+
# name: Rebase Command
16+
# on:
17+
# repository_dispatch:
18+
# types: [rebase-command]
19+
#
20+
# jobs:
21+
# rebase:
22+
# name: Rebase PR
23+
# uses: tektoncd/plumbing/.github/workflows/_rebase-command.yaml@main
24+
# secrets:
25+
# CHATOPS_TOKEN: ${{ secrets.CHATOPS_TOKEN }}
26+
27+
name: Rebase Command
28+
29+
on:
30+
workflow_call:
31+
secrets:
32+
CHATOPS_TOKEN:
33+
required: true
34+
35+
permissions:
36+
contents: write
37+
pull-requests: write
38+
issues: write
39+
40+
jobs:
41+
rebase:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Add reaction to trigger comment
45+
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
46+
with:
47+
token: ${{ secrets.CHATOPS_TOKEN }}
48+
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
49+
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
50+
reactions: "+1"
51+
52+
- name: Validate PR state
53+
id: validate
54+
env:
55+
GH_TOKEN: ${{ secrets.CHATOPS_TOKEN }}
56+
PR_NUMBER: ${{ github.event.client_payload.pull_request.number }}
57+
run: |
58+
# Get PR information
59+
echo "Fetching PR #$PR_NUMBER information..."
60+
PR_DATA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json state,headRefName,baseRefName,headRepository,headRepositoryOwner) || {
61+
echo "error=true" >> $GITHUB_OUTPUT
62+
echo "message=Failed to fetch PR information" >> $GITHUB_OUTPUT
63+
exit 0
64+
}
65+
66+
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
67+
if [ "$PR_STATE" != "OPEN" ]; then
68+
echo "error=true" >> $GITHUB_OUTPUT
69+
echo "message=PR #$PR_NUMBER is not open (state: $PR_STATE). Can only rebase open PRs." >> $GITHUB_OUTPUT
70+
exit 0
71+
fi
72+
73+
HEAD_REF=$(echo "$PR_DATA" | jq -r '.headRefName')
74+
BASE_REF=$(echo "$PR_DATA" | jq -r '.baseRefName')
75+
HEAD_REPO_OWNER=$(echo "$PR_DATA" | jq -r '.headRepositoryOwner.login')
76+
HEAD_REPO_NAME=$(echo "$PR_DATA" | jq -r '.headRepository.name')
77+
78+
# Check if this is a fork
79+
REPO_OWNER="${{ github.repository_owner }}"
80+
if [ "$HEAD_REPO_OWNER" != "$REPO_OWNER" ]; then
81+
echo "error=true" >> $GITHUB_OUTPUT
82+
echo "message=Cannot rebase PRs from forks. The PR branch must be in the same repository." >> $GITHUB_OUTPUT
83+
exit 0
84+
fi
85+
86+
echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT
87+
echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT
88+
echo "error=false" >> $GITHUB_OUTPUT
89+
90+
echo "PR #$PR_NUMBER: rebasing '$HEAD_REF' onto '$BASE_REF'"
91+
92+
- name: Comment on validation error
93+
if: steps.validate.outputs.error == 'true'
94+
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
95+
with:
96+
token: ${{ secrets.CHATOPS_TOKEN }}
97+
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
98+
issue-number: ${{ github.event.client_payload.github.payload.issue.number }}
99+
body: |
100+
❌ **Rebase failed**: ${{ steps.validate.outputs.message }}
101+
102+
- name: Checkout repository
103+
if: steps.validate.outputs.error == 'false'
104+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
105+
with:
106+
token: ${{ secrets.CHATOPS_TOKEN }}
107+
fetch-depth: 0
108+
ref: ${{ steps.validate.outputs.head_ref }}
109+
110+
- name: Perform rebase
111+
if: steps.validate.outputs.error == 'false'
112+
id: rebase
113+
continue-on-error: true
114+
env:
115+
GH_TOKEN: ${{ secrets.CHATOPS_TOKEN }}
116+
HEAD_REF: ${{ steps.validate.outputs.head_ref }}
117+
BASE_REF: ${{ steps.validate.outputs.base_ref }}
118+
PR_NUMBER: ${{ github.event.client_payload.pull_request.number }}
119+
run: |
120+
set -e
121+
122+
OUTPUT_FILE=$(mktemp)
123+
124+
{
125+
echo "🤖 Starting rebase process..."
126+
127+
git config user.name "Tekton Bot"
128+
git config user.email "tekton-bot@users.noreply.github.com"
129+
130+
# Fetch the base branch
131+
echo "Fetching base branch: $BASE_REF..."
132+
git fetch origin "$BASE_REF" || {
133+
echo "❌ ERROR: Failed to fetch base branch '$BASE_REF'"
134+
exit 1
135+
}
136+
137+
# Get current HEAD for comparison
138+
OLD_HEAD=$(git rev-parse HEAD)
139+
echo "Current HEAD: $OLD_HEAD"
140+
141+
# Perform the rebase
142+
echo "Rebasing '$HEAD_REF' onto 'origin/$BASE_REF'..."
143+
if ! git rebase "origin/$BASE_REF"; then
144+
echo "❌ ERROR: Rebase failed due to conflicts"
145+
echo ""
146+
echo "Conflicting files:"
147+
git diff --name-only --diff-filter=U
148+
git rebase --abort
149+
exit 1
150+
fi
151+
152+
NEW_HEAD=$(git rev-parse HEAD)
153+
echo "New HEAD: $NEW_HEAD"
154+
155+
# Check if anything changed
156+
if [ "$OLD_HEAD" = "$NEW_HEAD" ]; then
157+
echo "ℹ️ Branch is already up to date with '$BASE_REF'"
158+
echo "already_up_to_date=true" >> $GITHUB_OUTPUT
159+
else
160+
# Force push the rebased branch
161+
echo "Force pushing rebased branch..."
162+
git push --force-with-lease origin "$HEAD_REF" || {
163+
echo "❌ ERROR: Failed to push rebased branch. Someone may have pushed new commits."
164+
exit 1
165+
}
166+
167+
COMMIT_COUNT=$(git rev-list --count "origin/$BASE_REF".."$NEW_HEAD")
168+
echo "commits=$COMMIT_COUNT" >> $GITHUB_OUTPUT
169+
echo "already_up_to_date=false" >> $GITHUB_OUTPUT
170+
fi
171+
172+
echo "✅ Rebase completed successfully!"
173+
} 2>&1 | tee "$OUTPUT_FILE"
174+
175+
EXIT_CODE=${PIPESTATUS[0]}
176+
177+
# Save output for use in comments
178+
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
179+
echo "output<<$EOF" >> $GITHUB_OUTPUT
180+
cat "$OUTPUT_FILE" >> $GITHUB_OUTPUT
181+
echo "$EOF" >> $GITHUB_OUTPUT
182+
183+
rm -f "$OUTPUT_FILE"
184+
exit $EXIT_CODE
185+
186+
- name: Comment on success (up to date)
187+
if: steps.validate.outputs.error == 'false' && steps.rebase.outcome == 'success' && steps.rebase.outputs.already_up_to_date == 'true'
188+
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
189+
with:
190+
token: ${{ secrets.CHATOPS_TOKEN }}
191+
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
192+
issue-number: ${{ github.event.client_payload.github.payload.issue.number }}
193+
body: |
194+
ℹ️ **Already up to date!**
195+
196+
The branch `${{ steps.validate.outputs.head_ref }}` is already up to date with `${{ steps.validate.outputs.base_ref }}`.
197+
198+
- name: Comment on success (rebased)
199+
if: steps.validate.outputs.error == 'false' && steps.rebase.outcome == 'success' && steps.rebase.outputs.already_up_to_date == 'false'
200+
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
201+
with:
202+
token: ${{ secrets.CHATOPS_TOKEN }}
203+
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
204+
issue-number: ${{ github.event.client_payload.github.payload.issue.number }}
205+
body: |
206+
✅ **Rebase successful!**
207+
208+
The branch `${{ steps.validate.outputs.head_ref }}` has been rebased onto `${{ steps.validate.outputs.base_ref }}` and force pushed.
209+
210+
**Commits**: ${{ steps.rebase.outputs.commits }} commit(s) on top of `${{ steps.validate.outputs.base_ref }}`
211+
212+
- name: Comment on failure
213+
if: steps.validate.outputs.error == 'false' && steps.rebase.outcome == 'failure'
214+
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
215+
with:
216+
token: ${{ secrets.CHATOPS_TOKEN }}
217+
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
218+
issue-number: ${{ github.event.client_payload.github.payload.issue.number }}
219+
body: |
220+
❌ **Rebase failed!**
221+
222+
The rebase of `${{ steps.validate.outputs.head_ref }}` onto `${{ steps.validate.outputs.base_ref }}` failed.
223+
224+
**Output:**
225+
```
226+
${{ steps.rebase.outputs.output }}
227+
```
228+
229+
**Next steps:**
230+
- Check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details
231+
- If there are conflicts, you'll need to rebase manually and resolve them

.github/workflows/_slash.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# - /retest: re-run failed GitHub actions associated with the
1616
# caller of this workflow
1717
# - /cherry-pick: cherry-pick a merged PR to one or more target branches
18+
# - /rebase: rebase a PR branch against its base branch
1819
#
1920
# When a command is recognised, the rocket and eyes emojis are added
2021
#
@@ -54,5 +55,11 @@ jobs:
5455
"permission": "write",
5556
"issue_type": "pull-request",
5657
"repository": "${{ github.repository }}"
58+
},
59+
{
60+
"command": "rebase",
61+
"permission": "write",
62+
"issue_type": "pull-request",
63+
"repository": "${{ github.repository }}"
5764
}
5865
]

0 commit comments

Comments
 (0)