eee40896e5eb

never snapshot a nested repo's .koh or .git, so the outer repo cannot roll an inner repo's history back (3.0.33)

time
2026-09-24 03:47 UTC · 2 weeks ago
machine
koh2
agent
claude-opus-5-5
session
koh 3 reliability campaign
parent
ff9e6b8d886f
4 files VERSION+1 −1src/snapshot/ignore.v+6 −11src/snapshot/snapshot_test.v+9 −0v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.321+3.0.33

view file

src/snapshot/ignore.v+6 −11
@@ -98,26 +98,21 @@9898 }9999 100100 // hard_ignored covers the files Koh writes into a repo as part of working101-// there. They are never snapshotted whatever `.kohignore` says.101+// there. They are never snapshotted whatever `.kohignore` says, at any depth:102+// a repo inside the repo keeps its own history, and restoring an old copy of103+// its `.koh` or `.git` would roll that history back.102104 //103105 // LOG.md is rewritten after every save from the history itself. Tracking it104106 // would make the tree dirty the moment a save lands, so `status` could never105107 // report a clean tree and every diff would carry the log's own churn.106108 pub fn hard_ignored(rel string) bool {107-	if rel == '.koh' || rel == '.koh-wal' || rel == '.koh-shm' {108-		return true109-	}110-	if rel == '.koh-v2' || rel.starts_with('.koh-v2/') {111-		return true112-	}113-	if rel == '.git' || rel.starts_with('.git/') {114-		return true115-	}116109 	if rel == 'LOG.md' {117110 		return true118111 	}119-	return false112+	return rel.split('/').any(it in hard_ignored_names)120113 }114+115+const hard_ignored_names = ['.koh', '.koh-wal', '.koh-shm', '.koh-v2', '.git']121116 122117 // matches reports whether a repo-relative path is ignored. `is_dir` matters123118 // because a pattern ending in `/` names a directory: it covers the directory

view file

src/snapshot/snapshot_test.v+9 −0
@@ -50,6 +50,15 @@5050 	}5151 	assert !hard_ignored('.kohignore')5252 	assert !hard_ignored('src/log.md')53+	// A repo inside the repo keeps its own history: restoring an old copy of54+	// its .koh or .git would roll that history back.55+	for path in ['inner/.koh', 'inner/.koh-wal', 'inner/.koh-shm', 'a/b/.git', 'vendored/.git/HEAD',56+		'old/.koh-v2/snapshots/x.json', 'koh2repo/.koh/objects/ab/cd'] {57+		assert hard_ignored(path), '${path} should be hard ignored'58+	}59+	assert !hard_ignored('inner/.kohignore')60+	assert !hard_ignored('docs/.github/workflow.yml')61+	assert !hard_ignored('notes/.koh.md')5362 	// A pattern list cannot un-ignore them, and `.kohignore` itself is content.5463 	ig := parse('')5564 	assert ig.matches('.koh', false)

view file

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

view file