Merge v3-t80: extension snapshot of extensions/ only (git archive + tar), atomic last-good marker (T80)

This commit is contained in:
2026-11-08 07:02:08 +00:00
38 changed files with 535 additions and 117 deletions
+16 -4
View File
@@ -32712,7 +32712,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -32728,7 +32728,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -32818,8 +32819,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -70720,7 +70720,7 @@ async function loadExtensions(harness2, registry3, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -70736,7 +70736,8 @@ async function loadExtensions(harness2, registry3, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state2), context);
ops2.op_extension_status(JSON.stringify(state2));
await fs2.write("/src/sausage/.git/sausage-last-good", state2.good ?? "");
await fs2.write("/src/sausage/.git/sausage-last-good.tmp", state2.good ?? "");
await fs2.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -70826,8 +70827,19 @@ async function loadExtensions(harness2, registry3, storage2, models2, context, b
try {
await fs2.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs2.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs2.remove(archive);
}
await fs2.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs2.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -31486,7 +31486,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -31502,7 +31502,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -31592,8 +31593,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries3) => {
for (const facet of entries3) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -31486,7 +31486,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -31502,7 +31502,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -31592,8 +31593,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+35 -5
View File
@@ -31486,7 +31486,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -31502,7 +31502,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -31592,8 +31593,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
@@ -31720,6 +31732,9 @@ var harness = await Harness.open(storage, { models, registry, settings: { get ex
} } }, BACKGROUND_CONTEXT);
var root = await harness.root(BACKGROUND_CONTEXT, { agent: { model: { provider: "faux", modelId: "faux-1" } } });
globalThis.shared = { gate: Promise.withResolvers(), started: false, wait: false };
var markerWrite = fs.write;
var markerBlocked = Promise.withResolvers();
var markerRelease = Promise.withResolvers();
var loader = await loadExtensions(harness, registry, storage, models, BACKGROUND_CONTEXT);
harness.resume();
var turn = async () => {
@@ -31783,15 +31798,30 @@ return defineExtension({name:'demo',tools:[defineTool({name:'demo',description:'
globalThis.shared.gate.resolve();
assert((await running).includes("result A"), "Old call must finish on A");
assert((await turn()).includes("result C"), "Next call must use C");
await loader.reload();
fs.write = async (path, text) => {
if (path.includes("sausage-last-good")) {
fs.write = markerWrite;
await markerWrite(path, "");
markerBlocked.resolve();
await markerRelease.promise;
}
return markerWrite(path, text);
};
await git("/src/sausage", "update-ref", "refs/heads/main", b);
await until2(() => loader.state().current === b && !!loader.state().error);
assert((await exec(["extensions-revert"], { cwd: "/" })).code === 0, "Revert command");
await markerBlocked.promise;
const reverted = await exec(["extensions-revert"], { cwd: "/" });
markerRelease.resolve();
assert(reverted.code === 0, `Revert command: ${JSON.stringify(reverted)}`);
await until2(() => loader.state().current === c && !loader.state().error);
await git("/src/sausage", "update-ref", "refs/heads/main", b);
await until2(() => loader.state().current === b && !!loader.state().error);
await fs.write("/src/sausage/good.txt", c);
}
} finally {
markerRelease.resolve();
fs.write = markerWrite;
globalThis.shared.gate.resolve();
await loader.close();
await harness.close(BACKGROUND_CONTEXT);
+15 -2
View File
@@ -12,6 +12,7 @@ const registry=createRegistry(), storage=await openStorage(globalThis.storagePat
const harness=await Harness.open(storage,{models,registry,settings:{get extensions(){return registry.snapshot().installed();}}},context);
const root=await harness.root(context,{agent:{model:{provider:'faux',modelId:'faux-1'}}});
globalThis.shared={gate:Promise.withResolvers(),started:false,wait:false};
const markerWrite=fs.write, markerBlocked=Promise.withResolvers(), markerRelease=Promise.withResolvers();
const loader=await loadExtensions(harness,registry,storage,models,context);
harness.resume();
const turn = async()=> {
@@ -67,12 +68,24 @@ return defineExtension({name:'demo',tools:[defineTool({name:'demo',description:'
globalThis.shared.wait=false;globalThis.shared.gate.resolve();
assert((await running).includes('result A'),'Old call must finish on A');
assert((await turn()).includes('result C'),'Next call must use C');
await loader.reload();
// Suspend a marker write after truncation, as the asynchronous VFS write can do under load.
fs.write=async(path,text)=>{
if(path.includes('sausage-last-good')) {
fs.write=markerWrite;
await markerWrite(path,'');markerBlocked.resolve();await markerRelease.promise;
}
return markerWrite(path,text);
};
await git('/src/sausage','update-ref','refs/heads/main',b);
await until(()=>loader.state().current===b && !!loader.state().error);
assert((await exec(['extensions-revert'],{cwd:'/'})).code===0,'Revert command');
await markerBlocked.promise;
const reverted=await exec(['extensions-revert'],{cwd:'/'});
markerRelease.resolve();
assert(reverted.code===0,`Revert command: ${JSON.stringify(reverted)}`);
await until(()=>loader.state().current===c && !loader.state().error);
await git('/src/sausage','update-ref','refs/heads/main',b);
await until(()=>loader.state().current===b && !!loader.state().error);
await fs.write('/src/sausage/good.txt',c);
}
} finally {globalThis.shared.gate.resolve();await loader.close();await harness.close(context);}
} finally {markerRelease.resolve();fs.write=markerWrite;globalThis.shared.gate.resolve();await loader.close();await harness.close(context);}
+16 -4
View File
@@ -84,11 +84,11 @@ export async function loadExtensions(harness:any, registry:any, storage:any, mod
let settings:any = {};
let active = true;
const commands = new AbortController();
// Snapshot checkout copies the source tree. The tool helper's 30 seconds is too short under load.
// Loader commands have no wall-clock deadline. close() cancels them, even under machine load.
const loaderGit = async (...args:string[]) => {
for (let attempt=0;;attempt++) {
try {
const result = await exec(['git',...args],{cwd:repo,timeout:120,signal:commands.signal});
const result = await exec(['git',...args],{cwd:repo,signal:commands.signal});
if(result.code)throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch(error) {
@@ -104,7 +104,8 @@ export async function loadExtensions(harness:any, registry:any, storage:any, mod
const publish = async () => {
await harness.commit(async (tx:any)=>Object.assign(await tx.doc(ExtensionState),state),context);
ops.op_extension_status(JSON.stringify(state));
await fs.write('/src/sausage/.git/sausage-last-good',state.good ?? '');
await fs.write('/src/sausage/.git/sausage-last-good.tmp',state.good ?? '');
await fs.rename('/src/sausage/.git/sausage-last-good.tmp','/src/sausage/.git/sausage-last-good');
};
const dispose = async (entries:any[]) => { for(const facet of entries) for(const cleanup of facet.close) { try {await cleanup();} catch(error) {ops.op_output(`Extension cleanup: ${String(error)}\n`);} } };
const scope = (name:string, staged:Map<string,any>) => {
@@ -151,7 +152,18 @@ export async function loadExtensions(harness:any, registry:any, storage:any, mod
try {
const snapshot = `/run/sausage/extensions/${sha}`;
for(const path of ['/run/sausage','/run/sausage/extensions']) {try {await fs.mkdir(path);} catch(error) {if((error as any).code!=='already_exists')throw error;} }
try {await fs.stat(snapshot);} catch {await loaderGit('worktree','prune');await loaderGit('worktree','add','--detach',snapshot,sha);}
try {await fs.stat(snapshot);} catch {
// Only publish a complete subtree. A cancelled extraction can leave its staging directory.
const staging=`${snapshot}.tmp`, archive=`${snapshot}.tar`;
try {await fs.mkdir(staging);} catch(error) {if((error as any).code!=='already_exists')throw error;}
if(await loaderGit('ls-tree','--name-only',sha,'extensions')) {
await loaderGit('archive','--format=tar',`--output=${archive}`,sha,'extensions');
const result=await exec(['tar','-xf',archive,'-C',staging],{cwd:repo,signal:commands.signal});
if(result.code)throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging,snapshot);
}
const modules:any[] = [];
for(const entry of await fs.list(`${snapshot}/extensions`).catch(error=>{if(error.code==='not_found')return [];throw error;})) {
if(entry.kind!=='directory')continue;
+16 -4
View File
@@ -32712,7 +32712,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -32728,7 +32728,8 @@ async function loadExtensions(harness, registry, storage, models, context, built
const publish2 = async () => {
await harness.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -32818,8 +32819,19 @@ async function loadExtensions(harness, registry, storage, models, context, built
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -31486,7 +31486,7 @@ async function loadExtensions(harness2, registry, storage, models, context, buil
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -31502,7 +31502,8 @@ async function loadExtensions(harness2, registry, storage, models, context, buil
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -31592,8 +31593,19 @@ async function loadExtensions(harness2, registry, storage, models, context, buil
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries3) => {
for (const facet of entries3) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot2);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot2, sha);
const staging = `${snapshot2}.tmp`, archive = `${snapshot2}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot2);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot2}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness, registry, storage, models, context, built
const publish2 = async () => {
await harness.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness, registry, storage, models, context, built
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -31486,7 +31486,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -31502,7 +31502,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -31592,8 +31593,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot2);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot2, sha);
const staging = `${snapshot2}.tmp`, archive = `${snapshot2}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot2);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot2}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha2);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha2, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha2, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+16 -4
View File
@@ -30546,7 +30546,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const loaderGit = async (...args) => {
for (let attempt = 0; ; attempt++) {
try {
const result = await exec(["git", ...args], { cwd: repo, timeout: 120, signal: commands.signal });
const result = await exec(["git", ...args], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(result.stderr || `git exited ${result.code}`);
return result.stdout.trim();
} catch (error) {
@@ -30562,7 +30562,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
await fs.write("/src/sausage/.git/sausage-last-good.tmp", state.good ?? "");
await fs.rename("/src/sausage/.git/sausage-last-good.tmp", "/src/sausage/.git/sausage-last-good");
};
const dispose = async (entries2) => {
for (const facet of entries2) for (const cleanup of facet.close) {
@@ -30652,8 +30653,19 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await fs.stat(snapshot);
} catch {
await loaderGit("worktree", "prune");
await loaderGit("worktree", "add", "--detach", snapshot, sha);
const staging = `${snapshot}.tmp`, archive = `${snapshot}.tar`;
try {
await fs.mkdir(staging);
} catch (error) {
if (error.code !== "already_exists") throw error;
}
if (await loaderGit("ls-tree", "--name-only", sha, "extensions")) {
await loaderGit("archive", "--format=tar", `--output=${archive}`, sha, "extensions");
const result = await exec(["tar", "-xf", archive, "-C", staging], { cwd: repo, signal: commands.signal });
if (result.code) throw new Error(`tar: ${result.stderr || result.code}`);
await fs.remove(archive);
}
await fs.rename(staging, snapshot);
}
const modules = [];
for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => {
+3
View File
@@ -36,6 +36,8 @@ fn setup(directory: &Path) -> Result<()> {
"git",
"-p",
"curl",
"-p",
"sausage-archive-tools",
"--target",
"wasm32-wasip2",
"--target-dir"
@@ -58,6 +60,7 @@ fn setup(directory: &Path) -> Result<()> {
std::fs::create_dir_all(disk.join("bin"))?;
for (source, name) in [
("git.wasm", "git.wasm"),
("tar.wasm", "tar.wasm"),
("curl.wasm", "curl.wasm"),
("brush.wasm", "sh.wasm"),
] {
+9 -1
View File
@@ -141,7 +141,15 @@ pub fn build_git() -> &'static Path {
cwd_init();
assert!(
Command::new("cargo")
.args(["build", "--release", "-p", "git", "--manifest-path"])
.args([
"build",
"--release",
"-p",
"git",
"-p",
"sausage-archive-tools",
"--manifest-path"
])
.arg(userland.join("Cargo.toml"))
.args(["--target", "wasm32-wasip2", "--target-dir"])
.arg(&target)
+6 -4
View File
@@ -52,10 +52,12 @@ async fn core_extensions() -> anyhow::Result<()> {
let world = World::open(dir.path())?;
let disk = dir.path().join("world");
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
let source = disk.join("src/sausage");
for name in ["core", "models", "prompt", "conversations"] {
copy_tree(
+6 -4
View File
@@ -38,10 +38,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
copy_tree(&source.join("extensions"), &repo.join("extensions"))?;
std::fs::create_dir_all(repo.join("extensions/probe"))?;
std::fs::write(
+6 -2
View File
@@ -95,8 +95,12 @@ async fn world_extensions() -> anyhow::Result<()> {
let world = World::open(dir.path())?;
let disk = dir.path().join("world");
std::fs::create_dir_all(disk.join("bin"))?;
let git = target.join("wasm32-wasip2/release/git.wasm");
std::fs::copy(git, disk.join("bin/git.wasm"))?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
let repo = disk.join("src/sausage");
std::fs::create_dir_all(&repo)?;
assert!(
+38 -13
View File
@@ -22,7 +22,15 @@ impl Exec for Author {
] {
options.env.push((key.into(), value.into()));
}
self.0.exec(command, options, output).await
let label = format!("{command:?}");
let budget = options.timeout;
let started = std::time::Instant::now();
let result = self.0.exec(command, options, output).await;
eprintln!(
"Extension {label}: deadline {budget:?}, elapsed {:?}, result {result:?}",
started.elapsed()
);
result
}
}
#[tokio::test(flavor = "current_thread")]
@@ -41,6 +49,8 @@ async fn commits_and_restart() -> anyhow::Result<()> {
"git",
"-p",
"sausage-exec-tools",
"-p",
"sausage-archive-tools",
"--manifest-path"
])
.arg(userland.join("Cargo.toml"))
@@ -59,7 +69,7 @@ async fn commits_and_restart() -> anyhow::Result<()> {
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for name in ["extensions-revert", "env"] {
for name in ["extensions-revert", "env", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{name}.wasm")),
disk.join(format!("bin/{name}.wasm")),
@@ -113,7 +123,7 @@ async fn commits_and_restart() -> anyhow::Result<()> {
Ok(())
}
/// Model command time without a wall-clock delay, including work longer than the old 30 second budget.
/// Model loader commands without delays, and require cancellation instead of a wall-clock deadline.
struct LoaderCommand {
root: Arc<dyn sausage_vfs::Layer>,
failures: usize,
@@ -131,18 +141,21 @@ impl Exec for LoaderCommand {
let Command::Argv(args) = command else {
anyhow::bail!("unexpected shell")
};
assert!(
options.timeout.is_none(),
"Loader work must not have a wall-clock deadline"
);
if args[0] == "tar" {
return Ok(0);
}
match args[1].as_str() {
"rev-parse" => output(Stream::Stdout, b"new\n"),
"worktree" if args[2] == "prune" => {}
"worktree" if args[2] == "add" => {
"ls-tree" => output(Stream::Stdout, b"extensions\n"),
"archive" => {
let attempt = self
.attempts
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if options
.timeout
.is_some_and(|budget| budget < std::time::Duration::from_secs(31))
|| (attempt < self.failures && self.timeout)
{
if attempt < self.failures && self.timeout {
anyhow::bail!("command timed out");
}
if attempt < self.failures {
@@ -159,9 +172,21 @@ impl Exec for LoaderCommand {
"Retry must publish its error: {status}"
);
}
let snapshot = &args[4];
let archive = args[3].strip_prefix("--output=").unwrap();
let file = self
.root
.open(
archive,
sausage_vfs::Open {
write: true,
create: true,
..Default::default()
},
)
.await?;
file.write(0, vec![]).await?;
let snapshot = format!("{}.tmp", archive.strip_suffix(".tar").unwrap());
for path in [
snapshot.clone(),
format!("{snapshot}/extensions"),
format!("{snapshot}/extensions/demo"),
] {
@@ -217,7 +242,7 @@ async fn loader_command(failures: usize, timeout: bool) -> anyhow::Result<()> {
const testState = testLoader.state();
if(testState.good !== '{expected}') throw new Error(JSON.stringify(testState));
if((testState.error !== null) !== ({expected:?} === 'old')) throw new Error(JSON.stringify(testState));
if(testState.error && !testState.error.includes('git worktree add')) throw new Error(testState.error);
if(testState.error && !testState.error.includes('git archive')) throw new Error(testState.error);
await testLoader.close();
await testHarness.close(BACKGROUND_CONTEXT);
"#
+6 -4
View File
@@ -13,10 +13,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
crate::common::copy_tree(&source.join("extensions"), &repo.join("extensions"))?;
std::fs::create_dir_all(repo.join("agents"))?;
std::fs::create_dir_all(repo.join("extensions/probe"))?;
+8 -4
View File
@@ -26,6 +26,8 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
"git",
"-p",
"curl",
"-p",
"sausage-archive-tools",
"--manifest-path"
])
.arg(userland.join("Cargo.toml"))
@@ -57,10 +59,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
target.join("wasm32-wasip2/release/harness-api-client.wasm"),
disk.join("bin/harness-api-client.wasm"),
)?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
std::fs::copy(
target.join("wasm32-wasip2/release/curl.wasm"),
disk.join("bin/curl.wasm"),
+6 -4
View File
@@ -17,10 +17,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
for directory in [
"extensions/subagents",
"extensions/messages",
+2
View File
@@ -338,6 +338,8 @@ async fn bundled_extensions_load() -> anyhow::Result<()> {
const bundleLoader = await loadExtensions(bundleHarness, bundleRegistry, bundleStorage,
bundleModels, BACKGROUND_CONTEXT, [CodingTools2, CredentialRequests]);
if (bundleLoader.state().error) throw new Error(bundleLoader.state().error);
const snapshotEntries=await fs2.list(`/run/sausage/extensions/${{bundleLoader.state().good}}`);
if(JSON.stringify(snapshotEntries.map(entry=>entry.name)) !== '["extensions"]') throw new Error(JSON.stringify(snapshotEntries));
await bundleLoader.close();
await bundleHarness.close(BACKGROUND_CONTEXT);
"#
+6 -4
View File
@@ -30,10 +30,12 @@ fn prepare(dir: &Path, all_extensions: bool) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
if !target.join("wasm32-wasip2/release/brush.wasm").exists() {
anyhow::ensure!(
crate::common::build_brush()?.success(),
+6 -1
View File
@@ -24,7 +24,9 @@ fn prepare(dir: &Path) -> anyhow::Result<PathBuf> {
.args(["rev-parse", "HEAD"])
.output()?;
ensure!(revision.status.success(), "fixture revision");
if fs::read(bundle.join("revision")).ok().as_deref() != Some(revision.stdout.as_slice()) {
if fs::read(bundle.join("revision")).ok().as_deref() != Some(revision.stdout.as_slice())
|| !bundle.join("bin/tar.wasm").is_file()
{
if !userland.join("cwd-init/init.o").exists() {
crate::common::cwd_init();
}
@@ -42,6 +44,8 @@ fn prepare(dir: &Path) -> anyhow::Result<PathBuf> {
"undo",
"-p",
"sausage-coreutils",
"-p",
"sausage-archive-tools",
]))?;
ensure!(
crate::common::build_brush()?.success(),
@@ -53,6 +57,7 @@ fn prepare(dir: &Path) -> anyhow::Result<PathBuf> {
("brush", "sh"),
("brush", "bash"),
("git", "git"),
("tar", "tar"),
("cargo", "cargo"),
("box", "box"),
("undo", "undo"),
+6 -4
View File
@@ -10,10 +10,12 @@ async fn plan_todos_and_restart() -> anyhow::Result<()> {
let disk = dir.path().join("world");
let _world = World::open(dir.path())?;
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
let repo = disk.join("src/sausage");
std::fs::create_dir_all(&repo)?;
assert!(
+6 -4
View File
@@ -15,10 +15,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
for directory in [
"extensions/subagents",
"extensions/models",
+6 -4
View File
@@ -17,10 +17,12 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = crate::common::build_git();
std::fs::create_dir_all(disk.join("bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/git.wasm"),
disk.join("bin/git.wasm"),
)?;
for binary in ["git", "tar"] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{binary}.wasm")),
)?;
}
for directory in [
"extensions/subagents",
"extensions/messages",
+1 -1
View File
@@ -34,7 +34,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> {
assert!(crate::common::build_brush()?.success());
}
std::fs::create_dir_all(disk.join("bin"))?;
for (binary, name) in [("git", "git"), ("brush", "sh")] {
for (binary, name) in [("git", "git"), ("tar", "tar"), ("brush", "sh")] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
disk.join(format!("bin/{name}.wasm")),
+1
View File
@@ -1525,6 +1525,7 @@ dependencies = [
"sausage-ssh",
"serde",
"serde_json",
"tar",
"url",
"wasi 0.14.7+wasi-0.2.4",
]
+1
View File
@@ -16,3 +16,4 @@ gix-zlib = "0.4"
url = "2"
wasi = "0.14"
sausage-ssh = { path = "../ssh-client" }
tar = { version = "0.4", default-features = false }
+3
View File
@@ -78,6 +78,9 @@ It also checks these forms:
- `show --stat`, `--oneline`, `--name-only`, `show HEAD:path`, and merge commits without a combined diff.
- `cat-file -t`, `-p`, `-s`, and `-e` for object inspection.
- `ls-tree [-r] [--name-only] <rev> [path]` for literal paths.
- `archive [--format=tar] [-o <file>] <rev> [path]` for a tree or literal subtree.
The archive preserves file contents, executable modes, and symbolic links.
`archive-test.nu` compares extracted output with native Git.
- `diff HEAD~1`, `diff A..B`, `--name-only`, `--name-status`, and one literal path after `--`.
- `diff --check` and `diff --cached --check` for added whitespace errors and conflict markers.
- `branch -a`, `-d`, `-D`, `-f`, `-m`, and `--show-current`.
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env nu
# Compare the literal subtree archive with native Git, including binary files and links.
def main [component: path] {
let component = ($component | path expand)
let root = (mktemp -d -p /tmp git-archive.XXXXXX)
let repo = ($root | path join repo)
mkdir ($repo | path join extensions/demo) ($root | path join native) ($root | path join wasi)
^git init -q -b main $repo
'outside' | save ($repo | path join outside.txt)
0x[00 ff 7f 80] | save ($repo | path join 'extensions/demo/payload space.bin')
'executable' | save ($repo | path join extensions/demo/run)
^chmod +x ($repo | path join extensions/demo/run)
^ln -s 'payload space.bin' ($repo | path join extensions/demo/link)
^git -C $repo add .
^git -C $repo -c user.name=Test -c user.email=test@example.com commit --no-gpg-sign -q -m fixture
^git -C $repo archive --format=tar $'--output=($root)/native.tar' HEAD extensions
^wasmtime run -S http -S cli-exit-with-code=y --dir $root $component -C $repo archive --format=tar $'--output=($root)/wasi.tar' HEAD extensions
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'WASI archive failed'} }
for side in [native wasi] { ^tar -xf $'($root)/($side).tar' -C $'($root)/($side)' }
^diff -r $'($root)/native' $'($root)/wasi'
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'Extracted contents differ'} }
if ($'($root)/wasi/outside.txt' | path exists) { error make {msg: 'Archive includes unrelated files'} }
let native_mode = (^stat -c '%a' $'($root)/native/extensions/demo/run')
let wasi_mode = (^stat -c '%a' $'($root)/wasi/extensions/demo/run')
if $native_mode != $wasi_mode { error make {msg: 'Executable mode differs'} }
if (^readlink $'($root)/wasi/extensions/demo/link') != 'payload space.bin' { error make {msg: 'Symlink differs'} }
for args in [[archive bad-revision extensions] [archive HEAD missing] [archive --format=zip HEAD]] {
let result = (^wasmtime run -S http -S cli-exit-with-code=y --dir $root $component -C $repo ...$args | complete)
if $result.exit_code == 0 { error make {msg: $'Invalid archive succeeded: ($args)'} }
}
print $'Archive comparison passed: ($root)'
}
+86
View File
@@ -0,0 +1,86 @@
//! Export a committed tree or literal subtree as tar, without an index or worktree.
use crate::{Result, repo::error};
use std::{io::Write, path::Path};
pub(crate) fn archive(
repo: &gix::Repository,
revision: &str,
path: Option<&str>,
output: Option<&Path>,
) -> Result<()> {
if let Some(path) = path {
crate::repo::validate_path(path)?;
}
let root = crate::local::tree(repo, revision)?;
let writer: Box<dyn Write> = match output {
Some(path) => {
Box::new(std::fs::File::create(path).map_err(|e| error("archive output", e))?)
}
None => Box::new(std::io::stdout().lock()),
};
let mut tar = tar::Builder::new(writer);
fn walk(
repo: &gix::Repository,
id: gix::ObjectId,
prefix: &str,
filter: Option<&str>,
tar: &mut tar::Builder<Box<dyn Write>>,
) -> Result<bool> {
let tree = repo.find_tree(id).map_err(|e| error("archive tree", e))?;
let mut found = filter.is_none();
for entry in tree.iter() {
let entry = entry.map_err(|e| error("archive entry", e))?;
let path = format!("{prefix}{}", entry.filename());
let selected = filter.is_none_or(|f| path == f || path.starts_with(&format!("{f}/")));
let ancestor = filter.is_some_and(|f| f.starts_with(&format!("{path}/")));
if !selected && !ancestor {
continue;
}
found |= selected;
if entry.mode().is_tree() {
if selected {
let mut header = tar::Header::new_gnu();
header.set_entry_type(tar::EntryType::Directory);
header.set_mode(0o755);
header.set_size(0);
tar.append_data(&mut header, &path, std::io::empty())
.map_err(|e| error("archive directory", e))?;
}
found |= walk(
repo,
entry.oid().to_owned(),
&format!("{path}/"),
filter,
tar,
)?;
} else if selected && !entry.mode().is_commit() {
let object = repo
.find_blob(entry.oid())
.map_err(|e| error("archive blob", e))?;
let mut header = tar::Header::new_gnu();
header.set_mode(entry.mode().kind() as u32 & 0o777);
if entry.mode().is_link() {
header.set_entry_type(tar::EntryType::Symlink);
header.set_size(0);
header
.set_link_name(
std::str::from_utf8(&object.data)
.map_err(|e| error("archive link", e))?,
)
.map_err(|e| error("archive link", e))?;
tar.append_data(&mut header, &path, std::io::empty())
.map_err(|e| error("archive link", e))?;
} else {
header.set_size(object.data.len() as u64);
tar.append_data(&mut header, &path, object.data.as_slice())
.map_err(|e| error("archive file", e))?;
}
}
}
Ok(found)
}
if !walk(repo, root, "", path, &mut tar)? {
return Err(error("archive", "path did not match any files"));
}
tar.finish().map_err(|e| error("archive finish", e))
}
+17
View File
@@ -1,5 +1,6 @@
//! Local Git operations adapted from the v2 gix plugin, without RPC or host imports.
#![allow(dead_code)]
mod archive;
mod branches;
mod changes;
mod checkout;
@@ -78,6 +79,14 @@ pub struct Cli {
}
#[derive(Subcommand)]
enum Command {
Archive {
#[arg(long, default_value = "tar", value_parser = ["tar"])]
format: String,
#[arg(short = 'o', long)]
output: Option<std::path::PathBuf>,
revision: String,
path: Option<String>,
},
Show {
#[arg(short = 's', long = "no-patch")]
no_patch: bool,
@@ -1501,6 +1510,14 @@ pub fn run(cli: Cli) -> Result<i32> {
)?;
}
},
Command::Archive {
format: _,
output,
revision,
path,
} => {
archive::archive(&repository, &revision, path.as_deref(), output.as_deref())?;
}
Command::CatFile {
kind,
pretty: _,