╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#dba9c884daa5d5819ee82592aafa10043508531c",│ │ 7 - .hash = "xit-0.0.0-Ub3nDAGQUwGbntCLOsOx-h4tZw7UElsOWw1J9X_eHIY_", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#cf3dda9285596889171ff8c3dff33549b3eb30e5",│ │ 7 + .hash = "xit-0.0.0-Ub3nDFF1UwHbs36cNTAD6KVt4W6aHvt9Q3098WrOtKEB", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/event/PatchRev.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │79 } │ │80 │ │81 // only show the depth difference if a short first-parent walk reaches the base│ │82 +// both oids must identify commits, not annotated tags │ │83 pub fn commitCount( │ │84 comptime repo_kind: rp.RepoKind, │ │85 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 .xit => { │ │ 95 const oid_len = comptime hash.hexLen(repo_opts.hash); │ │ 96 if (base_oid.len != oid_len or source_oid.len != oid_len) return error.InvalidOid; │ │ 96 - const base_count = try state.commitCount(io, allocator, .{ .oid = base_oid[0..oid_len] }); │ │ 97 - const source_count = try state.commitCount(io, allocator, .{ .oid = source_oid[0..oid_len] }); │ │ 98 - const count = std.math.sub(u64, source_count, base_count) catch return null; │ │ 97 + const base_stats = (try xit.patch.readCommitStats(repo_opts, state.extra.moment, base_oid[0..oid_len])) orelse return null; │ │ 98 + const source_stats = (try xit.patch.readCommitStats(repo_opts, state.extra.moment, source_oid[0..oid_len])) orelse return null;│ │ 99 + const count = std.math.sub(u64, source_stats.first_parent_depth, base_stats.first_parent_depth) catch return null; │ │100 if (count > commit_count_limit) return null; │ │101 │ │102 // depth subtraction is only valid if the base is on the source's first-parent chain │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │13 const repo_opts: rp.RepoOpts(.xit) = .{ .is_test = true }; │ │14 const Repo = rp.Repo(.xit, repo_opts); │ │15 const author = evt.CommitAuthor{ .name = "alice", .email = "alice@example.test" }; │ │16 - │ │17 -test "patch commit counts require a short first-parent path" { │ │18 - const io = std.testing.io; │ │19 - const allocator = std.testing.allocator; │ │20 - var temp = std.testing.tmpDir(.{}); │ │21 - defer temp.cleanup(); │ │22 - const path = try temp.dir.realPathFileAlloc(io, ".", allocator); │ │23 - defer allocator.free(path); │ │24 - var repo = try Repo.init(io, allocator, .{ .path = path }); │ │25 - defer repo.deinit(io, allocator); │ │26 - const branch = rf.Ref{ .kind = .head, .name = "patch" }; │ │27 - │ │28 - // a second-parent base must not produce a misleading depth difference │ │29 - const root = try repo.commitAtRef(io, allocator, .{ .message = "root" }, null, branch); │ │30 - const base = try repo.commitAtRef(io, allocator, .{ .message = "base" }, null, branch); │ │31 - const source = try repo.commitAtRef(io, allocator, .{ .message = "source", .parent_oids = &.{root} }, null, branch); │ │32 - var tip = try repo.commitAtRef(io, allocator, .{ .message = "merge", .parent_oids = &.{ source, base } }, null, branch); │ │33 - var moment = try repo.core.latestMoment(); │ │34 - const state = Repo.State(.read_only){ .core = &repo.core, .extra = .{ .moment = &moment } }; │ │35 - try std.testing.expectEqual(null, try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &base, &tip)); │ │36 - try std.testing.expectEqual(@as(?u64, 2), try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &root, &tip)); │ │37 - try std.testing.expectEqual(null, try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &tip, &root)); │ │38 - try std.testing.expectEqual(null, try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &base, &source)); │ │39 - │ │40 - // long ranges omit the count instead of walking unbounded history │ │41 - for (2..256) |_| { │ │42 - tip = try repo.commitAtRef(io, allocator, .{ .message = "next" }, null, branch); │ │43 - } │ │44 - moment = try repo.core.latestMoment(); │ │45 - try std.testing.expectEqual(@as(?u64, 256), try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &root, &tip));│ │46 - tip = try repo.commitAtRef(io, allocator, .{ .message = "next" }, null, branch); │ │47 - moment = try repo.core.latestMoment(); │ │48 - try std.testing.expectEqual(null, try evt.PatchRev.commitCount(.xit, repo_opts, state, io, allocator, &root, &tip)); │ │49 -} │ │16 │ │17 test "branch patch lifecycle" { │ │18 try testBranchPatch(.git, .sha1); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 const first = (try evt.readFromRepo(evt.Patch, kind, opts, io, allocator, &arena, &repo, &id)) orelse return error.NotFound; │ │60 const first_revision = first.event.revision orelse return error.NotFound; │ │61 try std.testing.expectEqualStrings(&base, first_revision.source_oid); │ │96 - try std.testing.expectEqual(@as(?u64, if (kind == .xit) 0 else null), first_revision.commit_count); │ │62 try std.testing.expectEqualStrings(&base, &(try repo.readRef(io, .{ .kind = .head, .name = "master" }) orelse return error.NotFound));│ │63 │ │64 // an unchanged refresh does not add events │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 81 const updated = (try evt.readFromRepo(evt.Patch, kind, opts, io, allocator, &arena, &repo, &id)) orelse return error.NotFound;│ │ 82 const revision = updated.event.revision orelse return error.NotFound; │ │ 83 try std.testing.expectEqualStrings(&next, revision.source_oid); │ │119 - try std.testing.expectEqual(@as(?u64, if (kind == .xit) 1 else null), revision.commit_count); │ │ 84 try std.testing.expect(!std.mem.eql(u8, &first_revision.id, &revision.id)); │ │ 85 try std.testing.expectEqualStrings(patch.title, updated.event.title); │ │ 86 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/test/ui.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │285 } │ │286 const first = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, .object, &tip, content, &base, "", ""); │ │287 try std.testing.expectEqual(20, first.commits.len); │ │288 - try std.testing.expectEqual(@as(?u64, if (kind == .xit) 21 else null), first.commit_count); │ │288 const last = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, .object, first.next_start orelse return error.MissingNext, content, &base, "", "");│ │289 try std.testing.expectEqual(1, last.commits.len); │ │290 try std.testing.expectEqual(null, last.next_start); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │293 const full = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, .object, &previous, content, &base, "", "");│ │294 try std.testing.expectEqual(20, full.commits.len); │ │295 try std.testing.expectEqual(null, full.next_start); │ │297 - _ = try repo.addTag(io, allocator, .{ .name = "tip", .message = "annotated" }); │ │296 + const tag_oid = try repo.addTag(io, allocator, .{ .name = "tip", .message = "annotated" }); │ │297 const sources = [_]struct { ref: ui.RoutablePage.RefOrOid, value: []const u8 }{ │ │298 .{ .ref = .object, .value = &tip }, │ │299 + .{ .ref = .object, .value = &tag_oid }, │ │300 .{ .ref = .branch, .value = branch.name }, │ │301 .{ .ref = .tag, .value = "tip" }, │ │302 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │303 for (sources) |source| { │ │304 const empty = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, source.ref, source.value, content, &tip, "", ""); │ │305 try std.testing.expectEqual(0, empty.commits.len); │ │306 - try std.testing.expectEqual(@as(?u64, if (kind == .xit) 0 else null), empty.commit_count); │ │306 + const paged = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, source.ref, source.value, content, &base, "", first.next_start orelse return error.MissingNext);│ │307 + try std.testing.expectEqual(1, paged.commits.len); │ │308 + try std.testing.expectEqualStrings(last.commits[0].oid, paged.commits[0].oid); │ │309 + try std.testing.expectEqual(source.ref, paged.ref_or_oid); │ │310 + try std.testing.expectEqualStrings(source.value, paged.ref_or_oid_value); │ │311 } │ │312 │ │313 // an off-chain stopping point must not hide its parent on this chain │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │315 const off_chain = try Commits.init(kind, opts, &arena, &repo, io, allocator, null, location, .object, last.commits[0].oid, content, &other, "", "");│ │316 try std.testing.expectEqual(2, off_chain.commits.len); │ │317 try std.testing.expectEqualStrings(&base, off_chain.commits[1].oid); │ │314 - try std.testing.expectEqual(null, off_chain.commit_count); │ │318 } │ │319 │ │320 test "sync creates missing event branches and preserves head" { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯