2752ddfbc874

name the checkpoint that holds what revert discarded, recording both in one transaction (3.0.49)

time
2026-09-24 19:30 UTC · 2 weeks ago
machine
koh2
agent
claude-opus-5-5
session
koh 3 reliability campaign
parent
fcd909830822
4 files VERSION+1 −1src/cli/cmd_back.v+39 −14tests/cli_test.v+8 −1v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.481+3.0.49

view file

src/cli/cmd_back.v+39 −14
@@ -136,17 +136,23 @@136136 	}137137 	report := snapshot.restore_files(store, store.root, snapshot.load(store.root), plan.files,138138 		plan.remove) or { return refuse(as_json, err.msg()) }139+	// A revert's restore save sits right on the checkpoint of what it threw away.140+	kept := if discard_first { save.parent } else { '' }139141 	if as_json {140142 		emit_json(json.encode(BackJson{141-			id:        save.id142-			restores:  target.id143-			written:   report.written144-			removed:   report.removed145-			discarded: discard_first143+			id:         save.id144+			restores:   target.id145+			written:    report.written146+			removed:    report.removed147+			discarded:  discard_first148+			checkpoint: kept146149 		}))147150 		return 0148151 	}149152 	ok('restored [${save.id}]  ${save.message}')153+	if kept != '' {154+		note('the discarded changes are kept in [${kept}] — koh load ${kept} brings them back')155+	}150156 	return 0151157 }152158 @@ -169,16 +175,34 @@169175 }170176 171177 fn record_restore(store &storage.Store, target history.Save, verb string, plan RestorePlan, discard_first bool) !history.Save {178+	discarded := if discard_first {179+		snapshot.walk(store, store.root, snapshot.load(store.root))!180+	} else {181+		[]snapshot.FileEntry{}182+	}183+	// One transaction, as the document asks of every history change; it also184+	// keeps the discard checkpoint the restore save's parent, which is how185+	// revert names it.186+	store.begin()!187+	save := record_both(store, target, verb, plan, discard_first, discarded) or {188+		store.rollback()189+		return err190+	}191+	store.commit()!192+	history.refresh_log_md(store)193+	return save194+}195+196+fn record_both(store &storage.Store, target history.Save, verb string, plan RestorePlan, discard_first bool, discarded []snapshot.FileEntry) !history.Save {172197 	if discard_first {173198 		// Recorded even while a restore is unfinished: finishing it is what174-		// revert is for. The mark keeps every other writer off head meanwhile.175-		history.commit_files(store, store.root, snapshot.walk(store, store.root,176-			snapshot.load(store.root))!, history.SaveInput{199+		// revert is for.200+		history.record_save(store, store.root, discarded, history.SaveInput{177201 			message:       'revert: discarded changes'178202 			is_checkpoint: true179203 		})!180204 	}181-	return history.commit_files(store, '', plan.files, history.SaveInput{205+	return history.record_save(store, '', plan.files, history.SaveInput{182206 		message:  '${verb}: ${target.id}'183207 		restores: target.id184208 	})!@@ -250,11 +274,12 @@250274 }251275 252276 struct BackJson {253-	id        string254-	restores  string255-	written   int256-	removed   int257-	discarded bool277+	id         string278+	restores   string279+	written    int280+	removed    int281+	discarded  bool282+	checkpoint string // revert only: the checkpoint holding what was discarded258283 }259284 260285 struct RevertJson {

view file

tests/cli_test.v+8 −1
@@ -476,10 +476,17 @@476476 	assert run(dir, 'log').out == before.out477477 	assert os.read_file(os.join_path(dir, 'a.txt'))! == 'dirty\n'478478 	write(dir, 'untracked.txt', 'remove\n')479-	rv := run(dir, 'revert', '--yes')479+	rv := run(dir, 'revert', '--yes', '--json')480480 	assert rv.code == 0481481 	assert os.read_file(os.join_path(dir, 'a.txt'))! == 'two\n'482482 	assert !os.exists(os.join_path(dir, 'untracked.txt'))483+	// The way back to the discarded work is named, not left to be found.484+	kept := rv.out.all_after('"checkpoint":"')[..12]485+	assert run(dir, 'load', kept, '--yes').code == 0486+	assert os.read_file(os.join_path(dir, 'a.txt'))! == 'dirty\n'487+	assert os.read_file(os.join_path(dir, 'untracked.txt'))! == 'remove\n'488+	write(dir, 'a.txt', 'dirty again\n')489+	assert run(dir, 'revert', '--yes').out.contains('koh load ')483490 	assert run(dir, 'revert', '--yes').out.contains('working tree already matches')484491 	assert run(dir, 'load', '--checkpoint', '--yes').code == 0485492 	assert run(dir, 'load', id2, '--json').code == 1

view file

v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 	name: 'koh3'3-	version: '3.0.48'3+	version: '3.0.49'44 	description: 'One SQLite file per repo, one line of history.'55 	license: 'MIT'66 }

view file