973 lines · 42.8 KB · TypeScript
RawDownload
// Nightly: requires a non-core registry command./** * Integration tests for git command. * * Verifies init, add, commit, branch, checkout (with DWIM), plus local and * smart-HTTP remote clone via kernel.exec() with real WASM binaries. */ import { describe, it, expect, afterEach, beforeAll, afterAll, vi } from 'vitest';import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';import { resolve, join } from 'node:path';import { tmpdir } from 'node:os';import { createServer as createHttpsServer, type Server as HttpsServer } from 'node:https';import { spawn, spawnSync, execSync } from 'node:child_process';import { createHash } from 'node:crypto';import { createWasmVmRuntime } from '@rivet-dev/agentos-test-harness';import {  allowAll,  COMMANDS_DIR,  createInMemoryFileSystem,  createKernel,  describeIf,  hasWasmBinaries,} from '@rivet-dev/agentos-test-harness';import type { Kernel } from '@rivet-dev/agentos-test-harness'; vi.setConfig({ testTimeout: 30_000 }); /** Check git binary exists in addition to base WASM binaries */const hasGit = hasWasmBinaries && existsSync(resolve(COMMANDS_DIR, 'git'));const hasHostGit = spawnSync('git', ['--version'], { stdio: 'ignore' }).status === 0;// Smart HTTP needs Git's libcurl-backed remote helper. It is now a real second// WASM binary (git-remote-http links the overlaid mbedTLS libcurl in-process);// git-remote-https aliases to it.const hasGitHttpHelper =  hasGit && existsSync(resolve(COMMANDS_DIR, 'git-remote-http'));// The real OpenSSH client (software/ssh) lights up git-over-ssh; its presence// changes how ssh:// remotes fail when unreachable.const hasSshClient = existsSync(resolve(COMMANDS_DIR, 'ssh')); const gitConfig = [  '-c safe.directory=*',  '-c init.defaultBranch=main',  '-c user.name=agentos',  '-c user.email=agentos@example.invalid',].join(' '); function git(args: string) {  return `git ${gitConfig} ${args}`;} /** Create a kernel with a world-writable in-memory filesystem */async function createGitKernel() {  const vfs = createInMemoryFileSystem();  // Make root and /tmp writable by all users (WASM processes run as non-root)  await (vfs as any).chmod('/', 0o1777);  await vfs.mkdir('/tmp', { recursive: true });  await (vfs as any).chmod('/tmp', 0o1777);  const kernel = createKernel({ filesystem: vfs, syncFilesystemOnDispose: false });  await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }));  return { kernel, vfs, dispose: () => kernel.dispose() };} async function createGitKernelWithNet(loopbackExemptPorts: number[], seededCaPem?: string) {  const vfs = createInMemoryFileSystem();  await (vfs as any).chmod('/', 0o1777);  await vfs.mkdir('/tmp', { recursive: true });  await (vfs as any).chmod('/tmp', 0o1777);  const kernel = createKernel({    filesystem: vfs,    permissions: allowAll,    loopbackExemptPorts,    syncFilesystemOnDispose: false,  });  await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }));  // Seed the Debian-shaped trust store the way the native VM bootstrap does, so  // libcurl's compile-time default CA bundle (/etc/ssl/certs/ca-certificates.crt)  // resolves in-guest for git-remote-http's mbedTLS backend.  if (seededCaPem) {    await vfs.mkdir('/etc/ssl/certs', { recursive: true });    await kernel.writeFile('/etc/ssl/certs/ca-certificates.crt', seededCaPem);  }  return { kernel, vfs, dispose: () => kernel.dispose() };} // Build a real CA and a leaf server certificate signed by it, with a SAN that// covers the 127.0.0.1 loopback endpoint the VM connects to. This lets// git-remote-http's mbedTLS backend perform genuine chain + hostname// verification, exactly like Linux git against a private CA.function makeCaSignedCert(caCommonName: string): {  caPem: string;  serverKey: string;  serverCert: string;} {  const dir = mkdtempSync(join(tmpdir(), 'git-ca-'));  try {    execSync(`openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "${dir}/ca.key" 2>/dev/null`);    execSync(      `openssl req -x509 -new -key "${dir}/ca.key" -days 3650 -subj "/CN=${caCommonName}" -out "${dir}/ca.crt" 2>/dev/null`,    );    execSync(`openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "${dir}/srv.key" 2>/dev/null`);    execSync(`openssl req -new -key "${dir}/srv.key" -subj "/CN=localhost" -out "${dir}/srv.csr" 2>/dev/null`);    writeFileSync(`${dir}/ext.cnf`, 'subjectAltName=DNS:localhost,IP:127.0.0.1\n');    execSync(      `openssl x509 -req -in "${dir}/srv.csr" -CA "${dir}/ca.crt" -CAkey "${dir}/ca.key" ` +      `-CAcreateserial -days 3650 -extfile "${dir}/ext.cnf" -out "${dir}/srv.crt" 2>/dev/null`,    );    return {      caPem: readFileSync(`${dir}/ca.crt`, 'utf8'),      serverKey: readFileSync(`${dir}/srv.key`, 'utf8'),      serverCert: readFileSync(`${dir}/srv.crt`, 'utf8'),    };  } finally {    rmSync(dir, { recursive: true, force: true });  }} function runHostGit(args: string[], cwd?: string) {  const result = spawnSync('git', args, {    cwd,    encoding: 'utf8',  });  if (result.status !== 0) {    throw new Error(      `host git failed: git ${args.join(' ')}\nstdout: ${result.stdout}\nstderr: ${result.stderr}`,    );  }} async function runHostGitResult(args: string[], cwd?: string) {  return await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolveResult, reject) => {    const child = spawn('git', args, { cwd });    const stdout: Buffer[] = [];    const stderr: Buffer[] = [];    child.stdout.on('data', (chunk) => stdout.push(Buffer.from(chunk)));    child.stderr.on('data', (chunk) => stderr.push(Buffer.from(chunk)));    child.on('error', reject);    child.on('close', (status) => {      resolveResult({        status,        stdout: Buffer.concat(stdout).toString('utf8'),        stderr: Buffer.concat(stderr).toString('utf8'),      });    });  });} /** Helper: run command and assert success */async function run(kernel: Kernel, cmd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {  const r = await kernel.exec(cmd);  if (r.exitCode !== 0) {    throw new Error(`Command failed (exit ${r.exitCode}): ${cmd}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`);  }  return r;} async function expectGitRef(kernel: Kernel, repo: string, ref: string) {  const result = await run(kernel, git(`-C ${repo} rev-parse --verify ${ref}`));  expect(result.stdout.trim()).toMatch(/^[0-9a-f]{40,64}$/);} function sidebandPacket(band: 1 | 2 | 3, payload: Uint8Array): Uint8Array {  const packetLength = payload.length + 5;  const header = new TextEncoder().encode(packetLength.toString(16).padStart(4, '0'));  const packet = new Uint8Array(packetLength);  packet.set(header, 0);  packet[4] = band;  packet.set(payload, 5);  return packet;} function concatBytes(chunks: Uint8Array[]): Uint8Array {  const output = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));  let offset = 0;  for (const chunk of chunks) {    output.set(chunk, offset);    offset += chunk.length;  }  return output;} // TODO(P6): requires git WASM artifact, intentionally excluded from the fast software-build gate.describeIf(hasGit, 'git command', () => {  let kernel: Kernel;  let vfs: any;  let dispose: () => Promise<void>;   afterEach(async () => {    await dispose?.();  });   it('init creates .git directory structure', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     const result = await run(kernel, git('init /repo'));    expect(result.stdout).toContain('Initialized empty Git repository');     expect(await vfs.exists('/repo/.git/HEAD')).toBe(true);    expect(await vfs.exists('/repo/.git/objects')).toBe(true);    expect(await vfs.exists('/repo/.git/refs/heads')).toBe(true);     const head = new TextDecoder().decode(await vfs.readFile('/repo/.git/HEAD'));    expect(head.trim()).toBe('ref: refs/heads/main');  });   it('add + commit creates objects and updates ref', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /repo'));    await kernel.writeFile('/repo/hello.txt', 'hello world\n');    await run(kernel, git('-C /repo add hello.txt'));    await run(kernel, git("-C /repo commit -m 'first commit'"));     expect(await vfs.exists('/repo/.git/refs/heads/main')).toBe(true);  });   it('hidden sideband helper repeatedly streams band 1 and forwards progress without spooling', async () => {    ({ kernel, dispose } = await createGitKernel());     const encoder = new TextEncoder();    const payloadChunks = Array.from({ length: 128 }, (_, index) =>      encoder.encode(`pack-${index.toString().padStart(3, '0')}:${'x'.repeat(4096)}\n`));    const stream: Uint8Array[] = [];    for (const [index, payload] of payloadChunks.entries()) {      stream.push(sidebandPacket(1, payload));      if (index % 16 === 0)        stream.push(sidebandPacket(2, encoder.encode(`progress ${index}\n`)));    }    stream.push(encoder.encode('0000'));     const result = await kernel.exec('git sideband--helper demux parity-test 1', {      stdin: concatBytes(stream),    });    expect(result.exitCode, result.stderr).toBe(0);    expect(result.stdout).toBe(new TextDecoder().decode(concatBytes(payloadChunks)));    expect(result.stderr).toContain('progress 0');    expect(result.stderr).toContain('progress 112');     const coloredProgress = concatBytes([      sidebandPacket(2, encoder.encode('\x1b[31mred\x1b[0m\n')),      encoder.encode('0000'),    ]);    const sanitized = await kernel.exec('git sideband--helper demux parity-test 0', {      stdin: coloredProgress,    });    expect(sanitized.exitCode, sanitized.stderr).toBe(0);    expect(sanitized.stderr).toContain('^[[31mred^[[0m');     const ansiAllowed = await kernel.exec('git sideband--helper demux parity-test 1', {      stdin: coloredProgress,    });    expect(ansiAllowed.exitCode, ansiAllowed.stderr).toBe(0);    expect(ansiAllowed.stderr).toContain('\x1b[31mred\x1b[0m');     const invalidPolicy = await kernel.exec('git sideband--helper demux parity-test 16', {      stdin: coloredProgress,    });    expect(invalidPolicy.exitCode).not.toBe(0);    expect(invalidPolicy.stderr).toContain('invalid sideband control-character policy');     const muxed = await kernel.exec('git sideband--helper mux 65520 0 5', {      stdin: 'receive progress\n',    });    expect(muxed.exitCode, muxed.stderr).toBe(0);    expect(muxed.stdout).toMatch(/^[0-9a-f]{4}\u0002receive progress\n$/);     const help = await kernel.exec('git help -a');    expect(help.stdout).not.toContain('sideband--helper');     const patchText = readFileSync(      resolve(import.meta.dirname, '../../../toolchain/c/patches/git/0002-wasi-synchronous-sideband-demux.patch'),      'utf8',    );    const addedImplementation = patchText      .split('\n')      .filter((line) => line.startsWith('+') && !line.startsWith('+++'))      .join('\n');    expect(addedImplementation).not.toContain('tmp_sideband_');    expect(addedImplementation).not.toMatch(/odb_mkstemp|spool_fd|spool_tempfile/);  });   it('branch lists branches with current marked', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /repo'));    await kernel.writeFile('/repo/file.txt', 'content\n');    await run(kernel, git('-C /repo add file.txt'));    await run(kernel, git("-C /repo commit -m 'init'"));     const result = await run(kernel, git('-C /repo branch'));    expect(result.stdout.trim()).toBe('* main');  });   it('checkout -b creates a new branch', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /repo'));    await kernel.writeFile('/repo/file.txt', 'content\n');    await run(kernel, git('-C /repo add file.txt'));    await run(kernel, git("-C /repo commit -m 'init'"));     await run(kernel, git('-C /repo checkout -b feature'));     const result = await run(kernel, git('-C /repo branch'));    const lines = result.stdout.trim().split('\n').map((l: string) => l.trim());    expect(lines).toContain('* feature');    expect(lines).toContain('main');  });   it('full quickstart scenario: init, commit, branch, clone, checkout', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     // Create origin repo    await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', '# demo repo\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'initial commit'"));     // Check default branch    let r = await run(kernel, git('-C /tmp/origin branch'));    expect(r.stdout.trim()).toBe('* main');     // Create feature branch with a new file    await run(kernel, git('-C /tmp/origin checkout -b feature'));    await kernel.writeFile('/tmp/origin/feature.txt', 'checked out from feature\n');    await run(kernel, git('-C /tmp/origin add feature.txt'));    await run(kernel, git("-C /tmp/origin commit -m 'add feature file'"));     // Switch back to main    await run(kernel, git('-C /tmp/origin checkout main'));     // Clone    await run(kernel, git('clone /tmp/origin /tmp/clone'));     // Clone should only show main branch initially    r = await run(kernel, git('-C /tmp/clone branch'));    expect(r.stdout.trim()).toBe('* main');     // Checkout feature (DWIM from remote tracking)    await run(kernel, git('-C /tmp/clone checkout feature'));     // Now both branches should be listed    r = await run(kernel, git('-C /tmp/clone branch'));    const branches = r.stdout.trim().split('\n').map((l: string) => l.trim());    expect(branches).toContain('* feature');    expect(branches).toContain('main');     // Verify feature file exists in clone    const featureContent = new TextDecoder().decode(await vfs.readFile('/tmp/clone/feature.txt'));    expect(featureContent).toBe('checked out from feature\n');     // Verify README exists too    const readmeContent = new TextDecoder().decode(await vfs.readFile('/tmp/clone/README.md'));    expect(readmeContent).toBe('# demo repo\n');  });   it('clone without an explicit destination uses the source basename', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', 'default destination\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'seed'"));     await run(kernel, 'mkdir -p /work');    await run(kernel, git('-C /work clone /tmp/origin'));     expect(await vfs.exists('/work/origin/.git/HEAD')).toBe(true);    const readmeContent = new TextDecoder().decode(await vfs.readFile('/work/origin/README.md'));    expect(readmeContent).toBe('default destination\n');  });   it('clone without an explicit destination strips a trailing .git suffix', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin.git'));    await kernel.writeFile('/tmp/origin.git/README.md', 'suffix destination\n');    await run(kernel, git('-C /tmp/origin.git add README.md'));    await run(kernel, git("-C /tmp/origin.git commit -m 'seed'"));     await run(kernel, 'mkdir -p /work');    await run(kernel, git('-C /work clone /tmp/origin.git'));     expect(await vfs.exists('/work/origin/.git/HEAD')).toBe(true);    const readmeContent = new TextDecoder().decode(await vfs.readFile('/work/origin/README.md'));    expect(readmeContent).toBe('suffix destination\n');  });   it('clone into an existing empty destination directory succeeds', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', 'empty destination\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'seed'"));     await run(kernel, 'mkdir -p /tmp/clone');    await run(kernel, git('clone /tmp/origin /tmp/clone'));     expect(await vfs.exists('/tmp/clone/.git/HEAD')).toBe(true);    const readmeContent = new TextDecoder().decode(await vfs.readFile('/tmp/clone/README.md'));    expect(readmeContent).toBe('empty destination\n');  });   it('clone rejects a non-empty destination directory', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', 'origin\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'seed'"));     await run(kernel, 'mkdir -p /tmp/clone');    await kernel.writeFile('/tmp/clone/existing.txt', 'keep me\n');     const result = await kernel.exec(git('clone /tmp/origin /tmp/clone'));    expect(result.exitCode).not.toBe(0);    expect(result.stderr).toMatch(/already exists|not an empty directory|destination/i);     const existing = new TextDecoder().decode(await vfs.readFile('/tmp/clone/existing.txt'));    expect(existing).toBe('keep me\n');    expect(await vfs.exists('/tmp/clone/.git')).toBe(false);  });   it('clone of a missing repository fails without leaving a partial destination', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     const result = await kernel.exec(git('clone /tmp/missing /tmp/clone'));    expect(result.exitCode).not.toBe(0);    expect(result.stderr).toMatch(/not a git repository|missing|no such file|fatal/i);    expect(await vfs.exists('/tmp/clone')).toBe(false);  });   it('clone of an empty repository succeeds and leaves an empty worktree', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await run(kernel, git('clone /tmp/origin /tmp/clone'));     const head = new TextDecoder().decode(await vfs.readFile('/tmp/clone/.git/HEAD'));    expect(head.trim()).toBe('ref: refs/heads/main');    expect(await vfs.exists('/tmp/clone/.git/config')).toBe(true);    expect(await vfs.exists('/tmp/clone/.git/refs/heads/main')).toBe(false);    expect(await vfs.exists('/tmp/clone/README.md')).toBe(false);  });   it('clone preserves nested directory trees', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await run(kernel, 'mkdir -p /tmp/origin/src/nested');    await kernel.writeFile('/tmp/origin/src/nested/file.txt', 'nested payload\n');    await kernel.writeFile('/tmp/origin/src/root.txt', 'root payload\n');    await run(kernel, git('-C /tmp/origin add src/nested/file.txt src/root.txt'));    await run(kernel, git("-C /tmp/origin commit -m 'nested tree'"));     await run(kernel, git('clone /tmp/origin /tmp/clone'));     const nested = new TextDecoder().decode(await vfs.readFile('/tmp/clone/src/nested/file.txt'));    const root = new TextDecoder().decode(await vfs.readFile('/tmp/clone/src/root.txt'));    expect(nested).toBe('nested payload\n');    expect(root).toBe('root payload\n');  });   it('clone honors the source default branch when HEAD is not main', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', 'main branch\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'main'"));     await run(kernel, git('-C /tmp/origin checkout -b trunk'));    await kernel.writeFile('/tmp/origin/trunk.txt', 'trunk branch\n');    await run(kernel, git('-C /tmp/origin add trunk.txt'));    await run(kernel, git("-C /tmp/origin commit -m 'trunk'"));     await run(kernel, git('clone /tmp/origin /tmp/clone'));     const head = new TextDecoder().decode(await vfs.readFile('/tmp/clone/.git/HEAD'));    expect(head.trim()).toBe('ref: refs/heads/trunk');    expect(await vfs.exists('/tmp/clone/.git/refs/heads/trunk')).toBe(true);    const trunk = new TextDecoder().decode(await vfs.readFile('/tmp/clone/trunk.txt'));    expect(trunk).toBe('trunk branch\n');  });   it('clone copies nested branch refs and checkout DWIM works for branch names with slashes', async () => {    ({ kernel, vfs, dispose } = await createGitKernel());     await run(kernel, git('init /tmp/origin'));    await kernel.writeFile('/tmp/origin/README.md', '# demo repo\n');    await run(kernel, git('-C /tmp/origin add README.md'));    await run(kernel, git("-C /tmp/origin commit -m 'initial commit'"));     await run(kernel, git('-C /tmp/origin checkout -b feature/deep'));    await kernel.writeFile('/tmp/origin/feature.txt', 'nested branch payload\n');    await run(kernel, git('-C /tmp/origin add feature.txt'));    await run(kernel, git("-C /tmp/origin commit -m 'nested branch'"));    await run(kernel, git('-C /tmp/origin checkout main')); 

Lines 1–500 of 973.