Repository navigation
A re-import finds where the store ends in the file by comparing its ends - #201
Merged
Merged
Conversation
Resuming a grown file meant checking that the file's first stamp equalled the store's first chunk's, seeking to size(), and decompressing every imported chunk to compare it with the file (--quick: the first, middle and last). The gate failed once retention had dropped the head, so the import was refused as predating the store; size() counts from the current head, so the offset was only right before the first drop; and the verification cost the store. The offset is now the tape end, what retention dropped plus what the store holds, and the check is the store's last 16 KiB against the file just before that offset, plus its first 16 KiB while it still has its head. The join is decided by the bytes, so a half-imported last line resumes in the middle of the line and a store whose head is gone resumes like any other. A source that begins where the store begins still says it is the same file, so for that source a mismatch or a shorter file is refused as before; anything else that fails the comparison falls through to the per-source handling, so no store behaves worse than it did. --quick is accepted and ignored. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The first layer of docs/plans/import-resume.md: Step 0, the aligned candidate with a byte verification. No hint file and no search yet.
Problem
Resuming a grown file checked that the file's first stamp equalled the store's first chunk's, seeked to
size(), and decompressed every imported chunk to compare it with the file (--quick: the first, middle and last). Three things were wrong with that:size()counts from the current head, so the offset was only right before the first drop.Change
The resume offset is the tape end (what retention dropped plus what the store holds; new
FileStore::tape_start/tape_end). The check is the store's last 16 KiB against the file just before that offset, plus its first 16 KiB while it still has its head. The join is decided by the bytes, so a half-imported last line resumes in the middle of the line, and a retention-trimmed store resumes like any other.A source that begins where the store begins still says it is the same file, so for that source a mismatch or a shorter file is refused, with the old wording. Anything else that fails the comparison falls through to the per-source handling exactly as before, so no store behaves worse than it did.
--quickis accepted and ignored; the unit files that pass it keep working.Checked against the old binary
A store of 187 chunks, retention left 14, then the source grew by 200 lines:
exit 1:…predates everything in s.log (starts 2026-10-03 13:32:47.000) — import in chronological order, or to a new targetexit 0:imported 200 lines … after 766890 bytes already importedOn a 124 MB store, re-importing 1000 new lines: 0.07 s old, 0.01 s new, with the store byte-identical to the file either way and the same peak RSS (the old check went chunk by chunk). The old cost is proportional to the store and I only measured one size; I make no claim about larger ones. The two strings the VM test asserts (
already up to date,imported 1 lines) are unchanged.Tests
A grown file adds only the growth; a retention-trimmed store resumes; a line imported in part is completed in place, byte for byte; a rewrite that begins the same is refused; a truncated file is refused; a different file after the store is appended and not mistaken for a regrowth; a file whose start changed with the join intact is refused. Each of head, tail and retention is covered by its own test: removing the head comparison, the tail comparison, or the dropped-bytes term fails exactly that test. (My first versions of the head and tail tests did not discriminate, because the store was no larger than the window; I only found that by running the mutations.)
Not in this layer
The hint file, the search for a store with no hint, handling a file with no stamp, and the fallback table for an ambiguous resume. For a source that claims to be the same file, a mismatch is still refused: replacing that with the plan's fallback is a later layer, and I did not want to swap a refusal for an unbounded overlap dedup here.
🤖 Generated with Claude Code