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