╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ patch.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
194 // branch the events history before creating competing revisions 195 // 196 197 const side_events_ref: rf.Ref = .{ .kind = .head, .name = "haxy/patch-side" }; 198 { 199 var result = try target.switchDir(io, allocator, .{ .target = .{ .ref = evt.events_ref } }); 200 defer result.deinit(); 201 try target.addBranch(io, .{ .name = side_events_ref.name }); 202 } 203 204 // 205 // create two competing code revisions 206 // 207 208 const source_a_oid = blk: { 209 const file = try source.core.work_dir.createFile(io, "feature.zig", .{ .truncate = true }); 210 defer file.close(io); 211 try file.writeStreamingAll(io, "pub const answer = 43;\n"); 212 try source.add(io, allocator, &.{"feature.zig"}); 213 break :blk try source.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "revise feature one way", .timestamp = 6 }); 214 }; 215 const source_a_tree = try commitTree(io, allocator, &source, &source_a_oid); 216 const source_b_oid = blk: { 217 const file = try source.core.work_dir.createFile(io, "feature.zig", .{ .truncate = true }); 218 defer file.close(io); 219 try file.writeStreamingAll(io, "pub const answer = 44;\n"); 220 try source.add(io, allocator, &.{"feature.zig"}); 221 break :blk try source.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "revise feature another way", .timestamp = 7 }); 222 }; 223 const source_b_tree = try commitTree(io, allocator, &source, &source_b_oid); 224 { 225 var source_moment = try source.core.latestMoment(); 226 const source_state = Repo.State(.read_only){ .core = &source.core, .extra = .{ .moment = &source_moment } }; 227 var objects = try obj.ObjectIterator(.xit, repo_opts).init(source_state, io, allocator, .{ .kind = .all }); 228 defer objects.deinit(); 229 try objects.include(&source_b_oid); 230 try target.copyObjects(.xit, repo_opts, &objects, io, null); 231 } 232 233 // 234 // select the first revision on the events branch 235 // 236 237 const patchrev_a_id = evt.EventWithId.randomId(prng.random()); 238 const target_entries = [_]evt.EventTreeEntry{ 239 .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, 240 .{ .tree = .{ .name = "head", .oid = &source_a_tree } }, 241 }; 242 var target_patch = updated_patch; 243 target_patch.patchrev_id = std.fmt.bytesToHex(patchrev_a_id, .lower); 244 { 245 try evt.consume(.repo, .xit, repo_opts, io, allocator, &target, evt.events_ref, &.{ 246 .{ 247 .id = std.fmt.bytesToHex(patchrev_a_id, .lower), 248 .timestamp = 8, 249 .author = author, 250 .tree_entries = &target_entries, 251 .event = .{ .patchrev = .{ .base_oid = &base_oid, .source_oid = &source_a_oid, .target_ref = "refs/heads/master", .message = updated_patch.title } }, 252 }, 253 .{ 254 .id = std.fmt.bytesToHex(patch_id, .lower), 255 .timestamp = 9, 256 .author = author, 257 .event = .{ .patch = target_patch }, 258 }, 259 }); 260 } 261 262 // 263 // select the second revision on the side branch 264 // 265 266 const patchrev_b_id = evt.EventWithId.randomId(prng.random()); 267 const parent_entries = [_]evt.EventTreeEntry{ 268 .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, 269 .{ .tree = .{ .name = "head", .oid = &source_b_tree } }, 270 }; 271 var parent_patch = updated_patch; 272 parent_patch.patchrev_id = std.fmt.bytesToHex(patchrev_b_id, .lower); 273 { 274 try evt.consume(.repo, .xit, repo_opts, io, allocator, &target, side_events_ref, &.{ 275 .{ 276 .id = std.fmt.bytesToHex(patchrev_b_id, .lower), 277 .timestamp = 10, 278 .author = author, 279 .tree_entries = &parent_entries, 280 .event = .{ .patchrev = .{ .base_oid = &base_oid, .source_oid = &source_b_oid, .target_ref = "refs/heads/master", .message = updated_patch.title } }, 281 }, 282 .{ 283 .id = std.fmt.bytesToHex(patch_id, .lower), 284 .timestamp = 11, 285 .author = author, 286 .event = .{ .patch = parent_patch }, 287 }, 288 }); 289 } 290 291 // 292 // merge the competing patch revisions 293 // 294 295 { 296 try evt.mergeEvents(.xit, repo_opts, io, allocator, &target, side_events_ref); 297 try evt.consume(.repo, .xit, repo_opts, io, allocator, &target, evt.events_ref, &.{}); 298 } 299 300 // 301 // check the selected revision and conflict 302 // 303 304 _ = arena.reset(.retain_capacity); 305 const merged_moment = try evt.currentMoment(repo_opts, &target); 306 const merged = (try evt.Patch.readById(Repo.DB, repo_opts.hash, merged_moment, &arena, &patch_id)) orelse return error.NotFound; 307 try std.testing.expectEqualStrings(&std.fmt.bytesToHex(patchrev_a_id, .lower), &(merged.event.patchrev_id orelse return error.NotFound)); 308 const merged_patchrev = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, merged_moment, &arena, &patchrev_a_id)) orelse return error.NotFound; 309 try std.testing.expectEqualStrings(&source_a_oid, merged_patchrev.event.source_oid); 310 try std.testing.expectEqualStrings(&source_a_tree, merged_patchrev.head_tree_oid); 311 312 const conflicts_cursor = try merged_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.conflicts_key)) orelse return error.NotFound; 313 const conflicts = try Repo.DB.SortedMap(.read_only).init(conflicts_cursor); 314 const conflict_cursor = try conflicts.getCursor(&evt.orderKeyDesc(merged.created_order, &patch_id)) orelse return error.NotFound; 315 const conflict = try Repo.DB.HashMap(.read_only).init(conflict_cursor); 316 const fields_cursor = try conflict.getCursor(hash.hashInt(repo_opts.hash, evt.conflicted_fields_key)) orelse return error.NotFound; 317 try std.testing.expectEqualStrings("patchrev_id", try fields_cursor.readBytesAlloc(arena.allocator(), null)); 318 const their_cursor = try conflict.getCursor(hash.hashInt(repo_opts.hash, evt.their_record_key)) orelse return error.NotFound; 319 const theirs = try evt.read(evt.Patch.Record, Repo.DB, repo_opts.hash, &arena, try Repo.DB.HashMap(.read_only).init(their_cursor)); 320 try std.testing.expectEqualStrings(&std.fmt.bytesToHex(patchrev_b_id, .lower), &(theirs.event.patchrev_id orelse return error.NotFound)); 321 322 // 323 // create a patch stacked on the selected revision 324 // 325 326 const child_patchrev_id = evt.EventWithId.randomId(prng.random()); 327 const child_id = evt.EventWithId.randomId(prng.random()); 328 const child_entries = [_]evt.EventTreeEntry{ 329 .{ .tree = .{ .name = "base", .oid = merged_patchrev.head_tree_oid } }, 330 .{ .tree = .{ .name = "head", .oid = &source_b_tree } }, 331 }; 332 { 333 try evt.consume(.repo, .xit, repo_opts, io, allocator, &target, evt.events_ref, &.{ 334 .{ 335 .id = std.fmt.bytesToHex(child_patchrev_id, .lower), 336 .timestamp = 12, 337 .author = author, 338 .tree_entries = &child_entries, 339 .event = .{ .patchrev = .{ .base_oid = merged_patchrev.patch_oid, .source_oid = &source_b_oid, .target_ref = "refs/heads/master", .message = "stack another answer change" } }, 340 }, 341 .{ 342 .id = std.fmt.bytesToHex(child_id, .lower), 343 .timestamp = 13, 344 .author = author, 345 .event = .{ .patch = .{ 346 .title = "stack another answer change", 347 .description = "depends on the first patch", 348 .tags = "enhancement", 349 .target_patch_id = std.fmt.bytesToHex(patch_id, .lower), 350 .patchrev_id = std.fmt.bytesToHex(child_patchrev_id, .lower), 351 } }, 352 }, 353 }); 354 } 355 356 // 357 // check the derived commit and reverse index 358 // 359 360 _ = arena.reset(.retain_capacity); 361 const stacked_moment = try evt.currentMoment(repo_opts, &target); 362 const child = (try evt.Patch.readById(Repo.DB, repo_opts.hash, stacked_moment, &arena, &child_id)) orelse return error.NotFound; 363 var selected_patchrev_id: [evt.event_id_size]u8 = undefined; 364 _ = try std.fmt.hexToBytes(&selected_patchrev_id, &(child.event.patchrev_id orelse return error.NotFound)); 365 const child_patchrev = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, stacked_moment, &arena, &selected_patchrev_id)) orelse return error.NotFound; 366 var child_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; 367 @memcpy(&child_oid, child_patchrev.patch_oid); 368 { 369 var child_repo_moment = try target.core.latestMoment(); 370 const child_state = Repo.State(.read_only){ .core = &target.core, .extra = .{ .moment = &child_repo_moment } }; 371 var child_object = try obj.Object(.xit, repo_opts).init(child_state, io, allocator, &child_oid); 372 defer child_object.deinit(); 373 const child_commit = switch (child_object.content) { 374 .commit => |commit| commit, 375 else => return error.InvalidObject, 376 }; 377 const child_parents = child_commit.metadata.parent_oids orelse return error.ParentOidsNotFound; 378 try std.testing.expectEqualStrings(child_patchrev.event.base_oid, &child_parents[0]); 379 } 380 381 const targets_cursor = try stacked_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.target_patch_id_to_patch_id_set_key)) orelse return error.NotFound; 382 const targets = try Repo.DB.HashMap(.read_only).init(targets_cursor); 383 const children_cursor = try targets.getCursor(hash.hashInt(repo_opts.hash, &patch_id)) orelse return error.NotFound; 384 const children = try Repo.DB.SortedSet(.read_only).init(children_cursor); 385 try std.testing.expectEqual(1, try children.count()); 386 387 // 388 // retain the ref-free commits during garbage collection 389 // 390 391 { 392 const roots = try evt.PatchRev.gcRoots(Repo.DB, repo_opts.hash, allocator, stacked_moment); 393 defer allocator.free(roots); 394 try std.testing.expectEqual(4, roots.len); 395 _ = try target.garbageCollect(io, allocator, roots); 396 397 var after_gc_moment = try target.core.latestMoment(); 398 const after_gc_state = Repo.State(.read_only){ .core = &target.core, .extra = .{ .moment = &after_gc_moment } }; 399 var retained = try obj.Object(.xit, repo_opts).init(after_gc_state, io, allocator, &roots[0]); 400 retained.deinit(); 401 } 402 } 403 404 test "patch lifecycle" { 405 const io = std.testing.io; 406 const allocator = std.testing.allocator; 407 const temp_dir_name = "temp-patch-lifecycle"; 408 const cwd = std.Io.Dir.cwd(); 409 410 if (cwd.openDir(io, temp_dir_name, .{})) |dir_value| { 411 var dir = dir_value; 412 dir.close(io); 413 try cwd.deleteTree(io, temp_dir_name); 414 } else |_| {} 415 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 416 defer cwd.deleteTree(io, temp_dir_name) catch {}; 417 defer temp_dir.close(io); 418 419 const cwd_path = try std.process.currentPathAlloc(io, allocator); 420 defer allocator.free(cwd_path); 421 const root = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name }); 422 defer allocator.free(root); 423 const repos_dir = try std.fs.path.join(allocator, &.{ root, "repos" }); 424 defer allocator.free(repos_dir); 425 const admin_path = try std.fs.path.join(allocator, &.{ root, "admin" }); 426 defer allocator.free(admin_path); 427 const upstream_path = try std.fs.path.join(allocator, &.{ root, "upstream" }); 428 defer allocator.free(upstream_path); 429 const source_path = try std.fs.path.join(allocator, &.{ root, "source" }); 430 defer allocator.free(source_path); 431 432 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 433 const user_id = evt.EventWithId.randomId(prng.random()); 434 const repo_id = evt.EventWithId.randomId(prng.random()); 435 const repo_id_hex = std.fmt.bytesToHex(repo_id, .lower); 436 const target_path = try std.fs.path.join(allocator, &.{ repos_dir, &repo_id_hex }); 437 defer allocator.free(target_path); 438 439 var admin = try rp.Repo(.xit, evt.admin_repo_opts).init(io, allocator, .{ .path = admin_path }); 440 defer admin.deinit(io, allocator); 441 try evt.consume(.admin, .xit, evt.admin_repo_opts, io, allocator, &admin, evt.events_ref, &.{ 442 .{ 443 .id = std.fmt.bytesToHex(user_id, .lower), 444 .timestamp = 1, 445 .author = author, 446 .event = .{ .user = .{ 447 .name = author.name, 448 .email = author.email, 449 .password_hash = "", 450 } }, 451 }, 452 .{ 453 .id = repo_id_hex, 454 .timestamp = 1, 455 .author = author, 456 .event = .{ .repo = .{ 457 .user_id = &user_id, 458 .name = "repo", 459 .description = "", 460 } }, 461 }, 462 }); 463 464 // 465 // create the target repo 466 // 467 468 { 469 var upstream = try rp.Repo(.git, .{ .is_test = true }).init(io, allocator, .{ .path = upstream_path }); 470 defer upstream.deinit(io, allocator); 471 const file = try upstream.core.work_dir.createFile(io, "main.zig", .{ .truncate = true }); 472 defer file.close(io); 473 try file.writeStreamingAll(io, "pub fn main() void {}\n"); 474 try upstream.add(io, allocator, &.{"main.zig"}); 475 _ = try upstream.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "initial code", .timestamp = 1 }); 476 } 477 var target = try Repo.clone(io, allocator, upstream_path, target_path, target_path, null, .{}); 478 defer target.deinit(io, allocator); 479 480 // 481 // create the patch draft in the fork 482 // 483 484 const patch_id = evt.EventWithId.randomId(prng.random()); 485 const patch_id_hex = std.fmt.bytesToHex(patch_id, .lower); 486 const draft_path = try fork.create(repo_opts, io, allocator, repos_dir, &admin, .{ 487 .id = patch_id_hex, 488 .user_id = user_id, 489 .repo_id = repo_id, 490 .title = "add the answer", 491 .tags = "enhancement", 492 .description = "adds a reusable answer constant", 493 .author = author, 494 .timestamp = 2, 495 }); 496 defer allocator.free(draft_path); 497 498 // 499 // create and record the proposed revision in the fork 500 // 501 502 const base_oid = (try target.readRef(io, .{ .kind = .head, .name = "master" })) orelse return error.NotFound; 503 const base_tree_oid = try commitTree(io, allocator, &target, &base_oid); 504 var source = try Repo.clone(io, allocator, upstream_path, source_path, source_path, null, .{}); 505 defer source.deinit(io, allocator); 506 const source_oid = blk: { 507 const file = try source.core.work_dir.createFile(io, "feature.zig", .{ .truncate = true }); 508 defer file.close(io); 509 try file.writeStreamingAll(io, "pub const answer = 42;\n"); 510 try source.add(io, allocator, &.{"feature.zig"}); 511 break :blk try source.commit(io, allocator, .{ .author = "alice <alice@example.test>", .message = "add feature", .timestamp = 2 }); 512 }; 513 const head_tree_oid = try commitTree(io, allocator, &source, &source_oid); 514 const first_patchrev_id = evt.EventWithId.randomId(prng.random()); 515 const first_patchrev_hex = std.fmt.bytesToHex(first_patchrev_id, .lower); 516 const first_entries = [_]evt.EventTreeEntry{ 517 .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, 518 .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, 519 }; 520 { 521 var draft = try Repo.open(io, allocator, .{ .path = draft_path }); 522 defer draft.deinit(io, allocator); 523 var arena = std.heap.ArenaAllocator.init(allocator); 524 defer arena.deinit(); 525 526 const initial_moment = try evt.currentMoment(repo_opts, &draft); 527 const initial_patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, initial_moment, &arena, &patch_id)) orelse return error.NotFound; 528 try std.testing.expect(!initial_patch.removed); 529 try std.testing.expectEqualStrings("add the answer", initial_patch.event.title); 530 try std.testing.expectEqual(null, initial_patch.event.patchrev_id); 531 try std.testing.expectEqual(null, try evt.PatchRev.readNewest(Repo.DB, repo_opts.hash, initial_moment, &arena)); 532 533 var source_moment = try source.core.latestMoment(); 534 const source_state = Repo.State(.read_only){ .core = &source.core, .extra = .{ .moment = &source_moment } }; 535 var objects = try obj.ObjectIterator(.xit, repo_opts).init(source_state, io, allocator, .{ .kind = .all }); 536 defer objects.deinit(); 537 try objects.include(&source_oid); 538 try draft.copyObjects(.xit, repo_opts, &objects, io, null); 539 try evt.consume(.fork, .xit, repo_opts, io, allocator, &draft, evt.events_ref, &.{ 540 .{ 541 .id = first_patchrev_hex, 542 .timestamp = 3, 543 .author = author, 544 .tree_entries = &first_entries, 545 .event = .{ .patchrev = .{ 546 .base_oid = &base_oid, 547 .source_oid = &source_oid, 548 .target_ref = "refs/heads/master", 549 .message = "add the answer", 550 } }, 551 }, 552 .{ 553 .id = patch_id_hex, 554 .timestamp = 3, 555 .author = author, 556 .event = .{ .patch = .{ 557 .title = "add the answer", 558 .tags = "enhancement", 559 .description = "adds a reusable answer constant", 560 .patchrev_id = first_patchrev_hex, 561 } }, 562 }, 563 }); 564 565 _ = arena.reset(.retain_capacity); 566 const moment = try evt.currentMoment(repo_opts, &draft); 567 const patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; 568 try std.testing.expect(!patch.removed); 569 try std.testing.expectEqualStrings(&first_patchrev_hex, &(patch.event.patchrev_id orelse return error.NotFound)); 570 try std.testing.expect(null != try evt.PatchRev.readById(Repo.DB, repo_opts.hash, moment, &arena, &first_patchrev_id)); 571 } 572 try std.testing.expectError(error.NotFound, evt.currentMoment(repo_opts, &target)); 573 574 // 575 // publish the metadata and revision pointer 576 // 577 578 try fork.publish(repo_opts, io, allocator, &admin, &target, draft_path, .{ 579 .id = patch_id_hex, 580 .user_id = user_id, 581 .repo_id = repo_id, 582 .author = author, 583 .timestamp = 3, 584 }); 585 586 var arena = std.heap.ArenaAllocator.init(allocator); 587 defer arena.deinit(); 588 const moment = try evt.currentMoment(repo_opts, &target); 589 const patch = (try evt.Patch.readById(Repo.DB, repo_opts.hash, moment, &arena, &patch_id)) orelse return error.NotFound; 590 try std.testing.expectEqualStrings(&first_patchrev_hex, &(patch.event.patchrev_id orelse return error.NotFound)); 591 try std.testing.expectEqual(null, try evt.PatchRev.readById(Repo.DB, repo_opts.hash, moment, &arena, &first_patchrev_id)); 592 593 _ = arena.reset(.retain_capacity); 594 const admin_moment = try evt.currentMoment(evt.admin_repo_opts, &admin); 595 const published_fork = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &arena, &patch_id)) orelse return error.NotFound; 596 try std.testing.expect(!published_fork.removed); 597 { 598 var draft = try Repo.open(io, allocator, .{ .path = draft_path }); 599 defer draft.deinit(io, allocator); 600 _ = arena.reset(.retain_capacity); 601 const draft_moment = try evt.currentMoment(repo_opts, &draft); 602 const removed = (try evt.Patch.readById(Repo.DB, repo_opts.hash, draft_moment, &arena, &patch_id)) orelse return error.NotFound; 603 try std.testing.expect(removed.removed); 604 const revision = (try evt.PatchRev.readById(Repo.DB, repo_opts.hash, draft_moment, &arena, &first_patchrev_id)) orelse return error.NotFound; 605 try std.testing.expectEqualStrings("refs/heads/master", revision.event.target_ref); 606 try std.testing.expectEqual(hash.hexLen(repo_opts.hash), revision.event_oid.len); 607 var event_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; 608 @memcpy(&event_oid, revision.event_oid); 609 var repo_moment = try draft.core.latestMoment(); 610 const state = Repo.State(.read_only){ .core = &draft.core, .extra = .{ .moment = &repo_moment } }; 611 var event_object = try obj.Object(.xit, repo_opts).initCommit(state, io, allocator, &event_oid); 612 defer event_object.deinit(); 613 const trees = try evt.PatchRev.readTrees(.xit, repo_opts, state, io, allocator, &event_object.content.commit.tree); 614 try std.testing.expectEqualStrings(revision.base_tree_oid, &trees.base); 615 try std.testing.expectEqualStrings(revision.head_tree_oid, &trees.head); 616 } 617 } 618