Skip to content

Instantly share code, notes, and snippets.

@mxmilkiib
Last active June 18, 2026 05:32
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.
prompt: local AI dev process; 2 repos, one individual branches, other for merging n test build

prompt: Mixxx personal integration system — worktree-per-branch development, automated rebase/build/test, three-tier promotion chain, CI-conscious push policy

Mixxx Integration Branch Configuration

Last updated: 2026-06-18 06:31 (session 9) URL: https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6 RFC 2119

One git database, many worktrees. Each feature or bugfix lives in its own worktree under ~/src/mixxx-dev/, branched from upstream main. A script rebases all branches, builds test binaries, and runs the test suite. Branches that pass are merged into an integration branch and promoted through three gates: integration (working scratchpad) → integrating (locally tested) → integrated (CI confirmed). Pushes to open PR branches happen only when the patch content has actually changed — pure rebases are never pushed, to avoid wasting shared CI resources.

Session Start Protocol

Run these checks automatically at the start of every session, before any other work:

  1. Promotion check — run: gh run list --branch integrating --repo mxmilkiib/mixxx --limit 1 --json status,conclusion,headSha. If "conclusion": "success" and the SHA differs from $(git -C ~/src/mixxx-dev/integration rev-parse integrated), run --promote-integrated immediately.
  2. URGENT scan — check the Branch Status Outline for any entries marked URGENT and report them.
  3. Stale sentinel check — for any branch whose sentinel SHA (~/.cache/mixxx-integration/<name>.tested) does not match its current git rev-parse HEAD, note it as needing a re-test before the next --push-integrating.

Rules

  • Purpose: This living document tracks Milkii's personal Mixxx development setup, for creating and testing feature and bugfix branches, and MUST be updated as the workflow evolves.
  • Last updated: The "Last updated" date at the top of this file MUST be updated whenever this file is edited
  • Gist sync: This file AND mixxx-integration-update-branches.sh MUST be kept in sync with the Gist (https://gist.github.com/mxmilkiib/5fb35c401736efed47ad7d78268c80b6).
    • To sync INTEGRATION.md: gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename INTEGRATION.md INTEGRATION.md.
    • To sync the script: gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-integration-update-branches.sh mixxx-integration-update-branches.sh.
    • Both files MUST be updated whenever they change.
  • Dual dir: All source trees share the SAME .git database rooted at ~/src/mixxx/.git. Registered worktrees are not separate clones — git log, git branch -a, etc. show all branches from any path.
    • ~/src/mixxx/ — checked out on integrated; build/mixxx here is the CI-confirmed daily-driver binary
    • ~/src/mixxx-dev/integration/ — checked out on integration; script and helper files live here; this is where ./mixxx-integration-update-branches.sh is run from
    • ~/src/mixxx-dev/<branch>/ — individual feature/bugfix worktrees
  • Main sync: All worktrees 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.
  • Worktrees: All development MUST use worktrees, keeping individual branches clean for upstream PRs. The integration worktree at ~/src/mixxx-dev/integration/ is the script's operating base; ~/src/mixxx/ MUST remain on integrated so its build/mixxx is always the CI-confirmed binary.
  • Branch tiers: The mixxx repo MUST maintain three promotion-chain branches:
    • integration — working merge branch; script operates here; may be broken at any time. Rebuilt from scratch on each run by merging upstream/main then all [x]-marked worktree branches in order — it is intentionally ephemeral and MUST NOT be treated as stable.
    • integrating — locally-tested-clean tier; promoted from integration only after ALL non-skipped worktrees have passing test suites (--push-integrating); triggers GA CI. Local tests run against the same kernel/libraries/ccache as the build; they catch logic regressions but not environment or flag differences.
    • integrated — CI-confirmed-clean tier; promoted from integrating only after GA CI passes on origin/integrating (--promote-integrated); safe to build from daily. GA CI runs a full build + unit test suite on a clean runner with no shared cache, catching missing-dependency or build-flag issues invisible to local tests. Three tiers exist because local-passing does not imply clean-environment-passing: a missing system header, a flag difference, or a locally-installed library can mask a real breakage. integrating is the staging gate; integrated is the confirmed-clean consumption tier. integration combines all [x]-marked branches from mixxx-dev worktrees.
  • Dev location: All individual branch development should be done using the mixxx-dev worktree directory
  • Integration edits: mixxx can have some edits for testing purposes, but should be kept minimal
  • Clean commits: A branch in mixxx-dev MUST have clean commits before first being linked with a GitHub PR
  • Stability: This dual setup SHOULD provide consistency for a stable bleeding-edge build without interference from local development.
  • "Updating" the system: When the user asks to "update" or says the system has been updated, this MUST include all of the following post-update checks and tasks in order:
    1. Fetch upstream and check for new commits on upstream/main
    2. 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 — do this BEFORE rebasing or rebuilding so merged branches are excluded from both
    3. Rebase all non-merged worktree branches on new upstream/main (stash any WIP first); skip branches identified as merged in step 2; clean any branches with INTEGRATION.md or other cruft commits. Rebasing does NOT push — use --push-changed explicitly when PR branches need updating (e.g. after addressing review feedback, or to clear stale-bot)
    4. Rebuild the integration branch: merge upstream/main then re-merge all [x] branches in order, resolving any conflicts
    5. Build the integration branch (cmake --build build --target mixxx -- -j$(nproc --ignore=2)) and verify it succeeds
    6. All local tests must pass across ALL non-skipped worktrees. Run --build-all-tests to configure cmake and build any missing binaries (also rebuilds stale ones), then --run-tests to confirm 0 failures. Both steps are done automatically by --full. Once clean, run --push-integrating to promote integrationorigin/integrating. This triggers GA CI. 6b. Wait for GA CI on origin/integrating: gh run list --branch integrating --repo mxmilkiib/mixxx. Once all checks pass, run --promote-integrated to fast-forward integratedorigin/integrated. If CI fails, investigate whether failures are pre-existing on upstream/main or integration-introduced; do not promote until confirmed pre-existing or fixed.
    7. Check all open PRs for new review feedback (CHANGES_REQUESTED, new comments) and update INTEGRATION.md statuses accordingly 7b. If any PR branches have actual content changes (review feedback addressed, conflict resolutions), run --push-changed to push only those branches whose patch differs from origin — pure rebases are skipped to conserve upstream CI resources
    8. Update the "Last updated" timestamp and rebuild log entry in INTEGRATION.md, commit, and sync to Gist
  • Merge process: The integration merge process MUST follow the steps in the Integration Merge Process section below.
  • Rebase hygiene: All branches SHOULD be kept up-to-date and rebased with mixxxdj/mixxx main 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
  • 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.
  • 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
  • Non-interactive git: Git operations MUST be non-interactive using GIT_EDITOR=true and GIT_PAGER=cat to avoid vim/editor prompts
  • Issues: Most branches MAY have related upstream issues; related issues SHOULD be listed in the outline
  • Sections: Feature and fix branches should be in the correct outline sections
  • 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
  • Dates: Dates for branch creation, last PR comment, and last update MUST be recorded in the status outline
  • Standalone branches: Each feature/fix branch SHOULD work standalone without depending on other local branches (except where noted)
  • 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.
  • No cherry-pick: ALWAYS use git merge to bring branches into integration, NEVER git cherry-pick — cherry-picking creates duplicate commits with different SHAs, severs the branch relationship, makes bisect/revert unreliable, and hides what is actually in the build from git log
  • Dependencies: Any fix or feature branch that relies on another local branch MUST be noted in the Branch Dependencies section
  • Local-only: Some features (UTF-8 string controls) MUST NOT be submitted to mixxxdj/mixxx upstream as they are local-only/personal use
  • PR flow: PRs SHOULD be submitted to mxmilkiib/mixxx, and Milkii will create a further PR from there to mixxxdj/mixxx.
  • Merged cleanup: Once the PR is fully merged into mixxxdj/mixxx, the branch entry MUST be moved to the "Merged to Upstream" section of the outline and its [x] marker removed, so it is excluded from future integration rebuilds.
  • 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.5git 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, do not close the PR.
  • Commit messages: Commit messages must not be too verbose, and should be concise and descriptive.
  • Conflict resolution: When resolving merge conflicts — whether during rebases or integration merges — conflicts MUST be resolved and the operation continued non-interactively
  • Code quality: Code quality MUST be verified before pushing — code should be proper, straight to the point, robust, and follow Mixxx coding style
  • 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.
  • Worktree pruning: When a branch is merged upstream, closed, or abandoned, its worktree MUST be removed (git worktree remove ~/src/mixxx-dev/<name>) and the local branch ref MAY be deleted. This keeps mixxx-dev/ lean and prevents mixxx-integration-update-branches.sh from wasting time on dead branches.
  • Schema exclusion: Branches that introduce database schema migrations MUST NOT be merged into the integration branch unless all schema-changing branches use compatible, non-conflicting revision numbers. Schema branches are tracked in a dedicated "Schema-Changing Branches" section of the outline.
  • Local-only backup: All local-only branches MUST be pushed to origin (mxmilkiib/mixxx) for off-machine backup, even if they will never be PRed upstream. All worktrees share a single .git directory — losing it means losing every unpushed branch.
  • LOCAL_ONLY dependency chains: When rebasing branches that form a LOCAL_ONLY dependency chain, the dependency root MUST be rebased first, then each dependent in topological order. If the root bitrots or conflicts, all dependents are broken until the root is fixed.
  • mixxx-integration-update-branches.sh: The script MUST exist as a committed file in the integration branch and MUST be run from ~/src/mixxx-dev/integration/. All ${MIXXX_DEV}/*/ loops in the script MUST guard with [[ "$name" =~ ^[0-9]{4}\. ]] || continue so promotion-chain worktrees (integration, integrating, integrated) — which lack the YYYY. date prefix — are never treated as feature branches. It MUST skip worktrees whose branches have been merged upstream, closed, or abandoned.
  • Test binary staleness: After any system library upgrade (e.g. protobuf, Qt, libstdc++), the build/mixxx-test binary in each worktree MUST be rebuilt before pushing — stale binaries will fail the pre-push hook with a dynamic linker error, not a test failure. Run ldd <worktree>/build/mixxx-test | grep 'not found' to detect staleness without rebuilding. Use mixxx-integration-update-branches.sh --rebuild-tests to rebuild all stale test binaries serially.
  • 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).
  • 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.
  • Test serialisation: Local tests MUST be run serially (run_tests_serial), not in parallel. Multiple concurrent mixxx-test processes share the same audio device mocks, ControlObject registry, and SQLite test databases — parallel runs cause non-deterministic failures and resource exhaustion. Serial execution ensures reproducible results. Build compilation IS parallel within each worktree (-j$(nproc --ignore=2)) and configure steps are parallelised across worktrees.
  • 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-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 needed for robust cross-worktree sharing.
  • Skip list vs integration markers: SKIP_BRANCHES in mixxx-integration-update-branches.sh covers only branches whose worktrees are removed, merged, or abandoned — these are skipped in ALL operations. LOCAL_ONLY and schema-excluded branches are NOT in SKIP_BRANCHES; they are still rebased and tested. They are excluded only from integration merges, tracked via [ ] vs [x] markers in the Branch Status Outline (manual step).
  • Upstream test filter scope: When filtering known-failing upstream tests, filter the ENTIRE affected test suite (e.g. ControllerScriptEngineLegacyTimerTest.*) not just the specific failing cases. Individual tests in the suite that nominally pass can still corrupt shared QTimer/ControlObject state, causing unrelated downstream tests (e.g. MidiMappings/MappingTestFixture) to hang indefinitely. Root cause: coTimerId ControlPotmeter max=50 clamps any QTimer ID > 50, producing collisions that prevent timer callbacks from firing. Filtering the entire suite prevents state poisoning. Remove when upstream fix lands.
  • Pre-push hook timeout: The hook runs timeout 420 ./mixxx-test to prevent indefinite hangs. If the timeout fires, it reports the last test name and blocks the push. Investigate the hanging test by running it in isolation first (./mixxx-test --gtest_filter=SuiteName) — if it passes alone, it is a state-poisoning issue from a preceding test.
  • Monitoring progress: mixxx-integration-update-branches.sh --full 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.
  • 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.
  • Promotion currency: integrated MUST NOT lag behind a passing integrating. At the start of any session — before any other work — check whether origin/integrating has a completed, passing CI run that has not yet been promoted: gh run list --branch integrating --repo mxmilkiib/mixxx --limit 1 --json status,conclusion,headSha. If it shows "conclusion": "success" and the SHA differs from the current integrated HEAD, run --promote-integrated immediately. Letting integrated sit stale means ~/src/mixxx/build/mixxx is not the CI-confirmed binary.
  • Branch tier semantics: integrationintegratingintegrated. These are three distinct promotion gates, not aliases:
    • integration is a working scratchpad — never guaranteed to build or pass tests.
    • integrating is locally-tested-clean — every non-skipped worktree passed its test suite with 0 failures. This is NOT CI-confirmed.
    • integrated is CI-confirmed-clean — GA confirmed origin/integrating passed all platform checks. This is the branch to build from. integrated ≠ "guaranteed to build everywhere" — CI may still filter some known-upstream failures via KNOWN_FAILING. Its guarantee is: locally clean + GA Linux/macOS/Windows builds passed.
  • Test binary completeness: Before --push-integrating can succeed, ALL non-skipped worktree branches MUST have a build/mixxx-test binary AND each must have a per-branch passing sentinel for its current HEAD. --full uses --build-all-tests which: (1) launches all cmake configures in parallel (configure is I/O-bound; 16 configures take ~5s instead of ~70s serial), then (2) builds mixxx-test serially with -j(nproc-2), then (3) prints a ccache -s summary. Standalone: --build-all-tests then --run-tests. The script lists all blocking branches when --push-integrating is blocked. --rebuild-tests still exists but only handles stale binaries (ldd-detected) — it does NOT configure unconfigured branches.
  • Per-branch test status: Each active branch entry in this document SHOULD carry a Test binary: field with one of: [no-build], [build-only], [test-pass YYYY-MM-DD], [test-fail YYYY-MM-DD]. This is updated manually after each --run-tests run. The field reflects the LAST KNOWN state; the script is authoritative for the current live state.
  • CI-conscious 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. rebase_all() no longer pushes; use --push-changed to push only branches whose git diff upstream/main..HEAD differs from git diff upstream/main..origin/<branch>. Manual pushes (e.g. after addressing review feedback) bypass this — push directly from the worktree as needed.
  • Per-branch test sentinel: After each test run, run_tests_serial writes ~/.cache/mixxx-integration/<name>.tested containing <branch-HEAD-SHA> pass|fail. Sentinels survive reboots (unlike /tmp/). run_tests_serial is selective: branches whose sentinel matches their current HEAD SHA and status is pass are skipped, so re-runs after a single branch rebase only re-test that branch (~3 min vs ~65 min). push_integrating Gate 2 checks each branch's sentinel individually — reports exactly which branches need attention. A global summary sentinel is also written to ~/.cache/mixxx-integration/tests-passed for auditing. Invalidation is automatic: when a branch rebases its HEAD SHA changes, making the sentinel stale.

Worktree Branch Hygiene

CRITICAL: mixxx-dev/ worktrees MUST only contain commits belonging to their named feature.

  • NEVER 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/, confirm the active worktree matches 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):
    git -C ~/src/mixxx-dev/<worktree>/ log --oneline upstream/main..HEAD
  • If a worktree has accumulated cruft, reset it:
    • No real feature commits yet: git reset --hard upstream/main
    • Has real commits mixed with cruft: rebase only the feature commits onto upstream/main, then force-update the branch ref

Preventing Cross-Branch Contamination

  • ALWAYS create new feature branches from upstream/main, never from local main — defence-in-depth: if main ever accumulates stray commits (violating Main read-only), branching from upstream/main guarantees a clean base:
    git fetch upstream
    git worktree add ~/src/mixxx-dev/<name> -b feature/<branch-name> upstream/main
  • 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

Directory Structure

Path Purpose
~/src/mixxx/ Worktree checked out on integrated — CI-confirmed daily-driver binary
~/src/mixxx-dev/ Worktrees: integration/ (script base) + individual feature/fix branches

Branch promotion chain (all branches live in the single shared .git at ~/src/mixxx/):

integration  →[all worktrees pass --run-tests]→  integrating  →[GA CI green]→  integrated
(working, may break)          (locally clean)                  (CI-confirmed)

Branch Dependencies

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

Branches with dependencies on local-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: 4 awaiting re-review, 10 open PRs, 2 schema-excluded, 10 merged/resolved upstream, 6 local-only, 2 secondary patches, 2 build-broken/skipped

  • 🔴 Awaiting Review from Others
    • feature/2025.11nov.04-controller-wizard-quick-access - #15577 - CHANGES_REQUESTED
      • Issue: #12262
      • Created: 2025-11-04, Last comment: 2026-02-18, Rebased: 2026-05-26, Updated: 2026-02-22
      • 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.10oct.21-stacked-overview-waveform - #15516 - DRAFT - CHANGES_REQUESTED
      • Issue: #13265
      • Created: 2025-10-21, Last comment: 2026-02-22 (mxmilkiib), Rebased: 2026-05-26, Updated: 2026-02-18
      • Next: Stale bot fired (Feb 22); our naming comment (Feb 17) + clarification (Feb 22) are latest — re-request review to unstale; no new reviewer feedback
      • Specifics:
        • Remove redundant Stacked HSV and Stacked LMH renderers done
        • Remove unnecessary static_cast done
        • Rename "Stacked (RGB)" to "Stacked" done
        • All feedback addressed
        • Left comment 2026-02-17 re: Filtered/Stacked naming confusion — see #15996
      • Tested?: yes
    • feature/2025.10oct.20-restore-last-library-selection - #15460 - DRAFT - CHANGES_REQUESTED
      • Issue: #10125
      • Created: 2025-10-08, Last comment: 2026-02-26 (ronso0), Rebased: 2026-05-26, Updated: 2026-02-28
      • Next: CI failing (ronso0 Feb 26) + unrelated Reloop JS changes slipped in — fixed 2026-02-28: removed JS file from commit, cleaned commit message (had # Conflicts: lines), fixed all clang-format violations; re-request review
      • 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.05-hide-unenabled-controllers - #15580 - REVIEW_REQUIRED
      • Issue: #14275
      • Created: 2025-11-05, Last comment: 2025-11-17 (ronso0), Rebased: 2026-05-26, Updated: 2026-02-28
      • Next: Awaiting re-review — ronso0 Nov 17 feedback addressed Feb 28: removed redundant null checks, confirmed rename already done
      • 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
  • 🔧 Secondary Patches
    • bugfix/2026.05may.01-fix-timer-test-potmeter-clamping — upstream test bug
      • Created: 2026-05-01, Rebased: 2026-05-26
      • 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) 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 — LOCAL_ONLY
      • Created: 2026-02-21, Rebased: 2026-05-26, Updated: 2026-02-21
      • Note: originally tracked as residual from midi-makeinputhandler (#16003, merged upstream 2026-06-18) — stands alone
      • Next: Evaluate for upstream PR as standalone fix
      • Specifics:
        • hid_open() calls hid_init() lazily; multiple HidController background threads (one per device) race to call it concurrently on startup
        • hid_init() on the hidraw backend is not thread-safe — concurrent calls corrupt the udev context, crashing inside hid_enumerate
        • Fix: call hid_init() explicitly on the main thread in HidEnumerator::queryDevices() before hid_enumerate() and before any HidController objects are constructed
        • Triggered by 3+ HID devices (Launchpad Pro MK3, MPD218, BeatMix4) spawning concurrent background descriptor-fetch threads
      • Tested?: yes (crash no longer reproduced)
  • 🐛 BUG FIXES - Open PRs (REVIEW_REQUIRED)
    • bugfix/2026.02feb.19-textured-waveform-fbo-resize - #16010 - REVIEW_REQUIRED
      • Created: 2026-02-19, Last comment: none, Rebased: 2026-05-26, Updated: 2026-02-19
      • 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
      • Created: 2026-02-19, Last comment: none, Rebased: 2026-05-26, Updated: 2026-02-19
      • 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
    • bugfix/2026.02feb.19-wayland-opengl-resize-warning - #16014 - REVIEW_REQUIRED
      • Issue: #16013
      • Created: 2026-02-19, Last comment: 2026-05-26 (daschuer — "Does wayland-egl make a difference?"), Rebased: 2026-05-26, Updated: 2026-05-27
      • Next: Await review — replied to daschuer: wayland-egl is not separate on Qt6; updated check to startsWith("wayland") for Qt5 variants; clang-format applied 2026-05-27, CI re-triggered
      • 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
        • Detection uses startsWith("wayland") to cover Qt5 variants (wayland-egl, wayland-generic, wayland-xcomposite-egl, wayland-xcomposite-glx)
        • References issues #16013 (slow resize) and #14492 (sticky mouse on waveform)
      • Tested?: yes
  • 🟡 NEW FEATURES - Open PRs (REVIEW_REQUIRED)
    • feature/2026.05may.03-extend-waveform-zoom-range — No PR yet
      • Created: 2026-05-03, Rebased: 2026-05-26, Updated: 2026-05-03
      • 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.26-waveform-menu-order - #16046 - CHANGES_REQUESTED → addressed
      • Created: 2026-02-26, Last comment: 2026-05-26 (daschuer — simplify, move order to factory), Rebased: 2026-05-26, Updated: 2026-05-26
      • 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/2026.02feb.20-simple-waveform-top-and-overview - #16021 - REVIEW_REQUIRED
      • Issue: #16020
      • Created: 2026-02-20, Last comment: 2026-05-28 (mxmilkiib, stale-bot reset), Rebased: 2026-05-26, Updated: 2026-05-28
      • Next: Await review
      • Specifics:
        • Moves Simple to top of main waveform type combobox (after alphabetical sort)
        • Adds Simple as an overview waveform type (amplitude envelope, signal color, stereo mirrored)
        • Moves Simple to top of overview waveform combobox
      • Tested?: yes (2026-05-28)
    • feature/2025.10oct.21-replace-libmodplug-with-libopenmpt - #15519 - DRAFT - REVIEW_REQUIRED
      • Issue: #9862
      • Created: 2025-10-25, Last comment: 2026-02-22 (stale-bot), Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Address daschuer architecture feedback
      • Specifics:
        • DSP in SoundSource is "foreign to Mixxx" — daschuer wants bit-perfect decode, move DSP to effect rack instead
        • Rename constants to kXBassBufferSize style naming (daschuer)
        • Remove VS Code minimap // MARK: comments
        • Review comments on trackerdsp.cpp and trackerdsp.h
        • Windows CI test failure (screenWillSentRawDataIfConfigured timeout) — may be flaky or platform-specific QImage behavior
        • Test fix 2026-02-19: taglibStringToEnumFileType now excludes all openmpt tracker formats (mod, s3m, xm, it, mptm, 669, amf, ams, dbm, dmf, dsm, far, mdl, med, mtm, mt2, psm, ptm, ult, umx) — none are taglib formats
      • Tested?: no
    • feature/2025.10oct.20-hotcues-on-overview-waveform - #15514 - DRAFT - REVIEW_REQUIRED
      • Issue: #14994
      • Created: 2025-10-20, Last comment: 2026-02-22 (stale-bot), Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Check recent comment, await review
      • Specifics:
        • PR marked stale (Jan 19 2026) — needs activity to unstale
        • Paint hotcues on scaled image (option b) not full-width — scaling happens in OverviewCache so fixed pixel widths don't translate
        • Remove // MARK: comments
        • Get cue data from delegate columns instead of SQL queries (done)
        • Review feedback from ronso0 on marker rendering approach
      • Tested?: no
    • feature/2025.11nov.17-deere-channel-mute-buttons - #15624 - DRAFT - REVIEW_REQUIRED
      • Issue: #15623
      • Created: 2025-11-17, Last comment: 2026-02-15, Rebased: 2026-05-26, Updated: 2026-02-15
      • 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/2025.11nov.16-playback-position-control - #15617 - REVIEW_REQUIRED
      • Issue: #14288
      • Created: 2025-11-16, Last comment: 2026-05-26 (mxmilkiib), Rebased: 2026-05-26, Updated: 2026-05-26
      • Next: Await re-review — all ronso0 CHANGES_REQUESTED addressed 2026-05-26; 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
  • ⚠️ Schema-Changing Branches (Excluded from Integration)
    • feature/2025.10oct.17-library-column-hotcue-count - #15462 - REVIEW_REQUIRED
      • Issue: #15461
      • Created: 2025-10-17, Last comment: 2026-02-22 (stale-bot), Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Check recent comment, await review
      • 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
      • Created: 2025-11-16, Last comment: 2026-02-15, Rebased: 2026-05-26, Updated: 2026-02-15
      • 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.02feb.17-mono-waveform-optionSKIP_BRANCHES (build-broken)
      • Created: 2026-02-17, Rebased: 2026-05-26, Updated: 2026-02-17
      • Test binary: [build-fail 2026-05-13]
      • Next: Fix base class — add MonoSignal to WaveformRendererSignalBase::Option enum and store m_options as protected member; both were expected by the branch but never committed or were lost in rebase
      • Specifics:
        • waveformrendererfiltered.cpp and waveformrendererhsv.cpp reference m_options (line 89/90) and Option::MonoSignal — neither declared anywhere in the current base class
        • Base class WaveformRendererSignalBase Option enum only has: None, SplitStereoSignal, HighDetail, AllOptionsCombined
        • Fix: add MonoSignal = 0b100 to Option enum, store m_options in protected block, handle in both .cpp files
    • feature/2025.10oct.14-waveform-hotcue-label-options
      • Created: 2025-10-14, Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Maintain for personal use
    • feature/2025.10oct.08-utf8-string-controls
      • Dependency for: hotcue-labelling, hotcue-label-options
      • Created: 2025-10-08, Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Maintain for personal use (not for upstream)
    • feature/2025.09sep.25-hotcue-labelling
      • Created: 2025-09-25, Rebased: 2026-05-26, Updated: 2026-02-20
      • Next: Maintain for personal use
    • feature/2025.11nov.05-deere-waveform-zoom-deck-colors
      • Created: 2025-11-05, Rebased: 2026-05-26, Updated: 2026-01-30
      • Next: Merge to integration, decide if PR-worthy
      • Specifics:
        • Evaluate if the Deere-specific waveform zoom deck color change is worth a PR or remains personal use
        • Test visual appearance across deck configurations
    • draft/2025.10oct.21-tracker-module-stemsSKIP_BRANCHES (build-broken)
      • Created: 2025-10-21, Rebased: 2026-05-26, Updated: 2025-10-21
      • Test binary: [build-fail 2026-05-13]
      • Next: Investigate libopenmpt API — openmpt::module::set_channel_mute_status is absent from the stable C++ API (verified against libopenmpt 0.8.6); either the method does not exist in any release and the code must be rewritten using available API, or the branch was written against a proposed/unreleased API
      • Specifics:
        • soundsourceopenmptstem.cpp lines 216/221 call m_pModule->set_channel_mute_status(ch, true/false) which has no entry in the libopenmpt C++ headers
        • No alternative mute/channel-render method found in openmpt::module (0.8.6); C API also has no equivalent
        • Depends on replace-libmodplug-with-libopenmpt (#15519) being accepted first
        • Block until correct API is identified or available
    • feature/2025.02feb.17-waveform-blend-customization — LOCAL_ONLY — not in integration (branch name year typo: should be 2026)
      • Created: 2026-02-17, Rebased: 2026-05-26 (empty — no commits), Updated: unknown
      • Note: untracked worktree in mixxx-dev; branch has zero commits; placeholder or lost work
      • Next: Decide whether to populate, repurpose, or remove worktree
    • 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
  • 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

Integration Log

  • Integration rebuilt 2026-02-19: applied waveform FBO + openglwindow resize fixes; fixed hotcue-labelling merge (missing setLabel/slotHotcueLabelChangeRequest); merged midi-makeinputhandler-null-engine bugfix (was missing, caused SIGSEGV/SIGABRT on controller shutdown)
  • Integration rebuilt 2026-02-19 (second time): removed hotcue-count and catalogue-number branches — both require schema changes (v41, v42) that caused a cross-thread SQLite crash (SIGSEGV in BaseTrackCache::updateIndexWithQuery via Qt::DirectConnection on engine thread). Schema kept at upstream v40.
  • Integration patched 2026-02-19: midi-makeinputhandler-null-engine fix was missing from the rebuild — caused repeated SIGSEGV/SIGABRT on controller shutdown (MidiControllerJSProxy::makeInputHandler null shared_ptr). Re-merged.
  • Wayland root cause identified 2026-02-19: QOpenGLWindow subsurface resize blocks on compositor buffer realloc; workaround QT_QPA_PLATFORM=xcb
  • XCB resize gap 2026-02-19: WA_PaintOnScreen approach abandoned — WGLWidget lacks paintEngine(), causes heap corruption abort; gap is inherent to QOpenGLWindow+createWindowContainer
  • Integration updated 2026-02-20: added controlpickermenu-quickfx-deck-offset (#16019), fix-learning-wizard-from-prefs-button; fixed hotcue-labelling missing setLabel/slotHotcueLabelChangeRequest; build clean
  • Integration updated 2026-02-21: merged simple-waveform-top-and-overview (Simple to top of main waveform list; Simple overview type)
  • Integration updated 2026-02-21 (2): added Layered (LMH tail-to-tail) and Stems (stem channels) as main waveform types; build clean
  • Integration updated 2026-02-21 (3): added CQT spectrogram main waveform type (frequency-band heatmap, showcqt-style hue mapping); build clean
  • Integration updated 2026-02-21 (4): added Layered RGB (RGB colours, tail-to-tail); fixed CQT missing from mixxx-lib CMake target; build clean
  • Integration patched 2026-02-21 (5): added hid-init-race-on-enumeration secondary patch (explicit hid_init() before hid_enumerate prevents concurrent re-init crash from background descriptor fetch threads); WaveformRendererCQT zero visualIncrementPerPixel guard
  • Integration rebuilt 2026-02-28: rebased all branches on upstream/main (new upstream commits include stems model crash fix, MK2 LUT fixes, ringbuffer memory leak fix); resolved merge conflicts in waveformoverviewrenderer.cpp (StackedRGB vs Simple waveform ordering); added missing m_options member variables to waveformrendererfiltered.cpp and waveformrendererhsv.cpp; build clean
  • Integration patched 2026-02-23: re-merged controller-wizard-quick-access (clazy range-loop-detach fix — std::as_const on all range-for loops over Qt containers); build clean
  • Integration rebuilt 2026-03-11: rebased all branches on upstream/main (198 new upstream commits: fivefourths/fourfifths merged upstream as #16026, rating controls #15764, tuning field, BPM lock, key comparison effect, TS definitions); fivefourths and controlpickermenu-quickfx-deck-offset moved to Merged to Upstream; added waveform-menu-order (#16046); resolved conflicts in overviewtype.h + waveformoverviewrenderer.cpp/h + woverview.cpp (StackedRGB+Simple coexistence), dlgprefwaveform.cpp (moveWaveformTypeToIndex), cuecontrol.cpp/h (setLabel+slotHotcueLabelChangeRequest), dlgprefcontroller.h (showLearningWizard); build clean
  • Integration rebuilt 2026-05-01: rebased all branches on upstream/main (349 new upstream commits: kbd-mapping-reload, sidebar expand-on-hover, Parser public method, overview leaveEvent fix, cue button DnD fixup, bulk controller async polling, pref sound stretch column, export loops to Engine DJ, QML enhanced settings, SQLite3 link fix); resolved conflicts in cuecontrol.cpp/h (setLabel+slotHotcueLabelChangeRequest), wmainmenubar.cpp/h (KeyboardEventFilter+Controller coexistence), dlgprefcontroller.h (showLearningWizard), waveformoverviewrenderer.cpp/h+woverview.cpp+overviewtype.h (StackedRGB+Simple coexistence), dlgprefwaveform.cpp (moveWaveformTypeToIndex lambda); build clean
  • Integration updated 2026-05-11: merged upstream/main (13 new commits: Algoriddim djay support, Android output type name fix, beats preferences fix, 2.6 sync); resolved wmainmenubar.cpp/h conflict in controller-wizard-quick-access (KeyboardEventFilter+Controller coexistence); stripped cross-contamination commit from waveform-menu-order ("Add Simple overview waveform type" was a duplicate from simple-waveform branch); system protobuf upgraded v33→v34, stale test binaries block pre-push hook (not a code issue); feature branch pushes for controller-wizard and waveform-menu-order deferred pending test binary rebuild
  • Integration updated 2026-05-26: rebased all 21 active branches on upstream/main; all test binaries up to date; 21/21 tests pass; integration + integrating pushed; GA CI triggered on origin/integrating
  • Integration initiated 2026-06-18: 182 new upstream commits (SoundManager refactor, cmake4 fix, BPM-lock crash fix, non-latin char SoundSource test, FLAC recording fix, instantaneous-frequency detector, LateNightQML colour schemes + QML fixes, shout API warnings, library header assert crash fix); #16003 (midi-makeinputhandler-null-engine) merged upstream — moved to Merged to Upstream, worktree removed; simple-waveform-top-and-overview sentinel stale (updated 2026-05-28/29, needs re-test)
  • Integration updated 2026-06-18: taglib 1.x→2.x system upgrade required full cmake cache wipe (CMakeCache.txt + libdjinterop ExternalProject stamp dirs) and Ninja reconfigure across all 21 worktrees; 21/21 rebuilt clean against taglib 2.3; 21/21 tests pass; integrating pushed (d4ac90d11b); GA CI queued

TODO Summary

  • Awaiting re-review (5 PRs): restore-last-library-selection (#15460), controller-wizard-quick-access (#15577), stacked-overview-waveform (#15516), hide-unenabled-controllers (#15580), wayland-opengl-resize-warning (#16014, clang-format fixed 2026-05-27 — CI re-triggered)
  • Merged upstream: midi-makeinputhandler-null-engine (#16003 — MERGED 2026-06-18; worktree removed)
  • CHANGES_REQUESTED addressed, awaiting re-review: playback-position-control (#15617, addressed 2026-05-26 — CI failures are pre-existing flaky: Flatpak aarch64 network timeout, macOS x64 BeatsTranslateTest SEGFAULT unrelated to our changes); simple-waveform-top-and-overview (#16021, addressed 2026-05-29 — removed stray StackedRGB enum entry, daschuer+ninomp feedback resolved)
  • Architecture changes needed: replace-libmodplug-with-libopenmpt (daschuer: DSP to effect rack)
  • Draft / on hold: deere-channel-mute-buttons (draft — needs broader plan)
  • Secondary patches: hid-init-race-on-enumeration (evaluate for upstream PR)
  • Local dev decisions: deere-waveform-zoom-deck-colors (PR-worthy?), tracker-module-stems (continue or archive?)

Testing Checklist (Before Pushing to PR upstream)

Pre:

  • Branch rebased on latest mixxxdj/mixxx main

During:

  • Builds without errors
  • No new compiler warnings
  • Basic functionality tested

Post:

  • No regressions in related features

Batch Branch Update Process

This process updates all feature/bugfix branches in mixxx-dev/ to latest upstream:

  • Upstream MUST be fetched first (from any worktree): git fetch upstream
  • For each worktree directory in ~/src/mixxx-dev/:
    • The branch MUST be rebased on upstream/main: git rebase upstream/main
    • Conflicts MUST be resolved if any occur
    • The "Rebased" date in INTEGRATION.md MUST be updated
  • Rebased branches are NOT automatically pushed — use --push-changed to push only branches whose patch content differs from origin (avoids wasteful CI on pure rebases)
  • Branches with unresolved conflicts SHOULD be noted for later attention
  • After all branches are updated, the Integration Merge Process SHOULD be run

Automated via ./mixxx-integration-update-branches.sh (run from ~/src/mixxx-dev/integration/).

Dev Helper Scripts

All scripts use the mixxx-dev- or mixxx-integration- filename prefix. All are committed to the integration branch and synced to the gist at the URL in the header.

Script Gist Purpose
mixxx-integration-update-branches.sh 5fb35c4 Rebase all worktrees (no push), configure+build test binaries (parallel configure, serial build, nice 15), run test suite with per-branch sentinels for selective re-runs, smart-diff push (--push-changed — only content changes trigger CI), push integration/integrating, grand summary
mixxx-dev-gdb-run.sh da0d174 Launch Mixxx under GDB with --developer --controller-debug --debug-assert-break; auto-detects the mixxx binary; logs to timestamped file, discards on clean exit; sets debuginfod enabled, suppresses SIG32/SIGPIPE/SIGUSR*

Integration Merge Process

This process merges all [x] marked branches into the integration branch for a combined bleeding-edge build.

Steps

  1. Commit pending INTEGRATION.md changes (if any) before starting:

    git add INTEGRATION.md && git commit -m "update INTEGRATION.md before integration"
  2. Fetch upstream

    git fetch upstream
  3. Run batch branch update to rebase all worktree branches on upstream/main:

    ./mixxx-integration-update-branches.sh
  4. Checkout the integration branch

    git checkout integration
  5. Merge upstream/main into integration (merge, not rebase, to preserve integration history):

    git merge upstream/main
  6. Merge each [x] branch from the outline that has "Next: Merge to integration":

    git merge <local-branch-name>
  7. Resolve merge conflicts carefully. Common issues:

    • Schema revisions: increment version numbers
    • Enum IDs in trackmodel.h: assign unique IDs
    • Header declarations vs implementations: keep both sides' additions
  8. Update INTEGRATION.md:

    • Change [ ] to [x] for newly merged branches
    • Update "Rebased" and "Updated" dates to today
    • Update the summary line counts
    • Update the "Last updated" date at the top
  9. Build and verify — integration branch MUST be rebuilt after any branch is added or modified:

    Incremental rebuild (most common — after source changes):

    cmake --build /home/milkii/src/mixxx/build --target mixxx -- -j$(nproc --ignore=2)

    Full reconfigure (only needed when new branches add CMakeLists changes or new source files):

    cmake -B /home/milkii/src/mixxx/build -S /home/milkii/src/mixxx -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCCACHE_SUPPORT=ON
    cmake --build /home/milkii/src/mixxx/build --target mixxx -- -j$(nproc --ignore=2)

    Basic functionality SHOULD be tested after build.

  10. Sync to Gist:

    gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename INTEGRATION.md INTEGRATION.md
    gh gist edit 5fb35c401736efed47ad7d78268c80b6 --filename mixxx-integration-update-branches.sh mixxx-integration-update-branches.sh

Checking PR Status

gh pr view <PR-number>
gh pr list --repo mixxxdj/mixxx --author mxmilkiib

Outline Format Reference

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

Branch Entry Format

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

- [x] **branch-name** - [#PR](url) - STATUS
  - Issue: [#ISSUE](url)
  - Optional description
  - Created: YYYY-MM-DD, Last comment: YYYY-MM-DD, Rebased: YYYY-MM-DD, Updated: YYYY-MM-DD
  - Next: Action item
  - Specifics:
    - Details about the branch and what probably should happen next
  • [x] = merged to integration, [ ] = not merged
  • Branch name in bold
  • Issue link to related Mixxx issue/feature request (if applicable)
  • Created date required for all branches
  • Last comment date shows most recent PR comment ("none" if no comments), only for PRs
  • Rebased date shows when branch was last rebased on mixxxdj/mixxx main ("none" if never)
  • Updated date tracks last modification to branch
  • Next action describes what needs to be done for this branch
  • 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, LOCAL_ONLY
  • Secondary patch entries use Resolves-residual-from or Depends-on instead of Issue to link to the primary branch

Section Order

  1. Awaiting Review from Others (feedback addressed, waiting on reviewer)
  2. Secondary Patches
  3. Bug Fixes — Open PRs (REVIEW_REQUIRED)
  4. New Features — Open PRs (REVIEW_REQUIRED)
  5. Schema-Changing Branches (Excluded from Integration)
  6. Local Only (No PR)
  7. Merged to Upstream

Summary Line

When updating integration: Update the "Last updated" date at the top of this file.

Update the summary line at the top when adding/removing branches:

**Summary**: X need attention, Y awaiting review, Z schema-excluded, W merged upstream, V local-only, U secondary patch
#!/bin/bash
# Mixxx GDB runner with UX enhancements and datetime logging
# Usage: mixxx-gdb-run [additional mixxx args]
# Gist: https://gist.github.com/mxmilkiib/da0d174d1bf80bd6d3f182d5e62186ec
set -e
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 executable in ${SCRIPT_DIR}/build, current dir, or ${HOME}/src/mixxx/build." >&2
exit 1
fi
GDB_OPTS=(
-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 'bt full'
-ex 'info registers'
-ex 'thread apply all bt'
)
# Log alternatives: rr (record/replay), lldb (modern CLI), pwndbg/gef (GDB plugins), gdb-dashboard, Qt Creator, VS Code+Native Debug, gdbgui
printf -v CMD '%q ' gdb --batch "${GDB_OPTS[@]}" --args "${MIXXX_EXE}" ${MIXXX_ARGS}
CMD="${CMD% }"
echo "Logging to: ${LOG_FILE}"
{
echo "# ${CMD}"
echo ""
gdb --batch "${GDB_OPTS[@]}" --args "${MIXXX_EXE}" ${MIXXX_ARGS} 2>&1
} | tee "$LOG_FILE"
EXIT_CODE=${PIPESTATUS[0]}
if [ "$EXIT_CODE" -eq 0 ]; then
rm -f "$LOG_FILE"
echo "Clean exit, log discarded."
else
echo "Exit code ${EXIT_CODE}, log saved to: ${LOG_FILE}"
fi
#!/bin/bash
# Pre-push hook logic for mxmilkiib/mixxx.
# This file is committed to the integration branch and versioned alongside
# mixxx-integration-update-branches.sh. The actual .git/hooks/pre-push is a
# thin delegate that execs this script, so hook logic is tracked in git.
#
# Behaviour:
# 1. Runs clang-format diff check on changed lines (not full files).
# 2. Runs mixxx-test from $REPO_ROOT/build (skips gracefully if binary missing
# or has stale shared libs after a system update).
# 3. Allows all pushes to mxmilkiib/* remotes unconditionally.
# 4. For all other remotes (upstream PRs etc.), blocks pushes that touch
# local-only files: INTEGRATION.md, the integration script, the pre-push
# script, and the GDB helper — these must never reach mixxxdj/mixxx.
#
# KNOWN_FAILING must be kept in sync with the same constant in
# mixxx-integration-update-branches.sh. Remove entries once the upstream
# fix is merged into mixxxdj/mixxx main.
remote="$1"
url="$2"
# ── 1. clang-format style check (diff only, not full files) ───────────────────
echo "Running clang-format style check on changed lines..."
REPO_ROOT="$(git rev-parse --show-toplevel)"
if command -v git-clang-format &> /dev/null || git clang-format --help &> /dev/null; then
style_diff=$(git clang-format --diff HEAD~1 -- "*.cpp" "*.h" 2>/dev/null)
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! Your changes need formatting:"
echo "$style_diff" | head -30
echo ""
echo "Run: git clang-format HEAD~1"
echo "Then: git add -u && git commit --amend --no-edit"
exit 1
fi
echo "Style check passed!"
else
echo "Warning: git-clang-format not found, skipping style check"
fi
# ── 2. test suite (main worktree build only) ───────────────────────────────────
# Skips silently if binary is absent or has missing shared libs (stale after a
# system update). Full worktree test coverage is provided by --run-tests.
echo "Running Mixxx tests before push..."
BUILD_DIR="$REPO_ROOT/build"
# ControllerScriptEngineLegacyTimerTest.*: coTimerId ControlPotmeter max=50 clamped QTimer IDs;
# ALL timer tests filtered (not just singleShot*) because beginTimer_repeatedTimer leaves
# corrupted ID-50 state that causes downstream MidiMappings JS tests to hang indefinitely.
# keepWithespaceKey: getKeyText() returns internal enum string instead of display format
KNOWN_FAILING='ControllerScriptEngineLegacyTimerTest.*:TrackMetadataExportTest.keepWithespaceKey'
HOOK_TIMEOUT=420
if [ -f "$BUILD_DIR/mixxx-test" ]; then
if ldd "$BUILD_DIR/mixxx-test" 2>/dev/null | grep -q "not found"; then
echo "Warning: mixxx-test has missing shared libs (stale binary) — skipping tests."
echo "Rebuild with: cmake --build $BUILD_DIR --target mixxx-test"
else
cd "$BUILD_DIR"
timeout "$HOOK_TIMEOUT" ./mixxx-test --gtest_filter="-${KNOWN_FAILING}"
rc=$?
cd - > /dev/null
if [ $rc -eq 124 ]; then
echo "Tests TIMED OUT after ${HOOK_TIMEOUT}s — possible hang (Behringer_CMD_MM1 timer bug?)."
echo "Push skipped. Investigate with: cd $BUILD_DIR && ./mixxx-test --gtest_filter=MidiMappings"
exit 1
elif [ $rc -ne 0 ]; then
echo "Tests failed! Push aborted."
echo "Fix the failing tests before pushing."
exit 1
fi
echo "All tests passed!"
fi
else
echo "Warning: mixxx-test not found at $BUILD_DIR/mixxx-test, skipping tests"
fi
# ── 3. allow all pushes to personal fork ──────────────────────────────────────
if echo "$url" | grep -qi "mxmilkiib"; then
exit 0
fi
# ── 4. block local-only files from reaching non-personal remotes ───────────────
# These files are personal-workflow files that MUST NOT reach mixxxdj/mixxx.
while read local_ref local_oid remote_ref remote_oid; do
if [ "$local_oid" = "0000000000000000000000000000000000000000" ]; then
continue
fi
if [ "$remote_oid" = "0000000000000000000000000000000000000000" ]; then
range="$local_oid --not --remotes=$remote"
else
range="$remote_oid..$local_oid"
fi
protected_files="INTEGRATION.md mixxx-dev-gdb-run.sh mixxx-integration-update-branches.sh mixxx-integration-pre-push.sh"
for f in $protected_files; do
if git log --diff-filter=ACDMR --name-only --pretty=format: $range -- "$f" | grep -q .; then
echo "ERROR: Push blocked. $f must not be pushed to $remote ($url)."
echo " This file is local-only for mxmilkiib/mixxx."
exit 1
fi
done
done
exit 0
#!/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.
#
# Usage:
# ./mixxx-integration-update-branches.sh rebase all worktrees (no push)
# ./mixxx-integration-update-branches.sh --rebuild-tests detect and rebuild stale test binaries only (serial)
# ./mixxx-integration-update-branches.sh --build-all-tests configure cmake + build ALL non-skipped branches (serial)
# ./mixxx-integration-update-branches.sh --run-tests run mixxx-test suite; skips branches with valid per-branch sentinel
# ./mixxx-integration-update-branches.sh --push-changed push only PR branches whose patch content changed (smart-diff)
# ./mixxx-integration-update-branches.sh --push-integrating promote integration → integrating (requires all worktrees tested)
# ./mixxx-integration-update-branches.sh --promote-integrated promote integrating → integrated (requires GA CI green)
# ./mixxx-integration-update-branches.sh --full rebase + build-all-tests + run-tests + push-integration + push-integrating
#
# Three-branch promotion chain:
# integration — working merges; script operates here; may fail
# integrating — locally tested clean; ALL worktrees must have test binaries and pass
# integrated — GA CI confirmed clean on origin/integrating; safe to build from
#
# ccache cross-worktree sharing:
# Each worktree has a different path, so preprocessor #line markers embed different absolute paths,
# making cache keys differ between worktrees even for identical source files.
# Fix: pass CCACHE_BASEDIR=<worktree-root> to each build — ccache strips that prefix from all paths
# in the hash, normalising them to relative paths. Identical source files in different worktrees
# then produce the same hash and share cache entries.
# Config: ~/.config/ccache/ccache.conf sets hash_dir=false (CWD not in hash) and max_size=15G.
#
# Skip list:
# SKIP_BRANCHES lists bare worktree directory names (no path) for branches that have been merged
# upstream, closed, or abandoned. Their directories may still exist but are ignored by all modes.
# LOCAL_ONLY and schema-excluded branches are NOT in this list — they still get rebased and tested.
# They are excluded from integration merges via the [ ] vs [x] markers in INTEGRATION.md (manual step).
#
# Killing a running build:
# Ctrl-C in the running terminal sends SIGINT to the script's process group, which the trap handles.
# From another shell: kill -TERM -$(pgrep -fo 'mixxx-integration-update-branches.sh' | head -1)
# Do NOT use pkill on cmake/ninja alone — child cc1plus processes will survive and saturate the CPU.
#
# Related files (all committed to integration branch, all synced to gist 5fb35c4):
# mixxx-integration-update-branches.sh — this script
# mixxx-integration-pre-push.sh — hook logic (versioned); .git/hooks/pre-push delegates here
# mixxx-dev-gdb-run.sh — GDB launcher with logging (gist da0d174)
# INTEGRATION.md — branch registry, process rules, status outline
#
# Runtime state files (not committed):
# ~/.cache/mixxx-integration/<name>.tested — per-branch test sentinel: "<HEAD-SHA> pass|fail"
# run_tests_serial skips branches whose SHA + pass matches current HEAD
# ~/.cache/mixxx-integration/tests-passed — global sentinel written when ALL branches pass
# /tmp/mixxx-integration-status — append-only progress log; `tail -f` this in a second terminal
# /tmp/mixxx-test-logs/<name>.log — per-branch gtest output from the most recent test run
set -euo pipefail
# ── dependency check ───────────────────────────────────────────────────────────
# Two tiers: required tools abort immediately with a list of what is missing;
# optional tools emit a warning to stderr but allow the run to continue.
# Called unconditionally at startup, before any other work.
check_deps() {
local missing=() warn=()
local required=(git cmake ninja ldd timeout nice nproc awk free du cut)
local optional_map=(
"ccache:ccache is required for cross-worktree cache sharing (cmake configures it)"
"jq:jq is required for --promote-integrated (GA CI polling)"
"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
MIXXX_DEV="${HOME}/src/mixxx-dev"
# MIXXX_MAIN: the integration worktree where the script, INTEGRATION.md and
# helper scripts live and where git operations on the promotion branches run.
# ~/src/mixxx/ is now checked out on 'integrated' (the daily-driver binary).
MIXXX_MAIN="${HOME}/src/mixxx-dev/integration"
# BUILD_JOBS: leaves 2 threads unallocated. BUILD_NICE lowers compiler priority so
# interactive processes immediately preempt — more effective than core-count alone.
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"
# Known upstream test failures filtered in both run_tests_serial and the pre-push hook.
# Keep in sync with KNOWN_FAILING in .git/hooks/pre-push.
# Remove entries once the upstream fix is merged into mixxxdj/mixxx main.
# ControllerScriptEngineLegacyTimerTest.*: coTimerId ControlPotmeter max=50 clamped QTimer IDs;
# ALL timer tests filtered (not just singleShot*) because beginTimer_repeatedTimer leaves
# corrupted ID-50 state that causes downstream MidiMappings JS tests to hang indefinitely.
# keepWithespaceKey: getKeyText() returns internal enum string instead of display format
KNOWN_FAILING='ControllerScriptEngineLegacyTimerTest.*:TrackMetadataExportTest.keepWithespaceKey'
_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="[$(date '+%H:%M:%S')] $*" pc="" fc=""
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"
# ── skip list ──────────────────────────────────────────────────────────────────
# Worktrees for branches that are merged upstream, closed, or abandoned.
# Update when pruning worktrees. Do NOT add LOCAL_ONLY or schema-excluded branches here —
# those are still rebased/tested; they're excluded from integration merges via INTEGRATION.md markers.
SKIP_BRANCHES=(
"2025.05may.14-fivefourths"
"2025.06jun.08-deere-deck-bg-colour"
"2025.11nov.04-reloop-shift-jog-seek"
"2025.11nov.05-waveform-cache-size-format"
"2025.11nov.16-reloop-beatmix-mk2-naming"
"2026.02feb.19-wglwidget-xcb-resize-gap"
"2026.02feb.20-controlpickermenu-quickfx-deck-offset"
"2026.02feb.20-fix-learning-wizard-from-prefs-button"
# resolved: null engine guard already in upstream/2.5 and upstream/main; PR #16003 closed
"2026.02feb.18-midi-makeinputhandler-null-engine"
"2026.02feb.21-experimental-overview-waveforms"
# build-broken: openmpt::module::set_channel_mute_status absent from libopenmpt stable API
"2025.10oct.21-tracker-module-stems"
# build-broken: m_options member + Option::MonoSignal missing from WaveformRendererSignalBase —
# base class modifications were not committed or lost in rebase
"2026.02feb.17-mono-waveform-option"
)
# ── helpers ────────────────────────────────────────────────────────────────────
is_skipped() {
local n="$1"
for s in "${SKIP_BRANCHES[@]}"; do [[ "$n" == "$s" ]] && return 0; done
return 1
}
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"
}
ensure_ccache_enabled() {
local build_dir="$1/build"
[[ -f "$build_dir/CMakeCache.txt" ]] || return 0
if grep -q "CCACHE_SUPPORT:BOOL=OFF" "$build_dir/CMakeCache.txt"; then
echo " enabling ccache for $(basename "$1")"
cmake -DCCACHE_SUPPORT=ON "$build_dir" > /dev/null 2>&1
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 ──────────────────────────────────────────────────────────
# Default mode (no argument). Fetches upstream/main, then rebases every non-skipped
# worktree in MIXXX_DEV on upstream/main and force-pushes to origin if a remote exists.
# Skipped branches (SKIP_BRANCHES) are listed but otherwise untouched.
# Does NOT rebuild or retest — run --full or --build-all-tests separately.
rebase_all() {
_T_REBASE_START=$(date +%s)
status "PHASE rebase_all — fetching upstream"
GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch upstream
local failed=() skipped=() succeeded=()
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
# skip promotion-chain branches (integration/integrating/integrated) — no YYYY. prefix
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
if is_skipped "$name"; then
skipped+=("$name")
echo "--- Skipping $name (merged/closed/abandoned)"
continue
fi
status "rebase: $name"
if GIT_PAGER=cat git -C "$dir" rebase upstream/main; then
status " $name — rebased"
succeeded+=("$name")
else
status " FAILED $name — aborting rebase"
GIT_PAGER=cat git -C "$dir" rebase --abort 2>/dev/null || true
failed+=("$name")
fi
done
echo ""
_T_REBASE_END=$(date +%s)
_REBASE_N_OK=${#succeeded[@]}; _REBASE_N_SKIP=${#skipped[@]}; _REBASE_N_FAIL=${#failed[@]}
status "DONE rebase_all: ${#succeeded[@]} ok ${#skipped[@]} skipped ${#failed[@]} failed"
if [[ ${#failed[@]} -gt 0 ]]; then
echo "Failed branches:"; printf ' - %s\n' "${failed[@]}"; return 1
fi
}
# ── 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: git diff upstream/main..HEAD vs git diff upstream/main..origin/<branch>
# If the resulting patch text is byte-identical (sha256), the push is skipped.
# If origin/<branch> does not exist, the branch is always pushed (first push).
#
# Push policy categories:
# PUSH_ALWAYS — promotion chain (integration, integrating) — handled elsewhere
# PUSH_ON_CHANGE — all YYYY. worktrees with open PRs (default)
# PUSH_NEVER — SKIP_BRANCHES
push_changed_branches() {
status "PHASE push_changed_branches — smart-diff push (only content changes)"
# Fetch origin to ensure remote-tracking refs are current
GIT_PAGER=cat git -C "$MIXXX_MAIN" fetch origin --prune 2>/dev/null || true
local pushed=() skipped=() no_remote=() failed=()
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
local branch_name
branch_name=$(GIT_PAGER=cat git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
[[ -z "$branch_name" ]] && continue
# Check if origin/<branch> exists
if ! GIT_PAGER=cat git -C "$dir" rev-parse --verify "origin/$branch_name" >/dev/null 2>&1; then
# First push — always push
if GIT_PAGER=cat git -C "$dir" push --no-verify --force-with-lease origin HEAD 2>/dev/null; then
status " $name — pushed (new remote branch)"
pushed+=("$name")
else
status " $name — push FAILED"
failed+=("$name")
fi
continue
fi
# Compare patch content: local vs origin
local local_hash origin_hash
local_hash=$(GIT_PAGER=cat git -C "$dir" diff upstream/main HEAD 2>/dev/null | sha256sum | cut -d' ' -f1)
origin_hash=$(GIT_PAGER=cat git -C "$dir" diff upstream/main "origin/$branch_name" 2>/dev/null | sha256sum | cut -d' ' -f1)
if [[ "$local_hash" == "$origin_hash" ]]; then
skipped+=("$name")
continue
fi
# Content differs — push
if GIT_PAGER=cat git -C "$dir" push --no-verify --force-with-lease origin HEAD 2>/dev/null; then
status " $name — pushed (content changed)"
pushed+=("$name")
else
status " $name — push FAILED"
failed+=("$name")
fi
done
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 ─────────────────────────────────────────────────────
# Two-phase: configure then build.
# Configure (parallel, background): runs cmake -G Ninja for any worktree lacking
# a build dir, collecting pids and waiting for all to finish before building.
# Build (serial, nice 15): builds the mixxx-test target for every worktree that
# either has no binary, has a stale binary (ldd finds missing libs), or was
# just configured. Serial to avoid saturating memory with parallel link steps.
# CCACHE_BASEDIR is set per worktree so ccache path-normalisation works across
# all worktrees — see the ccache section in the header for details.
# After building, ensures ccache is enabled in all build dirs (including MIXXX_MAIN)
# and prints a colourised ccache statistics summary.
build_all_tests() {
_T_BUILD_START=$(date +%s)
status "PHASE build_all_tests"
trap kill_trap INT TERM
local to_configure=() to_build=() name
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
if ! has_build "$dir"; then
to_configure+=("$dir")
to_build+=("$dir")
elif ! has_test_bin "$dir" || is_test_binary_stale "$dir"; then
to_build+=("$dir")
fi
done
if [[ ${#to_configure[@]} -eq 0 && ${#to_build[@]} -eq 0 ]]; then
status "All test binaries up to date."
return 0
fi
if [[ ${#to_configure[@]} -gt 0 ]]; then
echo "Unconfigured (${#to_configure[@]}):"
for dir in "${to_configure[@]}"; do echo " - $(basename "$dir")"; done
fi
if [[ ${#to_build[@]} -gt 0 ]]; then
echo "Need (re)build (${#to_build[@]}):"
for dir in "${to_build[@]}"; do echo " - $(basename "$dir")"; done
fi
echo ""
# cmake configure phase — run all in parallel (configure is I/O-bound, safe to parallelise)
local configure_failed=() _pids=() _pdirs=()
for dir in "${to_configure[@]}"; do
name=$(basename "$dir")
local cfg_log="$CACHE_DIR/configure-${name}.log"
local cmake_args=(-S "$dir" -B "$dir/build"
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DCCACHE_SUPPORT=ON)
command -v ninja > /dev/null 2>&1 && cmake_args+=(-GNinja)
status " configure (launch) $name"
CCACHE_BASEDIR="$dir" cmake "${cmake_args[@]}" > "$cfg_log" 2>&1 &
_pids+=($!)
_pdirs+=("$dir")
done
echo " Waiting for ${#_pids[@]} parallel configures..."
for _i in "${!_pids[@]}"; do
local _n; _n=$(basename "${_pdirs[$_i]}")
if wait "${_pids[$_i]}"; then
status " configure OK: $_n"
else
status " configure FAILED: $_n (log: $CACHE_DIR/configure-${_n}.log)"
configure_failed+=("$_n")
fi
done
if [[ ${#configure_failed[@]} -gt 0 ]]; then
printf ' configure FAILED: %s\n' "${configure_failed[@]}"; return 1
fi
# ensure ccache on all build dirs (newly configured + existing)
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
is_skipped "$(basename "$dir")" && continue
ensure_ccache_enabled "$dir"
done
ensure_ccache_enabled "$MIXXX_MAIN"
# build phase
printf '%b\n' "Building serially — ${_C_BLD}-j${BUILD_JOBS}${_C_NC}, nice ${BUILD_NICE}, CCACHE_BASEDIR per worktree..."
local built=() build_failed=() idx=0 _phase_t0; _phase_t0=$(date +%s)
for dir in "${to_build[@]}"; do
name=$(basename "$dir")
(( idx++ )) || true
local _t0; _t0=$(date +%s)
local _commits _bsz
_commits=$(GIT_PAGER=cat git -C "$dir" log --oneline HEAD ^upstream/main 2>/dev/null | wc -l || echo "?")
_bsz=$(du -sh "$dir/build" 2>/dev/null | cut -f1 || echo "?")
printf '%b\n' "${_C_BLD} build [$idx/${#to_build[@]}] ${name}${_C_NC} ${_C_DIM}(${_commits} commits, build dir: ${_bsz})${_C_NC}"
if build_with_progress "$dir" mixxx-test "$idx" "${#to_build[@]}"; 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" "${#to_build[@]}" "$(( $(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)
echo ""; printf '%b\n' "=== Build: ${_C_GRN}${#built[@]} ok${_C_NC} $([[ ${#build_failed[@]} -gt 0 ]] && printf '%b' "${_C_RED}${#build_failed[@]} failed${_C_NC}" || echo "0 failed") ==="
[[ ${#build_failed[@]} -eq 0 ]] || { printf ' FAILED: %s\n' "${build_failed[@]}"; return 1; }
echo ""
status "ccache summary:"
ccache -s 2>/dev/null | while IFS= read -r line; do
case "$line" in
*"hit"*) printf '%b\n' " ${_C_GRN}${line}${_C_NC}" ;;
*"miss"*) printf '%b\n' " ${_C_YLW}${line}${_C_NC}" ;;
*"size"*|*"files"*) printf '%b\n' " ${_C_BLD}${line}${_C_NC}" ;;
*) printf '%b\n' " ${_C_DIM}${line}${_C_NC}" ;;
esac
done || printf '%b\n' " ${_C_DIM}(ccache -s unavailable)${_C_NC}"
}
# ── mode: rebuild_tests_serial ────────────────────────────────────────────────────
# Lighter alternative to --build-all-tests: skips branches with a healthy binary,
# rebuilds only those whose mixxx-test has missing shared libs (ldd "not found").
# Does NOT run cmake configure — use --build-all-tests for branches with no build dir.
rebuild_tests_serial() {
status "PHASE rebuild_tests_serial"
trap kill_trap INT TERM
local stale=() no_build=()
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
is_skipped "$name" && continue
if ! has_test_bin "$dir"; then
no_build+=("$name")
elif is_test_binary_stale "$dir"; then
stale+=("$name")
fi
done
if [[ ${#no_build[@]} -gt 0 ]]; then
echo "No test binary — build not configured (these branches are skipped):"
printf ' - %s\n' "${no_build[@]}"
fi
if [[ ${#stale[@]} -eq 0 ]]; then status "All test binaries up to date."; return 0; fi
echo ""; echo "Stale (${#stale[@]}):"; printf ' - %s\n' "${stale[@]}"; echo ""
echo "Enabling ccache on affected build dirs..."
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
is_skipped "$(basename "$dir")" && continue
ensure_ccache_enabled "$dir"
done
ensure_ccache_enabled "$MIXXX_MAIN"
echo "Rebuilding serially — -j${BUILD_JOBS}, CCACHE_BASEDIR per worktree for cross-worktree sharing..."
local rebuilt=() build_failed=() idx=0
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
is_test_binary_stale "$dir" || continue
(( idx++ )) || true
if build_with_progress "$dir" mixxx-test "$idx" "${#stale[@]}"; then
$_HAS_TTY && printf "\n"; status " rebuild done: $name"; rebuilt+=("$name")
else
$_HAS_TTY && printf "\n"; status " rebuild FAILED: $name"; build_failed+=("$name")
fi
done
echo ""; echo "=== Rebuild: ${#rebuilt[@]} ok ${#build_failed[@]} failed ==="
[[ ${#build_failed[@]} -eq 0 ]] || { printf ' FAILED: %s\n' "${build_failed[@]}"; return 1; }
}
# ── mode: run_tests_serial ────────────────────────────────────────────────────
# Runs mixxx-test serially across all non-skipped worktrees that have a test binary.
# Selective re-run via per-branch sentinels: if ~/.cache/mixxx-integration/<name>.tested
# contains the current HEAD SHA with status "pass", that branch is skipped entirely.
# A background heartbeat job prints running test count every 30s so the terminal is
# not silent during long-running tests. Tests are filtered by KNOWN_FAILING.
# On completion, writes/updates the per-branch sentinel and (if all pass) the global
# sentinel at SENTINEL_FILE. Any failure writes "fail" to the sentinel — the next
# run will re-test that branch.
run_tests_serial() {
_T_TEST_START=$(date +%s)
trap kill_trap INT TERM
mkdir -p "$TEST_LOG_DIR" "$CACHE_DIR"
# Selective: skip branches whose per-branch sentinel matches their current HEAD
local dirs_with_tests=() skipped_names=()
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
has_test_bin "$dir" || continue
local branch_sha; branch_sha=$(GIT_PAGER=cat git -C "$dir" rev-parse HEAD 2>/dev/null || echo unknown)
local bsentinel="$CACHE_DIR/${name}.tested"
if [[ -f "$bsentinel" ]]; then
local s_sha s_status; read -r s_sha s_status < "$bsentinel"
if [[ "$s_sha" == "$branch_sha" && "$s_status" == "pass" ]]; then
skipped_names+=("$name")
_TEST_RESULTS+=("${name}:0:skip")
continue
fi
fi
dirs_with_tests+=("$dir")
done
[[ ${#skipped_names[@]} -gt 0 ]] && \
echo "Sentinel current — skipping ${#skipped_names[@]}: ${skipped_names[*]}"
local total=${#dirs_with_tests[@]}
if [[ $total -eq 0 ]]; then
status "All worktrees have current passing sentinels — nothing to run"
return 0
fi
status "PHASE run_tests_serial ($total to run, ${#skipped_names[@]} skipped) — upstream failures filtered"
echo "Logs: $TEST_LOG_DIR"
echo ""
local passed=() failed=() idx=0
for dir in "${dirs_with_tests[@]}"; do
local name; name=$(basename "$dir")
(( idx++ )) || true
local log="$TEST_LOG_DIR/${name}.log"
local timeout_secs=420
local t0; t0=$(date +%s)
local branch_sha; branch_sha=$(GIT_PAGER=cat git -C "$dir" rev-parse HEAD 2>/dev/null || echo unknown)
status "test [$idx/$total] $name (log: $log)"
echo " Monitor: tail -f $log"
# background heartbeat: print test count every 30s so terminal is not silent
( while true; do
sleep 30
local c; c=$(grep -ac 'RUN ' "$log" 2>/dev/null || true)
local el=$(( $(date +%s) - t0 ))
status " ... [$idx/$total] $name — ${c:-?} tests, ${el}s elapsed"
done ) &
local _hb_pid=$!
if (cd "$dir/build" && timeout "$timeout_secs" ./mixxx-test --gtest_filter="-${KNOWN_FAILING}") > "$log" 2>&1; then
kill "$_hb_pid" 2>/dev/null; wait "$_hb_pid" 2>/dev/null || true
local elapsed=$(( $(date +%s) - t0 ))
status " PASS [$idx/$total] $name (${elapsed}s)"
passed+=("$name")
_TEST_RESULTS+=("${name}:${elapsed}:pass")
printf '%s pass\n' "$branch_sha" > "$CACHE_DIR/${name}.tested"
else
local rc=$?
kill "$_hb_pid" 2>/dev/null; wait "$_hb_pid" 2>/dev/null || true
local elapsed=$(( $(date +%s) - t0 ))
if [[ $rc -eq 124 ]]; then
status " TIMEOUT [$idx/$total] $name (exceeded ${timeout_secs}s after ${elapsed}s)"
status " Last: $(grep -a 'RUN ' "$log" | tail -1 || echo unknown)"
_TEST_RESULTS+=("${name}:${elapsed}:fail")
else
status " FAIL [$idx/$total] $name (${elapsed}s, see $log)"
grep -E "^\[ FAILED \]" "$log" | grep -v "FAILED\] [0-9]" | head -8 || true
_TEST_RESULTS+=("${name}:${elapsed}:fail")
fi
failed+=("$name")
printf '%s fail\n' "$branch_sha" > "$CACHE_DIR/${name}.tested"
fi
[[ $(( idx % 5 )) -eq 0 ]] && sys_stats
eta_line "$idx" "$total" "$(( $(date +%s) - _T_TEST_START ))" "test"
done
_T_TEST_END=$(date +%s)
echo ""
local total_pass=$(( ${#passed[@]} + ${#skipped_names[@]} ))
status "DONE run_tests_serial: ${#passed[@]} pass ${#failed[@]} fail ${#skipped_names[@]} skipped"
if [[ ${#failed[@]} -eq 0 ]]; then
local head_sha; head_sha=$(GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse HEAD)
printf '%s %d\n' "$head_sha" "$total_pass" > "$SENTINEL_FILE"
status " Global sentinel written ($total_pass branches passing)"
else
status "Failed: ${failed[*]}"; return 1
fi
}
# ── mode: push_integration ────────────────────────────────────────────────────
# Force-pushes the current integration branch to origin/integration.
# Triggers the pre-push hook (style check + main-worktree test run).
# Safe to run at any time — integration is intentionally ephemeral.
push_integration() {
status "PHASE push_integration"
GIT_PAGER=cat git -C "$MIXXX_MAIN" push --force-with-lease origin integration
status "DONE integration branch pushed"
}
# ── mode: push_integrating ────────────────────────────────────────────────────
# Gate 2: promotes integration → integrating only when ALL non-skipped worktrees
# satisfy both:
# (a) a test binary exists and passes ldd (not stale), AND
# (b) a per-branch sentinel exists for the current HEAD with status "pass".
# Pushing integrating triggers GA CI on a clean runner — different from local tests
# which run on the same kernel/libs as the build.
push_integrating() {
# Gate 1: every non-skipped worktree must have a test binary
local missing=() current_bins=0
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
if has_test_bin "$dir"; then
(( current_bins++ )) || true
else
missing+=("$name")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
status "BLOCKED push_integrating: ${#missing[@]} active branches have no test binary:"
printf ' - %s\n' "${missing[@]}"
status " Run: $0 --build-all-tests then $0 --run-tests"
return 1
fi
# Gate 2: every branch must have a per-branch passing sentinel for its current HEAD
local need_test=()
for dir in "${MIXXX_DEV}"/*/; do
[[ -d "$dir" ]] || continue
local name; name=$(basename "$dir")
[[ "$name" =~ ^[0-9]{4}\. ]] || continue
is_skipped "$name" && continue
has_test_bin "$dir" || continue
local branch_sha; branch_sha=$(GIT_PAGER=cat git -C "$dir" rev-parse HEAD 2>/dev/null || echo unknown)
local bsentinel="$CACHE_DIR/${name}.tested"
if [[ ! -f "$bsentinel" ]]; then
need_test+=("$name (no sentinel)")
else
local s_sha s_status; read -r s_sha s_status < "$bsentinel"
if [[ "$s_sha" != "$branch_sha" ]]; then
need_test+=("$name (HEAD moved)")
elif [[ "$s_status" != "pass" ]]; then
need_test+=("$name (last: $s_status)")
fi
fi
done
if [[ ${#need_test[@]} -gt 0 ]]; then
status "BLOCKED push_integrating: ${#need_test[@]} branches need passing tests:"
printf ' - %s\n' "${need_test[@]}"
status " Run: $0 --run-tests"
return 1
fi
status "PHASE push_integrating — all $current_bins worktrees built + per-branch tested"
GIT_PAGER=cat git -C "$MIXXX_MAIN" branch -f integrating HEAD
GIT_PAGER=cat git -C "$MIXXX_MAIN" push --force-with-lease origin integrating
status "DONE integrating pushed — GA CI triggered on origin/integrating"
status " Monitor: gh run list --branch integrating --repo mxmilkiib/mixxx"
status " Promote when green: $0 --promote-integrated"
}
# ── mode: promote_integrated ──────────────────────────────────────────────────
# Polls GA CI on origin/integrating. On success, fast-forwards local+remote
# integrated to match — this is the CI-confirmed-clean gate.
promote_integrated() {
local _jq; _jq=$(command -v jq 2>/dev/null || true)
if [[ -z "$_jq" ]]; then
status "ERROR: jq not found — install jq to use --promote-integrated"
return 1
fi
status "PHASE promote_integrated — polling GA CI on origin/integrating"
local timeout_secs=3600 poll_interval=60
local t0; t0=$(date +%s)
while true; do
local elapsed=$(( $(date +%s) - t0 ))
[[ $elapsed -gt $timeout_secs ]] && {
status "TIMEOUT: CI did not complete within ${timeout_secs}s"; return 1
}
local run_json
run_json=$(gh run list --branch integrating --repo mxmilkiib/mixxx \
--limit 1 --json databaseId,status,conclusion 2>/dev/null) || {
status "ERROR: gh run list failed — check: gh auth status"
return 1
}
local run_id run_status conclusion
run_id=$( echo "$run_json" | "$_jq" -r '.[0].databaseId // "?"')
run_status=$(echo "$run_json" | "$_jq" -r '.[0].status // "unknown"')
conclusion=$( echo "$run_json" | "$_jq" -r '.[0].conclusion // "null"')
if [[ "$run_status" == "completed" ]]; then break; fi
status " run #$run_id: $run_status — next check in ${poll_interval}s (${elapsed}s total)"
sleep "$poll_interval"
done
status " run #$run_id: completed — conclusion=$conclusion"
if [[ "$conclusion" == "success" ]]; then
status " GA CI passed — promoting integrating → integrated"
# git blocks any ref update (including push) for branches checked out in
# any worktree. Use gh api to update origin/integrated directly (no local
# ref touched), then fetch + reset --hard FETCH_HEAD in the integrated
# worktree (FETCH_HEAD is not a branch ref, so no worktree check fires).
local integrated_wt="${HOME}/src/mixxx"
local integrating_sha; integrating_sha=$(GIT_PAGER=cat git -C "$MIXXX_MAIN" rev-parse integrating)
# Capture first — grep -q closes the pipe after first match, which sends
# SIGPIPE to git; with pipefail that makes the condition false every time.
local _wt_list; _wt_list=$(GIT_PAGER=cat git -C "$MIXXX_MAIN" worktree list --porcelain 2>/dev/null)
if echo "$_wt_list" | grep -q "worktree ${integrated_wt}$"; then
local new_sha
new_sha=$(gh api repos/mxmilkiib/mixxx/git/refs/heads/integrated \
--method PATCH --field sha="$integrating_sha" --field force=true \
--jq '.object.sha')
status " origin/integrated → ${new_sha}"
GIT_PAGER=cat git -C "$integrated_wt" fetch origin integrated
GIT_PAGER=cat git -C "$integrated_wt" reset --hard FETCH_HEAD
else
GIT_PAGER=cat git -C "$MIXXX_MAIN" branch -f integrated integrating
GIT_PAGER=cat git -C "$MIXXX_MAIN" push --no-verify --force-with-lease origin integrated
fi
status "DONE integrated pushed — CI-confirmed clean build"
else
status " GA CI FAILED ($conclusion) — NOT promoting integrated"
status " View failures: gh run view $run_id --repo mxmilkiib/mixxx"
return 1
fi
}
# ── 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 full_integration.
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%%:*}"
[[ "$_r" == ok ]] && (( b_ok++ )) || (( b_fail++ ))
[[ $_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=$( (( rb_c )) && printf '%b' "${_C_RED}" || printf '%b' "${_C_GRN}" )
local rc_b=$( (( bi_c )) && printf '%b' "${_C_RED}" || printf '%b' "${_C_GRN}" )
local rc_t=$( (( ts_c )) && printf '%b' "${_C_RED}" || printf '%b' "${_C_GRN}" )
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: full ──────────────────────────────────────────────────────────────────────────────────
# Orchestrates the complete integration pipeline in order:
# 1. rebase_all — fetch upstream, rebase all worktrees
# 2. build_all_tests — configure (parallel) + build (serial) all test binaries
# 3. run_tests_serial — run tests, skip branches with current passing sentinels
# 4. push_integration — push integration branch to origin
# 5. push_integrating — promote to integrating if all gates pass (skipped on build failure)
# 6. print_grand_summary — timing, per-phase results, slowest branch, sys stats
# A build failure is non-fatal: steps 3–4 still run, step 5 is blocked.
# Stopped partway: each phase persists its own state (cmake build dirs, per-branch
# sentinels), so individual modes can be re-run to resume from any point.
full_integration() {
local ts; ts=$(date '+%Y-%m-%d %H:%M')
echo "╔══════════════════════════════════════════════╗"
printf "║ Full integration run — %-21s║\n" "$ts"
echo "╚══════════════════════════════════════════════╝"
echo ""
echo "Real-time status: tail -f $STATUS_FILE"
echo "Test logs: tail -f $TEST_LOG_DIR/<worktree>.log"
echo ""
rebase_all || { echo "ABORT: rebase step had failures."; exit 1; }
echo ""
local _build_ok=true
build_all_tests || _build_ok=false
echo ""
run_tests_serial || { echo "ABORT: tests failed — fix before pushing."; exit 1; }
echo ""
push_integration
echo ""
local _integrating_ok=true
if ! $_build_ok; then
status "push_integrating skipped — build failures present (see above)"
_integrating_ok=false
else
push_integrating || _integrating_ok=false
fi
echo ""
print_grand_summary "$_build_ok" "$_integrating_ok"
}
# ── dispatch ───────────────────────────────────────────────────────────────────
case "${1:-}" in
--rebuild-tests) rebuild_tests_serial ;;
--build-all-tests) build_all_tests ;;
--run-tests) run_tests_serial ;;
--push-changed) push_changed_branches ;;
--push-integration) push_integration ;;
--push-integrating) push_integrating ;;
--promote-integrated) promote_integrated ;;
--full) full_integration ;;
"") rebase_all ;;
*) echo "Usage: $0 [--rebuild-tests | --build-all-tests | --run-tests | --push-changed | --push-integration | --push-integrating | --promote-integrated | --full]"; exit 1 ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment