╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────╮ │120 user_auth: Array(evt.User.name_max_len),│ │121 repo_files: RepoFilesRoute, │ │122 repo_commits: RepoCommitsRoute, │ │123 + repo_diff: RepoDiffRoute, │ │124 repo_refs: struct { │ │125 name: Array(repo_route_max_len), │ │126 kind: RefKind = .branch, │ ╰─────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │193 │ │194 // the "key:value" segments of a url tail, collected in any order │ │195 const Params = struct { │ │195 - const ParamKey = enum { start, line, branch, tag, object, base, theirs }; │ │196 + const ParamKey = enum { start, line, branch, tag, object, base, patchrev, theirs };│ │197 │ │198 // a branch:/tag:/object: param; the value stays url-encoded │ │199 const RefParam = struct { kind: RefOrOid, value: []const u8 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │336 name: Array(repo_identity_max_len), │ │337 ref_kind: ?RefOrOid = null, │ │338 ref_value: Array(ref_route_max_len) = .{}, │ │339 + patchrev_id: Array(evt.event_id_size * 2) = .{}, │ │340 path: Array(repo_route_max_len) = .{}, │ │341 // the selected file's first visible line (1-based; 0 = unset).│ │342 line: usize = 0, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │343 }; │ │344 │ │345 + pub const RepoDiffRoute = struct { │ │346 + name: Array(repo_identity_max_len), │ │347 + ref_or_oid: ?RefOrOid = null, │ │348 + value: Array(ref_route_max_len) = .{}, │ │349 + base_oid: Array(ref_route_max_len) = .{}, │ │350 + patchrev_id: Array(evt.event_id_size * 2) = .{},│ │351 + start: usize = 0, │ │352 + path: Array(repo_route_max_len) = .{}, │ │353 + }; │ │354 + │ │355 pub const RepoCommitsRoute = struct { │ │356 name: Array(repo_identity_max_len), │ │357 ref_or_oid: ?RefOrOid = null, │ ╰─────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │520 } }; │ │521 } │ │522 │ │523 + pub fn repoPatchRevFilesRoute(identity: []const u8, id: []const u8, path: []const u8, line: usize) ?RoutablePage {│ │524 + _ = evt.parseEventId(id) catch return null; │ │525 + return .{ .repo_files = .{ │ │526 + .name = Array(repo_identity_max_len).from(identity) orelse return null, │ │527 + .patchrev_id = Array(evt.event_id_size * 2).from(id) orelse return null, │ │528 + .path = Array(repo_route_max_len).from(path) orelse return null, │ │529 + .line = line, │ │530 + } }; │ │531 + } │ │532 + │ │533 // build a `.repo_commits` route (a null ref_or_oid = the default branch). │ │534 // always carries the `commits` marker so the bare route doesn't collide │ │535 // with the files root. a non-empty `path` filters the diff pane to that │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │545 .start = start, │ │546 .path = Array(repo_route_max_len).from(path) orelse return null, │ │547 } }, │ │548 + } }; │ │549 + } │ │550 + │ │551 + pub fn repoDiffRoute(identity: []const u8, ref_or_oid: ?RefOrOid, value: []const u8, start: usize, path: []const u8, base_oid: []const u8) ?RoutablePage {│ │552 + if (base_oid.len == 0 or (ref_or_oid == null and value.len != 0)) return null; │ │553 + return .{ .repo_diff = .{ │ │554 + .name = Array(repo_identity_max_len).from(identity) orelse return null, │ │555 + .ref_or_oid = ref_or_oid, │ │556 + .value = Array(ref_route_max_len).from(value) orelse return null, │ │557 + .base_oid = Array(ref_route_max_len).from(base_oid) orelse return null, │ │558 + .start = start, │ │559 + .path = Array(repo_route_max_len).from(path) orelse return null, │ │560 } }; │ │561 } │ │562 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │563 + pub fn repoPatchRevDiffRoute(identity: []const u8, id: []const u8, start: usize, path: []const u8) ?RoutablePage { │ │564 + _ = evt.parseEventId(id) catch return null; │ │565 + return .{ .repo_diff = .{ │ │566 + .name = Array(repo_identity_max_len).from(identity) orelse return null, │ │567 + .patchrev_id = Array(evt.event_id_size * 2).from(id) orelse return null, │ │568 + .start = start, │ │569 + .path = Array(repo_route_max_len).from(path) orelse return null, │ │570 + } }; │ │571 + } │ │572 + │ │573 // build a `.repo_commits` route showing the ref/oid's commit message in │ │574 // full, which is the only content that page renders. │ │575 pub fn repoCommitMessageRoute(identity: []const u8, ref_or_oid: RefOrOid, value: []const u8, base_oid: []const u8) ?RoutablePage {│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │894 .user_auth => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/auth", .{name.slice()}), │ │895 .repo_files => |f| blk: { │ │896 const prefix = try repoUrlPrefix(arena, f.name.slice()); │ │853 - if (f.ref_kind == null and f.line == 0) break :blk if (prefix.len == 0) "/" else prefix; │ │897 + if (f.ref_kind == null and f.patchrev_id.len == 0 and f.line == 0) break :blk if (prefix.len == 0) "/" else prefix; │ │898 var out: std.Io.Writer.Allocating = .init(arena.allocator()); │ │899 try out.writer.print("{s}/" ++ files_seg, .{prefix}); │ │900 if (f.ref_kind) |kind| if (f.ref_value.len != 0) try out.writer.print("/{s}:{s}", .{ @tagName(kind), f.ref_value.slice() });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │901 + if (f.patchrev_id.len != 0) try out.writer.print("/patchrev:{s}", .{f.patchrev_id.slice()});│ │902 if (f.line != 0) try out.writer.print("/" ++ line_seg ++ "{d}", .{f.line}); │ │903 if (f.path.len != 0) try out.writer.print("/" ++ path_seg ++ "{s}", .{f.path.slice()}); │ │904 break :blk out.written(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │916 }, │ │917 .message => try out.writer.print("/" ++ message_seg, .{}), │ │918 } │ │919 + break :blk out.written(); │ │920 + }, │ │921 + .repo_diff => |d| blk: { │ │922 + const prefix = try repoUrlPrefix(arena, d.name.slice()); │ │923 + var out: std.Io.Writer.Allocating = .init(arena.allocator()); │ │924 + try out.writer.print("{s}/diff", .{prefix}); │ │925 + if (d.ref_or_oid) |kind| if (d.value.len != 0) try out.writer.print("/{s}:{s}", .{ @tagName(kind), d.value.slice() });│ │926 + if (d.patchrev_id.len != 0) try out.writer.print("/patchrev:{s}", .{d.patchrev_id.slice()}); │ │927 + if (d.base_oid.len != 0) try out.writer.print("/" ++ base_seg ++ "{s}", .{d.base_oid.slice()}); │ │928 + if (d.start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{d.start}); │ │929 + if (d.path.len != 0) try out.writer.print("/" ++ path_seg ++ "{s}", .{d.path.slice()}); │ │930 break :blk out.written(); │ │931 }, │ │932 .repo_refs => |r| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯