╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │289 │ │290 const haxy_moment = try evt.currentMomentFromRepoMoment(repo_opts.hash, moment.readOnly()); │ │117 - const patch_record = (try evt.Patch.readById(DB, repo_opts.hash, haxy_moment, &arena, patch_id)) orelse return; │ │118 - const selected = patch_record.event.revision orelse return; │ │119 - if (patch_record.removed or patch_record.event.status == .merged) return; │ │291 + const patch_record = (try evt.Patch.readById(DB, repo_opts.hash, haxy_moment, &arena, patch_id)) orelse return false; │ │292 + const selected = patch_record.event.revision orelse return false; │ │293 + if (patch_record.removed or patch_record.event.status == .merged) return false; │ │294 + if (expected_patch) |expected| { │ │295 + if (!evt.fieldEqual(evt.Patch, expected, patch_record.event)) return false; │ │296 + } │ │297 const revision_id = try evt.parseEventId(&selected.id); │ │298 │ │122 - const patch_hex = std.fmt.bytesToHex(patch_id.*, .lower); │ │123 - const data_path = std.fs.path.dirname(repo_root_path) orelse "."; │ │124 - const fork_path = try std.fs.path.join(arena.allocator(), &.{ data_path, "forks", &patch_hex }); │ │125 - var fork_repo = rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = fork_path, .require_repo_root = true }) catch return; │ │126 - defer fork_repo.deinit(io, allocator); │ │299 + const fork_moment = try evt.currentMoment(repo_opts, fork_repo); │ │300 + const revision = (try evt.PatchRev.readById(DB, repo_opts.hash, fork_moment, &arena, &revision_id)) orelse return false; │ │301 + if (!selected.matches(revision)) return false; │ │302 │ │128 - const fork_moment = evt.currentMoment(repo_opts, &fork_repo) catch return; │ │129 - const revision = (evt.PatchRev.readById(DB, repo_opts.hash, fork_moment, &arena, &revision_id) catch return) orelse return; │ │130 - if (!selected.matches(revision)) return; │ │131 - │ │303 var fork_repo_moment = try fork_repo.core.latestMoment(); │ │304 const fork_state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &fork_repo.core, .extra = .{ .moment = &fork_repo_moment } };│ │305 var event_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │135 - if (revision.event_oid.len != event_oid.len) return; │ │306 + if (revision.event_oid.len != event_oid.len) return false; │ │307 @memcpy(&event_oid, revision.event_oid); │ │137 - var event_object = obj.Object(.xit, repo_opts).initCommit(fork_state, io, allocator, &event_oid) catch return;│ │308 + var event_object = try obj.Object(.xit, repo_opts).initCommit(fork_state, io, allocator, &event_oid); │ │309 defer event_object.deinit(); │ │139 - const original_author = commitAuthor(event_object.content.commit.metadata.author orelse return) catch return; │ │310 + const original_author = try commitAuthor(event_object.content.commit.metadata.author orelse return false); │ │311 │ │312 var events: [2]evt.EventWithId = undefined; │ │313 var event_count: usize = 0; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │336 patch.status = .merged; │ │337 events[event_count] = .{ │ │338 .id = std.fmt.bytesToHex(patch_id.*, .lower), │ │168 - .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │169 - .author = original_author, │ │339 + .timestamp = if (merged_author != null) merged_timestamp else @intCast(std.Io.Timestamp.now(io, .real).toSeconds()),│ │340 + .author = merged_author orelse original_author, │ │341 .event = .{ .patch = patch }, │ │342 }; │ │343 event_count += 1; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │344 │ │345 try evt.commitEvents(.xit, repo_opts, state, io, allocator, evt.events_ref, events[0..event_count], null); │ │346 if (!try evt.consumeInTransaction(.repo, .xit, repo_opts, state, db, moment, io, allocator, evt.events_ref)) return error.CancelTransaction;│ │347 + return true; │ │348 } │ │349 │ │350 +// find patches merged by commits received in a push │ │351 pub fn detectMerged( │ │352 comptime repo_opts: rp.RepoOpts(.xit), │ │353 state: rp.Repo(.xit, repo_opts).State(.read_write), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │357 allocator: std.mem.Allocator, │ │358 repo_root_path: []const u8, │ │359 updates: []const xit.net_server_receive_pack.AppliedRefUpdate,│ │360 + error_writer: *std.Io.Writer, │ │361 ) !void { │ │362 const DB = evt.EventDB(repo_opts.hash); │ │363 var arena = std.heap.ArenaAllocator.init(allocator); │ ╰───────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │395 const pair = try cursor.readKeyValuePair(); │ │396 var patch_id: [evt.event_id_size]u8 = undefined; │ │397 if ((try pair.key_cursor.readBytes(&patch_id)).len != patch_id.len) return error.InvalidPatch; │ │224 - try importMerged(repo_opts, state, db, moment, io, allocator, repo_root_path, &patch_id); │ │398 + const patch_hex = std.fmt.bytesToHex(patch_id, .lower); │ │399 + importMerged(repo_opts, state, db, moment, io, allocator, repo_root_path, &patch_id) catch |err| { │ │400 + serve_common.logError(io, error_writer, "failed to mark patch {s} as merged: {s}\n", .{ &patch_hex, @errorName(err) });│ │401 + continue; │ │402 + }; │ │403 } │ │404 } │ │405 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │403 }; │ │404 defer allocator.free(repo_path); │ │405 │ │406 - if (try serveIfExists(repo_path, handler.repo_root_path, sess, io, allocator, parsed.service)) { │ │406 + if (try serveIfExists(repo_path, handler.repo_root_path, sess, io, allocator, parsed.service, handler.err)) {│ │407 try sess.exit(0); │ │408 return; │ │409 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │413 // create the on-disk repo for the just-minted event and serve the push │ │414 var repo = try createRepo(any_repo_opts.toRepoOpts(), io, allocator, repo_path); │ │415 defer repo.deinit(io, allocator); │ │416 - try servePack(repo.self_repo_opts, &repo, handler.repo_root_path, sess, io, allocator, parsed.service); │ │416 + try servePack(repo.self_repo_opts, &repo, handler.repo_root_path, sess, io, allocator, parsed.service, handler.err);│ │417 try sess.exit(0); │ │418 } │ │419 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │425 io: std.Io, │ │426 allocator: std.mem.Allocator, │ │427 service: GitService, │ │428 + error_writer: *std.Io.Writer, │ │429 ) !bool { │ │430 // a bare open() creates the directory while probing for the repo, so only│ │431 // attempt it when the path already exists │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │439 defer any_repo.deinit(io, allocator); │ │440 │ │441 switch (any_repo) { │ │441 - inline else => |*repo| try servePack(repo.self_repo_opts, repo, repo_root_path, sess, io, allocator, service), │ │442 + inline else => |*repo| try servePack(repo.self_repo_opts, repo, repo_root_path, sess, io, allocator, service, error_writer),│ │443 } │ │444 return true; │ │445 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯