diff --git a/e2e/scenarios/hotfix/hotfix-generation-threshold.yaml b/e2e/scenarios/hotfix/hotfix-generation-threshold.yaml index 8f516805..918e9f47 100644 --- a/e2e/scenarios/hotfix/hotfix-generation-threshold.yaml +++ b/e2e/scenarios/hotfix/hotfix-generation-threshold.yaml @@ -39,7 +39,7 @@ steps: - "target_env:" - "pull_request:" - "types: [closed]" - - "'env/*'" + - "'env/**'" - "format('hotfix-finalize-{0}', github.repository)" - "format('hotfix-{0}', github.event.inputs.target_env)" - " plan:" diff --git a/internal/generate/hotfix.go b/internal/generate/hotfix.go index 811c262b..a5264554 100644 --- a/internal/generate/hotfix.go +++ b/internal/generate/hotfix.go @@ -238,7 +238,12 @@ func (g *HotfixGenerator) writeTriggers(sb *strings.Builder) { sb.WriteString(" pull_request:\n") sb.WriteString(" types: [closed]\n") sb.WriteString(" branches:\n") - sb.WriteString(" - 'env/*'\n") + // Double-star matches env branches at any depth. A GitHub Actions branch + // glob `*` stops at a slash, so a single-star `env/*` never matches a + // per-component branch like env/api/staging and the closed-PR finalize + // would never fire for a multi-component pipeline. `env/**` matches both + // the single-component branch (env/staging) and the per-component branch. + sb.WriteString(" - 'env/**'\n") sb.WriteString("\n") } diff --git a/internal/generate/hotfix_test.go b/internal/generate/hotfix_test.go index 6914a7ea..e28e3595 100644 --- a/internal/generate/hotfix_test.go +++ b/internal/generate/hotfix_test.go @@ -75,7 +75,7 @@ func TestHotfixGenerator_Triggers(t *testing.T) { assert.Contains(t, content, "pull_request:") assert.Contains(t, content, "types: [closed]") assert.Contains(t, content, "branches:") - assert.Contains(t, content, "'env/*'") + assert.Contains(t, content, "'env/**'") // Dispatch inputs. assert.Contains(t, content, "commit:") @@ -89,6 +89,25 @@ func TestHotfixGenerator_Triggers(t *testing.T) { assert.NotContains(t, content, "- dev") } +// TestHotfixGenerator_FinalizeTriggerMatchesNestedEnvBranches guards that the +// finalize pull_request trigger's branch filter matches multi-component env +// branches. Per-component env branches carry two path segments +// (env/api/staging), and a GitHub Actions `*` glob stops at a slash, so a +// single-star `env/*` filter never matches them and the closed-PR finalize +// never fires. A double-star `env/**` matches any depth, covering both the +// single-component branch (env/staging) and the per-component branch +// (env/api/staging). +func TestHotfixGenerator_FinalizeTriggerMatchesNestedEnvBranches(t *testing.T) { + gen := NewHotfixGenerator(threeEnvHotfixConfig(), "") + content, err := gen.Generate() + require.NoError(t, err) + + assert.Contains(t, content, " - 'env/**'", + "finalize trigger must use env/** so it matches per-component env branches like env/api/staging") + assert.NotContains(t, content, " - 'env/*'\n", + "finalize trigger must not use a single-star env/* filter, which a GitHub Actions glob will not match across the slash of env/api/staging") +} + // TestHotfixGenerator_CommitInputAcceptsMultiple guards that the dispatch // `commit` input documents comma-delimited multi-commit hotfixes, so an operator // can hand the workflow a stack of trunk fixes to cherry-pick as one chain. diff --git a/internal/generate/testdata/byte_identical_baseline/.github__workflows__cascade-hotfix.yaml.golden b/internal/generate/testdata/byte_identical_baseline/.github__workflows__cascade-hotfix.yaml.golden index 4b80dbf3..ae7d6e15 100644 --- a/internal/generate/testdata/byte_identical_baseline/.github__workflows__cascade-hotfix.yaml.golden +++ b/internal/generate/testdata/byte_identical_baseline/.github__workflows__cascade-hotfix.yaml.golden @@ -40,7 +40,7 @@ on: pull_request: types: [closed] branches: - - 'env/*' + - 'env/**' permissions: contents: read