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
10 changes: 9 additions & 1 deletion internal/generate/hotfix.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,15 @@ func (g *HotfixGenerator) writeApplyJob(sb *strings.Builder) {
fmt.Fprintf(sb, " --label %s \\\n", hotfixLabel)
sb.WriteString(" --title \"hotfix(${TARGET_ENV}): cherry-pick ${SHORT_SHA}\" \\\n")
sb.WriteString(" --body \"$BODY\"\n")
sb.WriteString(" gh pr merge --auto --squash \"$BRANCH\"\n")
// Prefer auto-merge so required checks gate the merge on protected
// branches. GitHub rejects enablePullRequestAutoMerge when the target
// branch has no protection rule, so fall back to an immediate squash
// merge. The `if !` guard keeps the failing attempt from tripping
// `set -e` and aborting the step.
sb.WriteString(" if ! gh pr merge --auto --squash \"$BRANCH\"; then\n")
sb.WriteString(" echo \"::notice::auto-merge unavailable (branch likely unprotected); merging directly\"\n")
sb.WriteString(" gh pr merge --squash --delete-branch \"$BRANCH\"\n")
sb.WriteString(" fi\n")
sb.WriteString(" else\n")
sb.WriteString(" echo \"::warning::Cherry-pick conflicted; opening resolution PR for manual resolve\"\n")
sb.WriteString(" CONFLICTS=$(git diff --name-only --diff-filter=U)\n")
Expand Down
20 changes: 20 additions & 0 deletions internal/generate/hotfix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,26 @@ func TestHotfixGenerator_CleanPath(t *testing.T) {
assert.Contains(t, content, "gh pr merge --auto")
}

// TestHotfixGenerator_CleanPathMergeFallback guards the regression where the
// clean cherry-pick path merged with `gh pr merge --auto`, which calls GitHub's
// enablePullRequestAutoMerge mutation. GitHub rejects that mutation on a branch
// with no protection rule, so the hotfix step exited non-zero under `set -e` and
// the PR was left open on unprotected env branches. The clean path must prefer
// auto-merge but fall back to an immediate squash merge when auto-merge cannot
// be enabled, and the `--auto` attempt must be guarded so its failure does not
// abort the step.
func TestHotfixGenerator_CleanPathMergeFallback(t *testing.T) {
gen := NewHotfixGenerator(threeEnvHotfixConfig(), "")
content, err := gen.Generate()
require.NoError(t, err)

// The auto-merge attempt is guarded by `if !` so a failure on an
// unprotected branch does not trip `set -e` and abort the step.
assert.Contains(t, content, "if ! gh pr merge --auto --squash \"$BRANCH\"; then")
// The fallback merges immediately when auto-merge is unavailable.
assert.Contains(t, content, "gh pr merge --squash --delete-branch \"$BRANCH\"")
}

// TestHotfixGenerator_SeedsLabels guards the regression where the apply job ran
// `gh pr create --label cascade-hotfix[-conflict]` without ever creating those
// labels. `gh pr create --label X` hard-fails when label X does not exist, so
Expand Down
Loading