mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-12 12:45:07 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef6d6fe493 |
@@ -6,14 +6,10 @@ import { ChildProcess } from "effect/unstable/process"
|
||||
const cfg = [
|
||||
"--no-optional-locks",
|
||||
"-c",
|
||||
"core.autocrlf=false",
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
"-c",
|
||||
"core.longpaths=true",
|
||||
"-c",
|
||||
"core.symlinks=true",
|
||||
"-c",
|
||||
"core.quotepath=false",
|
||||
] as const
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@ export type FileDiff = typeof FileDiff.Type
|
||||
|
||||
const prune = "7.days"
|
||||
const limit = 2 * 1024 * 1024
|
||||
const core = ["-c", "core.longpaths=true", "-c", "core.symlinks=true"]
|
||||
const cfg = ["-c", "core.autocrlf=false", ...core]
|
||||
const quote = [...cfg, "-c", "core.quotepath=false"]
|
||||
// Operational flags are safe for both repositories. Working-tree semantics are
|
||||
// resolved from the source repository and persisted in the private snapshot repository.
|
||||
const operational = ["-c", "core.longpaths=true"]
|
||||
const quote = [...operational, "-c", "core.quotepath=false"]
|
||||
const mirrored = ["core.autocrlf", "core.symlinks"]
|
||||
interface GitResult {
|
||||
readonly code: ChildProcessSpawner.ExitCode
|
||||
readonly text: string
|
||||
@@ -99,27 +101,25 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
),
|
||||
)
|
||||
|
||||
const required = Effect.fnUntraced(function* (
|
||||
cmd: string[],
|
||||
opts?: { cwd?: string; env?: Record<string, string>; stdin?: string },
|
||||
) {
|
||||
const result = yield* git(cmd, opts)
|
||||
if (result.code === 0) return result
|
||||
return yield* Effect.die(
|
||||
new Error(`git ${cmd.join(" ")} failed with exit code ${result.code}: ${result.stderr.trim()}`),
|
||||
)
|
||||
})
|
||||
|
||||
const ignore = Effect.fnUntraced(function* (files: string[]) {
|
||||
if (!files.length) return new Set<string>()
|
||||
// check-ignore treats a leading colon as pathspec magic but accepts and echoes a protective ./ prefix.
|
||||
const checkIgnorePaths = files.map((item) => (item.startsWith(":") ? `./${item}` : item))
|
||||
const check = yield* git(
|
||||
[
|
||||
...quote,
|
||||
"--git-dir",
|
||||
path.join(state.worktree, ".git"),
|
||||
"--work-tree",
|
||||
state.worktree,
|
||||
"check-ignore",
|
||||
"--no-index",
|
||||
"--stdin",
|
||||
"-z",
|
||||
],
|
||||
{
|
||||
cwd: state.worktree,
|
||||
stdin: encodeNulTerminatedPaths(checkIgnorePaths),
|
||||
},
|
||||
)
|
||||
const check = yield* git([...quote, "check-ignore", "--no-index", "--stdin", "-z"], {
|
||||
cwd: state.worktree,
|
||||
stdin: encodeNulTerminatedPaths(checkIgnorePaths),
|
||||
})
|
||||
if (check.code !== 0 && check.code !== 1) return new Set<string>()
|
||||
return new Set(
|
||||
check.text
|
||||
@@ -131,9 +131,9 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
|
||||
const drop = Effect.fnUntraced(function* (files: string[]) {
|
||||
if (!files.length) return
|
||||
yield* git(
|
||||
yield* required(
|
||||
[
|
||||
...cfg,
|
||||
...operational,
|
||||
...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]),
|
||||
],
|
||||
{
|
||||
@@ -145,18 +145,13 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
|
||||
const stage = Effect.fnUntraced(function* (files: string[]) {
|
||||
if (!files.length) return
|
||||
const result = yield* git(
|
||||
[...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])],
|
||||
yield* required(
|
||||
[...operational, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])],
|
||||
{
|
||||
cwd: state.worktree,
|
||||
stdin: encodeTopLevelLiteralPathspecs(files),
|
||||
},
|
||||
)
|
||||
if (result.code === 0) return
|
||||
yield* Effect.logWarning("failed to add snapshot files", {
|
||||
exitCode: result.code,
|
||||
stderr: result.stderr,
|
||||
})
|
||||
})
|
||||
|
||||
const exists = (file: string) => fs.exists(file).pipe(Effect.orDie)
|
||||
@@ -226,12 +221,62 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
|
||||
// Seed the index from the source repo so already-hashed entries are reused.
|
||||
// Best-effort: a missing/incompatible index just falls back to a full add.
|
||||
const sourceIndex = path.join(source, "index")
|
||||
const index = yield* git(["rev-parse", "--path-format=absolute", "--git-path", "index"], {
|
||||
cwd: state.worktree,
|
||||
})
|
||||
const sourceIndex = index.text.trim()
|
||||
if (yield* exists(sourceIndex)) {
|
||||
yield* fs.copyFile(sourceIndex, path.join(state.gitdir, "index")).pipe(Effect.catch(() => Effect.void))
|
||||
}
|
||||
})
|
||||
|
||||
const prepare = Effect.fnUntraced(function* () {
|
||||
const existed = yield* exists(state.gitdir)
|
||||
yield* fs.ensureDir(state.gitdir).pipe(Effect.orDie)
|
||||
if (!(yield* exists(path.join(state.gitdir, "config")))) {
|
||||
yield* required(["init"], {
|
||||
env: { GIT_DIR: state.gitdir, GIT_WORK_TREE: state.worktree },
|
||||
})
|
||||
}
|
||||
|
||||
const semantics = yield* Effect.forEach(mirrored, (key) =>
|
||||
git(["config", "--get", key], { cwd: state.worktree }).pipe(
|
||||
Effect.map((result) => {
|
||||
if (result.code === 0) return [key, result.text.trim()] as const
|
||||
if (result.code === 1) return [key, key === "core.autocrlf" ? "false" : "true"] as const
|
||||
throw new Error(`failed to resolve ${key}: ${result.stderr.trim()}`)
|
||||
}),
|
||||
),
|
||||
)
|
||||
const fingerprint = `v1;${semantics.map(([key, value]) => `${key}=${value}`).join(";")}`
|
||||
const previous = yield* git([
|
||||
"--git-dir",
|
||||
state.gitdir,
|
||||
"config",
|
||||
"--local",
|
||||
"--get",
|
||||
"opencode.snapshotSemantics",
|
||||
])
|
||||
if (previous.text.trim() === fingerprint) return
|
||||
const config = [
|
||||
...semantics,
|
||||
["core.longpaths", "true"],
|
||||
["core.fsmonitor", "false"],
|
||||
["feature.manyFiles", "true"],
|
||||
["index.version", "4"],
|
||||
["index.threads", "true"],
|
||||
["core.untrackedCache", "true"],
|
||||
] as const
|
||||
yield* Effect.forEach(
|
||||
config,
|
||||
([key, value]) => required(["--git-dir", state.gitdir, "config", "--local", key, value]),
|
||||
{ discard: true },
|
||||
)
|
||||
yield* seed()
|
||||
yield* required(["--git-dir", state.gitdir, "config", "--local", "opencode.snapshotSemantics", fingerprint])
|
||||
if (!existed) yield* Effect.logInfo("initialized")
|
||||
})
|
||||
|
||||
const add = Effect.fnUntraced(function* () {
|
||||
yield* sync()
|
||||
const [diff, other] = yield* Effect.all(
|
||||
@@ -246,13 +291,11 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
{ concurrency: 2 },
|
||||
)
|
||||
if (diff.code !== 0 || other.code !== 0) {
|
||||
yield* Effect.logWarning("failed to list snapshot files", {
|
||||
diffCode: diff.code,
|
||||
diffStderr: diff.stderr,
|
||||
otherCode: other.code,
|
||||
otherStderr: other.stderr,
|
||||
})
|
||||
return
|
||||
return yield* Effect.die(
|
||||
new Error(
|
||||
`failed to list snapshot files: diff-files=${diff.code} ${diff.stderr.trim()} ls-files=${other.code} ${other.stderr.trim()}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const tracked = diff.text.split("\0").filter(Boolean)
|
||||
@@ -319,26 +362,9 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
if (!(yield* enabled())) return
|
||||
const existed = yield* exists(state.gitdir)
|
||||
yield* fs.ensureDir(state.gitdir).pipe(Effect.orDie)
|
||||
if (!existed) {
|
||||
yield* git(["init"], {
|
||||
env: { GIT_DIR: state.gitdir, GIT_WORK_TREE: state.worktree },
|
||||
})
|
||||
yield* git(["--git-dir", state.gitdir, "config", "core.autocrlf", "false"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "core.longpaths", "true"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "core.symlinks", "true"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "core.fsmonitor", "false"])
|
||||
// Tuning for very large worktrees so the first add stays bounded.
|
||||
yield* git(["--git-dir", state.gitdir, "config", "feature.manyFiles", "true"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "index.version", "4"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "index.threads", "true"])
|
||||
yield* git(["--git-dir", state.gitdir, "config", "core.untrackedCache", "true"])
|
||||
yield* seed()
|
||||
yield* Effect.logInfo("initialized")
|
||||
}
|
||||
yield* prepare()
|
||||
yield* add()
|
||||
const result = yield* git(args(["write-tree"]), { cwd: state.directory })
|
||||
const result = yield* required(args(["write-tree"]), { cwd: state.directory })
|
||||
const hash = result.text.trim()
|
||||
yield* Effect.logInfo("tracking", { hash, cwd: state.directory, git: state.gitdir })
|
||||
return hash
|
||||
@@ -349,6 +375,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
const patch = Effect.fnUntraced(function* (hash: string) {
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
yield* prepare()
|
||||
yield* add()
|
||||
const result = yield* git(
|
||||
[...quote, ...args(["diff", "--cached", "--no-ext-diff", "--name-only", hash, "--", "."])],
|
||||
@@ -382,24 +409,11 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
const restore = Effect.fnUntraced(function* (snapshot: string) {
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
yield* prepare()
|
||||
yield* Effect.logInfo("restore", { commit: snapshot })
|
||||
const result = yield* git([...core, ...args(["read-tree", snapshot])], { cwd: state.worktree })
|
||||
if (result.code === 0) {
|
||||
const checkout = yield* git([...core, ...args(["checkout-index", "-a", "-f"])], {
|
||||
cwd: state.worktree,
|
||||
})
|
||||
if (checkout.code === 0) return
|
||||
yield* Effect.logError("failed to restore snapshot", {
|
||||
snapshot,
|
||||
exitCode: checkout.code,
|
||||
stderr: checkout.stderr,
|
||||
})
|
||||
return
|
||||
}
|
||||
yield* Effect.logError("failed to restore snapshot", {
|
||||
snapshot,
|
||||
exitCode: result.code,
|
||||
stderr: result.stderr,
|
||||
yield* required([...operational, ...args(["read-tree", snapshot])], { cwd: state.worktree })
|
||||
yield* required([...operational, ...args(["checkout-index", "-a", "-f"])], {
|
||||
cwd: state.worktree,
|
||||
})
|
||||
}),
|
||||
)
|
||||
@@ -408,6 +422,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
const revert = Effect.fnUntraced(function* (patches: Patch[]) {
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
yield* prepare()
|
||||
const ops: { hash: string; file: string; rel: string }[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const item of patches) {
|
||||
@@ -424,20 +439,21 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
|
||||
const single = Effect.fnUntraced(function* (op: (typeof ops)[number]) {
|
||||
yield* Effect.logInfo("reverting", { file: op.file, hash: op.hash })
|
||||
const result = yield* git([...core, ...args(["checkout", op.hash, "--", op.file])], {
|
||||
const result = yield* git([...operational, ...args(["checkout", op.hash, "--", op.file])], {
|
||||
cwd: state.worktree,
|
||||
})
|
||||
if (result.code === 0) return
|
||||
const tree = yield* git([...core, ...args(["ls-tree", op.hash, "--", op.rel])], {
|
||||
const tree = yield* git([...operational, ...args(["ls-tree", op.hash, "--", op.rel])], {
|
||||
cwd: state.worktree,
|
||||
})
|
||||
if (tree.code === 0 && tree.text.trim()) {
|
||||
yield* Effect.logInfo("file existed in snapshot but checkout failed, keeping", {
|
||||
file: op.file,
|
||||
hash: op.hash,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (tree.code !== 0)
|
||||
yield* Effect.die(
|
||||
new Error(`failed to inspect ${op.file} in snapshot ${op.hash}: ${tree.stderr.trim()}`),
|
||||
)
|
||||
if (tree.text.trim())
|
||||
yield* Effect.die(
|
||||
new Error(`failed to restore ${op.file} from snapshot ${op.hash}: ${result.stderr.trim()}`),
|
||||
)
|
||||
yield* Effect.logInfo("file did not exist in snapshot, deleting", { file: op.file, hash: op.hash })
|
||||
yield* remove(op.file)
|
||||
})
|
||||
@@ -464,7 +480,10 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
}
|
||||
|
||||
const tree = yield* git(
|
||||
[...core, ...args(["ls-tree", "--name-only", first.hash, "--", ...run.map((item) => item.rel)])],
|
||||
[
|
||||
...operational,
|
||||
...args(["ls-tree", "--name-only", first.hash, "--", ...run.map((item) => item.rel)]),
|
||||
],
|
||||
{
|
||||
cwd: state.worktree,
|
||||
},
|
||||
@@ -493,7 +512,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
if (list.length) {
|
||||
yield* Effect.logInfo("reverting", { hash: first.hash, files: list.length })
|
||||
const result = yield* git(
|
||||
[...core, ...args(["checkout", first.hash, "--", ...list.map((item) => item.file)])],
|
||||
[...operational, ...args(["checkout", first.hash, "--", ...list.map((item) => item.file)])],
|
||||
{
|
||||
cwd: state.worktree,
|
||||
},
|
||||
@@ -526,6 +545,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
const diff = Effect.fnUntraced(function* (hash: string) {
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
yield* prepare()
|
||||
yield* add()
|
||||
const result = yield* git([...quote, ...args(["diff", "--cached", "--no-ext-diff", hash, "--", "."])], {
|
||||
cwd: state.worktree,
|
||||
@@ -546,6 +566,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
const diffFull = Effect.fnUntraced(function* (from: string, to: string) {
|
||||
return yield* locked(
|
||||
Effect.gen(function* () {
|
||||
yield* prepare()
|
||||
type Row = {
|
||||
file: string
|
||||
status: "added" | "deleted" | "modified"
|
||||
@@ -565,12 +586,14 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
if (row.status === "added") {
|
||||
return [
|
||||
"",
|
||||
yield* git([...cfg, ...args(["show", `${to}:${row.file}`])]).pipe(Effect.map((item) => item.text)),
|
||||
yield* git([...operational, ...args(["show", `${to}:${row.file}`])]).pipe(
|
||||
Effect.map((item) => item.text),
|
||||
),
|
||||
]
|
||||
}
|
||||
if (row.status === "deleted") {
|
||||
return [
|
||||
yield* git([...cfg, ...args(["show", `${from}:${row.file}`])]).pipe(
|
||||
yield* git([...operational, ...args(["show", `${from}:${row.file}`])]).pipe(
|
||||
Effect.map((item) => item.text),
|
||||
),
|
||||
"",
|
||||
@@ -578,8 +601,10 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
}
|
||||
return yield* Effect.all(
|
||||
[
|
||||
git([...cfg, ...args(["show", `${from}:${row.file}`])]).pipe(Effect.map((item) => item.text)),
|
||||
git([...cfg, ...args(["show", `${to}:${row.file}`])]).pipe(Effect.map((item) => item.text)),
|
||||
git([...operational, ...args(["show", `${from}:${row.file}`])]).pipe(
|
||||
Effect.map((item) => item.text),
|
||||
),
|
||||
git([...operational, ...args(["show", `${to}:${row.file}`])]).pipe(Effect.map((item) => item.text)),
|
||||
],
|
||||
{ concurrency: 2 },
|
||||
)
|
||||
@@ -602,7 +627,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | C
|
||||
if (!refs.length) return new Map<string, { before: string; after: string }>()
|
||||
|
||||
const batch = yield* appProcess.run(
|
||||
ChildProcess.make("git", [...cfg, ...args(["cat-file", "--batch"])], {
|
||||
ChildProcess.make("git", [...operational, ...args(["cat-file", "--batch"])], {
|
||||
cwd: state.directory,
|
||||
extendEnv: true,
|
||||
}),
|
||||
|
||||
@@ -115,6 +115,39 @@ describe("Git", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("respects repository line ending configuration", () =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* scopedTmpdir({ git: true })
|
||||
yield* Effect.promise(() => $`git config core.autocrlf true`.cwd(tmp.path).quiet())
|
||||
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "line-endings.txt"), "before\n", "utf-8"))
|
||||
yield* Effect.promise(() => $`git add line-endings.txt`.cwd(tmp.path).quiet())
|
||||
yield* Effect.promise(() => $`git commit --no-gpg-sign -m "add line endings"`.cwd(tmp.path).quiet())
|
||||
yield* Effect.promise(() => fs.rm(path.join(tmp.path, "line-endings.txt")))
|
||||
yield* Effect.promise(() => $`git checkout -- line-endings.txt`.cwd(tmp.path).quiet())
|
||||
|
||||
const git = yield* Git.Service
|
||||
expect(yield* git.status(tmp.path)).toEqual([])
|
||||
expect(yield* git.diff(tmp.path, "HEAD")).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("respects repository symlink configuration", () =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* scopedTmpdir({ git: true })
|
||||
const blob = yield* Effect.promise(() => $`echo -n target.txt | git hash-object -w --stdin`.cwd(tmp.path).text())
|
||||
yield* Effect.promise(() =>
|
||||
$`git update-index --add --cacheinfo 120000,${blob.trim()},link.txt`.cwd(tmp.path).quiet(),
|
||||
)
|
||||
yield* Effect.promise(() => $`git commit --no-gpg-sign -m "add symlink"`.cwd(tmp.path).quiet())
|
||||
yield* Effect.promise(() => $`git config core.symlinks false`.cwd(tmp.path).quiet())
|
||||
yield* Effect.promise(() => $`git checkout-index -f link.txt`.cwd(tmp.path).quiet())
|
||||
|
||||
const git = yield* Git.Service
|
||||
expect(yield* git.status(tmp.path)).toEqual([])
|
||||
expect(yield* git.diff(tmp.path, "HEAD")).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("patch() returns capped native patch output", () =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* scopedTmpdir({ git: true })
|
||||
|
||||
@@ -5,7 +5,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Effect, Fiber, Layer } from "effect"
|
||||
import { Effect, Exit, Fiber, Layer } from "effect"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import {
|
||||
disposeAllInstances,
|
||||
@@ -117,6 +117,68 @@ it.instance(
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"uses source repository line ending semantics",
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* TestInstance
|
||||
const file = path.join(tmp.directory, "line-endings.txt")
|
||||
yield* exec(tmp.directory, ["git", "config", "core.autocrlf", "true"])
|
||||
yield* write(file, "before\n")
|
||||
yield* exec(tmp.directory, ["git", "add", "line-endings.txt"])
|
||||
yield* exec(tmp.directory, ["git", "commit", "--no-gpg-sign", "-m", "add line endings"])
|
||||
yield* rm(file)
|
||||
yield* exec(tmp.directory, ["git", "checkout", "--", "line-endings.txt"])
|
||||
|
||||
const snapshot = yield* Snapshot.Service
|
||||
const before = yield* snapshot.track()
|
||||
yield* write(file, "before\n")
|
||||
const after = yield* snapshot.track()
|
||||
expect(after).toBe(before)
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.autocrlf", "false"])
|
||||
yield* write(file, "before\r\n")
|
||||
expect(yield* snapshot.track()).not.toBe(before)
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.autocrlf", "true"])
|
||||
expect(yield* snapshot.track()).toBe(before)
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.autocrlf", "input"])
|
||||
yield* write(file, "changed\n")
|
||||
yield* snapshot.restore(before!)
|
||||
expect(yield* readText(file)).toBe("before\n")
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.autocrlf", "true"])
|
||||
yield* write(file, "changed\n")
|
||||
yield* snapshot.restore(before!)
|
||||
expect(yield* readText(file)).toBe("before\r\n")
|
||||
}),
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"uses source repository symlink semantics",
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* TestInstance
|
||||
const blob = yield* Effect.promise(() =>
|
||||
$`printf target.txt | git hash-object -w --stdin`.cwd(tmp.directory).text(),
|
||||
)
|
||||
yield* exec(tmp.directory, ["git", "update-index", "--add", "--cacheinfo", `120000,${blob.trim()},link.txt`])
|
||||
yield* exec(tmp.directory, ["git", "commit", "--no-gpg-sign", "-m", "add symlink"])
|
||||
yield* exec(tmp.directory, ["git", "config", "core.symlinks", "false"])
|
||||
yield* exec(tmp.directory, ["git", "checkout-index", "-f", "link.txt"])
|
||||
|
||||
const snapshot = yield* Snapshot.Service
|
||||
const before = yield* snapshot.track()
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.symlinks", "true"])
|
||||
expect(yield* snapshot.track()).not.toBe(before)
|
||||
|
||||
yield* exec(tmp.directory, ["git", "config", "core.symlinks", "false"])
|
||||
expect(yield* snapshot.track()).toBe(before)
|
||||
}),
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"revert should remove new files",
|
||||
withTrackedSnapshot(({ tmp, snapshot, before }) =>
|
||||
@@ -287,6 +349,20 @@ it.instance(
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"revert preserves files when snapshot lookup fails",
|
||||
withTrackedSnapshot(({ tmp, snapshot }) =>
|
||||
Effect.gen(function* () {
|
||||
const file = path.join(tmp.path, "protected.txt")
|
||||
yield* write(file, "keep")
|
||||
const result = yield* Effect.exit(snapshot.revert([{ hash: "invalid-hash", files: [file] }]))
|
||||
expect(Exit.isFailure(result)).toBe(true)
|
||||
expect(yield* readText(file)).toBe("keep")
|
||||
}),
|
||||
),
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"unicode filenames",
|
||||
withTrackedSnapshot(({ tmp, snapshot, before }) =>
|
||||
|
||||
Reference in New Issue
Block a user