╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ fork.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
125 allocator: std.mem.Allocator, 126 127 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { 128 var moment = try DB.HashMap(.read_write).init(cursor.*); 129 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; 130 const head_oid_maybe = try rf.readHeadRecurMaybe(.xit, repo_opts, state.readOnly(), ctx.io); 131 132 var path_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; 133 const events_path = try evt.events_ref.toPath(&path_buffer); 134 rf.remove(.xit, repo_opts, state, ctx.io, events_path) catch |err| switch (err) { 135 error.RefNotFound => {}, 136 else => |other| return other, 137 }; 138 const patch_path = try ref.toPath(&path_buffer); 139 rf.remove(.xit, repo_opts, state, ctx.io, patch_path) catch |err| switch (err) { 140 error.RefNotFound => {}, 141 else => |other| return other, 142 }; 143 144 _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.history_key)); 145 _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.last_object_id_key)); 146 // forks aren't searched, so the copied versions would linger 147 // with nothing to prune them 148 _ = try moment.remove(hash.hashInt(repo_opts.hash, srch_cmmt.index_key)); 149 150 if (head_oid_maybe) |*head_oid| { 151 try rf.write(.xit, repo_opts, state, ctx.io, patch_path, .{ .oid = head_oid }); 152 } else { 153 try bch.add(.xit, repo_opts, state, ctx.io, .{ .name = ref.name, .target = .none }); 154 } 155 try rf.replaceHead(.xit, repo_opts, state, ctx.io, .{ .ref = ref }); 156 157 var config = try xit.config.Config(.xit, repo_opts).init(state.readOnly(), ctx.io, ctx.allocator); 158 defer config.deinit(); 159 try config.add(state, ctx.io, .{ .name = "receive.denydeletes", .value = "true" }); 160 161 // the copied db carries the target's file index, so the patch 162 // branch's entry starts from one of those rather than walking 163 // the tree 164 _ = try find.refreshInTransaction(repo_opts, state, &moment, ctx.io, ctx.allocator, null); 165 166 try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = undo_action } }); 167 } 168 }; 169 170 try fork_repo.core.db_file.lock(io, .exclusive); 171 defer fork_repo.core.db_file.unlock(io); 172 173 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); 174 try history.appendContext( 175 .{ .slot = try history.getSlot(-1) }, 176 Ctx{ .core = &fork_repo.core, .io = io, .allocator = allocator }, 177 ); 178 } 179 180 // create the patch event 181 try evt.consume(.server, .fork, .xit, repo_opts, io, allocator, &fork_repo, evt.events_ref, &.{.{ 182 .id = input.id, 183 .timestamp = input.timestamp, 184 .author = input.author, 185 .event = .{ .patch = .{ 186 .title = input.title, 187 .description = input.description, 188 .labels = input.labels, 189 .target_branch = input.target_branch, 190 } }, 191 }}); 192 193 // create the fork event 194 try evt.consume(.server, .admin, .xit, evt.admin_repo_opts, io, allocator, admin_repo, evt.events_ref, &.{.{ 195 .id = input.id, 196 .timestamp = input.timestamp, 197 .author = input.author, 198 .event = .{ .fork = .{ 199 .user_id = &input.user_id, 200 .repo_id = &input.repo_id, 201 } }, 202 }}); 203 204 return fork_path; 205 } 206 207 // tombstone first so a missing fork never remains visible 208 pub fn remove( 209 io: std.Io, 210 allocator: std.mem.Allocator, 211 repo_root_path: []const u8, 212 admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), 213 id: *const [evt.event_id_size * 2]u8, 214 expected_user_id: ?*const [evt.event_id_size]u8, 215 author: evt.CommitAuthor, 216 ) !void { 217 const fork_id = try evt.parseEventId(id); 218 var arena = std.heap.ArenaAllocator.init(allocator); 219 defer arena.deinit(); 220 const moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); 221 const record = (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft; 222 if (expected_user_id) |user_id| { 223 if (!std.mem.eql(u8, record.event.user_id, user_id)) return error.InvalidPatchDraft; 224 } 225 if (!record.removed) try evt.remove(.server, .admin, .xit, evt.admin_repo_opts, io, allocator, admin_repo, &fork_id, .fork, author); 226 227 const path = try forkPath(allocator, repo_root_path, id); 228 defer allocator.free(path); 229 try std.Io.Dir.cwd().deleteTree(io, path); 230 } 231