╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │342 const fork_record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, &fork_id)) orelse │ │343 return writeError(sess, "patch draft not found"); │ │344 if (fork_record.removed) return writeError(sess, "patch draft not found"); │ │345 - const target_repo = (try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, owner_repo.owner, owner_repo.name)) orelse│ │346 - return writeError(sess, "repo not found"); │ │347 - if (!std.mem.eql(u8, fork_record.event.repo_id, &target_repo.event_id)) return writeError(sess, "patch draft belongs to another repo"); │ │345 const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, fork_record.event.user_id)) orelse │ │346 return writeError(sess, "invalid patch draft"); │ │347 if (user.removed) return writeError(sess, "invalid patch draft"); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │353 var draft = rp.AnyRepo(.xit, .{}).open(io, allocator, .{ .path = draft_path }) catch │ │354 return writeError(sess, "patch draft not found"); │ │355 defer draft.deinit(io, allocator); │ │359 - switch (draft) { │ │360 - inline else => |*repo| { │ │361 - var reader_buf: [4096]u8 = undefined; │ │362 - var writer_buf: [4096]u8 = undefined; │ │363 - var reader = ssh.SessionReader.init(sess, &reader_buf); │ │364 - var writer = ssh.SessionWriter.init(sess, &writer_buf); │ │365 - switch (parsed.service) { │ │366 - .upload_pack => try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{}), │ │367 - .receive_pack => try repo.receivePack(io, allocator, &reader.interface, &writer.interface, .{}), │ │368 - } │ │369 - writer.interface.flush() catch {}; │ │370 - if (parsed.service == .receive_pack) { │ │371 - const source_oid = (try repo.readRef(io, fork.ref)) orelse return writeError(sess, "push HEAD to refs/heads/patch"); │ │372 - try fork.recordPush(io, allocator, &admin, &route.id, route.target, &source_oid, .{ │ │373 - .name = user.event.name, │ │374 - .email = user.event.email, │ │375 - }, @intCast(std.Io.Timestamp.now(io, .real).toSeconds())); │ │376 - } │ │377 - }, │ │356 + var reader_buf: [4096]u8 = undefined; │ │357 + var writer_buf: [4096]u8 = undefined; │ │358 + var reader = ssh.SessionReader.init(sess, &reader_buf); │ │359 + var writer = ssh.SessionWriter.init(sess, &writer_buf); │ │360 + if (parsed.service == .upload_pack) { │ │361 + switch (draft) { │ │362 + inline else => |*repo| try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{}), │ │363 + } │ │364 + } else { │ │365 + const target = (try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, &admin_arena, owner_repo.owner, owner_repo.name)) orelse │ │366 + return writeError(sess, "repo not found"); │ │367 + if (!std.mem.eql(u8, fork_record.event.repo_id, &target.event_id)) return writeError(sess, "patch draft belongs to another repo"); │ │368 + const target_id = std.fmt.bytesToHex(target.event_id, .lower); │ │369 + const target_path = try std.fs.path.join(allocator, &.{ handler.repo_root_path, &target_id }); │ │370 + defer allocator.free(target_path); │ │371 + const author: evt.CommitAuthor = .{ .name = user.event.name, .email = user.event.email }; │ │372 + const timestamp: u64 = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()); │ │373 + switch (draft) { │ │374 + inline else => |*repo| { │ │375 + var target_repo = rp.Repo(.xit, repo.self_repo_opts).open(io, allocator, .{ .path = target_path }) catch │ │376 + return writeError(sess, "repo not found or has the wrong hash"); │ │377 + defer target_repo.deinit(io, allocator); │ │378 + try fork.receivePack(repo.self_repo_opts, io, allocator, repo, &target_repo, &route.id, route.target, author, timestamp, &reader.interface, &writer.interface);│ │379 + }, │ │380 + } │ │381 } │ │382 + writer.interface.flush() catch {}; │ │383 try sess.exit(0); │ │384 return; │ │385 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/admin.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────╮ │395 .id = fork_id_hex, │ │396 .user_id = user_id, │ │397 .repo_id = repo_id, │ │398 - .target = "master", │ │398 + .title = "add a feature", │ │399 + .description = "a draft patch",│ │400 + .tags = "enhancement", │ │401 .author = author, │ │402 .timestamp = 2, │ │403 }); │ ╰────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 var record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.NotFound;│ │410 try std.testing.expectEqualSlices(u8, &user_id, record.event.user_id); │ │411 try std.testing.expectEqualSlices(u8, &repo_id, record.event.repo_id); │ │410 - try std.testing.expectEqualStrings("master", record.event.target); │ │411 - try std.testing.expectEqual(null, record.event.source_oid); │ │412 try std.testing.expectEqual(1, try indexedForkCount(moment, evt.Fork.user_id_to_fork_id_set_key, &user_id)); │ │413 try std.testing.expectEqual(1, try indexedForkCount(moment, evt.Fork.repo_id_to_fork_id_set_key, &repo_id)); │ │414 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │415 - const source_oid = "0123456789abcdef0123456789abcdef01234567"; │ │416 - try fork.recordPush(io, allocator, &admin, &fork_id_hex, "feature/topic", source_oid, author, 3); │ │417 - _ = arena.reset(.retain_capacity); │ │418 - moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │419 - record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.NotFound; │ │420 - try std.testing.expectEqualStrings("feature/topic", record.event.target); │ │421 - try std.testing.expectEqualStrings(source_oid, record.event.source_oid orelse return error.NotFound); │ │415 + var draft_repo = try rp.Repo(.xit, fork_repo_opts).open(io, allocator, .{ .path = draft_path }); │ │416 + defer draft_repo.deinit(io, allocator); │ │417 + const draft_moment = try evt.currentMoment(fork_repo_opts, &draft_repo); │ │418 + const draft = (try evt.Patch.readById(evt.EventDB(fork_repo_opts.hash), fork_repo_opts.hash, draft_moment, &arena, &fork_id)) orelse return error.NotFound;│ │419 + try std.testing.expectEqualStrings("add a feature", draft.event.title); │ │420 + try std.testing.expectEqual(null, draft.event.patchrev_id); │ │421 │ │422 const same_path = try fork.create(fork_repo_opts, io, allocator, repos_dir, &admin, .{ │ │423 .id = fork_id_hex, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │424 .user_id = user_id, │ │425 .repo_id = repo_id, │ │427 - .target = "feature/topic", │ │426 + .title = "add a feature", │ │427 + .description = "a draft patch",│ │428 + .tags = "enhancement", │ │429 .author = author, │ │430 .timestamp = 4, │ │431 }); │ ╰────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │452 fn indexedForkCount( │ │453 moment: evt.AdminDB.HashMap(.read_only), │ │454 index_key: []const u8, │ │454 - parent_id: *const [evt.event_id_size]u8, │ │455 + parent_id: []const u8, │ │456 ) !u64 { │ │457 const index_cursor = try moment.getCursor(hash.hashInt(evt.admin_repo_opts.hash, index_key)) orelse return 0;│ │458 const index = try evt.AdminDB.HashMap(.read_only).init(index_cursor); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────╮ │27 }; │ │28 } │ │29 │ │30 -test "patch lifecycle" { │ │30 +test "patch event conflicts, stacking, and gc" {│ │31 const io = std.testing.io; │ │32 const allocator = std.testing.allocator; │ │33 const temp_dir_name = "temp-event-patch"; │ ╰────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │110 .title = "add the answer", │ │111 .description = "adds a reusable answer constant", │ │112 .tags = "enhancement", │ │113 - .target_ref = "refs/heads/master", │ │113 .patchrev_id = std.fmt.bytesToHex(patchrev_id, .lower),│ │114 }; │ │115 const tree_entries = [_]evt.EventTreeEntry{ │ ╰────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │128 .timestamp = 3, │ │129 .author = author, │ │130 .tree_entries = &tree_entries, │ │132 - .event = .{ .patchrev = .{ .base_oid = &base_oid, .source_oid = &source_oid, .message = patch.title } }, │ │131 + .event = .{ .patchrev = .{ .base_oid = &base_oid, .source_oid = &source_oid, .target_ref = "refs/heads/master", .message = patch.title } },│ │132 }, │ │133 .{ │ │134 .id = std.fmt.bytesToHex(patch_id, .lower), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯