19c02504e67a
leave the line alone when steal finds this machine only ahead of the remote (3.0.4)
- time
- 2026-09-23 21:19 UTC · 2 weeks ago
- machine
- koh2
- agent
- claude-opus-5-5
- session
- koh 3 review fixes
- parent
- 00a1b4528f25
↓ koh-19c02504.tar.gzatlas at this save
files changed4 files
VERSION+1 −1src/sync/steal.v+16 −2src/sync/sync_test.v+25 −0v.mod+1 −1src/sync/steal.v+16 −2
@@ -77,7 +77,12 @@7777 }7878 mut local_only := []history.Save{}7979 if head := head_before {80- local_only = saves_after(store, head.id, fetched.base) or { []history.Save{} }80+ // The remote builds on `base`. If that save is here but no longer on81+ // the line, neither line contains the other; jumping to the remote82+ // would silently leave this line behind.83+ local_only = saves_after(store, head.id, fetched.base) or {84+ return error('the remote builds on save ${fetched.base}, which is not on this line — load the save you want, then offer')85+ }8186 }8287 if fetched.base == '' && local_only.len > 0 {8388 return error('the remote keeps a different history — it shares no save with this repo')@@ -89,8 +94,13 @@8994 return err9095 })9196 store.commit() or { return err }97+ report.head = final.id9298 9399 if head := head_before {100+ if final.id == head.id {101+ // The line did not move, so neither does the working tree.102+ return report103+ }94104 files := history.files_of(store, final.id)!95105 mut wanted := map[string]bool{}96106 for file in files {@@ -109,7 +119,6 @@109119 files := history.files_of(store, final.id)!110120 snapshot.restore_files(store, store.root, snapshot.load(store.root), files, [])!111121 }112- report.head = final.id113122 history.write_log_md(store)!114123 return report115124 }@@ -119,6 +128,11 @@119128 // transaction and returns the new head.120129 fn write_and_combine(store &storage.Store, fetched Fetched, local_only []history.Save, opts StealOptions, mut report StealReport) !history.Save {121130 write_fetched(store, fetched)!131+ if fetched.saves.len == 0 {132+ // The remote head is already on this line: level, or this machine is133+ // ahead and the offer that follows a steal sends the rest.134+ return history.head(store) or { return error('this repo has no head') }135+ }122136 if local_only.len == 0 {123137 // Nothing of ours is missing from the remote: the line simply moves on.124138 history.set_head(store, fetched.head)!src/sync/sync_test.v+25 −0
@@ -135,6 +135,31 @@135135 assert history.head(b)!.id == fetched.head136136 }137137 138+// A machine that is only ahead has nothing to combine: stealing must leave its139+// saves, and their IDs, exactly where they are.140+fn test_steal_while_only_ahead_leaves_the_line_alone() {141+ base := scratch('ahead')!142+ defer {143+ os.rmdir_all(base) or {}144+ }145+ mut backend := remote.new_folder(os.join_path(base, 'remote'))!146+ mut a := open_repo(machine('ahead_a')!)!147+ defer {148+ a.close()149+ }150+ put(a.root, 'a.txt', 'one\n')!151+ commit(a, 'offered')!152+ offer_now(a, mut backend)!153+ put(a.root, 'a.txt', 'two\n')!154+ ahead := commit(a, 'not offered yet')!155+ report := steal_now(a, mut backend)!156+ assert report.replayed == 0157+ assert history.head(a)!.id == ahead.id158+ assert history.all_saves(a)!.len == 2159+ offer_now(a, mut backend)!160+ assert backend.get(head_key('proj'))!.body.bytestr() == ahead.id161+}162+138163 // A chmod is unsaved work too: a steal would restore the old mode over it.139164 fn test_steal_refuses_an_unsaved_executable_bit_change() {140165 base := scratch('chmod')!v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.3'3+ version: '3.0.4'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }