╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Issue.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
144 if (tag.len > tag_max_len) return error.TagTooLong; 145 var key_buffer: TagStatusKey = undefined; 146 const tag_set_cursor = try tag_to_issues.putCursor(try tagStatusKey(&key_buffer, tag, event.status)); 147 const tag_set = try DB.SortedSet(.read_write).init(tag_set_cursor); 148 try tag_set.put(&order_key); 149 } 150 } else { 151 // drop it from the ordered sets using its recorded creation timestamp and status 152 if (try event_id_to_issue.getCursor(issue_key)) |existing_cursor| { 153 const existing_issue = try DB.HashMap(.read_only).init(existing_cursor); 154 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_issue); 155 const order_key = evt.orderKeyDesc(existing_event.created_ts, event_id); 156 157 const status_set = try statusSet(DB, status_to_issues, existing_event.status); 158 _ = try status_set.remove(&order_key); 159 160 try removeFromTagSets(DB, tag_to_issues, existing_event.tags, existing_event.status, &order_key); 161 162 const issue_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key)); 163 const issue_id_set = try DB.SortedSet(.read_write).init(issue_id_set_cursor); 164 _ = try issue_id_set.remove(&order_key); 165 166 _ = try conflicts.remove(&order_key); 167 _ = try issue_id_to_field_to_oid.remove(issue_key); 168 } 169 if (!try event_id_to_issue.remove(issue_key)) return error.EventNotFound; 170 } 171 } 172 173 // `status`'s sorted set within `statuses`, keyed by status name 174 fn statusSet( 175 comptime DB: type, 176 statuses: DB.SortedMap(.read_write), 177 status: Status, 178 ) !DB.SortedSet(.read_write) { 179 const cursor = try statuses.putCursor(@tagName(status)); 180 return DB.SortedSet(.read_write).init(cursor); 181 } 182 183 // what an update replaces on an issue: its status, or its content 184 pub const Update = union(enum) { 185 status: Status, 186 fields: struct { title: []const u8, tags: []const u8, description: []const u8 }, 187 }; 188 189 // re-emit the issue `id_bytes` names with `change` applied, preserving the 190 // rest of its current state. error.NotFound when the id names no issue. 191 // `repo` must be writable. 192 pub fn update( 193 comptime repo_kind: rp.RepoKind, 194 comptime repo_opts: rp.RepoOpts(repo_kind), 195 io: std.Io, 196 allocator: std.mem.Allocator, 197 repo: *rp.Repo(repo_kind, repo_opts), 198 id_bytes: *const [evt.event_id_size]u8, 199 change: Update, 200 author_email: []const u8, 201 ) !void { 202 var arena = std.heap.ArenaAllocator.init(allocator); 203 defer arena.deinit(); 204 205 const issue = (try readById(repo_kind, repo_opts, io, allocator, &arena, repo, id_bytes)) orelse return error.NotFound; 206 207 var updated = issue; 208 switch (change) { 209 .status => |status| { 210 if (issue.status == status) return; 211 updated.status = status; 212 }, 213 .fields => |fields| { 214 updated.title = fields.title; 215 updated.tags = fields.tags; 216 updated.description = fields.description; 217 }, 218 } 219 220 try evt.consume(repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{ 221 .id = std.fmt.bytesToHex(id_bytes.*, .lower), 222 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 223 .author_email = author_email, 224 .event = .{ .issue = updated }, 225 }}); 226 } 227 228 // the issue `id_bytes` names in the repo's consumed event db (the repo's own 229 // db for xit; the side db next to a git repo), or null 230 fn readById( 231 comptime repo_kind: rp.RepoKind, 232 comptime repo_opts: rp.RepoOpts(repo_kind), 233 io: std.Io, 234 allocator: std.mem.Allocator, 235 arena: *std.heap.ArenaAllocator, 236 repo: *rp.Repo(repo_kind, repo_opts), 237 id_bytes: *const [evt.event_id_size]u8, 238 ) !?@This() { 239 const DB = evt.EventDB(repo_opts.hash); 240 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; 241 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 242 const moment = (if (event_db_maybe) |*event_db| 243 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 244 else if (repo_kind == .git) 245 return null 246 else 247 evt.currentMoment(repo_opts, repo)) catch return null; 248 const event_id_to_issue_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->issue"))) orelse return null; 249 const event_id_to_issue = try DB.HashMap(.read_only).init(event_id_to_issue_cursor); 250 const issue_cursor = (try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, id_bytes))) orelse return null; 251 const issue_map = try DB.HashMap(.read_only).init(issue_cursor); 252 return try evt.read(@This(), DB, repo_opts.hash, arena, issue_map); 253 } 254 255 // remove an issue's order key from its tags' `status` sets, pruning entries 256 // whose set becomes empty 257 fn removeFromTagSets( 258 comptime DB: type, 259 tag_to_issues: DB.SortedMap(.read_write), 260 tags: []const u8, 261 status: Status, 262 order_key: []const u8, 263 ) !void { 264 var tag_iter = tagIterator(tags); 265 while (tag_iter.next()) |tag| { 266 if (tag.len == 0) continue; 267 var key_buffer: TagStatusKey = undefined; 268 const key = try tagStatusKey(&key_buffer, tag, status); 269 const tag_set_cursor = try tag_to_issues.putCursor(key); 270 const tag_set = try DB.SortedSet(.read_write).init(tag_set_cursor); 271 _ = try tag_set.remove(order_key); 272 if (0 == try tag_set.count()) _ = try tag_to_issues.remove(key); 273 } 274 } 275