╭ changed ────────────╮ │src/search_commit.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │257 return true; │ │258 } │ │259 │ │289 -// the version a tip committed at `timestamp` derives from when no push names │ │260 +// the version a tip committed at `timestamp` derives from when no move names │ │261 // one: the default branch's unless it is newer than the tip, else the nearest│ │262 // older one, else the oldest. a guess by dates that only the cost rides on. │ │263 fn nearestVersion( │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │264 comptime hash_kind: hash.HashKind, │ │294 - versions: []const Commit(hash_kind), │ │265 + versions: std.AutoArrayHashMapUnmanaged([hash.hexLen(hash_kind)]u8, u64),│ │266 default_tip: ?[hash.hexLen(hash_kind)]u8, │ │267 timestamp: u64, │ │268 ) ?[hash.hexLen(hash_kind)]u8 { │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │298 - var older: ?Commit(hash_kind) = null; │ │299 - var oldest: ?Commit(hash_kind) = null; │ │300 - for (versions) |version| { │ │301 - if (version.timestamp <= timestamp) { │ │302 - if (default_tip) |oid| if (std.mem.eql(u8, &oid, &version.oid)) return oid; │ │303 - const nearer = if (older) |found| version.timestamp > found.timestamp else true;│ │304 - if (nearer) older = version; │ │269 + var older: ?[hash.hexLen(hash_kind)]u8 = null; │ │270 + var older_timestamp: u64 = 0; │ │271 + var oldest: ?[hash.hexLen(hash_kind)]u8 = null; │ │272 + var oldest_timestamp: u64 = std.math.maxInt(u64); │ │273 + for (versions.keys(), versions.values()) |oid, version_timestamp| { │ │274 + if (version_timestamp <= timestamp) { │ │275 + if (default_tip) |tip| if (std.mem.eql(u8, &tip, &oid)) return tip; │ │276 + if (older == null or version_timestamp > older_timestamp) { │ │277 + older = oid; │ │278 + older_timestamp = version_timestamp; │ │279 + } │ │280 } │ │306 - const earlier = if (oldest) |found| version.timestamp < found.timestamp else true; │ │307 - if (earlier) oldest = version; │ │281 + if (version_timestamp < oldest_timestamp) { │ │282 + oldest = oid; │ │283 + oldest_timestamp = version_timestamp; │ │284 + } │ │285 } │ │309 - return if (older orelse oldest) |version| version.oid else null; │ │286 + return older orelse oldest; │ │287 } │ │288 │ │312 -// the commit this push moved a ref to `tip` from, when it has a version to │ │313 -// derive from. │ │314 -fn pushedFrom( │ │289 +// the commit a move took a ref to `tip` from, when it has a version to derive │ │290 +// from. │ │291 +fn movedFrom( │ │292 comptime repo_opts: rp.RepoOpts(.xit), │ │316 - updates: ?[]const xit.net_server_receive_pack.AppliedRefUpdate, │ │293 + moves: []const Move, │ │294 tip: *const [hash.hexLen(repo_opts.hash)]u8, │ │318 - existing: std.AutoArrayHashMapUnmanaged([hash.hexLen(repo_opts.hash)]u8, void), │ │295 + versions: std.AutoArrayHashMapUnmanaged([hash.hexLen(repo_opts.hash)]u8, u64), │ │296 ) ?[hash.hexLen(repo_opts.hash)]u8 { │ │320 - for (updates orelse &.{}) |update| { │ │321 - if (!std.mem.eql(u8, update.new_oid, tip)) continue; │ │322 - if (update.old_oid.len != hash.hexLen(repo_opts.hash)) continue; │ │297 + for (moves) |move| { │ │298 + if (!std.mem.eql(u8, move.new_oid, tip)) continue; │ │299 + if (move.old_oid.len != hash.hexLen(repo_opts.hash)) continue; │ │300 var oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │324 - @memcpy(&oid, update.old_oid); │ │325 - if (existing.contains(oid)) return oid; │ │301 + @memcpy(&oid, move.old_oid); │ │302 + if (versions.contains(oid)) return oid; │ │303 } │ │304 return null; │ │305 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │306 │ │330 -// read `commit`'s message into `message` and return the doc key it indexes │ │331 -// under. │ │332 -fn readPosting( │ │307 +// turn a copy of `base_tip`'s version into `tip`'s by moving the postings of│ │308 +// the commits the two don't share │ │309 +fn derive( │ │310 comptime repo_opts: rp.RepoOpts(.xit), │ │311 state: rp.Repo(.xit, repo_opts).State(.read_only), │ │312 io: std.Io, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │313 allocator: std.mem.Allocator, │ │337 - message: *std.ArrayList(u8), │ │338 - commit: Commit(repo_opts.hash), │ │339 -) ![docKeyLen(repo_opts.hash)]u8 { │ │340 - message.clearRetainingCapacity(); │ │341 - var object = try obj.Object(.xit, repo_opts).init(state, io, allocator, &commit.oid); │ │342 - defer object.deinit(); │ │343 - object.readMessage(allocator, message, .limited(srch.max_indexed_bytes)) catch |err| switch (err) { │ │344 - // only the indexed prefix matters │ │345 - error.StreamTooLong => {}, │ │346 - else => |e| return e, │ │347 - }; │ │348 - return docKey(repo_opts.hash, commit.timestamp, &commit.oid); │ │314 + version: rp.Repo(.xit, repo_opts).DB.SortedMap(.read_write), │ │315 + base_tip: *const [hash.hexLen(repo_opts.hash)]u8, │ │316 + tip: *const [hash.hexLen(repo_opts.hash)]u8, │ │317 + progress_ctx_maybe: ?repo_opts.ProgressCtx, │ │318 +) !void { │ │319 + const DB = rp.Repo(.xit, repo_opts).DB; │ │320 + const Ancestry = mrg.Ancestry(.xit, repo_opts); │ │321 + var ancestry = try Ancestry.init(state, io, allocator, base_tip, tip); │ │322 + defer ancestry.deinit(); │ │323 + │ │324 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .walking_commit, .estimated_total_items = 0 } }); │ │325 + try ancestry.finish(progress_ctx_maybe); │ │326 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .walking_commit }); │ │327 + │ │328 + // the walk stops at the shared history, so it trusts timestamps: a │ │329 + // commit dated later than a descendant can be passed from the base │ │330 + // side before the tip turns out to reach it too, and its postings are │ │331 + // then dropped. settling that would mean walking the whole history. │ │332 + // a wrong addition only re-puts postings, so a fast-forward removes │ │333 + // nothing, which the base tip picking up the tip's flag proves. │ │334 + const base_node = ancestry.nodes.get(ancestry.tips[0]) orelse unreachable; │ │335 + const fast_forward = base_node.flags & Ancestry.two != 0; │ │336 + │ │337 + // a commit one tip alone reaches has postings to move. tag objects are │ │338 + // peeled through, not walked. │ │339 + var total: usize = 0; │ │340 + var counted = ancestry.nodes.valueIterator(); │ │341 + while (counted.next()) |node| { │ │342 + if (node.tag_target != null) continue; │ │343 + switch (node.flags & Ancestry.both) { │ │344 + Ancestry.one => total += @intFromBool(!fast_forward), │ │345 + Ancestry.two => total += 1, │ │346 + else => {}, │ │347 + } │ │348 + } │ │349 + │ │350 + var message: std.ArrayList(u8) = .empty; │ │351 + defer message.deinit(allocator); │ │352 + │ │353 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .text = "Indexing commits" }); │ │354 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = total } });│ │355 + var iter = ancestry.nodes.iterator(); │ │356 + while (iter.next()) |entry| { │ │357 + if (entry.value_ptr.tag_target != null) continue; │ │358 + const flags = entry.value_ptr.flags & Ancestry.both; │ │359 + if (flags == Ancestry.both or flags == 0 or (flags == Ancestry.one and fast_forward)) continue; │ │360 + if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; │ │361 + defer progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); │ │362 + │ │363 + var object = try obj.Object(.xit, repo_opts).init(state, io, allocator, entry.key_ptr); │ │364 + defer object.deinit(); │ │365 + const key = try readPosting(repo_opts, allocator, &message, &object); │ │366 + if (flags == Ancestry.one) { │ │367 + try srch.remove(DB, version, allocator, &key, message.items); │ │368 + } else { │ │369 + try srch.add(DB, version, allocator, &key, message.items); │ │370 + } │ │371 + } │ │372 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .writing_patch }); │ │373 } │ │374 │ │351 -// what `tip` reaches that `base_tip` doesn't, and the other way round, built │ │352 -// into `aa`. with no base the whole history is added. │ │353 -fn commitDiff( │ │375 +// index every commit `tip` reaches into an empty `version`, as the walk │ │376 +// reaches it │ │377 +fn indexHistory( │ │378 comptime repo_opts: rp.RepoOpts(.xit), │ │379 state: rp.Repo(.xit, repo_opts).State(.read_only), │ │380 io: std.Io, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │381 allocator: std.mem.Allocator, │ │358 - aa: std.mem.Allocator, │ │359 - base_tip: ?*const [hash.hexLen(repo_opts.hash)]u8, │ │382 + version: rp.Repo(.xit, repo_opts).DB.SortedMap(.read_write), │ │383 tip: *const [hash.hexLen(repo_opts.hash)]u8, │ │384 progress_ctx_maybe: ?repo_opts.ProgressCtx, │ │362 -) !struct { removed: []const Commit(repo_opts.hash), added: []const Commit(repo_opts.hash) } { │ │363 - var removed: std.ArrayList(Commit(repo_opts.hash)) = .empty; │ │364 - var added: std.ArrayList(Commit(repo_opts.hash)) = .empty; │ │365 - │ │366 - if (base_tip) |base| { │ │367 - const Ancestry = mrg.Ancestry(.xit, repo_opts); │ │368 - var ancestry = try Ancestry.init(state, io, allocator, base, tip); │ │369 - defer ancestry.deinit(); │ │370 - │ │371 - progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .walking_commit, .estimated_total_items = 0 } });│ │372 - try ancestry.finish(progress_ctx_maybe); │ │373 - progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .walking_commit }); │ │385 +) !void { │ │386 + const DB = rp.Repo(.xit, repo_opts).DB; │ │387 │ │375 - // the walk stops at the shared history, so it trusts timestamps: a │ │376 - // commit dated later than a descendant can be passed from the base │ │377 - // side before the tip turns out to reach it too, and its postings are │ │378 - // then dropped. settling that would mean walking the whole history. │ │379 - // a wrong addition only re-puts postings, so a fast-forward removes │ │380 - // nothing, which the base tip picking up the tip's flag proves. │ │381 - const base_node = ancestry.nodes.get(ancestry.tips[0]) orelse unreachable; │ │382 - const fast_forward = base_node.flags & Ancestry.two != 0; │ │388 + var message: std.ArrayList(u8) = .empty; │ │389 + defer message.deinit(allocator); │ │390 │ │384 - var iter = ancestry.nodes.iterator(); │ │385 - while (iter.next()) |entry| { │ │386 - // tag objects are peeled through, not walked │ │387 - if (entry.value_ptr.tag_target != null) continue; │ │388 - const commit = Commit(repo_opts.hash){ .oid = entry.key_ptr.*, .timestamp = entry.value_ptr.timestamp }; │ │389 - switch (entry.value_ptr.flags & Ancestry.both) { │ │390 - Ancestry.one => if (!fast_forward) try removed.append(aa, commit), │ │391 - Ancestry.two => try added.append(aa, commit), │ │392 - else => {}, │ │393 - } │ │394 - } │ │395 - } else { │ │396 - // a whole history has no count until it is walked, so the walk reports │ │397 - // the commits it has reached so far │ │398 - progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .walking_commit, .estimated_total_items = 0 } });│ │399 - var iter = try obj.ObjectIterator(.xit, repo_opts).init(state, io, allocator, .{ .kind = .commit }); │ │400 - defer iter.deinit(); │ │401 - try iter.include(tip); │ │402 - while (try iter.next(allocator)) |object| { │ │403 - defer object.deinit(); │ │404 - try added.append(aa, .{ .oid = object.oid, .timestamp = object.content.commit.metadata.timestamp }); │ │405 - progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .walking_commit }); │ │406 - } │ │407 - progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .walking_commit }); │ │391 + // a whole history has no count until it is walked, so it reports the │ │392 + // commits indexed so far │ │393 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .text = "Indexing commits" }); │ │394 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = 0 } }); │ │395 + var iter = try obj.ObjectIterator(.xit, repo_opts).init(state, io, allocator, .{ .kind = .commit }); │ │396 + defer iter.deinit(); │ │397 + try iter.include(tip); │ │398 + while (try iter.next(allocator)) |object| { │ │399 + defer object.deinit(); │ │400 + if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; │ │401 + const key = try readPosting(repo_opts, allocator, &message, object); │ │402 + try srch.add(DB, version, allocator, &key, message.items); │ │403 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); │ │404 } │ │405 + progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .writing_patch }); │ │406 +} │ │407 │ │410 - return .{ .removed = removed.items, .added = added.items }; │ │408 +// replace `message` with the indexed prefix of `object`'s message and return │ │409 +// the doc key it indexes under │ │410 +fn readPosting( │ │411 + comptime repo_opts: rp.RepoOpts(.xit), │ │412 + allocator: std.mem.Allocator, │ │413 + message: *std.ArrayList(u8), │ │414 + object: *obj.Object(.xit, repo_opts), │ │415 +) ![docKeyLen(repo_opts.hash)]u8 { │ │416 + message.clearRetainingCapacity(); │ │417 + object.readMessage(allocator, message, .limited(srch.max_indexed_bytes)) catch |err| switch (err) { │ │418 + // only the indexed prefix matters │ │419 + error.StreamTooLong => {}, │ │420 + else => |e| return e, │ │421 + }; │ │422 + return docKey(repo_opts.hash, object.content.commit.metadata.timestamp, &object.oid); │ │423 } │ │424 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯