╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1194 defer author_arena.deinit(); │ │1195 const author = (try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request); │ │1196 │ │1132 - updateIssue(io, allocator, host, parts, .{ .status = status }, author) catch |err| switch (err) { │ │1197 + (switch (parts.thread_kind) { │ │1198 + .issue => updateThread(evt.Issue, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .status = if (open) .open else .closed }, author),│ │1199 + .patch => updateThread(evt.Patch, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .status = if (open) .open else .closed }, author),│ │1200 + else => return respondRemoveNotFound(request), │ │1201 + }) catch |err| switch (err) { │ │1202 error.NotFound => { │ │1203 try request.respond(not_found, .{ │ │1204 .status = .not_found, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1231 defer author_arena.deinit(); │ │1232 const author = (try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request);│ │1233 │ │1165 - var body_buf: [256]u8 = undefined; │ │1166 - const reader = request.readerExpectNone(&body_buf); │ │1167 - const body = reader.allocRemaining(allocator, .limited(65536)) catch &[_]u8{}; │ │1234 + const body = try readFormBody(request, allocator); │ │1235 defer allocator.free(body); │ │1236 │ │1237 const title = (try parseFormField(allocator, body, "title")) orelse try allocator.dupe(u8, ""); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │1248 // invalid fields send the user back to the edit form │ │1249 const valid = switch (parts.thread_kind) { │ │1250 .issue => evt.Issue.fieldsValid(title, tags), │ │1251 + .patch => evt.Patch.fieldsValid(title, tags), │ │1252 .discuss => evt.Discussion.fieldsValid(title, tags),│ │1253 else => unreachable, │ │1254 }; │ ╰──────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │1264 │ │1265 const not_found = switch (parts.thread_kind) {│ │1266 .issue => "issue not found", │ │1267 + .patch => "patch not found", │ │1268 .discuss => "discussion not found", │ │1269 else => unreachable, │ │1270 }; │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1271 (switch (parts.thread_kind) { │ │1203 - .issue => updateIssue(io, allocator, host, .{ .repo_base = parts.repo_base, .id_bytes = parts.thread_id }, .{ .fields = .{│ │1272 + .issue => updateThread(evt.Issue, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .fields = .{ │ │1273 + .title = title, │ │1274 + .tags = tags, │ │1275 + .description = description, │ │1276 + } }, author), │ │1277 + .patch => updateThread(evt.Patch, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .fields = .{ │ │1278 .title = title, │ │1279 .tags = tags, │ │1280 .description = description, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │1298 }); │ │1299 } │ │1300 │ │1227 -// resolve the conflict of the issue the url names (base is │ │1228 -// "/repo/<owner>/<name>/issue:<id>", identity elided in local mode; the│ │1229 -// form posts to "<base>/resolve") │ │1230 -fn handleIssueResolve( │ │1301 +// resolve every conflicted field of the thread the url names │ │1302 +fn handleThreadResolve( │ │1303 io: std.Io, │ │1304 request: *std.http.Server.Request, │ │1305 allocator: std.mem.Allocator, │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1310 defer author_arena.deinit(); │ │1311 const author = (try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request);│ │1312 │ │1241 - var body_buf: [256]u8 = undefined; │ │1242 - const reader = request.readerExpectNone(&body_buf); │ │1243 - const body = reader.allocRemaining(allocator, .limited(65536)) catch &[_]u8{}; │ │1313 + const body = try readFormBody(request, allocator); │ │1314 defer allocator.free(body); │ │1315 │ │1316 var field_arena = std.heap.ArenaAllocator.init(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1329 try hunks.append(aa, try std.mem.replaceOwned(u8, aa, posted, "\r\n", "\n")); │ │1330 } │ │1331 │ │1262 - const not_found = "issue not found"; │ │1263 - const parts = issueBaseParts(base) orelse { │ │1264 - try request.respond(not_found, .{ │ │1332 + const parts = commentBaseParts(base) orelse { │ │1333 + try request.respond("thread not found", .{ │ │1334 + .status = .not_found, │ │1335 + .extra_headers = &.{.{ .name = "content-type", .value = "text/plain" }}, │ │1336 + }); │ │1337 + return; │ │1338 + }; │ │1339 + if (parts.comment_id != null or (parts.thread_kind != .issue and parts.thread_kind != .patch)) {│ │1340 + try request.respond("thread not found", .{ │ │1341 + .status = .not_found, │ │1342 + .extra_headers = &.{.{ .name = "content-type", .value = "text/plain" }}, │ │1343 + }); │ │1344 + return; │ │1345 + } │ │1346 + const route = ui.RoutablePage.fromUrl(base) orelse ui.RoutablePage.fromUrlLocal(base) orelse { │ │1347 + try request.respond("thread not found", .{ │ │1348 .status = .not_found, │ │1349 .extra_headers = &.{.{ .name = "content-type", .value = "text/plain" }}, │ │1350 }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1351 return; │ │1352 }; │ │1353 + const theirs: []const u8 = switch (route) { │ │1354 + .repo_patches => |*patch| patch.theirs.slice(), │ │1355 + .repo_issues => "", │ │1356 + else => unreachable, │ │1357 + }; │ │1358 + const not_found = if (parts.thread_kind == .patch) "patch not found" else "issue not found"; │ │1359 │ │1271 - updateIssue(io, allocator, host, parts, .{ .resolve = .{ │ │1272 - .title = title, │ │1273 - .tags = tags, │ │1274 - .hunks = hunks.items, │ │1275 - } }, author) catch |err| switch (err) { │ │1360 + (switch (parts.thread_kind) { │ │1361 + .issue => updateThread(evt.Issue, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .resolve = .{│ │1362 + .title = title, │ │1363 + .tags = tags, │ │1364 + .hunks = hunks.items, │ │1365 + } }, author), │ │1366 + .patch => updateThread(evt.Patch, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .resolve = .{│ │1367 + .title = title, │ │1368 + .tags = tags, │ │1369 + .hunks = hunks.items, │ │1370 + .theirs = theirs, │ │1371 + } }, author), │ │1372 + else => unreachable, │ │1373 + }) catch |err| switch (err) { │ │1374 error.NotFound => { │ │1375 try request.respond(not_found, .{ │ │1376 .status = .not_found, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │1986 return null; │ │1987 } │ │1988 │ │1989 +fn readFormBody(request: *std.http.Server.Request, allocator: std.mem.Allocator) ![]u8 {│ │1990 + var buffer: [256]u8 = undefined; │ │1991 + return request.readerExpectNone(&buffer).allocRemaining(allocator, .limited(65536));│ │1992 +} │ │1993 + │ │1994 fn decodeFormValue(allocator: std.mem.Allocator, encoded: []const u8) ![]u8 { │ │1995 var out: std.ArrayList(u8) = .empty; │ │1996 errdefer out.deinit(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯