╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 - .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), │ │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, .locked_branches = pusher.locked_branches }, pusher.author, handler.server, handler.err, sess),│ │418 }, │ │419 } │ │420 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────╮ │559 const Pusher = struct { │ │560 author: evt.CommitAuthor, │ │561 protected_branches: []const []const u8,│ │562 + locked_branches: []const []const u8, │ │563 }; │ │564 │ │565 // the owner of a repo a push may create │ ╰────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │601 // any key may fetch a public repo; a push always needs a key holder │ │602 if (service == .upload_pack and repo.role != null) return .{ .allowed = .fetch }; │ │603 │ │603 - // a key can be shared, so a holder whose role falls short doesn't end the search │ │604 + // a key can be shared, so the push acts as the holder with the highest role │ │605 const owner_id = try evt.parseEventId(&repo.repo.event.user_id); │ │606 + var best: ?struct { id: [evt.event_id_size]u8, role: evt.Repo.Role } = null; │ │607 for (try evt.User.readIdsByFingerprint(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, fingerprint)) |holder_id| { │ │608 const seen = (try evt.readRepoById(io, allocator, arena, users_dir, &owner_id, &repo.event_id, holder_id)) orelse return .not_found;│ │609 const role = seen.role orelse continue; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │608 - if (!role.atLeast(min_role)) continue; │ │609 - return .{ .allowed = switch (service) { │ │610 - .upload_pack => .fetch, │ │611 - .receive_pack => .{ .push = .{ │ │612 - .author = try commitAuthor(moment, arena, &holder_id), │ │613 - .protected_branches = try repo.repo.event.protectedBranchNames(arena.allocator()),│ │614 - } }, │ │615 - } }; │ │610 + if (best) |held| if (held.role.atLeast(role)) continue; │ │611 + best = .{ .id = holder_id, .role = role }; │ │612 } │ │617 - return .denied; │ │613 + const holder = best orelse return .denied; │ │614 + if (!holder.role.atLeast(min_role)) return .denied; │ │615 + return .{ │ │616 + .allowed = switch (service) { │ │617 + .upload_pack => .fetch, │ │618 + .receive_pack => blk: { │ │619 + const names = try repo.repo.event.protectedBranchNames(arena.allocator()); │ │620 + // members change protected branches only by merging a patch │ │621 + const owner = holder.role == .owner; │ │622 + break :blk .{ .push = .{ │ │623 + .author = try commitAuthor(moment, arena, &holder.id), │ │624 + .protected_branches = if (owner) names else &.{}, │ │625 + .locked_branches = if (owner) &.{} else names, │ │626 + } }; │ │627 + }, │ │628 + }, │ │629 + }; │ │630 } │ │631 │ │632 // an active user as a commit author │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯