╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────╮ │11 const Size = xitui.layout.Size; │ │12 const ui = @import("./ui.zig"); │ │13 const ssh = @import("./serve_ssh_protocol.zig");│ │14 +const evt = @import("./event.zig"); │ │15 │ │16 pub const SessionHandler = struct { │ │17 admin_repo_path: []const u8, │ ╰────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │69 var page_arena = std.heap.ArenaAllocator.init(allocator); │ │70 defer page_arena.deinit(); │ │71 │ │71 - const repo_opts: rp.RepoOpts(.xit) = .{}; │ │72 - const Repo = rp.Repo(.xit, repo_opts); │ │72 + const Repo = rp.Repo(.xit, evt.admin_repo_opts); │ │73 var repo = try Repo.open(io, allocator, .{ .path = handler.admin_repo_path }); │ │74 defer repo.deinit(io, allocator); │ │75 - var ui_session = try ui.Session.init(repo_opts, &page_arena, &repo, .{}); │ │75 + var ui_session = try ui.Session.init(&page_arena, &repo, .{}); │ │76 │ │77 - const page: ui.Page = .{ .home = try .init(repo_opts, &page_arena, ui_session.haxy_moment orelse unreachable) };│ │77 + const page: ui.Page = .{ .home = try .init(&page_arena, ui_session.haxy_moment orelse unreachable) }; │ │78 │ │79 var root = try ui.initRoot(allocator, &page, &ui_session); │ │80 defer root.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │125 .close => terminal.requestQuit(), │ │126 } │ │127 │ │128 - // apply any actions the widgets queued this frame, persisting those │ │129 - // that belong to a logged-in user │ │130 - try ui.Action.dispatchPending(repo_opts, io, allocator, &repo, &ui_session);│ │128 + try ui_session.applyAndWritePending(io, allocator, &repo); │ │129 │ │130 try root.build(allocator, .{ │ │131 .min_size = .{ .width = null, .height = null }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │170 return; │ │171 } │ │172 │ │175 - const repo_opts: rp.RepoOpts(.xit) = .{}; │ │176 - const Repo = rp.Repo(.xit, repo_opts); │ │173 const create_if_missing = parsed.service == .receive_pack; │ │174 + const any_repo_opts: rp.AnyRepoOpts(.xit) = .{}; │ │175 │ │179 - var repo = openOrCreate(Repo, io, allocator, repo_path, create_if_missing) catch |err| switch (err) { │ │176 + var any_repo = rp.AnyRepo(.xit, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |err| switch (err) {│ │177 error.RepoNotFound => { │ │181 - try writeError(sess, "repo not found"); │ │182 - try sess.exit(1); │ │178 + if (!create_if_missing) { │ │179 + try writeError(sess, "repo not found"); │ │180 + try sess.exit(1); │ │181 + return; │ │182 + } │ │183 + // nothing on disk to detect a hash from, so create with the │ │184 + // default concrete options and serve that. │ │185 + var repo = try createRepo(any_repo_opts.toRepoOpts(), io, allocator, repo_path); │ │186 + defer repo.deinit(io, allocator); │ │187 + try servePack(repo.self_repo_opts, &repo, sess, io, allocator, parsed.service); │ │188 + try sess.exit(0); │ │189 return; │ │190 }, │ │191 else => |e| return e, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 }; │ │187 - defer repo.deinit(io, allocator); │ │193 + defer any_repo.deinit(io, allocator); │ │194 + │ │195 + switch (any_repo) { │ │196 + inline else => |*repo| try servePack(repo.self_repo_opts, repo, sess, io, allocator, parsed.service),│ │197 + } │ │198 + │ │199 + try sess.exit(0); │ │200 +} │ │201 + │ │202 +fn createRepo( │ │203 + comptime repo_opts: rp.RepoOpts(.xit), │ │204 + io: std.Io, │ │205 + allocator: std.mem.Allocator, │ │206 + repo_path: []const u8, │ │207 +) !rp.Repo(.xit, repo_opts) { │ │208 + var repo = try rp.Repo(.xit, repo_opts).init(io, allocator, .{ .path = repo_path }); │ │209 + errdefer repo.deinit(io, allocator); │ │210 + try repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); │ │211 + try repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); │ │212 + return repo; │ │213 +} │ │214 │ │189 - // wrap the channel I/O in std.Io.Reader/Writer adapters so the repo's │ │190 - // pack functions can consume them like any other stream. │ │215 +// wrap the channel I/O in std.Io.Reader/Writer adapters so the repo's pack │ │216 +// functions can consume them like any other stream, then run the requested │ │217 +// service │ │218 +fn servePack( │ │219 + comptime repo_opts: rp.RepoOpts(.xit), │ │220 + repo: *rp.Repo(.xit, repo_opts), │ │221 + sess: *ssh.SessionCtx, │ │222 + io: std.Io, │ │223 + allocator: std.mem.Allocator, │ │224 + service: GitService, │ │225 +) !void { │ │226 var session_reader_buf: [4096]u8 = undefined; │ │227 var session_writer_buf: [4096]u8 = undefined; │ │228 var session_reader = ssh.SessionReader.init(sess, &session_reader_buf); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │229 var session_writer = ssh.SessionWriter.init(sess, &session_writer_buf); │ │230 │ │196 - switch (parsed.service) { │ │231 + switch (service) { │ │232 .upload_pack => try repo.uploadPack(io, allocator, &session_reader.interface, &session_writer.interface, .{}), │ │233 .receive_pack => try repo.receivePack(io, allocator, &session_reader.interface, &session_writer.interface, .{}),│ │234 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │235 │ │236 // flush whatever the pack op buffered into our writer adapter │ │237 session_writer.interface.flush() catch {}; │ │203 - │ │204 - try sess.exit(0); │ │205 -} │ │206 - │ │207 -fn openOrCreate( │ │208 - comptime Repo: type, │ │209 - io: std.Io, │ │210 - allocator: std.mem.Allocator, │ │211 - repo_path: []const u8, │ │212 - create_if_missing: bool, │ │213 -) !Repo { │ │214 - return Repo.open(io, allocator, .{ .path = repo_path }) catch |err| switch (err) { │ │215 - error.RepoNotFound => { │ │216 - if (!create_if_missing) return err; │ │217 - var repo = try Repo.init(io, allocator, .{ .path = repo_path }); │ │218 - errdefer repo.deinit(io, allocator); │ │219 - try repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); │ │220 - try repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" });│ │221 - return repo; │ │222 - }, │ │223 - else => |e| return e, │ │224 - }; │ │238 } │ │239 │ │240 fn parseGitCommand(allocator: std.mem.Allocator, command: []const u8) !ParsedGitCommand { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯