╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Issue.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
193 repo: *rp.Repo(repo_kind, repo_opts), 194 id_bytes: *const [evt.event_id_size]u8, 195 change: Update, 196 author_email: []const u8, 197 ) !void { 198 var arena = std.heap.ArenaAllocator.init(allocator); 199 defer arena.deinit(); 200 201 const issue = (try readById(repo_kind, repo_opts, io, allocator, &arena, repo, id_bytes)) orelse return error.NotFound; 202 203 var updated = issue.event; 204 switch (change) { 205 .status => |status| { 206 if (updated.status == status) return; 207 updated.status = status; 208 }, 209 .fields => |fields| { 210 updated.title = fields.title; 211 updated.tags = fields.tags; 212 updated.description = fields.description; 213 }, 214 } 215 216 try evt.consume(repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{ 217 .id = std.fmt.bytesToHex(id_bytes.*, .lower), 218 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 219 .author_email = author_email, 220 .event = .{ .issue = updated }, 221 }}); 222 } 223 224 // the issue `id_bytes` names in the repo's consumed event db (the repo's own 225 // db for xit; the side db next to a git repo), or null 226 fn readById( 227 comptime repo_kind: rp.RepoKind, 228 comptime repo_opts: rp.RepoOpts(repo_kind), 229 io: std.Io, 230 allocator: std.mem.Allocator, 231 arena: *std.heap.ArenaAllocator, 232 repo: *rp.Repo(repo_kind, repo_opts), 233 id_bytes: *const [evt.event_id_size]u8, 234 ) !?Record { 235 const DB = evt.EventDB(repo_opts.hash); 236 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; 237 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 238 const moment = (if (event_db_maybe) |*event_db| 239 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 240 else if (repo_kind == .git) 241 return null 242 else 243 evt.currentMoment(repo_opts, repo)) catch return null; 244 const event_id_to_issue_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->issue"))) orelse return null; 245 const event_id_to_issue = try DB.HashMap(.read_only).init(event_id_to_issue_cursor); 246 const issue_cursor = (try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, id_bytes))) orelse return null; 247 const issue_map = try DB.HashMap(.read_only).init(issue_cursor); 248 return try evt.read(Record, DB, repo_opts.hash, arena, issue_map); 249 } 250 251 // remove an issue's order key from its tags' `status` sets, pruning entries 252 // whose set becomes empty 253 fn removeFromTagSets( 254 comptime DB: type, 255 tag_to_issues: DB.SortedMap(.read_write), 256 tags: []const u8, 257 status: Status, 258 order_key: []const u8, 259 ) !void { 260 var tag_iter = tagIterator(tags); 261 while (tag_iter.next()) |tag| { 262 // skipped the same way consume skipped it, so it was never indexed 263 if (tag.len == 0 or tag.len > tag_max_len) continue; 264 var key_buffer: TagStatusKey = undefined; 265 const key = try tagStatusKey(&key_buffer, tag, status); 266 const tag_set_cursor = try tag_to_issues.putCursor(key); 267 const tag_set = try DB.SortedSet(.read_write).init(tag_set_cursor); 268 _ = try tag_set.remove(order_key); 269 if (0 == try tag_set.count()) _ = try tag_to_issues.remove(key); 270 } 271 } 272