╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const evt = @import("../event.zig"); │ │3 +const diff3 = @import("../diff3.zig");│ │4 const xit = @import("xit"); │ │5 +const rp = xit.repo; │ │6 const hash = xit.hash; │ │7 │ │8 title: []const u8, │ ╰─────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │54 for (@typeInfo(Status).@"enum".fields) |field| len = @max(len, field.name.len);│ │55 break :blk len; │ │56 }; │ │57 +}; │ │58 + │ │59 +pub const Resolve = struct { │ │60 + title: ?[]const u8 = null, │ │61 + tags: ?[]const u8 = null, │ │62 + hunks: []const []const u8 = &.{}, │ │63 + theirs: []const u8 = "", │ │64 }; │ │65 │ │66 +pub const Update = union(enum) { │ │67 + status: Status, │ │68 + fields: struct { title: []const u8, tags: []const u8, description: []const u8 }, │ │69 + resolve: Resolve, │ │70 +}; │ │71 + │ │72 pub const tag_max_len = 64; │ │73 pub const merge_policy: evt.MergePolicy = .field_conflicts; │ │74 pub const record_map_key = "event-id->patch"; │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │239 } else if (parent.status == .merged and evt.fieldEqual(?Revision, parent.revision, merged.revision)) { │ │240 merged.status = .merged; │ │241 outcome[status_index] = .parent; │ │242 + } │ │243 +} │ │244 + │ │245 +pub fn update( │ │246 + comptime repo_kind: rp.RepoKind, │ │247 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │248 + io: std.Io, │ │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 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯