╭ changed ──────╮ │src/test/ui.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │15 │ │16 const data = Commits{ │ │17 .identity = identity, │ │18 + .ref_or_oid = .object, │ │19 + .ref_or_oid_value = oid0, │ │20 .commits = &.{ │ │21 .{ .oid = oid0, .date = "2024-01-01", .message = "first", .hunks = &.{}, .window_start = 0, .has_prev = false, .has_more = false },│ │22 }, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │24 }; │ │25 │ │26 var session = ui.Session{ .arena = &arena, .page_arena = &arena, .is_terminal = true }; │ │25 - session.data.current_page = ui.RoutablePage.repoCommitsRoute(identity, oid0, 0).?; │ │27 + session.data.current_page = ui.RoutablePage.repoCommitsRoute(identity, .object, oid0, 0).?;│ │28 │ │29 var view = try Commits.View.init(allocator, &data, &session); │ │30 defer view.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────╮ │1 const std = @import("std"); │ │2 const xit = @import("xit"); │ │3 const rp = xit.repo; │ │4 +const rf = xit.ref; │ │5 +const hash = xit.hash; │ │6 const xitui = xit.xitui; │ │7 const term = xitui.terminal;│ │8 const wgt = xitui.widget; │ ╰───────────────────────────────╯ ╭──────────────────────────────────────────────────────────╮ │84 pub const RefOrOid = enum { │ │85 branch, │ │86 tag, │ │85 - oid, │ │87 + object, │ │88 │ │89 fn fromSeg(s: []const u8) ?RefOrOid { │ │90 return std.meta.stringToEnum(RefOrOid, s);│ ╰──────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │162 return .{ .repo = Array(repo_route_max_len).from(s) orelse return null }; │ │163 } │ │164 │ │163 - // the start oid of a `.repo_commits` route's stored string ("" = page 1, │ │164 - // which the commits tab walks from HEAD). │ │165 - pub fn repoCommitsStart(s: []const u8) []const u8 { │ │165 + // the ref/oid a `.repo_commits` route's stored string walks from. kind is │ │166 + // null for the bare "owner/name/commits" (the repo's default branch). the │ │167 + // returned value slices into `s`. │ │168 + pub const CommitsRef = struct { ref_or_oid: ?RefOrOid, value: []const u8 }; │ │169 + pub fn repoCommitsRef(s: []const u8) CommitsRef { │ │170 const marker = "/" ++ commits_seg ++ "/"; │ │167 - const i = std.mem.indexOf(u8, s, marker) orelse return ""; │ │168 - return s[i + marker.len ..]; │ │171 + const i = std.mem.indexOf(u8, s, marker) orelse return .{ .ref_or_oid = null, .value = "" }; │ │172 + const ref_part = s[i + marker.len ..]; // "<refkind>/<refvalue>" │ │173 + const k1 = std.mem.indexOfScalar(u8, ref_part, '/') orelse return .{ .ref_or_oid = null, .value = "" }; │ │174 + const kind = RefOrOid.fromSeg(ref_part[0..k1]) orelse return .{ .ref_or_oid = null, .value = "" }; │ │175 + const value = ref_part[k1 + 1 ..]; │ │176 + if (value.len == 0) return .{ .ref_or_oid = null, .value = "" }; │ │177 + return .{ .ref_or_oid = kind, .value = value }; │ │178 } │ │179 │ │171 - // build a `.repo_commits` route for "owner/name" (identity) at `start_oid` │ │172 - // ("" = the first page). always carries the `commits` marker so page 1 │ │180 + // build a `.repo_commits` route for "owner/name" (identity), walking from │ │181 + // `ref_or_oid`/`value` (a null ref_or_oid yields the bare default-branch │ │182 + // route). always carries the `commits` marker so the bare route │ │183 // ("owner/name/commits") doesn't collide with the files root. │ │174 - pub fn repoCommitsRoute(identity: []const u8, start_oid: []const u8, after: usize) ?RoutablePage { │ │184 + pub fn repoCommitsRoute(identity: []const u8, ref_or_oid: ?RefOrOid, value: []const u8, after: usize) ?RoutablePage { │ │185 var buf: [repo_route_max_len]u8 = undefined; │ │176 - const s = if (start_oid.len == 0) │ │177 - std.fmt.bufPrint(&buf, "{s}/" ++ commits_seg, .{identity}) catch return null │ │186 + const s = if (ref_or_oid) |kind| │ │187 + std.fmt.bufPrint(&buf, "{s}/" ++ commits_seg ++ "/{s}/{s}", .{ identity, @tagName(kind), value }) catch return null│ │188 else │ │179 - std.fmt.bufPrint(&buf, "{s}/" ++ commits_seg ++ "/{s}", .{ identity, start_oid }) catch return null; │ │189 + std.fmt.bufPrint(&buf, "{s}/" ++ commits_seg, .{identity}) catch return null; │ │190 return .{ .repo_commits = .{ .name = Array(repo_route_max_len).from(s) orelse return null, .after = after } }; │ │191 } │ │192 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │316 const dir = if (k2) |i| after_kind[i + 1 ..] else ""; │ │317 return repoFilesRoute(pair, kind, value, dir); │ │318 } │ │309 - if (std.mem.eql(u8, sub, commits_seg)) return repoCommitsRoute(pair, "", after); // trailing /commits == page 1 │ │310 - if (std.mem.startsWith(u8, sub, commits_seg ++ "/")) return repoCommitsRoute(pair, sub[commits_seg.len + 1 ..], after); │ │319 + if (std.mem.eql(u8, sub, commits_seg)) return repoCommitsRoute(pair, null, "", after); // trailing /commits == default branch│ │320 + if (std.mem.startsWith(u8, sub, commits_seg ++ "/")) { │ │321 + // "commits/<refkind>/<refvalue>" │ │322 + const ref_part = sub[commits_seg.len + 1 ..]; │ │323 + const k1 = std.mem.indexOfScalar(u8, ref_part, '/') orelse return null; │ │324 + const kind = RefOrOid.fromSeg(ref_part[0..k1]) orelse return null; │ │325 + const value = ref_part[k1 + 1 ..]; │ │326 + if (value.len == 0) return null; │ │327 + return repoCommitsRoute(pair, kind, value, after); │ │328 + } │ │329 return null; // unknown sub-path │ │330 } │ │331 return repoFilesRoute(pair, null, "", ""); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │382 ref_or_oid: ?RefOrOid, │ │383 ref_or_oid_value: []const u8, │ │384 files_dir: []const u8, │ │367 - commits_start: []const u8, │ │385 + commits_ref_or_oid: ?RefOrOid, │ │386 + commits_ref_or_oid_value: []const u8,│ │387 commits_after: usize, │ │388 refs_kind: RefKind, │ │389 refs_after: usize, │ ╰──────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │399 .repo_refs => |*r2| r2.name.slice(), │ │400 else => return null, │ │401 }; │ │383 - const rf = RepoFiles.parse(name) orelse return null; │ │402 + const files = RepoFiles.parse(name) orelse return null; │ │403 // a 0 offset means no refs pagination, so canonicalize the column to │ │404 // branch then — otherwise the base /refs route would look like a content│ │405 // change relative to the same page mirrored with kind=tag. │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │408 else => 0, │ │409 }; │ │410 const is_files = std.meta.activeTag(r.*) == .repo; │ │411 + const is_commits = std.meta.activeTag(r.*) == .repo_commits; │ │412 + const commits_ref = if (is_commits) repoCommitsRef(name) else CommitsRef{ .ref_or_oid = null, .value = "" };│ │413 return .{ │ │393 - .identity = rf.identity, │ │394 - .ref_or_oid = if (is_files) rf.ref_kind else null, │ │395 - .ref_or_oid_value = if (is_files) rf.ref_value else "", │ │396 - .files_dir = if (is_files) rf.dir else "", │ │397 - .commits_start = if (std.meta.activeTag(r.*) == .repo_commits) repoCommitsStart(name) else "", │ │414 + .identity = files.identity, │ │415 + .ref_or_oid = if (is_files) files.ref_kind else null, │ │416 + .ref_or_oid_value = if (is_files) files.ref_value else "", │ │417 + .files_dir = if (is_files) files.dir else "", │ │418 + .commits_ref_or_oid = commits_ref.ref_or_oid, │ │419 + .commits_ref_or_oid_value = commits_ref.value, │ │420 .commits_after = switch (r.*) { │ │421 .repo_commits => |c| c.after, │ │422 else => 0, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │437 ca.ref_or_oid != cb.ref_or_oid or │ │438 !std.mem.eql(u8, ca.ref_or_oid_value, cb.ref_or_oid_value) or │ │439 !std.mem.eql(u8, ca.files_dir, cb.files_dir) or │ │418 - !std.mem.eql(u8, ca.commits_start, cb.commits_start) or │ │440 + ca.commits_ref_or_oid != cb.commits_ref_or_oid or │ │441 + !std.mem.eql(u8, ca.commits_ref_or_oid_value, cb.commits_ref_or_oid_value) or│ │442 ca.commits_after != cb.commits_after or │ │443 ca.refs_kind != cb.refs_kind or │ │444 ca.refs_after != cb.refs_after; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯