| bed495a9 Andy McCall ↶ |
1 |
// The process wrapper against the real oxy binary on a scratch repository. |
| 2 |
// Runs with `npm test`; needs oxy on PATH, at ~/.local/bin/oxy, or in OXY_BIN. |
| 3 |
|
| 4 |
import { test } from "node:test"; |
| 5 |
import assert from "node:assert/strict"; |
| 6 |
import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs"; |
| 7 |
import { tmpdir } from "node:os"; |
| 8 |
import { join } from "node:path"; |
| 9 |
import { Oxy, findBinary } from "../out/oxy.mjs"; |
| 10 |
|
| 11 |
const binary = process.env.OXY_BIN ?? findBinary(undefined); |
| 12 |
const oxy = new Oxy(binary); |
| 13 |
|
| 14 |
test("root, status, cat, log and commit round trip", async () => { |
| 15 |
const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-")); |
| 16 |
const home = join(dir, "home"); |
| 17 |
mkdirSync(home); |
| 18 |
process.env.OXY_HOME = home; |
| 19 |
await oxy.check(dir, ["init", "repo"]); |
| 20 |
const root = join(dir, "repo"); |
| 21 |
await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]); |
| 22 |
|
| 23 |
const version = await oxy.version(); |
| 24 |
assert.equal(version.protocol, 1); |
| 25 |
assert.equal(await oxy.root(dir), null, "outside a repository"); |
| 26 |
const info = await oxy.root(root); |
| 27 |
assert.equal(info?.bookmark, "main"); |
| 28 |
assert.ok(info?.root.endsWith("repo")); |
| 29 |
|
| 30 |
writeFileSync(join(root, "a.txt"), "one\n"); |
| 31 |
let status = await oxy.status(root); |
| 32 |
assert.deepEqual(status.paths, [{ path: "a.txt", status: "Added" }]); |
| 33 |
assert.equal(status.clean, false); |
| 34 |
await oxy.commit(root, "first"); |
| 35 |
status = await oxy.status(root); |
| 36 |
assert.equal(status.clean, true); |
| 37 |
|
| 38 |
writeFileSync(join(root, "a.txt"), "one\ntwo\n"); |
| 39 |
assert.equal((await oxy.cat(root, "a.txt")).toString(), "one\n", "the base version"); |
| 40 |
const log = await oxy.log(root, 10); |
| 41 |
assert.equal(log.changes.filter((c) => !c.working_copy).length, 1); |
| 42 |
assert.equal(log.changes.find((c) => !c.working_copy)?.signature?.status, "unsigned"); |
| 43 |
assert.ok((await oxy.prompt(root)).includes("main")); |
| 44 |
const bookmarks = await oxy.bookmarks(root); |
| 45 |
assert.deepEqual(bookmarks.map((b) => [b.name, b.current]), [["main", true]]); |
| 46 |
await assert.rejects(oxy.cat(root, "missing.txt"), /not in that state/); |
| 35d56b58 Andy McCall ↶ |
47 |
|
| 5779e5c7 Andy McCall ↶ |
48 |
// Amend folds the edit into the single head. |
| 49 |
await oxy.amend(root, "first, amended"); |
| 50 |
assert.equal((await oxy.status(root)).clean, true); |
| 51 |
assert.ok((await oxy.log(root, 10)).changes.some((c) => c.message === "first, amended")); |
| 52 |
|
| 53 |
// Hunks: commit only b.txt's, leave a.txt's edit, then discard it. |
| 54 |
writeFileSync(join(root, "a.txt"), "one\ntwo\nthree\n"); |
| 35d56b58 Andy McCall ↶ |
55 |
writeFileSync(join(root, "b.txt"), "b\n"); |
| 56 |
const hunks = await oxy.hunks(root); |
| 57 |
assert.ok(hunks.length >= 2, JSON.stringify(hunks)); |
| 58 |
const bHunk = hunks.find((h) => h.path === "b.txt"); |
| 59 |
await oxy.commitHunks(root, "b only", [bHunk.index]); |
| 60 |
status = await oxy.status(root); |
| 61 |
assert.deepEqual(status.paths.map((p) => p.path), ["a.txt"], "a.txt stays uncommitted"); |
| 5779e5c7 Andy McCall ↶ |
62 |
assert.equal((await oxy.cat(root, "b.txt")).toString(), "b\n", "committed with its content"); |
| 35d56b58 Andy McCall ↶ |
63 |
await oxy.discard(root, ["a.txt"]); |
| 64 |
assert.equal((await oxy.status(root)).clean, true); |
| 5779e5c7 Andy McCall ↶ |
65 |
|
| 66 |
// The operation log, and undo of the last operation (the commit). |
| 35d56b58 Andy McCall ↶ |
67 |
const ops = await oxy.opLog(root); |
| 68 |
assert.ok(ops[0].current && ops.length > 3); |
| 69 |
assert.match(await oxy.undo(root), /Undone/); |
| 5779e5c7 Andy McCall ↶ |
70 |
status = await oxy.status(root); |
| 71 |
assert.ok(status.paths.some((p) => p.path === "b.txt"), "the commit is undone; b.txt is uncommitted work again"); |
| bed495a9 Andy McCall ↶ |
72 |
}); |
| c8f95589 Andy McCall ↶ |
73 |
|
| 74 |
test("bookmarks, remotes and sync against a local origin", async () => { |
| 75 |
const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-sync-")); |
| 76 |
const home = join(dir, "home"); |
| 77 |
mkdirSync(home); |
| 78 |
process.env.OXY_HOME = home; |
| 79 |
await oxy.check(dir, ["init", "origin"]); |
| 80 |
await oxy.clone(dir, join(dir, "origin"), join(dir, "work")); |
| 81 |
const root = join(dir, "work"); |
| 82 |
writeFileSync(join(root, "a.txt"), "a\n"); |
| 83 |
await oxy.commit(root, "a"); |
| 84 |
const remotes = await oxy.remotes(root); |
| 85 |
assert.equal(remotes[0].name, "origin"); |
| 86 |
let push = await oxy.push(root); |
| 87 |
assert.equal(push[0].ok, true); |
| 88 |
let bookmarks = await oxy.bookmarks(root); |
| 89 |
assert.deepEqual(bookmarks.map((b) => [b.name, b.current, b.ahead, b.behind]), [["main", true, 0, 0]]); |
| 90 |
|
| 91 |
await oxy.bookmarkCreate(root, "feature"); |
| 92 |
await oxy.bookmarkSwitch(root, "feature"); |
| 93 |
writeFileSync(join(root, "a.txt"), "a\nfeature\n"); |
| 94 |
await oxy.commit(root, "feature work"); |
| 95 |
bookmarks = await oxy.bookmarks(root); |
| 96 |
assert.equal(bookmarks.find((b) => b.current)?.name, "feature"); |
| 97 |
await oxy.bookmarkSwitch(root, "main"); |
| 98 |
assert.equal((await oxy.cat(root, "a.txt")).toString(), "a\n"); |
| 99 |
writeFileSync(join(root, "b.txt"), "b\n"); |
| 100 |
await assert.rejects(oxy.bookmarkSwitch(root, "feature"), /has changes/); |
| 101 |
await oxy.commit(root, "b on main"); |
| 102 |
assert.match(await oxy.merge(root, "feature"), /Merged/); |
| 103 |
assert.equal((await oxy.cat(root, "a.txt")).toString(), "a\nfeature\n"); |
| 104 |
push = await oxy.push(root, "origin", "feature"); |
| 105 |
assert.equal(push[0].ok, true); |
| 106 |
push = await oxy.push(root); |
| 107 |
assert.equal(push[0].ok, true); |
| 108 |
const fetch = await oxy.fetch(root); |
| 109 |
assert.equal(fetch.remote, "origin"); |
| 110 |
const pull = await oxy.pull(root); |
| 111 |
assert.equal(pull.changed, false); |
| 112 |
await oxy.bookmarkDelete(root, "feature"); |
| 113 |
bookmarks = await oxy.bookmarks(root); |
| 114 |
assert.deepEqual(bookmarks.map((b) => b.name), ["main"]); |
| ab81294a Andy McCall ↶ |
115 |
|
| 116 |
// Blame, show, diff text, and file history. |
| 117 |
const blame = await oxy.blame(root, "a.txt"); |
| 118 |
assert.deepEqual(blame.map((l) => l.line), [1, 2]); |
| 119 |
assert.notEqual(blame[0].hash, blame[1].hash, "two changes introduced the two lines"); |
| 120 |
const show = await oxy.show(root, `#${blame[1].hash.slice(0, 12)}`); |
| 121 |
assert.equal(show.message, "feature work"); |
| 122 |
assert.deepEqual(show.files.map((f) => f.path), ["a.txt"]); |
| 123 |
assert.match(await oxy.diffText(root, `#${blame[1].hash.slice(0, 12)}`), /\+feature/); |
| 124 |
const history = await oxy.log(root, 50, "a.txt"); |
| 125 |
assert.equal(history.changes.filter((c) => !c.working_copy).length, 2); |
| d2c9a584 Andy McCall ↶ |
126 |
|
| 127 |
// Tags, workspaces, trust, bisect status, subrepos. |
| 128 |
await oxy.tagCreate(root, "v1", [], "first"); |
| 129 |
const tags = await oxy.tags(root); |
| 130 |
assert.deepEqual(tags.map((t) => [t.name, t.message]), [["v1", "first"]]); |
| 131 |
const workspaces = await oxy.workspaces(root); |
| 132 |
assert.equal(workspaces.length, 1); |
| 133 |
assert.equal(workspaces[0].current, true); |
| 134 |
assert.equal(await oxy.policyShow(root), null, "no policy yet"); |
| 135 |
const verify = await oxy.verify(root); |
| 136 |
assert.ok(verify.every((v) => v.status === "unsigned")); |
| 137 |
assert.equal(await oxy.bisectStatus(root), null); |
| 138 |
assert.deepEqual(await oxy.submodules(root), []); |
| 139 |
await oxy.bisectStart(root); |
| 140 |
assert.equal((await oxy.bisectStatus(root))?.state, "need-marks"); |
| 141 |
await oxy.bisectReset(root); |
| c8f95589 Andy McCall ↶ |
142 |
}); |
| 143 |
|
| b4626290 Andy McCall ↶ |
144 |
|
| 145 |
test("split, squash and rebase through the history actions, each undoable", async () => { |
| 146 |
const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-hist-")); |
| 147 |
const home = join(dir, "home"); |
| 148 |
mkdirSync(home); |
| 149 |
process.env.OXY_HOME = home; |
| 150 |
await oxy.check(dir, ["init", "repo"]); |
| 151 |
const root = join(dir, "repo"); |
| 152 |
await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]); |
| 153 |
writeFileSync(join(root, "a.txt"), "a\n"); |
| 154 |
await oxy.commit(root, "one"); |
| 155 |
writeFileSync(join(root, "b.txt"), "b\n"); |
| 156 |
writeFileSync(join(root, "c.txt"), "c\n"); |
| 157 |
await oxy.commit(root, "two files"); |
| 158 |
const find = async (m) => (await oxy.log(root, 50)).changes.find((c) => !c.working_copy && c.message.startsWith(m)); |
| 159 |
|
| 160 |
// Split: b.txt becomes its own change, c.txt stays. |
| 161 |
const two = await find("two files"); |
| 162 |
const out = await oxy.split(root, `#${two.hash.slice(0, 12)}`, ["b.txt"]); |
| 163 |
assert.match(out, /Split into/); |
| 164 |
const part = await find("two files (part 1"); |
| 165 |
assert.ok(part, "the first part exists"); |
| 166 |
assert.deepEqual((await oxy.show(root, `#${part.hash.slice(0, 12)}`)).files.map((f) => f.path), ["b.txt"]); |
| 167 |
await oxy.undo(root); |
| 168 |
assert.equal(await find("two files (part 1"), undefined, "undo reverses the split"); |
| 169 |
|
| 170 |
// Squash: an edit of a.txt folds into "one", which keeps its ChangeId. |
| 171 |
writeFileSync(join(root, "a.txt"), "a\na2\n"); |
| 172 |
await oxy.commit(root, "edit a"); |
| 173 |
const one = await find("one"); |
| 174 |
const edit = await find("edit a"); |
| 175 |
assert.deepEqual(edit.deps, [one.hash]); |
| 176 |
assert.match(await oxy.squash(root, `#${edit.hash.slice(0, 12)}`, `#${one.hash.slice(0, 12)}`), /Squashed into/); |
| 177 |
const after = await find("one"); |
| 178 |
assert.equal(after.change_id, one.change_id); |
| 179 |
assert.equal(await find("edit a"), undefined); |
| 180 |
|
| 181 |
// Rebase onto a state that already has the change is refused, with oxy's reason. |
| 182 |
await assert.rejects(oxy.rebase(root, `#${after.hash.slice(0, 12)}`, ["main"]), /already part of the target state/); |
| 183 |
}); |
| 44bfd5ad Andy McCall ↶ |
184 |
|
| 185 |
test("identity reports how commits are signed", async () => { |
| 186 |
const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-id-")); |
| 187 |
const home = join(dir, "home"); |
| 188 |
mkdirSync(home); |
| 189 |
process.env.OXY_HOME = home; |
| 190 |
await oxy.check(dir, ["init", "repo"]); |
| 191 |
const root = join(dir, "repo"); |
| 192 |
assert.equal(await oxy.identity(root), null, "none yet"); |
| 193 |
await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]); |
| 194 |
const id = await oxy.identity(root); |
| 195 |
assert.equal(id?.who, "Test <[email protected]>"); |
| 196 |
assert.equal(id?.secret, true); |
| 197 |
assert.ok(id?.mode === "change" || id?.mode === "seal"); |
| 198 |
}); |