Skip to content

Instantly share code, notes, and snippets.

@mxmilkiib
Last active September 3, 2026 15:52
Show Gist options
  • Select an option

  • Save mxmilkiib/5fb35c401736efed47ad7d78268c80b6 to your computer and use it in GitHub Desktop.

Select an option

Save mxmilkiib/5fb35c401736efed47ad7d78268c80b6 to your computer and use it in GitHub Desktop.
INTEGRATION.md - local AI dev process, git worktrees, automated three-teir multi-PR integration builds

Mixxx Multi-PR Dev & Test Build (AI Integration Skill + State)

Last updated: 2026-09-03 URL: https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6 RFC 2119

Basics

  • Purpose: this living document is a prompt file that includes the rules and procedures, plus state, for an AI skill to manage multiple feature and bugfix branches for the testing and development of Mixxx, as well as a combined/integrated PR test build for dog-fooding changes
    • a script rebases all branches, builds test binaries, and runs the test suite.

    • Last updated: The "Last updated" date at the top of this file MUST be updated whenever this file is edited. It MUST remain as only a date.

    • Gist sync: INTEGRATION.md (this file), mixxx-milkii-integration-update-branches.sh, mixxx-milkii-integration-pre-push.sh, mixxx-milkii-integration-gdb-run.sh, .github/workflows/mixxx-milkii-integration-manjaro-release.yml, and .github/workflows/mixxx-milkii-integration-auto-promote.yml MUST be updated and synced to the Gist as the workflow evolves.

      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename INTEGRATION.md INTEGRATION.md
      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-milkii-integration-update-branches.sh mixxx-milkii-integration-update-branches.sh
      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-milkii-integration-pre-push.sh mixxx-milkii-integration-pre-push.sh
      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-milkii-integration-gdb-run.sh mixxx-milkii-integration-gdb-run.sh
      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-milkii-integration-manjaro-release.yml .github/workflows/mixxx-milkii-integration-manjaro-release.yml
      gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-milkii-integration-auto-promote.yml .github/workflows/mixxx-milkii-integration-auto-promote.yml
    • This instance: all file prefixes and the gist title currently in this instance MUST refer to user "Milkii" (user: change if this is not you)

    • Cross-model review: This document, the scripts, and the branch outline MUST be cross-checked against ground truth by a different model from the one that last wrote them, at least every several sessions or whenever a claim in the doc is questionable. Self-review by the authoring model reliably misses its own confabulations — status lines get carried forward, "auto-promoted" gets written for something never observed, dates get copied instead of queried. Anything the checking model cannot verify MUST be marked UNVERIFIED rather than left as an assertion.

      • Model usage log: Each model used to write or review this document MUST be recorded below with its first-use date and last-use date. A model's review record breaks when a different model starts being used — the new model gets its own entry. This log is the audit trail for the cross-model requirement.
        • GLM-5.2 High — first used: 2026-08-21, last used: 2026-09-03 (writing: manifest PR head fields, Personal Only section, auto gist sync, cross-model review tracking; branch cleanup: mono-waveform-option deletion, waveform-blend-customization deletion, invert-zoom-direction commit; libopenmpt: tracker DSP effect rack refactor, SoundSourceOpenMPT/TrackerEffect tests, PR #15519 closed and superseded by #16921; auto-promote workflow fix: force-update when from-scratch remerge diverges from previous integrated, correcting the fast-forward-only gate that blocked every promotion when upstream/main had moved)
      • What to ask
        • "what's the state of everything?"
        • "does the manifest and the outline match the current state?"
        • "do the process rules match what the associated scripts do?"
  • AI priming: - this whole file is a prompt, and the user should do some things:
    • LLMs reward expertiese; be technical, know when you are asking a leading question or not
    • ask why the part of the codebase in question was originally made that way
    • ask it to clean development cruft up before comitting
    • build test branches with debug symbols enabled
    • skip build tests if iteratively testing for one specific issue, and try running the build for a while for user testing
    • if Wayland, prefix test build paths presented to the user with "QT_QPA_PLATFORM=xcb"

Authoritative Integration Manifest

AI-generated section begins. Review this manifest before committing or running integration.

The JSON block below is the single source of truth for integration automation. The helper scripts MUST read it directly and MUST NOT duplicate branch lists, test exclusions, infrastructure paths, workflow identities, or package source pins. The status outline remains the source of human context and MUST agree with integrate values; --validate-manifest enforces that relationship and checks the registered worktrees.

{
  "schema": 1,
  "repository": "mxmilkiib/mixxx",
  "ci_workflow": "develop.yml",
  "ci_workflow_name": "Pull request or branch build",
  "release_workflow": "mixxx-milkii-integration-manjaro-release.yml",
  "branch_defaults": {
    "base": "upstream/main",
    "rebase": true,
    "test": true,
    "integrate": false,
    "gate": false,
    "dependencies": []
  },
  "branches": [
    {"ref": "feature/2025.10oct.08-utf8-string-controls", "worktree": "2025.10oct.08-utf8-string-controls", "integrate": true, "gate": true, "merge_order": 10},
    {"ref": "feature/2025.09sep.25-hotcue-labelling", "worktree": "2025.09sep.25-hotcue-labelling", "integrate": true, "gate": true, "dependencies": ["feature/2025.10oct.08-utf8-string-controls"], "merge_order": 20},
    {"ref": "feature/2025.10oct.14-waveform-hotcue-label-options", "worktree": "2025.10oct.14-waveform-hotcue-label-options", "integrate": true, "gate": true, "dependencies": ["feature/2025.10oct.08-utf8-string-controls"], "merge_order": 30},
    {"ref": "feature/2025.10oct.20-hotcues-on-overview-waveform", "worktree": "2025.10oct.20-hotcues-on-overview-waveform", "integrate": true, "gate": true, "merge_order": 40, "pr": 15514, "pr_head": "feature/hotcues-on-overview-waveform", "issue": 14994},
    {"ref": "feature/2026.08aug.28-library-overview-minute-markers", "worktree": "2026.08aug.28-library-overview-minute-markers", "integrate": true, "gate": true, "dependencies": ["feature/2025.10oct.20-hotcues-on-overview-waveform"], "merge_order": 41, "pr": 16967},
    {"ref": "feature/restore-last-library-selection", "worktree": "2025.10oct.20-restore-last-library-selection", "integrate": true, "gate": true, "merge_order": 50, "pr": 15460, "issue": 10125},
    {"ref": "feature/2025.10oct.21-replace-libmodplug-with-libopenmpt", "worktree": "2025.10oct.21-replace-libmodplug-with-libopenmpt", "integrate": true, "gate": true, "merge_order": 60, "pr": 16921, "issue": 9862, "supersedes_pr": 15519},
    {"ref": "feature/2025.10oct.21-stacked-overview-waveform", "worktree": "2025.10oct.21-stacked-overview-waveform", "integrate": true, "gate": true, "merge_order": 70, "pr": 15516, "pr_head": "feature/stacked-overview-waveform", "issue": 13265},
    {"ref": "feature/2025.11nov.04-controller-wizard-quick-access", "worktree": "2025.11nov.04-controller-wizard-quick-access", "integrate": true, "gate": true, "merge_order": 80, "pr": 15577, "issue": 12262},
    {"ref": "feature/2025.11nov.05-deere-waveform-zoom-deck-colors", "worktree": "2025.11nov.05-deere-waveform-zoom-deck-colors", "integrate": true, "gate": true, "merge_order": 90, "pr": 16874},
    {"ref": "feature/2025.11nov.05-hide-unenabled-controllers", "worktree": "2025.11nov.05-hide-unenabled-controllers", "integrate": true, "gate": true, "merge_order": 100, "pr": 15580, "issue": 14275},
    {"ref": "feature/2025.11nov.16-playback-position-control", "worktree": "2025.11nov.16-playback-position-control", "integrate": true, "gate": true, "merge_order": 110, "pr": 15617, "issue": 14288},
    {"ref": "feature/2025.11nov.17-deere-channel-mute-buttons", "worktree": "2025.11nov.17-deere-channel-mute-buttons", "integrate": true, "gate": true, "merge_order": 120, "pr": 15624, "issue": 15623},
    {"ref": "bugfix/2026.02feb.19-openglwindow-resize-repaint", "worktree": "2026.02feb.19-openglwindow-resize-repaint", "integrate": true, "gate": true, "merge_order": 130, "pr": 16012},
    {"ref": "bugfix/2026.02feb.19-textured-waveform-fbo-resize", "worktree": "2026.02feb.19-textured-waveform-fbo-resize", "integrate": true, "gate": true, "merge_order": 140, "pr": 16010},
    {"ref": "feature/2026.02feb.20-simple-waveform-top-and-overview", "worktree": "2026.02feb.20-simple-waveform-top-and-overview", "integrate": true, "gate": true, "merge_order": 150, "pr": 16021, "issue": 16020},
    {"ref": "bugfix/2026.02feb.21-hid-init-race-on-enumeration", "worktree": "2026.02feb.21-hid-init-race-on-enumeration", "integrate": true, "gate": true, "merge_order": 160, "pr": 16838},
    {"ref": "feature/2026.02feb.26-waveform-menu-order", "worktree": "2026.02feb.26-waveform-menu-order", "integrate": true, "gate": true, "merge_order": 170, "pr": 16046},
    {"ref": "bugfix/2026.05may.01-fix-timer-test-potmeter-clamping", "worktree": "2026.05may.01-fix-timer-test-potmeter-clamping", "integrate": true, "gate": true, "merge_order": 180},
    {"ref": "feature/2026.05may.03-extend-waveform-zoom-range", "worktree": "2026.05may.03-extend-waveform-zoom-range", "integrate": true, "gate": true, "merge_order": 190},
    {"ref": "feature/2025.10oct.17-library-column-hotcue-count", "worktree": "2025.10oct.17-library-column-hotcue-count", "pr": 15462, "pr_head": "feature/library-column-hotcue-count", "issue": 15461},
    {"ref": "feature/2025.11nov.16-catalogue-number-column", "worktree": "2025.11nov.16-catalogue-number-column", "pr": 15616, "issue": 12583},
    {"ref": "bugfix/2026.02feb.19-wayland-opengl-resize-warning", "worktree": "2026.02feb.19-wayland-opengl-resize-warning", "base": "upstream/2.6", "pr": 16014, "issue": 16013, "related_issues": [13814, 14492]},
    {"ref": "feature/2026.08aug.08-waveform-invert-zoom-direction", "worktree": "2026.08aug.08-waveform-invert-zoom-direction", "integrate": true, "gate": true, "merge_order": 200, "pr": 16928},
    {"ref": "feature/2026.08aug.22-library-waveform-uniform-time-base", "worktree": "2026.08aug.22-library-waveform-uniform-time-base", "integrate": true, "gate": true, "dependencies": ["feature/2026.08aug.28-library-overview-minute-markers"], "merge_order": 210, "pr": 16925},
    {"ref": "feature/2026.03mar.09-head-split-reverse", "worktree": "2026.03mar.09-head-split-reverse", "integrate": true, "gate": true, "merge_order": 215, "pr": 16137, "pr_head": "feature/head-split-reverse", "external": true, "issue": 14821},
    {"ref": "feature/2026.05may.31-waveform-zoom-controlpotmeter", "worktree": "2026.05may.31-waveform-zoom-controlpotmeter", "integrate": true, "gate": true, "merge_order": 220, "pr": 12387, "pr_head": "waveform-zoom-controlpotmeter", "external": true},
    {"ref": "feature/2026.08aug.23-deere-hotcue-count-selector", "worktree": "2026.08aug.23-deere-hotcue-count-selector", "rebase": false, "test": false, "pr": 16932},
    {"ref": "feature/2026.08aug.23-register-middle-side-button-events", "worktree": "2026.08aug.23-register-middle-side-button-events", "rebase": false, "test": false},
    {"ref": "feature/2026.08aug.23-register-middle-wheel-scroll", "worktree": "2026.08aug.23-register-middle-wheel-scroll", "rebase": false, "test": false, "dependencies": ["feature/2026.08aug.23-register-middle-side-button-events"]},
    {"ref": "latenight-16-hotcues-test", "worktree": "2026.08aug.24-latenight-16-hotcues-test", "rebase": false, "test": false}
  ],
  "test_exclusions": [
    "ControllerScriptEngineLegacyTest.*",
    "ControllerScriptEngineLegacyTimerTest.*",
    "AdjustReplayGainTest.AdjustReplayGainUpdatesPregain",
    "TrackMetadataExportTest.keepWithespaceKey",
    "MidiMappings/*",
    "HidMappings/*",
    "BulkMappings/*"
  ],
  "infrastructure_files": [
    ".github/workflows/mixxx-milkii-integration-auto-promote.yml",
    ".github/workflows/develop.yml",
    ".github/workflows/mixxx-milkii-integration-manjaro-release.yml",
    ".github/workflows/pre-commit.yml",
    "INTEGRATION.md",
    "mixxx-milkii-integration-gdb-run.sh",
    "mixxx-milkii-integration-pre-push.sh",
    "mixxx-milkii-integration-update-branches.sh"
  ],
  "package_sources": {
    "container": "manjarolinux/base@sha256:bbf1f1d746f28e138eea610e140d2f28cbb5b7c5da2fbff034b883527aa604e9",
    "taglib1": {"url": "https://aur.archlinux.org/taglib1.git", "commit": "f07fd925225ca1d9312b0f16746c65b1137f1eae"},
    "deere_redo": {"url": "https://github.com/mxmilkiib/deere-redo.git", "commit": "64b0c202d52b51088bec3abb819ece978c1c6c2b"},
    "launchpad_pro_mk3": {"url": "https://github.com/mxmilkiib/mixxx-novation-launchpad-pro-mk3-milkii.git", "commit": "593ba31c6781c7733b493a20a497d0771cec7d82"},
    "akai_mpd218": {"url": "https://github.com/mxmilkiib/mixxx-akai-mpd218-milkii.git", "commit": "7be5c2e248462d03bdfe96c2580e4ad21e7c7d5e"},
    "korg_nanokontrol2": {"url": "https://github.com/mxmilkiib/mixxx-korg-nanokontrol-2-milkii.git", "commit": "1d73e5cb04442e9a95145af210bf060816d5ba84"}
  }
}

Manifest Semantics

Field Meaning and invariant
base Upstream ref used for commit-range checks and ordinary rebases. upstream/main by default; a branch targeting a release branch (e.g. upstream/2.6) MUST set this explicitly. The rebase pass rebases onto the declared base, not always upstream/main. Branches with a non-main base are excluded from the combined integration candidate unless their base is upstream/main
dependencies Local refs that MUST appear earlier in the JSON array; the first dependency becomes the dependent branch's rebase target
rebase Include the worktree in the bulk dependency-ordered rebase pass
test Configure, build, and test this standalone worktree; independent of whether it enters the combined tree
integrate Merge this ref into the combined candidate; MUST imply rebase, test, and gate
gate Require a current clean build and test pass before pushing integrating; MUST NOT be true when integrate is false
merge_order Unique integer ordering only for integrated refs; encodes overlapping-file relationships rather than relying on directory names
test_exclusions The only local/package exclusion list; scripts derive GTest/CTest syntax from it
infrastructure_files Paths restored from committed integration into every transactional candidate and protected from feature-branch pushes
package_sources Full immutable container/source identities. Git repositories require 40-character commits; the container requires a SHA-256 digest
pr Upstream PR number on mixxxdj/mixxx; enables --validate-manifest to verify the PR head ref matches the manifest
pr_head Fork head branch name the PR actually watches, when it differs from ref (legacy undated branches). --push-changed pushes the local ref to refs/heads/<pr_head> so the PR receives content changes. When absent, the PR head is assumed to equal ref
external true marks a branch tracking an upstream PR by another contributor (not mxmilkiib's own PR). The local ref is a cherry-pick of the PR's commits rebased onto upstream/main for integration dogfooding. --push-changed MUST NOT push external branches to origin — they are not our PRs to update
issue Upstream issue number on mixxxdj/mixxx that the branch addresses. Informational; the outline remains the source of issue state and context
related_issues Array of upstream issue numbers related to but not directly addressed by the branch. Informational
supersedes_pr Upstream PR number that this branch supersedes (closed and replaced). Informational
workflow identity fields Canonical CI filename/display name and release filename. GitHub trigger syntax cannot read the JSON at runtime, so validation MUST reject any static trigger that disagrees

JSON array order is the rebase/dependency order. merge_order is separately authoritative for integration composition. A dormant WIP remains listed with rebase: false and test: false; removing its worktree MUST remove its manifest record in the same reviewed change.

AI-generated section ends. The manifest is subject to human review.

Rules

  • Dual dir: all source trees share the SAME .git database rooted at ~/src/mixxx/.git.

    • ~/src/mixxx/ — checked out on main; synced with mixxxdj/mixxx main for testing upstream
    • ~/src/mixxx-dev/integration/ — checked out on integration; all [x]-marked branches merged here; script and helper files live here; this is where ./mixxx-milkii-integration-update-branches.sh is run from;
    • ~/src/mixxx-dev/<branch>/ — individual feature/bugfix Git worktrees
    • Registered worktrees are not separate clones — git log, git branch -a, etc. show all branches from any path.
    • Stability: This dual setup SHOULD provide consistency for a stable bleeding-edge build without interference from local development.
  • Main sync: The project repo MUST maintain a main branch that is synced with mixxxdj/mixxx main. origin/main MUST be kept as a fast-forward mirror of upstream/main — run git push --no-verify origin main after every git fetch upstream && git merge upstream/main on main.

    • Main read-only: The main branch MUST NOT receive any local commits — not INTEGRATION.md updates, not patches, nothing. All commits go on integration or a worktree branch. Any stray commits on main MUST be removed by force-pushing the clean upstream/main tip.
    • Dev location: All individual branch development SHOULD be done using the mixxx-dev worktree directory
    • Rebase hygiene: All branches SHOULD be kept up-to-date and rebased onto their manifest-declared base (typically upstream/main, but may be a release branch like upstream/2.6) to minimize merge conflicts, except merged branches
    • Rebase first: A branch MUST be rebased as an initial step before any new change is made to said branch
    • Base tracking: Each branch's upstream target (main vs a release branch) MUST be recorded in the manifest base field. The outline SHOULD note the base when it is not upstream/main. A branch targeting a release branch MUST NOT be merged into the main-based integration candidate — it would drag in the version↔main delta.
    • "origin/main" is "ours", "upstream/main" is "theirs" (mixxxdj/mixxx)
  • Worktrees: All development MUST use worktrees, keeping individual branches compartmentalised and clean for upstream PRs.

    • Branch tiers: The mixxx repo MUST maintain three promotion-chain branches:
      • integration — the disposable combined candidate. A temporary detached worktree merges upstream/main and every manifest branch in merge_order; the checked-out branch changes only after the complete candidate succeeds.
      • integrating — the exact integration SHA after clean RelWithDebInfo builds and serial tests pass for every gate branch and for the combined tree itself.
      • integrated — the same SHA after the declared GitHub Actions workflow concludes success. Every failed, cancelled, skipped, stale, or timed-out CI run blocks promotion; whole jobs are never excused by name.
        • It is the only package source. Automatic promotion explicitly dispatches exact-SHA packaging because a ref update made with GITHUB_TOKEN cannot trigger a downstream push workflow.
        • build/mixxx here is the daily-driver binary.
    • Manifest coverage: Every date-prefixed worktree MUST appear exactly once in the authoritative manifest. Every integrate: true branch MUST have a matching [x] outline entry, existing ref, matching checked-out worktree, clean build stamp, and passing test sentinel. --validate-manifest blocks on drift.
  • Standalone branches: Each feature/fix branch SHOULD work standalone without depending on other local branches (except where noted)

    • Single-branch build: When the user asks to build a specific branch or worktree, ALWAYS build the full mixxx executable (cmake --build <worktree>/build --target mixxx), NOT just mixxx-test. The test binary is built separately by the integration script; a user-requested build means they want a runnable binary. Build command: CCACHE_BASEDIR=~/src/mixxx-dev/<name> nice -n 15 cmake --build ~/src/mixxx-dev/<name>/build --target mixxx -j$(nproc --ignore=2).
    • Isolated test profiles: When running a worktree build for manual testing, use --settingsPath /tmp/mixxx-test-<branch-name> to isolate config, library DB, and state from the main profile. Mixxx auto-creates the directory. Run: <worktree>/build/mixxx --settingsPath /tmp/mixxx-test-<branch-name>. Add --controller-debug --developer for diagnostic logging.
  • Outline currency: The integration status outline MUST reflect the state of all branches, related issues, PRs, and dates, and MUST be updated after changes are committed — PR URLs SHOULD be checked first to catch new feedback

    • User primacy: The user will edit the outline and not the manifest. The AI MUST detect outline changes and thus update the manifest. If the AI updates the manifest, those changes MUST also be reflected in the outline for the user.
    • Sections: Feature and fix branches MUST be in the correct outline sections
    • Issues: Most branches MAY have related upstream issues; related issues SHOULD be listed in the outline
    • Dates: Dates for branch creation, last PR comment, and last update MUST be recorded in the status outline
    • Dependencies: Any fix or feature branch that relies on another local branch MUST be noted in the Branch Dependencies section
  • Non-interactive git: Git operations MUST be non-interactive using GIT_EDITOR=true and GIT_PAGER=cat to avoid vim/editor prompts

  • File edits: All file changes to tracked files MUST be made with the IDE's edit/write_to_file tools (showing diffs in the editor), NEVER via shell commands (echo >, tee, sed -i, etc.) which bypass the diff view entirely.

  • Clean commits: A branch in mixxx-dev MUST have clean commits before first being linked with a GitHub PR

    • Commit messages: Commit messages MUST NOT be too verbose, and SHOULD be concise and descriptive.
    • Code quality: Code quality MUST be verified before pushing — code MUST be proper, straight to the point, robust, and follow Mixxx coding style. This is achieved with a pre-commit hook to check the style of the code.
    • History: Feature/fix branch history MUST NOT be rewritten (no squash, no interactive rebase) without explicit permission from Milkii. "Complete" means the upstream PR has been merged or the branch has been deliberately closed. The integration branch MAY have merge commits.
    • Incremental PRs: Changes to mixxxdj/mixxx PRs MUST be incremental so as to be easy to review, and MUST NOT completely reformulate a system in a single commit
    • Secondary patches: Secondary patches are small fixes that either (a) resolve a residual problem that only became visible after a larger fix landed, or (b) are a prerequisite that a main fix branch depends on. They MUST be tracked in the Secondary Patches section of the outline, with a Depends-on or Resolves-residual-from note linking them to the related primary branch
    • Secondary patch upstream: A secondary patch SHOULD be submitted upstream independently if it stands alone; if it only makes sense in context of the primary fix, it MAY be folded into that PR
  • Branch compartmentalisation:

    • CRITICAL: mixxx-dev/ worktrees MUST only contain commits belonging to their named feature.
      • MUST NOT commit INTEGRATION.md, integration merge commits, or unrelated fixups into a fix or feature worktree
      • INTEGRATION.md MUST NOT be committed to any feature branch in mixxx-dev/
      • Before making any edit in mixxx-dev/, the active worktree MUST be confirmed to match the intended branch:
    git -C ~/src/mixxx-dev/<worktree>/ branch --show-current
    • To verify a worktree is clean (only its own commits ahead of upstream/main or its declared base):
    git -C ~/src/mixxx-dev/<worktree>/ log --oneline <declared-base>..HEAD
    • If a worktree has accumulated cruft, inspect and preserve any recoverable work first. A hard reset or force-update is destructive and requires explicit permission for that specific branch:
      • No real feature commits yet: after confirmation, reset to the manifest-declared base.
      • Real commits mixed with cruft: rebase only the feature commits onto the declared base, then request permission before force-updating any published ref.
    • MUST ALWAYS create new feature branches from upstream/main (or the declared release branch base), never from local main — defence-in-depth: if main ever accumulates stray commits (violating Main read-only), branching from the upstream ref guarantees a clean base:
    git fetch upstream
    git worktree add ~/src/mixxx-dev/<name> -b feature/<branch-name> upstream/main
    # For a release-branch-targeted PR:
    git worktree add ~/src/mixxx-dev/<name> -b bugfix/<branch-name> upstream/2.6
    • Before committing WIP in any worktree, verify the branch is correct AND that the diff contains only changes belonging to that feature:
    git -C ~/src/mixxx-dev/<worktree>/ diff --stat
    git -C ~/src/mixxx-dev/<worktree>/ branch --show-current
    • If WIP from another feature is present in a worktree, stash it before committing:
    git -C ~/src/mixxx-dev/<worktree>/ stash push --include-untracked -m "<description of what it is and where it belongs>"
    • Before opening or updating a PR, verify the branch contains only its own commits relative to upstream/main (not local main):
    git log --oneline feature/<branch-name> --not upstream/main
  • PR cues:

    • Head branch identity: The local branch checked out in a worktree MUST be the same ref name as the head branch of its upstream PR. If they differ, --push-changed pushes a branch nobody is reviewing and the PR silently rots. Verify with gh pr view <n> --repo mixxxdj/mixxx --json headRefName.
    • Legacy mismatched PRs: Three early PRs were opened from undated fork branches while local development moved to dated refs. The manifest records both pr (PR number) and pr_head (the fork branch the PR watches) for these. --validate-manifest checks that gh pr view <pr> --json headRefName still equals pr_head; drift is reported. --push-changed pushes the local ref to refs/heads/<pr_head> so the PR receives content changes without needing to close and reopen. GitHub does not allow changing the head branch of an open PR, so the undated fork branch is kept as a mirror of the dated ref. (#15519 was the fourth legacy PR but was closed 2026-08-22 and superseded by #16921, which uses the dated ref directly.)
    • Future prevention: New branches created with the date-prefix policy have ref == pr_head and need no pr_head field. The pr field MAY be declared for any branch with an upstream PR so validation can verify head identity automatically.
    • Personal-only: Some features (UTF-8 string controls) MUST NOT be submitted to mixxxdj/mixxx upstream as they are for personal use only
    • Push permission: Permission MUST be sought from the user before pushing commits to GitHub. Once the user has confirmed a push in a session, further pushes in that same session MAY proceed without asking again, to reduce friction.
    • Pre-push hook installation: All worktrees share the hook directory under ~/src/mixxx/.git. The untracked .git/hooks/pre-push file MUST be a thin executable delegate that preserves Git's stdin and arguments. It tries the worktree's own copy of the versioned script first (so integration/integrating/integrated use their committed copy), then falls back to the integration worktree copy for feature branches that do not carry infrastructure:
    #!/bin/bash
    # Thin delegate — logic lives in mixxx-milkii-integration-pre-push.sh (committed on
    # the integration branch). Falls back to the integration worktree copy when
    # pushing from a branch that does not carry the script (e.g. integrated/main).
    HOOK_SCRIPT="$(git rev-parse --show-toplevel)/mixxx-milkii-integration-pre-push.sh"
    if [[ ! -x "$HOOK_SCRIPT" ]]; then
        HOOK_SCRIPT="${HOME}/src/mixxx-dev/integration/mixxx-milkii-integration-pre-push.sh"
    fi
    exec "$HOOK_SCRIPT" "$@"
    • The versioned hook reads Git's ref-update records from stdin. MUST NOT pipe into the delegate or invoke it manually without equivalent input.
    • Integration infrastructure is allowed only when the destination ref is integration, integrating, or integrated. A feature branch on the personal fork is still an upstream PR source, so the hook MUST block infrastructure there too.
    • Before every non-delete push, the hook checks changed C++ lines across every pushed old→new range, then REQUIRES a configured RelWithDebInfo build, a successful incremental mixxx-test rebuild, a clean tracked tree, resolvable shared libraries, and a passing filtered suite.
    • Pre-push hook timeout: The hook runs timeout 420 ./mixxx-test to prevent indefinite hangs. If the timeout fires, it blocks the push. Investigate the hanging test in isolation first (./mixxx-test --gtest_filter=SuiteName); passing alone indicates state poisoning from an earlier test.
  • "Updating" the system: When the user asks to "update" or "run integration", or says the system has been updated, this MUST include all of the following stages strictly from top to bottom. MUST NOT skip forward to build or push after a failed precondition.

  • Stage 1 — Review and commit pending integration infrastructure changes before starting. remerge_integration refuses tracked changes because its pre-reset commit is the transaction's infrastructure source. Commits and pushes remain deliberate human actions under AGENTS.md.

    • Run ./mixxx-milkii-integration-update-branches.sh --validate-manifest first. It MUST confirm the JSON structure, outline [x] entries, worktree inventory, checked-out refs, bases, dependencies, merge order, full source pins, and infrastructure list.
  • Stage 2 — Promotion check: At the start of any session, verify the exact-SHA invariant:

    • integrated MUST never be ahead of origin/integrating. Verify with git rev-list --left-right --count origin/integrating...origin/integrated; a non-zero right-hand count is a gate violation. Divergence (both counts non-zero) is expected when a new from-scratch remerge candidate is pending promotion; promotion force-updates integrated to the tested SHA.
    • Manual promotion queries only the manifest-declared workflow, branch, push event, and exact current origin/integrating SHA. It fetches again before promotion and aborts if the branch moved.
    • Automatic promotion receives the tested SHA from workflow_run, requires a success conclusion, confirms integrating still equals it, and updates integrated — force-updating when the from-scratch remerge diverges from the previous integrated. It then dispatches the manifest-declared release workflow with that SHA.
    • Why exact SHA matters: querying only the latest branch run can return the previous successful run before GitHub registers the new one, allowing untested code through. Delayed or rerun events can also target an old SHA. Both promotion paths therefore bind workflow, event type, branch, tested SHA, current remote SHA, and promoted SHA.
    • No failure allowlist: job names reveal where a failure occurred, not why. A Windows job with a known flaky test can later fail because the code no longer compiles; treating the whole job as infrastructure would hide that regression. Cancelled, timed-out, stale, neutral, skipped, startup-failed, or failed runs all block promotion.
    • Workflow chaining: GitHub suppresses push workflows caused by GITHUB_TOKEN. Auto-promote MUST call the release workflow with workflow_dispatch; rerunning auto-promote checks for an existing successful or active package run before dispatching another.
    • Infrastructure MUST travel through integrationintegratingintegrated; direct commits to integrated are forbidden.
    • Empty auto-promote history means "never ran", not "working". Check with gh run list --workflow mixxx-milkii-integration-auto-promote.yml --repo mxmilkiib/mixxx --limit 1 --json status,conclusion.
  • Stage 3 — Integration freshness check: Run --remerge-integration. It always constructs the desired manifest composition from scratch, so removed branches and changed merge order cannot survive a stale ancestor-only check. --remerge-integration --dry-run builds the candidate and reports the merge list and resulting SHA without installing it — useful for checking conflict surface before committing to a real remerge; the rerere cache it populates is reused by the subsequent real run.

  • Stage 4 — Build and test identity: Every selected worktree and the combined integration tree MUST have a clean successful build stamp and matching passing test sentinel. Both bind HEAD, CMakeCache.txt, test-binary mtime, linked-library metadata, and manifest hash. Dirty trees, missing or updated linked libraries, changed configuration, failed builds, changed exclusions, or moved HEADs invalidate the gate. --build-all-tests performs an incremental build of every selected tree; --run-tests runs invalidated suites serially.

  • Stage 5 — Fetch upstream/main and sync

    • git fetch upstream && git rev-list --left-right --count upstream/main...main;
    • if the left count is non-zero, main is behind upstream and MUST be fast-forwarded and pushed to origin. If the right count is non-zero, main has stray commits (violates Main read-only): stop, show them, preserve anything recoverable, and request explicit permission before any hard reset or force-push.
    • Branch merged Check all [x] branches: for each, verify whether its commits are already present in upstream/main (git log upstream/main --oneline | grep <keyword>); if fully merged, move the entry to the "Merged to Upstream" section, remove the [x] marker, and record the merge date — MUST be done BEFORE rebasing or rebuilding so merged branches are excluded from both
  • Stage 6 — Review-state refresh: Check all open PRs for new review feedback (CHANGES_REQUESTED, new comments) and update INTEGRATION.md statuses accordingly.

    • gh pr view <PR-number>
    • gh pr list --repo mixxxdj/mixxx --author mxmilkiib
  • Stage 7 — Validate + rebase + transactional remerge + exact build + serial test + push — run --rebase-merge-test-push from ~/src/mixxx-dev/integration/.

    • The manifest order is topological: dependency roots are rebased first, and a dependent rebases onto its declared dependency rather than blindly onto upstream/main.
    • Remerge happens in a temporary detached worktree. A conflict, failed infrastructure restore, or interruption leaves the checked-out integration branch unchanged and disables rerere before returning. There is no partial-build or "continue without remerge" route.
    • Every phase is fatal. Neither integration nor integrating is pushed after a failed rebase, merge, build, or test.
    • --rebase-merge-test-push-promote additionally waits for CI on the exact pushed SHA and promotes integrated only after strict success.
    • After tests pass, the pipeline also builds the main mixxx executable for the integration worktree so one has a runnable local binary at ~/src/mixxx-dev/integration/build/mixxx.
    • Quick integration: When one only needs a runnable integration binary without local tests or remote operations, run --quick-integration instead of the full Stage 7. It rebases all branches, transactionally remerges, and builds both mixxx-test and mixxx for the integration tree only, skipping per-branch builds, local tests, pushes, CI, and promotion. Use this for fast iteration between full pipeline runs.
    • Personal-only backup: Committed personal-only branches SHOULD be pushed to origin for off-machine backup. Pushing a branch ref cannot back up uncommitted working-tree changes.
    • Monitoring progress: mixxx-milkii-integration-update-branches.sh --rebase-merge-test-push (or --rebase-merge-test-push-promote) writes timestamped phase/branch updates to STATUS_FILE=/tmp/mixxx-integration-status. In a second terminal: tail -f /tmp/mixxx-integration-status. Individual test suite logs: tail -f /tmp/mixxx-test-logs/<worktree>.log. During test runs, a heartbeat prints test count every 30 s to the main terminal so it never appears frozen. During CI polling (--promote-integrated or --rebase-merge-test-push-promote), the exact run ID, status, conclusion, SHA, and elapsed time are reported; use gh run view <id> --repo mxmilkiib/mixxx for per-job detail.
  • Stage 8 — CI-conscious feature-branch pushing:

    • feature branch force-pushes to origin trigger full CI matrix on mixxxdj/mixxx.
    • a pure rebase (same patch, different base) has zero CI value — it wastes shared runner time and annoys reviewers who monitor PR activity.
    • so rebased branches MUST NOT be automatically pushed
    • if any PR branches have actual content changes (review feedback addressed, conflict resolutions), run --push-changed. It compares stable patch IDs relative to each branch's manifest-declared base, so a pure rebase is skipped while a changed conflict resolution is pushed.
    • manual pushes (e.g. after addressing review feedback) MAY bypass this — push directly from the worktree as needed.
    • Conflict resolution: When resolving merge conflicts — whether during rebases or integration merges — conflicts MUST be carefully resolved and the operation continued non-interactively. Common issues:
      • Schema revisions: increment version numbers
      • Enum IDs in trackmodel.h: assign unique IDs
      • Header declarations vs implementations: keep both sides' additions
    • MUST update "Rebased" and "Updated" dates in the outline to today.
    • Branches with unresolved conflicts SHOULD be noted for later attention.
  • Stage 9 — Update INTEGRATION.md after the run:

    • Update the top Last updated date, outline summary counts, per-branch rebased/updated dates derived from commands, unresolved conflicts, review state, and CI/package status.
    • MUST NOT copy dates or success claims from an older revision. Anything not verified from Git, GitHub, or a test artifact is UNVERIFIED.
    • If worktree participation, merge order, a base, dependency, exclusion, infrastructure path, workflow identity, or package source changed, the manifest MUST be updated first and --validate-manifest rerun.
    • Sync every listed integration file to the Gist. --rebase-merge-test-push and --rebase-merge-test-push-promote call sync_gist automatically after a successful run; --sync-gist syncs on demand. Local edits are not deployed automation, and Gist upload is not Git branch deployment.
  • Merge Order: for Overlapping Source Files

    • Some active branches modify the same source files. Their authoritative merge_order values MUST preserve these relationships; filesystem order is irrelevant and MUST NOT control merges:
      • src/waveform/renderers/waveformoverviewrenderer.cpp and .h — 4 branches touch these:
          1. 2025.10oct.20-hotcues-on-overview-waveform (adds hotcue rendering to overview)
          1. 2026.08aug.28-library-overview-minute-markers (adds minute marker rendering, depends on hotcues)
          1. 2025.10oct.21-stacked-overview-waveform (adds StackedRGB enum + renderer)
          1. 2026.02feb.20-simple-waveform-top-and-overview (adds Simple overview type)
    • Each builds on the previous — merging out of order will cause conflict resolutions that silently drop changes.
      • src/preferences/dialog/dlgprefwaveform.cpp — 4 branches touch this:
          1. 2025.10oct.21-stacked-overview-waveform (adds Stacked to combobox)
          1. 2026.02feb.20-simple-waveform-top-and-overview (adds Simple to top of combobox)
          1. 2026.02feb.26-waveform-menu-order (reorders combobox via kValues, removes sort lambda)
          1. 2026.05may.03-extend-waveform-zoom-range (extends zoom range entries)
      • waveform-menu-order MUST merge after simple-waveform — the manifest enforces this explicitly.
      • src/library/library.cpp, src/library/sidebarmodel.cpp, src/library/sidebarmodel.h — only restore-last-library-selection touches these. The transactional remerge always starts from the current branch ref, so a rewritten branch cannot leave a stale prior merge in the candidate.
  • Integration remerge — automated by --rebase-merge-test-push and available alone as --remerge-integration.

    • Schema exclusion: Schema-changing branches MUST remain integrate: false until their revisions are compatible.

    • A detached temporary worktree starts at upstream/main, merges the manifest's integrate: true refs by merge_order, and restores exactly the manifest's infrastructure_files from the original committed integration SHA.

    • The real integration worktree MUST be clean and remains untouched until the candidate is complete. The candidate is then installed with one local reset. Failed or interrupted transactions clean their temporary worktree and disable rerere.

    • No cherry-pick: MUST ALWAYS merge branch refs. Cherry-picks duplicate commits and hide branch provenance.

    • Rerere is enabled only inside the temporary merge transaction and disabled on every return path; feature rebases never inherit integration conflict resolutions.

    • Infrastructure preservation: upstream/main does not contain the personal workflows, this document, or helper scripts, and upstream versions of develop.yml/pre-commit.yml do not contain the fork-specific changes. The transaction restores every path in infrastructure_files from the original committed integration SHA before installing the candidate. A missing path is fatal.

    • Conflict policy: Feature branches MUST stay suitable for standalone upstream review. Compatibility needed only when branch B is combined with branch A belongs in the integration merge and its rerere resolution, not in B's PR commits. Genuine branch defects MUST remain fixed on the branch itself.

    • A conflict MUST NOT be followed by build or promotion. The temporary merge is aborted and removed, the real branch remains unchanged, and the conflicting relationship MUST be resolved before rerunning the whole transaction.

    • Dry run: --remerge-integration --dry-run performs every step through candidate resolution but skips the final reset --hard, leaving integration at its original SHA. The rerere cache is still written, so a following real run reuses recorded resolutions. Side effects are limited to the temp worktree, the rerere.enabled config toggle, and rr-cache.

    • integration and integrating are reconstructed refs and therefore use --force-with-lease; integrated is promotion-only — both manual and automatic promotion force-update when the from-scratch remerge diverges from the previous integrated.

    • A full reconfigure is REQUIRED only when branches add CMake files or sources:

      cmake -B ~/src/mixxx-dev/integration/build -S ~/src/mixxx-dev/integration -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCCACHE_SUPPORT=ON
    • The common incremental build is:

      CCACHE_BASEDIR=~/src/mixxx-dev/integration nice -n 15 cmake --build ~/src/mixxx-dev/integration/build --target mixxx -- -j$(nproc --ignore=2)
  • Build type: All worktree builds MUST use CMAKE_BUILD_TYPE=RelWithDebInfo. Debug builds abort on DEBUG_ASSERT calls, causing tests to crash with non-zero exit that looks like a test failure (e.g. SoundSourceProxyTest.openEmptyFile firing FileInfo::canonicalLocation assert). Release builds suppress the crash but lose debug symbols. RelWithDebInfo is the correct balance. Check: grep CMAKE_BUILD_TYPE <worktree>/build/CMakeCache.txt. Reconfigure with: cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo <worktree>/build.

    • Build parallelism: NEVER launch multiple full worktree builds simultaneously — 5 × -j$(nproc) on a 32-core machine means 150 competing jobs and effectively no progress. Worktree builds MUST be run serially using -j$(nproc --ignore=2). With ccache + CCACHE_BASEDIR correctly set, each subsequent build is mostly cache hits making serial runs fast.
    • Killing builds: pkill -f cmake only kills the cmake wrapper — ninja/make/cc1plus children survive and saturate the CPU. To kill a full build tree: ps aux | grep -E 'cc1plus|ninja|/usr/bin/make' | grep -v grep | awk '{print $2}' | xargs -r kill -9. In the script, Ctrl-C triggers a kill 0 trap that kills the whole process group cleanly.
    • ccache: All worktree builds MUST be configured with -DCCACHE_SUPPORT=ON. Cache size SHOULD be 15 GB or more. To enable on an existing build: cmake -DCCACHE_SUPPORT=ON <worktree>/build (in-place reconfigure).
      • Cross-worktree sharing requires CCACHE_BASEDIR: worktrees are at different paths, so preprocessor #line markers embed different absolute paths in the hash. Setting CCACHE_BASEDIR=<worktree-root> at build time strips that prefix, making paths relative — identical upstream files then produce the same hash across worktrees. This is set automatically by mixxx-milkii-integration-update-branches.sh; manual builds MUST also set it: CCACHE_BASEDIR=~/src/mixxx-dev/<name> cmake --build ....
      • hash_dir = false in ~/.config/ccache/ccache.conf prevents the build directory path from entering the hash (complementary to CCACHE_BASEDIR). Both settings are REQUIRED for robust cross-worktree sharing.
  • Manifest selection: rebase, test, integrate, and gate are independent manifest booleans. This replaces SKIP_BRANCHES and filesystem-discovery policy. A dirty or intentionally dormant WIP can remain documented with rebase: false and test: false without silently entering a gate.

  • Exact build and test completeness:

    • Missing build trees are configured in parallel. Every selected mixxx-test, including integration/build/mixxx-test, is then built serially with -j$(nproc --ignore=2) and CCACHE_BASEDIR.
    • A clean successful build writes <name>.built; a passing serial test writes <name>.tested. Both contain the exact HEAD, binary mtime, CMake cache hash, linked-library metadata hash, and manifest hash. Only an exact pass is accepted. There are no dirty-pass or nocommits exceptions.
    • push_integrating checks every gate: true branch and the combined tree again. An old executable left behind by a failed build cannot satisfy the gate.
    • The separate outline field Tested?: means manual testing by Milkii on real DJ hardware; automated sentinels never change it.
    • Test exclusions exist only in the manifest. The update script, pre-push hook, and package workflow read them directly. Removing or adding an exclusion changes the manifest hash and invalidates prior test sentinels.
    • Local and package tests MUST run serially because concurrent mixxx-test processes share audio mocks, the ControlObject registry, timers, and SQLite test state.
    • GitHub Actions CI promotion is strict: the complete declared workflow MUST conclude success. A flaky or infrastructure failure MUST be rerun or fixed; a job name MUST NOT convert failure into success.
    • --promote-integrated and automatic promotion both bind the CI run, branch tip, and promoted ref to one SHA.
  • Test Exclusion Rationale

    • The manifest list is authoritative; these explanations record why each blind spot currently exists. An exclusion MUST be removed after its upstream cause is verified fixed, and a new exclusion MUST NOT be added merely to obtain a green run.
      • ControllerScriptEngineLegacyTest.*: softTakeover_setParameter hangs in the headless environment, and neighbouring tests can leave shared ControlObject state behind.
      • ControllerScriptEngineLegacyTimerTest.*: coTimerId was a ControlPotmeter capped at 50 while real QTimer IDs are much larger. Clamping creates ID collisions; beginTimer_repeatedTimer can poison later mapping tests. The local fix is tracked by bugfix/2026.05may.01-fix-timer-test-potmeter-clamping. Filter the whole suite, not only singleShot*.
      • AdjustReplayGainTest.AdjustReplayGainUpdatesPregain: consistently segfaults in the headless local/package environment; it remains a specific test exclusion, never an excuse for an entire platform job.
      • TrackMetadataExportTest.keepWithespaceKey: getKeyText() returns the internal B_FLAT_MINOR spelling instead of the expected display text B♭m.
      • MidiMappings/.*, HidMappings/.*, and BulkMappings/.*: parameterised fixtures load every bundled controller XML and initialise JavaScript engines. Residual timer/ControlObject state can hang a later fixture; Pioneer_CDJ_350_Ch2_midi_xml was one observed stopping point. The three suites share the same fixture lifecycle, so filtering only one family is not reliable.
    • The clean-room GitHub CI workflow is not converted to success by this list. Only the local pre-push/integration suite and Manjaro package test read these exclusions; strict CI still decides promotion.
  • Manjaro/Arch Release Pipeline

    • Default branch: The fork default is integrated because the auto-promote workflow_run listener MUST exist on the default branch. main remains a read-only upstream/main mirror. Verify rather than assume this repository setting.
    • Upstream release workflow: The fork's inherited release.yml remains disabled because a mirrored main push MUST NOT launch Mixxx's publishing matrix without upstream secrets.
    • Exact source gate:
      • A normal human push to integrated triggers packaging.
      • Automatic promotion explicitly calls workflow_dispatch with expected_sha; GitHub suppresses downstream push workflows for changes made with GITHUB_TOKEN.
      • Manual dispatch MUST use the exact current integrated SHA. The manifest job checks out that SHA and compares it with refs/heads/integrated; selecting another branch MUST NOT change the packaged source.
    • Manifest job: A small Ubuntu job checks out expected_sha, confirms the remote integrated ref still equals it, parses this document, and exports the container digest, test regex, and every external URL/commit. The build job consumes those outputs; workflow actions themselves are pinned to full commits in the workflow file.
    • Build environment:
      • Runner: ubuntu-24.04; build container: the digest-pinned package_sources.container, without --privileged.
      • pacman -Syu intentionally follows current rolling Manjaro packages, so source inputs are pinned but repository binaries are not bit-for-bit reproducible.
      • Base tools: base-devel, Git, CMake, Ninja, and ccache. Mixxx dependencies include protobuf, vamp-plugin-sdk, chromaprint, libid3tag, rubberband, soundtouch, LAME, Ogg/Vorbis, MAD, MP4v2, FAAD2, Opus, WavPack, libshout, libsndfile, PortMidi, PortAudio, SQLite, UPower, Lilv, libebur128, libopenmpt, Qt 6 modules, Microsoft GSL, hidapi, FFmpeg, FFTW, FLAC, GLU, PipeWire/JACK, libusb, jsoncpp, GoogleTest, and GoogleMock.
      • A non-root builder runs both makepkg operations and has no sudo permission. The workflow installs completed packages as root in a separate step.
    • Pinned AUR dependency:
      • taglib1 is absent from the Manjaro repositories. Its AUR Git commit is authoritative in package_sources.taglib1; the workflow MUST verify the detached checkout before running makepkg.
      • utf8cpp, cppunit, and zlib are installed for its build/check dependencies. PGP/checksum verification MUST NOT be bypassed.
    • CMake configuration:
      • CMAKE_BUILD_TYPE=RelWithDebInfo, QT6=ON, QML=ON, BULK=ON, FFMPEG=ON, LOCALECOMPARE=ON, MAD=ON, MODPLUG=OFF, OPENMPT=ON, WAVPACK=ON, BATTERY=ON, BROADCAST=ON, DOWNLOAD_MANUAL=ON, HID=ON, KEYFINDER=ON, LILV=ON, OPUS=ON, QTKEYCHAIN=ON, and VINYLCONTROL=ON.
      • User udev-rule installation is disabled; warnings and debug assertions are non-fatal for packaging. C and C++ both use ccache.
      • Cache path /ccache, 5 GB limit, hash_dir=false, exact-SHA save key, and prefix restore fallback.
    • Tests: CTest uses a 45-second per-test timeout, --parallel 1, failure output, and exactly the manifest exclusions. Parallel compilation is MAY; parallel tests MUST NOT.
    • Pinned personal resources:
      • Deere-Redo, Launchpad Pro MK3, Akai MPD218, and Korg nanoKONTROL2 repositories are checked out detached at their manifest commits and verified before selected files are copied into staging.
      • Changing a pin MUST review the old-to-new commit diff, change this manifest, and allow the manifest hash to invalidate prior local test sentinels.
    • Package construction:
      • CMake installs into a staging DESTDIR. Runtime linked libraries are mapped to owning packages with ldd and pacman -Qo; runtime-loaded plugins not visible to ldd remain covered by the explicit dependency installation above.
      • Generated identity: pkgname=mixxx-milkiib-integrated-manjaro, pkgver=<major.minor>_<UTC timestamp>_<short SHA>, pkgrel=1, arch=('x86_64'), conflicts=('mixxx' 'mixxx-git'), provides=('mixxx'), and unstripped debug symbols.
      • The staged /usr/local tree is relocated into the package's /usr tree. A package build failure MUST be fatal; no release MUST be created.
    • Release and provenance:
      • Pre-release tag: mixxx-milkiib-integrated-<UTC timestamp>-<short SHA>. The release remains explicitly unsigned and intended for personal integration testing.
      • Attachments are the package, its SHA-256 file, and a provenance file recording exact Mixxx SHA, git describe, container digest, AUR commit, skin commit, and controller commits.
    • Concurrency: Newer packaging cancels an older in-progress package build. This intentionally keeps only the newest daily-driver package when promotions arrive quickly. Packaging remains asynchronous after promotion; inspect it with gh run list --workflow mixxx-milkii-integration-manjaro-release.yml --repo mxmilkiib/mixxx.
    • Retention: The workflow does not prune old prereleases or tags. Deleting either MUST be a separate destructive operation requiring explicit permission.
  • Worktree Branch Hygiene

    • Upstream-resolved cleanup: If an upstream commit (by any contributor) fully resolves the problem a local branch was addressing — rendering the local branch redundant or superseded — the branch entry MUST also be moved to the "Merged to Upstream" section and marked RESOLVED (not MERGED), with a note identifying the upstream commit or PR that resolved it. The worktree MUST then be pruned per the Worktree pruning rule.
    • Upstream verification before closing: Before closing or marking a PR/branch as RESOLVED, the fix MUST be verified by reading the actual code in upstream/main and upstream/2.[latest-major]git show upstream/main:path/to/file.cpp | grep -A N "function". A verbal claim that the fix is upstream is NOT sufficient. If verification fails, the PR MUST NOT be closed.
    • Worktree pruning: When a branch is merged upstream, closed, or abandoned, its manifest entry and [x] marker MUST first be removed in the same reviewed infrastructure change, then its worktree MUST be removed.
      • The local branch ref MAY be deleted only with explicit permission.
      • --validate-manifest MUST intentionally fail while either the document or disk inventory still contains only one side of the change.
      • Merged cleanup: Move the outline entry to "Merged to Upstream" and record the upstream commit or PR.

Dev Helper Scripts

Script Purpose
mixxx-milkii-integration-update-branches.sh Parses and validates this document; performs dependency-aware rebases, transactional ordered remerge, exact build/test stamping for branches plus the combined tree, content-aware pushes, local gating, exact-SHA CI polling, and exact-SHA promotion (force-update when diverged)
mixxx-milkii-integration-pre-push.sh Reads exclusions and protected paths from this document; blocks integration infrastructure on every non-tier destination, requires formatting, incrementally rebuilds mixxx-test, rejects dirty/stale trees, and runs tests
mixxx-milkii-integration-gdb-run.sh Runs Mixxx non-interactively under GDB with debuginfod, forwards additional arguments, returns the inferior's exit status, collects crash diagnostics conditionally, and retains timestamped logs

Integration Helper Modes

Invocation Contract
no option Validate, fetch upstream, and rebase every rebase: true worktree in manifest dependency order; never push
--validate-manifest Parse JSON, validate defaults/dependencies/order/pins, compare [x] outline state, require exact worktree/ref/base/file coverage, and verify PR head identity via gh pr view for branches declaring pr
--remerge-integration Build and install one complete transactional combined candidate; never build or push
--remerge-integration --dry-run Build the candidate in the temp worktree and report the resulting SHA and merge list, but skip the final reset --hard; integration ref is left unchanged. Rerere cache is still populated, so a subsequent real run reuses recorded resolutions
--build-all-tests / --rebuild-tests Configure missing selected trees in parallel, then incrementally build every selected branch and the combined tree serially; write exact build stamps
--run-tests Run only tests whose exact build signature lacks a current pass; never accept a dirty tree
--push-changed Push only rebase: true branches whose stable patch IDs differ from their fork refs; pushes to pr_head when declared (legacy undated PR heads); pre-push checks still apply
--push-integrating Recheck every gate: true branch and the combined tree, then force-with-lease the exact integration SHA to integrating
--promote-integrated Poll only the declared push workflow for exact origin/integrating, require success and an unchanged ref, then update integrated (force-update when diverged)
--rebase-merge-test-push Run the complete local pipeline; every phase is fatal; syncs Gist after success
--rebase-merge-test-push-promote Run the local pipeline, wait for exact-SHA CI, promote after success; syncs Gist after success
--quick-integration Rebase, transactional remerge, build mixxx-test and mixxx for the integration tree only; skip local tests and all remote operations (push, CI, promotion). Use for fast iteration when one only needs a runnable integration binary
--sync-gist Sync every infrastructure file to the Gist on demand; non-fatal on failure

Runtime State

  • ~/.cache/mixxx-integration/<name>.built: <HEAD> <mixxx-test mtime> <CMakeCache hash> <linked-library metadata hash> <manifest hash>.
  • ~/.cache/mixxx-integration/<name>.tested: the same signature followed by pass or fail.
  • ~/.cache/mixxx-integration/tests-passed: combined integration SHA, manifest hash, and passing-tree count for audit only; per-tree sentinels remain authoritative.
  • /tmp/mixxx-integration-status: append-only phase and branch progress for tail -f.
  • /tmp/mixxx-test-logs/<name>.log: latest complete output for each test tree.

GDB Helper Details

  • Executable search order: integration/build/mixxx, $PWD/mixxx, then ~/src/mixxx/build/mixxx.
  • Default Mixxx arguments: --developer --controller-debug --debug-assert-break; caller arguments are appended without flattening or re-parsing.
  • GDB runs with pagination and confirmations disabled, pretty printing enabled, debuginfod enabled, and SIG32, SIGPIPE, SIGUSR1, and SIGUSR2 ignored.
  • A signalled stop records a full backtrace, registers, and all-thread backtraces. A normal exit skips unavailable stack commands. --return-child-result makes the shell status describe Mixxx rather than merely GDB command processing.
  • Logs are named mixxx_gdb-<user>_<YYYYMMDD_HHMMSS>.log beside the helper and retained for both clean and failed runs.

Outline Format Reference

This section documents the structure of this file for AI assistants and future maintainers.

Summary Line

Update the summary line when adding/removing branches. Every branch is counted exactly once, in the section it appears in — do not count a schema-excluded branch again under review-required:

**Summary** (verified YYYY-MM-DD; each branch counted once): A changes-addressed · B review-required · C changes-requested-open · D schema-excluded · E local-only/abandoned/archived · F personal-only · G secondary patches · H merged/resolved upstream · I untracked WIP worktree


### Section Order
1. 🔴 Awaiting Review from Others — a reviewer owes us a response and no change request is outstanding
2. 🟠 Changes Addressed — Awaiting Re-review — we answered a change request; GitHub may still report CHANGES_REQUESTED until re-review
3. 🔧 Secondary Patches
4. 🐛 Bug Fixes — Open PRs
5. 🟡 New Features — Open PRs
6. 🟤 External PRs (Testing) — upstream PRs by other contributors, cherry-picked for integration dogfooding; never pushed to origin
7. ⚠️ Schema-Changing Branches (Excluded from Integration)
8. 🔵 Local Only (No PR) — undecided WIP, archived, or abandoned branches with no PR
9. 🟣 Personal Only (No PR, deliberately not for upstream) — deliberately personal-use features that will not be submitted upstream
10. ✅ Merged to Upstream / Archived

Sections 1 and 2 are distinct: 1 means the ball is in a reviewer's court from the start, 2 means it
was in ours and we passed it back. `CHANGES_ADDRESSED` is a local status with no GitHub equivalent —
`reviewDecision` will keep saying `CHANGES_REQUESTED` until someone re-reviews, so the two MUST NOT be
treated as contradictory. Record both: the local judgement and the raw `reviewDecision`.


### Branch Entry Format
Branch naming convention: feature/YYYY.MMmon.DD-thing-descriptive-title

```markdown
- [x] **branch-name** - [#PR](url) - STATUS
  - Issue: [#ISSUE](url) (OPEN|CLOSED <reason> <date>)
  - Optional description
  - Created: YYYY-MM-DD, Last comment: YYYY-MM-DD (author), Last review: YYYY-MM-DD (author, STATE), Rebased: YYYY-MM-DD, Updated: YYYY-MM-DD
  - Next: Action item
  - Specifics:
    - Details about the branch and what probably should happen next
  • [x] = integrate: true in the authoritative manifest, [ ] = not integrated. Validation fails if the active outline and manifest disagree.
  • Branch name in bold, and it MUST be the real ref name — check with git -C <worktree> branch --show-current, not the worktree directory name, which may carry a date prefix the branch does not
  • Issue link to related Mixxx issue/feature request (if applicable), with its open/closed state — a closed driving issue changes whether the branch is still worth pursuing
  • Created date required for all branches
  • Last comment date is the most recent issue comment ("none" if none), with its author. Reviews are separate: a CHANGES_REQUESTED review is not a comment and will not show up in the comments list. Record both, from gh pr view <n> --json comments,reviews
  • Rebased date shows when branch was last rebased on mixxxdj/mixxx main ("none" if never). If the PR head is a different ref, say so — the local ref being rebased does not rebase the PR
  • Updated date tracks last modification to branch
  • Next action describes what needs to be done for this branch
  • Every date in an entry MUST come from a command, not from the previous version of this file. Carried-forward dates are the single most common defect found in cross-model audits
  • Within each section: [x] (integrated) branches first, then [ ] (not integrated) branches
  • Within each group ([x] or [ ]), sort by updated date (newest first)
  • STATUS is one of: DRAFT, REVIEW_REQUIRED, CHANGES_REQUESTED, MERGED, RESOLVED, LOCAL_ONLY, PERSONAL_ONLY, ARCHIVED
    • DRAFT: PR is a draft, not ready for review
    • REVIEW_REQUIRED: PR is open and awaiting first review
    • CHANGES_REQUESTED: reviewer has requested changes that have not yet been addressed
    • CHANGES_ADDRESSED: reviewer requested changes, changes have been made, awaiting re-review (no reviewer comment since the changes were pushed)
    • MERGED: PR has been merged into mixxxdj/mixxx
    • RESOLVED: no PR, or PR closed because the problem was resolved upstream by another contributor
    • LOCAL_ONLY: no PR, exists only locally with no decided upstream/personal status yet (e.g. uncommitted or undecided WIP)
    • PERSONAL_ONLY: no PR, personal-use feature that has been deliberately decided will not be submitted upstream
    • ARCHIVED: branch/PR abandoned or shelved indefinitely; worktree removed
  • Secondary patch entries use Resolves-residual-from or Depends-on instead of Issue to link to the primary branch

Branch Dependencies

utf8-string-controls (PERSONAL_ONLY)
├── hotcue-labelling (PERSONAL_ONLY)
└── hotcue-label-options (PERSONAL_ONLY)

Branches with dependencies on personal-only branches cannot be submitted upstream as-is. They MUST be refactored to remove the dependency or the dependency MUST be upstreamed first.

Branch and Integration Status Outline

Summary (verified 2026-08-25; each branch counted once): 7 changes-addressed · 7 review-required · 1 changes-requested-open · 2 schema-excluded · 7 local-only/abandoned/archived · 3 personal-only · 2 secondary patches · 2 external-testing · 10 merged/resolved upstream · 0 untracked WIP worktree

  • 🔴 Awaiting Review from Others
    • none
  • 🟠 Changes Addressed — Awaiting Re-review
    • feature/restore-last-library-selection - #15460 - CHANGES_ADDRESSED
      • Worktree: ~/src/mixxx-dev/2025.10oct.20-restore-last-library-selection/ (dir is dated, branch ref is not — branch matches the PR head, so pushes land correctly)
      • Issue: #10125 (OPEN)
      • Created: 2025-10-08, Last comment: 2026-08-08 (mxmilkiib), Last review: 2025-11-17 (ronso0, CHANGES_REQUESTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Local HEAD 57aa59b4f5 · origin/PR head e322da90bd — rebased-only since last push, patch identical, nothing to push
      • Next: Redone as two commits, confirmed working — awaiting re-review; mxmilkiib left update comment 2026-08-08
      • Specifics:
        • Separate commits for changes done - 4 commits with explanations
        • Store selection with debounced saves done - 3 second debounce timer
        • Use VERIFY_OR_DEBUG_ASSERT done
        • Root node crash in saveSelectionToConfig fixed
        • scheduleSelectionSave never called from clicked() fixed
        • DataRole mismatch in restoreLastSelection fixed — uses Qt::DisplayRole
        • activateDefaultSelection overwriting restore fixed — conditional fallback
        • Feature not activated on restore fixed — activate()/activateChild() called
        • Track row selection save/restore added via WTrackTableView
      • Tested?: yes
    • feature/2025.11nov.16-playback-position-control - #15617 - DRAFT - CHANGES_ADDRESSED
      • Issue: #14288 (OPEN)
      • Created: 2025-11-16, Last comment: 2026-02-09 (mxmilkiib), Last review: 2026-05-15 (ronso0, CHANGES_REQUESTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await re-review — ronso0 CHANGES_REQUESTED (2026-05-15) recorded as addressed 2026-05-26, but no comment or push is visible on the PR after 2026-02-09 and reviewDecision is still CHANGES_REQUESTED — UNVERIFIED, confirm before treating as addressed; CI failures are pre-existing flaky (Flatpak aarch64 network timeout, macOS x64 BeatsTranslateTest SEGFAULT — unrelated to our changes)
      • Specifics:
        • daschuer (Feb 9): "this feature already exists" (pref option) — clarified: pref has no CO for runtime control
        • ronso0 confirmed: if it's about changing marker pos on the fly, the pref option has no CO
        • Adds [Waveform],PlayMarkerPosition ControlPotmeter (0.0–1.0) for runtime control
      • Tested?: no
    • feature/2025.11nov.04-controller-wizard-quick-access - #15577 - CHANGES_ADDRESSED
      • Issue: #12262 (OPEN)
      • Created: 2025-11-04, Last comment: 2026-02-18 (mxmilkiib), Last review: 2025-11-16 (ronso0, CHANGES_REQUESTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Note: rebased with wmainmenubar.cpp/h conflict resolved (Controller+KeyboardEventFilter both included)
      • Next: Awaiting re-review — ronso0 CHANGES_REQUESTED (Nov 16) addressed Feb 18; fix-learning-wizard folded in Feb 22 (ffc28f8)
      • Specifics:
        • devicesChanged not updating menu post-startup fixed — connected to mappingApplied
        • range-for style on m_controllerPages done
        • fix-learning-wizard folded in: emit mappingStarted() before show() so prefs dialog hides before wizard appears
      • Tested?: yes
    • feature/2025.11nov.05-hide-unenabled-controllers - #15580 - CHANGES_ADDRESSED
      • Issue: #14275 (OPEN)
      • Created: 2025-11-05, Last comment: 2026-08-03 (mxmilkiib), Last review: 2025-11-17 (ronso0, COMMENTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Awaiting re-review — ronso0 Nov 17 feedback addressed Feb 28: removed redundant null checks, confirmed rename already done; GitHub reviewDecision is REVIEW_REQUIRED (no outstanding change request)
      • Specifics:
        • Rename "unenabled" to "disabled" everywhere — config keys, function names, and UI text (ronso0) done
        • Remove unnecessary null checks on tree items — always valid post-construction (ronso0) done
      • Tested?: yes
    • feature/2026.02feb.26-waveform-menu-order - #16046 - CHANGES_ADDRESSED
      • Created: 2026-02-26, Last comment: none (no issue comments), Last review: 2026-05-26 (daschuer, CHANGES_REQUESTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await re-review — addressed daschuer: removed lambda + alphabetical sort; order now set via kValues in waveformwidgettype.h
      • Specifics:
        • Reorders kValues in WaveformWidgetType: Simple, Filtered, HSV, RGB, Stacked, VSyncTest
        • Removes alphabetical combobox sort from DlgPrefWaveform — factory order is display order
        • Old: Simple, Filtered, HSV, VSyncTest, RGB, Stacked → New: Simple, Filtered, HSV, RGB, Stacked, VSyncTest
      • Tested?: yes (1203 tests pass)
    • feature/2025.10oct.21-stacked-overview-waveform - #15516 - DRAFT - CHANGES_ADDRESSED
      • PR head repointed 2026-08-21: fork branch feature/stacked-overview-waveform force-pushed to match the dated ref. The manifest declares pr_head so --push-changed keeps them in sync going forward.
      • Issue: #13265 (OPEN)
      • Created: 2025-10-21, Last comment: 2026-02-17 (mxmilkiib), Last review: 2025-11-01 (mxmilkiib, COMMENTED), Rebased: 2026-08-20 (local ref only), Updated: 2026-08-20
      • Next: Repoint the PR head — push the dated branch to origin/feature/stacked-overview-waveform after reviewing the diff, or close and reopen from the current ref. Then re-request review to unstale; no new reviewer feedback since the naming comment (Feb 17).
      • Specifics:
        • Remove redundant Stacked HSV and Stacked LMH renderers done
        • Remove unnecessary static_cast<int> done
        • Rename "Stacked (RGB)" to "Stacked" done
        • All feedback addressed
        • Left comment 2026-02-17 re: Filtered/Stacked naming confusion — see #15996
      • Tested?: yes
    • bugfix/2026.02feb.19-wayland-opengl-resize-warning - #16014 - CHANGES_ADDRESSED
      • Issue: #16013 (CLOSED 2026-02-21 as completed — the PR still adds the warning, but the issue no longer justifies it; check whether the PR is still wanted), related #13814 (OPEN), #14492 (OPEN)
      • Created: 2026-02-19, Last comment: 2026-08-03 (daschuer), Rebased: 2026-08-03, Updated: 2026-08-03
      • Base: upstream/2.6; the manifest rebases and tests it against that base but excludes it from the main-based combined tree and promotion gate
      • Next: Await review — rebased onto 2.6 per daschuer, daschuer CHANGES_REQUESTED addressed 2026-08-03, marked ready for review
      • Note: NOT in integration merges — branch now targets upstream/2.6, merging into main-based integration would drag in the 2.6↔main delta
      • Specifics:
        • Wayland + QOpenGLWindow subsurface resize causes synchronous compositor buffer realloc on every pixel of drag
        • Workaround: QT_QPA_PLATFORM=xcb (XWayland)
        • Adds qWarning at startup when Wayland detected with OpenGL waveforms and spinny widgets
        • Warning fires once per session (static flag), not per OpenGLWindow construction
        • Detection uses startsWith("wayland") to cover Qt5 variants (wayland-egl, wayland-generic, wayland-xcomposite-egl, wayland-xcomposite-glx)
        • MIXXX_USE_QOPENGL guard removed per daschuer — getSurfaceFormat callers are already GL-gated
        • References issues #16013 (slow resize), #14492 (sticky mouse on waveform) and #13814 (resize mouse defocus, GLSL types)
      • Tested?: yes (1174 tests pass on 2.6 base, 2026-08-03; stale pre-rebase mixxx-test binary was hanging the pre-push hook — rebuilt)
  • 🔧 Secondary Patches
    • bugfix/2026.05may.01-fix-timer-test-potmeter-clamping — upstream test bug
      • Created: 2026-05-01, Rebased: 2026-08-20
      • No PR (upstream or fork) exists — the "Next" below has been outstanding since 2026-05-01
      • coTimerId ControlPotmeter max=50 clamped QTimer IDs (10000+ in full suite); replaced with ControlObject
      • Next: open upstream PR to mixxxdj/mixxx
      • Workaround: pre-push hook and script filter ControllerScriptEngineLegacyTimerTest.* (entire suite — beginTimer_repeatedTimer corrupts clamped-ID-50 state, causing MidiMappings JS tests to hang; filtering only singleShot* is insufficient), ControllerScriptEngineLegacyTest.* (softTakeboard state poisoning), MidiMappings/.*/HidMappings/.*/BulkMappings/.* (MappingTestFixture hangs from residual state poisoning even with Legacy suites filtered — observed: Pioneer_CDJ_350_Ch2_midi_xml), and TrackMetadataExportTest.keepWithespaceKey (getKeyText() returns B_FLAT_MINOR internal string instead of B♭m display format, fails in all worktrees). Remove filters once upstream fix lands.
    • bugfix/2026.02feb.21-hid-init-race-on-enumeration
      • Created: 2026-02-21, Rebased: 2026-08-20, Updated: 2026-08-20
      • Note: originally tracked as residual from midi-makeinputhandler (#16003, merged upstream 2026-06-18) — stands alone
      • PR: #16838 — REVIEW_REQUIRED, opened 2026-08-04
      • Next: Await review on #16838
      • Merged into integration: 2026-08-05 (mutex fix merged — conflict in hidenumerator.cpp comment resolved)
      • Specifics:
        • The hidraw backend of hidapi is not thread-safe; hid_open()/hid_open_path() internally call hid_enumerate()udev_enumerate_scan_devices()
        • Upstream PR #15692 (Joerg, loadHidReportDescriptorAtEnumeration2) made each HidController constructor spawn a QtConcurrent::run background thread in fetchReportDescriptorInBackground() that calls hid_open*
        • With 3+ HID devices (Launchpad Pro MK3, MPD218, BeatMix4) those background threads race inside udev, corrupting the heap — manifests as free(): chunks in smallbin corrupted from an unrelated free() later (e.g. library scanner RecursiveScanDirectoryTask destructor)
        • Fix part 1: call hid_init() explicitly on the main thread in HidEnumerator::queryDevices() before hid_enumerate() and before any HidController objects are constructed
        • Fix part 2 (added 2026-08-05): guard the hid_open* calls in fetchReportDescriptorInBackground() with a global std::mutex s_hidOpenMutex so only one background fetch thread touches udev at a time — hid_init() alone was insufficient
      • Tested?: yes (crash no longer reproduced; library scan completes cleanly, 33091 tracks)
  • 🐛 BUG FIXES - Open PRs (REVIEW_REQUIRED)
    • bugfix/2026.02feb.19-textured-waveform-fbo-resize - #16010 - REVIEW_REQUIRED
      • Worktree: ~/src/mixxx-dev/2026.02feb.19-textured-waveform-fbo-resize/ (created 2026-08-20)
      • Created: 2026-02-19, Last comment: 2026-05-21 (stale-bot), Last review: none, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await review
      • Specifics:
        • Improved: defer FBO reallocation to paintGL via m_pendingResize flag
      • Tested?: yes
    • bugfix/2026.02feb.19-openglwindow-resize-repaint - #16012 - DRAFT - REVIEW_REQUIRED
      • Worktree: ~/src/mixxx-dev/2026.02feb.19-openglwindow-resize-repaint/ (created 2026-08-20)
      • Created: 2026-02-19, Last comment: 2026-05-21 (stale-bot), Last review: none, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await review
      • Specifics:
        • Restores m_dirty flag: defers extra paintGL+swapBuffers from resizeGL to next vsync
        • Does not fix Wayland resize lag (compositor-level issue)
      • Tested?: yes
  • 🟡 NEW FEATURES - Open PRs (REVIEW_REQUIRED)
    • feature/2025.11nov.05-deere-waveform-zoom-deck-colors - #16874 - REVIEW_REQUIRED
      • Created: 2025-11-05, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await review
      • Specifics:
        • Sets WaveformZoomContainer background to DeckBackgroundColor (was hardcoded #333333)
        • Changes zoom button icon fills from #d2d2d2 to #000000 for visibility on deck-colored background
        • Removes hardcoded background-color from style.qss (now set via XML BgColor)
        • Deere-only change, 5 files, 4 insertions / 4 deletions
      • Tested?: yes (2026-08-10)
    • feature/2026.05may.03-extend-waveform-zoom-range — No PR yet
      • Created: 2026-05-03, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Test, then open upstream PR
      • Specifics:
        • Extends s_waveformMinZoom from 1.0 → 0.5 (allows 200% zoom-in, twice as detailed)
        • Extends s_waveformMaxZoom from 10.0 → 80.0 (allows 1.25% zoom-out, 8× more overview)
        • Fixes DlgPrefWaveform combo box to handle sub-1.0 zoom entries without integer cast div-by-zero
        • Index↔zoom mapping generalised via subOneCount offset
      • Tested?: no
    • feature/2026.02feb.20-simple-waveform-top-and-overview - #16021 - CHANGES_REQUESTED
      • Issue: #16020 (OPEN)
      • Created: 2026-02-20, Last comment: 2026-05-28 (mxmilkiib, stale-bot reset), Last review: 2026-05-29 (daschuer, CHANGES_REQUESTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Address daschuer upgrade-path issue — RGB selected before update becomes Simple after; default should remain RGB; likely combobox position stored instead of enum value
      • Specifics:
        • Adds Simple as an overview waveform type (amplitude envelope, signal color, stereo mirrored)
        • Moves Simple to top of overview waveform combobox
        • daschuer CHANGES_REQUESTED: upgrade path alters waveform selection — RGB becomes Simple after update
        • ninomp inline: unused StackedRGB enum entry — mxmilkiib confirmed cruft from branch overlap
      • Tested?: yes (2026-07-16)
    • feature/2026.08aug.28-library-overview-minute-markers - #16967 - REVIEW_REQUIRED
      • Created: 2026-08-28, Updated: 2026-08-28
      • Next: Await review
      • Specifics:
        • Adds minute marker rendering to library overview waveform, gated on existing draw_overview_minute_markers preference
        • Markers drawn proportionally at 60s intervals as 2px white lines at top and bottom
        • Depends on hotcues-on-overview branch for TrackPointer API and trackDurationMillis plumbing
        • 3 files, 76 insertions / 41 deletions across waveformoverviewrenderer.cpp/.h, overviewcache.cpp
      • Tested?: no
    • feature/2026.08aug.22-library-waveform-uniform-time-base - #16925 - REVIEW_REQUIRED
      • Branch rebased 2026-08-28: now depends on feature/2026.08aug.28-library-overview-minute-markers instead of main; passes drawMinuteMarkers=false to renderer when uniform is on; redrawMinuteMarkersUniform gated on preference; SQL duration query replaced with pTrack->getDuration()
      • Created: 2026-08-22, Last comment: none, Last review: none, Updated: 2026-08-28
      • Next: Await review
      • Specifics:
        • Adds [Waveform],overview_uniform_time_base control and [Waveform],overview_time_base_minutes preference (default 6.0 min) so all library overview waveforms share the same pixels-per-second ratio; short tracks occupy less than the full column width, long tracks are clipped
        • When uniform is on, proportional minute markers from renderer are skipped and redrawMinuteMarkersUniform redraws them at exact pixel positions; both paths gated on draw_overview_minute_markers
        • 7 files, 230 insertions / 18 deletions across overviewcache.cpp/.h, overviewdelegate.cpp/.h, dlgprefwaveform.cpp/.h, dlgprefwaveformdlg.ui
      • Tested?: yes (2026-08-22)
    • feature/2026.08aug.08-waveform-invert-zoom-direction - #16928 - REVIEW_REQUIRED
      • Created: 2026-08-08, Last comment: none, Last review: none, Rebased: 2026-08-20, Updated: 2026-08-23
      • Next: Await review
      • Specifics:
        • Adds invert zoom direction preference for waveform mouse wheel (4 files, 42 insertions / 12 deletions across dlgprefwaveform.cpp/.h, dlgprefwaveformdlg.ui, wwaveformviewer.cpp)
      • Tested?: yes (2026-08-21)
    • feature/2025.10oct.21-replace-libmodplug-with-libopenmpt - #16921 - REVIEW_REQUIRED
      • Supersedes #15519 (CLOSED 2026-08-22)
      • Issue: #9862 (OPEN)
      • Created: 2025-10-25, Last comment: none, Last review: none, Rebased: 2026-08-20, Updated: 2026-08-22
      • Next: Await review on #16921
      • Specifics:
        • Tracker DSP moved from SoundSource decoder to native built-in effect (org.mixxx.effects.trackerdsp) per daschuer architecture feedback
        • OpenMPT decoding is bit-perfect — no optional DSP applied inside SoundSourceOpenMPT
        • Former module-FX controls (reverb, mega bass, surround, noise reduction) exposed as editable parameters in effect rows
        • Modplug preferences pane removed in favor of effect-row parameters
        • SoundSourceOpenMPTTest: open, seek consistency, read-beyond-end, multiple buffer sizes (6 tests)
        • TrackerEffectTest: manifest validation, disabled passthrough, enabled processing with individual and combined DSP effects, state reset (7 tests)
        • Full mixxx-test suite passes (1195 tests)
        • Intermittent double-free crash under heavy library scanning — not reproduced in 8 consecutive runs; investigation ongoing
        • Test fix 2026-02-19: taglibStringToEnumFileType now excludes all openmpt tracker formats
      • Tested?: no (human test with real module files and DJ setup still required)
    • feature/2025.10oct.20-hotcues-on-overview-waveform - #15514 - DRAFT - REVIEW_REQUIRED
      • PR head repointed 2026-08-21: fork branch feature/hotcues-on-overview-waveform force-pushed to match the dated ref. The manifest declares pr_head so --push-changed keeps them in sync going forward.
      • Branch rewritten 2026-08-28: minute markers split out into feature/2026.08aug.28-library-overview-minute-markers; this branch now contains hotcue rendering only, squashed to 1 commit
      • Issue: #14994 (OPEN)
      • Created: 2025-10-20, Last comment: 2026-01-19 (stale-bot), Last review: 2025-10-20 (ronso0, COMMENTED), Rebased: 2026-08-28 (rewrite), Updated: 2026-08-28
      • Next: Repoint the PR head to the dated ref, which also rebases it; await review
      • Specifics:
        • Hotcue rendering only (minute markers moved to separate branch)
        • OverviewCache API changed from TrackId to TrackPointer to access cue points and duration
        • Hotcues drawn as contrasting border + bright fill lines, matching deck overview style
      • Tested?: no
    • feature/2025.11nov.17-deere-channel-mute-buttons - #15624 - DRAFT - REVIEW_REQUIRED
      • Issue: #15623 (OPEN)
      • Created: 2025-11-17, Last comment: 2026-02-23 (ronso0), Last review: none, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: On hold - marked as DRAFT by ronso0
      • Specifics:
        • Marked as DRAFT by ronso0 (Feb 9)
        • daschuer (Feb 9): "mute this PR until we have demand and good plan for this turntableist feature"
        • Needs visual feedback for mute state in Mixxx
        • daschuer suggests "unmute by cue" is more accurate term than "silent cue"
        • ronso0 questions necessity — "Why is the Vol fader not sufficient?"
        • daschuer suggests broader approach: knob widget with integrated kill/mute feature, explore Tremolo effect for "Transformer" effect
        • Needs stronger justification or pivot to the broader knob-with-kill approach
      • Tested?: yes
    • feature/2026.08aug.23-deere-hotcue-count-selector - #16932 - DRAFT
      • Created: 2026-08-23, Last comment: 2026-08-25 (mxmilkiib), Updated: 2026-08-25
      • Next: Finish testing, mark as ready for review
      • Specifics:
        • Commits: e4da42d283 LateNight: add 16 hotcues option; 999e389f91 Deere: rework hotcue count selector using WidgetStack
        • ronso0 published related LateNight 16-hotcues mod #16934; adjusted to work like #16934 with #16941
      • Tested?: no
  • 🟤 External PRs (Testing — not our PRs, do not push)
    • feature/2026.03mar.09-head-split-reverse - #16137 - EXTERNAL
      • Author: yashrajpurohit7 · PR head: feature/head-split-reverse (on contributor's fork)
      • Issue: #14821
      • Created: 2026-08-25 (local cherry-pick), PR opened: 2026-03-09, Rebased: 2026-08-25, Updated: 2026-08-25
      • Local ref is a cherry-pick of the PR's single commit rebased onto upstream/main for integration dogfooding
      • Next: Monitor upstream PR for review feedback or merge; rebuild integration when the PR updates
      • Specifics:
        • Adds [Master],head_split_reverse ControlPushButton to invert split cue channels
        • When enabled, swaps left/right in split cue mode — main mix in left ear, headphone mix in right
        • Logic in processHeadphones() in enginemixer.cpp; TODO in PR: add to preferences panel
      • Tested?: no
    • feature/2026.05may.31-waveform-zoom-controlpotmeter - #12387 - EXTERNAL
      • Author: ronso0 · PR head: waveform-zoom-controlpotmeter (on contributor's fork)
      • Created: 2026-08-25 (local cherry-pick), PR opened: 2023-12-02, Rebased: 2026-08-25, Updated: 2026-08-25
      • Local ref is a cherry-pick of the PR's single commit rebased onto upstream/main for integration dogfooding
      • Next: Monitor upstream PR for review feedback or merge; rebuild integration when the PR updates
      • Specifics:
        • Converts waveform_zoom from ControlObject to ControlPotmeter (adds _up, _down, _set_default)
        • Step size 1 — _up/_down act exactly as before
        • C++ alternative to #12373; manual updated via mixxxdj/manual#722
        • Touches dlgprefwaveform.cpp (merge_order 220, after all local dlgprefwaveform branches) and Deere/style.qss
      • Tested?: no
  • ⚠️ Schema-Changing Branches (Excluded from Integration)
    • feature/2025.10oct.17-library-column-hotcue-count - #15462 - REVIEW_REQUIRED
      • PR head repointed 2026-08-21: fork branch feature/library-column-hotcue-count force-pushed to match the dated ref. The manifest declares pr_head so --push-changed keeps them in sync going forward.
      • Issue: #15461 (CLOSED 2025-11-16 as DUPLICATE — the driving issue is gone; decide whether the PR still has a home before spending more on it)
      • Created: 2025-10-17, Last comment: 2026-01-17 (stale-bot), Last review: none, Rebased: 2026-08-20 (local ref only), Updated: 2026-08-20
      • Next: Resolve the head mismatch and the closed-issue question before touching the schema work
      • Specifics:
        • PR marked stale (Jan 17 2026) — needs activity to unstale
        • Broad discussion about whether hotcue count column is the right approach vs a "prepared" state flag (daschuer, ronso0)
        • Potential pie chart icon instead of plain number (daschuer suggestion)
        • Related to hotcues-on-overview-waveform PR #15514 (acolombier suggested rendering hotcues in overview column instead)
        • Schema change v39→v40 — will conflict with other schema changes
        • Removed from integration: cross-thread SQLite crash (Qt::DirectConnection cuesUpdated lambda runs updateTrackHotcueCount on engine thread)
        • Crash fixed in branch: cuesUpdated now uses AutoConnection + DB-counting overload; CueDAO::updateTrackHotcueCount(TrackId) made public
      • Tested?: no
    • feature/2025.11nov.16-catalogue-number-column - #15616 - REVIEW_REQUIRED
      • Issue: #12583 (OPEN)
      • Created: 2025-11-16, Last comment: none (no issue comments), Last review: 2026-02-15 (mxmilkiib, COMMENTED), Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Await review
      • Specifics:
        • acolombier left review comment 2026-02-14; replied 2026-02-15
        • Schema migration revision 40 — will conflict with hotcue-count branch (also schema change)
        • Removed from integration: schema change; keeping integration at upstream schema v40 until schema branches are stable
        • Uses MusicBrainz Picard tag mapping conventions
      • Tested?: no
  • 🔵 Local Only (No PR)
    • feature/2026.08aug.23-register-middle-side-button-events — LOCAL_ONLY
      • Created: 2026-08-23, Updated: 2026-08-23
      • Commit: d19c2f454d — Register MiddleButton, BackButton, and ForwardButton events for buttons
      • Next: Test, decide whether to open an upstream PR
    • feature/2026.08aug.23-register-middle-wheel-scroll — LOCAL_ONLY
      • Depends-on: feature/2026.08aug.23-register-middle-side-button-events
      • Created: 2026-08-23, Updated: 2026-08-23
      • Commits: d19c2f454d (from side-button-events) + 60f7bab83d — Register scroll-wheel events for buttons
      • Next: Test, decide whether to open an upstream PR
    • latenight-16-hotcues-test — LOCAL_ONLY
      • Created: 2026-08-24, Updated: 2026-08-24
      • Commit: 95f46f6972 — LateNight: add 16 hotcues option
      • Note: branch ref lacks feature/ prefix — non-standard name
      • Next: Test, decide whether to open an upstream PR or fold into deere-hotcue-count-selector
    • draft/2025.10oct.21-tracker-module-stems — ARCHIVED
      • Created: 2025-10-21, Rebased: 2026-08-03, Updated: 2026-08-10, Archived: 2026-08-10
      • Test binary: [build-fail 2026-05-13]
      • Next: Archived — openmpt::module::set_channel_mute_status is absent from the stable C++ API (verified against libopenmpt 0.8.6); no alternative mute/channel-render method found. Depends on replace-libmodplug-with-libopenmpt (#15519) being accepted first. Worktree removed.
    • bugfix/2026.02feb.19-wglwidget-xcb-resize-gap — ABANDONED
      • Created: 2026-02-19, Updated: 2026-02-19
      • Next: Archive or delete branch
      • Specifics:
        • Attempted WA_PaintOnScreen on WGLWidget to reduce XCB resize gap
        • Abandoned: WGLWidget lacks paintEngine(), WA_PaintOnScreen causes heap corruption abort
        • Gap is inherent to QOpenGLWindow+createWindowContainer; no viable fix
  • 🟣 Personal Only (No PR, deliberately not for upstream)
    • feature/2025.10oct.08-utf8-string-controls — PERSONAL_ONLY
      • Dependency for: hotcue-labelling, hotcue-label-options
      • Created: 2025-10-08, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Maintain for personal use (not for upstream)
    • feature/2025.09sep.25-hotcue-labelling — PERSONAL_ONLY
      • Created: 2025-09-25, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Maintain for personal use
    • feature/2025.10oct.14-waveform-hotcue-label-options — PERSONAL_ONLY
      • Created: 2025-10-14, Rebased: 2026-08-20, Updated: 2026-08-20
      • Next: Maintain for personal use
  • Merged to Upstream
    • bugfix/2026.02feb.20-fix-learning-wizard-from-prefs-button - #16018 CLOSED 2026-02-28 — fix folded into #15577 (commit ffc28f8); bug only manifested in context of wizard menu changes; not a standalone upstream issue
    • bugfix/2026.02feb.18-midi-makeinputhandler-null-engine - #16003 MERGED 2026-06-18 — daschuer APPROVED; merged upstream (PR base 2.5); worktree removed 2026-06-18
    • feature/2025.06jun.08-deere-deck-bg-colour MERGED — commit cca2285b36 in upstream/main (change deck bg to subtle shade of deck colour, landed with Deere stems work); worktree removed 2026-05-11
    • feature/2025.05may.14-fivefourths - #16026 MERGED 2026-03-11 — merged upstream as Swarnadip-Kar's PR with fourfifths + BPM lock
    • bugfix/2026.02feb.20-controlpickermenu-quickfx-deck-offset - #16019 MERGED 2026-03-11 — ronso0's fix merged to main
    • bugfix/qt6-guiprivate-missing-component RESOLVED 2026-02-19 — fixed upstream, branch deleted
    • feature/2025.11nov.05-waveform-cache-size-format - #15578 MERGED 2026-02-16
    • bugfix/2025.11nov.04-reloop-shift-jog-seek - #15575 MERGED 2026-02-15
    • bugfix/2025.11nov.16-reloop-beatmix-mk2-naming - #15615 MERGED 2026-02-11
    • bugfix/2025.11nov.04-fx-routing-persistence - #15574 MERGED 2025-11-14

TODO Summary

This section is only the short list of things that are actively wrong or actively waiting on Milkii. Detailed per-branch status lives in the outline above and is not repeated here, aside from the branch name and a brief description of the state.

  • Blocking, system-level
    • The exact-SHA transactional pipeline changes dated 2026-08-21 are working-tree changes only. A human MUST review and commit them, sync the Gist, place them on integrating and default-branch integrated, then observe one complete CI promotion and package run before declaring the remote automation active.
    • 4 PRs point at legacy undated fork branches RESOLVED 2026-08-21: all 4 PR heads (#15516, #15519, #15514, #15462) force-pushed to match their dated refs. #15519 closed 2026-08-22 and superseded by #16921 (dated ref, no pr_head needed). The remaining 3 manifest entries declare pr and pr_head so --push-changed keeps them in sync.
  • Waiting on reviewers — 7 changes-addressed PRs; nothing to do but re-request review on the stale ones
  • Waiting on Milkii
    • #16021 simple-waveform — daschuer's upgrade path issue is the only unaddressed change request
    • #15519 libopenmpt — CLOSED 2026-08-22, superseded by #16921 (tracker DSP moved to effect rack, tests added)
    • extend-waveform-zoom-range and fix-timer-test-potmeter-clamping have no PR at all
    • #15624 deere-channel-mute-buttons is on hold pending a broader plan, not pending code
  • CI status — PASS. Run #33120201431 for SHA 777d21f25b043ae8653c84a119bc67c97141c7d3 completed with all jobs green (coverage, clazy, clang-tidy, all builds). integrated force-updated to this SHA (non-fast-forward because main moved between candidates). Includes uniform time base (#16925), invert zoom (#16928), deere hotcue count (#16932), and the OpenMPT duplicate MIME type fix.
  • Architecture changes needed: none outstanding — replace-libmodplug-with-libopenmpt tracker DSP effect rack refactor done, PR #16921 open for review
  • Archived: tracker-module-stems (libopenmpt API gap, worktree removed 2026-08-10)
  • Secondary patches: hid-init-race-on-enumeration (#16838 — REVIEW_REQUIRED, opened 2026-08-04), fix-timer-test-potmeter-clamping (no PR yet)
name: auto-promote to integrated; build and package
on:
workflow_run:
workflows:
- "Pull request or branch build"
branches:
- integrating
types:
- completed
permissions:
contents: write
actions: write
concurrency:
group: auto-promote
cancel-in-progress: false
jobs:
promote:
name: Promote the exact CI-tested SHA
runs-on: ubuntu-latest
steps:
- name: "Check out the tested integration configuration"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 1
- name: "Read the authoritative manifest"
id: manifest
run: |
python3 - <<'PY'
import json
import os
import pathlib
import re
text = pathlib.Path("INTEGRATION.md").read_text()
match = re.search(
r"<!-- MIXXX_INTEGRATION_MANIFEST_START -->\s*```json\s*(.*?)\s*```\s*<!-- MIXXX_INTEGRATION_MANIFEST_END -->",
text,
re.DOTALL,
)
if not match:
raise SystemExit("integration manifest not found")
data = json.loads(match.group(1))
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
output.write(f"release_workflow={data['release_workflow']}\n")
PY
- name: "Verify CI result and branch identity"
id: verify
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
run: |
set -euo pipefail
if [ "$RUN_CONCLUSION" != success ]; then
echo "CI concluded $RUN_CONCLUSION; promotion is blocked"
exit 1
fi
INTEGRATING_SHA=$(gh api "repos/${{ github.repository }}/git/refs/heads/integrating" --jq '.object.sha')
INTEGRATED_SHA=$(gh api "repos/${{ github.repository }}/git/refs/heads/integrated" --jq '.object.sha')
if [ "$INTEGRATING_SHA" != "$RUN_SHA" ]; then
echo "integrating moved from tested SHA $RUN_SHA to $INTEGRATING_SHA"
exit 1
fi
if [ "$INTEGRATED_SHA" = "$RUN_SHA" ]; then
echo "promote=false" >> "$GITHUB_OUTPUT"
echo "run_sha=$RUN_SHA" >> "$GITHUB_OUTPUT"
echo "integrated already equals $RUN_SHA"
exit 0
fi
COMPARE_STATUS=$(gh api "repos/${{ github.repository }}/compare/${INTEGRATED_SHA}...${RUN_SHA}" --jq '.status')
case "$COMPARE_STATUS" in
ahead)
echo "force=false" >> "$GITHUB_OUTPUT"
;;
diverged)
echo "force=true" >> "$GITHUB_OUTPUT"
echo "integrated diverged from tested SHA — force-updating per from-scratch remerge design"
;;
behind)
echo "tested SHA is behind integrated ($COMPARE_STATUS) — refusing to regress"
exit 1
;;
*)
echo "unexpected compare status: $COMPARE_STATUS"
exit 1
;;
esac
echo "promote=true" >> "$GITHUB_OUTPUT"
echo "run_sha=$RUN_SHA" >> "$GITHUB_OUTPUT"
- name: "Update integrated"
if: ${{ steps.verify.outputs.promote == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_SHA: ${{ steps.verify.outputs.run_sha }}
FORCE: ${{ steps.verify.outputs.force }}
run: |
set -euo pipefail
CURRENT=$(gh api "repos/${{ github.repository }}/git/refs/heads/integrating" --jq '.object.sha')
if [ "$CURRENT" != "$RUN_SHA" ]; then
echo "integrating moved to $CURRENT before promotion"
exit 1
fi
PROMOTED=$(gh api "repos/${{ github.repository }}/git/refs/heads/integrated" \
--method PATCH \
--field sha="$RUN_SHA" \
--field force="$FORCE" \
--jq '.object.sha')
test "$PROMOTED" = "$RUN_SHA"
- name: "Dispatch exact-SHA Manjaro packaging"
if: ${{ steps.verify.outputs.run_sha != '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_SHA: ${{ steps.verify.outputs.run_sha }}
RELEASE_WORKFLOW: ${{ steps.manifest.outputs.release_workflow }}
run: |
set -euo pipefail
EXISTING=$(gh run list \
--workflow "$RELEASE_WORKFLOW" \
--commit "$RUN_SHA" \
--repo "${{ github.repository }}" \
--limit 1 \
--json status,conclusion \
--jq '.[0] // empty | [.status, (.conclusion // "")] | @tsv')
if [ -n "$EXISTING" ]; then
IFS=$'\t' read -r STATUS CONCLUSION <<< "$EXISTING"
if [ "$STATUS" != completed ] || [ "$CONCLUSION" = success ]; then
echo "Packaging already $STATUS${CONCLUSION:+/$CONCLUSION} for $RUN_SHA"
exit 0
fi
fi
gh workflow run "$RELEASE_WORKFLOW" \
--repo "${{ github.repository }}" \
--ref integrated \
-f expected_sha="$RUN_SHA"
echo "Dispatched $RELEASE_WORKFLOW for $RUN_SHA"
#!/bin/bash
# Mixxx GDB runner with datetime logging
# Usage: mixxx-milkii-integration-gdb-run.sh [additional Mixxx arguments]
# Gist: https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
USERNAME=$(id -un)
LOG_FILE="${SCRIPT_DIR}/mixxx_gdb-${USERNAME}_${TIMESTAMP}.log"
MIXXX_ARGS=(--developer --controller-debug --debug-assert-break "$@")
MIXXX_EXE=""
if [[ -x "${SCRIPT_DIR}/build/mixxx" ]]; then
MIXXX_EXE="${SCRIPT_DIR}/build/mixxx"
elif [[ -x "${PWD}/mixxx" ]]; then
MIXXX_EXE="${PWD}/mixxx"
elif [[ -x "${HOME}/src/mixxx/build/mixxx" ]]; then
MIXXX_EXE="${HOME}/src/mixxx/build/mixxx"
fi
if [[ -z "$MIXXX_EXE" ]]; then
echo "Error: Could not find mixxx in ${SCRIPT_DIR}/build, the current directory, or ${HOME}/src/mixxx/build." >&2
exit 1
fi
# GDB convenience variables must remain literal.
# shellcheck disable=SC2016
GDB_OPTS=(
--batch
--return-child-result
-ex 'set pagination off'
-ex 'set print pretty on'
-ex 'set print frame-arguments scalars'
-ex 'set print thread-events off'
-ex 'set confirm off'
-ex 'set debuginfod enabled on'
-ex 'handle SIG32 nostop noprint'
-ex 'handle SIGPIPE nostop noprint'
-ex 'handle SIGUSR1 nostop noprint'
-ex 'handle SIGUSR2 nostop noprint'
-ex run
-ex 'if $_isvoid($_exitcode)'
-ex 'bt full'
-ex 'info registers'
-ex 'thread apply all bt'
-ex end
)
printf -v CMD '%q ' gdb "${GDB_OPTS[@]}" --args "$MIXXX_EXE" "${MIXXX_ARGS[@]}"
CMD="${CMD% }"
echo "Logging to: $LOG_FILE"
set +e
{
echo "# $CMD"
echo
gdb "${GDB_OPTS[@]}" --args "$MIXXX_EXE" "${MIXXX_ARGS[@]}" 2>&1
} | tee "$LOG_FILE"
EXIT_CODE=${PIPESTATUS[0]}
set -e
if [[ $EXIT_CODE -eq 0 ]]; then
echo "Clean exit, log saved to: $LOG_FILE"
else
echo "Exit code $EXIT_CODE, log saved to: $LOG_FILE"
fi
exit "$EXIT_CODE"
name: Manjaro/Arch Release
on:
push:
branches:
- integrated
workflow_dispatch:
inputs:
expected_sha:
description: Exact current integrated SHA to package
required: true
type: string
permissions:
contents: write
concurrency:
group: manjaro-release
cancel-in-progress: true
jobs:
manifest:
name: Resolve authoritative package inputs
runs-on: ubuntu-latest
outputs:
expected_sha: ${{ steps.manifest.outputs.expected_sha }}
container: ${{ steps.manifest.outputs.container }}
test_filter: ${{ steps.manifest.outputs.test_filter }}
taglib_url: ${{ steps.manifest.outputs.taglib_url }}
taglib_commit: ${{ steps.manifest.outputs.taglib_commit }}
deere_url: ${{ steps.manifest.outputs.deere_url }}
deere_commit: ${{ steps.manifest.outputs.deere_commit }}
launchpad_url: ${{ steps.manifest.outputs.launchpad_url }}
launchpad_commit: ${{ steps.manifest.outputs.launchpad_commit }}
mpd218_url: ${{ steps.manifest.outputs.mpd218_url }}
mpd218_commit: ${{ steps.manifest.outputs.mpd218_commit }}
nano2_url: ${{ steps.manifest.outputs.nano2_url }}
nano2_commit: ${{ steps.manifest.outputs.nano2_commit }}
steps:
- name: "Check out the requested SHA"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.expected_sha || github.sha }}
fetch-depth: 1
- name: "Validate integrated and read its manifest"
id: manifest
env:
EXPECTED_SHA: ${{ inputs.expected_sha || github.sha }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
INTEGRATED_SHA=$(git ls-remote origin refs/heads/integrated | cut -f1)
test "$INTEGRATED_SHA" = "$EXPECTED_SHA"
python3 - <<'PY'
import json
import os
import pathlib
import re
text = pathlib.Path("INTEGRATION.md").read_text()
match = re.search(
r"<!-- MIXXX_INTEGRATION_MANIFEST_START -->\s*```json\s*(.*?)\s*```\s*<!-- MIXXX_INTEGRATION_MANIFEST_END -->",
text,
re.DOTALL,
)
if not match:
raise SystemExit("integration manifest not found")
data = json.loads(match.group(1))
sources = data["package_sources"]
values = {
"expected_sha": os.environ["EXPECTED_SHA"],
"container": sources["container"],
"test_filter": "|".join(data["test_exclusions"]),
"taglib_url": sources["taglib1"]["url"],
"taglib_commit": sources["taglib1"]["commit"],
"deere_url": sources["deere_redo"]["url"],
"deere_commit": sources["deere_redo"]["commit"],
"launchpad_url": sources["launchpad_pro_mk3"]["url"],
"launchpad_commit": sources["launchpad_pro_mk3"]["commit"],
"mpd218_url": sources["akai_mpd218"]["url"],
"mpd218_commit": sources["akai_mpd218"]["commit"],
"nano2_url": sources["korg_nanokontrol2"]["url"],
"nano2_commit": sources["korg_nanokontrol2"]["commit"],
}
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
for key, value in values.items():
output.write(f"{key}={value}\n")
PY
build:
name: Build exact integrated package
needs: manifest
runs-on: ubuntu-24.04
container:
image: ${{ needs.manifest.outputs.container }}
env:
EXPECTED_SHA: ${{ needs.manifest.outputs.expected_sha }}
TEST_FILTER: ${{ needs.manifest.outputs.test_filter }}
TAGLIB_URL: ${{ needs.manifest.outputs.taglib_url }}
TAGLIB_COMMIT: ${{ needs.manifest.outputs.taglib_commit }}
DEERE_URL: ${{ needs.manifest.outputs.deere_url }}
DEERE_COMMIT: ${{ needs.manifest.outputs.deere_commit }}
LAUNCHPAD_URL: ${{ needs.manifest.outputs.launchpad_url }}
LAUNCHPAD_COMMIT: ${{ needs.manifest.outputs.launchpad_commit }}
MPD218_URL: ${{ needs.manifest.outputs.mpd218_url }}
MPD218_COMMIT: ${{ needs.manifest.outputs.mpd218_commit }}
NANO2_URL: ${{ needs.manifest.outputs.nano2_url }}
NANO2_COMMIT: ${{ needs.manifest.outputs.nano2_commit }}
steps:
- name: "Install base dependencies"
run: |
pacman -Syu --noconfirm
pacman -S --noconfirm --needed base-devel git cmake ninja ccache
id builder >/dev/null 2>&1 || useradd -m -s /bin/bash builder
- name: "Check out exact integrated source"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.manifest.outputs.expected_sha }}
fetch-depth: 0
- name: "Verify source identity"
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
git fetch origin --force --tags
- name: "Restore ccache"
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: /ccache
key: ccache-manjaro-${{ needs.manifest.outputs.expected_sha }}
restore-keys: |
ccache-manjaro-
- name: "Configure ccache"
run: |
mkdir -p /ccache
ccache -M 5G
ccache -o cache_dir=/ccache
ccache -o hash_dir=false
ccache -z
- name: "Install Mixxx build dependencies"
run: |
set -euo pipefail
pacman -S --needed --noconfirm \
protobuf vamp-plugin-sdk \
chromaprint libid3tag rubberband soundtouch \
lame libogg libmad libvorbis libmp4v2 faad2 opusfile wavpack \
libshout libsndfile portmidi portaudio \
sqlite upower lilv libebur128 libopenmpt \
qt6-declarative qtkeychain-qt6 qt6-svg qt6-shadertools \
qt6-5compat qt6-multimedia-ffmpeg qt6-scxml microsoft-gsl \
hidapi ffmpeg fftw flac glu pipewire pipewire-jack libusb jsoncpp \
gtest gmock utf8cpp cppunit zlib
if ! pacman -Qi taglib1 >/dev/null 2>&1; then
git clone "$TAGLIB_URL" /tmp/taglib1
git -C /tmp/taglib1 checkout --detach "$TAGLIB_COMMIT"
test "$(git -C /tmp/taglib1 rev-parse HEAD)" = "$TAGLIB_COMMIT"
chown -R builder:builder /tmp/taglib1
su builder -c 'cd /tmp/taglib1 && makepkg -f --noconfirm'
pacman -U --noconfirm /tmp/taglib1/*.pkg.tar.zst
fi
- name: "Patch default skin to Deere-Redo"
run: |
sed -i 's/return "LateNight"/return "Deere-Redo"/' src/skin/skinloader.cpp
grep -q 'return "Deere-Redo"' src/skin/skinloader.cpp
- name: "Configure"
run: >-
cmake
-S .
-B build
-GNinja
-DQT6=ON
-DQML=ON
-DBULK=ON
-DFFMPEG=ON
-DLOCALECOMPARE=ON
-DMAD=ON
-DMODPLUG=OFF
-DOPENMPT=ON
-DWAVPACK=ON
-DINSTALL_USER_UDEV_RULES=OFF
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DWARNINGS_FATAL=OFF
-DDEBUG_ASSERTIONS_FATAL=OFF
-DBATTERY=ON
-DBROADCAST=ON
-DDOWNLOAD_MANUAL=ON
-DHID=ON
-DKEYFINDER=ON
-DLILV=ON
-DOPUS=ON
-DQTKEYCHAIN=ON
-DVINYLCONTROL=ON
-DCMAKE_VERBOSE_MAKEFILE=OFF
-DCMAKE_C_COMPILER_LAUNCHER=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: "Build"
run: cmake --build build --config RelWithDebInfo --parallel "$(nproc)"
env:
CCACHE_NOCOMPRESS: true
CCACHE_DIR: /ccache
- name: "Print ccache stats"
run: ccache -s
env:
CCACHE_DIR: /ccache
- name: "Test serially"
run: ctest --test-dir build --timeout 45 --parallel 1 --exclude-regex "$TEST_FILTER"
env:
GTEST_COLOR: 1
CTEST_OUTPUT_ON_FAILURE: 1
- name: "Generate build identity"
id: git-describe
run: |
GIT_DESC=$(git describe --always --first-parent --dirty=-modified)
SHORT_SHA=$(git rev-parse --short HEAD)
TIMESTAMP=$(date -u +%Y%m%d-%H%M)
PKGVER_TIMESTAMP=$(date -u +%Y%m%d_%H%M)
MIXXX_VERSION=$(grep -m1 'project(mixxx VERSION' CMakeLists.txt | sed 's/.*VERSION \([0-9]*\.[0-9]*\).*/\1/')
echo "git_describe=${GIT_DESC}" >> "$GITHUB_OUTPUT"
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "timestamp=${TIMESTAMP}" >> "$GITHUB_OUTPUT"
echo "mixxx_version=${MIXXX_VERSION}" >> "$GITHUB_OUTPUT"
echo "GIT_DESC=${GIT_DESC}" >> "$GITHUB_ENV"
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
echo "PKGVER_TIMESTAMP=${PKGVER_TIMESTAMP}" >> "$GITHUB_ENV"
echo "MIXXX_VERSION=${MIXXX_VERSION}" >> "$GITHUB_ENV"
- name: "Install to staging directory"
run: |
DESTDIR="${GITHUB_WORKSPACE}/staging" cmake --install build --config RelWithDebInfo
chmod -R a+rX "${GITHUB_WORKSPACE}/staging"
- name: "Fetch pinned personal resources"
run: |
set -euo pipefail
STAGING="${GITHUB_WORKSPACE}/staging"
MIXXX_BIN=$(find "$STAGING" -name mixxx -type f -print -quit)
STAGING_USR=$(dirname "$(dirname "$MIXXX_BIN")")
MIXXX_DATA="${STAGING_USR}/share/mixxx"
WORK="${GITHUB_WORKSPACE}/.mixxx-extras"
mkdir -p "${MIXXX_DATA}/skins" "${MIXXX_DATA}/controllers" "$WORK"
cd "$WORK"
checkout_pinned() {
local url="$1" commit="$2" directory="$3"
git clone "$url" "$directory"
git -C "$directory" checkout --detach "$commit"
test "$(git -C "$directory" rev-parse HEAD)" = "$commit"
}
checkout_pinned "$DEERE_URL" "$DEERE_COMMIT" deere-redo
rm -rf deere-redo/.git
mkdir -p "${MIXXX_DATA}/skins/Deere-Redo"
cp -aT deere-redo/ "${MIXXX_DATA}/skins/Deere-Redo/"
rm -rf deere-redo
rm -f \
"${MIXXX_DATA}/skins/Deere-Redo/CHANGELOG_FORK.md" \
"${MIXXX_DATA}/skins/Deere-Redo/CHANNEL_MUTE_ANALYSIS.md" \
"${MIXXX_DATA}/skins/Deere-Redo/TEMPLATE_STRUCTURE_OUTLINE.txt" \
"${MIXXX_DATA}/skins/Deere-Redo/skin_include_tree.dot" \
"${MIXXX_DATA}/skins/Deere-Redo/skin_include_tree.png" \
"${MIXXX_DATA}/skins/Deere-Redo/style.qss.backup"
find "${MIXXX_DATA}/skins/Deere-Redo" -type f \( -name '*.xml' -o -name '*.qss' \) \
-exec sed -i 's/\[Deere\]/[Deere-Redo]/g' {} +
checkout_pinned "$LAUNCHPAD_URL" "$LAUNCHPAD_COMMIT" lppro
cp lppro/LaunchpadProMK3.midi.xml lppro/LaunchpadProMK3.js "${MIXXX_DATA}/controllers/"
rm -rf lppro
checkout_pinned "$MPD218_URL" "$MPD218_COMMIT" mpd218
cp mpd218/Akai-MPD218-milkii.midi.xml mpd218/Akai-MPD218-milkii-scripts.js "${MIXXX_DATA}/controllers/"
rm -rf mpd218
checkout_pinned "$NANO2_URL" "$NANO2_COMMIT" nano2
cp nano2/Korg-nanoKONTROL-2-milkii.midi.xml nano2/Korg-nanoKONTROL-2-scripts-milkii.js "${MIXXX_DATA}/controllers/"
rm -rf nano2
chmod -R a+rX "${MIXXX_DATA}/skins/Deere-Redo" "${MIXXX_DATA}/controllers"
- name: "Create Manjaro/Arch package"
shell: bash
run: |
set -euo pipefail
PKG_DIR="${GITHUB_WORKSPACE}/pkgbuild"
STAGING="${GITHUB_WORKSPACE}/staging"
PKGVER="${MIXXX_VERSION}_${PKGVER_TIMESTAMP}_${SHORT_SHA}"
mkdir -p "$PKG_DIR"
MIXXX_BIN=$(find "$STAGING" -name mixxx -type f -print -quit)
STAGING_USR=$(dirname "$(dirname "$MIXXX_BIN")")
DEPS=$(ldd "$MIXXX_BIN" 2>/dev/null \
| awk '/=> \// {print $3}' \
| while read -r lib; do pacman -Qo "$lib" 2>/dev/null; done \
| sed 's/.*is owned by \([^ ]*\) .*/\1/' \
| awk -F'[<>=]' '{print $1}' \
| sort -u \
| grep -v -E '^(gcc|glibc)$' \
| sed "s/^/'/; s/$/'/" \
| tr '\n' ' ' || true)
printf '%s\n' \
'# Maintainer: mxmilkiib' \
'pkgname=mixxx-milkiib-integrated-manjaro' \
"pkgver=${PKGVER}" \
'pkgrel=1' \
'pkgdesc="Digital DJ software (integration build)"' \
"arch=('x86_64')" \
'url="https://mixxx.org"' \
"license=('GPL2')" \
"depends=(${DEPS})" \
"conflicts=('mixxx' 'mixxx-git')" \
"provides=('mixxx')" \
"options=('!strip' '!emptydirs')" \
'' \
'package() {' \
' cp -a "'"${STAGING_USR}"'/" "${pkgdir}/usr/"' \
'}' \
> "${PKG_DIR}/PKGBUILD"
chown -R builder:builder "$PKG_DIR"
su builder -c "cd ${PKG_DIR} && makepkg -f --noconfirm"
package_files=("${PKG_DIR}"/*.pkg.tar.zst)
test -f "${package_files[0]}"
echo "PACKAGE_FILE=${package_files[0]}" >> "$GITHUB_ENV"
echo "PACKAGE_NAME=$(basename "${package_files[0]}")" >> "$GITHUB_ENV"
- name: "Generate checksum and provenance"
run: |
CHECKSUM_FILE="${PACKAGE_FILE}.sha256"
PROVENANCE_FILE="${PACKAGE_FILE}.provenance.txt"
sha256sum "$PACKAGE_FILE" > "$CHECKSUM_FILE"
printf '%s\n' \
"mixxx_sha=${EXPECTED_SHA}" \
"git_describe=${GIT_DESC}" \
"container=${{ needs.manifest.outputs.container }}" \
"taglib1=${TAGLIB_COMMIT}" \
"deere_redo=${DEERE_COMMIT}" \
"launchpad_pro_mk3=${LAUNCHPAD_COMMIT}" \
"akai_mpd218=${MPD218_COMMIT}" \
"korg_nanokontrol2=${NANO2_COMMIT}" \
> "$PROVENANCE_FILE"
echo "CHECKSUM_FILE=$CHECKSUM_FILE" >> "$GITHUB_ENV"
echo "PROVENANCE_FILE=$PROVENANCE_FILE" >> "$GITHUB_ENV"
- name: "Upload package artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: manjaro-package
path: |
${{ env.PACKAGE_FILE }}
${{ env.CHECKSUM_FILE }}
${{ env.PROVENANCE_FILE }}
- name: "Create GitHub prerelease"
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: mixxx-milkiib-integrated-${{ steps.git-describe.outputs.timestamp }}-${{ steps.git-describe.outputs.short_sha }}
name: "Mixxx Integrated Manjaro Build ${{ steps.git-describe.outputs.mixxx_version }}-pre-${{ steps.git-describe.outputs.short_sha }}"
body: |
## Manjaro/Arch Linux Package
Built and tested from the exact `integrated` commit `${{ needs.manifest.outputs.expected_sha }}`.
The attached checksum and provenance files record all pinned non-repository inputs.
### Install
```bash
sudo pacman -U ${{ env.PACKAGE_NAME }}
```
### Notes
- This is an unsigned personal integration build for testing
- Tests ran serially with exclusions declared in `INTEGRATION.md`
- Dependencies are resolved by pacman
files: |
${{ env.PACKAGE_FILE }}
${{ env.CHECKSUM_FILE }}
${{ env.PROVENANCE_FILE }}
prerelease: true
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Save ccache"
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: /ccache
key: ccache-manjaro-${{ needs.manifest.outputs.expected_sha }}
#!/bin/bash
# Pre-push hook logic for mxmilkiib/mixxx.
# The actual .git/hooks/pre-push delegates to this versioned script.
# Gist: https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6
set -euo pipefail
remote="$1"
url="$2"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
INTEGRATION_DOC="$SCRIPT_DIR/INTEGRATION.md"
REPO_ROOT="$(git rev-parse --show-toplevel)"
BUILD_DIR="$REPO_ROOT/build"
HOOK_TIMEOUT=420
BUILD_JOBS=$(( $(nproc) - 2 )); [[ $BUILD_JOBS -lt 1 ]] && BUILD_JOBS=1
manifest_query() {
python3 - "$INTEGRATION_DOC" "$@" <<'PY'
import json
import pathlib
import re
import sys
text = pathlib.Path(sys.argv[1]).read_text()
match = re.search(
r"<!-- MIXXX_INTEGRATION_MANIFEST_START -->\s*```json\s*(.*?)\s*```\s*<!-- MIXXX_INTEGRATION_MANIFEST_END -->",
text,
re.DOTALL,
)
if not match:
raise SystemExit("integration manifest not found")
data = json.loads(match.group(1))
if sys.argv[2] == "test-filter":
print(":".join(data["test_exclusions"]))
elif sys.argv[2] == "infrastructure-files":
print("\n".join(data["infrastructure_files"]))
elif sys.argv[2] == "branch-base":
defaults = data["branch_defaults"]
ref = sys.argv[3]
for branch in data["branches"]:
if branch["ref"] == ref:
print(branch.get("base", defaults["base"]))
break
else:
raise SystemExit(f"branch absent from manifest: {ref}")
else:
raise SystemExit("unknown manifest query")
PY
}
KNOWN_FAILING="$(manifest_query test-filter)" || exit 1
mapfile -t PROTECTED_FILES < <(manifest_query infrastructure-files)
[[ ${#PROTECTED_FILES[@]} -gt 0 ]] || { echo "ERROR: Manifest has no infrastructure files."; exit 1; }
[[ ! -t 0 ]] || { echo "ERROR: Pre-push ref updates are required on stdin."; exit 1; }
mapfile -t UPDATES
[[ ${#UPDATES[@]} -gt 0 ]] || { echo "ERROR: Git supplied no pre-push ref updates."; exit 1; }
has_non_delete=false
declare local_branch declared_base format_base infra_base
declare -a format_bases=() format_heads=()
for update in "${UPDATES[@]}"; do
read -r local_ref local_oid remote_ref remote_oid <<< "$update"
[[ "$local_oid" == "0000000000000000000000000000000000000000" ]] && continue
has_non_delete=true
local_branch="${local_ref#refs/heads/}"
if ! declared_base=$(manifest_query branch-base "$local_branch" 2>/dev/null); then
declared_base=upstream/main
fi
if [[ "$remote_oid" == "0000000000000000000000000000000000000000" ]]; then
format_base=$(git merge-base "$local_oid" "$declared_base")
else
if ! env GIT_PAGER=cat git cat-file -e "$remote_oid^{commit}" 2>/dev/null; then
env GIT_PAGER=cat git fetch --no-tags "$remote" "$remote_ref"
fi
# For force-pushes where remote_oid is not an ancestor of local_oid
# (e.g. PR head repoint), use the declared base so only local commits
# are format-checked, not upstream commits in the old→new range.
if env GIT_PAGER=cat git merge-base --is-ancestor "$remote_oid" "$local_oid" 2>/dev/null; then
format_base="$remote_oid"
else
format_base=$(git merge-base "$local_oid" "$declared_base")
fi
fi
case "$remote_ref" in
refs/heads/integration|refs/heads/integrating|refs/heads/integrated)
# Promotion branches carry merge commits from multiple feature
# branches; each branch was already format-checked individually.
# git clang-format cannot traverse merge-commit ranges, and the
# infrastructure file check is also skipped for these refs.
continue
;;
esac
format_bases+=("$format_base")
format_heads+=("$local_oid")
# Infrastructure file check: only check local commits above the declared
# base, not upstream commits in the old→new range. Upstream commits that
# touch develop.yml etc. are fine — they are not our integration changes.
infra_base=$(git merge-base "$local_oid" "$declared_base")
for file in "${PROTECTED_FILES[@]}"; do
if env GIT_PAGER=cat git log --diff-filter=ACDMR --name-only --pretty=format: \
"${infra_base}..${local_oid}" -- "$file" | grep -q .; then
echo "ERROR: $file is integration-only and cannot be pushed to $remote_ref ($url)."
exit 1
fi
done
done
$has_non_delete || exit 0
echo "Running clang-format style checks for every pushed range..."
if ! command -v git-clang-format >/dev/null 2>&1; then
echo "ERROR: git-clang-format is required before pushing."
exit 1
fi
for index in "${!format_heads[@]}"; do
if ! style_diff=$(git clang-format --diff "${format_bases[$index]}" \
"${format_heads[$index]}" -- "*.cpp" "*.h" 2>/dev/null); then
echo "ERROR: git clang-format could not inspect ${format_bases[$index]}..${format_heads[$index]}."
exit 1
fi
if [[ -n "$style_diff" &&
"$style_diff" != "no modified files to format" &&
"$style_diff" != "clang-format did not modify any files" ]]; then
echo "Style check failed for ${format_bases[$index]}..${format_heads[$index]}."
printf '%s\n' "$style_diff" | head -30
exit 1
fi
done
if [[ ! -f "$BUILD_DIR/CMakeCache.txt" ]]; then
echo "ERROR: Build directory is not configured at $BUILD_DIR."
exit 1
fi
if ! grep -q '^CMAKE_BUILD_TYPE:STRING=RelWithDebInfo$' "$BUILD_DIR/CMakeCache.txt"; then
echo "ERROR: $BUILD_DIR must use CMAKE_BUILD_TYPE=RelWithDebInfo."
exit 1
fi
if ! CCACHE_BASEDIR="$REPO_ROOT" nice -n 15 cmake --build "$BUILD_DIR" \
--target mixxx-test -- -j"$BUILD_JOBS"; then
echo "ERROR: mixxx-test failed to build."
exit 1
fi
if [[ ! -f "$BUILD_DIR/mixxx-test" ]] || \
ldd "$BUILD_DIR/mixxx-test" 2>/dev/null | grep -q "not found"; then
echo "ERROR: mixxx-test is missing or has unresolved shared libraries."
exit 1
fi
if ! env GIT_PAGER=cat git -C "$REPO_ROOT" diff --quiet || \
! env GIT_PAGER=cat git -C "$REPO_ROOT" diff --cached --quiet; then
echo "ERROR: Tracked working-tree changes make the test result ambiguous."
exit 1
fi
echo "Running Mixxx tests before push..."
set +e
(cd "$BUILD_DIR" && timeout "$HOOK_TIMEOUT" ./mixxx-test \
--gtest_filter="-${KNOWN_FAILING}")
rc=$?
set -e
if [[ $rc -eq 0 ]]; then
echo "All tests passed."
exit 0
fi
if [[ $rc -eq 124 ]]; then
echo "ERROR: Tests timed out after ${HOOK_TIMEOUT}s."
else
echo "ERROR: Tests failed with exit code $rc."
fi
exit 1
#!/bin/bash
# Mixxx Integration Branch Helper
# Manages worktree rebases, test binary rebuilds, test runs, and branch pushes.
# MUST be committed to the integration branch — see INTEGRATION.md.
# Gist: https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6
#
# Usage:
# ./mixxx-milkii-integration-update-branches.sh --validate-manifest
# ./mixxx-milkii-integration-update-branches.sh # rebase manifest-selected worktrees
# ./mixxx-milkii-integration-update-branches.sh --build-all-tests # build selected branches + integration
# ./mixxx-milkii-integration-update-branches.sh --run-tests # serial exact-build tests
# ./mixxx-milkii-integration-update-branches.sh --remerge-integration # transactional clean remerge
# ./mixxx-milkii-integration-update-branches.sh --remerge-integration --dry-run # build candidate, do not install
# ./mixxx-milkii-integration-update-branches.sh --push-changed # push branches with changed patches
# ./mixxx-milkii-integration-update-branches.sh --push-integrating # enforce local gate and trigger CI
# ./mixxx-milkii-integration-update-branches.sh --promote-integrated # promote exact successful CI SHA
# ./mixxx-milkii-integration-update-branches.sh --rebase-merge-test-push
# ./mixxx-milkii-integration-update-branches.sh --rebase-merge-test-push-promote
# ./mixxx-milkii-integration-update-branches.sh --quick-integration # rebase+remerge+build integration only, skip tests and remote
#
# INTEGRATION.md is authoritative for managed worktrees, bases, dependencies,
# merge order, gate membership, test exclusions, workflows, and infrastructure.
#
# Three-branch promotion chain:
# integration — transactional combined merge candidate
# integrating — exact integration SHA built and tested locally
# integrated — the same SHA after strict GitHub Actions success
#
# Runtime state files (not committed):
# ~/.cache/mixxx-integration/<name>.built — exact build signature
# ~/.cache/mixxx-integration/<name>.tested — build signature plus pass/fail
# ~/.cache/mixxx-integration/tests-passed — combined pass summary
# /tmp/mixxx-integration-status — progress log
# /tmp/mixxx-test-logs/<name>.log — per-tree gtest output
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
MIXXX_MAIN="$SCRIPT_DIR"
MIXXX_DEV="$(dirname "$MIXXX_MAIN")"
INTEGRATION_DOC="$MIXXX_MAIN/INTEGRATION.md"
BUILD_JOBS=$(( $(nproc) - 2 )); [[ $BUILD_JOBS -lt 1 ]] && BUILD_JOBS=1
BUILD_NICE=15
TEST_LOG_DIR="/tmp/mixxx-test-logs"
CACHE_DIR="${HOME}/.cache/mixxx-integration"
check_deps() {
local missing=() warn=()
local required=(git gh python3 cmake ninja ccache ldd timeout nice nproc awk free du cut stat sha256sum mktemp)
local optional_map=(
"sensors:sensors (lm-sensors) is optional — omitted from sys_stats CPU temp"
"tqdm:tqdm is optional — progress bars during builds fall back to plain output"
)
for cmd in "${required[@]}"; do
command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
done
for entry in "${optional_map[@]}"; do
local cmd="${entry%%:*}" msg="${entry#*:}"
command -v "$cmd" >/dev/null 2>&1 || warn+=(" WARN: $msg")
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "ABORT: missing required dependencies: ${missing[*]}" >&2
printf ' install: %s\n' "${missing[@]}" >&2
exit 1
fi
for w in "${warn[@]}"; do echo "$w" >&2; done
}
check_deps
manifest_query() {
python3 - "$INTEGRATION_DOC" "$@" <<'PY'
import hashlib
import json
import pathlib
import re
import sys
path = pathlib.Path(sys.argv[1])
action = sys.argv[2]
text = path.read_text()
match = re.search(
r"<!-- MIXXX_INTEGRATION_MANIFEST_START -->\s*```json\s*(.*?)\s*```\s*<!-- MIXXX_INTEGRATION_MANIFEST_END -->",
text,
re.DOTALL,
)
if not match:
raise SystemExit(f"{path}: integration manifest markers not found")
data = json.loads(match.group(1))
defaults = data["branch_defaults"]
branches = [{**defaults, **branch} for branch in data["branches"]]
if action == "validate-json":
if data.get("schema") != 1:
raise SystemExit("unsupported integration manifest schema")
refs = [branch["ref"] for branch in branches]
worktrees = [branch["worktree"] for branch in branches]
if len(refs) != len(set(refs)) or len(worktrees) != len(set(worktrees)):
raise SystemExit("duplicate branch ref or worktree in integration manifest")
known = set(refs)
seen = set()
merge_orders = []
for branch in branches:
missing = set(branch["dependencies"]) - known
if missing:
raise SystemExit(f"{branch['ref']}: unknown dependencies: {sorted(missing)}")
unordered = set(branch["dependencies"]) - seen
if unordered:
raise SystemExit(f"{branch['ref']}: dependencies must appear first: {sorted(unordered)}")
for key in ("rebase", "test", "integrate", "gate"):
if not isinstance(branch[key], bool):
raise SystemExit(f"{branch['ref']}: {key} must be boolean")
if branch["integrate"]:
if not all((branch["rebase"], branch["test"], branch["gate"])):
raise SystemExit(f"{branch['ref']}: integrated branches must rebase, test, and gate")
if not isinstance(branch.get("merge_order"), int):
raise SystemExit(f"{branch['ref']}: merge_order is required")
merge_orders.append(branch["merge_order"])
elif branch["gate"]:
raise SystemExit(f"{branch['ref']}: a gate branch must be integrated")
seen.add(branch["ref"])
if len(merge_orders) != len(set(merge_orders)):
raise SystemExit("duplicate merge_order in integration manifest")
outline = text.split("## Branch and Integration Status Outline", 1)[1]
outline = outline.split("## TODO Summary", 1)[0]
outline_refs = set(re.findall(r"^\s*- \[x\] \*\*([^*]+)\*\*", outline, re.MULTILINE))
integrated_refs = {branch["ref"] for branch in branches if branch["integrate"]}
if outline_refs != integrated_refs:
missing = sorted(integrated_refs - outline_refs)
extra = sorted(outline_refs - integrated_refs)
raise SystemExit(f"manifest/outline integration mismatch; missing={missing}, extra={extra}")
workflow_dir = path.parent / ".github" / "workflows"
for key in ("ci_workflow", "release_workflow"):
if not (workflow_dir / data[key]).is_file():
raise SystemExit(f"manifest {key} does not exist: {data[key]}")
ci_text = (workflow_dir / data["ci_workflow"]).read_text()
ci_name_match = re.search(r"^name:\s*['\"]?(.+?)['\"]?\s*$", ci_text, re.MULTILINE)
if not ci_name_match or ci_name_match.group(1) != data["ci_workflow_name"]:
raise SystemExit("manifest CI workflow name disagrees with its workflow file")
for infrastructure_file in data["infrastructure_files"]:
if not (path.parent / infrastructure_file).is_file():
raise SystemExit(f"manifest infrastructure file does not exist: {infrastructure_file}")
auto_promote_files = [
file for file in data["infrastructure_files"] if file.endswith("integration-auto-promote.yml")
]
if len(auto_promote_files) != 1:
raise SystemExit("manifest must declare exactly one auto-promote workflow")
auto_promote_text = (path.parent / auto_promote_files[0]).read_text()
if f'- "{data["ci_workflow_name"]}"' not in auto_promote_text:
raise SystemExit("auto-promote trigger disagrees with manifest CI workflow name")
for name, source in data["package_sources"].items():
if name == "container":
if "@sha256:" not in source:
raise SystemExit("package container must be digest-pinned")
elif not re.fullmatch(r"[0-9a-f]{40}", source["commit"]):
raise SystemExit(f"{name}: package source must use a full commit SHA")
for branch in branches:
if "issue" in branch and not isinstance(branch["issue"], int):
raise SystemExit(f"{branch['ref']}: issue must be an integer")
if "related_issues" in branch:
if not isinstance(branch["related_issues"], list) or \
not all(isinstance(i, int) for i in branch["related_issues"]):
raise SystemExit(f"{branch['ref']}: related_issues must be an array of integers")
if "supersedes_pr" in branch and not isinstance(branch["supersedes_pr"], int):
raise SystemExit(f"{branch['ref']}: supersedes_pr must be an integer")
print(f"manifest valid: {len(branches)} worktrees, {len(merge_orders)} integrated branches")
elif action == "branch-records":
mode = sys.argv[3] if len(sys.argv) > 3 else "all"
for branch in branches:
if mode != "all" and not branch[mode]:
continue
dependency = branch["dependencies"][0] if branch["dependencies"] else "-"
pr_head = branch.get("pr_head", "-")
external = str(branch.get("external", False)).lower()
fields = (
branch["ref"],
branch["worktree"],
branch["base"],
str(branch["rebase"]).lower(),
str(branch["test"]).lower(),
str(branch["integrate"]).lower(),
str(branch["gate"]).lower(),
dependency,
pr_head,
external,
)
print("\t".join(fields))
elif action == "pr-records":
for branch in branches:
if "pr" in branch:
print(f"{branch['ref']}\t{branch['pr']}\t{branch.get('pr_head', branch['ref'])}")
elif action == "integration-branches":
for branch in sorted((b for b in branches if b["integrate"]), key=lambda b: b["merge_order"]):
print(branch["ref"])
elif action == "test-filter":
print(":".join(data["test_exclusions"]))
elif action == "infrastructure-files":
print("\n".join(data["infrastructure_files"]))
elif action == "manifest-hash":
canonical = json.dumps(data, sort_keys=True, separators=(",", ":")).encode()
print(hashlib.sha256(canonical).hexdigest())
elif action == "get":
value = data
for key in sys.argv[3].split("."):
value = value[key]
print(value)
else:
raise SystemExit(f"unknown manifest query: {action}")
PY
}
manifest_query validate-json >/dev/null
KNOWN_FAILING="$(manifest_query test-filter)"
MANIFEST_HASH="$(manifest_query manifest-hash)"
REPOSITORY="$(manifest_query get repository)"
CI_WORKFLOW="$(manifest_query get ci_workflow)"
mapfile -t INTEGRATION_BRANCHES < <(manifest_query integration-branches)
mapfile -t INFRASTRUCTURE_FILES < <(manifest_query infrastructure-files)
env GIT_PAGER=cat git -C "$MIXXX_MAIN" config rerere.enabled false
env GIT_PAGER=cat git -C "$MIXXX_MAIN" config rerere.autoupdate true
_TQDM=$(command -v tqdm 2>/dev/null || true)
_HAS_TTY=false; [[ -t 1 ]] && _HAS_TTY=true
_PROG_IDX=0; _PROG_TOTAL=0; _PROG_NAME=""
# ── terminal colours ───────────────────────────────────────────────────────
# _C_* always set — used in STATUS_FILE writes so tail -f shows colour.
# _P_* used for stdout: equal to _C_* when running in a TTY, empty otherwise.
_C_RED='\033[0;31m'; _C_GRN='\033[0;32m'; _C_YLW='\033[1;33m'
_C_BLU='\033[0;34m'; _C_CYN='\033[0;36m'; _C_MAG='\033[0;35m'
_C_BLD='\033[1m'; _C_DIM='\033[2m'; _C_NC='\033[0m'
_P_RED=''; _P_GRN=''; _P_YLW=''; _P_BLU=''
_P_CYN=''; _P_MAG=''; _P_BLD=''; _P_DIM=''; _P_NC=''
if $_HAS_TTY; then
_P_RED="$_C_RED"; _P_GRN="$_C_GRN"; _P_YLW="$_C_YLW"; _P_BLU="$_C_BLU"
_P_CYN="$_C_CYN"; _P_MAG="$_C_MAG"; _P_BLD="$_C_BLD"; _P_DIM="$_C_DIM"
_P_NC="$_C_NC"
fi
# ── timing + summary accumulators ──────────────────────────────────────────
_T_SCRIPT_START=$(date +%s)
_T_REBASE_START=0; _T_REBASE_END=0
_T_BUILD_START=0; _T_BUILD_END=0
_T_TEST_START=0; _T_TEST_END=0
_REBASE_N_OK=0; _REBASE_N_SKIP=0; _REBASE_N_FAIL=0
_BUILD_RESULTS=() # "name:secs:ok|fail"
_TEST_RESULTS=() # "name:secs:pass|fail|skip"
# Status file: `tail -f $STATUS_FILE` in a separate terminal shows real-time progress
# without the noise of thousands of test lines. Updated at each phase/branch transition.
STATUS_FILE="/tmp/mixxx-integration-status"
SENTINEL_FILE="${CACHE_DIR}/tests-passed"
mkdir -p "$CACHE_DIR" "$TEST_LOG_DIR"
# Timestamped status line: colour-codes by keyword (PHASE/DONE/PASS/FAIL/WARN).
# Stdout uses _P_* codes (TTY-conditional); STATUS_FILE always gets _C_* codes so
# `tail -f $STATUS_FILE` shows colour in any terminal that renders ANSI sequences.
status() {
local msg pc="" fc=""
msg="[$(date '+%H:%M:%S')] $*"
case "$*" in
*PHASE*) pc="${_P_CYN}${_P_BLD}"; fc="${_C_CYN}${_C_BLD}" ;;
*DONE*|*" OK"*) pc="${_P_GRN}${_P_BLD}"; fc="${_C_GRN}${_C_BLD}" ;;
*PASS*|*" ok"*|*"OK:"*) pc="${_P_GRN}"; fc="${_C_GRN}" ;;
*FAIL*|*BLOCKED*|*ABORT*|*TIMEOUT*) pc="${_P_RED}"; fc="${_C_RED}" ;;
*WARN*) pc="${_P_YLW}"; fc="${_C_YLW}" ;;
esac
printf '%b\n' "${pc}${msg}${_P_NC}"
printf '%b\n' "${fc}${msg}${_C_NC}" >> "$STATUS_FILE"
}
# Snapshot of system load, memory, and CPU package temperature (if lm-sensors present).
# Called every 5 tests in run_tests_serial and once in print_grand_summary.
sys_stats() {
local load mem temp=""
load=$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null || echo "?")
mem=$(free -h 2>/dev/null | awk '/^Mem:/{print $3"/"$2}' || echo "?")
if command -v sensors >/dev/null 2>&1; then
local t; t=$(sensors 2>/dev/null | grep -oP '(?<=Package id 0: \+)\S+' | head -1 || true)
[[ -n "$t" ]] && temp=" ${_C_YLW}CPU ${t}${_C_NC}"
fi
printf '%b\n' " ${_C_DIM}sys: load ${load} | mem ${mem}${temp}${_C_NC}"
}
# Prints an ETA estimate based on elapsed time / completed items, projected linearly.
# Called after each completed build or test. Suppressed when done >= total.
eta_line() {
local done=$1 total=$2 elapsed=$3 label=$4
[[ $done -le 0 || $total -le $done ]] && return 0
local avg=$(( elapsed / done )) rem
rem=$(( avg * (total - done) ))
printf '%b\n' " ${_C_DIM}[ETA ~$(printf '%dm%02ds' $(( rem/60 )) $(( rem%60 ))) | ${done}/${total} done | avg ${avg}s/${label}]${_C_NC}"
}
echo "=== $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$STATUS_FILE"
# ── manifest validation + helpers ─────────────────────────────────────────────
validate_manifest() {
manifest_query validate-json
local errors=() ref worktree base _rebase _test _integrate _gate _dependency dir current
declare -A declared=()
while IFS=$'\t' read -r ref worktree base _rebase _test _integrate _gate _dependency _pr_head; do
declared["$worktree"]=1
dir="$MIXXX_DEV/$worktree"
if [[ ! -d "$dir" ]]; then
errors+=("$worktree (declared worktree missing)")
continue
fi
current=$(env GIT_PAGER=cat git -C "$dir" branch --show-current 2>/dev/null || true)
[[ "$current" == "$ref" ]] || errors+=("$worktree (expected $ref, found ${current:-detached})")
env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse --verify "$base" >/dev/null 2>&1 || \
errors+=("$worktree (base $base missing)")
done < <(manifest_query branch-records all)
for dir in "$MIXXX_DEV"/[0-9][0-9][0-9][0-9].*/; do
[[ -d "$dir" ]] || continue
worktree=$(basename "$dir")
[[ -n "${declared[$worktree]:-}" ]] || errors+=("$worktree (worktree absent from manifest)")
done
# PR head identity check: for branches with a declared PR, verify the PR
# head ref on GitHub still matches the manifest's pr_head (or ref if no
# pr_head). Drift means the PR is watching a branch nobody is developing on.
local pr_ref pr_number pr_head_declared pr_head_remote
while IFS=$'\t' read -r pr_ref pr_number pr_head_declared; do
pr_head_remote=$(gh pr view "$pr_number" --repo mixxxdj/mixxx \
--json headRefName --jq '.headRefName' 2>/dev/null || true)
if [[ -z "$pr_head_remote" ]]; then
errors+=("$pr_ref (PR #$pr_number not found or gh query failed)")
elif [[ "$pr_head_remote" != "$pr_head_declared" ]]; then
errors+=("$pr_ref (PR #$pr_number head is '$pr_head_remote', manifest declares '$pr_head_declared')")
fi
done < <(manifest_query pr-records)
if [[ ${#errors[@]} -gt 0 ]]; then
status "ABORT manifest/worktree validation failed:"
printf ' - %s\n' "${errors[@]}"
return 1
fi
status "OK manifest and ${#declared[@]} managed worktrees agree"
}
has_build() { [[ -d "$1/build" && -f "$1/build/CMakeCache.txt" ]]; }
has_test_bin() { [[ -f "$1/build/mixxx-test" ]]; }
is_test_binary_stale() {
local bin="$1/build/mixxx-test"
[[ -f "$bin" ]] && ldd "$bin" 2>/dev/null | grep -q "not found"
}
is_dirty_tree() {
! env GIT_PAGER=cat git -C "$1" diff --quiet 2>/dev/null || \
! env GIT_PAGER=cat git -C "$1" diff --cached --quiet 2>/dev/null
}
build_signature() {
local dir="$1" bin="$1/build/mixxx-test" cache="$1/build/CMakeCache.txt"
[[ -f "$bin" && -f "$cache" ]] || return 1
local head bin_mtime cache_hash linked_library_hash
head=$(env GIT_PAGER=cat git -C "$dir" rev-parse HEAD)
bin_mtime=$(stat -c %Y "$bin")
cache_hash=$(sha256sum "$cache" | cut -d' ' -f1)
linked_library_hash=$(ldd "$bin" | \
awk '$2 == "=>" && $3 ~ /^\// {print $3} $1 ~ /^\// {print $1}' | \
sort -u | while IFS= read -r library; do stat -c '%n:%Y:%s' "$library"; done | \
sha256sum | cut -d' ' -f1)
printf '%s %s %s %s %s' "$head" "$bin_mtime" "$cache_hash" \
"$linked_library_hash" "$MANIFEST_HASH"
}
write_build_stamp() {
local dir="$1" name="$2" signature
is_dirty_tree "$dir" && return 1
signature=$(build_signature "$dir") || return 1
printf '%s\n' "$signature" > "$CACHE_DIR/${name}.built"
}
build_stamp_valid() {
local dir="$1" name="$2" signature
[[ -f "$CACHE_DIR/${name}.built" ]] || return 1
is_dirty_tree "$dir" && return 1
is_test_binary_stale "$dir" && return 1
signature=$(build_signature "$dir") || return 1
[[ "$(<"$CACHE_DIR/${name}.built")" == "$signature" ]]
}
test_sentinel_valid() {
local dir="$1" name="$2" signature status_value
[[ -f "$CACHE_DIR/${name}.tested" ]] || return 1
signature=$(build_signature "$dir") || return 1
read -r _ _ _ _ _ status_value < "$CACHE_DIR/${name}.tested"
[[ "$(<"$CACHE_DIR/${name}.tested")" == "$signature pass" && "$status_value" == "pass" ]]
}
test_records() {
manifest_query branch-records test
printf 'integration\tintegration\tupstream/main\tfalse\ttrue\ttrue\ttrue\t-\n'
}
gate_records() {
manifest_query branch-records gate
printf 'integration\tintegration\tupstream/main\tfalse\ttrue\ttrue\ttrue\t-\n'
}
ensure_build_config() {
local dir="$1" build_dir="$1/build"
[[ -f "$build_dir/CMakeCache.txt" ]] || return 0
if ! grep -q '^CCACHE_SUPPORT:BOOL=ON$' "$build_dir/CMakeCache.txt" || \
! grep -q '^CMAKE_BUILD_TYPE:STRING=RelWithDebInfo$' "$build_dir/CMakeCache.txt"; then
echo " refreshing build configuration for $(basename "$dir")"
CCACHE_BASEDIR="$dir" cmake -S "$dir" -B "$build_dir" \
-DCCACHE_SUPPORT=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo > /dev/null
fi
}
kill_trap() { printf "\n"; echo "Interrupted — killing build/test processes"; kill 0; exit 130; }
# ── progress display ───────────────────────────────────────────────────────────
# TTY: two-row in-place (row 1 = overall [N/M], row 2 = cmake [XX%]).
# non-TTY + tqdm: line-count progress bar piped through tqdm.
# fallback: plain echo.
progress_start() {
_PROG_IDX="$1"; _PROG_TOTAL="$2"; _PROG_NAME="$3"
if $_HAS_TTY; then
printf "\r\033[K[%d/%d] %s\n\033[K" "$1" "$2" "$3"
else
echo "[$1/$2] $3"
fi
}
_progress_cmake_line() {
$_HAS_TTY || return 0
local pct
pct=$(printf '%s' "$1" | grep -oP '(?<=\[)\s*\d+(?=%\])' | tr -d ' ')
[[ -z "$pct" ]] && return 0
printf "\033[1A\033[K[%d/%d] %s\n\033[K%3d%%: %s" \
"$_PROG_IDX" "$_PROG_TOTAL" "$_PROG_NAME" "$pct" "$1"
}
build_with_progress() {
# build_with_progress <dir> <target> <idx> <total>
# Sets CCACHE_BASEDIR=$dir so ccache normalises all absolute paths under the worktree root
# to relative paths in its hash — enabling cross-worktree cache sharing for unchanged files.
local dir="$1" target="$2"
progress_start "$3" "$4" "$(basename "$dir")"
if $_HAS_TTY; then
CCACHE_BASEDIR="$dir" nice -n "$BUILD_NICE" cmake --build "$dir/build" --target "$target" \
-- -j"${BUILD_JOBS}" 2>&1 | \
while IFS= read -r line; do _progress_cmake_line "$line"; done
return "${PIPESTATUS[0]}"
elif [[ -n "$_TQDM" ]]; then
CCACHE_BASEDIR="$dir" nice -n "$BUILD_NICE" cmake --build "$dir/build" --target "$target" \
-- -j"${BUILD_JOBS}" 2>&1 | \
"$_TQDM" --desc "$(basename "$dir")" --unit " lines" > /dev/null
return "${PIPESTATUS[0]}"
else
CCACHE_BASEDIR="$dir" nice -n "$BUILD_NICE" cmake --build "$dir/build" --target "$target" \
-- -j"${BUILD_JOBS}"
fi
}
# ── mode: rebase_all ──────────────────────────────────────────────────────────
# Processes manifest branches in dependency order and rebases each onto either
# its declared base or its first declared dependency.
rebase_all() {
_T_REBASE_START=$(date +%s)
validate_manifest || return 1
status "PHASE rebase_all — fetching upstream"
env GIT_PAGER=cat git -C "$MIXXX_MAIN" config rerere.enabled false
env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch upstream
local failed=() succeeded=() _ref name base _rebase _test _integrate _gate dependency dir target
while IFS=$'\t' read -r _ref name base _rebase _test _integrate _gate dependency _pr_head; do
dir="$MIXXX_DEV/$name"
target="$base"
[[ "$dependency" != "-" ]] && target="$dependency"
if is_dirty_tree "$dir"; then
status " FAILED $name — dirty worktree must be committed or stashed"
failed+=("$name")
continue
fi
status "rebase: $name onto $target"
if env GIT_PAGER=cat git -C "$dir" rebase "$target"; then
status " $name — rebased"
succeeded+=("$name")
else
status " FAILED $name — aborting rebase"
env GIT_PAGER=cat git -C "$dir" rebase --abort 2>/dev/null || true
failed+=("$name")
fi
done < <(manifest_query branch-records rebase)
_T_REBASE_END=$(date +%s)
_REBASE_N_OK=${#succeeded[@]}; _REBASE_N_SKIP=0; _REBASE_N_FAIL=${#failed[@]}
status "DONE rebase_all: ${#succeeded[@]} ok ${#failed[@]} failed"
if [[ ${#failed[@]} -gt 0 ]]; then
printf ' FAILED: %s\n' "${failed[@]}"
return 1
fi
}
# ── mode: remerge_integration ──────────────────────────────────────────────────
# Builds the candidate in a temporary detached worktree. The checked-out
# integration branch is updated only after every merge and infrastructure restore
# succeeds, so conflicts and interruptions leave it untouched.
remerge_integration() {
validate_manifest || return 1
local current_branch pre_reset_sha candidate_sha branch f unresolved
local temp_root="" temp_tree=""
if [[ "${DRY_RUN:-0}" == "1" ]]; then
status "DRY-RUN remerge_integration: candidate will be built but not installed"
fi
current_branch=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" branch --show-current 2>/dev/null || true)
if [[ "$current_branch" != "integration" ]]; then
status "ABORT remerge_integration: MIXXX_MAIN is on '${current_branch:-detached}', not integration"
return 1
fi
if is_dirty_tree "$MIXXX_MAIN"; then
status "ABORT remerge_integration: integration has tracked changes; commit or stash them first"
return 1
fi
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch upstream; then
status "ABORT remerge_integration: upstream fetch failed"
return 1
fi
pre_reset_sha=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse HEAD)
cleanup_remerge() {
trap - INT TERM
if [[ -n "$temp_tree" ]]; then
env GIT_PAGER=cat git -C "$MIXXX_MAIN" worktree remove --force "$temp_tree" >/dev/null 2>&1 || true
fi
[[ -z "$temp_root" ]] || rmdir "$temp_root" >/dev/null 2>&1 || true
env GIT_PAGER=cat git -C "$MIXXX_MAIN" config rerere.enabled false || true
}
trap 'cleanup_remerge; exit 130' INT TERM
if ! temp_root=$(mktemp -d /tmp/mixxx-integration-remerge.XXXXXX); then
cleanup_remerge
status "ABORT remerge_integration: failed to create temporary directory"
return 1
fi
temp_tree="$temp_root/worktree"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" config rerere.enabled true; then
cleanup_remerge
status "ABORT remerge_integration: failed to enable scoped rerere"
return 1
fi
status "PHASE remerge_integration — transactionally merging ${#INTEGRATION_BRANCHES[@]} branches"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" worktree add --detach "$temp_tree" upstream/main >/dev/null; then
cleanup_remerge
status "ABORT remerge_integration: failed to create temporary worktree"
return 1
fi
local merged=() idx=0
for branch in "${INTEGRATION_BRANCHES[@]}"; do
(( idx++ )) || true
status " merge [$idx/${#INTEGRATION_BRANCHES[@]}] $branch"
if env GIT_PAGER=cat git -C "$temp_tree" merge --no-edit "$branch"; then
merged+=("$branch")
continue
fi
unresolved=$(env GIT_PAGER=cat git -C "$temp_tree" diff --name-only --diff-filter=U 2>/dev/null)
if [[ -z "$unresolved" ]] && \
env GIT_PAGER=cat git -C "$temp_tree" rev-parse --verify -q MERGE_HEAD >/dev/null && \
GIT_EDITOR=true env GIT_PAGER=cat git -C "$temp_tree" commit --no-edit; then
status " OK (rerere): $branch"
merged+=("$branch")
continue
fi
env GIT_PAGER=cat git -C "$temp_tree" merge --abort >/dev/null 2>&1 || true
cleanup_remerge
status "BLOCKED remerge_integration: conflict in $branch; integration remains at $pre_reset_sha"
status " Resolve the branch interaction, then rerun the complete remerge"
return 1
done
local restore_files=()
for f in "${INFRASTRUCTURE_FILES[@]}"; do
if env GIT_PAGER=cat git -C "$MIXXX_MAIN" cat-file -e "$pre_reset_sha:$f" 2>/dev/null; then
restore_files+=("$f")
else
cleanup_remerge
status "ABORT remerge_integration: $f is absent from pre-reset integration"
return 1
fi
done
if ! env GIT_PAGER=cat git -C "$temp_tree" checkout "$pre_reset_sha" -- "${restore_files[@]}"; then
cleanup_remerge
status "ABORT remerge_integration: infrastructure restore failed"
return 1
fi
if ! env GIT_PAGER=cat git -C "$temp_tree" diff --cached --quiet; then
if ! GIT_EDITOR=true env GIT_PAGER=cat git -C "$temp_tree" commit \
-m "restore integration infrastructure after remerge" >/dev/null; then
cleanup_remerge
status "ABORT remerge_integration: infrastructure commit failed"
return 1
fi
fi
if ! candidate_sha=$(env GIT_PAGER=cat git -C "$temp_tree" rev-parse HEAD); then
cleanup_remerge
status "ABORT remerge_integration: failed to resolve candidate SHA"
return 1
fi
if [[ "${DRY_RUN:-0}" == "1" ]]; then
cleanup_remerge
status "DRY-RUN DONE remerge_integration: ${#merged[@]} branches merged; candidate $candidate_sha NOT installed (integration remains at $pre_reset_sha)"
return 0
fi
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" reset --hard "$candidate_sha"; then
cleanup_remerge
status "ABORT remerge_integration: failed to install candidate $candidate_sha"
return 1
fi
cleanup_remerge
status "DONE remerge_integration: ${#merged[@]} branches merged at $candidate_sha"
}
# ── mode: push_changed_branches ──────────────────────────────────────────────
# Pushes feature/bugfix branches to origin ONLY when their patch content has
# actually changed relative to what is already on origin. A pure rebase (same
# diff, different base commit) is not pushed — avoids wasteful CI runs.
#
# Compares stable patch IDs against each branch's manifest-declared base. Branches
# with rebase=false are deliberately excluded.
push_changed_branches() {
validate_manifest || return 1
status "PHASE push_changed_branches — smart-diff push (only content changes)"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin --prune; then
status "ABORT push_changed_branches: origin fetch failed"
return 1
fi
local pushed=() skipped=() failed=()
local branch_name name base _rebase _test _integrate _gate _dependency pr_head external dir local_pids origin_pids push_target
while IFS=$'\t' read -r branch_name name base _rebase _test _integrate _gate _dependency pr_head external; do
dir="$MIXXX_DEV/$name"
# External branches track upstream PRs by other contributors — never push to origin
if [[ "$external" == "true" ]]; then
skipped+=("$name (external)")
continue
fi
# When pr_head is declared (legacy undated PR head), push to that ref
# so the upstream PR receives the content. Otherwise push to the local ref.
push_target="$branch_name"
[[ "$pr_head" != "-" && -n "$pr_head" ]] && push_target="$pr_head"
if ! env GIT_PAGER=cat git -C "$dir" rev-parse --verify "origin/$push_target" >/dev/null 2>&1; then
if env GIT_PAGER=cat git -C "$dir" push --force-with-lease origin "HEAD:refs/heads/$push_target"; then
status " $name — pushed (new remote branch $push_target)"
pushed+=("$name")
else
status " $name — push FAILED"
failed+=("$name")
fi
continue
fi
local_pids=$(env GIT_PAGER=cat git -C "$dir" log --no-merges -p "${base}..HEAD" 2>/dev/null | \
git patch-id --stable 2>/dev/null | awk '{print $1}' | sort)
origin_pids=$(env GIT_PAGER=cat git -C "$dir" log --no-merges -p \
"${base}..origin/${push_target}" 2>/dev/null | \
git patch-id --stable 2>/dev/null | awk '{print $1}' | sort)
if [[ "$local_pids" == "$origin_pids" ]]; then
skipped+=("$name")
continue
fi
if env GIT_PAGER=cat git -C "$dir" push --force-with-lease origin "HEAD:refs/heads/$push_target"; then
status " $name — pushed (content changed → $push_target)"
pushed+=("$name")
else
status " $name — push FAILED"
failed+=("$name")
fi
done < <(manifest_query branch-records rebase)
echo ""
status "DONE push_changed_branches: ${#pushed[@]} pushed ${#skipped[@]} skipped (unchanged) ${#failed[@]} failed"
if [[ ${#skipped[@]} -gt 0 ]]; then
printf '%b\n' " ${_C_DIM}Skipped (pure rebase, no CI value): ${skipped[*]}${_C_NC}"
fi
[[ ${#failed[@]} -eq 0 ]] || { printf ' FAILED: %s\n' "${failed[@]}"; return 1; }
}
# ── mode: build_all_tests ─────────────────────────────────────────────────────
# Configures missing build trees in parallel, then incrementally builds every
# manifest test branch and the combined integration tree serially. A successful
# clean build writes a signature bound to HEAD, CMakeCache, binary mtime, and the
# authoritative manifest.
build_all_tests() {
_T_BUILD_START=$(date +%s)
validate_manifest || return 1
status "PHASE build_all_tests"
trap kill_trap INT TERM
local dirs=() names=() to_configure=()
local _ref name _base _rebase _test _integrate _gate _dependency dir
while IFS=$'\t' read -r _ref name _base _rebase _test _integrate _gate _dependency _pr_head; do
if [[ "$name" == "integration" ]]; then dir="$MIXXX_MAIN"; else dir="$MIXXX_DEV/$name"; fi
dirs+=("$dir")
names+=("$name")
has_build "$dir" || to_configure+=("$dir")
done < <(test_records)
local configure_failed=() _pids=() _pdirs=() _i
for dir in "${to_configure[@]}"; do
name=$(basename "$dir")
local cfg_log="$CACHE_DIR/configure-${name}.log"
status " configure (launch) $name"
CCACHE_BASEDIR="$dir" cmake -S "$dir" -B "$dir/build" -GNinja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCCACHE_SUPPORT=ON > "$cfg_log" 2>&1 &
_pids+=($!)
_pdirs+=("$dir")
done
for _i in "${!_pids[@]}"; do
name=$(basename "${_pdirs[$_i]}")
if wait "${_pids[$_i]}"; then
status " configure OK: $name"
else
status " configure FAILED: $name (log: $CACHE_DIR/configure-${name}.log)"
configure_failed+=("$name")
fi
done
if [[ ${#configure_failed[@]} -gt 0 ]]; then
printf ' configure FAILED: %s\n' "${configure_failed[@]}"
return 1
fi
local config_failed=()
for dir in "${dirs[@]}"; do
if ! ensure_build_config "$dir"; then
config_failed+=("$(basename "$dir")")
fi
done
if [[ ${#config_failed[@]} -gt 0 ]]; then
printf ' configuration FAILED: %s\n' "${config_failed[@]}"
return 1
fi
local built=() build_failed=() idx=0 _phase_t0; _phase_t0=$(date +%s)
for _i in "${!dirs[@]}"; do
dir="${dirs[$_i]}"; name="${names[$_i]}"
(( idx++ )) || true
local _t0; _t0=$(date +%s)
if is_dirty_tree "$dir"; then
status " build BLOCKED: $name has tracked changes"
build_failed+=("$name")
_BUILD_RESULTS+=("${name}:0:fail")
continue
fi
if build_with_progress "$dir" mixxx-test "$idx" "${#dirs[@]}" && \
! is_test_binary_stale "$dir" && write_build_stamp "$dir" "$name"; then
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build OK: $name (${_et}s)"
built+=("$name")
_BUILD_RESULTS+=("${name}:${_et}:ok")
eta_line "$idx" "${#dirs[@]}" "$(( $(date +%s) - _phase_t0 ))" "build"
else
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build FAILED: $name (${_et}s)"
build_failed+=("$name")
_BUILD_RESULTS+=("${name}:${_et}:fail")
fi
done
_T_BUILD_END=$(date +%s)
trap - INT TERM
status "DONE build_all_tests: ${#built[@]} ok ${#build_failed[@]} failed"
if [[ ${#build_failed[@]} -gt 0 ]]; then
printf ' FAILED: %s\n' "${build_failed[@]}"
return 1
fi
ccache -s 2>/dev/null || true
}
rebuild_tests_serial() {
build_all_tests
}
# ── mode: build_integration_binary ───────────────────────────────────────────
# Builds the main mixxx executable (not mixxx-test) for the integration worktree
# so one has a runnable local binary after a remerge. Called by the full pipeline
# and by --quick-integration.
build_integration_binary() {
local dir="$MIXXX_MAIN"
status "PHASE build_integration_binary — building mixxx for integration tree"
has_build "$dir" || {
local cfg_log="$CACHE_DIR/configure-integration.log"
status " configure (launch) integration"
CCACHE_BASEDIR="$dir" cmake -S "$dir" -B "$dir/build" -GNinja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCCACHE_SUPPORT=ON > "$cfg_log" 2>&1 \
|| { status "configure FAILED: integration (log: $cfg_log)"; return 1; }
}
ensure_build_config "$dir" || { status "configuration FAILED: integration"; return 1; }
if is_dirty_tree "$dir"; then
status " build BLOCKED: integration has tracked changes"
return 1
fi
local _t0; _t0=$(date +%s)
if build_with_progress "$dir" mixxx 1 1; then
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build OK: integration mixxx (${_et}s)"
else
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build FAILED: integration mixxx (${_et}s)"
return 1
fi
status "DONE build_integration_binary: ~/src/mixxx-dev/integration/build/mixxx"
}
# ── mode: run_tests_serial ────────────────────────────────────────────────────
# Runs every manifest-selected test tree plus the combined integration tree.
# Passing sentinels are valid only while their corresponding build signature is
# unchanged; dirty trees and missing build stamps are blocking.
run_tests_serial() {
_T_TEST_START=$(date +%s)
validate_manifest || return 1
trap kill_trap INT TERM
local dirs_with_tests=() names_with_tests=() skipped_names=() blocked=()
local _ref name _base _rebase _test _integrate _gate _dependency dir
while IFS=$'\t' read -r _ref name _base _rebase _test _integrate _gate _dependency _pr_head; do
if [[ "$name" == "integration" ]]; then dir="$MIXXX_MAIN"; else dir="$MIXXX_DEV/$name"; fi
if is_dirty_tree "$dir"; then
blocked+=("$name (tracked changes)")
elif ! build_stamp_valid "$dir" "$name"; then
blocked+=("$name (missing or stale build stamp)")
elif test_sentinel_valid "$dir" "$name"; then
skipped_names+=("$name")
_TEST_RESULTS+=("${name}:0:skip")
else
dirs_with_tests+=("$dir")
names_with_tests+=("$name")
fi
done < <(test_records)
if [[ ${#blocked[@]} -gt 0 ]]; then
status "BLOCKED run_tests_serial: build clean trees first"
printf ' - %s\n' "${blocked[@]}"
return 1
fi
local total=${#dirs_with_tests[@]} passed=() failed=() idx=0 _i
status "PHASE run_tests_serial ($total to run, ${#skipped_names[@]} current)"
for _i in "${!dirs_with_tests[@]}"; do
dir="${dirs_with_tests[$_i]}"; name="${names_with_tests[$_i]}"
(( idx++ )) || true
local log="$TEST_LOG_DIR/${name}.log" timeout_secs=420 t0
t0=$(date +%s)
status "test [$idx/$total] $name (log: $log)"
( while true; do
sleep 30
local count elapsed
count=$(grep -ac 'RUN ' "$log" 2>/dev/null || true)
elapsed=$(( $(date +%s) - t0 ))
status " ... [$idx/$total] $name — ${count:-?} tests, ${elapsed}s elapsed"
done ) &
local heartbeat_pid=$!
if (cd "$dir/build" && timeout "$timeout_secs" ./mixxx-test \
--gtest_filter="-${KNOWN_FAILING}") > "$log" 2>&1; then
kill "$heartbeat_pid" 2>/dev/null; wait "$heartbeat_pid" 2>/dev/null || true
local elapsed signature
elapsed=$(( $(date +%s) - t0 ))
signature=$(build_signature "$dir")
printf '%s pass\n' "$signature" > "$CACHE_DIR/${name}.tested"
status " PASS [$idx/$total] $name (${elapsed}s)"
passed+=("$name")
_TEST_RESULTS+=("${name}:${elapsed}:pass")
else
local rc=$? elapsed signature
kill "$heartbeat_pid" 2>/dev/null; wait "$heartbeat_pid" 2>/dev/null || true
elapsed=$(( $(date +%s) - t0 ))
signature=$(build_signature "$dir" 2>/dev/null || echo "unknown unknown unknown unknown $MANIFEST_HASH")
printf '%s fail\n' "$signature" > "$CACHE_DIR/${name}.tested"
if [[ $rc -eq 124 ]]; then
status " TIMEOUT [$idx/$total] $name (${elapsed}s)"
else
status " FAIL [$idx/$total] $name (${elapsed}s, see $log)"
fi
failed+=("$name")
_TEST_RESULTS+=("${name}:${elapsed}:fail")
fi
[[ $(( idx % 5 )) -eq 0 ]] && sys_stats
eta_line "$idx" "$total" "$(( $(date +%s) - _T_TEST_START ))" "test"
done
_T_TEST_END=$(date +%s)
trap - INT TERM
local total_pass=$(( ${#passed[@]} + ${#skipped_names[@]} ))
status "DONE run_tests_serial: ${#passed[@]} pass ${#failed[@]} fail ${#skipped_names[@]} current"
if [[ ${#failed[@]} -gt 0 ]]; then
printf ' FAILED: %s\n' "${failed[@]}"
return 1
fi
printf '%s %s %d\n' "$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse HEAD)" \
"$MANIFEST_HASH" "$total_pass" > "$SENTINEL_FILE"
}
# ── mode: push_integration / push_integrating ─────────────────────────────────
push_integration() {
status "PHASE push_integration"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin; then
status "ABORT push_integration: origin fetch failed"
return 1
fi
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" push --force-with-lease origin \
"integration:refs/heads/integration"; then
status "ABORT push_integration: push failed"
return 1
fi
status "DONE integration branch pushed"
}
push_integrating() {
validate_manifest || return 1
local blocking=() _ref name _base _rebase _test _integrate _gate _dependency dir
while IFS=$'\t' read -r _ref name _base _rebase _test _integrate _gate _dependency _pr_head; do
if [[ "$name" == "integration" ]]; then dir="$MIXXX_MAIN"; else dir="$MIXXX_DEV/$name"; fi
is_dirty_tree "$dir" && blocking+=("$name (tracked changes)")
build_stamp_valid "$dir" "$name" || blocking+=("$name (stale or missing build)")
test_sentinel_valid "$dir" "$name" || blocking+=("$name (stale or missing test pass)")
done < <(gate_records)
if [[ ${#blocking[@]} -gt 0 ]]; then
status "BLOCKED push_integrating: local gate is incomplete"
printf ' - %s\n' "${blocking[@]}"
return 1
fi
local integration_sha
integration_sha=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse integration)
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin; then
status "ABORT push_integrating: origin fetch failed"
return 1
fi
env GIT_PAGER=cat git -C "$MIXXX_MAIN" branch -f integrating "$integration_sha"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" push --force-with-lease origin \
"$integration_sha:refs/heads/integrating"; then
status "ABORT push_integrating: push failed"
return 1
fi
status "DONE integrating pushed at $integration_sha — GA CI triggered"
status " Monitor: gh run list --workflow $CI_WORKFLOW --branch integrating --commit $integration_sha --repo $REPOSITORY"
}
# ── mode: promote_integrated ──────────────────────────────────────────────────
# Polls the declared CI workflow for the exact origin/integrating SHA. Promotion
# is a fast-forward and is abandoned if integrating moves while CI is running.
promote_integrated() {
validate_manifest || return 1
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin; then
status "ABORT promote_integrated: origin fetch failed"
return 1
fi
local expected_sha timeout_secs=3600 poll_interval=30 t0 run_line
local run_id run_status conclusion run_sha elapsed
expected_sha=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse origin/integrating)
t0=$(date +%s)
status "PHASE promote_integrated — waiting for $CI_WORKFLOW at $expected_sha"
while true; do
elapsed=$(( $(date +%s) - t0 ))
if [[ $elapsed -gt $timeout_secs ]]; then
status "TIMEOUT: exact-SHA CI did not complete within ${timeout_secs}s"
return 1
fi
if ! run_line=$(gh run list --workflow "$CI_WORKFLOW" --branch integrating \
--commit "$expected_sha" --event push --repo "$REPOSITORY" --limit 1 \
--json databaseId,status,conclusion,headSha \
--jq '.[0] | [.databaseId, .status, (.conclusion // ""), .headSha] | join("|")' 2>/dev/null); then
status "ABORT promote_integrated: unable to query GitHub Actions"
return 1
fi
if [[ -z "$run_line" ]]; then
status " waiting for CI run registration (${elapsed}s)"
sleep "$poll_interval"
continue
fi
IFS='|' read -r run_id run_status conclusion run_sha <<< "$run_line"
if [[ "$run_sha" != "$expected_sha" ]]; then
status "ABORT promote_integrated: CI returned unexpected SHA $run_sha"
return 1
fi
status " run #$run_id: $run_status${conclusion:+/$conclusion} (${elapsed}s)"
[[ "$run_status" == "completed" ]] && break
sleep "$poll_interval"
done
if [[ "$conclusion" != "success" ]]; then
status "BLOCKED promote_integrated: exact-SHA CI concluded $conclusion"
status " Inspect: gh run view $run_id --repo $REPOSITORY"
return 1
fi
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin; then
status "ABORT promote_integrated: final origin fetch failed"
return 1
fi
local current_integrating current_integrated
current_integrating=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse origin/integrating)
current_integrated=$(env GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse origin/integrated)
if [[ "$current_integrating" != "$expected_sha" ]]; then
status "BLOCKED promote_integrated: integrating moved to $current_integrating after CI"
return 1
fi
if [[ "$current_integrated" == "$expected_sha" ]]; then
status "DONE integrated already equals tested SHA $expected_sha"
return 0
fi
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" merge-base --is-ancestor \
"$current_integrated" "$expected_sha"; then
status "NOTE promote_integrated: integrated ($current_integrated) is not an ancestor of tested SHA — main moved between candidates; force-updating"
fi
env GIT_PAGER=cat git -C "$MIXXX_MAIN" branch -f integrated "$expected_sha"
if ! env GIT_PAGER=cat git -C "$MIXXX_MAIN" push --force origin \
"$expected_sha:refs/heads/integrated"; then
status "ABORT promote_integrated: push failed"
return 1
fi
status "DONE integrated updated to CI-tested SHA $expected_sha"
}
# ── grand summary ──────────────────────────────────────────────────────────────────────────────────
# Reads the phase timing accumulators (_T_*_START/END) and result arrays
# (_BUILD_RESULTS, _TEST_RESULTS) to produce a colourised summary table.
# Reports: total elapsed, per-phase duration + pass/fail/skip counts, slowest
# build and test branch, a sys_stats snapshot, and the final promotion outcome.
# Called automatically at the end of rebase_merge_test_push.
print_grand_summary() {
local build_ok=$1 integrating_ok=$2
local t_end; t_end=$(date +%s)
local total=$(( t_end - _T_SCRIPT_START ))
local rebase_t=$(( _T_REBASE_END > 0 ? _T_REBASE_END - _T_REBASE_START : 0 ))
local build_t=$(( _T_BUILD_END > 0 ? _T_BUILD_END - _T_BUILD_START : 0 ))
local test_t=$(( _T_TEST_END > 0 ? _T_TEST_END - _T_TEST_START : 0 ))
local b_ok=0 b_fail=0 t_pass=0 t_fail=0 t_skip=0
local slowest_build="" slow_bt=0 slowest_test="" slow_tt=0
for e in "${_BUILD_RESULTS[@]+"${_BUILD_RESULTS[@]}"}" ; do
local _n="${e%%:*}" _r="${e##*:}" _t="${e#*:}"; _t="${_t%%:*}"
if [[ "$_r" == ok ]]; then (( ++b_ok )); else (( ++b_fail )); fi
[[ $_t -gt $slow_bt ]] && { slowest_build="$_n"; slow_bt=$_t; }
done
for e in "${_TEST_RESULTS[@]+"${_TEST_RESULTS[@]}"}" ; do
local _n="${e%%:*}" _r="${e##*:}" _t="${e#*:}"; _t="${_t%%:*}"
case "$_r" in pass) (( ++t_pass ));; fail) (( ++t_fail ));; skip) (( ++t_skip ));; esac
[[ $_t -gt $slow_tt && "$_r" != skip ]] && { slowest_test="$_n"; slow_tt=$_t; }
done
local rb_c=$(( _REBASE_N_FAIL > 0 )) bi_c=$(( b_fail > 0 )) ts_c=$(( t_fail > 0 ))
local SEP="${_C_CYN}${_C_BLD}"; local DIV="${_C_DIM}"; local E="${_C_NC}"
printf '%b\n' "${SEP}═══════════════════════════════════════════════════${E}"
printf '%b\n' " ${_C_BLD}GRAND SUMMARY — $(date '+%Y-%m-%d %H:%M')${E}"
printf '%b\n' " Total: ${_C_BLD}$(printf '%dm%02ds' $(( total/60 )) $(( total%60 )))${E}"
printf '%b\n' "${DIV}───────────────────────────────────────────────────${E}"
printf '%b\n' " ${DIV}Phase Dur Result${E}"
local rc_r rc_b rc_t
if (( rb_c )); then rc_r="$_C_RED"; else rc_r="$_C_GRN"; fi
if (( bi_c )); then rc_b="$_C_RED"; else rc_b="$_C_GRN"; fi
if (( ts_c )); then rc_t="$_C_RED"; else rc_t="$_C_GRN"; fi
printf '%b\n' " Rebase $(printf '%dm%02ds' $(( rebase_t/60 )) $(( rebase_t%60 ))) ${rc_r}${_REBASE_N_OK} ok ${_REBASE_N_SKIP} skip ${_REBASE_N_FAIL} fail${E}"
printf '%b\n' " Build $(printf '%dm%02ds' $(( build_t/60 )) $(( build_t%60 ))) ${rc_b}${b_ok} ok ${b_fail} fail${E}"
printf '%b\n' " Test $(printf '%dm%02ds' $(( test_t/60 )) $(( test_t%60 ))) ${rc_t}${t_pass} pass ${t_fail} fail ${t_skip} skip${E}"
if [[ -n "$slowest_build" || -n "$slowest_test" ]]; then
printf '%b\n' "${DIV}───────────────────────────────────────────────────${E}"
[[ -n "$slowest_build" ]] && printf '%b\n' " Slowest build: ${_C_BLD}${slowest_build}${E} ${slow_bt}s"
[[ -n "$slowest_test" ]] && printf '%b\n' " Slowest test: ${_C_BLD}${slowest_test}${E} ${slow_tt}s"
fi
printf '%b\n' "${DIV}───────────────────────────────────────────────────${E}"
sys_stats
printf '%b\n' "${DIV}───────────────────────────────────────────────────${E}"
if ! $build_ok; then
printf '%b\n' " integration pushed → origin/integration"
printf '%b\n' " ${_C_RED}integrating BLOCKED — build failure(s)${E}"
printf '%b\n' " ${DIV}Fix builds, then: --build-all-tests --run-tests --push-integrating${E}"
elif $integrating_ok; then
printf '%b\n' " integration pushed → origin/integration"
printf '%b\n' " ${_C_GRN}integrating pushed → origin/integrating${E}"
printf '%b\n' " ${DIV}GA CI triggered — monitor, then: --promote-integrated${E}"
else
printf '%b\n' " integration pushed → origin/integration"
printf '%b\n' " ${_C_YLW}integrating BLOCKED — not all branches tested${E}"
printf '%b\n' " ${DIV}Once ready: --run-tests then --push-integrating${E}"
fi
printf '%b\n' "${SEP}═══════════════════════════════════════════════════${E}"
}
# ── mode: rebase_merge_test_push ──────────────────────────────────────────────
# Orchestrates manifest validation, dependency-aware rebases, transactional remerge,
# exact builds, serial tests, and both local-tier pushes. Any failed phase is fatal;
# no partial candidate is pushed or promoted.
rebase_merge_test_push() {
local ts; ts=$(date '+%Y-%m-%d %H:%M')
echo "╔══════════════════════════════════════════════╗"
printf "║ Rebase+Merge+Test+Push — %-20s║\n" "$ts"
echo "╚══════════════════════════════════════════════╝"
echo "Real-time status: tail -f $STATUS_FILE"
echo "Test logs: tail -f $TEST_LOG_DIR/<worktree>.log"
rebase_all || { status "ABORT pipeline: rebase failed"; return 1; }
remerge_integration || { status "ABORT pipeline: transactional remerge failed"; return 1; }
build_all_tests || { status "ABORT pipeline: build failed"; return 1; }
run_tests_serial || { status "ABORT pipeline: tests failed"; return 1; }
build_integration_binary || { status "ABORT pipeline: integration binary build failed"; return 1; }
push_integration || return 1
push_integrating || return 1
print_grand_summary true true
}
rebase_merge_test_push_promote() {
rebase_merge_test_push || return 1
promote_integrated || {
status "promote_integrated failed — rerun --promote-integrated after exact-SHA CI passes"
return 1
}
}
# ── mode: quick_integration ───────────────────────────────────────────────────
# Rebases, remerges, and builds only the combined integration tree. Skips local
# tests and all remote operations (push, CI, promotion). Use for fast iteration
# when one only needs a runnable integration binary.
quick_integration() {
local ts; ts=$(date '+%Y-%m-%d %H:%M')
echo "╔══════════════════════════════════════════════╗"
printf "║ Quick Integration — %-20s ║\n" "$ts"
echo "╚══════════════════════════════════════════════╝"
echo "Real-time status: tail -f $STATUS_FILE"
rebase_all || { status "ABORT pipeline: rebase failed"; return 1; }
remerge_integration || { status "ABORT pipeline: transactional remerge failed"; return 1; }
# Build only the integration worktree, not all test branches.
_T_BUILD_START=$(date +%s)
validate_manifest || return 1
status "PHASE quick_integration — building integration tree only"
trap kill_trap INT TERM
local dir="$MIXXX_MAIN"
has_build "$dir" || {
local cfg_log="$CACHE_DIR/configure-integration.log"
status " configure (launch) integration"
CCACHE_BASEDIR="$dir" cmake -S "$dir" -B "$dir/build" -GNinja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCCACHE_SUPPORT=ON > "$cfg_log" 2>&1 \
|| { status "configure FAILED: integration (log: $cfg_log)"; return 1; }
}
ensure_build_config "$dir" || { status "configuration FAILED: integration"; return 1; }
local _t0; _t0=$(date +%s)
if is_dirty_tree "$dir"; then
status " build BLOCKED: integration has tracked changes"
return 1
fi
if build_with_progress "$dir" mixxx-test 1 1 && \
! is_test_binary_stale "$dir" && write_build_stamp "$dir" "integration"; then
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build OK: integration (${_et}s)"
else
local _et=$(( $(date +%s) - _t0 ))
$_HAS_TTY && printf "\n"
status " build FAILED: integration (${_et}s)"
return 1
fi
_T_BUILD_END=$(date +%s)
trap - INT TERM
build_integration_binary || { status "ABORT quick_integration: integration binary build failed"; return 1; }
ccache -s 2>/dev/null || true
status "DONE quick_integration: integration tree built; tests and remote CI skipped"
}
# ── mode: sync_gist ────────────────────────────────────────────────────────────
# Syncs every infrastructure file to the Gist after a successful pipeline run or
# on demand. Reads the Gist ID from the document header. Non-fatal — a gist sync
# failure warns but does not block branch operations.
GIST_ID="5fb35c401736efed47ad7d78268c80b6"
sync_gist() {
status "PHASE sync_gist — syncing infrastructure files to gist $GIST_ID"
local file gist_name failed=()
local -A gist_map=(
["INTEGRATION.md"]="INTEGRATION.md"
["mixxx-milkii-integration-update-branches.sh"]="mixxx-milkii-integration-update-branches.sh"
["mixxx-milkii-integration-pre-push.sh"]="mixxx-milkii-integration-pre-push.sh"
["mixxx-milkii-integration-gdb-run.sh"]="mixxx-milkii-integration-gdb-run.sh"
[".github/workflows/mixxx-milkii-integration-manjaro-release.yml"]="mixxx-milkii-integration-manjaro-release.yml"
[".github/workflows/mixxx-milkii-integration-auto-promote.yml"]="mixxx-milkii-integration-auto-promote.yml"
)
for file in "${!gist_map[@]}"; do
local local_path="$MIXXX_MAIN/$file"
local gist_name="${gist_map[$file]}"
if [[ ! -f "$local_path" ]]; then
status " WARN: $file not found, skipping"
failed+=("$file")
continue
fi
if gh gist edit "$GIST_ID" --filename "$gist_name" "$local_path" 2>/dev/null; then
status " OK synced: $gist_name"
else
status " FAIL syncing: $gist_name"
failed+=("$file")
fi
done
if [[ ${#failed[@]} -gt 0 ]]; then
status "WARN sync_gist: ${#failed[@]} file(s) failed to sync"
return 1
fi
status "DONE sync_gist: all infrastructure files synced"
}
# ── dispatch ───────────────────────────────────────────────────────────────────
# Optional --dry-run as second arg: currently only --remerge-integration honours
# it (builds the candidate in a temp worktree but skips the final reset). Other
# modes ignore it.
DRY_RUN=0
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=1
case "${1:-}" in
--validate-manifest) validate_manifest ;;
--rebuild-tests) rebuild_tests_serial ;;
--build-all-tests) build_all_tests ;;
--run-tests) run_tests_serial ;;
--remerge-integration) remerge_integration ;;
--push-changed) push_changed_branches ;;
--push-integrating) push_integrating ;;
--promote-integrated) promote_integrated ;;
--sync-gist) sync_gist ;;
--rebase-merge-test-push) rebase_merge_test_push; sync_gist || true ;;
--rebase-merge-test-push-promote) rebase_merge_test_push_promote; sync_gist || true ;;
--quick-integration) quick_integration ;;
"") rebase_all ;;
*) echo "Usage: $0 [--validate-manifest | --rebuild-tests | --build-all-tests | --run-tests | --remerge-integration [--dry-run] | --push-changed | --push-integrating | --promote-integrated | --sync-gist | --rebase-merge-test-push | --rebase-merge-test-push-promote | --quick-integration]"; exit 1 ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment