-
Notifications
You must be signed in to change notification settings - Fork 9
206 lines (171 loc) · 8.56 KB
/
create-hotfix-pr.yml
File metadata and controls
206 lines (171 loc) · 8.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
name: Create Hotfix PR
on:
pull_request:
types:
- closed # Trigger when a PR is closed
branches:
- 'release-candidate/*' # Trigger for any branch starting with 'release-candidate/'
jobs:
create_hotfix_pr:
# Only run this job if the PR was actually merged
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write # To push branches and commit changes
pull-requests: write # To create the pull request
env:
BOT_NAME: "flow360-auto-hotfix-bot" # Name for the git committer
BOT_EMAIL: "flow360-auto-hotfix-bot@users.noreply.github.com" # Email for the git committer
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config user.name "${{ env.BOT_NAME }}"
git config user.email "${{ env.BOT_EMAIL }}"
- name: Get Merged Commit SHA (Assuming Squash Merge)
id: get_commit
# This SHA reliably points to the single commit if squash-and-merge was used.
# If using merge commits, this SHA points to the merge commit itself,
# which might be harder to cherry-pick cleanly.
run: |
echo "sha=${{ github.event.pull_request.merge_commit_sha }}" >> $GITHUB_OUTPUT
echo "branch_name=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
echo "base_branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
- name: Get Hotfix Target Branches
id: get_target_branches
run: |
PR_BASE_BRANCH="${{ steps.get_commit.outputs.base_branch }}"
echo "PR was merged into: $PR_BASE_BRANCH"
TARGET_BRANCHES=("main") # Always target main for hotfix
# Get all remote 'release-candidate' branches with version pattern as YY.I or YY.II
ALL_RELEASE_CANDIDATE_BRANCHES=$(git branch -r | grep 'origin/release-candidate/' | sed 's/.*origin\///' | grep -E '^release-candidate/[0-9]{2}\.[0-9]{1,2}$' | sort -V)
echo "Found release-candidate branches: $ALL_RELEASE_CANDIDATE_BRANCHES"
# Determine the version of the current PR's base branch
# Example: release-candidate/25.5 -> 25.05 (for proper numerical comparison)
PR_BASE_VERSION=$(echo "$PR_BASE_BRANCH" | sed -n 's/release-candidate\/\([0-9]\{2\}\)\.\([0-9]\{1,2\}\)/\1.\2/p')
# Handle cases like 25.5 vs 25.05 for comparison
if [[ "$PR_BASE_VERSION" =~ ^[0-9]{2}\.[0-9]$ ]]; then
PR_BASE_VERSION=$(echo "$PR_BASE_VERSION" | awk '{printf "%s%02d", substr($1,1,3), substr($1,4)}')
fi
if [[ -n "$PR_BASE_VERSION" ]]; then
echo "Base branch version: $PR_BASE_VERSION"
for branch in $ALL_RELEASE_CANDIDATE_BRANCHES; do
# Extract version for comparison
BRANCH_VERSION=$(echo "$branch" | sed -n 's/release-candidate\/\([0-9]\{2\}\)\.\([0-9]\{1,2\}\)/\1.\2/p')
if [[ -n "$BRANCH_VERSION" ]]; then
if [[ "$BRANCH_VERSION" =~ ^[0-9]{2}\.[0-9]$ ]]; then
BRANCH_VERSION=$(echo "$BRANCH_VERSION" | awk '{printf "%s%02d", substr($1,1,3), substr($1,4)}')
fi
# Compare versions
if (( $(echo "$BRANCH_VERSION > $PR_BASE_VERSION" | bc -l) )); then
TARGET_BRANCHES+=("$branch")
echo "Adding $branch (version $BRANCH_VERSION) as it's higher than $PR_BASE_BRANCH (version $PR_BASE_VERSION)"
fi
fi
done
fi
# Prepare output for next step
HOTFIX_BRANCHES_JSON=$(printf '%s\n' "${TARGET_BRANCHES[@]}" | jq -R . | jq -s .)
DELIMITER=$(uuidgen) # Generate a unique delimiter
echo "HOTFIX_BRANCHES<<$DELIMITER" >> $GITHUB_OUTPUT
echo "$HOTFIX_BRANCHES_JSON" >> $GITHUB_OUTPUT
echo "$DELIMITER" >> $GITHUB_OUTPUT
echo "Calculated hotfix target branches: ${TARGET_BRANCHES[@]}"
- name: Define Branch Names
id: define_branches
run: |
ORIGINAL_PR_NUMBER=${{ github.event.pull_request.number }}
# Convert JSON array to space-separated list
BRANCHES=$(echo '${{ steps.get_target_branches.outputs.HOTFIX_BRANCHES }}' | jq -r '.[]')
# Create outputs for all branches
branch_list=""
for branch in $BRANCHES; do
new_branch="hotfix-${branch//\//-}-pr${ORIGINAL_PR_NUMBER}" # Replace '/' with '-' for new branch name
branch_list+="${branch}:${new_branch} "
done
# Remove trailing space and store the list
branch_list="${branch_list% }"
echo "branch_pairs=${branch_list}" >> $GITHUB_OUTPUT
- name: Create and Process Hotfix Branches
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
# Read branch pairs and process each one
IFS=' ' read -r -a PAIRS <<< "${{ steps.define_branches.outputs.branch_pairs }}"
for pair in "${PAIRS[@]}"; do
IFS=':' read -r base_branch new_branch <<< "$pair"
echo "Processing hotfix for branch: $base_branch"
# Create and switch to new branch
git checkout "$base_branch"
git pull origin "$base_branch"
git checkout -b "$new_branch"
# Attempt cherry-pick
if ! git cherry-pick ${{ steps.get_commit.outputs.sha }} --empty=keep; then
echo "Cherry-pick encountered conflicts for $base_branch, attempting to resolve..."
git add -A
# Check if there are actual changes staged after conflict resolution attempt
if git diff --cached --quiet; then
echo "Conflict resolution resulted in no changes. Aborting cherry-pick."
git cherry-pick --abort
continue # Skip PR creation if no changes after resolution
fi
git cherry-pick --continue
if [ $? -eq 0 ]; then
echo "Successfully resolved conflicts for $base_branch"
had_conflicts="true"
else
echo "Failed to resolve conflicts for $base_branch"
git cherry-pick --abort
continue
fi
else
echo "Cherry-pick successful for $base_branch"
had_conflicts="false"
fi
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD)
if [ -z "$CHANGED_FILES" ]; then
echo "Cherry-pick resulted in no changes, skipping PR creation for $base_branch"
git reset --hard HEAD^ # Discard the empty commit if one was created by cherry-pick
continue
fi
echo "Changed files:"
echo "$CHANGED_FILES"
# Push branch
if git push origin "$new_branch"; then
echo "Successfully pushed branch $new_branch"
# Create PR
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_URL="${{ github.event.pull_request.html_url }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
# Adjust PR title based on the target branch
if [[ "$base_branch" == "main" ]]; then
HOTFIX_PR_TITLE="[Hotfix Main]: $PR_TITLE"
else
base_branch_name=${base_branch#"release-candidate/"}
HOTFIX_PR_TITLE="[Hotfix $base_branch_name]: $PR_TITLE"
fi
PR_BODY="Hotfix of PR #${PR_NUMBER} (${PR_URL}) to the \`${base_branch}\` branch.
Hey @${PR_AUTHOR}, please review this hotfix PR created from your original PR."
# Add conflict warning if needed
if [[ "$had_conflicts" == "true" ]]; then
PR_BODY="${PR_BODY}
### ⚠️ **Note:** This PR had conflicts with the base branch and was resolved automatically. Please review the changes carefully."
fi
# Create PR using gh cli
gh pr create \
--title "$HOTFIX_PR_TITLE" \
--body "$PR_BODY" \
--head "$new_branch" \
--base "$base_branch" \
--label "auto-hotfix" \
--assignee "$PR_AUTHOR" \
--draft
else
echo "Failed to push branch $new_branch"
fi
done