╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1119 │ │1120 // make a commit │ │1157 - { │ │1158 - const goodbye_txt = try server_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true }); │ │1159 - defer goodbye_txt.close(io); │ │1160 - try goodbye_txt.writeStreamingAll(io, "goodbye, world!"); │ │1161 - try server_repo.add(io, allocator, &.{"goodbye.txt"}); │ │1162 - _ = try server_repo.commit(io, allocator, .{ .message = "add goodbye file" }); │ │1163 - } │ │1121 + _ = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "goodbye.txt", .content = "goodbye, world!" }} }, .{ .message = "add goodbye file" });│ │1122 │ │1123 // export server repo │ │1124 { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1168 } │ │1169 │ │1170 // make a third commit on the server │ │1213 - { │ │1214 - const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true }); │ │1215 - defer extra_txt.close(io); │ │1216 - try extra_txt.writeStreamingAll(io, "extra content"); │ │1217 - try server_repo.add(io, allocator, &.{"extra.txt"}); │ │1218 - _ = try server_repo.commit(io, allocator, .{ .message = "add extra file" }); │ │1219 - } │ │1171 + _ = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "extra.txt", .content = "extra content" }} }, .{ .message = "add extra file" });│ │1172 │ │1173 // pull --unshallow to deepen the clone and get the new commit │ │1174 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │1309 temp_path, │ │1310 client_path, │ │1311 null, │ │1360 - .{ .wire = .{ .ssh = .{ │ │1312 + .{ .transport = .{ .wire = .{ .ssh = .{│ │1313 .command = ssh_cmd_maybe, │ │1362 - } } }, │ │1314 + } } } }, │ │1315 ); │ │1316 defer client_repo.deinit(io, allocator); │ │1317 │ ╰─────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1358 const server_path = (try repoOnDiskPath(io, allocator, temp_dir_name, "testrepo", true)).?; │ │1359 defer allocator.free(server_path); │ │1360 │ │1409 - var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = server_path }); │ │1361 + var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .bare = true, .path = server_path }); │ │1362 defer server_repo.deinit(io, allocator); │ │1411 - │ │1412 - var server_dir = try cwd.openDir(io, server_path, .{}); │ │1413 - defer server_dir.close(io); │ │1414 - │ │1415 - // copy files from current repo into server dir │ │1416 - for (&[_][]const u8{"src"}) |dir_name| { │ │1417 - var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true }); │ │1418 - defer src_repo_dir.close(io); │ │1419 - │ │1420 - var dest_repo_dir = try server_dir.createDirPathOpen(io, dir_name, .{}); │ │1421 - defer dest_repo_dir.close(io); │ │1422 - │ │1423 - try copyDir(io, src_repo_dir, dest_repo_dir); │ │1424 - │ │1425 - try server_repo.add(io, allocator, &.{dir_name}); │ │1426 - } │ │1363 │ │1428 - // make a commit │ │1429 - const commit1 = try server_repo.commit(io, allocator, .{ .message = "let there be light" }); │ │1364 + // make a commit with files from the current repo │ │1365 + const commit1 = try commitServer(&server_repo, io, allocator, .{ .dirs = &.{"src"} }, .{ .message = "let there be light" });│ │1366 │ │1367 // export server repo │ │1368 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1424 } │ │1425 │ │1426 // make another commit on the server │ │1491 - const commit2 = blk: { │ │1492 - const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true }); │ │1493 - defer extra_txt.close(io); │ │1494 - try extra_txt.writeStreamingAll(io, "extra content"); │ │1495 - try server_repo.add(io, allocator, &.{"extra.txt"}); │ │1496 - break :blk try server_repo.commit(io, allocator, .{ .message = "add extra file" }); │ │1497 - }; │ │1427 + const commit2 = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "extra.txt", .content = "extra content" }} }, .{ .message = "add extra file" });│ │1428 │ │1429 // fetch with ref-in-want (git uses want-ref in protocol v2 when fetching named refs) │ │1430 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1509 const server_path = (try repoOnDiskPath(io, allocator, temp_dir_name, "testrepo", true)).?; │ │1510 defer allocator.free(server_path); │ │1511 │ │1582 - var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = server_path }); │ │1512 + var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .bare = true, .path = server_path });│ │1513 defer server_repo.deinit(io, allocator); │ │1514 │ │1515 // add config │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1586 - try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" }); │ │1587 - try server_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" });│ │1516 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); │ │1517 │ │1518 // export server repo │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1633 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; │ │1634 try std.testing.expectEqualStrings(&commit2, &oid_master); │ │1635 │ │1708 - const hello_txt = try server_repo.core.work_dir.openFile(io, "hello.txt", .{}); │ │1709 - defer hello_txt.close(io); │ │1636 + try std.testing.expectError(error.FileNotFound, server_repo.core.work_dir.openFile(io, "hello.txt", .{}));│ │1637 } │ │1638 } │ │1639 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1666 const admin_repo_path = try std.fs.path.join(allocator, &.{ cwd_path, data_dir_name, "admin" }); │ │1667 defer allocator.free(admin_repo_path); │ │1668 │ │1742 - var repo = try rp.Repo(.xit, evt.admin_repo_opts).init(io, allocator, .{ .path = admin_repo_path }); │ │1669 + var repo = try rp.Repo(.xit, evt.admin_repo_opts).init(io, allocator, .{ .bare = true, .path = admin_repo_path });│ │1670 defer repo.deinit(io, allocator); │ │1671 │ │1672 try repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1749 defer allocator.free(priv_key_path); │ │1750 │ │1751 return try std.fmt.allocPrint(allocator, "ssh -p {} -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o IdentitiesOnly=yes -o IdentityFile=\"{s}\"", .{ port + 1, priv_key_path });│ │1752 +} │ │1753 + │ │1754 +const ServerFiles = union(enum) { │ │1755 + files: []const struct { path: []const u8, content: []const u8 }, │ │1756 + dirs: []const []const u8, │ │1757 +}; │ │1758 + │ │1759 +/// commit files without writing the server's index or worktree. │ │1760 +fn commitServer(repo: anytype, io: std.Io, allocator: std.mem.Allocator, files: ServerFiles, metadata: xit.object.CommitMetadata(repo.self_repo_opts.hash)) ![xit.hash.hexLen(repo.self_repo_opts.hash)]u8 { │ │1761 + const R = @TypeOf(repo.*); │ │1762 + const opts = repo.self_repo_opts; │ │1763 + const Index = xit.index.Index(.xit, opts); │ │1764 + var result: [xit.hash.hexLen(opts.hash)]u8 = undefined; │ │1765 + const Ctx = struct { │ │1766 + core: *R.Core, │ │1767 + io: std.Io, │ │1768 + allocator: std.mem.Allocator, │ │1769 + files: ServerFiles, │ │1770 + metadata: xit.object.CommitMetadata(opts.hash), │ │1771 + result: *[xit.hash.hexLen(opts.hash)]u8, │ │1772 + │ │1773 + fn addFile(ctx: @This(), state: R.State(.read_write), index: *Index, path: []const u8, content: []const u8) !void { │ │1774 + var reader = std.Io.Reader.fixed(content); │ │1775 + var oid: [xit.hash.byteLen(opts.hash)]u8 = undefined; │ │1776 + try xit.object.writeObject(.xit, opts, state, ctx.io, ctx.allocator, &reader, .{ .kind = .blob, .size = content.len }, &oid); │ │1777 + const path_parts = try xit.fs.splitPath(ctx.allocator, path); │ │1778 + defer ctx.allocator.free(path_parts); │ │1779 + try index.addTreeEntryFile(&.{ .oid = oid, .mode = @bitCast(@as(u32, 0o100644)) }, path_parts, content.len, 0); │ │1780 + } │ │1781 + │ │1782 + fn addDir(ctx: @This(), state: R.State(.read_write), index: *Index, path: []const u8) anyerror!void { │ │1783 + var dir = try std.Io.Dir.cwd().openDir(ctx.io, path, .{ .iterate = true }); │ │1784 + defer dir.close(ctx.io); │ │1785 + var entries = dir.iterate(); │ │1786 + while (try entries.next(ctx.io)) |entry| { │ │1787 + const child = try std.fs.path.join(ctx.allocator, &.{ path, entry.name }); │ │1788 + defer ctx.allocator.free(child); │ │1789 + switch (entry.kind) { │ │1790 + .directory => try ctx.addDir(state, index, child), │ │1791 + .file => { │ │1792 + const content = try dir.readFileAlloc(ctx.io, entry.name, ctx.allocator, .unlimited); │ │1793 + defer ctx.allocator.free(content); │ │1794 + try ctx.addFile(state, index, child, content); │ │1795 + }, │ │1796 + else => {}, │ │1797 + } │ │1798 + } │ │1799 + } │ │1800 + │ │1801 + pub fn run(ctx: @This(), cursor: *R.DB.Cursor(.read_write)) !void { │ │1802 + var moment = try R.DB.HashMap(.read_write).init(cursor.*); │ │1803 + const state = R.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │1804 + const head_oid = try rf.readHeadRecurMaybe(.xit, opts, state.readOnly(), ctx.io); │ │1805 + var index = if (head_oid) |oid| try Index.initFromCommit(state.readOnly(), ctx.io, ctx.allocator, &oid) else try Index.init(state.readOnly(), ctx.io, ctx.allocator); │ │1806 + defer index.deinit(); │ │1807 + switch (ctx.files) { │ │1808 + .files => |updates| for (updates) |file| try ctx.addFile(state, &index, file.path, file.content), │ │1809 + .dirs => |dirs| for (dirs) |dir| try ctx.addDir(state, &index, dir), │ │1810 + } │ │1811 + var tree = try xit.object.Tree.initFromIndex(.xit, opts, state, ctx.io, ctx.allocator, &index); │ │1812 + defer tree.deinit(); │ │1813 + var head_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │1814 + const head = try rf.readHead(.xit, opts, state.readOnly(), ctx.io, &head_buffer) orelse return error.HeadNotFound; │ │1815 + ctx.result.* = try xit.object.writeCommit(.xit, opts, state, ctx.io, ctx.allocator, ctx.metadata, &tree, head.ref); │ │1816 + try xit.undo.writeMessage(opts, state, .{ .commit = ctx.metadata }); │ │1817 + } │ │1818 + }; │ │1819 + try repo.core.db_file.lock(io, .exclusive); │ │1820 + defer repo.core.db_file.unlock(io); │ │1821 + const history = try R.DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │1822 + try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │1823 + .core = &repo.core, │ │1824 + .io = io, │ │1825 + .allocator = allocator, │ │1826 + .files = files, │ │1827 + .metadata = metadata, │ │1828 + .result = &result, │ │1829 + }); │ │1830 + return result; │ │1831 } │ │1832 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯