╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────╮ │ 7 name: []const u8, │ │ 8 description: []const u8, │ │ 9 read_access: Access = .private, │ │10 -write_access: Access = .private, │ │10 // the least role that may write in each tab, null turns the tab off│ │11 discuss_role: ?Role = .read, │ │12 issue_role: ?Role = .read, │ ╰────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │75 } │ │76 │ │77 // the role `user_id_maybe` holds in the repo, or null when they hold none: its│ │79 -// creator owns it, and anyone else gets the higher of the base role and their │ │80 -// grant. `user_moment` is the owner's, which holds the grants. a null user is │ │81 -// logged out. │ │78 +// creator owns it, and anyone else gets their grant, else the base role. │ │79 +// `user_moment` is the owner's, which holds the grants. a null user is logged │ │80 +// out. │ │81 pub fn roleOf( │ │82 comptime DB: type, │ │83 comptime hash_kind: hash.HashKind, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │87 repo_id: *const [evt.event_id_size]u8, │ │88 user_id_maybe: ?[evt.event_id_size]u8, │ │89 ) !?Role { │ │91 - // what the access fields give everyone, logged in or not │ │92 - const base_maybe: ?Role = if (record.event.write_access == .public) .write else if (record.event.read_access == .public) .read else null;│ │93 - const user_id = user_id_maybe orelse return base_maybe; │ │90 + // what read access gives everyone, logged in or not │ │91 + const base: ?Role = if (record.event.read_access == .public) .read else null; │ │92 + const user_id = user_id_maybe orelse return base; │ │93 if (std.mem.eql(u8, &record.event.user_id, &std.fmt.bytesToHex(user_id, .lower))) return .owner; │ │94 │ │96 - const granted = (try evt.Grant.readRole(DB, hash_kind, user_moment, arena, repo_id, &user_id)) orelse return base_maybe; │ │97 - const base = base_maybe orelse return granted; │ │98 - return if (granted.atLeast(base)) granted else base; │ │95 + // a grant is never below the base role │ │96 + return (try evt.Grant.readRole(DB, hash_kind, user_moment, arena, repo_id, &user_id)) orelse base; │ │97 } │ │98 │ │99 // the moment keys `evt.merge` reads and writes for this kind │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │49 state: rp.Repo(.xit, repo_opts).State(.read_write), │ │50 io: std.Io, │ │51 allocator: std.mem.Allocator, │ │52 - author: ?evt.CommitAuthor, │ │52 + author: evt.CommitAuthor, │ │53 updates: []const xit.net_server_receive_pack.AppliedRefUpdate, │ │54 ) !void { │ │55 // what the payload may take up: the record holds a timestamp and the│ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │60 defer arena.deinit(); │ │61 const aa = arena.allocator(); │ │62 │ │63 - const author_json: std.json.ObjectMap = if (author) |user| │ │64 - try .init(aa, &.{ "username", "email" }, &.{ .{ .string = user.name }, .{ .string = user.email } }) │ │65 - else │ │66 - .empty; │ │67 - var payload: std.json.ObjectMap = try .init(aa, &.{"author"}, &.{if (author != null) .{ .object = author_json } else .null}); │ │63 + const author_json: std.json.ObjectMap = try .init(aa, &.{ "username", "email" }, &.{ .{ .string = author.name }, .{ .string = author.email } });│ │64 + var payload: std.json.ObjectMap = try .init(aa, &.{"author"}, &.{.{ .object = author_json }}); │ │65 │ │66 // where each ref landed, so the undo tab can say what the push did to it. │ │67 // an oid of all zeros means the ref has no side there, which is how a │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │115 reader: *std.Io.Reader, │ │116 writer: *std.Io.Writer, │ │117 options: xit.net_server_receive_pack.Options,│ │121 - author: ?evt.CommitAuthor, │ │118 + author: evt.CommitAuthor, │ │119 server: evt.Host.Server, │ │120 error_writer: *std.Io.Writer, │ │121 sess: ?*ssh.SessionCtx, │ ╰──────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │138 reader: *std.Io.Reader, │ │139 writer: *std.Io.Writer, │ │140 options: xit.net_server_receive_pack.Options, │ │144 - author: ?evt.CommitAuthor, │ │141 + author: evt.CommitAuthor, │ │142 response: *xit.net_server_receive_pack.Response,│ │143 server: evt.Host.Server, │ │144 error_writer: *std.Io.Writer, │ ╰─────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 defer any_repo.deinit(io, allocator); │ │410 │ │411 switch (any_repo) { │ │412 - inline else => |*repo| switch (parsed.service) { │ │413 - .upload_pack => { │ │412 + inline else => |*repo| switch (allowed) { │ │413 + .fetch => { │ │414 var sideband = progress.Sideband{ .writer = &writer.interface, .sess = sess }; │ │415 try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version }, &sideband);│ │416 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │417 - .receive_pack => try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version, .protected_branches = allowed.protected_branches }, allowed.author, handler.server, handler.err, sess),│ │417 + .push => |pusher| try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version, .protected_branches = pusher.protected_branches }, pusher.author, handler.server, handler.err, sess), │ │418 }, │ │419 } │ │420 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │549 │ │550 const RepoAuthorization = union(enum) { allowed: Allowed, create: RepoCreator, denied, not_found };│ │551 │ │552 -// a key allowed into an existing repo │ │553 -const Allowed = struct { │ │554 - // null when the repo's base role let the key in │ │555 - author: ?evt.CommitAuthor, │ │552 +// what a key may do in an existing repo │ │553 +const Allowed = union(enum) { │ │554 + fetch, │ │555 + push: Pusher, │ │556 +}; │ │557 + │ │558 +// the key holder a push acts as │ │559 +const Pusher = struct { │ │560 + author: evt.CommitAuthor, │ │561 protected_branches: []const []const u8, │ │562 }; │ │563 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯