╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────╮ │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, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯