╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │151 .publish => handlePatchPublish(io, request, allocator, base, host), │ │152 .merge => handlePatchMerge(io, request, allocator, base, host, .source), │ │153 .squash => handlePatchMerge(io, request, allocator, base, host, .squash),│ │154 + .undo => handleUndo(io, request, allocator, base, host), │ │155 .attach => handleAttach(io, request, allocator, base, host), │ │156 }; │ │157 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 } │ │159 }, │ │160 .local => |local| { │ │160 - const PostRoute = enum { new, edit, remove, open, close, resolve, sync, attach }; │ │161 + const PostRoute = enum { new, edit, remove, open, close, resolve, sync, attach, undo };│ │162 inline for (@typeInfo(PostRoute).@"enum".fields) |field| { │ │163 const suffix = "/" ++ field.name; │ │164 if (std.mem.endsWith(u8, path, suffix)) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │171 .close => handleThreadStatus(io, request, allocator, base, host, false),│ │172 .resolve => handleThreadResolve(io, request, allocator, base, host), │ │173 .sync => handleSync(io, request, allocator, base, local), │ │174 + .undo => handleUndo(io, request, allocator, base, host), │ │175 .attach => handleAttach(io, request, allocator, base, host), │ │176 }; │ │177 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1708 .status = .see_other, │ │1709 .extra_headers = &.{.{ .name = "location", .value = base }}, │ │1710 }); │ │1711 +} │ │1712 + │ │1713 +fn handleUndo(io: std.Io, request: *std.http.Server.Request, allocator: std.mem.Allocator, base: []const u8, host: Host) !void { │ │1714 + const route = (switch (host) { │ │1715 + .local => ui.RoutablePage.fromUrlLocal(base), │ │1716 + .server => ui.RoutablePage.fromUrl(base), │ │1717 + }) orelse return respondRepoNotFound(request); │ │1718 + if (route != .repo_undo) return respondRepoNotFound(request); │ │1719 + const target = route.repo_undo; │ │1720 + const index = target.index orelse return respondRepoNotFound(request); │ │1721 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1722 + defer arena.deinit(); │ │1723 + const repo_base = if (target.name.len == 0) "" else try std.fmt.allocPrint(arena.allocator(), "/repo/{s}", .{target.name.slice()}); │ │1724 + _ = (try authorizeWrite(io, allocator, &arena, request, host, repo_base, .write)) orelse return; │ │1725 + const resolved = (try requestRepoSource(io, allocator, host, repo_base)) orelse return respondRepoNotFound(request); │ │1726 + defer resolved.deinit(allocator); │ │1727 + ui.Repo.Undo.execute(io, allocator, resolved.source, index) catch |err| switch (err) { │ │1728 + error.NotFound, error.InvalidHistoryIndex, error.TransactionNotFound => return respondRepoNotFound(request), │ │1729 + else => return err, │ │1730 + }; │ │1731 + const location = try (ui.RoutablePage.repoUndoRoute(target.name.slice(), null) orelse return error.RouteTooLong).toUrl(&arena); │ │1732 + try request.respond("", .{ .status = .see_other, .keep_alive = false, .extra_headers = &.{.{ .name = "location", .value = location }} });│ │1733 } │ │1734 │ │1735 fn handleSync( │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯