fix(ci): replace PR promotion with squash push in promote-to-lts#1177
fix(ci): replace PR promotion with squash push in promote-to-lts#1177castrojo merged 2 commits intoublue-os:mainfrom
Conversation
- Replace gh pr create approach with direct squash merge push - Add pre-flight check: fail if lts has commits not in main - Use git merge --squash + single commit to keep lts history linear - No merge commits means no embedded cross-branch history pollution - workflow_dispatch trigger remains as the human approval gate - Add NEVER commit directly to lts rule to AGENTS.md - Update promote-to-lts.yml workflow description in AGENTS.md The previous PR-based approach created merge commits that embedded main's full history (including old circular lts→main references from ffa30fe) into lts on every promotion. Each promotion deepened the diamond graph. Squash merge creates one flat commit per promotion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Documentation Updates 4 document(s) were updated by changes in this PR: Bluefin Newsletter 2.0View Changes@@ -1,7 +1,7 @@
## Recent Completed Work
| Date Completed | Task | Overview | Impact |
|---|---|---|---|
-| 2026-03-02 | Prevent Branch Pollution with Manual LTS Promotion | A comprehensive three-layer defense was implemented to prevent branch pollution. **Layer 1:** The automated Pull app (`.github/pull.yml`) was removed and replaced with a manual `.github/workflows/promote-to-lts.yml` workflow triggered via workflow_dispatch, requiring manual operator approval for promotions. **Layer 2:** Renovate configuration updated with `baseBranchPatterns: ["main"]` to restrict automated PRs to the main branch only, preventing dependency updates on lts. **Layer 3:** Added `lts` to push triggers in 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) to enable validation builds that catch accidental merges. | Protects production lts branch from automated updates and prevents reverse merges. Ensures controlled, deliberate promotions from main to lts via explicit human action. Validation builds provide safety net against accidental merges without publishing. [Details](https://github.com/ublue-os/bluefin-lts/pull/1152) |
+| 2026-03-02 | Prevent Branch Pollution with Manual LTS Promotion | A comprehensive three-layer defense was implemented to prevent branch pollution caused by merge commits and circular history. **Layer 1:** The automated Pull app (`.github/pull.yml`) was removed and replaced with a manual `.github/workflows/promote-to-lts.yml` workflow triggered via workflow_dispatch. The workflow performs a pre-flight divergence check that blocks promotion if lts has commits not in main, then performs a squash push from main to lts after validation. All changes must land in main first; direct commits to lts are prohibited. This replaced the previous PR-based approach that created merge commits and polluted the git tree. **Layer 2:** Renovate configuration updated with `baseBranchPatterns: ["main"]` to restrict automated PRs to the main branch only, preventing dependency updates on lts. **Layer 3:** Added `lts` to push triggers in 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) to enable validation builds that catch accidental merges. | Protects production lts branch from automated updates and git tree pollution. Ensures controlled, deliberate promotions from main to lts via explicit human action with squash merge strategy for linear history. Validation builds provide safety net against accidental merges without publishing. [Details](https://github.com/ublue-os/bluefin-lts/pull/1152) |
| 2026-02-12 | Simplify Renovate Automerge Configuration | Renovate automerge configuration refactored from an allowlist approach to a universal pattern-based approach. All digest/pin/pinDigest updates across all managers (Dockerfile, GitHub Actions, regex/Justfile) are enabled for automerge, replacing fragmented per-container rules. GitHub Actions minor/patch version bumps also automerge. NVIDIA driver digests remain manual for compatibility review. | Reduces manual PR review for all dependency digest updates with simpler, more comprehensive automation rules. Improves security posture and reduces maintenance overhead. [Details](https://github.com/ublue-os/bluefin-lts/pull/1105) |
| 2026-01-31 | Add ibus-chewing to Bluefin LTS for zh_TW | The ibus-chewing input method is now included in Bluefin LTS, matching the default for the zh_TW locale in Bluefin Stable. This resolves issues where Chewing was configured but not available when installing from older ISOs, and improves the Traditional Chinese typing experience out of the box. | Ensures consistent and functional Traditional Chinese input for zh_TW users on both Stable and LTS. Reduces manual configuration and improves i18n parity. [Details](https://github.com/ublue-os/bluefin-lts/pull/1076) |
| 2025-12-16 | Reintroduce Renovate Automation | The Renovate dependency automation was restored. The `.github/renovate.json5` configuration was updated to include `ghcr.io/projectbluefin/common` in the automerge dependencies, ensuring that updates to this key dependency are now automatically merged. | Keeps dependencies up to date with less manual intervention, improving security and reliability. [Details](https://github.com/ublue-os/bluefin/pull/3853) |
@@ -24,7 +24,7 @@
## Summary Table
| Date | Change Summary |
|------------|---------------|
-| 2026-03-02 | Renovate restricted to main branch; Pull app replaced with manual promotion workflow; validation builds added to lts branch |
+| 2026-03-02 | Renovate restricted to main branch; Pull app replaced with squash-push promotion workflow with pre-flight divergence check; validation builds added to lts branch |
| 2026-02-12 | Renovate automerge refactored from allowlist to universal pattern-based rules covering all digest/pin updates |
| 2026-01-31 | ibus-chewing input method added to Bluefin LTS for zh_TW locale parity |
| 2025-12-16 | Renovate automation restored for `ghcr.io/projectbluefin/common` |Bluefin OSView Changes@@ -101,14 +101,16 @@
**Promotion Process:**
-Changes flow from `main` to `lts` via the `.github/workflows/promote-to-lts.yml` workflow, which is manually triggered via `workflow_dispatch`. The workflow creates a pull request directly using `gh pr create --base lts --head main`, allowing maintainers to review and approve the promotion. This simplified approach replaced the previous checkout+merge+intermediate-branch method and requires only `pull-requests: write` and `issues: write` permissions (no longer needs `contents: write`).
+Changes flow from `main` to `lts` via the `.github/workflows/promote-to-lts.yml` workflow, which is manually triggered via `workflow_dispatch`. The workflow performs a direct squash push from main to lts after running pre-flight divergence checks. The workflow includes a divergence check that blocks promotion if the lts branch contains commits not present in main, preventing circular history and git tree pollution. This approach requires `contents: write` permission to push directly to the lts branch.
+
+All changes, including emergency CI hotfixes, must land in main first before promotion to lts—direct commits to lts are not allowed. Triggering `workflow_dispatch` serves as the human approval gate for promotion.
**Build Triggers:**
All LTS build workflows (build-dx.yml, build-dx-hwe.yml, build-gdx.yml, build-regular.yml, build-regular-hwe.yml) trigger on both `main` and `lts` branches:
- **Builds on `main`**: Triggered by pushes, pull requests, and merge groups for validation and testing
-- **Builds on `lts`**: Triggered by pushes (including merged promotion PRs) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
+- **Builds on `lts`**: Triggered by pushes (including automatic squash pushes from promotion) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
**Dependency Management:**
@@ -180,7 +182,7 @@
Build workflows trigger on multiple events to support both development and production workflows:
- **main branch**: Builds triggered by push events, pull requests, and merge groups for validation and testing
-- **lts branch**: Builds triggered by push events (including merged promotion PRs) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
+- **lts branch**: Builds triggered by push events (including automatic squash pushes from promotion) for production releases. Weekly scheduled builds (Sundays at 2 AM UTC) are centralized through a dispatcher workflow (`scheduled-lts-release.yml`), with individual workflow `schedule:` triggers removed to prevent duplicate builds
All LTS variant build workflows (build-dx.yml, build-dx-hwe.yml, build-gdx.yml, build-regular.yml, build-regular-hwe.yml) trigger on both branches to ensure changes are validated before promotion to production.
Kernel Version ManagementView Changes@@ -165,6 +165,16 @@
- **main branch**: [Continuous testing builds published to `:lts-testing` with every push](https://github.com/ublue-os/bluefin-lts/pull/1138)
- **lts branch**: [Production releases only on scheduled runs or manual workflow_dispatch](https://github.com/ublue-os/bluefin-lts/pull/1138)
+
+**Promotion Workflow:**
+
+[The promote-to-lts.yml workflow performs a squash push from main → lts with pre-flight divergence checks](https://github.com/ublue-os/bluefin-lts/pull/1177). The workflow:
+
+1. Runs a pre-flight check that blocks promotion if lts has diverged from main
+2. Performs a `git merge --squash origin/main` to collapse all pending changes into a single staged changeset
+3. Pushes one clean commit to lts per promotion event, maintaining linear history
+
+[The workflow requires `contents: write` permissions](https://github.com/ublue-os/bluefin-lts/pull/1177) and uses `workflow_dispatch` as the human approval gate—a maintainer must explicitly trigger promotion. **Never commit directly to `lts`**: all changes including CI hotfixes must land in main first, preventing circular history and git tree pollution.
**Scheduling Architecture:**
@@ -292,7 +302,7 @@
| [build_files/post/dual-sign.sh](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/build_files/post/dual-sign.sh) | Module dual-signature implementation | ublue-os/akmods |
| [build_files/prep/dual-sign-check.sh](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/build_files/prep/dual-sign-check.sh) | Module signature verification | ublue-os/akmods |
| [Containerfile.in](https://github.com/ublue-os/akmods/blob/c24805395e7e057130ef94a099920f554fa0c1ae/Containerfile.in) | Multi-stage akmods build container | ublue-os/akmods |
-| [.github/workflows/promote-to-lts.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/promote-to-lts.yml) | LTS promotion workflow | ublue-os/bluefin-lts |
+| [.github/workflows/promote-to-lts.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/promote-to-lts.yml) | Performs squash push from main → lts with pre-flight divergence checks | ublue-os/bluefin-lts |
| [.github/workflows/scheduled-lts-release.yml](https://github.com/ublue-os/bluefin-lts/blob/main/.github/workflows/scheduled-lts-release.yml) | Weekly LTS release dispatcher | ublue-os/bluefin-lts |
## Related TopicsUniversal Blue Build and Update SystemView Changes@@ -53,7 +53,7 @@
- Manual `workflow_dispatch` triggers (for emergency releases)
- Individual build workflows no longer have `schedule:` triggers—the centralized dispatcher eliminates 10 duplicate no-op builds that previously ran on `main` every Tuesday alongside the 5 dispatcher runs on `lts`, working around GitHub Actions limitation that scheduled triggers always run on the default branch
-This architecture prevents accidental production tag publishing while maintaining validation builds on merges. The validation builds on `lts` push events provide a third layer of defense against branch pollution by verifying code integrity when promotion PRs merge.
+This architecture prevents accidental production tag publishing while maintaining validation builds on merges. The validation builds on `lts` push events provide a third layer of defense against branch pollution by verifying code integrity when promotions complete.
**Dispatcher Workflow Architecture:**
@@ -65,15 +65,16 @@
**Layer 1: Manual Promotion Workflow** - A manual `promote-to-lts.yml` workflow enables explicit promotion of tested changes from `main` to `lts`. The workflow:
- Requires manual trigger via GitHub Actions UI (`workflow_dispatch`)
-- Uses `gh pr create --base lts --head main` to directly create promotion PRs without intermediate branches
-- Opens pull request with documentation warning against reverse merges
-- Requires operator review and approval before merging
-- Only requires `pull-requests: write` and `issues: write` permissions (no `contents: write` needed)
+- Performs a direct squash push from `main` to `lts` (no intermediate branches or PRs)
+- Includes a pre-flight divergence check that blocks promotion if `lts` has commits not reachable from `main`
+- Automatically performs the squash push after pre-flight checks pass
+- Requires `contents: write` permission for direct push to `lts` branch
- Replaces the previous automatic Pull app (`.github/pull.yml` has been deleted)
+- **CRITICAL RULE**: NEVER commit directly to `lts` — all changes must land in `main` first
**Layer 2: Renovate Restriction** - Renovate configuration restricts dependency updates to target only the `main` branch via `baseBranchPatterns: ["main"]`. This prevents automated dependency PRs from targeting the `lts` branch, ensuring the production branch receives updates only through manual promotion.
-**Layer 3: Validation Build Triggers** - All 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) include `lts` in their push triggers. When promotion PRs merge to `lts`, validation builds execute to verify code integrity, but these builds do NOT publish production tags. Publishing only occurs via `workflow_dispatch` events (weekly dispatcher or manual triggers).
+**Layer 3: Validation Build Triggers** - All 5 build workflows (build-dx-hwe.yml, build-dx.yml, build-gdx.yml, build-regular-hwe.yml, build-regular.yml) include `lts` in their push triggers. When promotions push to `lts`, validation builds execute to verify code integrity, but these builds do NOT publish production tags. Publishing only occurs via `workflow_dispatch` events (weekly dispatcher or manual triggers).
This three-layer architecture prevents branch pollution incidents where AI agents or automated tools merge `lts` → `main` (wrong direction) when detecting divergence in `image-versions.yaml` digests. Such divergence is intentional—`main` has newer versions for testing while `lts` maintains stable production versions.
|
There was a problem hiding this comment.
Pull request overview
This PR updates the main → lts promotion mechanism to avoid merge-commit history pollution on lts by replacing PR-based promotion with a controlled squash-merge-and-push workflow, plus a divergence guard.
Changes:
- Replace
promote-to-lts.ymlPR creation with: checkoutlts→ pre-flight divergence check →git merge --squash origin/main→ commit → push tolts. - Adjust workflow permissions from PR/issue write to
contents: writefor direct branch updates. - Update
AGENTS.mdto document the new promotion flow and add a “NEVER commit directly tolts” rule.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
AGENTS.md |
Documents squash-push promotion flow and adds guidance to prevent lts divergence. |
.github/workflows/promote-to-lts.yml |
Implements pre-flight divergence check and squash-push promotion into lts without PRs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
.github/workflows/promote-to-lts.yml
Outdated
| run: git merge --squash origin/main | ||
|
|
||
| - name: Commit promotion | ||
| run: | | ||
| git commit \ | ||
| -m "${{ inputs.commit_title }}" \ | ||
| -m "${{ inputs.commit_body }}" | ||
|
|
||
| - name: Push to lts |
There was a problem hiding this comment.
If origin/main is already fully promoted into lts, git merge --squash origin/main will leave nothing staged and the subsequent git commit will fail with "nothing to commit". Decide on the intended behavior for a no-op promotion (e.g., detect an empty index after the squash merge and exit 0 with a clear message, or exit 1 with an explicit "nothing to promote" error) to avoid a confusing failure mode.
| run: git merge --squash origin/main | |
| - name: Commit promotion | |
| run: | | |
| git commit \ | |
| -m "${{ inputs.commit_title }}" \ | |
| -m "${{ inputs.commit_body }}" | |
| - name: Push to lts | |
| id: squash | |
| run: | | |
| set -e | |
| git merge --squash origin/main | |
| if git diff --cached --quiet; then | |
| echo "No changes to promote: origin/main is already fully merged into lts." | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Prepared squash merge of origin/main into lts." | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Commit promotion | |
| if: steps.squash.outputs.has_changes == 'true' | |
| run: | | |
| git commit \ | |
| -m "${{ inputs.commit_title }}" \ | |
| -m "${{ inputs.commit_body }}" | |
| - name: Push to lts | |
| if: steps.squash.outputs.has_changes == 'true' |
| - `build-dx-hwe.yml` — HWE kernel variant of `bluefin-dx` | ||
| - `scheduled-lts-release.yml` — Weekly production release dispatcher (sole owner of Sunday builds) | ||
| - `promote-to-lts.yml` — Opens a one-way `main` → `lts` promotion PR | ||
| - `promote-to-lts.yml` — Squash-pushes `main` into `lts` (with pre-flight divergence check) |
There was a problem hiding this comment.
AGENTS.md still describes promote-to-lts.yml as "Creates a PR to merge main → lts" in the earlier "Workflow Files and Their Roles" table, but this PR changes the workflow to squash-push directly. Please update that table entry as well so the documentation is internally consistent.
- Guard commit/push steps on has_changes output to avoid 'nothing to commit' failure when main is already fully merged into lts - Fix promote-to-lts.yml table row in AGENTS.md workflow roles table Addresses Copilot review feedback on PR ublue-os#1177. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Problem
The
promote-to-lts.ymlworkflow has caused repeated git tree pollution onlts. After digging through the full history, here is the root cause chain:ffa30fe(Merge branch 'lts' into main) accidentally mergedlts→main, embedding lts's pull-bot merge commits inside main's graph.ltsthat aren't inmain(old pull-bot merges + the2a780d2emergency CI sync).gh pr create --head mainapproach created a PR that, when merged with GitHub's default button, produced a merge commit with two parents. That commit dragged main's full cross-contaminated history intoltson every promotion, deepening the diamond graph each time.ltswas already in a diverged state.Solution
Replace the PR creation step with a direct squash push:
git rev-list origin/lts ^origin/main --count. If > 0, fail immediately and list the divergent commits with instructions to land them inmainfirst.git merge --squash origin/main— collapses all pending main changes into a single staged changeset. No merge commit. No embedded history.ltsper promotion event → linear history.The
workflow_dispatchtrigger is the human approval gate — a maintainer must explicitly trigger the workflow to promote.Why not keep the PR?
Previous attempts:
[pull]merge commits; also ran in reverseltson mergegh pr create --head main(last fix)2a780d2A squash push is deterministic — the workflow controls the merge strategy, not the human clicking the merge button.
Changes
.github/workflows/promote-to-lts.yml: Replacegh pr createwith checkout + pre-flight + squash merge + push. Swappull-requests: write→contents: write.AGENTS.md: Update promotion flow description; add NEVER commit directly toltsrule.ltscurrently has 4 commits not inmain. The pre-flight check will correctly block the next promotion and print those commits. Resolution: the CI fixes from2a780d2are already inmainas6ec7dd5— once the divergent commits are resolved andgit rev-list origin/lts ^origin/mainis empty, promotion will work cleanly.