╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │88 if (existing != null) return error.InvalidPatchDraft; │ │89 │ │90 // get the target repo │ │75 - const target_id = std.fmt.bytesToHex(input.repo_id, .lower); │ │76 - const target_path = try std.fs.path.join(allocator, &.{ repo_root_path, &target_id }); │ │91 + const target_path = try evt.repoPath(allocator, users_dir, &input.repo_user_id, &input.repo_id); │ │92 defer allocator.free(target_path); │ │93 var target_repo = try rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = target_path, .require_repo_root = true });│ │94 defer target_repo.deinit(io, allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │137 core: *rp.Repo(.xit, repo_opts).Core, │ │138 io: std.Io, │ │139 allocator: std.mem.Allocator, │ │125 - forker_id: [evt.event_id_size * 2]u8, │ │140 │ │141 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │142 var moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │171 var config = try xit.config.Config(.xit, repo_opts).init(state.readOnly(), ctx.io, ctx.allocator); │ │172 defer config.deinit(); │ │173 try config.add(state, ctx.io, .{ .name = "receive.denydeletes", .value = "true" }); │ │160 - try config.add(state, ctx.io, .{ .name = "haxy.forker", .value = &ctx.forker_id }); │ │174 + │ │175 + // the copied db carries the target's forker entries │ │176 + if (config.local_sections.get(forker_section)) |section| { │ │177 + var names_arena = std.heap.ArenaAllocator.init(ctx.allocator); │ │178 + defer names_arena.deinit(); │ │179 + const na = names_arena.allocator(); │ │180 + var names: std.ArrayList([]const u8) = .empty; │ │181 + for (section.keys()) |key| { │ │182 + if (std.mem.startsWith(u8, key, forker_prefix)) try names.append(na, try std.fmt.allocPrint(na, forker_section ++ ".{s}", .{key}));│ │183 + } │ │184 + for (names.items) |name| try config.remove(state, ctx.io, .{ .name = name }); │ │185 + } │ │186 │ │187 // the copied db carries the target's file index, so the patch │ │188 // branch's entry starts from one of those rather than walking │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │199 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); │ │200 try history.appendContext( │ │201 .{ .slot = try history.getSlot(-1) }, │ │177 - Ctx{ .core = &fork_repo.core, .io = io, .allocator = allocator, .forker_id = std.fmt.bytesToHex(input.user_id, .lower) },│ │202 + Ctx{ .core = &fork_repo.core, .io = io, .allocator = allocator }, │ │203 ); │ │204 } │ │205 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │217 }}); │ │218 │ │219 // create the fork event │ │195 - try evt.consume(.server, .user, .xit, evt.user_repo_opts, io, allocator, user_repo, evt.events_ref, &.{.{ │ │220 + try evt.consume(.server, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, evt.events_ref, &.{.{│ │221 .id = input.id, │ │222 .timestamp = input.timestamp, │ │223 .author = input.author, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │234 pub fn remove( │ │235 io: std.Io, │ │236 allocator: std.mem.Allocator, │ │212 - repo_root_path: []const u8, │ │213 - user_repo: *rp.Repo(.xit, evt.user_repo_opts),│ │237 + users_dir: []const u8, │ │238 + forker_id: *const [evt.event_id_size]u8, │ │239 id: *const [evt.event_id_size * 2]u8, │ │240 author: evt.CommitAuthor, │ │241 ) !void { │ ╰───────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯