╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Issue.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
232 if (!fieldsValid(updated.title, updated.labels)) return error.InvalidFields; 233 }, 234 } 235 236 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{ 237 .id = std.fmt.bytesToHex(id_bytes.*, .lower), 238 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 239 .author = author, 240 .event = .{ .issue = updated }, 241 }}); 242 } 243 244 // the live fields with `resolve` applied: submitted title/labels stand in for the 245 // live ones, and a conflicted description is reassembled from the hunk 246 // resolutions against the conflict entry. the chunking is deterministic: the 247 // entry holds immutable slots and any event since would have removed it, so 248 // the hunks match what the resolve form was built from. a settled or 249 // unreadable entry keeps the live description. 250 fn resolveFields( 251 comptime repo_kind: rp.RepoKind, 252 comptime repo_opts: rp.RepoOpts(repo_kind), 253 io: std.Io, 254 allocator: std.mem.Allocator, 255 arena: *std.heap.ArenaAllocator, 256 repo: *rp.Repo(repo_kind, repo_opts), 257 id_bytes: *const [evt.event_id_size]u8, 258 live: Record, 259 resolve: Resolve, 260 ) !Self { 261 const DB = evt.EventDB(repo_opts.hash); 262 const aa = arena.allocator(); 263 264 var updated = live.event; 265 if (resolve.title) |title| updated.title = title; 266 if (resolve.labels) |labels| updated.labels = labels; 267 268 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; 269 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 270 const moment = (if (event_db_maybe) |*event_db| 271 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 272 else if (repo_kind == .git) 273 return updated 274 else 275 evt.currentMoment(repo_opts, repo)) catch return updated; 276 277 const conflicts_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, conflicts_key))) orelse return updated; 278 const conflicts = try DB.SortedMap(.read_only).init(conflicts_cursor); 279 const order_key = evt.orderKeyDesc(live.created_order, id_bytes); 280 const conflict_cursor = (try conflicts.getCursor(&order_key)) orelse return updated; 281 const entry = try DB.HashMap(.read_only).init(conflict_cursor); 282 283 const fields_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.conflicted_fields_key))) orelse return updated; 284 const conflicted_fields = try fields_cursor.readBytesAlloc(aa, null); 285 if (std.mem.indexOf(u8, conflicted_fields, "description") == null) return updated; 286 287 const their_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.their_record_key))) orelse return updated; 288 const theirs = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(their_cursor)); 289 290 // absent when both sides created the issue independently 291 var base_description: []const u8 = ""; 292 if (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.base_record_key))) |base_cursor| { 293 const base = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(base_cursor)); 294 base_description = base.event.description; 295 } 296 297 const chunks = try diff3.chunks(allocator, arena, base_description, live.event.description, theirs.event.description); 298 var resolutions: std.ArrayList([]const u8) = .empty; 299 var hunk_index: usize = 0; 300 for (chunks) |chunk| { 301 if (chunk != .conflict) continue; 302 try resolutions.append(aa, if (hunk_index < resolve.hunks.len) resolve.hunks[hunk_index] else (chunk.conflict.ours orelse "")); 303 hunk_index += 1; 304 } 305 updated.description = try diff3.assemble(aa, chunks, resolutions.items); 306 return updated; 307 } 308 309 // the issue `id_bytes` names in the repo's consumed event db (the repo's own 310 // db for xit; the side db next to a git repo), or null 311 fn readById( 312 comptime repo_kind: rp.RepoKind, 313 comptime repo_opts: rp.RepoOpts(repo_kind), 314 io: std.Io, 315 allocator: std.mem.Allocator, 316 arena: *std.heap.ArenaAllocator, 317 repo: *rp.Repo(repo_kind, repo_opts), 318 id_bytes: *const [evt.event_id_size]u8, 319 ) !?Record { 320 const DB = evt.EventDB(repo_opts.hash); 321 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; 322 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 323 const moment = (if (event_db_maybe) |*event_db| 324 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 325 else if (repo_kind == .git) 326 return null 327 else 328 evt.currentMoment(repo_opts, repo)) catch return null; 329 const event_id_to_issue_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, record_map_key))) orelse return null; 330 const event_id_to_issue = try DB.HashMap(.read_only).init(event_id_to_issue_cursor); 331 const issue_cursor = (try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, id_bytes))) orelse return null; 332 const issue_map = try DB.HashMap(.read_only).init(issue_cursor); 333 return try evt.read(Record, DB, repo_opts.hash, arena, issue_map); 334 } 335 336 // remove an issue's order key from its labels' `status` sets, pruning entries 337 // whose set becomes empty 338 fn removeFromLabelSets( 339 comptime DB: type, 340 label_to_issues: DB.SortedMap(.read_write), 341 labels: []const u8, 342 status: Status, 343 order_key: []const u8, 344 ) !void { 345 var label_iter = labelIterator(labels); 346 while (label_iter.next()) |label| { 347 // skipped the same way consume skipped it, so it was never indexed 348 if (label.len == 0 or label.len > label_max_len) continue; 349 var key_buffer: LabelStatusKey = undefined; 350 const key = try labelStatusKey(&key_buffer, label, status); 351 const label_set_cursor = try label_to_issues.putCursor(key); 352 const label_set = try DB.SortedSet(.read_write).init(label_set_cursor); 353 _ = try label_set.remove(order_key); 354 if (0 == try label_set.count()) _ = try label_to_issues.remove(key); 355 } 356 } 357