╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ search_commit.zig ────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
228 ) ?[hash.hexLen(hash_kind)]u8 { 229 var older: ?[hash.hexLen(hash_kind)]u8 = null; 230 var older_timestamp: u64 = 0; 231 var oldest: ?[hash.hexLen(hash_kind)]u8 = null; 232 var oldest_timestamp: u64 = std.math.maxInt(u64); 233 for (versions.keys(), versions.values()) |oid, version_timestamp| { 234 if (version_timestamp <= timestamp) { 235 if (default_tip) |tip| if (std.mem.eql(u8, &tip, &oid)) return tip; 236 if (older == null or version_timestamp > older_timestamp) { 237 older = oid; 238 older_timestamp = version_timestamp; 239 } 240 } 241 if (version_timestamp < oldest_timestamp) { 242 oldest = oid; 243 oldest_timestamp = version_timestamp; 244 } 245 } 246 return older orelse oldest; 247 } 248 249 // the commit a move took a ref to `tip` from, when it has a version to derive 250 // from. 251 fn movedFrom( 252 comptime repo_opts: rp.RepoOpts(.xit), 253 moves: []const Move, 254 tip: *const [hash.hexLen(repo_opts.hash)]u8, 255 versions: std.AutoArrayHashMapUnmanaged([hash.hexLen(repo_opts.hash)]u8, u64), 256 ) ?[hash.hexLen(repo_opts.hash)]u8 { 257 for (moves) |move| { 258 if (!std.mem.eql(u8, move.new_oid, tip)) continue; 259 if (move.old_oid.len != hash.hexLen(repo_opts.hash)) continue; 260 var oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; 261 @memcpy(&oid, move.old_oid); 262 if (versions.contains(oid)) return oid; 263 } 264 return null; 265 } 266 267 // turn a copy of `base_tip`'s version into `tip`'s by moving the postings of 268 // the commits the two don't share 269 fn derive( 270 comptime repo_opts: rp.RepoOpts(.xit), 271 state: rp.Repo(.xit, repo_opts).State(.read_only), 272 io: std.Io, 273 allocator: std.mem.Allocator, 274 version: rp.Repo(.xit, repo_opts).DB.SortedMap(.read_write), 275 base_tip: *const [hash.hexLen(repo_opts.hash)]u8, 276 tip: *const [hash.hexLen(repo_opts.hash)]u8, 277 progress_ctx_maybe: ?repo_opts.ProgressCtx, 278 ) !void { 279 const DB = rp.Repo(.xit, repo_opts).DB; 280 const Ancestry = mrg.Ancestry(.xit, repo_opts); 281 var ancestry = try Ancestry.init(state, io, allocator, base_tip, tip); 282 defer ancestry.deinit(); 283 284 progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .walking_commit, .estimated_total_items = 0 } }); 285 try ancestry.finish(progress_ctx_maybe); 286 progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .walking_commit }); 287 288 // the walk stops at the shared history, so it trusts timestamps: a 289 // commit dated later than a descendant can be passed from the base 290 // side before the tip turns out to reach it too, and its postings are 291 // then dropped. settling that would mean walking the whole history. 292 // a wrong addition only re-puts postings, so a fast-forward removes 293 // nothing, which the base tip picking up the tip's flag proves. 294 const base_node = ancestry.nodes.get(ancestry.tips[0]) orelse unreachable; 295 const fast_forward = base_node.flags & Ancestry.two != 0; 296 297 // a commit one tip alone reaches has postings to move. tag objects are 298 // peeled through, not walked. 299 var total: usize = 0; 300 var counted = ancestry.nodes.valueIterator(); 301 while (counted.next()) |node| { 302 if (node.tag_target != null) continue; 303 switch (node.flags & Ancestry.both) { 304 Ancestry.one => total += @intFromBool(!fast_forward), 305 Ancestry.two => total += 1, 306 else => {}, 307 } 308 } 309 310 var message: std.ArrayList(u8) = .empty; 311 defer message.deinit(allocator); 312 313 progress.report(repo_opts, io, progress_ctx_maybe, .{ .text = "Indexing commits" }); 314 progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = total } }); 315 var iter = ancestry.nodes.iterator(); 316 while (iter.next()) |entry| { 317 if (entry.value_ptr.tag_target != null) continue; 318 const flags = entry.value_ptr.flags & Ancestry.both; 319 if (flags == Ancestry.both or flags == 0 or (flags == Ancestry.one and fast_forward)) continue; 320 if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; 321 defer progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); 322 323 var object = try obj.Object(.xit, repo_opts).init(state, io, allocator, entry.key_ptr); 324 defer object.deinit(); 325 const key = try readPosting(repo_opts, allocator, &message, &object); 326 const removed = flags == Ancestry.one; 327 try srch.replace(DB, version, allocator, &key, if (removed) message.items else "", if (removed) "" else message.items); 328 } 329 progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .writing_patch }); 330 } 331 332 // index every commit `tip` reaches into an empty `version`, as the walk 333 // reaches it. 334 // TODO: the walk holds every visited oid in memory. if that proves too costly 335 // for large repositories, consider indexing first-parent history only, which 336 // needs no visited set and matches the commits list. 337 fn indexHistory( 338 comptime repo_opts: rp.RepoOpts(.xit), 339 state: rp.Repo(.xit, repo_opts).State(.read_only), 340 io: std.Io, 341 allocator: std.mem.Allocator, 342 version: rp.Repo(.xit, repo_opts).DB.SortedMap(.read_write), 343 tip: *const [hash.hexLen(repo_opts.hash)]u8, 344 progress_ctx_maybe: ?repo_opts.ProgressCtx, 345 ) !void { 346 const DB = rp.Repo(.xit, repo_opts).DB; 347 348 var message: std.ArrayList(u8) = .empty; 349 defer message.deinit(allocator); 350 351 // a whole history has no count until it is walked, so it reports the 352 // commits indexed so far 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 = 0 } }); 355 var iter = try obj.ObjectIterator(.xit, repo_opts).init(state, io, allocator, .{ .kind = .commit }); 356 defer iter.deinit(); 357 try iter.include(tip); 358 while (try iter.next(allocator)) |object| { 359 defer object.deinit(); 360 if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; 361 const key = try readPosting(repo_opts, allocator, &message, object); 362 try srch.replace(DB, version, allocator, &key, "", message.items); 363 progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); 364 } 365 progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .writing_patch }); 366 } 367 368 // replace `message` with the indexed prefix of `object`'s message and return 369 // the doc key it indexes under 370 fn readPosting( 371 comptime repo_opts: rp.RepoOpts(.xit), 372 allocator: std.mem.Allocator, 373 message: *std.ArrayList(u8), 374 object: *obj.Object(.xit, repo_opts), 375 ) ![docKeyLen(repo_opts.hash)]u8 { 376 message.clearRetainingCapacity(); 377 object.readMessage(allocator, message, .limited(srch.max_indexed_bytes)) catch |err| switch (err) { 378 // only the indexed prefix matters 379 error.StreamTooLong => {}, 380 else => |e| return e, 381 }; 382 return docKey(repo_opts.hash, object.content.commit.metadata.timestamp, &object.oid); 383 } 384