module history import os import src.snapshot import src.storage fn temp_repo(name string) !(storage.Store, string) { dir := os.join_path(os.temp_dir(), 'koh3_hist_${os.getpid()}_${name}') os.rmdir_all(dir) or {} os.mkdir_all(dir, os.MkdirParams{})! store := storage.create(dir)! return store, dir } fn write_file(root string, rel string, text string) ! { full := os.join_path(root, rel) os.mkdir_all(os.dir(full), os.MkdirParams{})! os.write_file(full, text)! } fn test_walk_then_commit() { mut store, dir := temp_repo('commit')! defer { store.close() os.rmdir_all(dir) or {} } write_file(dir, 'a.txt', 'hello\n')! write_file(dir, 'sub/b.txt', 'world\n')! entries := snapshot.walk(store, dir, snapshot.Ignore{})! assert entries.len == 2 save := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'first save' })! assert save.id.len == 12 assert save.parent == '' assert head_id(store) == save.id files := files_of(store, save.id)! assert files.len == 2 assert files[0].path == 'a.txt' assert files[1].path == 'sub/b.txt' // Nothing changed, so a plain save refuses and a checkpoint records. if _ := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'again' }) { assert false, 'a save with no changes should refuse' } else { assert err.msg() == nothing_changed } second := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'checked anyway' is_checkpoint: true })! assert second.parent == save.id assert second.is_checkpoint } fn test_going_back_is_recorded_not_rewound() { mut store, dir := temp_repo('back')! defer { store.close() os.rmdir_all(dir) or {} } write_file(dir, 'a.txt', 'one\n')! first := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'one' })! write_file(dir, 'a.txt', 'two\n')! second := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'two' })! // effective position follows restores, so undo keeps stepping back. assert effective(store)!.id == second.id first_files := files_of(store, first.id)! restore := commit_files(store, '', first_files, SaveInput{ message: 'undo: ${first.id}' restores: first.id })! assert restore.parent == second.id assert restore.restores == first.id assert effective(store)!.id == first.id // Walking back from the first save has nowhere to go; undo says so rather // than looping. if _ := step_back(store, effective(store)!.id, 1) { assert false, 'there is nothing before the first save' } else { assert err.msg().contains('nothing before') } // The tree at head is the restored one. assert same_files(store, head_id(store), first_files)! } fn same_files(store &storage.Store, id string, expected []snapshot.FileEntry) !bool { actual := files_of(store, id)! if actual.len != expected.len { return false } for i in 0 .. actual.len { if actual[i].path != expected[i].path || actual[i].hash != expected[i].hash { return false } } return true } fn test_resolve_accepts_a_unique_prefix() { mut store, dir := temp_repo('resolve')! defer { store.close() os.rmdir_all(dir) or {} } write_file(dir, 'a.txt', 'one\n')! first := commit_worktree(store, dir, snapshot.Ignore{}, SaveInput{ message: 'one' })! assert resolve(store, first.id[..6])!.id == first.id if _ := resolve(store, 'abc') { assert false, 'a prefix shorter than six characters should be refused' } else { assert err.msg().contains('at least 6') } if _ := resolve(store, 'zzzzzz') { assert false, 'an unknown prefix should be refused' } else { assert err.msg().contains('no save starts with') } }