╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │510 }; │ │511 │ │512 // a `RefOrOid` resolved against an on-disk repo to the commit oid it points at │ │513 -pub const ResolvedRefOrOid = struct { │ │514 - // the hex-oid length for the default repo options the on-disk repos use │ │515 - pub const hex_len = hash.hexLen((rp.RepoOpts(.xit){}).hash); │ │513 +fn isUnreserved(c: u8) bool { │ │514 + return std.ascii.isAlphanumeric(c) or c == '-' or c == '.' or c == '_' or c == '~'; │ │515 +} │ │516 │ │517 - // the concrete ref/oid (a null request resolves to the default branch) │ │518 - ref_or_oid: RoutablePage.RefOrOid, │ │519 - // its branch/tag name or oid, url-encoded (ref names may contain '/', so the │ │520 - // route layer — which splits on '/' — stores them encoded). duped into `aa`. │ │521 - value: []const u8, │ │522 - // the commit oid it points at │ │523 - oid: [hex_len]u8, │ │517 +// percent-encode a ref name for use as a single url path segment. │ │518 +pub fn urlEncodeRef(aa: std.mem.Allocator, raw: []const u8) ![]const u8 { │ │519 + var out: std.Io.Writer.Allocating = .init(aa); │ │520 + try std.Uri.Component.percentEncode(&out.writer, raw, isUnreserved); │ │521 + return out.written(); │ │522 +} │ │523 + │ │524 +// a resolved ref/oid for a repo opened with `repo_opts` │ │525 +pub fn ResolvedRefOrOid(comptime repo_opts: rp.RepoOpts(.xit)) type { │ │526 + return struct { │ │527 + pub const hex_len = hash.hexLen(repo_opts.hash); │ │528 + │ │529 + // the concrete ref/oid (a null request resolves to the default branch) │ │530 + ref_or_oid: RoutablePage.RefOrOid, │ │531 + // its branch/tag name or oid, url-encoded (ref names may contain '/', so │ │532 + // the route layer — which splits on '/' — stores them encoded). duped │ │533 + // into `aa`. │ │534 + value: []const u8, │ │535 + // the commit oid it points at │ │536 + oid: [hex_len]u8, │ │537 + │ │538 + const Self = @This(); │ │539 + │ │540 + // resolve a requested ref/oid (null = the repo's default branch) to a │ │541 + // concrete ref/oid plus the commit oid it points at. `requested_value` is │ │542 + // the url-encoded form (as it appears in the route). null when it doesn't │ │543 + // resolve (an unknown branch/tag, or a malformed/unknown oid). │ │544 + pub fn init( │ │545 + repo: *rp.Repo(.xit, repo_opts), │ │546 + io: std.Io, │ │547 + aa: std.mem.Allocator, │ │548 + requested_ref_or_oid: ?RoutablePage.RefOrOid, │ │549 + requested_value: []const u8, │ │550 + ) !?Self { │ │551 + var ref_or_oid: RoutablePage.RefOrOid = requested_ref_or_oid orelse .branch; │ │552 + // the decoded ref name / oid to look up. a named ref arrives url-encoded. │ │553 + var value: []const u8 = if (requested_ref_or_oid == null) │ │554 + requested_value │ │555 + else │ │556 + std.Uri.percentDecodeInPlace(try aa.dupe(u8, requested_value)); │ │557 + // no ref named: fall back to HEAD's branch (or its oid when detached). │ │558 + if (requested_ref_or_oid == null) { │ │559 + var head_buf: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │560 + if (repo.head(io, &head_buf)) |head| switch (head) { │ │561 + .ref => |r| { │ │562 + ref_or_oid = .branch; │ │563 + value = r.name; │ │564 + }, │ │565 + .oid => |o| { │ │566 + ref_or_oid = .object; │ │567 + value = o; │ │568 + }, │ │569 + } else |_| {} │ │570 + } │ │571 │ │525 - // resolve a requested ref/oid (null = the repo's default branch) to a │ │526 - // concrete ref/oid plus the commit oid it points at. `requested_value` is the │ │527 - // url-encoded form (as it appears in the route). null when it doesn't resolve │ │528 - // (an unknown branch/tag, or a malformed/unknown oid). │ │529 - pub fn init( │ │530 - repo: *rp.Repo(.xit, .{}), │ │531 - io: std.Io, │ │532 - aa: std.mem.Allocator, │ │533 - requested_ref_or_oid: ?RoutablePage.RefOrOid, │ │534 - requested_value: []const u8, │ │535 - ) !?ResolvedRefOrOid { │ │536 - var ref_or_oid: RoutablePage.RefOrOid = requested_ref_or_oid orelse .branch; │ │537 - // the decoded ref name / oid to look up. a named ref arrives url-encoded. │ │538 - var value: []const u8 = if (requested_ref_or_oid == null) │ │539 - requested_value │ │540 - else │ │541 - std.Uri.percentDecodeInPlace(try aa.dupe(u8, requested_value)); │ │542 - // no ref named: fall back to HEAD's branch (or its oid when detached). │ │543 - if (requested_ref_or_oid == null) { │ │544 - var head_buf: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │545 - if (repo.head(io, &head_buf)) |head| switch (head) { │ │546 - .ref => |r| { │ │547 - ref_or_oid = .branch; │ │548 - value = r.name; │ │572 + var oid: [hex_len]u8 = undefined; │ │573 + switch (ref_or_oid) { │ │574 + .object => { │ │575 + if (value.len != hex_len) return null; │ │576 + @memcpy(&oid, value); │ │577 }, │ │550 - .oid => |o| { │ │551 - ref_or_oid = .object; │ │552 - value = o; │ │578 + .branch, .tag => { │ │579 + const ref_kind: rf.RefKind = if (ref_or_oid == .branch) .head else .tag; │ │580 + oid = (repo.readRef(io, .{ .kind = ref_kind, .name = value }) catch null) orelse return null;│ │581 }, │ │554 - } else |_| {} │ │582 + } │ │583 + // store url-encoded so the route layer can hold the value verbatim. │ │584 + return .{ .ref_or_oid = ref_or_oid, .value = try urlEncodeRef(aa, value), .oid = oid }; │ │585 } │ │586 + }; │ │587 +} │ │588 │ │557 - var oid: [hex_len]u8 = undefined; │ │558 - switch (ref_or_oid) { │ │559 - .object => { │ │560 - if (value.len != hex_len) return null; │ │561 - @memcpy(&oid, value); │ │562 - }, │ │563 - .branch, .tag => { │ │564 - const ref_kind: rf.RefKind = if (ref_or_oid == .branch) .head else .tag; │ │565 - oid = (repo.readRef(io, .{ .kind = ref_kind, .name = value }) catch null) orelse return null; │ │566 - }, │ │567 - } │ │568 - // store url-encoded so the route layer can hold the value verbatim. │ │569 - return .{ .ref_or_oid = ref_or_oid, .value = try urlEncode(aa, value), .oid = oid }; │ │570 - } │ │571 - │ │572 - fn isUnreserved(c: u8) bool { │ │573 - return std.ascii.isAlphanumeric(c) or c == '-' or c == '.' or c == '_' or c == '~'; │ │574 - } │ │575 - │ │576 - // percent-encode a ref name for use as a single url path segment. │ │577 - pub fn urlEncode(aa: std.mem.Allocator, raw: []const u8) ![]const u8 { │ │578 - var out: std.Io.Writer.Allocating = .init(aa); │ │579 - try std.Uri.Component.percentEncode(&out.writer, raw, isUnreserved); │ │580 - return out.written(); │ │581 - } │ │582 -}; │ │583 - │ │589 // per-connection mutable state. each SSH session / web session / local TUI │ │590 // run gets its own. │ │591 pub const Session = struct { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯