╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │366 } │ │367 │ │368 try consume(repo_opts, io, allocator, repo, ref); │ │369 +} │ │370 + │ │371 +// split a pushed "<owner>/<repo>" path into its two components, or null if it │ │372 +// isn't exactly two non-empty segments. │ │373 +pub fn parseOwnerRepoPath(path: []const u8) ?struct { owner: []const u8, name: []const u8 } { │ │374 + var it = std.mem.splitScalar(u8, path, '/'); │ │375 + const owner = it.next() orelse return null; │ │376 + const name = it.next() orelse return null; │ │377 + if (it.next() != null) return null; │ │378 + if (owner.len == 0 or name.len == 0) return null; │ │379 + │ │380 + return .{ .owner = owner, .name = name }; │ │381 +} │ │382 + │ │383 +// resolve a pushed `<owner>/<repo>` to the hex event id that names its on-disk │ │384 +// directory under the repos dir │ │385 +pub fn resolveOrCreateRepo( │ │386 + io: std.Io, │ │387 + allocator: std.mem.Allocator, │ │388 + admin_repo_path: []const u8, │ │389 + owner_name: []const u8, │ │390 + repo_name: []const u8, │ │391 + create_if_missing: bool, │ │392 +) !?[event_id_size * 2]u8 { │ │393 + var repo = rp.Repo(.xit, admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }) catch |err| switch (err) { │ │394 + error.RepoNotFound => return null, │ │395 + else => |e| return e, │ │396 + }; │ │397 + defer repo.deinit(io, allocator); │ │398 + │ │399 + var arena = std.heap.ArenaAllocator.init(allocator); │ │400 + defer arena.deinit(); │ │401 + │ │402 + const moment = try currentMoment(admin_repo_opts, &repo); │ │403 + │ │404 + // an already-registered repo reuses its event id, so a re-push (or a clone) │ │405 + // lands in the same repo │ │406 + if (try Repo.readByOwnerAndName(AdminDB, admin_repo_opts.hash, moment, &arena, owner_name, repo_name)) |found| { │ │407 + return std.fmt.bytesToHex(found.event_id, .lower); │ │408 + } │ │409 + │ │410 + if (!create_if_missing) return null; │ │411 + │ │412 + // the new repo is owned by the named user, read from the name index; an │ │413 + // unknown owner can't own a repo, so the push is rejected │ │414 + const name_to_user_id_cursor = try moment.getCursor(hash.hashInt(admin_repo_opts.hash, "name->user-id")) orelse return null;│ │415 + const name_to_user_id = try AdminDB.HashMap(.read_only).init(name_to_user_id_cursor); │ │416 + const owner_id_cursor = try name_to_user_id.getCursor(hash.hashInt(admin_repo_opts.hash, owner_name)) orelse return null; │ │417 + var owner_user_id: [event_id_size]u8 = undefined; │ │418 + _ = try owner_id_cursor.readBytes(&owner_user_id); │ │419 + │ │420 + var id_bytes: [event_id_size]u8 = undefined; │ │421 + io.random(&id_bytes); │ │422 + const event_id_hex = std.fmt.bytesToHex(id_bytes, .lower); │ │423 + │ │424 + try commitAndConsume(admin_repo_opts, io, allocator, &repo, events_ref, &[_]EventWithId{.{ │ │425 + .id = event_id_hex, │ │426 + .event = .{ .repo = .{ │ │427 + .user_id = &owner_user_id, │ │428 + .name = repo_name, │ │429 + .description = "", │ │430 + .enable_issue = true, │ │431 + } }, │ │432 + }}); │ │433 + │ │434 + return event_id_hex; │ │435 } │ │436 │ │437 // │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯