1ca868c479bf
apply every strip held here, not only those in the fetch, to saves arriving later and to the clash preview (3.1.1)
- time
- 2026-09-28 12:13 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- strips reach every arriving save
- parent
- c9861f20776d
↓ koh-1ca868c4.tar.gzatlas at this save
files changed5 files
VERSION+1 −1src/exchange/exchange.v+13 −4src/exchange/exchange_test.v+32 −0src/exchange/steal.v+2 −2v.mod+1 −1src/exchange/exchange.v+13 −4
@@ -278,20 +278,21 @@278278 for hash, blob in fetched.repo_objects {279279 store.put_object_blob(hash, blob)!280280 }281- mut strips := []events.Event{}281+ mut stripped := false282282 for event in fetched.events_out {283283 if !events.receive(store, event)! {284284 continue285285 }286286 if event.kind == events.kind_strip {287287 remove_path(store, event.path, event.save_id)!288- strips << event288+ stripped = true289289 }290290 }291+ strips := known_strips(store, fetched)!291292 for record in fetched.saves {292293 import_arriving(store, record, strips)!293294 }294- if strips.len > 0 {295+ if stripped {295296 store.drop_unreferenced_objects()!296297 }297298 }@@ -302,10 +303,18 @@302303 for hash, blob in fetched.repo_objects {303304 store.put_object_blob(hash, blob)!304305 }305- strips := fetched.events_out.filter(it.kind == events.kind_strip)306+ strips := known_strips(store, fetched)!306307 for record in fetched.saves {307308 import_arriving(store, record, strips)!308309 }310+}311+312+// known_strips is every strip held here and every one the fetch brings: an313+// arriving save loses what any of them reaches, not only what arrived with it.314+fn known_strips(store &storage.Store, fetched Fetched) ![]events.Event {315+ mut strips := events.strip_events(store)!316+ strips << fetched.events_out.filter(it.kind == events.kind_strip)317+ return strips309318 }310319 311320 // import_arriving lands one save from the remote, unless it is already here, less every path asrc/exchange/exchange_test.v+32 −0
@@ -954,6 +954,38 @@954954 assert !os.exists(os.join_path(c.root, 'secrets.env'))955955 }956956 957+// A strip this machine already holds bans its path from saves that arrive958+// later too, not only from those a fetch brings with the strip.959+fn test_a_held_strip_reaches_saves_arriving_after_it() {960+ base := scratch('strip_held')!961+ defer {962+ os.rmdir_all(base) or {}963+ }964+ mut backend := remote.new_folder(os.join_path(base, 'remote'))!965+ mut a := open_repo(machine('strip_held_a')!)!966+ defer {967+ a.close()968+ }969+ mut b := open_repo(machine('strip_held_b')!)!970+ defer {971+ b.close()972+ }973+ put(a.root, 'secrets.env', 'api_key=1\n')!974+ make_save(a, 'leak the key')!975+ offer_now(a, mut backend)!976+ join(b, mut backend)!977+ strip_local(b, 'secrets.env', '')!978+ put(a.root, 'keep.txt', 'fine\n')!979+ later := make_save(a, 'add keep')!980+ offer_now(a, mut backend)!981+ b.begin()!982+ write_fetched(b, fetch_back_to_known(b, mut backend, 'proj')!)!983+ b.commit()!984+ paths := history.files_of(b, later.id)!.map(it.path)985+ assert 'secrets.env' !in paths986+ assert 'keep.txt' in paths987+}988+957989 // The local half commits before the remote hears anything, so a remote failure958990 // leaves nothing to find locally; the pending note is what lets a re-run959991 // finish. Objects a remote-only save still needs must survive the strip.src/exchange/steal.v+2 −2
@@ -393,10 +393,10 @@393393 []snapshot.FileEntry{}394394 }395395 head := history.head(store) or { return []Clash{} }396- // The transaction applies the incoming strips before it replays, so the396+ // The transaction applies every known strip before it replays, so the397397 // clashes are found on the trees as they will be then, or the answers398398 // would be about files that are no longer there.399- strips := fetched.events_out.filter(it.kind == events.kind_strip)399+ strips := known_strips(store, fetched)!400400 remote_head := fetched.saves.last()401401 mut clashes := find_clashes(after_strips(base_files, fetched.base, strips), after_strips(history.files_of(store,402402 head.id)!, head.id, strips), after_strips(to_files(remote_head), remote_head.id, strips))v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.1.0'3+ version: '3.1.1'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }