8aa3154c04f0
make status and steal see executable-bit changes the way save does (3.0.1)
- time
- 2026-09-23 20:54 UTC · 2 weeks ago
- machine
- koh2
- agent
- claude-opus-5-5
- session
- koh 3 review fixes
- parent
- b09a48226a5b
↓ koh-8aa3154c.tar.gzatlas at this save
files changed6 files
VERSION+1 −1src/cli/cmd_status.v+2 −1src/sync/steal.v+2 −13src/sync/sync_test.v+22 −0tests/cli_test.v+14 −0v.mod+1 −1src/cli/cmd_status.v+2 −1
@@ -83,7 +83,8 @@8383 for path in after.keys() {8484 if path !in before {8585 added << path86- } else if before[path].hash != after[path].hash {86+ } else if before[path].hash != after[path].hash87+ || before[path].executable != after[path].executable {8788 modified << path8889 }8990 }src/sync/steal.v+2 −13
@@ -304,19 +304,8 @@304304 // dirty reports whether the working tree differs from `head` in a way a steal305305 // would overwrite.306306 fn dirty(store &storage.Store, head history.Save) !bool {307- before := snapshot.index_files(history.files_of(store, head.id)!)308- after := snapshot.index_files(snapshot.walk(store, store.root, snapshot.load(store.root))!)309- for path in after.keys() {310- if path !in before || before[path].hash != after[path].hash {311- return true312- }313- }314- for path in before.keys() {315- if path !in after {316- return true317- }318- }319- return false307+ return !snapshot.same_tree(history.files_of(store, head.id)!, snapshot.walk(store, store.root,308+ snapshot.load(store.root))!)320309 }321310 322311 // fetch_repo pulls a whole repo — head, saves, objects and events — forsrc/sync/sync_test.v+22 −0
@@ -135,6 +135,28 @@135135 assert history.head(b)!.id == fetched.head136136 }137137 138+// A chmod is unsaved work too: a steal would restore the old mode over it.139+fn test_steal_refuses_an_unsaved_executable_bit_change() {140+ base := scratch('chmod')!141+ defer {142+ os.rmdir_all(base) or {}143+ }144+ mut backend := remote.new_folder(os.join_path(base, 'remote'))!145+ mut a := open_repo(machine('chmod_a')!)!146+ defer {147+ a.close()148+ }149+ put(a.root, 'run.sh', 'echo hi\n')!150+ commit(a, 'script')!151+ offer_now(a, mut backend)!152+ os.chmod(os.join_path(a.root, 'run.sh'), 0o755)!153+ if _ := steal_now(a, mut backend) {154+ assert false, 'a chmod-only change should make the tree dirty'155+ } else {156+ assert err.msg() == dirty_tree157+ }158+}159+138160 fn test_divergence_without_clashes_keeps_both_machines_work() {139161 base := scratch('diverge')!140162 defer {tests/cli_test.v+14 −0
@@ -155,6 +155,20 @@155155 assert !run(dir, 'status').out.contains('LOG.md')156156 }157157 158+// `save` records the executable bit, so `status` has to see it too, or a159+// chmod would be invisible until it turned up in a save.160+fn test_status_sees_an_executable_bit_change() {161+ dir := temp_repo('chmod')162+ defer { os.rmdir_all(dir) or {} }163+ init_repo(dir)164+ write(dir, 'run.sh', 'echo hi\n')165+ assert run(dir, 'save', 'script').code == 0166+ os.chmod(os.join_path(dir, 'run.sh'), 0o755) or { panic(err) }167+ status := run(dir, 'status', '--json')168+ assert status.out.contains('"dirty":true')169+ assert status.out.contains('"modified":["run.sh"]')170+}171+158172 fn test_log_and_diff() {159173 dir := temp_repo('history')160174 defer { os.rmdir_all(dir) or {} }v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.0'3+ version: '3.0.1'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }