Artifact 44248444b07145496b44fc1ba0c39637b64c486f79ef195f3db42e717a2cc60e:
- File
src/git_import_cli_test.zig
— part of check-in
[dc0aa2f858]
at
2026-07-31 02:56:07
on branch sub-manifest
— git-import: translate a Git fast-export stream into a zit history
`zit import-git SOURCE DEST` reads a Git repository through `git fast- export` and builds a zit repository from it. The destination appears only after the export, artifact creation and publication have all completed, so a failure anywhere leaves no half-published repository behind; this is the first caller of the logstore artifact batch added earlier on this branch, which is what makes a check-in's blobs arrive as a unit.
Mode 160000 now parses as a gitlink instead of error.UnsupportedMode. That completes the split begun in the previous check-in: reporting what the stream said is the parser's job, and deciding that zit has no submodule concept is the importer's. The refusal still happens, but it now explains itself instead of failing as a parse error on input git legitimately emits.
Signed commits are refused by default rather than imported with the signature silently dropped. Fossil has no signature card, so importing one is a loss the operator should choose; --drop-signatures accepts it explicitly.
The sub-manifest parameter is analyzed, not guessed. Preflight measures the source's own name set and pins its measured optimum, and --submanifest-k overrides that only to match another repository's existing pin, which is what lets two repositories share sub-manifests by content address. On a 100,000 file corpus the analyzed value cuts the objective by roughly 3.4x against the fixed default it replaces.
Correctness is checked differentially against real Git rather than against fixtures we wrote: every materialized tree is compared to `git ls-tree` for the corresponding commit, refs and annotated tags are compared to the source, and both comparators now assert that a gitlink is absent rather than merely tolerating it, so the omission is a contract instead of an accident.
The rest is wiring. extras learns the import provenance names, root.zig exports the importer modules across the executable and library boundary, tidy and the CI step guard learn the new test roots and build steps, and the e2e help check learns that a verb spelled import_git in Zig is import-git on the command line. (user: dmestas, size: 53869) [annotate] [blame] [check-ins using]
//! RED coverage for Task 10's transactional `zit import-git` command. //! //! This file deliberately names the smallest testable production seam that does //! not execute a shell command or contact a remote: //! //! `git_import_cli.run(gpa, io, request, streams, dependencies) -> cli.Exit` //! //! `dependencies` supplies a `ChildFactory`/`ChildSession` and filesystem hooks. //! A `ChildSession` must expose its stdout reader plus `terminate`, `drainStderr`, //! and `reap`; each lifecycle operation is observable in the supplied fake. The //! real CLI dispatch will construct OS-backed dependencies. The filesystem hook //! is only needed to deterministically fail publication flush/close/sync/rename //! boundaries; ordinary scenarios exercise a real `std.testing.tmpDir`. //! //! The opt-in suite intentionally remains RED until Task 10 provides command //! preflight, real-pipe lifecycle, and publication-boundary fault seams. It is //! not part of `zig build test`. const std = @import("std"); const builtin = @import("builtin"); const cli = @import("cli.zig"); const fast_export = @import("zit").git_fast_export; const import_cli = @import("git_import_cli.zig"); const checkout = @import("zit").checkout; const helper_options = @import("git_import_cli_options"); const valid_export = "feature done\n" ++ "blob\n" ++ "mark :1\n" ++ "data 5\n" ++ "hello\n" ++ "commit refs/heads/main\n" ++ "mark :2\n" ++ "author A <[email protected]> 0 +0000\n" ++ "committer A <[email protected]> 0 +0000\n" ++ "data 4\n" ++ "init\n" ++ "M 100644 :1 README\n" ++ "\n" ++ "done\n"; const valid_export_with_remote_ref = "feature done\n" ++ "blob\n" ++ "mark :1\n" ++ "data 5\n" ++ "hello\n" ++ "commit refs/heads/main\n" ++ "mark :2\n" ++ "author A <[email protected]> 0 +0000\n" ++ "committer A <[email protected]> 0 +0000\n" ++ "data 4\n" ++ "init\n" ++ "M 100644 :1 README\n" ++ "\n" ++ "reset refs/remotes/origin/main\n" ++ "from :2\n" ++ "\n" ++ "done\n"; const valid_export_with_oids = "feature done\n" ++ "blob\n" ++ "mark :1\n" ++ "original-oid 100b93820ade4c16225673b4ca62bb3ade63c313\n" ++ "data 5\n" ++ "hello\n" ++ "commit refs/heads/main\n" ++ "mark :2\n" ++ "original-oid ed9b38a10070b504cecfacd892507af78fcb24a4\n" ++ "author A <[email protected]> 0 +0000\n" ++ "committer A <[email protected]> 0 +0000\n" ++ "data 4\n" ++ "init\n" ++ "M 100644 :1 README\n" ++ "\n" ++ "done\n"; const malformed_export = "feature done\n" ++ "commit refs/heads/main\n" ++ "mark :not-a-number\n"; const marked_tag_export = "feature done\n" ++ "blob\n" ++ "mark :1\n" ++ "data 5\n" ++ "hello\n" ++ "commit refs/heads/main\n" ++ "mark :2\n" ++ "author A <[email protected]> 0 +0000\n" ++ "committer A <[email protected]> 0 +0000\n" ++ "data 4\n" ++ "init\n" ++ "M 100644 :1 README.md\n" ++ "\n" ++ "tag v1\n" ++ "mark :3\n" ++ "from :2\n" ++ "tagger A <[email protected]> 0 +0000\n" ++ "data 0\n" ++ "\n" ++ "done\n"; const Result = struct { code: cli.Exit, out: []const u8, err: []const u8, lifecycle: import_cli.ChildLifecycle, }; /// The wished test adapter. Task 10's production implementation owns no fake /// process: it consumes this narrow interface, while the CLI passes the /// OS-backed implementation and this file passes a scripted one. const LifecycleFault = enum { terminate, drain_stderr, reap }; const ScriptedGit = struct { fast_export: []const u8, export_exit: u8 = 0, stderr: []const u8 = "", launched: usize = 0, terminated: usize = 0, stderr_drained: usize = 0, reaped: usize = 0, lifecycle_fault: ?LifecycleFault = null, limits: import_cli.ImportLimits = .{}, fn dependencies(self: *ScriptedGit, publication: import_cli.PublicationHooks) import_cli.Dependencies { return .{ .git = .{ .context = self, .vtable = &.{ .start_export = startExport, .terminate = terminate, .drain_stderr = drainStderr, .reap = reap, }, }, .publication = publication, .limits = self.limits, }; } fn startExport(context: *anyopaque, _: []const u8) !import_cli.ExportPipes { const self: *ScriptedGit = @ptrCast(@alignCast(context)); self.launched += 1; return .{ .stdout = .fixed(self.fast_export), .stderr = .fixed(self.stderr), .exit_code = self.export_exit, }; } fn terminate(context: *anyopaque) !void { const self: *ScriptedGit = @ptrCast(@alignCast(context)); self.terminated += 1; if (self.lifecycle_fault == .terminate) return error.InjectedLifecycleFailure; } fn drainStderr(context: *anyopaque) !void { const self: *ScriptedGit = @ptrCast(@alignCast(context)); self.stderr_drained += 1; if (self.lifecycle_fault == .drain_stderr) return error.InjectedLifecycleFailure; } fn reap(context: *anyopaque) !void { const self: *ScriptedGit = @ptrCast(@alignCast(context)); self.reaped += 1; if (self.lifecycle_fault == .reap) return error.InjectedLifecycleFailure; } }; fn runFixture( tmp: std.testing.TmpDir, source: []const u8, destination: []const u8, quiet: bool, git: *ScriptedGit, publication: import_cli.PublicationHooks, ) Result { const Capture = struct { var out_storage: [4096]u8 = undefined; var err_storage: [4096]u8 = undefined; }; var out = std.Io.Writer.fixed(&Capture.out_storage); var err = std.Io.Writer.fixed(&Capture.err_storage); const streams: cli.Streams = .{ .out = &out, .err = &err }; const code = import_cli.run( std.testing.allocator, std.testing.io, .{ .source = source, .destination_parent = tmp.dir, .destination_name = destination, .submanifest_k = 5, .attribution = .committer, .default_branch = null, .quiet = quiet, }, streams, git.dependencies(publication), ); return .{ .code = code, .out = out.buffered(), .err = err.buffered(), .lifecycle = .{ .launched = git.launched, .terminated = git.terminated, .stderr_drained = git.stderr_drained, .reaped = git.reaped, }, }; } fn retainedBytesForExport(bytes: []const u8) !u64 { var reader = fast_export.Reader.init(std.testing.allocator, .fixed(bytes), .{ .max_line = 1024 * 1024, .max_data = 1024 * 1024 * 1024, .max_path = 4096, }); defer reader.deinit(); var retained_bytes: u64 = 0; while (try reader.next()) |lease| { const charge = blk: { var event = lease; defer event.deinit(); break :blk try import_cli.retainedByteCharge(event.value); }; retained_bytes = try std.math.add(u64, retained_bytes, charge); } return retained_bytes; } fn exists(io: std.Io, dir: std.Io.Dir, path: []const u8) bool { var file = dir.openFile(io, path, .{}) catch return false; file.close(io); return true; } fn directoryExists(io: std.Io, dir: std.Io.Dir, path: []const u8) bool { var child = dir.openDir(io, path, .{}) catch return false; child.close(io); return true; } fn expectNoPublication(io: std.Io, tmp: std.testing.TmpDir, destination: []const u8) !void { try std.testing.expect(!directoryExists(io, tmp.dir, destination)); try std.testing.expect(!exists(io, tmp.dir, ".zit-import-map.jsonl")); var entries = tmp.dir.iterate(); while (try entries.next(io)) |entry| { try std.testing.expect(!std.mem.startsWith(u8, entry.name, ".zit-import-tmp-")); } } test "import-git publishes .zit refs and map together only after a complete export" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "Enumerating objects\n" }; const result = runFixture( tmp, "fixture.git", "imported", false, &git, .real_filesystem, ); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expectEqualStrings("imported\n", result.out); try std.testing.expect(std.mem.indexOf(u8, result.err, "import-git:") != null); var destination = try tmp.dir.openDir(std.testing.io, "imported", .{}); defer destination.close(std.testing.io); try std.testing.expect(exists(std.testing.io, destination, ".zit")); try std.testing.expect(directoryExists(std.testing.io, destination, "refs")); try std.testing.expect(exists(std.testing.io, destination, ".zit-import-map.jsonl")); var checkout_buf: [checkout.max_ck_payload + 1]u8 = undefined; const imported_checkout = (try checkout.read(std.testing.io, destination, &checkout_buf)).?; try std.testing.expectEqualStrings("", imported_checkout.version); try std.testing.expectEqualStrings("main", imported_checkout.branch); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); } test "import-git finalizes remote refs emitted with all refs" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = valid_export_with_remote_ref }; const result = runFixture(tmp, "fixture.git", "with-remote", false, &git, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, result.code); var destination = try tmp.dir.openDir(std.testing.io, "with-remote", .{}); defer destination.close(std.testing.io); try std.testing.expect(exists(std.testing.io, destination, "refs/sym-heads/main")); try std.testing.expect(exists(std.testing.io, destination, "refs/sym-refs/remotes/origin/main")); } test "import-git refuses an existing destination before Git starts" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.createDir(std.testing.io, "already-there", .default_dir); var git: ScriptedGit = .{ .fast_export = valid_export }; const result = runFixture(tmp, "fixture.git", "already-there", false, &git, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqualStrings("", result.out); try std.testing.expect(std.mem.indexOf(u8, result.err, "already exists") != null); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.launched); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.reaped); } test "import-git aborts malformed export by terminating draining and reaping once" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = malformed_export, .stderr = "fatal fixture diagnostic\n" }; const result = runFixture(tmp, "fixture.git", "broken", false, &git, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqualStrings("", result.out); try std.testing.expect(std.mem.indexOf(u8, result.err, "fatal fixture diagnostic") != null); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "broken"); } test "import-git removes all temporary output when storage creation fails" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = valid_export }; const result = runFixture( tmp, "fixture.git", "storage-failed", false, &git, .{ .fail_at = .create_store }, ); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqualStrings("", result.out); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "storage-failed"); } test "import-git removes all temporary output when publication flush or rename fails" { const failures = [_]import_cli.PublicationHooks.FailurePoint{ .flush_store, .sync_map, .rename_no_replace }; for (failures) |failure| { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = valid_export }; const result = runFixture( tmp, "fixture.git", "publication-failed", false, &git, .{ .fail_at = failure }, ); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqualStrings("", result.out); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "publication-failed"); } } test "import-git writes only a machine-readable destination to stdout and progress to stderr" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "git progress\n" }; const result = runFixture(tmp, "fixture.git", "machine", false, &git, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expectEqualStrings("machine\n", result.out); try std.testing.expect(std.mem.indexOf(u8, result.err, "git progress") != null); try std.testing.expect(std.mem.indexOf(u8, result.out, "progress") == null); } test "import-git --quiet suppresses progress but not diagnostics" { var quiet_tmp = std.testing.tmpDir(.{}); defer quiet_tmp.cleanup(); var quiet_git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "git progress\n" }; const quiet = runFixture(quiet_tmp, "fixture.git", "quiet", true, &quiet_git, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, quiet.code); try std.testing.expectEqualStrings("quiet\n", quiet.out); try std.testing.expectEqualStrings("", quiet.err); var error_tmp = std.testing.tmpDir(.{}); defer error_tmp.cleanup(); var error_git: ScriptedGit = .{ .fast_export = malformed_export, .stderr = "fatal fixture diagnostic\n" }; const failed = runFixture(error_tmp, "fixture.git", "quiet-error", true, &error_git, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, failed.code); try std.testing.expectEqualStrings("", failed.out); try std.testing.expect(std.mem.indexOf(u8, failed.err, "fatal fixture diagnostic") != null); } // The remaining tests specify the Task 10 seam not yet present in // `git_import_cli`: CommandScript records every preflight/export invocation, // supplies bounded stdout/stderr streams, and exposes deterministic // cancellation and publication-boundary faults. Production dispatch constructs // the OS adapter; tests use no shell or network. fn scripted( tmp: std.testing.TmpDir, script: *import_cli.CommandScript, destination: []const u8, quiet: bool, fault: import_cli.PublicationHooks, ) import_cli.ScriptResult { return import_cli.runScripted( std.testing.allocator, std.testing.io, .{ .source = "fixture.git", .destination_parent = tmp.dir, .destination_name = destination, .submanifest_k = 5, .attribution = .committer, .default_branch = null, .quiet = quiet, }, script, fault, ); } const ProcessResult = struct { code: cli.Exit, out: []const u8, err: []const u8, exit_code: u8, reaped: bool, reap_calls: usize, child_gone: bool, stderr_bytes_drained: usize, retained_diagnostic_len: usize, }; /// Drives the real ProductionGit session against the build-wired helper. The /// only substitution is argv; spawning, pipe adapters, concurrent diagnostic /// drain, terminate, join, and wait are the production implementation. fn runProductionPipe( tmp: std.testing.TmpDir, destination: []const u8, mode: []const u8, ) !ProcessResult { var threaded = std.Io.Threaded.init(std.testing.allocator, .{}); defer threaded.deinit(); const io = threaded.io(); var session = import_cli.ProductionGit.initForTest( std.testing.allocator, io, &.{ helper_options.git_import_pipe_helper, mode }, ); const Capture = struct { var out_storage: [4096]u8 = undefined; var err_storage: [4096]u8 = undefined; }; var out = std.Io.Writer.fixed(&Capture.out_storage); var err = std.Io.Writer.fixed(&Capture.err_storage); const code = import_cli.run( std.testing.allocator, io, .{ .source = "fixture.git", .destination_parent = tmp.dir, .destination_name = destination, }, .{ .out = &out, .err = &err }, session.dependencies(.real_filesystem), ); return .{ .code = code, .out = out.buffered(), .err = err.buffered(), .exit_code = session.exit_code, .reaped = session.reaped, .child_gone = session.child == null, .stderr_bytes_drained = session.stderr_bytes_drained, .reap_calls = session.reap_calls, .retained_diagnostic_len = session.stderr_ring_len, }; } fn runGit(io: std.Io, argv: []const []const u8) !void { const result = try std.process.run(std.testing.allocator, io, .{ .argv = argv }); defer std.testing.allocator.free(result.stdout); defer std.testing.allocator.free(result.stderr); switch (result.term) { .exited => |code| if (code == 0) return, else => {}, } return error.GitSetupFailed; } fn writeFixtureFile(io: std.Io, dir: std.Io.Dir, path: []const u8, bytes: []const u8) !void { var file = try dir.createFile(io, path, .{ .read = true, .truncate = true }); defer file.close(io); try file.writeStreamingAll(io, bytes); } fn runProductionPreflight( tmp: std.testing.TmpDir, source: []const u8, destination: []const u8, ) cli.Exit { const Capture = struct { var out_storage: [1024]u8 = undefined; var err_storage: [1024]u8 = undefined; }; var out = std.Io.Writer.fixed(&Capture.out_storage); var err = std.Io.Writer.fixed(&Capture.err_storage); var session = import_cli.ProductionGit.init(std.testing.allocator, std.testing.io); defer session.deinit(); return import_cli.run( std.testing.allocator, std.testing.io, .{ .source = source, .destination_parent = tmp.dir, .destination_name = destination, }, .{ .out = &out, .err = &err }, session.dependencies(.real_filesystem), ); } test "import-git parser leaves k unset for analysis and is a real dispatched verb" { // Absent means "analyze", not "5": the parser must not manufacture a pin // the runner would then be unable to distinguish from an operator's. const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" }); try std.testing.expectEqual(@as(?u4, null), defaulted.submanifest_k); try std.testing.expectEqual(cli.Verb.import_git, cli.verbOf("import-git")); try std.testing.expect(!cli.isPlanned("import-git")); const parsed = try cli.parseImportGitArgs(&.{ "--submanifest-k", "7", "--attribution", "author", "--default-branch", "main", "--quiet", "fixture.git", "dest", }); try std.testing.expectEqual(@as(?u4, 7), parsed.submanifest_k); try std.testing.expectEqual(.author, parsed.attribution); try std.testing.expectEqualStrings("main", parsed.default_branch.?); try std.testing.expect(parsed.quiet); const min_k = try cli.parseImportGitArgs(&.{ "--submanifest-k", "1", "fixture.git", "dest" }); try std.testing.expectEqual(@as(?u4, 1), min_k.submanifest_k); const max_k = try cli.parseImportGitArgs(&.{ "--submanifest-k", "12", "fixture.git", "dest" }); try std.testing.expectEqual(@as(?u4, 12), max_k.submanifest_k); for ([_][]const u8{ "0", "13", "14", "15" }) |value| { try std.testing.expectError( cli.ImportGitParseError.InvalidSubmanifestK, cli.parseImportGitArgs(&.{ "--submanifest-k", value, "fixture.git", "dest" }), ); } } test "import-git parser exposes the export payload budget as an operator flag" { // Every ceiling an honest source can reach must be raisable without a // recompile. This one budget carries both the cumulative payload limit and // the derived per-commit file-command ceiling, so it is the only knob the // verb needs. const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" }); try std.testing.expectEqual(@as(?u64, null), defaulted.max_export_payload_bytes); const raised = try cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "68719476736", "fixture.git", "dest", }); try std.testing.expectEqual(@as(?u64, 68719476736), raised.max_export_payload_bytes); // Zero derives a per-commit ceiling of zero: a budget no import can ever // satisfy is a typo, not a policy. try std.testing.expectError( cli.ImportGitParseError.InvalidExportPayloadBytes, cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "0", "fixture.git", "dest" }), ); try std.testing.expectError( cli.ImportGitParseError.InvalidExportPayloadBytes, cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "many", "fixture.git", "dest" }), ); try std.testing.expectError( cli.ImportGitParseError.MissingOptionValue, cli.parseImportGitArgs(&.{ "fixture.git", "dest", "--max-export-payload-bytes" }), ); try std.testing.expectError( cli.ImportGitParseError.RepeatedOption, cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "1024", "--max-export-payload-bytes", "2048", "fixture.git", "dest", }), ); } /// Whether `token` appears anywhere in `argv`. Positional indices would encode /// the argv's shape, which is not what this asserts. fn argvHas(argv: []const []const u8, token: []const u8) bool { for (argv) |candidate| { if (std.mem.eql(u8, candidate, token)) return true; } return false; } test "import-git strips signatures by default and aborts only under --refuse-signed" { // Fossil has no signature card, so refusing by default withheld the import // without saving anything. The default must therefore strip. const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" }); try std.testing.expect(!defaulted.refuse_signed); const stripping = import_cli.exportArgv("fixture.git", defaulted.refuse_signed); try std.testing.expect(argvHas(&stripping, "--signed-commits=strip")); try std.testing.expect(argvHas(&stripping, "--signed-tags=strip")); try std.testing.expect(!argvHas(&stripping, "--signed-commits=abort")); try std.testing.expect(!argvHas(&stripping, "--signed-tags=abort")); // The opt-out restores the refusal, and nothing else about the argv moves. const opted_out = try cli.parseImportGitArgs(&.{ "--refuse-signed", "fixture.git", "dest" }); try std.testing.expect(opted_out.refuse_signed); const aborting = import_cli.exportArgv("fixture.git", opted_out.refuse_signed); try std.testing.expect(argvHas(&aborting, "--signed-commits=abort")); try std.testing.expect(argvHas(&aborting, "--signed-tags=abort")); try std.testing.expectEqual(stripping.len, aborting.len); for (stripping, aborting) |strip_token, abort_token| { if (std.mem.startsWith(u8, strip_token, "--signed-")) continue; try std.testing.expectEqualStrings(strip_token, abort_token); } // Same error shape as every other boolean flag: repeated is a usage error, // and the retired spelling is simply unknown. try std.testing.expectError( cli.ImportGitParseError.RepeatedOption, cli.parseImportGitArgs(&.{ "--refuse-signed", "--refuse-signed", "fixture.git", "dest" }), ); try std.testing.expectError( cli.ImportGitParseError.UnknownOption, cli.parseImportGitArgs(&.{ "--drop-signatures", "fixture.git", "dest" }), ); } test "import-git help exits zero and malformed argument forms exit usage without process launch" { const help = import_cli.dispatchForTest(&.{"--help"}); try std.testing.expectEqual(cli.Exit.ok, help.code); try std.testing.expectEqualStrings("", help.err); try std.testing.expect(std.mem.indexOf(u8, help.out, "zit import-git SOURCE DEST") != null); const accepted_max = import_cli.dispatchForTest(&.{ "--submanifest-k", "12", "fixture.git", "dest" }); try std.testing.expectEqual(cli.Exit.failure, accepted_max.code); try std.testing.expectEqual(@as(usize, 0), accepted_max.lifecycle.launched); try std.testing.expectEqualStrings("test dispatch requires scripted dependencies\n", accepted_max.err); for ([_][]const []const u8{ &.{ "--submanifest-k", "0", "fixture.git", "dest" }, &.{ "--submanifest-k", "13", "fixture.git", "dest" }, &.{ "--submanifest-k", "14", "fixture.git", "dest" }, &.{ "--submanifest-k", "15", "fixture.git", "dest" }, &.{ "--attribution", "committerish", "fixture.git", "dest" }, &.{"fixture.git"}, &.{ "fixture.git", "dest", "extra" }, }) |args| { const usage = import_cli.dispatchForTest(args); try std.testing.expectEqual(cli.Exit.usage, usage.code); try std.testing.expectEqual(@as(usize, 0), usage.lifecycle.launched); try std.testing.expectEqualStrings("", usage.out); try std.testing.expect(usage.err.len != 0); } } test "import-git rejects missing Git non-repository shallow and partial sources before export" { const preflight_failures = [_]import_cli.CommandScript.Preflight{ .git_missing, .not_repository, .missing_objects, .shallow, .partial_promisor, }; for (preflight_failures) |preflight| { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = preflight }); const result = scripted(tmp, &script, "rejected", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqual(@as(usize, 0), script.export_starts); try std.testing.expect(script.preflight_starts != 0); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "rejected"); } } test "import-git production preflight rejects absent non-repository missing-object shallow and partial sources" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var root_buf: [128]u8 = undefined; const root = try std.fmt.bufPrint(&root_buf, ".zig-cache/tmp/{s}", .{tmp.sub_path}); var source_buf: [160]u8 = undefined; const source = try std.fmt.bufPrint(&source_buf, "{s}/source", .{root}); // A non-existent -C path and an existing non-repository both execute real // ProductionGit query children and fail before an export can launch. try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "missing-git")); try tmp.dir.createDir(std.testing.io, "not-repository", .default_dir); var nonrepo_buf: [160]u8 = undefined; const nonrepo = try std.fmt.bufPrint(&nonrepo_buf, "{s}/not-repository", .{root}); try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, nonrepo, "not-repository-dest")); try expectNoPublication(std.testing.io, tmp, "missing-git"); try expectNoPublication(std.testing.io, tmp, "not-repository-dest"); try runGit(std.testing.io, &.{ "git", "init", source }); try runGit(std.testing.io, &.{ "git", "-C", source, "config", "user.name", "Zit Test" }); try runGit(std.testing.io, &.{ "git", "-C", source, "config", "user.email", "[email protected]" }); try runGit(std.testing.io, &.{ "git", "-C", source, "commit", "--allow-empty", "-m", "init" }); try runGit(std.testing.io, &.{ "git", "-C", source, "config", "extensions.partialClone", "origin" }); try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "partial")); try runGit(std.testing.io, &.{ "git", "-C", source, "config", "--unset", "extensions.partialClone" }); try writeFixtureFile(std.testing.io, tmp.dir, "source/.git/shallow", "0000000000000000000000000000000000000000\n"); try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "shallow")); try tmp.dir.deleteFile(std.testing.io, "source/.git/shallow"); try tmp.dir.deleteTree(std.testing.io, "source/.git/objects"); try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "missing-objects")); try expectNoPublication(std.testing.io, tmp, "partial"); try expectNoPublication(std.testing.io, tmp, "shallow"); try expectNoPublication(std.testing.io, tmp, "missing-objects"); } test "import-git records detached HEAD and refuses it unless default branch is explicit" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .detached_head, .fast_export = valid_export }); const rejected = scripted(tmp, &script, "detached", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, rejected.code); try std.testing.expectEqual(@as(usize, 0), script.export_starts); try expectNoPublication(std.testing.io, tmp, "detached"); script.default_branch = "main"; const accepted = scripted(tmp, &script, "detached-with-branch", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, accepted.code); try std.testing.expect(script.detached_head_recorded); } test "import-git starts fast-export with the exact safe argv and disabled prompt and lazy fetch" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export }); const result = scripted(tmp, &script, "argv", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expectEqualStrings( "git -C fixture.git fast-export --all --show-original-ids --mark-tags --signed-commits=strip --signed-tags=strip --use-done-feature", script.exportCommandLine(), ); try std.testing.expectEqualStrings("0", script.environment.get("GIT_TERMINAL_PROMPT").?); try std.testing.expectEqualStrings("1", script.environment.get("GIT_NO_LAZY_FETCH").?); } test "import-git drains real OS-pipe backpressure concurrently while stdout remains active" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result = try runProductionPipe(tmp, "flood", "success"); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expectEqual(@as(usize, 8 * 1024 * 1024), result.stderr_bytes_drained); try std.testing.expectEqual(@as(usize, 4096), result.retained_diagnostic_len); try std.testing.expect(result.err.len < 4096); try std.testing.expect(std.mem.indexOf(u8, result.err, "imported 'fixture.git'") != null); try std.testing.expectEqual(@as(u8, 0), result.exit_code); try std.testing.expect(result.reaped); try std.testing.expect(result.child_gone); } test "import-git cancellation terminates a real child then joins and reaps its actual status once" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result = try runProductionPipe(tmp, "cancelled", "hold"); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(result.exit_code != 0); try std.testing.expect(result.err.len < 4096); try std.testing.expect(std.mem.indexOf(u8, result.err, "Malformed") != null); try std.testing.expect(std.mem.indexOf(u8, result.err, "GitExportFailed") == null); try std.testing.expect(result.reaped); try std.testing.expect(result.child_gone); try expectNoPublication(std.testing.io, tmp, "cancelled"); } test "import-git non-Darwin/Linux fallback drains stable stderr ownership then reaps once" { if (builtin.os.tag == .macos or builtin.os.tag == .linux) return; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result = try runProductionPipe(tmp, "fallback-cancelled", "hold"); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(result.stderr_bytes_drained != 0); try std.testing.expect(result.reaped); try std.testing.expectEqual(@as(usize, 1), result.reap_calls); try std.testing.expect(result.child_gone); try expectNoPublication(std.testing.io, tmp, "fallback-cancelled"); } test "import-git rejects a nonzero completed export after draining and reaping it once" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result = try runProductionPipe(tmp, "nonzero-export", "nonzero"); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqual(@as(u8, 23), result.exit_code); try std.testing.expect(result.reaped); try std.testing.expect(result.child_gone); try expectNoPublication(std.testing.io, tmp, "nonzero-export"); } test "import-git prefers a nonzero truncated export status over its parser EOF" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result = try runProductionPipe(tmp, "signed-like-export", "truncated-nonzero"); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqual(@as(u8, 23), result.exit_code); try std.testing.expect(std.mem.indexOf(u8, result.err, "GitExportFailed") != null); try std.testing.expect(std.mem.indexOf(u8, result.err, "fatal: signed commit rejected") != null); try std.testing.expect(std.mem.indexOf(u8, result.err, "UnexpectedEof") == null); try std.testing.expect(result.reaped); try std.testing.expect(result.child_gone); try expectNoPublication(std.testing.io, tmp, "signed-like-export"); } test "import-git verifies the frozen .zit and import map before the atomic rename" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export }); const result = scripted(tmp, &script, "verified", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expect(script.publication.store_reopened_for_verify); try std.testing.expect(script.publication.map_reopened_for_verify); try std.testing.expect(script.publication.verify_before_publish); } test "import-git competing destination at commit point never clobbers it and removes its temporary sibling" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export, .competing_destination_at_publish = true, }); const result = scripted(tmp, &script, "raced", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(script.competitor_destination_intact); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try std.testing.expect(!script.temporary_sibling_left); } test "import-git never publishes or deletes a replaced staging sibling" { const replacements = [_]import_cli.CommandScript.StagingReplacement{ .directory, .symlink }; for (replacements) |replacement| { for (0..8) |_| { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.createDir(std.testing.io, "outside", .default_dir); try writeFixtureFile(std.testing.io, tmp.dir, "outside/replacement-sentinel", "survives\n"); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export, .replace_staging_at_publish = replacement, }); const result = scripted(tmp, &script, "raced-staging", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(std.mem.indexOf(u8, result.err, "StagingEntryReplaced") != null); try std.testing.expect(script.staging_replacement_observed); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try std.testing.expect(!directoryExists(std.testing.io, tmp.dir, "raced-staging")); try std.testing.expect(!exists(std.testing.io, tmp.dir, ".zit-import-map.jsonl")); const staging_name = script.stagingReplacementName(); try std.testing.expect(staging_name.len != 0); var outside = try tmp.dir.openDir(std.testing.io, "outside", .{ .follow_symlinks = false }); defer outside.close(std.testing.io); try std.testing.expect(exists(std.testing.io, outside, "replacement-sentinel")); switch (replacement) { .directory => { var preserved = try tmp.dir.openDir(std.testing.io, staging_name, .{ .follow_symlinks = false }); defer preserved.close(std.testing.io); try std.testing.expect(exists(std.testing.io, preserved, "replacement-sentinel")); }, .symlink => { var target: [32]u8 = undefined; const len = try tmp.dir.readLink(std.testing.io, staging_name, &target); try std.testing.expectEqualStrings("outside", target[0..len]); }, } } } } test "import-git default ceilings clear the retired 200k per-commit file wall" { const limits: import_cli.ImportLimits = .{}; const ceiling = fast_export.fileCommandCeiling(.{ .max_line = 0, .max_data = 0, .max_path = 0, .max_total_data = limits.max_export_payload_bytes, }); // The retired ceiling was 200,000 because that is the benchmark corpus // width. The derived one must clear it by orders of magnitude, so no // ordinary monorepo root commit can reach it, and it must move only with // the byte budget. try std.testing.expect(ceiling > 100 * 200_000); try std.testing.expectEqual( limits.max_export_payload_bytes / fast_export.min_retained_bytes_per_file_command, ceiling, ); try std.testing.expect(limits.max_export_events >= 400_000); try std.testing.expect(limits.max_export_payload_bytes >= 64 * 1024 * 1024); try std.testing.expect(limits.max_retained_entries >= 6 * 400_000); try std.testing.expect(limits.max_retained_bytes >= 64 * 1024 * 1024); } test "import-git reader limits reject before event and data parsing allocations" { const event_limited = "feature done\nblob\nmark :1\ndata 1\nx\n"; var event_reader = fast_export.Reader.init(std.testing.allocator, .fixed(event_limited), .{ .max_line = 1024, .max_data = 1024, .max_path = 1024, .max_events = 0, .max_total_data = 1024, }); defer event_reader.deinit(); try std.testing.expectError(error.EventLimitExceeded, event_reader.next()); const data_limited = "feature done\nblob\nmark :1\ndata 1\nx\n"; var data_reader = fast_export.Reader.init(std.testing.allocator, .fixed(data_limited), .{ .max_line = 1024, .max_data = 1024, .max_path = 1024, .max_events = 1, .max_total_data = 0, }); defer data_reader.deinit(); try std.testing.expectError(error.DataLimitExceeded, data_reader.next()); } test "import-git enforces total import ceilings before publication" { // The payload budget now carries two ceilings, so the accepted value sits // at the tighter of them: exactly one command's worth of minimum retention, // which is also comfortably above the stream's nine payload bytes. const one_command_budget = fast_export.min_retained_bytes_per_file_command; const accepted_limits: import_cli.ImportLimits = .{ .max_export_events = 3, .max_export_payload_bytes = one_command_budget, .max_retained_entries = 7, .max_retained_bytes = 1024, }; const RejectedLimit = struct { limits: import_cli.ImportLimits, error_name: []const u8, }; const rejected_limits = [_]RejectedLimit{ .{ .limits = .{ .max_export_events = accepted_limits.max_export_events - 1, .max_export_payload_bytes = accepted_limits.max_export_payload_bytes, .max_retained_entries = accepted_limits.max_retained_entries, }, .error_name = "ExportEventLimitExceeded", }, .{ // One byte below the stream's own payload. The blob's five bytes // admit; the commit message's four push the total past it, before // any file command is parsed. .limits = .{ .max_export_events = accepted_limits.max_export_events, .max_export_payload_bytes = 8, .max_retained_entries = accepted_limits.max_retained_entries, }, .error_name = "ExportPayloadLimitExceeded", }, .{ .limits = .{ .max_export_events = accepted_limits.max_export_events, .max_export_payload_bytes = accepted_limits.max_export_payload_bytes, .max_retained_entries = accepted_limits.max_retained_entries - 1, }, .error_name = "ImporterRetainedEntryLimitExceeded", }, .{ .limits = .{ .max_export_events = accepted_limits.max_export_events, .max_export_payload_bytes = accepted_limits.max_export_payload_bytes, .max_retained_entries = accepted_limits.max_retained_entries, .max_retained_bytes = 0, }, .error_name = "ImporterRetainedByteLimitExceeded", }, }; { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export_with_oids, .limits = accepted_limits, }); const result = scripted(tmp, &script, "at-limit", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, result.code); try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); } for (rejected_limits, 0..) |rejected, index| { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export_with_oids, .limits = rejected.limits, }); var destination: [32]u8 = undefined; const destination_name = try std.fmt.bufPrint(&destination, "limit-{d}", .{index}); const result = scripted(tmp, &script, destination_name, false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(std.mem.indexOf(u8, result.err, rejected.error_name) != null); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, destination_name); } { // One byte below one command's minimum retention derives a ceiling of // zero, so the stream's single `M` command is refused. The failure must // name the observed count, the ceiling, the derivation, and the flag // that raises it — the error name alone is not actionable. var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export_with_oids, .limits = .{ .max_export_events = accepted_limits.max_export_events, .max_export_payload_bytes = one_command_budget - 1, .max_retained_entries = accepted_limits.max_retained_entries, .max_retained_bytes = accepted_limits.max_retained_bytes, }, }); const result = scripted(tmp, &script, "command-ceiling", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); var expected: [512]u8 = undefined; const diagnostic = try std.fmt.bufPrint( &expected, "import-git: import failed: ExportFileCommandLimitExceeded\n" ++ "import-git: a commit exceeded the per-commit file-command ceiling: " ++ "observed at least 1, ceiling 0, derived from the {d}-byte export payload " ++ "budget at {d} minimum retained bytes per command; re-run with " ++ "--max-export-payload-bytes above {d}\n", .{ one_command_budget - 1, one_command_budget, one_command_budget - 1 }, ); try std.testing.expect(std.mem.indexOf(u8, result.err, diagnostic) != null); try expectNoPublication(std.testing.io, tmp, "command-ceiling"); } { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = "feature done\nprogress first\nprogress second\n", .limits = .{ .max_export_events = 1 }, }); const result = scripted(tmp, &script, "progress-limit", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(std.mem.indexOf(u8, result.err, "ExportEventLimitExceeded") != null); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "progress-limit"); } } test "import-git aborts every pre-rename writable-boundary failure without a partial destination" { const failures = [_]import_cli.PublicationHooks.FailurePoint{ .before_flush_store, .after_flush_store, .before_sync_store, .after_sync_store, .before_close_store, .after_close_store, .before_flush_map, .after_flush_map, .before_sync_map, .after_sync_map, .before_close_map, .after_close_map, .before_sync_temporary_directory, .after_sync_temporary_directory, .before_rename_no_replace, }; for (failures) |failure| { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export }); const result = scripted(tmp, &script, "boundary-failed", false, .{ .fail_at = failure }); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "boundary-failed"); } } test "import-git reports parent sync durability uncertainty after publish while retaining the verified destination" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export }); const result = scripted(tmp, &script, "durability-uncertain", false, .{ .fail_at = .sync_parent_after_publish }); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(result.published_durability_uncertain); var destination = try tmp.dir.openDir(std.testing.io, "durability-uncertain", .{}); defer destination.close(std.testing.io); try std.testing.expect(exists(std.testing.io, destination, ".zit")); try std.testing.expect(exists(std.testing.io, destination, ".zit-import-map.jsonl")); } test "import-git start session failure after stderr thread closes pipes and reaps exactly once" { var session = import_cli.ProductionGit.initForTest( std.testing.allocator, std.testing.io, &.{ helper_options.git_import_pipe_helper, "hold" }, ); defer session.deinit(); session.test_startup_failure = .after_stderr_thread; const dependencies = session.dependencies(.real_filesystem); try std.testing.expectError( error.InjectedStartFailure, dependencies.git.vtable.start_export(dependencies.git.context, "fixture.git"), ); try std.testing.expect(session.child == null); try std.testing.expect(session.stderr_thread == null); try std.testing.expect(session.stderr_file == null); try std.testing.expect(session.reaped); try std.testing.expectEqual(@as(usize, 1), session.reap_calls); } test "import-git rejects a replacement destination after rename without deleting it" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export, .replace_destination_after_rename = true, }); const result = scripted(tmp, &script, "replacement-after-rename", false, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(std.mem.indexOf(u8, result.err, "PublishedDestinationReplaced") != null); try std.testing.expect(script.competitor_destination_intact); var replacement = try tmp.dir.openDir(std.testing.io, "replacement-after-rename", .{ .follow_symlinks = false, }); defer replacement.close(std.testing.io); try std.testing.expect(exists(std.testing.io, replacement, "replacement-sentinel")); try std.testing.expect(!script.temporary_sibling_left); } test "import-git reaps after termination failure and reports the lifecycle error" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var git: ScriptedGit = .{ .fast_export = malformed_export, .lifecycle_fault = .terminate, }; const result = runFixture(tmp, "fixture.git", "termination-failure", false, &git, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, result.code); try std.testing.expect(std.mem.indexOf(u8, result.err, "InjectedLifecycleFailure") != null); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained); try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped); try expectNoPublication(std.testing.io, tmp, "termination-failure"); } test "import-git accounts the peeled target retained for marked tags at the exact byte boundary" { const exact_limit = try retainedBytesForExport(marked_tag_export); var exact_tmp = std.testing.tmpDir(.{}); defer exact_tmp.cleanup(); var exact_git: ScriptedGit = .{ .fast_export = marked_tag_export, .limits = .{ .max_retained_bytes = exact_limit }, }; const exact = runFixture(exact_tmp, "fixture.git", "marked-tag-exact", false, &exact_git, .real_filesystem); try std.testing.expectEqual(cli.Exit.ok, exact.code); var exceeded_tmp = std.testing.tmpDir(.{}); defer exceeded_tmp.cleanup(); var exceeded_git: ScriptedGit = .{ .fast_export = marked_tag_export, .limits = .{ .max_retained_bytes = exact_limit - 1 }, }; const exceeded = runFixture(exceeded_tmp, "fixture.git", "marked-tag-exceeded", false, &exceeded_git, .real_filesystem); try std.testing.expectEqual(cli.Exit.failure, exceeded.code); try std.testing.expect(std.mem.indexOf(u8, exceeded.err, "ImporterRetainedByteLimitExceeded") != null); try expectNoPublication(std.testing.io, exceeded_tmp, "marked-tag-exceeded"); }