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
4 files VERSION+1 −1src/sync/steal.v+16 −2src/sync/sync_test.v+25 −0v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.31+3.0.4

view file

src/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)!

view file

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')!

view file

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 }

view file