╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Patch.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
249 allocator: std.mem.Allocator, 250 repo: *rp.Repo(repo_kind, repo_opts), 251 id: *const [evt.event_id_size]u8, 252 change: Update, 253 author: evt.CommitAuthor, 254 ) !void { 255 var arena = std.heap.ArenaAllocator.init(allocator); 256 defer arena.deinit(); 257 const record = (try readFromRepo(repo_kind, repo_opts, io, allocator, &arena, repo, id)) orelse return error.NotFound; 258 if (record.removed) return error.NotFound; 259 260 var updated = record.event; 261 switch (change) { 262 .status => |status| { 263 if (updated.status == status) return; 264 updated.status = status; 265 }, 266 .fields => |fields| { 267 updated.title = fields.title; 268 updated.tags = fields.tags; 269 updated.description = fields.description; 270 }, 271 .resolve => |resolve| { 272 updated = try resolveFields(repo_kind, repo_opts, io, allocator, &arena, repo, id, record, resolve); 273 }, 274 } 275 if (!fieldsValid(updated.title, updated.tags)) return error.InvalidFields; 276 277 try evt.consume(.repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 278 .id = std.fmt.bytesToHex(id.*, .lower), 279 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 280 .author = author, 281 .event = .{ .patch = updated }, 282 }}); 283 } 284 285 fn resolveFields( 286 comptime repo_kind: rp.RepoKind, 287 comptime repo_opts: rp.RepoOpts(repo_kind), 288 io: std.Io, 289 allocator: std.mem.Allocator, 290 arena: *std.heap.ArenaAllocator, 291 repo: *rp.Repo(repo_kind, repo_opts), 292 id: *const [evt.event_id_size]u8, 293 live: Record, 294 resolve: Resolve, 295 ) !Self { 296 const DB = evt.EventDB(repo_opts.hash); 297 var updated = live.event; 298 if (resolve.title) |title| updated.title = title; 299 if (resolve.tags) |tags| updated.tags = tags; 300 301 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; 302 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 303 const moment = (if (event_db_maybe) |*event_db| 304 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 305 else if (repo_kind == .git) 306 return updated 307 else 308 evt.currentMoment(repo_opts, repo)) catch return updated; 309 const conflicts_cursor = (try moment.getCursor(hash.hashInt(repo_opts.hash, conflicts_key))) orelse return updated; 310 const conflicts = try DB.SortedMap(.read_only).init(conflicts_cursor); 311 const conflict_cursor = (try conflicts.getCursor(&evt.orderKeyDesc(live.created_order, id))) orelse return updated; 312 const entry = try DB.HashMap(.read_only).init(conflict_cursor); 313 const fields_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.conflicted_fields_key))) orelse return updated; 314 const fields = try fields_cursor.readBytesAlloc(arena.allocator(), null); 315 const their_cursor = (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.their_record_key))) orelse return updated; 316 const theirs = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(their_cursor)); 317 318 var field_iter = std.mem.splitScalar(u8, fields, ' '); 319 while (field_iter.next()) |field| { 320 if (std.mem.eql(u8, field, "status") and fieldListed(resolve.theirs, ',', field)) updated.status = theirs.event.status; 321 if (std.mem.eql(u8, field, "revision") and fieldListed(resolve.theirs, ',', field)) updated.revision = theirs.event.revision; 322 } 323 324 if (!fieldListed(fields, ' ', "description")) return updated; 325 var base_description: []const u8 = ""; 326 if (try entry.getCursor(hash.hashInt(repo_opts.hash, evt.base_record_key))) |base_cursor| { 327 const base = try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(base_cursor)); 328 base_description = base.event.description; 329 } 330 const chunks = try diff3.chunks(io, allocator, arena, base_description, live.event.description, theirs.event.description); 331 var resolutions: std.ArrayList([]const u8) = .empty; 332 var hunk_index: usize = 0; 333 for (chunks) |chunk| { 334 if (chunk != .conflict) continue; 335 try resolutions.append(arena.allocator(), if (hunk_index < resolve.hunks.len) resolve.hunks[hunk_index] else (chunk.conflict.ours orelse "")); 336 hunk_index += 1; 337 } 338 updated.description = try diff3.assemble(arena.allocator(), chunks, resolutions.items); 339 return updated; 340 } 341 342 fn fieldListed(fields: []const u8, delimiter: u8, name: []const u8) bool { 343 var iter = std.mem.splitScalar(u8, fields, delimiter); 344 while (iter.next()) |field| { 345 if (std.mem.eql(u8, field, name)) return true; 346 } 347 return false; 348 } 349 350 fn readFromRepo( 351 comptime repo_kind: rp.RepoKind, 352 comptime repo_opts: rp.RepoOpts(repo_kind), 353 io: std.Io, 354 allocator: std.mem.Allocator, 355 arena: *std.heap.ArenaAllocator, 356 repo: *rp.Repo(repo_kind, repo_opts), 357 id: *const [evt.event_id_size]u8, 358 ) !?Record { 359 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; 360 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 361 const moment = (if (event_db_maybe) |*event_db| 362 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 363 else if (repo_kind == .git) 364 return null 365 else 366 evt.currentMoment(repo_opts, repo)) catch return null; 367 return readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, arena, id); 368 } 369 370 fn revisionKey(allocator: std.mem.Allocator, target_ref: []const u8, oid: []const u8) ![]u8 { 371 return std.mem.concat(allocator, u8, &.{ target_ref, "\x00", oid }); 372 } 373 374 fn statusSet( 375 comptime DB: type, 376 statuses: DB.SortedMap(.read_write), 377 status: Status, 378 ) !DB.SortedSet(.read_write) { 379 return DB.SortedSet(.read_write).init(try statuses.putCursor(@tagName(status))); 380 } 381 382 fn removeFromTagSets( 383 comptime DB: type, 384 tag_statuses: DB.SortedMap(.read_write), 385 tags: []const u8, 386 status: Status, 387 order_key: []const u8, 388 ) !void { 389 var tag_iter = tagIterator(tags); 390 while (tag_iter.next()) |tag| { 391 if (tag.len == 0 or tag.len > tag_max_len) continue; 392 var key_buffer: TagStatusKey = undefined; 393 const key = try tagStatusKey(&key_buffer, tag, status); 394 const set = try DB.SortedSet(.read_write).init(try tag_statuses.putCursor(key)); 395 _ = try set.remove(order_key); 396 if (0 == try set.count()) _ = try tag_statuses.remove(key); 397 } 398 } 399 400 pub fn readById( 401 comptime DB: type, 402 comptime hash_kind: hash.HashKind, 403 haxy_moment: DB.HashMap(.read_only), 404 arena: *std.heap.ArenaAllocator, 405 id: *const [evt.event_id_size]u8, 406 ) !?Record { 407 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 408 const records = try DB.HashMap(.read_only).init(records_cursor); 409 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, id)) orelse return null; 410 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 411 } 412