╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ push.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
340 var sideband = progress.Sideband{ 341 .response = &response, 342 .writer = writer, 343 .sess = sess, 344 .size_limit = if (max_repo_size) |max| .{ .file = fork_repo.core.db_file, .max = max } else null, 345 }; 346 347 // execute a transaction that receives the push and materializes its revision 348 const result = blk: { 349 const DB = rp.Repo(.xit, repo_opts).DB; 350 const State = rp.Repo(.xit, repo_opts).State; 351 const Ctx = struct { 352 core: *rp.Repo(.xit, repo_opts).Core, 353 target_core: *rp.Repo(.xit, repo_opts).Core, 354 io: std.Io, 355 allocator: std.mem.Allocator, 356 reader: *std.Io.Reader, 357 writer: *std.Io.Writer, 358 response: *xit.net_server_receive_pack.Response, 359 patch_id: [evt.event_id_size * 2]u8, 360 patch: evt.Patch.Record, 361 published: bool, 362 target_oid: [hash.hexLen(repo_opts.hash)]u8, 363 title: []const u8, 364 author: evt.CommitAuthor, 365 timestamp: u64, 366 newest: ?evt.PatchRev.WithId, 367 revision_id_maybe: *?[evt.event_id_size]u8, 368 sideband: *progress.Sideband, 369 370 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { 371 // receive the branch update 372 var moment = try DB.HashMap(.read_write).init(cursor.*); 373 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; 374 try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, .{ .allowed_ref = "refs/heads/" ++ fork.ref.name, .deferred_response = ctx.response }, ctx.sideband); 375 try writeReceivedPatches(repo_opts, state, ctx.io, ctx.allocator, ctx.response, ctx.writer); 376 if (ctx.sideband.gone) return error.ClientGone; 377 378 // copy the target history needed to preserve the merge base, 379 // skipping what the fork already holds 380 const source_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), ctx.io, .{ .ref = fork.ref })) orelse return error.CancelTransaction; 381 var target_repo_moment = try ctx.target_core.latestMoment(); 382 const target_state = State(.read_only){ .core = ctx.target_core, .extra = .{ .moment = &target_repo_moment } }; 383 var objects = try obj.ObjectIterator(.xit, repo_opts).init(target_state, ctx.io, ctx.allocator, .{ .kind = .all }); 384 defer objects.deinit(); 385 try pch.includeMissing(repo_opts, &objects, state.readOnly(), target_state, ctx.io, ctx.allocator, &ctx.target_oid); 386 try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, ctx.sideband); 387 388 const base_oid = try mrg.commonAncestor(.xit, repo_opts, state.readOnly(), ctx.io, ctx.allocator, &ctx.target_oid, &source_oid); 389 const existing_revision = if (ctx.newest) |latest| 390 if (std.mem.eql(u8, latest.record.event.base_oid, &base_oid) and 391 std.mem.eql(u8, latest.record.event.source_oid, &source_oid)) latest else null 392 else 393 null; 394 395 // reuse an identical revision or record a new one 396 var revision_arena = std.heap.ArenaAllocator.init(ctx.allocator); 397 defer revision_arena.deinit(); 398 var events: [2]evt.EventWithId = undefined; 399 var event_count: usize = 0; 400 if (existing_revision) |latest| { 401 if (ctx.published) ctx.revision_id_maybe.* = latest.id; 402 } else { 403 const prepared = try evt.PatchRev.prepare(.xit, repo_opts, state, ctx.io, &revision_arena, &base_oid, &source_oid, ctx.title, ctx.author, ctx.timestamp); 404 events[event_count] = prepared.event; 405 event_count += 1; 406 ctx.revision_id_maybe.* = try evt.parseEventId(&prepared.event.id); 407 408 if (!ctx.published) { 409 var patch = ctx.patch.event; 410 patch.revision = prepared.revision; 411 events[event_count] = .{ 412 .id = ctx.patch_id, 413 .timestamp = ctx.timestamp, 414 .author = ctx.author, 415 .event = .{ .patch = patch }, 416 }; 417 event_count += 1; 418 } 419 } 420 421 // published patches keep only revisions in the fork 422 if (ctx.published and !ctx.patch.removed) { 423 events[event_count] = .{ 424 .id = ctx.patch_id, 425 .timestamp = ctx.timestamp, 426 .author = ctx.author, 427 .event = .{ .patch = null }, 428 }; 429 event_count += 1; 430 } 431 432 // commit and index any new events 433 if (event_count > 0) { 434 try evt.commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, evt.events_ref, events[0..event_count], null); 435 _ = try evt.consumeInTransaction(.fork, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, evt.events_ref); 436 } 437 // the patch branch's file index lands with the push 438 _ = try find.refreshInTransaction(repo_opts, state, &moment, ctx.io, ctx.allocator, ctx.sideband); 439 440 // a fork push tracks no ref updates, so it records no ranges 441 try writeUndo(repo_opts, state, ctx.io, ctx.allocator, ctx.author, &.{}); 442 // the copied target history and the index count against the limit too 443 try ctx.sideband.checkSize(ctx.io); 444 } 445 }; 446 447 try fork_repo.core.db_file.lock(io, .exclusive); 448 defer fork_repo.core.db_file.unlock(io); 449 450 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); 451 break :blk history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ 452 .core = &fork_repo.core, 453 .target_core = &target_repo.core, 454 .io = io, 455 .allocator = allocator, 456 .reader = reader, 457 .writer = writer, 458 .response = &response, 459 .patch_id = id.*, 460 .patch = fork_patch, 461 .published = published, 462 .target_oid = target_oid, 463 .title = title, 464 .author = author, 465 .timestamp = timestamp, 466 .newest = newest, 467 .revision_id_maybe = &revision_id_maybe, 468 .sideband = &sideband, 469 }); 470 }; 471 result catch |err| switch (err) { 472 // a refused push has already written the report saying why 473 error.CancelTransaction => return response.finish(writer, null), 474 else => { 475 response.finish(writer, @errorName(err)) catch {}; 476 return err; 477 }, 478 }; 479 480 if (!published) return response.finish(writer, null); 481 482 sideband.run(io, .{ .text = "Updating patches" }) catch {}; 483 sideband.run(io, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = 1 } }) catch {}; 484 485 // best-effort update the published patch so it has the new revision 486 var refreshed = false; 487 update: { 488 const revision_id = revision_id_maybe orelse break :update; 489 var update_arena = std.heap.ArenaAllocator.init(allocator); 490 defer update_arena.deinit(); 491 const target_update_moment = evt.currentMoment(repo_opts, target_repo) catch break :update; 492 const published_patch = (evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, target_update_moment, &update_arena, &patch_id) catch break :update) orelse break :update; 493 if (published_patch.removed) break :update; 494 const fork_update_moment = evt.currentMoment(repo_opts, fork_repo) catch break :update; 495 const revision = (evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_update_moment, &update_arena, &revision_id) catch break :update) orelse break :update; 496 if (revision.removed) break :update; 497 const selected = evt.Patch.Revision.fromRecord(revision_id, revision); 498 if (published_patch.event.revision) |current| { 499 if (evt.fieldEqual(evt.Patch.Revision, current, selected)) break :update; 500 } 501 var patch = published_patch.event; 502 patch.revision = selected; 503 evt.consume(.{ .server = server }, .repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ 504 .id = id.*, 505 .timestamp = timestamp, 506 .author = author, 507 .event = .{ .patch = patch }, 508 }}) catch |update_err| { 509 serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) }); 510 break :update; 511 }; 512 // consume also refreshed mergeability 513 refreshed = true; 514 } 515 516 if (!refreshed) pch.refreshMergeability(repo_opts, io, allocator, target_repo, server.users_dir, patch_id); 517 sideband.run(io, .{ .complete_one = .writing_patch }) catch {}; 518 sideband.run(io, .{ .end = .writing_patch }) catch {}; 519 520 // the client may read either repo as soon as it gets the final response. 521 try response.finish(writer, null); 522 } 523