╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────╮ │1732 + users_dir: []const u8, │ │1733 admin_repo_path: []const u8,│ │1734 owner_name: []const u8, │ │1735 repo_name: []const u8, │ ╰──────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1736 create_options_maybe: ?CreateRepoOptions, │ │1742 -) !?[event_id_size * 2]u8 { │ │1737 +) !?RepoLocation { │ │1738 var admin_repo = rp.Repo(.xit, admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }) catch |err| switch (err) {│ │1739 error.RepoNotFound => return null, │ │1740 else => |e| return e, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1748 │ │1749 // an unknown owner can't own a repo, so the push is rejected │ │1750 const owner_user_id = (try User.readIdByName(AdminDB, admin_repo_opts.hash, admin_moment, owner_name)) orelse return null;│ │1756 - const repo_root_path = try std.fs.path.join(allocator, &.{ std.fs.path.dirname(admin_repo_path) orelse ".", "repos" }); │ │1757 - defer allocator.free(repo_root_path); │ │1758 - var user_repo = (try openUserRepo(io, allocator, repo_root_path, &owner_user_id)) orelse return null; │ │1751 + var user_repo = (try openUserRepo(io, allocator, users_dir, &owner_user_id)) orelse return null; │ │1752 defer user_repo.deinit(io, allocator); │ │1753 │ │1754 // an already-registered repo reuses its event id, so a re-push (or a clone) │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1755 // lands in the same repo │ │1756 if (try userMoment(&user_repo)) |moment| { │ │1757 if (try Repo.readByName(UserDB, user_repo_opts.hash, moment, &arena, repo_name)) |found| {│ │1765 - return std.fmt.bytesToHex(found.event_id, .lower); │ │1758 + return .{ .owner_id = owner_user_id, .repo_id = found.event_id }; │ │1759 } │ │1760 } │ │1761 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │1779 } }, │ │1780 }}); │ │1781 │ │1789 - return event_id_hex; │ │1782 + return .{ .owner_id = owner_user_id, .repo_id = id_bytes };│ │1783 } │ │1784 │ │1785 // │ ╰─────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │210 return user_id; │ │211 } │ │212 │ │213 -// a user's id via the email->user-id index │ │214 -pub fn readIdByEmail( │ │215 - comptime DB: type, │ │216 - comptime hash_kind: hash.HashKind, │ │217 - haxy_moment: DB.HashMap(.read_only), │ │218 - email: []const u8, │ │219 -) !?[evt.event_id_size]u8 { │ │220 - const index_cursor = (try haxy_moment.getCursor(hash.hashInt(hash_kind, email_to_user_id_key))) orelse return null;│ │221 - const index = try DB.HashMap(.read_only).init(index_cursor); │ │222 - const user_id_cursor = (try index.getCursor(hash.hashInt(hash_kind, email))) orelse return null; │ │223 - var user_id: [evt.event_id_size]u8 = undefined; │ │224 - _ = try user_id_cursor.readBytes(&user_id); │ │225 - return user_id; │ │226 -} │ │227 - │ │213 // a user's record map via the event-id->user index │ │214 fn userMap( │ │215 comptime DB: type, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 if (identity.len == 0 or id_text.len != evt.event_id_size * 2) return null; │ │27 const id_bytes = evt.parseEventId(id_text) catch return null; │ │28 return .{ .identity = identity, .id = std.fmt.bytesToHex(id_bytes, .lower) }; │ │29 +} │ │30 + │ │31 +// a fork's path, inside its forker's dir │ │32 +pub fn forkPath(allocator: std.mem.Allocator, users_dir: []const u8, forker_id: *const [evt.event_id_size]u8, fork_id: *const [evt.event_id_size]u8) ![]u8 {│ │33 + return try std.fs.path.join(allocator, &.{ users_dir, &std.fmt.bytesToHex(forker_id.*, .lower), "forks", &std.fmt.bytesToHex(fork_id.*, .lower) }); │ │34 } │ │35 │ │31 -pub fn forkPath(allocator: std.mem.Allocator, repo_root_path: []const u8, id: []const u8) ![]u8 { │ │32 - _ = try evt.parseEventId(id); │ │33 - return try std.fs.path.join(allocator, &.{ std.fs.path.dirname(repo_root_path) orelse ".", "forks", id }); │ │36 +const forker_section = "haxy"; │ │37 +const forker_prefix = "fork-"; │ │38 + │ │39 +// the target repo config name recording who forked a published patch │ │40 +pub fn forkerConfigName(patch_id: *const [evt.event_id_size]u8) [forker_section.len + 1 + forker_prefix.len + evt.event_id_size * 2]u8 { │ │41 + return (forker_section ++ "." ++ forker_prefix).* ++ std.fmt.bytesToHex(patch_id.*, .lower); │ │42 +} │ │43 + │ │44 +// the forker of a published patch from the target repo's config, or null when it has no fork │ │45 +pub fn readForkerId(sections: *const xit.config.Sections, patch_id: *const [evt.event_id_size]u8) !?[evt.event_id_size]u8 { │ │46 + const section = sections.get(forker_section) orelse return null; │ │47 + const name = forkerConfigName(patch_id); │ │48 + return try evt.parseEventId(section.get(name[forker_section.len + 1 ..]) orelse return null); │ │49 } │ │50 │ │51 pub const CreateInput = struct { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │65 comptime repo_opts: rp.RepoOpts(.xit), │ │66 io: std.Io, │ │67 allocator: std.mem.Allocator, │ │53 - repo_root_path: []const u8, │ │54 - user_repo: *rp.Repo(.xit, evt.user_repo_opts), │ │68 + users_dir: []const u8, │ │69 input: CreateInput, │ │70 ) ![]u8 { │ │71 if (!evt.Patch.fieldsValid(input.title, input.labels)) return error.InvalidPatch;│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │73 │ │74 // get the fork id and path │ │75 const fork_id = try evt.parseEventId(&input.id); │ │62 - const fork_path = try forkPath(allocator, repo_root_path, &input.id); │ │76 + const fork_path = try forkPath(allocator, users_dir, &input.user_id, &fork_id);│ │77 errdefer allocator.free(fork_path); │ │78 │ │79 // make sure the fork id doesn't already exist │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │80 + var user_repo = (try evt.openUserRepo(io, allocator, users_dir, &input.user_id)) orelse return error.InvalidPatchDraft;│ │81 + defer user_repo.deinit(io, allocator); │ │82 var arena = std.heap.ArenaAllocator.init(allocator); │ │83 defer arena.deinit(); │ │68 - const existing = if (try evt.userMoment(user_repo)) |moment| │ │84 + const existing = if (try evt.userMoment(&user_repo)) |moment| │ │85 try evt.Fork.readById(evt.UserDB, evt.user_repo_opts.hash, moment, &arena, &fork_id) │ │86 else │ │87 null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯