╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ fork.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
165 rf.remove(.xit, repo_opts, state, ctx.io, events_path) catch |err| switch (err) { 166 error.RefNotFound => {}, 167 else => |other| return other, 168 }; 169 const patch_path = try ref.toPath(&path_buffer); 170 rf.remove(.xit, repo_opts, state, ctx.io, patch_path) catch |err| switch (err) { 171 error.RefNotFound => {}, 172 else => |other| return other, 173 }; 174 175 _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.history_key)); 176 _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.last_object_id_key)); 177 // forks aren't searched, so the copied versions would linger 178 // with nothing to prune them 179 _ = try moment.remove(hash.hashInt(repo_opts.hash, srch_cmmt.index_key)); 180 181 if (head_oid_maybe) |*head_oid| { 182 try rf.write(.xit, repo_opts, state, ctx.io, patch_path, .{ .oid = head_oid }); 183 } else { 184 try bch.add(.xit, repo_opts, state, ctx.io, .{ .name = ref.name, .target = .none }); 185 } 186 try rf.replaceHead(.xit, repo_opts, state, ctx.io, .{ .ref = ref }); 187 188 var config = try xit.config.Config(.xit, repo_opts).init(state.readOnly(), ctx.io, ctx.allocator); 189 defer config.deinit(); 190 try config.add(state, ctx.io, .{ .name = "receive.denydeletes", .value = "true" }); 191 192 // the copied db carries the target's forker entries 193 if (config.local_sections.get(forker_section)) |section| { 194 var names_arena = std.heap.ArenaAllocator.init(ctx.allocator); 195 defer names_arena.deinit(); 196 const na = names_arena.allocator(); 197 var names: std.ArrayList([]const u8) = .empty; 198 for (section.keys()) |key| { 199 if (std.mem.startsWith(u8, key, forker_prefix)) try names.append(na, try na.print(forker_section ++ ".{s}", .{key})); 200 } 201 for (names.items) |name| try config.remove(state, ctx.io, .{ .name = name }); 202 } 203 204 // the copied db carries the target's file index, so the patch 205 // branch's entry starts from one of those rather than walking 206 // the tree 207 _ = try find.refreshInTransaction(repo_opts, state, &moment, ctx.io, ctx.allocator, null); 208 209 try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = undo_action } }); 210 } 211 }; 212 213 try fork_repo.core.db_file.lock(io, .exclusive); 214 defer fork_repo.core.db_file.unlock(io); 215 216 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); 217 try history.appendContext( 218 .{ .slot = try history.getSlot(-1) }, 219 Ctx{ .core = &fork_repo.core, .io = io, .allocator = allocator }, 220 ); 221 } 222 223 // create the patch event 224 try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .fork, .xit, repo_opts, io, allocator, &fork_repo, evt.events_ref, &.{.{ 225 .id = input.id, 226 .timestamp = input.timestamp, 227 .author = input.author, 228 .event = .{ .patch = .{ 229 .title = input.title, 230 .description = input.description, 231 .labels = input.labels, 232 .target_branch = input.target_branch, 233 } }, 234 }}); 235 236 // create the fork event 237 try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, user_repo, evt.events_ref, &.{.{ 238 .id = input.id, 239 .timestamp = input.timestamp, 240 .author = input.author, 241 .event = .{ .fork = .{ 242 .repo_id = std.fmt.bytesToHex(input.repo_id, .lower), 243 .repo_user_id = std.fmt.bytesToHex(input.repo_user_id, .lower), 244 } }, 245 }}); 246 } 247 248 // tombstone first so a missing fork never remains visible 249 pub fn remove( 250 io: std.Io, 251 allocator: std.mem.Allocator, 252 users_dir: []const u8, 253 forker_id: *const [evt.event_id_size]u8, 254 id: *const [evt.event_id_size * 2]u8, 255 author: evt.CommitAuthor, 256 ) !void { 257 const fork_id = try evt.parseEventId(id); 258 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, forker_id)) orelse return error.InvalidPatchDraft; 259 defer user_repo.deinit(io, allocator); 260 var arena = std.heap.ArenaAllocator.init(allocator); 261 defer arena.deinit(); 262 const moment = (try evt.userMoment(&user_repo)) orelse return error.InvalidPatchDraft; 263 const record = (try evt.Fork.readById(evt.UserDB, evt.user_repo_opts.hash, moment, &arena, &fork_id)) orelse return error.InvalidPatchDraft; 264 if (!record.removed) try evt.remove(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, &fork_id, .fork, author); 265 266 const path = try forkPath(allocator, users_dir, forker_id, &fork_id); 267 defer allocator.free(path); 268 try std.Io.Dir.cwd().deleteTree(io, path); 269 } 270