Skip to content

fix(locallibrary): let sync go ahead after a merge that took in the whole source - #518

Queued
botandbang wants to merge 3 commits into
mainfrom
bnb/loc-235-merge-records-source
Queued

botandbang wants to merge 3 commits into
mainfrom
bnb/loc-235-merge-records-source

Conversation

@botandbang

@botandbang botandbang commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What and why

The docs say that when both drives changed, you merge and then sync (commands.md:307). merge recorded only the target's new state and the source's id (merge.rs:982, commit.rs:416), so the source's fingerprint never entered the target's lineage, has_held stayed false (device.rs:227) and the drives still compared as diverged (compare.rs:401). The next sync refused.

Added to the merge plan:

  • differing_tracks: source tracks the target already has whose details (the fields diff compares), file path, or analysis files (the size of the DAT, EXT and 2EX, since their modification times differ between any two drives exported apart) differ. The target's versions stay, so a later sync copies them over the source's. The CLI lists them under the new-track count.
  • takes_in_source: whether the merge leaves nothing of the source out. That means the whole library is selected (no --only), playlists are merged (not --playlists none), every source track is new or matched (none skipped for a missing file, an unsafe path or details too long), and history lists come along or the target has them already. The CLI says either way what the sync after it will do.

Changed:

  • Planning a merge fingerprints the source as well as scanning it, and the plan keeps that fingerprint.
  • Applying a merge whose plan takes in the source, when the source's database files still have the size and modification time the plan stamped, records the source's state in the target's lineage just before the merged state. It's recorded with a new lineage op, merged_in. The target then counts as having been through the source's state, so compare calls it newer and sync copies it over the source. The record is only written as part of applying a plan that already listed the differing tracks.
  • commit::Options has a held entry for that record.
  • Undoing a change (putting a backup back) drops merged_in records made after that backup was taken, so an undone merge leaves the drives diverged instead of letting a sync overwrite the source with the pre-merge target.
  • The CLI's merge plan lists its new and differing tracks through one helper with compare's lists, so --all shows all of them (it showed 15 before).
  • loclib-model's track_field_changes is public, so the merge plan compares tracks the way diff does.
  • Planning a merge stamps the source's database files (size and modification time) from the scan it already makes, and fingerprints the source only when the merge is applied. track_field_changes formats only the fields that differ, and analysis keys are built in one reused buffer. That keeps the 500-track merge plan within its allocation budget, and it cuts what comparing two 2,000-track libraries allocates by a quarter, so compare.2k's limits come down to 51,034 allocations and 5,589,600 bytes.
  • docs/commands.md: the merge-then-sync workflow passes --history and says which merges leave the drives diverged. The RPC schema and TypeScript types are regenerated for the new plan fields and lineage op.

Added: a changelog line in apps/locallibrary/changes/loc-235-merge-records-source.md.

Fixes LOC-235

How it was tested

merge::diverged::a_merge_that_takes_in_the_source_lets_sync_go_ahead (hegel) syncs drive A onto B, exports a different new track to each, and maybe edits a shared track's comment on A, so the two compare as diverged. For each of five outcomes it then plans a merge of A into B and either stops there, applies it whole, applies it for one playlist, applies it with --playlists none, or applies it and undoes it. A whole merge's plan lists the edited track (and only it) as differing and says it takes in the source. After a whole merge, B compares newer, sync goes ahead, and A ends up with B's library files. In every other case the drives still compare as diverged and sync refuses with diverged.

With the lineage record turned off, the whole merge leaves the drives diverged, which is the bug, and the test fails there. With the undo cleanup turned off, the undone merge still compares newer and the test fails there too. With both in, it passes, along with every merge test and props_merge.

The locallibrary, locallibrary-cli and loclib-model suites pass (4058 tests). cargo fmt --check, workspace clippy, clippy with --no-default-features, and clippy for x86_64-pc-windows-gnu are clean. cargo coverage --workspace on macOS: lines 98.34%, functions 97.70%, regions 97.02% (gates 96.8, 96.2, 95.3). tools/hegel-coverage.sh: lines 97.12%, functions 96.50%, regions 95.66%. Those runs were before the merge plan's lists moved into the shared helper, a change that only removed an unreached line. After it, the tests reach every new line in merge.rs, commit.rs, backup.rs and render.rs.

Checklist

  • Tests cover the change
  • For locallibrary: apps/locallibrary/changes/loc-235-merge-records-source.md has a changelog line
  • The READMEs, docs/ and --help still say what the code does (apps/locallibrary/docs/rpc/ too)

…hole source

A merge recorded only the target's new state, so the two drives still compared as diverged and the sync the docs suggest next was refused. A merge that leaves nothing of the source out now records the source's state in the target's lineage first, and its plan lists the tracks both drives have that differ. Undoing the merge takes that record back out.
@botandbang
botandbang requested a review from bnb as a code owner November 9, 2026 06:09
@linear-code

linear-code Bot commented Nov 9, 2026

Copy link
Copy Markdown

LOC-235

@github-actions github-actions Bot added documentation Improvements or additions to documentation locallibrary locallibrary and the loclib-* crates locallibrary: cli The locallibrary command line locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary: core The core crate, the data model and rekordbox's formats tests Tests, fixtures and test harnesses labels Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Size

Compared with main. KiB are 1,024 bytes.

This pull request main Change
Client JavaScript 806.1 KiB 806.1 KiB 0 B (0%)
Client JavaScript, brotli 272.1 KiB 272.1 KiB 0 B (0%)
Client CSS 99.1 KiB 99.1 KiB 0 B (0%)
Client CSS, brotli 14.2 KiB 14.2 KiB 0 B (0%)

bnb
bnb previously approved these changes Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Rust coverage

From run 614. Both are held to the thresholds in .cargo/config.toml.

Tests Lines Functions Regions
All 98.06% (90319/92103) 97.54% (14733/15105) 96.73% (200280/207058)
Hegel property tests alone 96.81% (89163/92103) 96.29% (14545/15105) 95.35% (197420/207058)

… allocation budget

Planning a merge fingerprinted the source and compared every matched track by
formatting each field and building analysis keys, which took the 500-track merge
plan 25% over its allocation budget. Planning now stamps the source's database
files from the scan it already makes, and the fingerprint is taken only when the
merge is applied. Analysis files are compared by size, since their times differ
between any two drives exported apart, and their keys are built in one reused
buffer. track_field_changes formats only the fields that differ, which also cuts
what comparing two 2,000-track libraries allocates by a quarter, so that budget
is lowered to match.
bnb
bnb previously approved these changes Nov 9, 2026
… costs

Comparing tracks formats only the fields that differ, so checking two 500-track drives allocates 2.1% less: 205,609 on CI against a limit of 210,117, past the 1% tolerance the ratchet allows.
@botandbang
botandbang added this pull request to the merge queue Nov 9, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation locallibrary: cli The locallibrary command line locallibrary: core The core crate, the data model and rekordbox's formats locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary locallibrary and the loclib-* crates tests Tests, fixtures and test harnesses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants