╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 var key_buffer: TagStatusKey = undefined; │ │193 const set = try DB.SortedSet(.read_write).init(try tag_statuses.putCursor(try tagStatusKey(&key_buffer, tag, record.event.status)));│ │194 try set.put(&order_key); │ │195 + } │ │196 + │ │197 + if (record.event.status != .merged) { │ │198 + if (record.event.revision) |revision| { │ │199 + const revisions = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, revision_to_id_set_key))); │ │200 + for ([_][]const u8{ revision.squash_oid, revision.source_oid }, 0..) |oid, i| { │ │201 + if (i != 0 and std.mem.eql(u8, revision.squash_oid, revision.source_oid)) continue; │ │202 + const key = try revisionKey(arena.allocator(), revision.target_ref, oid); │ │203 + const ids = try DB.CountedHashSet(.read_write).init(try revisions.putCursor(hash.hashInt(hash_kind, key))); │ │204 + try ids.put(record_key, .{ .bytes = event_id }); │ │205 + } │ │206 + } │ │207 } │ │208 } │ │209 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │215 outcome: *[std.meta.fields(Self).len]evt.FieldMerge, │ │216 ) void { │ │217 const status_index = std.meta.fieldIndex(Self, "status") orelse @compileError("Patch.status not found"); │ │164 - const patchrev_index = std.meta.fieldIndex(Self, "patchrev_id") orelse @compileError("Patch.patchrev_id not found");│ │165 - if (outcome[status_index] != .conflicted or outcome[patchrev_index] == .conflicted) return; │ │218 + const revision_index = std.meta.fieldIndex(Self, "revision") orelse @compileError("Patch.revision not found"); │ │219 + if (outcome[status_index] != .conflicted or outcome[revision_index] == .conflicted) return; │ │220 │ │167 - if (target.status == .merged and std.meta.eql(target.patchrev_id, merged.patchrev_id)) { │ │221 + if (target.status == .merged and evt.fieldEqual(?Revision, target.revision, merged.revision)) { │ │222 merged.status = .merged; │ │223 outcome[status_index] = .kept; │ │170 - } else if (parent.status == .merged and std.meta.eql(parent.patchrev_id, merged.patchrev_id)) { │ │224 + } else if (parent.status == .merged and evt.fieldEqual(?Revision, parent.revision, merged.revision)) { │ │225 merged.status = .merged; │ │226 outcome[status_index] = .parent; │ │227 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │228 } │ │229 │ │230 +fn revisionKey(allocator: std.mem.Allocator, target_ref: []const u8, oid: []const u8) ![]u8 {│ │231 + return std.mem.concat(allocator, u8, &.{ target_ref, "\x00", oid }); │ │232 +} │ │233 + │ │234 fn statusSet( │ │235 comptime DB: type, │ │236 statuses: DB.SortedMap(.read_write), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/event/PatchRev.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │27 pub const record_map_key = "event-id->patchrev"; │ │28 pub const id_set_key = "patchrev-id-set"; │ │29 │ │30 +// create the mergeable squash commit represented by this patch revision │ │31 +pub fn writeSquashCommit( │ │32 + comptime repo_kind: rp.RepoKind, │ │33 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │34 + state: rp.Repo(repo_kind, repo_opts).State(.read_write), │ │35 + io: std.Io, │ │36 + allocator: std.mem.Allocator, │ │37 + event: Self, │ │38 + head_tree_oid: *const [hash.hexLen(repo_opts.hash)]u8, │ │39 + author: []const u8, │ │40 + committer: []const u8, │ │41 + timestamp: u64, │ │42 +) ![hash.hexLen(repo_opts.hash)]u8 { │ │43 + var base_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │44 + if (event.base_oid.len != base_oid.len) return error.InvalidPatch; │ │45 + @memcpy(&base_oid, event.base_oid); │ │46 + try validateOid(repo_opts.hash, &base_oid); │ │47 + const parent_oids = [_][hash.hexLen(repo_opts.hash)]u8{base_oid}; │ │48 + return try obj.writeCommitWithoutRef(repo_kind, repo_opts, state, io, allocator, .{│ │49 + .author = author, │ │50 + .committer = committer, │ │51 + .message = event.message, │ │52 + .parent_oids = &parent_oids, │ │53 + .timestamp = timestamp, │ │54 + }, head_tree_oid); │ │55 +} │ │56 + │ │57 pub fn readTrees( │ │58 comptime repo_kind: rp.RepoKind, │ │59 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │116 │ │117 try validateOid(hash_kind, record.event.base_oid); │ │118 try validateOid(hash_kind, record.event.source_oid); │ │ 92 - if (!std.mem.startsWith(u8, record.event.target_ref, "refs/heads/") or │ │ 93 - !xit.ref.validateName(record.event.target_ref["refs/".len..])) return error.InvalidTarget;│ │119 + try validateTarget(record.event.target_ref); │ │120 try validateOid(hash_kind, record.event_oid); │ │121 try validateOid(hash_kind, record.base_tree_oid); │ │122 try validateOid(hash_kind, record.head_tree_oid); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │148 } │ │149 } │ │150 │ │125 -fn validateOid(comptime hash_kind: hash.HashKind, oid: []const u8) !void { │ │151 +pub fn validateOid(comptime hash_kind: hash.HashKind, oid: []const u8) !void { │ │152 var bytes: [hash.byteLen(hash_kind)]u8 = undefined; │ │153 _ = try std.fmt.hexToBytes(&bytes, oid); │ │154 if (!std.mem.eql(u8, oid, &std.fmt.bytesToHex(bytes, .lower))) return error.InvalidOid;│ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │155 } │ │156 │ │157 +pub fn validateTarget(target_ref: []const u8) !void { │ │158 + if (!std.mem.startsWith(u8, target_ref, "refs/heads/") or │ │159 + !xit.ref.validateName(target_ref["refs/".len..])) return error.InvalidTarget;│ │160 +} │ │161 + │ │162 pub fn readById( │ │163 comptime DB: type, │ │164 comptime hash_kind: hash.HashKind, │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │194 return null; │ │195 } │ │196 │ │166 -// derived commits referenced only by patch revision records│ │197 +// squash commits referenced only by patch revision records │ │198 pub fn gcRoots( │ │199 comptime DB: type, │ │200 comptime hash_kind: hash.HashKind, │ ╰─────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │146 else => |other| return other, │ │147 }; │ │148 │ │149 - _ = try moment.remove(hash.hashInt(repo_opts.hash, "haxy")); │ │150 - _ = try moment.remove(hash.hashInt(repo_opts.hash, "haxy-last-object-id")); │ │149 + _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.materialized_key)); │ │150 + _ = try moment.remove(hash.hashInt(repo_opts.hash, evt.last_object_id_key)); │ │151 │ │152 if (head_oid_maybe) |*head_oid| { │ │153 try rf.write(.xit, repo_opts, state, ctx.io, patch_path, .{ .oid = head_oid });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │232 if (!std.mem.eql(u8, patch.author_email orelse "", author.email)) return error.PatchDataUnavailable; │ │233 if (patch.event.status == .merged) return error.PatchAlreadyMerged; │ │234 } │ │235 - const published = target_patch != null; │ │235 + const posted = target_patch != null; │ │236 const title = if (target_patch) |patch| patch.event.title else fork_patch.event.title; │ │237 │ │238 const target_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = target_branch })) orelse return error.TargetNotFound;│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯