╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ fork.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
318 ); 319 tree_entries = .{ 320 .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, 321 .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, 322 }; 323 events[event_count] = .{ 324 .id = revision_hex, 325 .timestamp = ctx.timestamp, 326 .author = ctx.author, 327 .tree_entries = &tree_entries, 328 .event = .{ .patchrev = revision_event }, 329 }; 330 event_count += 1; 331 ctx.revision_id_maybe.* = revision_id; 332 333 if (!ctx.published) { 334 var patch = ctx.patch.event; 335 patch.revision = .{ 336 .id = revision_hex, 337 .squash_oid = &patch_oid, 338 .source_oid = &source_oid, 339 }; 340 events[event_count] = .{ 341 .id = ctx.patch_id, 342 .timestamp = ctx.timestamp, 343 .author = ctx.author, 344 .event = .{ .patch = patch }, 345 }; 346 event_count += 1; 347 } 348 } 349 350 // published patches keep only revisions in the fork 351 if (ctx.published and !ctx.patch.removed) { 352 events[event_count] = .{ 353 .id = ctx.patch_id, 354 .timestamp = ctx.timestamp, 355 .author = ctx.author, 356 .event = .{ .patch = null }, 357 }; 358 event_count += 1; 359 } 360 361 // commit and index any new events 362 if (event_count > 0) { 363 try evt.commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, evt.events_ref, events[0..event_count], null); 364 if (!try evt.consumeInTransaction(.fork, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, evt.events_ref)) return error.CancelTransaction; 365 } 366 try xit.undo.writeMessage(repo_opts, state, .push); 367 } 368 }; 369 370 try fork_repo.core.db_file.lock(io, .exclusive); 371 defer fork_repo.core.db_file.unlock(io); 372 373 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); 374 history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ 375 .core = &fork_repo.core, 376 .target_core = &target_repo.core, 377 .io = io, 378 .allocator = allocator, 379 .reader = reader, 380 .writer = writer, 381 .patch_id = id.*, 382 .patch = fork_patch, 383 .published = published, 384 .target_oid = target_oid, 385 .title = title, 386 .author = author, 387 .timestamp = timestamp, 388 .newest = newest, 389 .revision_id_maybe = &revision_id_maybe, 390 }) catch |err| switch (err) { 391 error.CancelTransaction => {}, 392 else => |other| return other, 393 }; 394 try writer.flush(); 395 } 396 397 // best-effort update the published patch so it has the new revision 398 { 399 const revision_id = revision_id_maybe orelse return; 400 if (!published) return; 401 var update_arena = std.heap.ArenaAllocator.init(allocator); 402 defer update_arena.deinit(); 403 const target_update_moment = evt.currentMoment(repo_opts, target_repo) catch return; 404 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; 405 if (published_patch.removed) return; 406 const fork_update_moment = evt.currentMoment(repo_opts, fork_repo) catch return; 407 const revision = (evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_update_moment, &update_arena, &revision_id) catch return) orelse return; 408 if (revision.removed) return; 409 const selected = evt.Patch.Revision.fromRecord(revision_id, revision); 410 if (published_patch.event.revision) |current| { 411 if (evt.fieldEqual(evt.Patch.Revision, current, selected)) return; 412 } 413 var patch = published_patch.event; 414 patch.revision = selected; 415 evt.consume(.repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ 416 .id = id.*, 417 .timestamp = timestamp, 418 .author = author, 419 .event = .{ .patch = patch }, 420 }}) catch |update_err| { 421 serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) }); 422 }; 423 } 424 } 425 426 // tombstone first so a missing fork never remains visible 427 pub fn remove( 428 io: std.Io, 429 allocator: std.mem.Allocator, 430 repo_root_path: []const u8, 431 admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), 432 id: *const [evt.event_id_size * 2]u8, 433 expected_user_id: ?*const [evt.event_id_size]u8, 434 author: evt.CommitAuthor, 435 ) !void { 436 const fork_id = try evt.parseEventId(id); 437 var arena = std.heap.ArenaAllocator.init(allocator); 438 defer arena.deinit(); 439 const moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); 440 const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft; 441 if (expected_user_id) |user_id| { 442 if (!std.mem.eql(u8, record.event.user_id, user_id)) return error.InvalidPatchDraft; 443 } 444 if (!record.removed) try evt.remove(.admin, .xit, evt.admin_repo_opts, io, allocator, admin_repo, &fork_id, .fork, author); 445 446 const path = try forkPath(allocator, repo_root_path, id); 447 defer allocator.free(path); 448 try std.Io.Dir.cwd().deleteTree(io, path); 449 } 450