╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │159 switch (host) { │ │160 .server => |server| { │ │161 // "repo/new" and "user/new" come before "new", which would claim them │ │162 - const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, addkey, removekey, settings, grant, revoke, promote, demote, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, subscribe, mute, undo, clear }; │ │162 + const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, addkey, removekey, settings, grant, revoke, promote, demote, protect, unprotect, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, subscribe, mute, undo, clear };│ │163 inline for (@typeInfo(PostRoute).@"enum".field_names) |name| { │ │164 const suffix = "/" ++ name; │ │165 if (std.mem.endsWith(u8, path, suffix)) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │177 .revoke => handleMembers(io, request, allocator, base, host, .revoke), │ │178 .promote => handleMembers(io, request, allocator, base, host, .promote), │ │179 .demote => handleMembers(io, request, allocator, base, host, .demote), │ │180 + .protect => handleBranches(io, request, allocator, base, host, .protect), │ │181 + .unprotect => handleBranches(io, request, allocator, base, host, .unprotect),│ │182 .new => handleNew(io, request, allocator, base, host), │ │183 .edit => handleEdit(io, request, allocator, base, host), │ │184 .remove => handleRemove(io, request, allocator, base, host), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │801 return respondFormFailure(request, allocator, server.session_store, location, .{ .repo_members = .{ .failure = value, .name = name } }); │ │802 } │ │803 const location = try (ui.Repo.Members.landingRoute(identity, target.start, actor, action, name) orelse return error.RouteTooLong).toUrl(&arena); │ │804 + try request.respond("", .{ .status = .see_other, .keep_alive = false, .extra_headers = &.{.{ .name = "location", .value = location }} }); │ │805 +} │ │806 + │ │807 +// protect or unprotect a branch of the repo the branches url `base` names, as │ │808 +// one of its owners, then redirect back to the list. the add form names the │ │809 +// branch in its body and the row forms in the url. │ │810 +fn handleBranches(io: std.Io, request: *std.http.Server.Request, allocator: std.mem.Allocator, base: []const u8, host: Host, action: ui.Repo.Branches.Action) !void { │ │811 + const server = switch (host) { │ │812 + .server => |server| server, │ │813 + .local => return respondRepoNotFound(request), │ │814 + }; │ │815 + const route = routeFromUrl(host, base) orelse return respondRepoNotFound(request); │ │816 + if (route != .repo_branches) return respondRepoNotFound(request); │ │817 + const target = route.repo_branches; │ │818 + var arena = std.heap.ArenaAllocator.init(allocator); │ │819 + defer arena.deinit(); │ │820 + const identity = target.name.slice(); │ │821 + const actor = (try authorizeWrite(io, allocator, &arena, request, host, try arena.allocator().print("/{s}", .{identity}), .owner, null)) orelse return; │ │822 + │ │823 + const branch = switch (action) { │ │824 + .protect => blk: { │ │825 + const body = try readFormBody(request, allocator); │ │826 + defer allocator.free(body); │ │827 + break :blk (try parseFormField(arena.allocator(), body, "name")) orelse ""; │ │828 + }, │ │829 + .unprotect => if (target.branch.len != 0) std.Uri.percentDecodeInPlace(try arena.allocator().dupe(u8, target.branch.slice())) else return respondRepoNotFound(request),│ │830 + }; │ │831 + │ │832 + const failure = ui.Repo.Branches.perform(io, allocator, server.users_dir, actor, identity, action, branch) catch |err| switch (err) { │ │833 + error.NotFound => return respondRepoNotFound(request), │ │834 + else => return err, │ │835 + }; │ │836 + const location = try (ui.RoutablePage.repoBranchesRoute(identity) orelse return error.RouteTooLong).toUrl(&arena); │ │837 + if (failure) |value| return respondFormFailure(request, allocator, server.session_store, location, .{ .repo_branches = .{ .failure = value, .name = branch } }); │ │838 try request.respond("", .{ .status = .see_other, .keep_alive = false, .extra_headers = &.{.{ .name = "location", .value = location }} }); │ │839 } │ │840 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯