╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ net.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
897 "origin", 898 "master", 899 false, 900 .{ .refspecs = &.{"refs/tags/1.0.0:refs/tags/1.0.0"}, .wire = .{ .ssh = .{ 901 .command = ssh_cmd_maybe, 902 } } }, 903 ); 904 905 // the push registered admin/server; resolve its on-disk directory 906 const server_path = (try repoOnDiskPath(io, allocator, temp_path, "testrepo", false)).?; 907 defer allocator.free(server_path); 908 909 var server_repo = try rp.Repo(.xit, .{ .is_test = true }).open(io, allocator, .{ .path = server_path }); 910 defer server_repo.deinit(io, allocator); 911 try std.testing.expect(try server_repo.isBare(io, allocator)); 912 913 try std.testing.expect(null != try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" })); 914 915 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 916 try std.testing.expectEqualStrings(&commit1, &oid_master); 917 } 918 919 fn testPushEvents( 920 comptime repo_kind: rp.RepoKind, 921 comptime transport_def: net.TransportDefinition, 922 comptime hash_kind: hash.HashKind, 923 comptime port: u16, 924 io: std.Io, 925 allocator: std.mem.Allocator, 926 ) !void { 927 // create the temp dir 928 var temp = std.testing.tmpDir(.{}); 929 defer temp.cleanup(); 930 const temp_path = try temp.dir.realPathFileAlloc(io, ".", allocator); 931 defer allocator.free(temp_path); 932 933 // init server 934 var server_process = try runServer(io, allocator, temp_path, port); 935 defer _ = server_process.kill(io); 936 937 // register the repo under admin and locate its on-disk directory 938 const server_path = (try repoOnDiskPath(io, allocator, temp_path, "testrepo", true)).?; 939 defer allocator.free(server_path); 940 941 const repo_opts: rp.RepoOpts(.xit) = .{ .hash = hash_kind, .is_test = true }; 942 const ServerRepo = rp.Repo(.xit, repo_opts); 943 var server_repo = try ServerRepo.init(io, allocator, .{ .bare = true, .path = server_path }); 944 defer server_repo.deinit(io, allocator); 945 946 const client_path = try std.fs.path.join(allocator, &.{ temp_path, "client" }); 947 defer allocator.free(client_path); 948 949 var client_repo = try rp.Repo(repo_kind, .{ .hash = hash_kind, .is_test = true }).init(io, allocator, .{ .path = client_path }); 950 defer client_repo.deinit(io, allocator); 951 952 // add remote 953 { 954 const remote_url = try remoteUrl(transport_def, allocator, "testrepo", port); 955 defer allocator.free(remote_url); 956 957 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 958 } 959 960 const is_ssh = switch (transport_def) { 961 .file => false, 962 .wire => |wire_kind| .ssh == wire_kind, 963 }; 964 const ssh_cmd_maybe = try sshCommand(is_ssh, allocator, temp_path, port); 965 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 966 967 var arena = std.heap.ArenaAllocator.init(allocator); 968 defer arena.deinit(); 969 970 // 971 // define test events 972 // 973 974 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 975 const issue_event_id = evt.EventWithId.randomId(prng.random()); 976 977 const events_to_push = [_]evt.EventWithId{ 978 .{ 979 .id = std.fmt.bytesToHex(issue_event_id, .lower), 980 .author = .{ .name = "haxy", .email = "user@haxy" }, 981 .event = .{ 982 .issue = .{ 983 .title = "Login form clears password on validation error", 984 .description = "Submitting an invalid email address resets the password field. Preserve the field value and show an inline validation message.", 985 .labels = "bug priority-high ui", 986 }, 987 }, 988 }, 989 }; 990 991 // commit the issue on the client and push it to the server 992 try evt.consume(.local, .repo, repo_kind, .{ .hash = hash_kind, .is_test = true }, io, allocator, &client_repo, evt.events_ref, &events_to_push); 993 try client_repo.push( 994 io, 995 allocator, 996 "origin", 997 "haxy/events", 998 false, 999 .{ .wire = .{ .ssh = .{ 1000 .command = ssh_cmd_maybe, 1001 } } }, 1002 ); 1003 1004 // the push consumed the issue into the server repo's db 1005 { 1006 const haxy_moment = try evt.currentMoment(repo_opts, &server_repo); 1007 const records_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.record_map_key)) orelse return error.NotFound; 1008 const records = try ServerRepo.DB.HashMap(.read_only).init(records_cursor); 1009 const issue_cursor = try records.getCursor(hash.hashInt(repo_opts.hash, &issue_event_id)) orelse return error.NotFound; 1010 const issue = try evt.read(evt.Issue.Record, ServerRepo.DB, repo_opts.hash, &arena, try ServerRepo.DB.HashMap(.read_only).init(issue_cursor)); 1011 1012 try std.testing.expectEqualStrings(events_to_push[0].event.issue.?.description, issue.event.description); 1013 try std.testing.expectEqualStrings(events_to_push[0].event.issue.?.labels, issue.event.labels); 1014 } 1015 1016 // 1017 // edit the issue 1018 // 1019 1020 // this event edits the previous one because it has the same id 1021 const events_to_push2 = [_]evt.EventWithId{ 1022 .{ 1023 .id = std.fmt.bytesToHex(issue_event_id, .lower), 1024 .author = .{ .name = "haxy", .email = "user@haxy" }, 1025 .event = .{ 1026 .issue = .{ 1027 .title = "Login form clears password on validation error", 1028 .description = "Submitting an invalid email address resets the password field and removes typed input. Preserve the field value and show an inline validation message.", 1029 .labels = "bug priority-low ui", 1030 }, 1031 }, 1032 }, 1033 }; 1034 1035 // commit the edit on the client and push it to the server 1036 try evt.consume(.local, .repo, repo_kind, .{ .hash = hash_kind, .is_test = true }, io, allocator, &client_repo, evt.events_ref, &events_to_push2); 1037 try client_repo.push( 1038 io, 1039 allocator, 1040 "origin", 1041 "haxy/events", 1042 false, 1043 .{ .wire = .{ .ssh = .{ 1044 .command = ssh_cmd_maybe, 1045 } } }, 1046 ); 1047 1048 // the push consumed the edit into the server repo's db 1049 { 1050 const haxy_moment = try evt.currentMoment(repo_opts, &server_repo); 1051 const records_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.record_map_key)) orelse return error.NotFound; 1052 const records = try ServerRepo.DB.HashMap(.read_only).init(records_cursor); 1053 const issue_cursor = try records.getCursor(hash.hashInt(repo_opts.hash, &issue_event_id)) orelse return error.NotFound; 1054 const issue = try evt.read(evt.Issue.Record, ServerRepo.DB, repo_opts.hash, &arena, try ServerRepo.DB.HashMap(.read_only).init(issue_cursor)); 1055 1056 // the description and labels were correctly edited 1057 try std.testing.expectEqualStrings(events_to_push2[0].event.issue.?.description, issue.event.description); 1058 try std.testing.expectEqualStrings(events_to_push2[0].event.issue.?.labels, issue.event.labels); 1059 } 1060 } 1061 1062 fn testClone( 1063 comptime repo_kind: rp.RepoKind, 1064 comptime transport_def: net.TransportDefinition, 1065 comptime shell_out_to_git: bool, 1066 comptime hash_kind: hash.HashKind, 1067 comptime port: u16, 1068 io: std.Io, 1069 allocator: std.mem.Allocator, 1070 ) !void { 1071 // create the temp dir 1072 var temp = std.testing.tmpDir(.{}); 1073 defer temp.cleanup(); 1074 const temp_path = try temp.dir.realPathFileAlloc(io, ".", allocator); 1075 defer allocator.free(temp_path); 1076 1077 // init server 1078 var server_process = try runServer(io, allocator, temp_path, port); 1079 defer _ = server_process.kill(io); 1080 1081 // register the repo under admin and locate its on-disk directory 1082 const server_path = (try repoOnDiskPath(io, allocator, temp_path, "testrepo", true)).?; 1083 defer allocator.free(server_path); 1084 1085 // init server repo with default branch name as main 1086 // is_test must be false when shell_out_to_git so commits get real timestamps (needed for --shallow-since) 1087 var server_repo = try rp.Repo(.xit, .{ .hash = hash_kind, .is_test = !shell_out_to_git }).init(io, allocator, .{ .bare = true, .path = server_path, .create_default_branch = "main" }); 1088 defer server_repo.deinit(io, allocator); 1089 1090 if (shell_out_to_git) { 1091 try server_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "test" }); 1092 try server_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "test@test" }); 1093 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowfilter", .value = "true" }); 1094 } 1095 1096 // make a commit 1097 _ = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "hello.txt", .content = "hello, world!" }} }, .{ .message = "let there be light" }); 1098 1099 // tag first commit 1100 _ = try server_repo.addTag(io, allocator, .{ .name = "v1", .message = "first" }); 1101 1102 // make a commit 1103 _ = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "goodbye.txt", .content = "goodbye, world!" }} }, .{ .message = "add goodbye file" }); 1104 1105 // export server repo 1106 { 1107 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1108 defer export_file.close(io); 1109 } 1110 1111 const client_path = try std.fs.path.join(allocator, &.{ temp_path, "client" }); 1112 defer allocator.free(client_path); 1113 1114 // get remote url 1115 const remote_url = try remoteUrl(transport_def, allocator, "testrepo", port); 1116 defer allocator.free(remote_url); 1117 1118 const is_ssh = switch (transport_def) { 1119 .file => false, 1120 .wire => |wire_kind| .ssh == wire_kind, 1121 }; 1122 1123 if (shell_out_to_git) { 1124 const ssh_cmd = (try sshCommand(true, allocator, temp_path, port)).?; 1125 defer allocator.free(ssh_cmd); 1126 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand={s}", .{ssh_cmd}); 1127 defer allocator.free(ssh_config_arg); 1128 1129 { 1130 var process = try std.process.spawn(io, .{ 1131 .argv = if (is_ssh) 1132 &.{ "git", "-c", ssh_config_arg, "clone", "--quiet", "--depth", "1", remote_url, "client" } 1133 else 1134 &.{ "git", "clone", "--quiet", "--depth", "1", remote_url, "client" }, 1135 .cwd = .{ .path = temp_path }, 1136 .stdin = .ignore, 1137 .stdout = .ignore, 1138 .stderr = .inherit, 1139 }); 1140 defer process.kill(io); 1141 const term = try process.wait(io); 1142 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1143 } 1144 1145 // make sure shallow clone was successful 1146 { 1147 const hello_txt = try temp.dir.openFile(io, "client/hello.txt", .{}); 1148 hello_txt.close(io); 1149 } 1150 1151 // make a third commit on the server 1152 _ = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "extra.txt", .content = "extra content" }} }, .{ .message = "add extra file" }); 1153 1154 // pull --unshallow to deepen the clone and get the new commit 1155 { 1156 var process = try std.process.spawn(io, .{ 1157 .argv = if (is_ssh) 1158 &.{ "git", "-c", ssh_config_arg, "pull", "--quiet", "--unshallow" } 1159 else 1160 &.{ "git", "pull", "--quiet", "--unshallow" }, 1161 .cwd = .{ .path = client_path }, 1162 .stdin = .ignore, 1163 .stdout = .ignore, 1164 .stderr = .inherit, 1165 }); 1166 defer process.kill(io); 1167 const term = try process.wait(io); 1168 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1169 } 1170 1171 // make sure unshallow pull was successful 1172 { 1173 const extra_txt = try temp.dir.openFile(io, "client/extra.txt", .{}); 1174 extra_txt.close(io); 1175 } 1176 1177 // delete client and clone again with --shallow-since 1178 try temp.dir.deleteTree(io, "client"); 1179 1180 { 1181 var process = try std.process.spawn(io, .{ 1182 .argv = if (is_ssh) 1183 &.{ "git", "-c", ssh_config_arg, "clone", "--quiet", "--shallow-since=2000-01-01", remote_url, "client" } 1184 else 1185 &.{ "git", "clone", "--quiet", "--shallow-since=2000-01-01", remote_url, "client" }, 1186 .cwd = .{ .path = temp_path }, 1187 .stdin = .ignore, 1188 .stdout = .ignore, 1189 .stderr = .inherit, 1190 }); 1191 defer process.kill(io); 1192 const term = try process.wait(io); 1193 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1194 } 1195 1196 // make sure shallow clone was successful 1197 { 1198 const hello_txt = try temp.dir.openFile(io, "client/hello.txt", .{}); 1199 hello_txt.close(io); 1200 } 1201 1202 // delete client and clone again with --shallow-exclude 1203 try temp.dir.deleteTree(io, "client"); 1204 1205 { 1206 var process = try std.process.spawn(io, .{ 1207 .argv = if (is_ssh) 1208 &.{ "git", "-c", ssh_config_arg, "clone", "--quiet", "--shallow-exclude=v1", remote_url, "client" } 1209 else 1210 &.{ "git", "clone", "--quiet", "--shallow-exclude=v1", remote_url, "client" }, 1211 .cwd = .{ .path = temp_path }, 1212 .stdin = .ignore, 1213 .stdout = .ignore, 1214 .stderr = .inherit, 1215 }); 1216 defer process.kill(io); 1217 const term = try process.wait(io); 1218 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1219 } 1220 1221 // make sure shallow clone was successful 1222 { 1223 const hello_txt = try temp.dir.openFile(io, "client/hello.txt", .{}); 1224 hello_txt.close(io); 1225 } 1226 1227 // delete client and clone again with --filter=blob:none 1228 try temp.dir.deleteTree(io, "client"); 1229 1230 { 1231 var process = try std.process.spawn(io, .{ 1232 .argv = if (is_ssh) 1233 &.{ "git", "-c", ssh_config_arg, "clone", "--quiet", "--filter=blob:none", remote_url, "client" } 1234 else 1235 &.{ "git", "clone", "--quiet", "--filter=blob:none", remote_url, "client" }, 1236 .cwd = .{ .path = temp_path }, 1237 .stdin = .ignore, 1238 .stdout = .ignore, 1239 .stderr = .inherit, 1240 }); 1241 defer process.kill(io); 1242 const term = try process.wait(io); 1243 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1244 } 1245 1246 // make sure partial clone was successful 1247 { 1248 const hello_txt = try temp.dir.openFile(io, "client/hello.txt", .{}); 1249 hello_txt.close(io); 1250 } 1251 1252 // delete client and clone again with --filter=tree:0 1253 try temp.dir.deleteTree(io, "client"); 1254 1255 { 1256 var process = try std.process.spawn(io, .{ 1257 .argv = if (is_ssh) 1258 &.{ "git", "-c", ssh_config_arg, "clone", "--quiet", "--filter=tree:0", remote_url, "client" } 1259 else 1260 &.{ "git", "clone", "--quiet", "--filter=tree:0", remote_url, "client" }, 1261 .cwd = .{ .path = temp_path }, 1262 .stdin = .ignore, 1263 .stdout = .ignore, 1264 .stderr = .inherit, 1265 }); 1266 defer process.kill(io); 1267 const term = try process.wait(io); 1268 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1269 } 1270 1271 // make sure treeless clone was successful 1272 { 1273 const goodbye_txt = try temp.dir.openFile(io, "client/goodbye.txt", .{}); 1274 goodbye_txt.close(io); 1275 } 1276 } else { 1277 const ssh_cmd_maybe = try sshCommand(is_ssh, allocator, temp_path, port); 1278 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1279 1280 // clone repo 1281 var clone_result = try rp.AnyRepo(repo_kind, .{ .is_test = true }).clone( 1282 io, 1283 allocator, 1284 remote_url, 1285 temp_path, 1286 client_path, 1287 null, 1288 .{ .transport = .{ .wire = .{ .ssh = .{ 1289 .command = ssh_cmd_maybe, 1290 } } } }, 1291 ); 1292 defer clone_result.deinit(io, allocator); 1293 const client_repo = &@field(clone_result, @tagName(hash_kind)); 1294 1295 // make sure HEAD points to the right default branch 1296 var current_branch_buffer = [_]u8{0} ** rf.MAX_REF_CONTENT_SIZE; 1297 const head = try client_repo.head(io, &current_branch_buffer); 1298 try std.testing.expectEqualStrings("main", head.ref.name); 1299 1300 // make sure clone was successful 1301 const hello_txt = try temp.dir.openFile(io, "client/hello.txt", .{}); 1302 defer hello_txt.close(io); 1303 } 1304 } 1305 1306 fn testFetchLarge( 1307 comptime repo_kind: rp.RepoKind, 1308 comptime transport_def: net.TransportDefinition, 1309 comptime shell_out_to_git: bool, 1310 comptime hash_kind: hash.HashKind, 1311 comptime port: u16, 1312 io: std.Io, 1313 allocator: std.mem.Allocator, 1314 ) !void { 1315 // create the temp dir 1316 var temp = std.testing.tmpDir(.{}); 1317 defer temp.cleanup(); 1318 const temp_path = try temp.dir.realPathFileAlloc(io, ".", allocator); 1319 defer allocator.free(temp_path); 1320 1321 // init server 1322 var server_process = try runServer(io, allocator, temp_path, port); 1323 defer _ = server_process.kill(io); 1324 1325 // register the repo under admin and locate its on-disk directory 1326 const server_path = (try repoOnDiskPath(io, allocator, temp_path, "testrepo", true)).?; 1327 defer allocator.free(server_path); 1328 1329 var server_repo = try rp.Repo(.xit, .{ .hash = hash_kind, .is_test = true }).init(io, allocator, .{ .bare = true, .path = server_path }); 1330 defer server_repo.deinit(io, allocator); 1331 1332 // make a commit with files from the current repo 1333 const commit1 = try commitServer(&server_repo, io, allocator, .{ .dirs = &.{"src"} }, .{ .message = "let there be light" }); 1334 1335 // export server repo 1336 { 1337 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1338 defer export_file.close(io); 1339 } 1340 1341 if (shell_out_to_git) { 1342 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowrefinwant", .value = "true" }); 1343 } 1344 1345 const client_path = try std.fs.path.join(allocator, &.{ temp_path, "client" }); 1346 defer allocator.free(client_path); 1347 1348 var client_repo = try rp.Repo(repo_kind, .{ .hash = hash_kind, .is_test = true }).init(io, allocator, .{ .path = client_path }); 1349 defer client_repo.deinit(io, allocator); 1350 1351 // add remote 1352 { 1353 const remote_url = try remoteUrl(transport_def, allocator, "testrepo", port); 1354 defer allocator.free(remote_url); 1355 1356 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 1357 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 1358 } 1359 1360 const is_ssh = switch (transport_def) { 1361 .file => false, 1362 .wire => |wire_kind| .ssh == wire_kind, 1363 }; 1364 1365 if (shell_out_to_git) { 1366 const ssh_cmd = (try sshCommand(true, allocator, temp_path, port)).?; 1367 defer allocator.free(ssh_cmd); 1368 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand={s}", .{ssh_cmd}); 1369 defer allocator.free(ssh_config_arg); 1370 1371 { 1372 var process = try std.process.spawn(io, .{ 1373 .argv = if (is_ssh) 1374 &.{ "git", "-c", ssh_config_arg, "pull", "--quiet", "origin", "master" } 1375 else 1376 &.{ "git", "pull", "--quiet", "origin", "master" }, 1377 .cwd = .{ .path = client_path }, 1378 .stdin = .ignore, 1379 .stdout = .ignore, 1380 .stderr = .inherit, 1381 }); 1382 defer process.kill(io); 1383 const term = try process.wait(io); 1384 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1385 } 1386 1387 // make sure pull was successful 1388 { 1389 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1390 try std.testing.expectEqualStrings(&commit1, &oid_master); 1391 } 1392 1393 // make another commit on the server 1394 const commit2 = try commitServer(&server_repo, io, allocator, .{ .files = &.{.{ .path = "extra.txt", .content = "extra content" }} }, .{ .message = "add extra file" }); 1395 1396 // fetch with ref-in-want (git uses want-ref in protocol v2 when fetching named refs) 1397 { 1398 var process = try std.process.spawn(io, .{ 1399 .argv = if (is_ssh) 1400 &.{ "git", "-c", ssh_config_arg, "fetch", "--quiet", "origin", "master" } 1401 else 1402 &.{ "git", "fetch", "--quiet", "origin", "master" }, 1403 .cwd = .{ .path = client_path }, 1404 .stdin = .ignore, 1405 .stdout = .ignore, 1406 .stderr = .inherit, 1407 }); 1408 defer process.kill(io); 1409 const term = try process.wait(io); 1410 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1411 } 1412 1413 // make sure fetch with want-ref was successful 1414 { 1415 const oid_remote_master = (try client_repo.readRef(io, .{ .kind = .{ .remote = "origin" }, .name = "master" })).?; 1416 try std.testing.expectEqualStrings(&commit2, &oid_remote_master); 1417 } 1418 } else { 1419 const refspecs = &.{ 1420 "+refs/heads/master:refs/heads/master", 1421 }; 1422 1423 const ssh_cmd_maybe = try sshCommand(is_ssh, allocator, temp_path, port); 1424 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1425 1426 try client_repo.fetch( 1427 io, 1428 allocator, 1429 "origin", 1430 .{ .refspecs = refspecs, .wire = .{ .ssh = .{ 1431 .command = ssh_cmd_maybe, 1432 } } }, 1433 ); 1434 1435 // update the working dir 1436 try client_repo.restore(io, allocator, "."); 1437 } 1438 1439 // make sure fetch was successful 1440 { 1441 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1442 try std.testing.expectEqualStrings(&commit1, &oid_master); 1443 } 1444 } 1445 1446 fn testPushLarge( 1447 comptime repo_kind: rp.RepoKind, 1448 comptime transport_def: net.TransportDefinition, 1449 comptime shell_out_to_git: bool, 1450 comptime hash_kind: hash.HashKind, 1451 comptime port: u16, 1452 io: std.Io, 1453 allocator: std.mem.Allocator, 1454 ) !void { 1455 // create the temp dir 1456 var temp = std.testing.tmpDir(.{}); 1457 defer temp.cleanup(); 1458 const temp_path = try temp.dir.realPathFileAlloc(io, ".", allocator); 1459 defer allocator.free(temp_path); 1460 1461 // init server 1462 var server_process = try runServer(io, allocator, temp_path, port); 1463 defer _ = server_process.kill(io); 1464 1465 // register the repo under admin and locate its on-disk directory 1466 const server_path = (try repoOnDiskPath(io, allocator, temp_path, "testrepo", true)).?; 1467 defer allocator.free(server_path); 1468 1469 var server_repo = try rp.Repo(.xit, .{ .hash = hash_kind, .is_test = true }).init(io, allocator, .{ .bare = true, .path = server_path }); 1470 defer server_repo.deinit(io, allocator); 1471 1472 // add config 1473 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); 1474 1475 // export server repo 1476 { 1477 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1478 defer export_file.close(io); 1479 } 1480 1481 const client_path = try std.fs.path.join(allocator, &.{ temp_path, "client" }); 1482 defer allocator.free(client_path); 1483 1484 var client_repo = try rp.Repo(repo_kind, .{ .hash = hash_kind, .is_test = true }).init(io, allocator, .{ .path = client_path }); 1485 defer client_repo.deinit(io, allocator); 1486 1487 var client_dir = try temp.dir.openDir(io, "client", .{}); 1488 defer client_dir.close(io); 1489 1490 { 1491 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 1492 defer hello_txt.close(io); 1493 try hello_txt.writeStreamingAll(io, "hello, world!"); 1494 try client_repo.add(io, allocator, &.{"hello.txt"}); 1495 } 1496 1497 // copy files from current repo into client dir 1498 const cwd = std.Io.Dir.cwd(); 1499 for (&[_][]const u8{"src"}) |dir_name| { 1500 var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true }); 1501 defer src_repo_dir.close(io); 1502 1503 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{}); 1504 defer dest_repo_dir.close(io); 1505 1506 try copyDir(io, src_repo_dir, dest_repo_dir); 1507 1508 try client_repo.add(io, allocator, &.{dir_name}); 1509 } 1510 1511 _ = try client_repo.commit(io, allocator, .{ .message = "let there be light" }); 1512 1513 // change the files so git will send them as delta objects 1514 for (&[_][]const u8{"src"}) |dir_name| { 1515 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{ .open_options = .{ .iterate = true } }); 1516 defer dest_repo_dir.close(io); 1517 1518 { 1519 var iter = dest_repo_dir.iterate(); 1520 while (try iter.next(io)) |entry| { 1521 switch (entry.kind) { 1522 .file => { 1523 const file = try dest_repo_dir.openFile(io, entry.name, .{ .mode = .read_write }); 1524 defer file.close(io); 1525 var writer = file.writer(io, &.{}); 1526 try writer.interface.writeAll("EDIT"); 1527 }, 1528 else => {}, 1529 } 1530 } 1531 } 1532 1533 try client_repo.add(io, allocator, &.{dir_name}); 1534 } 1535 1536 const commit2 = try client_repo.commit(io, allocator, .{ .message = "more stuff" }); 1537 1538 // add remote 1539 { 1540 const remote_url = try remoteUrl(transport_def, allocator, "testrepo", port); 1541 defer allocator.free(remote_url); 1542 1543 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 1544 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 1545 } 1546 1547 const is_ssh = switch (transport_def) { 1548 .file => false, 1549 .wire => |wire_kind| .ssh == wire_kind, 1550 }; 1551 1552 if (shell_out_to_git) { 1553 const ssh_cmd = (try sshCommand(true, allocator, temp_path, port)).?; 1554 defer allocator.free(ssh_cmd); 1555 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand={s}", .{ssh_cmd}); 1556 defer allocator.free(ssh_config_arg); 1557 1558 // shell out to git so it will send delta objects 1559 var process = try std.process.spawn(io, .{ 1560 .argv = if (is_ssh) 1561 &.{ "git", "-c", ssh_config_arg, "push", "--quiet", "origin", "master" } 1562 else 1563 &.{ "git", "push", "--quiet", "origin", "master" }, 1564 .cwd = .{ .path = client_path }, 1565 .stdin = .ignore, 1566 .stdout = .ignore, 1567 .stderr = .inherit, 1568 }); 1569 defer process.kill(io); 1570 const term = try process.wait(io); 1571 if (term != .exited or term.exited != 0) return error.GitCommandFailed; 1572 } else { 1573 const ssh_cmd_maybe = try sshCommand(is_ssh, allocator, temp_path, port); 1574 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1575 1576 try client_repo.push( 1577 io, 1578 allocator, 1579 "origin", 1580 "master", 1581 false, 1582 .{ .wire = .{ .ssh = .{ 1583 .command = ssh_cmd_maybe, 1584 } } }, 1585 ); 1586 } 1587 1588 // make sure push was successful 1589 { 1590 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1591 try std.testing.expectEqualStrings(&commit2, &oid_master); 1592 1593 try std.testing.expectError(error.FileNotFound, server_repo.core.work_dir.openFile(io, "hello.txt", .{})); 1594 } 1595 } 1596 1597 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void { 1598 var iter = src_dir.iterate(); 1599 while (try iter.next(io)) |entry| { 1600 switch (entry.kind) { 1601 .file => try src_dir.copyFile(entry.name, dest_dir, entry.name, io, .{}), 1602 .directory => { 1603 try dest_dir.createDirPath(io, entry.name); 1604 var dest_entry_dir = try dest_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 1605 defer dest_entry_dir.close(io); 1606 var src_entry_dir = try src_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 1607 defer src_entry_dir.close(io); 1608 try copyDir(io, src_entry_dir, dest_entry_dir); 1609 }, 1610 else => {}, 1611 } 1612 } 1613 } 1614 1615 // the dev SSH public key matching the private key written by runServer 1616 const admin_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark"; 1617 1618 // create the admin event repo with a user holding the dev SSH public key 1619 fn setupAdmin(io: std.Io, allocator: std.mem.Allocator, data_path: []const u8) !void { 1620 const admin_repo_path = try std.fs.path.join(allocator, &.{ data_path, "admin" }); 1621 defer allocator.free(admin_repo_path); 1622 1623 var repo = try rp.Repo(.xit, evt.admin_repo_opts).init(io, allocator, .{ .bare = true, .path = admin_repo_path }); 1624 defer repo.deinit(io, allocator); 1625 1626 try repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 1627 try repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 1628 1629 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 1630 const user_id = evt.EventWithId.randomId(prng.random()); 1631 1632 var password_hash_buf: [evt.User.password_hash_max_len]u8 = undefined; 1633 const password_hash = try evt.User.hashPassword("password", &password_hash_buf, io); 1634 1635 try evt.consume(.server, .admin, .xit, evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{.{ 1636 .id = std.fmt.bytesToHex(user_id, .lower), 1637 .author = .{ .name = "admin", .email = "admin@example.test" }, 1638 .event = .{ .user = .{ 1639 .name = "admin", 1640 .email = "admin@example.test", 1641 .password_hash = password_hash, 1642 .ssh_keys = admin_ssh_key, 1643 } }, 1644 }}); 1645 } 1646 1647 // resolve admin/<repo_name> to its on-disk directory under <data_dir>/repos via 1648 // the event store 1649 fn repoOnDiskPath( 1650 io: std.Io, 1651 allocator: std.mem.Allocator, 1652 data_path: []const u8, 1653 repo_name: []const u8, 1654 create: bool, 1655 ) !?[]u8 { 1656 const admin_repo_path = try std.fs.path.join(allocator, &.{ data_path, "admin" }); 1657 defer allocator.free(admin_repo_path); 1658 1659 const event_id_hex = (try evt.resolveOrCreateRepo( 1660 io, 1661 allocator, 1662 admin_repo_path, 1663 "admin", 1664 repo_name, 1665 if (create) .{ .read_access = .public } else null, 1666 )) orelse return null; 1667 return try std.fs.path.join(allocator, &.{ data_path, "repos", &event_id_hex }); 1668 } 1669 1670 // build the remote URL addressing admin/<repo_name> over the given transport 1671 fn remoteUrl( 1672 comptime transport_def: net.TransportDefinition, 1673 allocator: std.mem.Allocator, 1674 repo_name: []const u8, 1675 port: u16, 1676 ) ![]u8 { 1677 return switch (transport_def) { 1678 .file => unreachable, 1679 .wire => |wire_kind| switch (wire_kind) { 1680 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/admin/{s}", .{ port, repo_name }), 1681 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/admin/{s}", .{ port, repo_name }), 1682 .ssh => try std.fmt.allocPrint(allocator, "git@localhost:admin/{s}", .{repo_name}), 1683 }, 1684 }; 1685 } 1686 1687 // the ssh command the haxy git client should invoke, or null when not using 1688 // ssh. haxy generates a fresh host key on every run, so accept the unknown host 1689 // and skip writing to known_hosts. 1690 fn sshCommand( 1691 is_ssh: bool, 1692 allocator: std.mem.Allocator, 1693 temp_path: []const u8, 1694 port: u16, 1695 ) !?[]u8 { 1696 if (!is_ssh) return null; 1697 1698 const priv_key_path = try std.fs.path.join(allocator, &.{ temp_path, "key" }); 1699 defer allocator.free(priv_key_path); 1700 1701 if (builtin.os.tag == .windows) std.mem.replaceScalar(u8, priv_key_path, '\\', '/'); 1702 const null_path = if (builtin.os.tag == .windows) "NUL" else "/dev/null"; 1703 return try std.fmt.allocPrint(allocator, "ssh -p {} -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile={s} -o LogLevel=ERROR -o IdentitiesOnly=yes -i \"{s}\"", .{ port + 1, null_path, priv_key_path }); 1704 } 1705 1706 const ServerFiles = union(enum) { 1707 files: []const struct { path: []const u8, content: []const u8 }, 1708 dirs: []const []const u8, 1709 }; 1710 1711 /// commit files without writing the server's index or worktree. 1712 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 { 1713 const R = @TypeOf(repo.*); 1714 const opts = repo.self_repo_opts; 1715 const Index = xit.index.Index(.xit, opts); 1716 var result: [xit.hash.hexLen(opts.hash)]u8 = undefined; 1717 const Ctx = struct { 1718 core: *R.Core, 1719 io: std.Io, 1720 allocator: std.mem.Allocator, 1721 files: ServerFiles, 1722 metadata: xit.object.CommitMetadata(opts.hash), 1723 result: *[xit.hash.hexLen(opts.hash)]u8, 1724 1725 fn addFile(ctx: @This(), state: R.State(.read_write), index: *Index, path: []const u8, content: []const u8) !void { 1726 var reader = std.Io.Reader.fixed(content); 1727 var oid: [xit.hash.byteLen(opts.hash)]u8 = undefined; 1728 try xit.object.writeObject(.xit, opts, state, ctx.io, ctx.allocator, &reader, .{ .kind = .blob, .size = content.len }, &oid); 1729 const path_parts = try xit.fs.splitPath(ctx.allocator, path); 1730 defer ctx.allocator.free(path_parts); 1731 try index.addTreeEntryFile(&.{ .oid = oid, .mode = @bitCast(@as(u32, 0o100644)) }, path_parts, content.len, 0); 1732 } 1733 1734 fn addDir(ctx: @This(), state: R.State(.read_write), index: *Index, path: []const u8) anyerror!void { 1735 var dir = try std.Io.Dir.cwd().openDir(ctx.io, path, .{ .iterate = true }); 1736 defer dir.close(ctx.io); 1737 var entries = dir.iterate(); 1738 while (try entries.next(ctx.io)) |entry| { 1739 const child = try std.fs.path.join(ctx.allocator, &.{ path, entry.name }); 1740 defer ctx.allocator.free(child); 1741 switch (entry.kind) { 1742 .directory => try ctx.addDir(state, index, child), 1743 .file => { 1744 const content = try dir.readFileAlloc(ctx.io, entry.name, ctx.allocator, .unlimited); 1745 defer ctx.allocator.free(content); 1746 try ctx.addFile(state, index, child, content); 1747 }, 1748 else => {}, 1749 } 1750 } 1751 } 1752 1753 pub fn run(ctx: @This(), cursor: *R.DB.Cursor(.read_write)) !void { 1754 var moment = try R.DB.HashMap(.read_write).init(cursor.*); 1755 const state = R.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; 1756 const head_oid = try rf.readHeadRecurMaybe(.xit, opts, state.readOnly(), ctx.io); 1757 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); 1758 defer index.deinit(); 1759 switch (ctx.files) { 1760 .files => |updates| for (updates) |file| try ctx.addFile(state, &index, file.path, file.content), 1761 .dirs => |dirs| for (dirs) |dir| try ctx.addDir(state, &index, dir), 1762 } 1763 var tree = try xit.object.Tree.initFromIndex(.xit, opts, state, ctx.io, ctx.allocator, &index); 1764 defer tree.deinit(); 1765 var head_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; 1766 const head = try rf.readHead(.xit, opts, state.readOnly(), ctx.io, &head_buffer) orelse return error.HeadNotFound; 1767 ctx.result.* = try xit.object.writeCommit(.xit, opts, state, ctx.io, ctx.allocator, ctx.metadata, &tree, head.ref); 1768 try xit.undo.write(opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .commit = try .init(ctx.metadata.message, ctx.result.*, opts.max_read_size) }); 1769 } 1770 }; 1771 try repo.core.db_file.lock(io, .exclusive); 1772 defer repo.core.db_file.unlock(io); 1773 const history = try R.DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); 1774 try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ 1775 .core = &repo.core, 1776 .io = io, 1777 .allocator = allocator, 1778 .files = files, 1779 .metadata = metadata, 1780 .result = &result, 1781 }); 1782 return result; 1783 } 1784