╭ changed ────────────╮ │src/search_commit.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │91 │ │92 var work: std.ArrayList(Work) = .empty; │ │93 var gone: std.ArrayList(Oid) = .empty; │ │94 - var default_base: ?Oid = null; │ │95 - var newest: ?Tip = null; │ │94 + var default_tip: ?Oid = null; │ │95 + // the versions a new tip can derive from │ │96 + var versions: std.ArrayList(Tip) = .empty; │ │97 │ │98 // held across the reads and the writes, so a concurrent refresh can't│ │99 // change the entries these decisions were made from. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 if (entry.value_ptr.base == null) entry.value_ptr.base = pushedFrom(repo_opts, updates, &tip, existing);│ │159 } │ │160 │ │160 - // the base a brand-new tip falls back to, preferring the default │ │161 - // branch so most of its map is shared rather than rewritten. │ │161 var head_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │162 if (repo.head(io, &head_buffer)) |head| switch (head) { │ │164 - .ref => |ref| if (try repo.readRef(io, .{ .kind = .head, .name = ref.name })) |oid| { │ │165 - if (existing.contains(oid)) default_base = oid; │ │166 - }, │ │163 + .ref => |ref| default_tip = try repo.readRef(io, .{ .kind = .head, .name = ref.name }), │ │164 .oid => {}, │ │165 } else |_| {} │ │166 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │170 - // ... else the newest version there is, which only matters when a │ │171 - // version has to be built without the default branch's. an entry whose │ │172 - // commit is gone can't be walked from, so it isn't a candidate. │ │173 - if (missing.count() > 0 and default_base == null) for (existing.keys()) |oid| { │ │167 + // an entry whose commit is gone can't be walked from, so it isn't a │ │168 + // candidate. │ │169 + const unbased = for (missing.values()) |item| { │ │170 + if (item.base == null) break true; │ │171 + } else false; │ │172 + if (unbased) for (existing.keys()) |oid| { │ │173 var commit = obj.Object(.xit, repo_opts).initCommit(state, io, allocator, &oid) catch continue; │ │174 defer commit.deinit(); │ │176 - const timestamp = commit.content.commit.metadata.timestamp; │ │177 - if (newest) |tip| if (timestamp <= tip.timestamp) continue; │ │178 - newest = .{ .oid = oid, .timestamp = timestamp }; │ │175 + try versions.append(aa, .{ .oid = oid, .timestamp = commit.content.commit.metadata.timestamp });│ │176 }; │ │177 │ │178 try work.appendSlice(aa, missing.values()); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │196 allocator: std.mem.Allocator, │ │197 work: []const Work, │ │198 gone: []const Oid, │ │202 - default_base: ?Oid, │ │203 - newest: ?Tip, │ │199 + aa: std.mem.Allocator, │ │200 + default_tip: ?Oid, │ │201 + versions: *std.ArrayList(Tip), │ │202 │ │203 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │204 var moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │205 const state = Repo.State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } };│ │206 const index_hash = hash.hashInt(repo_opts.hash, index_key); │ │209 - var newest_built = ctx.newest; │ │207 │ │208 var message: std.ArrayList(u8) = .empty; │ │209 defer message.deinit(ctx.allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │210 │ │211 for (ctx.work) |item| { │ │212 const index = try DB.HashMap(.read_write).init(try moment.putCursor(index_hash)); │ │216 - const fallback: ?Oid = if (newest_built) |tip| tip.oid else null; │ │217 - const base = item.base orelse ctx.default_base orelse fallback; │ │213 + const base = item.base orelse nearestVersion(repo_opts.hash, ctx.versions.items, ctx.default_tip, item.timestamp);│ │214 │ │215 var oid_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │216 _ = try std.fmt.hexToBytes(&oid_bytes, &item.oid); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │235 try srch.add(DB, version, ctx.allocator, &key, message.items); │ │236 } │ │237 │ │242 - const supersedes = if (newest_built) |tip| item.timestamp > tip.timestamp else true;│ │243 - if (supersedes) newest_built = .{ .oid = item.oid, .timestamp = item.timestamp }; │ │238 + try ctx.versions.append(ctx.aa, .{ .oid = item.oid, .timestamp = item.timestamp }); │ │239 // the next version copies this one's slot, so everything │ │240 // written so far has to be shared rather than mutated │ │241 try ctx.core.db.freeze(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 .allocator = allocator, │ │255 .work = work.items, │ │256 .gone = gone.items, │ │262 - .default_base = default_base, │ │263 - .newest = newest, │ │257 + .aa = aa, │ │258 + .default_tip = default_tip, │ │259 + .versions = &versions, │ │260 }); │ │261 +} │ │262 + │ │263 +// the version a tip committed at `timestamp` derives from when no push names │ │264 +// one: the default branch's unless it is newer than the tip, else the nearest │ │265 +// older one, else the oldest. a guess by dates that only the cost rides on. │ │266 +fn nearestVersion( │ │267 + comptime hash_kind: hash.HashKind, │ │268 + versions: []const Commit(hash_kind), │ │269 + default_tip: ?[hash.hexLen(hash_kind)]u8, │ │270 + timestamp: u64, │ │271 +) ?[hash.hexLen(hash_kind)]u8 { │ │272 + var older: ?Commit(hash_kind) = null; │ │273 + var oldest: ?Commit(hash_kind) = null; │ │274 + for (versions) |version| { │ │275 + if (version.timestamp <= timestamp) { │ │276 + if (default_tip) |oid| if (std.mem.eql(u8, &oid, &version.oid)) return oid; │ │277 + const nearer = if (older) |found| version.timestamp > found.timestamp else true;│ │278 + if (nearer) older = version; │ │279 + } │ │280 + const earlier = if (oldest) |found| version.timestamp < found.timestamp else true; │ │281 + if (earlier) oldest = version; │ │282 + } │ │283 + return if (older orelse oldest) |version| version.oid else null; │ │284 } │ │285 │ │286 // the commit this push moved a ref to `tip` from, when it has a version to │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯