╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │559 }, &tree, ref); │ │560 // later events parent on the ref's new tip │ │561 parent_oids = null; │ │562 - } │ │563 -} │ │564 - │ │565 -pub const PushProgress = struct { │ │566 - response: *xit.net_server_receive_pack.Response, │ │567 - writer: *std.Io.Writer, │ │568 - count: usize = 0, │ │569 - total: usize = 0, │ │570 - │ │571 - pub fn run(self: *PushProgress, _: std.Io, event: rp.ProgressEvent) !void { │ │572 - switch (event) { │ │573 - .start => |start| { │ │574 - if (start.kind != .writing_patch) return; │ │575 - self.total = start.estimated_total_items; │ │576 - self.count = 0; │ │577 - }, │ │578 - .complete_one => |kind| { │ │579 - if (kind != .writing_patch) return; │ │580 - self.count += 1; │ │581 - }, │ │582 - .end => |kind| { │ │583 - if (kind != .writing_patch) return; │ │584 - }, │ │585 - else => return, │ │586 - } │ │587 - var buffer: [128]u8 = undefined; │ │588 - const percent = if (self.total == 0) 100 else @as(u128, self.count) * 100 / self.total; │ │589 - const text = try std.fmt.bufPrint(&buffer, "Processing commits: {d}% ({d}/{d}){s}", .{ percent, self.count, self.total, if (event == .end) "\n" else "\r" });│ │590 - try self.response.progress(self.writer, text); │ │591 - } │ │592 -}; │ │593 - │ │594 -// prepare all reachable commits, including history received before patch │ │595 -// generation was enabled. this shares the receive-pack transaction. │ │596 -pub fn writeReceivedPatches( │ │597 - comptime repo_opts: rp.RepoOpts(.xit), │ │598 - state: rp.Repo(.xit, repo_opts).State(.read_write), │ │599 - io: std.Io, │ │600 - allocator: std.mem.Allocator, │ │601 - response: *xit.net_server_receive_pack.Response, │ │602 - writer: *std.Io.Writer, │ │603 -) !void { │ │604 - try response.progress(writer, "Processing commits...\n"); │ │605 - var config = try xit.config.Config(.xit, repo_opts).init(state.readOnly(), io, allocator); │ │606 - defer config.deinit(); │ │607 - try config.add(state, io, .{ .name = "merge.algorithm", .value = "patch" }); │ │608 - │ │609 - var iter = try obj.ObjectIterator(.xit, repo_opts).init(state.readOnly(), io, allocator, .{ .kind = .commit }); │ │610 - defer iter.deinit(); │ │611 - var refs = try rf.AllRefIterator(.xit, repo_opts).init(state.readOnly(), allocator); │ │612 - defer refs.deinit(); │ │613 - while (try refs.next()) |ref| { │ │614 - if (try rf.readRecur(.xit, repo_opts, state.readOnly(), io, .{ .ref = ref })) |oid| { │ │615 - try iter.include(&oid); │ │616 - } │ │562 } │ │618 - var progress = PushProgress{ .response = response, .writer = writer }; │ │619 - try xit.patch.writePatches(repo_opts, state, io, allocator, &iter, &progress); │ │620 -} │ │621 - │ │622 -// serve a receive-pack and consume any events it pushed to the events branch, │ │623 -// all in one transaction: the push and the views derived from it commit │ │624 -// atomically, and a failed consume cancels the push │ │625 -pub fn receivePackAndConsume( │ │626 - comptime repo_opts: rp.RepoOpts(.xit), │ │627 - io: std.Io, │ │628 - allocator: std.mem.Allocator, │ │629 - repo: *rp.Repo(.xit, repo_opts), │ │630 - reader: *std.Io.Reader, │ │631 - writer: *std.Io.Writer, │ │632 - options: xit.net_server_receive_pack.Options, │ │633 - repo_root_path: []const u8, │ │634 - error_writer: *std.Io.Writer, │ │635 -) !void { │ │636 - const DB = rp.Repo(.xit, repo_opts).DB; │ │637 - const State = rp.Repo(.xit, repo_opts).State; │ │638 - │ │639 - var response = xit.net_server_receive_pack.Response.init(allocator); │ │640 - defer response.deinit(); │ │641 - │ │642 - const Ctx = struct { │ │643 - core: *rp.Repo(.xit, repo_opts).Core, │ │644 - io: std.Io, │ │645 - allocator: std.mem.Allocator, │ │646 - reader: *std.Io.Reader, │ │647 - writer: *std.Io.Writer, │ │648 - options: xit.net_server_receive_pack.Options, │ │649 - response: *xit.net_server_receive_pack.Response, │ │650 - repo_root_path: []const u8, │ │651 - error_writer: *std.Io.Writer, │ │652 - │ │653 - pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │654 - var moment = try DB.HashMap(.read_write).init(cursor.*); │ │655 - const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │656 - var updates = xit.net_server_receive_pack.AppliedRefUpdates.init(ctx.allocator); │ │657 - defer updates.deinit(); │ │658 - var receive_options = ctx.options; │ │659 - receive_options.applied_ref_updates = &updates; │ │660 - receive_options.deferred_response = ctx.response; │ │661 - try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, receive_options); │ │662 - try writeReceivedPatches(repo_opts, state, ctx.io, ctx.allocator, ctx.response, ctx.writer); │ │663 - │ │664 - // a repo without an events branch has no events to consume. a │ │665 - // no-op consume must not cancel here, since the push shares the │ │666 - // transaction and must commit regardless. │ │667 - if (null != try rf.readRecur(.xit, repo_opts, state.readOnly(), ctx.io, .{ .ref = events_ref })) { │ │668 - _ = try consumeInTransaction(.repo, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, events_ref); │ │669 - try pch.detectMerged(repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.repo_root_path, updates.items.items, ctx.error_writer); │ │670 - } │ │671 - try xit.undo.writeMessage(repo_opts, state, .push); │ │672 - } │ │673 - }; │ │674 - │ │675 - try repo.core.db_file.lock(io, .exclusive); │ │676 - defer repo.core.db_file.unlock(io); │ │677 - │ │678 - const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │679 - history.appendContext( │ │680 - .{ .slot = try history.getSlot(-1) }, │ │681 - Ctx{ │ │682 - .core = &repo.core, │ │683 - .io = io, │ │684 - .allocator = allocator, │ │685 - .reader = reader, │ │686 - .writer = writer, │ │687 - .options = options, │ │688 - .response = &response, │ │689 - .repo_root_path = repo_root_path, │ │690 - .error_writer = error_writer, │ │691 - }, │ │692 - ) catch |err| { │ │693 - response.finish(writer, @errorName(err)) catch {}; │ │694 - if (err == error.CancelTransaction) return; │ │695 - return err; │ │696 - }; │ │697 - │ │698 - try response.finish(writer, null); │ │563 } │ │564 │ │565 // a standalone event db holding events consumed from a local repo's events │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("event.zig"); │ │3 -const serve_common = @import("serve_common.zig");│ │3 const xit = @import("xit"); │ │4 const rp = xit.repo; │ │5 const hash = xit.hash; │ ╰────────────────────────────────────────────────────╯ ╭──────────────────────────╮ │7 -const obj = xit.object;│ │8 -const mrg = xit.merge; │ │6 const bch = xit.branch;│ │7 const rf = xit.ref; │ │8 │ ╰──────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │ 9 pub const ref = rf.Ref{ .kind = .head, .name = "patch" };│ │13 -const ref_path = "refs/heads/patch"; │ │10 │ │11 pub const Route = struct { │ │12 identity: []const u8, │ ╰─────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │188 return fork_path; │ │189 } │ │190 │ │195 -pub fn receivePack( │ │196 - comptime repo_opts: rp.RepoOpts(.xit), │ │197 - io: std.Io, │ │198 - allocator: std.mem.Allocator, │ │199 - fork_repo: *rp.Repo(.xit, repo_opts), │ │200 - target_repo: *rp.Repo(.xit, repo_opts), │ │201 - id: *const [evt.event_id_size * 2]u8, │ │202 - author: evt.CommitAuthor, │ │203 - timestamp: u64, │ │204 - reader: *std.Io.Reader, │ │205 - writer: *std.Io.Writer, │ │206 - error_writer: *std.Io.Writer, │ │207 -) !void { │ │208 - const patch_id = try evt.parseEventId(id); │ │209 - var arena = std.heap.ArenaAllocator.init(allocator); │ │210 - defer arena.deinit(); │ │211 - │ │212 - // load the fork and target patch state │ │213 - const fork_moment = evt.currentMoment(repo_opts, fork_repo) catch return error.PatchDataUnavailable; │ │214 - const fork_patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &patch_id)) orelse return error.PatchDataUnavailable; │ │215 - const target_moment = evt.currentMoment(repo_opts, target_repo) catch |err| switch (err) { │ │216 - error.NotFound => null, │ │217 - else => |other| return other, │ │218 - }; │ │219 - const target_patch = if (target_moment) |moment| │ │220 - try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patch_id) │ │221 - else │ │222 - null; │ │223 - if (fork_patch.removed and target_patch == null) return error.PatchDataUnavailable; │ │224 - if (target_patch) |patch| { │ │225 - if (!std.mem.eql(u8, patch.author_email orelse "", author.email)) return error.PatchDataUnavailable; │ │226 - if (patch.event.status.kind() == .merged) return error.PatchAlreadyMerged; │ │227 - } │ │228 - const published = target_patch != null; │ │229 - const active_patch = target_patch orelse fork_patch; │ │230 - const title = active_patch.event.title; │ │231 - │ │232 - // resolve the target and the newest fork revision │ │233 - const target_branch = active_patch.event.target_branch; │ │234 - const target_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = target_branch })) orelse return error.TargetNotFound; │ │235 - const newest = try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena); │ │236 - var revision_id_maybe: ?[evt.event_id_size]u8 = null; │ │237 - │ │238 - // execute a transaction that receives the push and materializes its revision │ │239 - { │ │240 - var response = xit.net_server_receive_pack.Response.init(allocator); │ │241 - defer response.deinit(); │ │242 - const DB = rp.Repo(.xit, repo_opts).DB; │ │243 - const State = rp.Repo(.xit, repo_opts).State; │ │244 - const Ctx = struct { │ │245 - core: *rp.Repo(.xit, repo_opts).Core, │ │246 - target_core: *rp.Repo(.xit, repo_opts).Core, │ │247 - io: std.Io, │ │248 - allocator: std.mem.Allocator, │ │249 - reader: *std.Io.Reader, │ │250 - writer: *std.Io.Writer, │ │251 - response: *xit.net_server_receive_pack.Response, │ │252 - patch_id: [evt.event_id_size * 2]u8, │ │253 - patch: evt.Patch.Record, │ │254 - published: bool, │ │255 - target_oid: [hash.hexLen(repo_opts.hash)]u8, │ │256 - title: []const u8, │ │257 - author: evt.CommitAuthor, │ │258 - timestamp: u64, │ │259 - newest: ?evt.PatchRev.WithId, │ │260 - revision_id_maybe: *?[evt.event_id_size]u8, │ │261 - │ │262 - pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │263 - // receive the branch update │ │264 - var moment = try DB.HashMap(.read_write).init(cursor.*); │ │265 - const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │266 - try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, .{ .allowed_ref = ref_path, .deferred_response = ctx.response });│ │267 - try evt.writeReceivedPatches(repo_opts, state, ctx.io, ctx.allocator, ctx.response, ctx.writer); │ │268 - │ │269 - // copy the target history needed to preserve the merge base │ │270 - const source_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), ctx.io, .{ .ref = ref })) orelse return error.CancelTransaction; │ │271 - var target_repo_moment = try ctx.target_core.latestMoment(); │ │272 - const target_state = State(.read_only){ .core = ctx.target_core, .extra = .{ .moment = &target_repo_moment } }; │ │273 - var objects = try obj.ObjectIterator(.xit, repo_opts).init(target_state, ctx.io, ctx.allocator, .{ .kind = .all }); │ │274 - defer objects.deinit(); │ │275 - try objects.include(&ctx.target_oid); │ │276 - try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, null); │ │277 - │ │278 - const base_oid = try mrg.commonAncestor(.xit, repo_opts, state.readOnly(), ctx.io, ctx.allocator, &ctx.target_oid, &source_oid); │ │279 - const existing_revision = if (ctx.newest) |latest| │ │280 - if (std.mem.eql(u8, latest.record.event.base_oid, &base_oid) and │ │281 - std.mem.eql(u8, latest.record.event.source_oid, &source_oid)) latest else null │ │282 - else │ │283 - null; │ │284 - │ │285 - // reuse an identical revision or record a new one │ │286 - var events: [2]evt.EventWithId = undefined; │ │287 - var event_count: usize = 0; │ │288 - var base_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │289 - var head_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │290 - var tree_entries: [2]evt.EventTreeEntry = undefined; │ │291 - if (existing_revision) |latest| { │ │292 - if (ctx.published) ctx.revision_id_maybe.* = latest.id; │ │293 - } else { │ │294 - var base_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &base_oid); │ │295 - defer base_object.deinit(); │ │296 - var source_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &source_oid); │ │297 - defer source_object.deinit(); │ │298 - base_tree_oid = base_object.content.commit.tree; │ │299 - head_tree_oid = source_object.content.commit.tree; │ │300 - │ │301 - var revision_id: [evt.event_id_size]u8 = undefined; │ │302 - ctx.io.random(&revision_id); │ │303 - const revision_hex = std.fmt.bytesToHex(revision_id, .lower); │ │304 - const revision_event: evt.PatchRev = .{ │ │305 - .base_oid = &base_oid, │ │306 - .source_oid = &source_oid, │ │307 - .message = ctx.title, │ │308 - }; │ │309 - const identity = try std.fmt.allocPrint(ctx.allocator, "{s} <{s}>", .{ ctx.author.name, ctx.author.email }); │ │310 - defer ctx.allocator.free(identity); │ │311 - const patch_oid = try evt.PatchRev.writeSquashCommit( │ │312 - .xit, │ │313 - repo_opts, │ │314 - state, │ │315 - ctx.io, │ │316 - ctx.allocator, │ │317 - revision_event, │ │318 - &head_tree_oid, │ │319 - identity, │ │320 - identity, │ │321 - ctx.timestamp, │ │322 - ); │ │323 - tree_entries = .{ │ │324 - .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, │ │325 - .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, │ │326 - }; │ │327 - events[event_count] = .{ │ │328 - .id = revision_hex, │ │329 - .timestamp = ctx.timestamp, │ │330 - .author = ctx.author, │ │331 - .tree_entries = &tree_entries, │ │332 - .event = .{ .patchrev = revision_event }, │ │333 - }; │ │334 - event_count += 1; │ │335 - ctx.revision_id_maybe.* = revision_id; │ │336 - │ │337 - if (!ctx.published) { │ │338 - var patch = ctx.patch.event; │ │339 - patch.revision = .{ │ │340 - .id = revision_hex, │ │341 - .squash_oid = &patch_oid, │ │342 - .source_oid = &source_oid, │ │343 - }; │ │344 - events[event_count] = .{ │ │345 - .id = ctx.patch_id, │ │346 - .timestamp = ctx.timestamp, │ │347 - .author = ctx.author, │ │348 - .event = .{ .patch = patch }, │ │349 - }; │ │350 - event_count += 1; │ │351 - } │ │352 - } │ │353 - │ │354 - // published patches keep only revisions in the fork │ │355 - if (ctx.published and !ctx.patch.removed) { │ │356 - events[event_count] = .{ │ │357 - .id = ctx.patch_id, │ │358 - .timestamp = ctx.timestamp, │ │359 - .author = ctx.author, │ │360 - .event = .{ .patch = null }, │ │361 - }; │ │362 - event_count += 1; │ │363 - } │ │364 - │ │365 - // commit and index any new events │ │366 - if (event_count > 0) { │ │367 - try evt.commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, evt.events_ref, events[0..event_count], null); │ │368 - if (!try evt.consumeInTransaction(.fork, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, evt.events_ref)) return error.CancelTransaction; │ │369 - } │ │370 - try xit.undo.writeMessage(repo_opts, state, .push); │ │371 - } │ │372 - }; │ │373 - │ │374 - try fork_repo.core.db_file.lock(io, .exclusive); │ │375 - defer fork_repo.core.db_file.unlock(io); │ │376 - │ │377 - const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); │ │378 - history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │379 - .core = &fork_repo.core, │ │380 - .target_core = &target_repo.core, │ │381 - .io = io, │ │382 - .allocator = allocator, │ │383 - .reader = reader, │ │384 - .writer = writer, │ │385 - .response = &response, │ │386 - .patch_id = id.*, │ │387 - .patch = fork_patch, │ │388 - .published = published, │ │389 - .target_oid = target_oid, │ │390 - .title = title, │ │391 - .author = author, │ │392 - .timestamp = timestamp, │ │393 - .newest = newest, │ │394 - .revision_id_maybe = &revision_id_maybe, │ │395 - }) catch |err| { │ │396 - response.finish(writer, @errorName(err)) catch {}; │ │397 - if (err == error.CancelTransaction) return; │ │398 - return err; │ │399 - }; │ │400 - try response.finish(writer, null); │ │401 - } │ │402 - │ │403 - // best-effort update the published patch so it has the new revision │ │404 - { │ │405 - const revision_id = revision_id_maybe orelse return; │ │406 - if (!published) return; │ │407 - var update_arena = std.heap.ArenaAllocator.init(allocator); │ │408 - defer update_arena.deinit(); │ │409 - const target_update_moment = evt.currentMoment(repo_opts, target_repo) catch return; │ │410 - const published_patch = (evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, target_update_moment, &update_arena, &patch_id) catch return) orelse return; │ │411 - if (published_patch.removed) return; │ │412 - const fork_update_moment = evt.currentMoment(repo_opts, fork_repo) catch return; │ │413 - const revision = (evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_update_moment, &update_arena, &revision_id) catch return) orelse return; │ │414 - if (revision.removed) return; │ │415 - const selected = evt.Patch.Revision.fromRecord(revision_id, revision); │ │416 - if (published_patch.event.revision) |current| { │ │417 - if (evt.fieldEqual(evt.Patch.Revision, current, selected)) return; │ │418 - } │ │419 - var patch = published_patch.event; │ │420 - patch.revision = selected; │ │421 - evt.consume(.repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ │ │422 - .id = id.*, │ │423 - .timestamp = timestamp, │ │424 - .author = author, │ │425 - .event = .{ .patch = patch }, │ │426 - }}) catch |update_err| { │ │427 - serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) }); │ │428 - }; │ │429 - } │ │430 -} │ │431 - │ │191 // tombstone first so a missing fork never remains visible │ │192 pub fn remove( │ │193 io: std.Io, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/lib.zig│ ╰───────────╯ ╭────────────────────────────────────────────────╮ │2 pub const command = @import("./command.zig");│ │3 pub const main = @import("./main.zig"); │ │4 pub const event = @import("./event.zig"); │ │5 +pub const push = @import("./push.zig"); │ │6 pub const fork = @import("./fork.zig"); │ │7 pub const pch = @import("./patch.zig"); │ │8 pub const serve = @import("./serve.zig"); │ ╰────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────╮ │1 const std = @import("std"); │ │2 +const push = @import("push.zig");│ │3 const xit = @import("xit"); │ │4 const rp = xit.repo; │ │5 const xitui = xit.xitui; │ ╰────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │360 │ │361 const draft_path = try fork.forkPath(allocator, handler.repo_root_path, &route.id); │ │362 defer allocator.free(draft_path); │ │362 - var draft = rp.AnyRepo(.xit, .{ .ProgressCtx = *evt.PushProgress }).open(io, allocator, .{ .path = draft_path }) catch │ │363 + var draft = rp.AnyRepo(.xit, .{ .ProgressCtx = *push.PushProgress }).open(io, allocator, .{ .path = draft_path }) catch│ │364 return writeError(sess, "patch draft not found"); │ │365 defer draft.deinit(io, allocator); │ │366 var reader_buf: [4096]u8 = undefined; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │385 var target_repo = rp.Repo(.xit, repo.self_repo_opts).open(io, allocator, .{ .path = target_path }) catch │ │386 return writeError(sess, "repo not found or has the wrong hash"); │ │387 defer target_repo.deinit(io, allocator); │ │387 - try fork.receivePack(repo.self_repo_opts, io, allocator, repo, &target_repo, &route.id, author, timestamp, &reader.interface, &writer.interface, handler.err);│ │388 + try push.receiveFork(repo.self_repo_opts, io, allocator, repo, &target_repo, &route.id, author, timestamp, &reader.interface, &writer.interface, handler.err);│ │389 }, │ │390 } │ │391 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │398 const repo_identity = if (std.mem.startsWith(u8, path, repo_prefix)) path[repo_prefix.len..] else path; │ │399 │ │400 const create_if_missing = parsed.service == .receive_pack; │ │400 - const any_repo_opts: rp.AnyRepoOpts(.xit) = .{ .ProgressCtx = *evt.PushProgress }; │ │401 + const any_repo_opts: rp.AnyRepoOpts(.xit) = .{ .ProgressCtx = *push.PushProgress }; │ │402 │ │403 const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>/<repo>"); │ │404 switch (try authorizeRepoKey(io, allocator, handler.admin_repo_path, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯