╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ find.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
218 var branches = try rf.RefIterator(.xit, repo_opts).init(read_state, io, allocator, .head, .beginning); 219 defer branches.deinit(); 220 while (try branches.next()) |ref| { 221 if (std.mem.eql(u8, ref.name, evt.events_ref.name)) continue; 222 const branch_hash = hash.hashInt(repo_opts.hash, ref.name); 223 const own = if (existing.fetchSwapRemove(branch_hash)) |kv| kv.value else null; 224 const oid = (try rf.readRecur(.xit, repo_opts, read_state, io, .{ .ref = .{ .kind = .head, .name = ref.name } })) orelse continue; 225 const tree = try rootTreeOid(repo_opts, read_state, io, allocator, &oid); 226 if (own) |entry| if (std.mem.eql(u8, &entry.tree, &tree)) continue; 227 try work.append(aa, .{ .branch_hash = branch_hash, .tree = tree, .own = own }); 228 } 229 230 // whatever is left belongs to a branch that no longer exists 231 for (existing.keys()) |branch_hash| try work.append(aa, .{ .branch_hash = branch_hash, .tree = null, .own = null }); 232 } 233 234 if (work.items.len == 0) return false; 235 236 { 237 const index_hash = hash.hashInt(repo_opts.hash, index_key); 238 var new_branch_base = base; 239 progress.report(repo_opts, io, progress_ctx_maybe, .{ .text = "Indexing files" }); 240 241 var key: std.ArrayList(u8) = .empty; 242 defer key.deinit(allocator); 243 244 for (work.items) |item| { 245 if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; 246 const index = try DB.HashMap(.read_write).init(try moment.putCursor(index_hash)); 247 const tree = item.tree orelse { 248 _ = try index.remove(item.branch_hash); 249 continue; 250 }; 251 // the entry this one starts from (null = index the whole tree) 252 const from = item.own orelse new_branch_base; 253 254 var tree_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; 255 _ = try std.fmt.hexToBytes(&tree_bytes, &tree); 256 // putKey only fills an empty key slot, and this one changes 257 // whenever the branch moves 258 var tree_cursor = try index.putKeyCursor(item.branch_hash); 259 try tree_cursor.write(.{ .bytes = &tree_bytes }); 260 var files_cursor = try index.putCursor(item.branch_hash); 261 if (from) |entry| try files_cursor.write(.{ .slot = entry.files }); 262 const files = try DB.SortedMap(.read_write).init(files_cursor); 263 264 if (from) |entry| { 265 var tree_diff = tr.TreeDiff(.xit, repo_opts).init(allocator); 266 defer tree_diff.deinit(); 267 try tree_diff.compare(state.readOnly(), io, &entry.tree, &tree, null); 268 progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = tree_diff.changes.count() } }); 269 for (tree_diff.changes.keys(), tree_diff.changes.values()) |path, change| { 270 defer progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); 271 try fileKey(allocator, &key, path); 272 const oid = if (change.new) |*tree_entry| indexedOid(repo_opts.hash, tree_entry) else null; 273 if (oid) |bytes| { 274 try files.put(key.items, .{ .bytes = bytes }); 275 } else { 276 _ = try files.remove(key.items); 277 } 278 } 279 } else { 280 // a whole tree has no count until it is walked, so it reports 281 // the files it has indexed so far 282 progress.report(repo_opts, io, progress_ctx_maybe, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = 0 } }); 283 var paths = std.heap.ArenaAllocator.init(allocator); 284 defer paths.deinit(); 285 try indexTree(repo_opts, state.readOnly(), io, allocator, paths.allocator(), files, &key, "", &tree, progress_ctx_maybe); 286 } 287 progress.report(repo_opts, io, progress_ctx_maybe, .{ .end = .writing_patch }); 288 289 // the first entry ever written becomes the base for the next 290 // new branch, so only one branch walks a whole tree. 291 if (new_branch_base == null) new_branch_base = .{ .tree = tree, .files = (try index.getSlot(item.branch_hash)) orelse unreachable }; 292 // a later entry copies an earlier one's slot, so everything 293 // written so far has to be shared rather than mutated 294 try state.core.db.freeze(); 295 } 296 } 297 return true; 298 } 299 300 // insert every file under `oid` (a tree), as tr.Tree.read walks one. 301 fn indexTree( 302 comptime repo_opts: rp.RepoOpts(.xit), 303 state: rp.Repo(.xit, repo_opts).State(.read_only), 304 io: std.Io, 305 allocator: std.mem.Allocator, 306 paths: std.mem.Allocator, 307 files: rp.Repo(.xit, repo_opts).DB.SortedMap(.read_write), 308 key: *std.ArrayList(u8), 309 prefix: []const u8, 310 oid: *const [hash.hexLen(repo_opts.hash)]u8, 311 progress_ctx_maybe: ?repo_opts.ProgressCtx, 312 ) !void { 313 var object = try obj.Object(.xit, repo_opts).init(state, io, allocator, oid); 314 defer object.deinit(); 315 const tree = switch (object.content) { 316 .tree => |tree| tree, 317 else => return, 318 }; 319 for (tree.entries.keys(), tree.entries.values()) |name, *tree_entry| { 320 if (progress.cancelled(repo_opts, progress_ctx_maybe)) return error.ClientGone; 321 const path = try fs.joinPath(paths, &.{ prefix, name }); 322 if (tree_entry.isTree()) { 323 try indexTree(repo_opts, state, io, allocator, paths, files, key, path, &std.fmt.bytesToHex(tree_entry.oid, .lower), progress_ctx_maybe); 324 } else if (indexedOid(repo_opts.hash, tree_entry)) |bytes| { 325 try fileKey(allocator, key, path); 326 try files.put(key.items, .{ .bytes = bytes }); 327 progress.report(repo_opts, io, progress_ctx_maybe, .{ .complete_one = .writing_patch }); 328 } 329 } 330 } 331 332 // the root tree an index entry covers, read from its key slot as hex. 333 fn readTree( 334 comptime repo_opts: rp.RepoOpts(.xit), 335 key_cursor: rp.Repo(.xit, repo_opts).DB.Cursor(.read_only), 336 ) ![hash.hexLen(repo_opts.hash)]u8 { 337 var tree: [hash.byteLen(repo_opts.hash)]u8 = undefined; 338 if ((try key_cursor.readBytes(&tree)).len != tree.len) return error.InvalidFileIndex; 339 return std.fmt.bytesToHex(tree, .lower); 340 } 341 342 // build a file's key: its lowercased basename, a separator, then its full path. 343 fn fileKey(allocator: std.mem.Allocator, key: *std.ArrayList(u8), path: []const u8) !void { 344 const slash = std.mem.lastIndexOfScalar(u8, path, '/'); 345 const base = if (slash) |i| path[i + 1 ..] else path; 346 key.clearRetainingCapacity(); 347 try key.ensureTotalCapacity(allocator, base.len + 1 + path.len); 348 for (base) |char| key.appendAssumeCapacity(std.ascii.toLower(char)); 349 key.appendAssumeCapacity(0); 350 key.appendSliceAssumeCapacity(path); 351 } 352 353 // the blob oid a tree entry is indexed under, or null for a submodule: its oid 354 // is a commit in another repo, not a file here. 355 fn indexedOid(comptime hash_kind: hash.HashKind, tree_entry: *const tr.TreeEntry(hash_kind)) ?[]const u8 { 356 if (tree_entry.mode.content.object_type == .gitlink) return null; 357 return &tree_entry.oid; 358 } 359 360 fn parseOid(comptime hash_kind: hash.HashKind, value: []const u8) ![hash.byteLen(hash_kind)]u8 { 361 const len = comptime hash.byteLen(hash_kind); 362 if (value.len != len) return error.InvalidFileIndex; 363 return value[0..len].*; 364 } 365