╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │425 .merged => return error.PatchAlreadyMerged, │ │426 } │ │427 const selected = patch_record.event.revision orelse return error.PatchNotPushed; │ │424 - const revision_id = try evt.parseEventId(&selected.id); │ │428 const target_ref = rf.Ref{ .kind = .head, .name = patch_record.event.target_branch }; │ │429 │ │427 - // open and lock the fork │ │430 + // open the fork │ │431 const fork_path = try fork.forkPath(arena.allocator(), repo_root_path, &input.id); │ │432 var fork_repo = rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = fork_path, .require_repo_root = true }) catch return error.PatchDataUnavailable;│ │433 defer fork_repo.deinit(io, allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │431 - try fork_repo.core.db_file.lock(io, .shared); │ │432 - defer fork_repo.core.db_file.unlock(io); │ │433 - │ │434 - // require the fork's newest revision │ │435 - { │ │436 - const fork_moment = try evt.currentMoment(repo_opts, &fork_repo); │ │437 - const newest = (try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena)) orelse return error.PatchDataUnavailable;│ │438 - if (!std.mem.eql(u8, &newest.id, &revision_id) or │ │439 - !selected.matches(newest.record)) return error.PatchOutOfDate; │ │440 - } │ │441 - │ │434 // select the commit and merge identity │ │435 const merge_oid = oidArray(repo_opts.hash, switch (input.revision) { │ │436 .squash => selected.squash_oid, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │462 │ │463 var check_arena = std.heap.ArenaAllocator.init(ctx.allocator); │ │464 defer check_arena.deinit(); │ │465 + │ │466 + // require the fork's newest revision while both repos are locked │ │467 + const selected_revision = ctx.expected_patch.revision.?; │ │468 + const revision_id = try evt.parseEventId(&selected_revision.id); │ │469 + const fork_moment = try evt.currentMoment(repo_opts, ctx.fork_repo); │ │470 + const newest = (try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &check_arena)) orelse return error.PatchDataUnavailable;│ │471 + if (!std.mem.eql(u8, &newest.id, &revision_id) or │ │472 + !selected_revision.matches(newest.record)) return error.PatchOutOfDate; │ │473 + │ │474 const availability = try readMergeability(repo_opts, state.readOnly(), ctx.io, &check_arena, &ctx.patch_id, ctx.expected_patch); │ │475 switch (availability.get(ctx.revision)) { │ │476 .clean => {}, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │515 } │ │516 }; │ │517 │ │518 + try fork_repo.core.db_file.lock(io, .shared); │ │519 + defer fork_repo.core.db_file.unlock(io); │ │520 try target_repo.core.db_file.lock(io, .exclusive);│ │521 defer target_repo.core.db_file.unlock(io); │ │522 │ ╰───────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │536 .timestamp = input.timestamp, │ │537 }); │ │538 } │ │539 + refreshMergeability(repo_opts, io, allocator, target_repo, null);│ │540 } │ │541 │ │542 // merge a published patch and remove its fork │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │549 target_repo: *rp.Repo(.xit, repo_opts), │ │550 input: MergeInput, │ │551 ) !void { │ │548 - merge(repo_opts, io, allocator, repo_root_path, target_repo, input) catch |err| { │ │549 - if (err == error.PatchDataUnavailable or err == error.PatchOutOfDate) { │ │550 - const id = evt.parseEventId(&input.id) catch return err; │ │551 - refreshMergeability(repo_opts, io, allocator, target_repo, id); │ │552 - } │ │553 - return err; │ │554 - }; │ │552 + try merge(repo_opts, io, allocator, repo_root_path, target_repo, input); │ │553 try fork.remove(io, allocator, repo_root_path, admin_repo, &input.id, null, input.author);│ │556 - refreshMergeability(repo_opts, io, allocator, target_repo, null); │ │554 } │ │555 │ │556 fn commitAuthor(line: []const u8) !evt.CommitAuthor { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │358 if (err == error.CancelTransaction) return; │ │359 return err; │ │360 }; │ │361 + │ │362 + response.progress(writer, "Checking merges...\n") catch {}; │ │363 │ │364 // best-effort update the published patch so it has the new revision│ │365 update: { │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │379 } │ │380 var patch = published_patch.event; │ │381 patch.revision = selected; │ │380 - evt.consume(.repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ │ │382 + evt.consume(.server, .repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{│ │383 .id = id.*, │ │384 .timestamp = timestamp, │ │385 .author = author, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │386 .event = .{ .patch = patch }, │ │387 }}) catch |update_err| { │ │388 serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) });│ │389 + break :update; │ │390 }; │ │391 + // consume refreshed the published patch before acknowledging the push │ │392 + return response.finish(writer, null); │ │393 } │ │394 │ │390 - response.progress(writer, "Checking merges...\n") catch {}; │ │395 pch.refreshMergeability(repo_opts, io, allocator, target_repo, patch_id); │ │396 │ │397 // the client may read either repo as soon as it gets the final response. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │138 try err.print("serving web UI on http://{s}:{d}/\n", .{ wui_listen_address.host, wui_server.socket.address.getPort() });│ │139 try err.flush(); │ │140 │ │141 - runWebListener(io, allocator, &wui_server, &tasks, .{ .remote = .{ │ │141 + runWebListener(io, allocator, &wui_server, &tasks, .{ .server = .{ │ │142 .admin_repo_path = admin_repo_path, │ │143 .session_store = session_store, │ │144 .git_http_port = git_http_port, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯