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
38 changes: 23 additions & 15 deletions .github/workflows/create-hotfix-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,18 @@ jobs:

# 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..."
echo "Cherry-pick failed for $base_branch, checking for merge conflicts..."
CONFLICT_FILES=$(git diff --name-only --diff-filter=U)

if [[ -z "$CONFLICT_FILES" ]]; then
echo "::error::Cherry-pick failed for $base_branch without merge conflicts."
git status --short
exit 1
fi

echo "Cherry-pick encountered conflicts for $base_branch:"
echo "$CONFLICT_FILES"
had_conflicts="true"
git add -A
# Check if there are actual changes staged after conflict resolution attempt
if git diff --cached --quiet; then
Expand All @@ -147,15 +158,7 @@ jobs:
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
echo "Continuing with conflict hotfix PR for $base_branch"
else
echo "Cherry-pick successful for $base_branch"
fi
Expand Down Expand Up @@ -250,14 +253,19 @@ jobs:
if git push origin "$new_branch"; then
echo "Successfully pushed branch $new_branch"

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."
PR_BODY=$(printf '%s\n' \
"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."
PR_BODY=$(printf '%s\n\n%s\n\n%s\n%s\n\n%s\n```\n%s\n```' \
"$PR_BODY" \
"### Manual conflict resolution required" \
"This hotfix PR contains cherry-pick conflicts against \`${base_branch}\`." \
"Resolve the conflict markers in this branch and push a follow-up commit before merging." \
"**Conflicted files:**" \
"$CONFLICT_FILES")
fi

# Create PR using gh cli
Expand Down
58 changes: 45 additions & 13 deletions .github/workflows/hotfix-tracking-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,65 @@ jobs:
permissions:
pull-requests: read
steps:
- name: Fail on auto-hotfix placeholder files
- name: Skip non-auto-hotfix PRs
if: ${{ !contains(github.event.pull_request.labels.*.name, 'auto-hotfix') }}
run: echo "PR does not have auto-hotfix label. Passing."

- name: Fail on placeholder files and unresolved conflict markers
if: contains(github.event.pull_request.labels.*.name, 'auto-hotfix')
uses: actions/github-script@v7
with:
script: |
const labels = (context.payload.pull_request.labels || []).map((label) => label.name);
if (!labels.includes("auto-hotfix")) {
core.info("PR does not have auto-hotfix label. Passing.");
return;
}

const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
per_page: 100,
});

const placeholderFiles = files
.map((file) => file.filename)
const fileNames = files.map((file) => file.filename);
const placeholderFiles = fileNames
.filter((filename) => filename.startsWith(".github/hotfix-manual/"));

if (placeholderFiles.length === 0) {
core.info("auto-hotfix PR has no hotfix tracking placeholder files. Passing.");
if (placeholderFiles.length > 0) {
core.error(`Found hotfix tracking placeholder file(s): ${placeholderFiles.join(", ")}`);
core.setFailed(
"Manual hotfix tracking PR detected (.github/hotfix-manual/*). Close this PR after manually cherry-picking the changes. Do NOT merge."
);
return;
}

const conflictStartPattern = /^<{7,}(?:$| )/;
const conflictMiddlePattern = /^={7,}$/;
const conflictEndPattern = /^>{7,}(?:$| )/;

const filesWithoutPatch = fileNames.filter((filename, index) => typeof files[index].patch !== "string");
if (filesWithoutPatch.length > 0) {
core.warning(`Could not inspect diff patch for: ${filesWithoutPatch.join(", ")}`);
}

const conflictedFiles = files
.filter((file) => typeof file.patch === "string")
.filter((file) => {
const addedLines = file.patch
.split("\n")
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
.map((line) => line.slice(1));

return (
addedLines.some((line) => conflictStartPattern.test(line)) &&
addedLines.some((line) => conflictMiddlePattern.test(line)) &&
addedLines.some((line) => conflictEndPattern.test(line))
);
})
.map((file) => file.filename);

if (conflictedFiles.length === 0) {
core.info("No unresolved conflict markers found in added lines for auto-hotfix PR files.");
return;
}

core.error(`Found hotfix tracking placeholder file(s): ${placeholderFiles.join(", ")}`);
core.error(`Found unresolved conflict markers in: ${conflictedFiles.join(", ")}`);
core.setFailed(
"Manual hotfix tracking PR detected (.github/hotfix-manual/*). Close this PR after manually cherry-picking the changes. Do NOT merge."
"Auto-hotfix PR still contains unresolved conflict markers in added lines. Resolve them in this branch and push a follow-up commit before merging."
);
Loading