╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │228 .min_size = .{ .width = null, .height = null }, │ │229 .max_size = .{ .width = last_size.width, .height = last_size.height }, │ │230 }, nav.root.getFocus()); │ │226 - _ = try terminal.render(&nav.root, &last_grid, &last_size); │ │231 + if (terminal_maybe) |*terminal| _ = try terminal.render(&nav.root, &last_grid, &last_size);│ │232 │ │233 // event loop. nextEvent blocks until something interesting arrives; │ │234 // for each event, rebuild the widget tree and re-render so the user │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │235 // sees the effect of their input on the same iteration. │ │231 - while (!terminal.shouldQuit()) { │ │236 + event_loop: while (true) { │ │237 const event = try sess.nextEvent(); │ │233 - switch (event) { │ │234 - .data => |payload| { │ │235 - defer allocator.free(payload); │ │236 - try terminal.writeBytes(payload); │ │237 - while (terminal.popKey()) |key| { │ │238 - try ui.inputKey(allocator, &nav.root, key, &ui_session); │ │239 - } │ │240 - }, │ │241 - .resize => |sz| { │ │242 - terminal.pushResize(.{ .width = sz.width_cells, .height = sz.height_cells }); │ │243 - while (terminal.popKey()) |key| { │ │244 - try ui.inputKey(allocator, &nav.root, key, &ui_session); │ │245 - } │ │246 - }, │ │247 - // either way no more input can arrive, so nothing is left to │ │248 - // drive the ui — but on .eof the channel is still open, so the │ │249 - // teardown below still reaches the client. │ │250 - .eof, .close => terminal.requestQuit(), │ │238 + if (terminal_maybe) |*terminal| { │ │239 + switch (event) { │ │240 + .data => |payload| { │ │241 + defer allocator.free(payload); │ │242 + try terminal.writeBytes(payload); │ │243 + while (terminal.popKey()) |key| { │ │244 + try ui.inputKey(allocator, &nav.root, key, &ui_session); │ │245 + } │ │246 + }, │ │247 + .resize => |sz| { │ │248 + terminal_size = .{ .width = sz.width_cells, .height = sz.height_cells }; │ │249 + terminal.pushResize(terminal_size); │ │250 + while (terminal.popKey()) |key| { │ │251 + try ui.inputKey(allocator, &nav.root, key, &ui_session); │ │252 + } │ │253 + }, │ │254 + .eof, .close => break :event_loop, │ │255 + } │ │256 + } else { │ │257 + switch (event) { │ │258 + .data => |payload| { │ │259 + defer allocator.free(payload); │ │260 + if (std.mem.indexOfAny(u8, payload, "\r\n") == null) continue; │ │261 + terminal_maybe = try StreamTerminal.init(allocator, &session_writer.interface, terminal_size); │ │262 + last_size = .{ .width = 0, .height = 0 }; │ │263 + }, │ │264 + .resize => |sz| { │ │265 + terminal_size = .{ .width = sz.width_cells, .height = sz.height_cells }; │ │266 + continue; │ │267 + }, │ │268 + .eof, .close => break :event_loop, │ │269 + } │ │270 + } │ │271 + │ │272 + if (ui_session.host_request) |request| { │ │273 + ui_session.host_request = null; │ │274 + switch (request) { │ │275 + .clone_url => |url| { │ │276 + if (terminal_maybe) |*terminal| terminal.deinit(); │ │277 + terminal_maybe = null; │ │278 + try session_writer.interface.print("\x1b[2J\x1b[H{s}\r\n\r\npress enter to go back\r\n", .{url});│ │279 + try session_writer.interface.flush(); │ │280 + continue; │ │281 + }, │ │282 + } │ │283 } │ │284 │ │285 try ui_session.applyAndWritePending(io, allocator, &repo); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │292 try nav.sync(allocator, &ui_session); │ │293 │ │294 // the quit button (on the quit tab) asks the host to tear down│ │263 - if (ui_session.quit_requested) terminal.requestQuit(); │ │295 + if (ui_session.quit_requested) break :event_loop; │ │296 │ │297 try nav.root.build(allocator, .{ │ │298 .min_size = .{ .width = null, .height = null }, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │299 .max_size = .{ .width = last_size.width, .height = last_size.height }, │ │300 }, nav.root.getFocus()); │ │269 - _ = try terminal.render(&nav.root, &last_grid, &last_size); │ │301 + if (terminal_maybe) |*terminal| _ = try terminal.render(&nav.root, &last_grid, &last_size);│ │302 } │ │303 } │ │304 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 │ │324 const parsed = parseGitCommand(allocator, command) catch return writeError(sess, "unsupported command (expected git-upload-pack or git-receive-pack)");│ │325 defer parsed.deinit(allocator); │ │326 + const repo_prefix = "/repo/"; │ │327 + const repo_identity = if (std.mem.startsWith(u8, parsed.dir, repo_prefix)) parsed.dir[repo_prefix.len..] else parsed.dir; │ │328 │ │329 const create_if_missing = parsed.service == .receive_pack; │ │330 const any_repo_opts: rp.AnyRepoOpts(.xit) = .{}; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │332 // authenticate pushes: the authenticated key must be registered to the │ │333 // repo's owner. │ │334 if (create_if_missing) { │ │301 - const owner_repo = evt.parseOwnerRepoPath(parsed.dir) orelse return writeError(sess, "repo path must be <owner>/<repo>"); │ │335 + const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>/<repo>");│ │336 if (!try isKeyAuthorized(io, allocator, handler.admin_repo_path, owner_repo.owner, &sess.fingerprint)) │ │337 return writeError(sess, "unauthorized: this SSH key is not registered to the repo owner"); │ │338 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │339 │ │306 - const repo_path = switch (try serve_common.resolveRepoPath(io, allocator, handler.repo_root_path, handler.admin_repo_path, parsed.dir, create_if_missing)) { │ │340 + const repo_path = switch (try serve_common.resolveRepoPath(io, allocator, handler.repo_root_path, handler.admin_repo_path, repo_identity, create_if_missing)) {│ │341 .ok => |p| p, │ │342 .invalid => return writeError(sess, "repo path must be <owner>/<repo>"), │ │343 .not_found => return writeError(sess, "repo not found"), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │774 io: std.Io, │ │775 key_path: []const u8, │ │776 │ │777 - pub fn run(self: @This(), web_port: u16, ssh_port: u16) !void { │ │777 + pub fn run(self: @This(), web_port: u16, http_port: u16, ssh_port: u16) !void {│ │778 _ = web_port; │ │779 + _ = http_port; │ │780 std.debug.print( │ │781 \\ │ │782 \\connect to the TUI with: │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │816 session: *ui.Session, │ │817 repo: *Repo, │ │818 │ │818 - pub fn run(self: @This(), web_port: u16, ssh_port: u16) !void { │ │819 - _ = ssh_port; │ │819 + pub fn run(self: @This(), web_port: u16, http_port: u16, ssh_port: u16) !void {│ │820 // launch the TUI; the footer points at whatever port was bound │ │821 self.session.web_port = web_port; │ │822 + self.session.data.clone_http_port = http_port; │ │823 + self.session.data.clone_ssh_port = ssh_port; │ │824 try hx.ui.run(self.io, self.allocator, self.session, self.repo); │ │825 } │ │826 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────╮ │1 const std = @import("std"); │ │2 +const builtin = @import("builtin");│ │3 const xit = @import("xit"); │ │4 const rp = xit.repo; │ │5 const rf = xit.ref; │ ╰──────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯