╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1859 const create_options = create_options_maybe orelse return null; │ │1860 │ │1861 const owner = (try User.readById(AdminDB, admin_repo_opts.hash, admin_moment, &arena, &owner_user_id)) orelse unreachable; │ │1862 + │ │1863 + var id_bytes: [event_id_size]u8 = undefined; │ │1864 + io.random(&id_bytes); │ │1865 + try writeRepoEvent(io, allocator, users_dir, &user_repo, &owner_user_id, .{ .name = owner.event.name, .email = owner.event.email }, &id_bytes, repo_name, "", create_options.read_access);│ │1866 + return .{ .owner_id = owner_user_id, .repo_id = id_bytes }; │ │1867 +} │ │1868 + │ │1869 +// create a bare repo with `hash_kind` and register it in its owner's user repo │ │1870 +pub fn createRepo( │ │1871 + io: std.Io, │ │1872 + allocator: std.mem.Allocator, │ │1873 + users_dir: []const u8, │ │1874 + owner_id: *const [event_id_size]u8, │ │1875 + owner: CommitAuthor, │ │1876 + name: []const u8, │ │1877 + description: []const u8, │ │1878 + read_access: Repo.Access, │ │1879 + hash_kind: hash.HashKind, │ │1880 +) !RepoLocation { │ │1881 + try Repo.validateName(name); │ │1882 + var user_repo = (try openUserRepo(io, allocator, users_dir, owner_id)) orelse return error.NotFound; │ │1883 + defer user_repo.deinit(io, allocator); │ │1884 + if (try userMoment(&user_repo)) |moment| { │ │1885 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1886 + defer arena.deinit(); │ │1887 + if (try Repo.readByName(UserDB, user_repo_opts.hash, moment, &arena, name) != null) return error.NameTaken; │ │1888 + } │ │1889 │ │1890 var id_bytes: [event_id_size]u8 = undefined; │ │1891 io.random(&id_bytes); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1865 - const event_id_hex = std.fmt.bytesToHex(id_bytes, .lower); │ │1892 + │ │1893 + // the directory comes first so a listed repo always has one │ │1894 + { │ │1895 + const path = try repoPath(allocator, users_dir, owner_id, &id_bytes); │ │1896 + defer allocator.free(path); │ │1897 + switch (hash_kind) { │ │1898 + inline else => |kind| { │ │1899 + var repo = try rp.Repo(.xit, .{ .hash = kind }).init(io, allocator, .{ .path = path, .bare = true }); │ │1900 + defer repo.deinit(io, allocator); │ │1901 + }, │ │1902 + } │ │1903 + } │ │1904 + │ │1905 + try writeRepoEvent(io, allocator, users_dir, &user_repo, owner_id, owner, &id_bytes, name, description, read_access); │ │1906 + return .{ .owner_id = owner_id.*, .repo_id = id_bytes }; │ │1907 +} │ │1908 │ │1909 +// consume a new repo's event into its owner's user repo and mark it active │ │1910 +fn writeRepoEvent( │ │1911 + io: std.Io, │ │1912 + allocator: std.mem.Allocator, │ │1913 + users_dir: []const u8, │ │1914 + user_repo: *rp.Repo(.xit, user_repo_opts), │ │1915 + owner_id: *const [event_id_size]u8, │ │1916 + owner: CommitAuthor, │ │1917 + id_bytes: *const [event_id_size]u8, │ │1918 + name: []const u8, │ │1919 + description: []const u8, │ │1920 + read_access: Repo.Access, │ │1921 +) !void { │ │1922 const timestamp: u64 = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()); │ │1868 - try consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, user_repo_opts, io, allocator, &user_repo, events_ref, &[_]EventWithId{.{│ │1869 - .id = event_id_hex, │ │1923 + try consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, user_repo_opts, io, allocator, user_repo, events_ref, &[_]EventWithId{.{ │ │1924 + .id = std.fmt.bytesToHex(id_bytes.*, .lower), │ │1925 .timestamp = timestamp, │ │1871 - .author = .{ .name = owner.event.name, .email = owner.event.email }, │ │1926 + .author = owner, │ │1927 .event = .{ .repo = .{ │ │1873 - .user_id = &owner_user_id, │ │1874 - .name = repo_name, │ │1875 - .description = "", │ │1876 - .read_access = create_options.read_access, │ │1928 + .user_id = owner_id, │ │1929 + .name = name, │ │1930 + .description = description, │ │1931 + .read_access = read_access, │ │1932 } }, │ │1933 }}); │ │1879 - bumpRepoActivity(io, allocator, users_dir, &owner_user_id, &id_bytes, timestamp) catch |err| { │ │1934 + bumpRepoActivity(io, allocator, users_dir, owner_id, id_bytes, timestamp) catch |err| { │ │1935 std.log.warn("failed to record repo activity: {s}", .{@errorName(err)}); │ │1936 }; │ │1882 - │ │1883 - return .{ .owner_id = owner_user_id, .repo_id = id_bytes }; │ │1937 } │ │1938 │ │1939 // │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────────╮ │62 }; │ │63 │ │64 pub fn create( │ │65 - comptime repo_opts: rp.RepoOpts(.xit), │ │65 + comptime any_opts: rp.AnyRepoOpts(.xit),│ │66 io: std.Io, │ │67 allocator: std.mem.Allocator, │ │68 users_dir: []const u8, │ ╰────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 87 null; │ │ 88 if (existing != null) return error.InvalidPatchDraft; │ │ 89 │ │ 90 - // get the target repo │ │ 90 + // the fork copies the target's db, so it takes the target's hash kind │ │ 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); │ │ 93 + var any_target = try rp.AnyRepo(.xit, any_opts).open(io, allocator, .{ .path = target_path, .require_repo_root = true }); │ │ 94 + defer any_target.deinit(io, allocator); │ │ 95 + switch (any_target) { │ │ 96 + inline else => |*target_repo| try copyTarget(target_repo.self_repo_opts, io, allocator, users_dir, target_repo, &user_repo, fork_path, input),│ │ 97 + } │ │ 98 + return fork_path; │ │ 99 +} │ │100 + │ │101 +// copy the target into a new fork at `fork_path` and record its patch and fork events │ │102 +fn copyTarget( │ │103 + comptime repo_opts: rp.RepoOpts(.xit), │ │104 + io: std.Io, │ │105 + allocator: std.mem.Allocator, │ │106 + users_dir: []const u8, │ │107 + target_repo: *rp.Repo(.xit, repo_opts), │ │108 + user_repo: *rp.Repo(.xit, evt.user_repo_opts), │ │109 + fork_path: []const u8, │ │110 + input: CreateInput, │ │111 +) !void { │ │112 if ((try target_repo.readRef(io, .{ .kind = .head, .name = input.target_branch })) == null) return error.TargetNotFound; │ │113 │ │114 // create the fork repo dir │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │234 }}); │ │235 │ │236 // create the fork event │ │220 - try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, evt.events_ref, &.{.{│ │237 + try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, user_repo, evt.events_ref, &.{.{ │ │238 .id = input.id, │ │239 .timestamp = input.timestamp, │ │240 .author = input.author, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │243 .repo_user_id = &input.repo_user_id, │ │244 } }, │ │245 }}); │ │229 - │ │230 - return fork_path; │ │246 } │ │247 │ │248 // tombstone first so a missing fork never remains visible│ ╰───────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/admin.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │365 try target.add(io, allocator, &.{"README"}); │ │366 _ = try target.commit(io, allocator, .{ .message = "initial" }); │ │367 │ │368 - const draft_path = try fork.create(fork_repo_opts, io, allocator, users_dir, .{ │ │368 + const draft_path = try fork.create(.{ .is_test = true }, io, allocator, users_dir, .{│ │369 .target_branch = "master", │ │370 .id = fork_id_hex, │ │371 .user_id = user_id, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │882 │ │883 const patch_id = evt.EventWithId.randomId(prng.random()); │ │884 const patch_id_hex = std.fmt.bytesToHex(patch_id, .lower); │ │885 - const draft_path = try fork.create(repo_opts, io, allocator, users_dir, .{ │ │885 + const draft_path = try fork.create(.{ .is_test = true }, io, allocator, users_dir, .{│ │886 .target_branch = "master", │ │887 .id = patch_id_hex, │ │888 .user_id = user_id, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │968 │ │969 const unpushed_id = evt.EventWithId.randomId(prng.random()); │ │970 const unpushed_id_hex = std.fmt.bytesToHex(unpushed_id, .lower); │ │971 - const unpushed_path = try fork.create(repo_opts, io, allocator, users_dir, .{ │ │971 + const unpushed_path = try fork.create(.{ .is_test = true }, io, allocator, users_dir, .{│ │972 .id = unpushed_id_hex, │ │973 .user_id = user_id, │ │974 .repo_id = repo_id, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │548 @memcpy(&user_id, target.repo.event.user_id); │ │549 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, &user_id)) orelse return error.NotFound;│ │550 defer user_repo.deinit(io, allocator); │ │551 - break :blk try fork.create(repo_opts, io, allocator, users_dir, .{ │ │551 + break :blk try fork.create(.{}, io, allocator, users_dir, .{ │ │552 .id = fork_id_hex, │ │553 .user_id = user_id, │ │554 .repo_id = repo_id, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯