259ca814c682
import cloned saves parent-first so a combined repo can be stolen fresh (3.0.5)
- time
- 2026-09-23 21:21 UTC · 2 weeks ago
- machine
- koh2
- agent
- claude-opus-5-5
- session
- koh 3 review fixes
- parent
- 19c02504e67a
↓ koh-259ca814.tar.gzatlas at this save
files changed4 files
VERSION+1 −1src/sync/steal.v+31 −1src/sync/sync_test.v+27 −0v.mod+1 −1src/sync/steal.v+31 −1
@@ -351,7 +351,7 @@351351 fetched.remote_save_ids[record.id] = true352352 fetched.saves << record353353 }354- fetched.saves.sort(a.created_at < b.created_at)354+ fetched.saves = parent_first(fetched.saves)!355355 for key in event_keys {356356 body := backend.get(key)!357357 if body.found {@@ -359,4 +359,34 @@359359 }360360 }361361 return fetched362+}363+364+// parent_first orders saves so every parent lands before its children, as the365+// foreign key on `parent` demands. Time cannot do it: a replayed save keeps its366+// original, older time but sits on a newer parent.367+fn parent_first(records []SaveRecord) ![]SaveRecord {368+ mut by_id := map[string]SaveRecord{}369+ for record in records {370+ by_id[record.id] = record371+ }372+ mut placed := map[string]bool{}373+ mut ordered := []SaveRecord{}374+ for record in records {375+ mut chain := []SaveRecord{}376+ mut cursor := record.id377+ for cursor != '' && cursor !in placed {378+ if chain.len > records.len {379+ return error('the save records on the remote form a loop')380+ }381+ current := by_id[cursor] or { return error('the remote is missing save ${cursor}') }382+ chain << current383+ cursor = current.parent384+ }385+ chain.reverse_in_place()386+ for current in chain {387+ placed[current.id] = true388+ ordered << current389+ }390+ }391+ return ordered362392 }src/sync/sync_test.v+27 −0
@@ -160,6 +160,33 @@160160 assert backend.get(head_key('proj'))!.body.bytestr() == ahead.id161161 }162162 163+// A replayed save keeps its original time but sits on a newer parent, so a164+// clone cannot order saves by time.165+fn test_a_clone_imports_parents_before_children() {166+ base := scratch('clone_order')!167+ defer {168+ os.rmdir_all(base) or {}169+ }170+ mut backend := remote.new_folder(os.join_path(base, 'remote'))!171+ mut a := open_repo(machine('order_a')!)!172+ defer {173+ a.close()174+ }175+ put(a.root, 'a.txt', 'one\n')!176+ parent := commit(a, 'parent')!177+ child := history.commit_files(a, '', history.files_of(a, parent.id)!, history.SaveInput{178+ message: 'replayed, older than its parent'179+ created_at: parent.created_at - 3600180+ })!181+ offer_now(a, mut backend)!182+ mut b := open_repo(machine('order_b')!)!183+ defer {184+ b.close()185+ }186+ join(b, mut backend)!187+ assert history.head(b)!.id == child.id188+}189+163190 // A chmod is unsaved work too: a steal would restore the old mode over it.164191 fn test_steal_refuses_an_unsaved_executable_bit_change() {165192 base := scratch('chmod')!v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.4'3+ version: '3.0.5'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }