Skip to documentation
SLOP

tiny.sql.sync

Reference tiny.sql sync

Defined in tiny.sql.

API (76)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.sqlsync
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersversionsamesync.HistoryRelationupToDate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsrepositoryRepositorysyncexportRefsprivate sourcelib.sql.src.syncrefsForNamessyncexportRefNames
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallssyncexportAllsyncexportRefNamesprivate sourcelib.accy.src.preparation.kernelization.loweri...finishprivate sourcelib.sql.src.syncbuilderForRefssyncexportRefs
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsrepositoryRepositorysyncheadHex
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsrepositoryRepositorytest sourcelib.sql.src.synctest: sync history planning reports r...syncmissingHistoryCommitCountsynchistoryRelation
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsrepositoryRepositorysyncfetchPacksyncimportPacksyncpullFastForwardsyncpushFastForwardTotest sourcelib.sql.src.synctest: sync pack import rejects a conf...CommitinitversionsamesyncimportObjects
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callssynchistoryRelationsyncmissingHistoryCommitCount
Static calls · unresolved targets: 2 · external targets: 6.
Called byCallsNo direct callsrepositoryRepositorytest sourcelib.sql.src.synctest: sync history planning reports r...syncmissingPackCounts
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsrepositoryRepositorytest sourcelib.sql.src.synctest: sync history planning reports r...syncpackRefTarget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsrepositoryRepositorytest sourcelib.sql.src.synctest: sync history planning reports r...syncexportMissingRefNamessyncplanHistoryAdopt
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsrepositoryRepositorytest sourcelib.sql.src.synctest: sync history planning reports r...syncexportMissingRefNamessyncplanHistoryTransfer
Static calls · unresolved targets: 0 · external targets: 3.

Source: lib/sql/src/root.zig:44

zig
pub const sync = @import("sync.zig");

Source: lib/sql/src/sync.zig

zig
const std = @import("std");const branch = @import("branch.zig");const connection_mod = @import("connection.zig");const file = @import("file.zig");const history_mod = @import("history/root.zig");const version = @import("version.zig");const wal = @import("wal.zig");const Allocator = std.mem.Allocator;const testing_io = std.Options.debug_io;const pack_magic: u32 = 0x7473_706b;const pack_format_version: u32 = 1;const remote_config_magic: u32 = 0x7473_726d;const remote_config_format_version: u32 = 2;const branch_upstream_config_magic: u32 = 0x7473_7570;const branch_upstream_config_format_version: u32 = 1;const advertisement_magic: u32 = 0x7473_6164;const advertisement_format_version: u32 = 1;const fetch_request_magic: u32 = 0x7473_6672;const fetch_request_format_version: u32 = 1;pub const Error = Allocator.Error || history_mod.Error || branch.Error || error{    InvalidPack,    InvalidRemoteConfig,    InvalidBranchUpstreamConfig,    InvalidAdvertisement,    InvalidFetchRequest,    InvalidRemoteName,    RemoteExists,    RemoteNotFound,    BranchUpstreamExists,    RemoteBusy,};pub const Stats = struct {    refs: usize = 0,    commits: usize = 0,    records: usize = 0,};pub const HeadHex = [version.hash_bytes * 2]u8;pub const FetchOptions = struct {    remote: []const u8 = "origin",    prune: bool = false,};pub const HistoryDirection = enum { push, pull };pub const HistoryRelation = struct {    local_head: version.Hash,    remote_head: ?version.Hash,    ahead: usize,    behind: usize,    pub fn upToDate(self: HistoryRelation) bool {        const remote_head = self.remote_head orelse return false;        return version.same(self.local_head, remote_head);    }    pub fn diverged(self: HistoryRelation) bool {        return self.ahead != 0 and self.behind != 0;    }};pub const HistoryTransferPlan = struct {    commits: usize = 0,    records: usize = 0,};pub const HistoryPlanError = Error || error{    HistoryDiverged,    HistoryRemoteAhead,};pub const FileRemote = struct {    io: std.Io = std.Options.debug_io,    dir: std.Io.Dir,    path: []const u8 = "tiny.sql.history",    lock_path: ?[]const u8 = null,};pub const RemoteConfigOptions = struct {    io: std.Io = std.Options.debug_io,    path: []const u8 = "tiny.sql.remotes",    max_bytes: usize = 64 * 1024,};pub const BranchUpstreamOptions = struct {    io: std.Io = std.Options.debug_io,    path: []const u8 = "tiny.sql.upstreams",    max_bytes: usize = 64 * 1024,};pub const RemoteConfigEntry = struct {    name: []u8,    history_path: []u8,    lock_path: ?[]u8 = null,    default_branch: ?[]u8 = null,    pub fn init(allocator: Allocator, name: []const u8, history_path: []const u8, lock_path: ?[]const u8) Error!RemoteConfigEntry {        return try initWithDefaultBranch(allocator, name, history_path, lock_path, null);    }    pub fn initWithDefaultBranch(allocator: Allocator, name: []const u8, history_path: []const u8, lock_path: ?[]const u8, default_branch: ?[]const u8) Error!RemoteConfigEntry {        if (!validRemoteName(name)) return error.InvalidRemoteName;        if (default_branch) |branch_name| {            if (branch_name.len == 0) return error.InvalidRemoteConfig;        }        const owned_name = try allocator.dupe(u8, name);        errdefer allocator.free(owned_name);        const owned_history_path = try allocator.dupe(u8, history_path);        errdefer allocator.free(owned_history_path);        const owned_lock_path = if (lock_path) |path| try allocator.dupe(u8, path) else null;        errdefer if (owned_lock_path) |path| allocator.free(path);        const owned_default_branch = if (default_branch) |branch_name| try allocator.dupe(u8, branch_name) else null;        errdefer if (owned_default_branch) |branch_name| allocator.free(branch_name);        return .{            .name = owned_name,            .history_path = owned_history_path,            .lock_path = owned_lock_path,            .default_branch = owned_default_branch,        };    }    pub fn deinit(self: *RemoteConfigEntry, allocator: Allocator) void {        allocator.free(self.name);        allocator.free(self.history_path);        if (self.lock_path) |path| allocator.free(path);        if (self.default_branch) |branch_name| allocator.free(branch_name);        self.* = undefined;    }    pub fn fileRemote(self: *const RemoteConfigEntry, io: std.Io, dir: std.Io.Dir) FileRemote {        return .{            .io = io,            .dir = dir,            .path = self.history_path,            .lock_path = self.lock_path,        };    }};pub const RemoteConfig = struct {    allocator: Allocator,    remotes: []RemoteConfigEntry,    pub fn init(allocator: Allocator, remotes: []const RemoteConfigEntry) Error!RemoteConfig {        try validateRemoteEntries(remotes);        const owned = try allocator.alloc(RemoteConfigEntry, remotes.len);        var count: usize = 0;        errdefer {            for (owned[0..count]) |*remote| remote.deinit(allocator);            if (owned.len != 0) allocator.free(owned);        }        for (remotes, owned) |remote, *target| {            target.* = try RemoteConfigEntry.initWithDefaultBranch(allocator, remote.name, remote.history_path, remote.lock_path, remote.default_branch);            count += 1;        }        return .{            .allocator = allocator,            .remotes = owned,        };    }    pub fn deinit(self: *RemoteConfig) void {        for (self.remotes) |*remote| remote.deinit(self.allocator);        if (self.remotes.len != 0) self.allocator.free(self.remotes);        self.* = undefined;    }    pub fn find(self: *const RemoteConfig, name: []const u8) ?*const RemoteConfigEntry {        for (self.remotes) |*remote| {            if (std.mem.eql(u8, remote.name, name)) return remote;        }        return null;    }    pub fn fileRemote(self: *const RemoteConfig, io: std.Io, dir: std.Io.Dir, name: []const u8) Error!FileRemote {        const remote = self.find(name) orelse return error.RemoteNotFound;        return remote.fileRemote(io, dir);    }};pub const BranchUpstreamEntry = struct {    branch: []u8,    remote: []u8,    remote_branch: []u8,    pub fn init(allocator: Allocator, branch_name: []const u8, remote_name: []const u8, remote_branch_name: []const u8) Error!BranchUpstreamEntry {        if (branch_name.len == 0 or remote_branch_name.len == 0) return error.InvalidBranchUpstreamConfig;        if (!validRemoteName(remote_name)) return error.InvalidRemoteName;        const owned_branch = try allocator.dupe(u8, branch_name);        errdefer allocator.free(owned_branch);        const owned_remote = try allocator.dupe(u8, remote_name);        errdefer allocator.free(owned_remote);        const owned_remote_branch = try allocator.dupe(u8, remote_branch_name);        errdefer allocator.free(owned_remote_branch);        return .{            .branch = owned_branch,            .remote = owned_remote,            .remote_branch = owned_remote_branch,        };    }    pub fn deinit(self: *BranchUpstreamEntry, allocator: Allocator) void {        allocator.free(self.branch);        allocator.free(self.remote);        allocator.free(self.remote_branch);        self.* = undefined;    }};pub const BranchUpstreamConfig = struct {    allocator: Allocator,    upstreams: []BranchUpstreamEntry,    pub fn init(allocator: Allocator, upstreams: []const BranchUpstreamEntry) Error!BranchUpstreamConfig {        try validateBranchUpstreamEntries(upstreams);        const owned = try allocator.alloc(BranchUpstreamEntry, upstreams.len);        var count: usize = 0;        errdefer {            for (owned[0..count]) |*upstream| upstream.deinit(allocator);            if (owned.len != 0) allocator.free(owned);        }        for (upstreams, owned) |upstream, *target| {            target.* = try BranchUpstreamEntry.init(allocator, upstream.branch, upstream.remote, upstream.remote_branch);            count += 1;        }        return .{            .allocator = allocator,            .upstreams = owned,        };    }    pub fn deinit(self: *BranchUpstreamConfig) void {        for (self.upstreams) |*upstream| upstream.deinit(self.allocator);        if (self.upstreams.len != 0) self.allocator.free(self.upstreams);        self.* = undefined;    }    pub fn find(self: *const BranchUpstreamConfig, branch_name: []const u8) ?*const BranchUpstreamEntry {        for (self.upstreams) |*upstream| {            if (std.mem.eql(u8, upstream.branch, branch_name)) return upstream;        }        return null;    }};pub const FileRemoteLock = struct {    io: std.Io,    file: std.Io.File,    pub fn release(self: *FileRemoteLock) void {        self.file.close(self.io);        self.* = undefined;    }};pub const Advertisement = struct {    allocator: Allocator,    refs: []RefObject,    pub fn deinit(self: *Advertisement) void {        for (self.refs) |*ref_value| ref_value.deinit(self.allocator);        if (self.refs.len != 0) self.allocator.free(self.refs);        self.* = undefined;    }    pub fn ref(self: *const Advertisement, name: []const u8) ?version.Ref {        for (self.refs) |ref_value| {            if (std.mem.eql(u8, ref_value.name, name)) {                return .{                    .name = ref_value.name,                    .target = ref_value.target,                };            }        }        return null;    }};pub const FetchRequest = struct {    allocator: Allocator,    wants: [][]u8,    haves: []version.Hash,    pub fn init(allocator: Allocator, wants: []const []const u8, haves: []const version.Hash) Allocator.Error!FetchRequest {        const owned_wants = try allocator.alloc([]u8, wants.len);        var want_count: usize = 0;        errdefer {            for (owned_wants[0..want_count]) |want| allocator.free(want);            if (owned_wants.len != 0) allocator.free(owned_wants);        }        for (wants, owned_wants) |want, *target| {            target.* = try allocator.dupe(u8, want);            want_count += 1;        }        const owned_haves = try allocator.dupe(version.Hash, haves);        errdefer if (owned_haves.len != 0) allocator.free(owned_haves);        return .{            .allocator = allocator,            .wants = owned_wants,            .haves = owned_haves,        };    }    pub fn deinit(self: *FetchRequest) void {        for (self.wants) |want| self.allocator.free(want);        if (self.wants.len != 0) self.allocator.free(self.wants);        if (self.haves.len != 0) self.allocator.free(self.haves);        self.* = undefined;    }};pub const CommitObject = struct {    hash: version.Hash,    root: version.Hash,    parents: []version.Hash,    fn deinit(self: *CommitObject, allocator: Allocator) void {        if (self.parents.len != 0) allocator.free(self.parents);        self.* = undefined;    }};pub const RefObject = struct {    name: []u8,    target: version.Hash,    fn deinit(self: *RefObject, allocator: Allocator) void {        allocator.free(self.name);        self.* = undefined;    }};pub const PackFrame = struct {    kind: history_mod.PackRecordKind,    payload: []u8,    fn deinit(self: *PackFrame, allocator: Allocator) void {        allocator.free(self.payload);        self.* = undefined;    }};pub const Pack = struct {    allocator: Allocator,    refs: []RefObject,    commits: []CommitObject,    frames: []PackFrame,    pub fn deinit(self: *Pack) void {        for (self.refs) |*ref_value| ref_value.deinit(self.allocator);        for (self.commits) |*commit| commit.deinit(self.allocator);        for (self.frames) |*frame| frame.deinit(self.allocator);        if (self.refs.len != 0) self.allocator.free(self.refs);        if (self.commits.len != 0) self.allocator.free(self.commits);        if (self.frames.len != 0) self.allocator.free(self.frames);        self.* = undefined;    }    pub fn stats(self: *const Pack) Stats {        return .{            .refs = self.refs.len,            .commits = self.commits.len,            .records = self.frames.len,        };    }};pub fn encodePack(allocator: Allocator, pack: *const Pack) Error![]u8 {    var bytes: std.ArrayList(u8) = .empty;    errdefer bytes.deinit(allocator);    try appendU32(allocator, &bytes, pack_magic);    try appendU32(allocator, &bytes, pack_format_version);    try appendCount(allocator, &bytes, pack.refs.len);    try appendCount(allocator, &bytes, pack.commits.len);    try appendCount(allocator, &bytes, pack.frames.len);    for (pack.refs) |ref_value| {        try appendBytes(allocator, &bytes, ref_value.name);        try appendHash(allocator, &bytes, ref_value.target);    }    for (pack.commits) |commit| {        try appendHash(allocator, &bytes, commit.hash);        try appendHash(allocator, &bytes, commit.root);        try appendCount(allocator, &bytes, commit.parents.len);        for (commit.parents) |parent| try appendHash(allocator, &bytes, parent);    }    for (pack.frames) |frame| {        try appendU32(allocator, &bytes, @backingInt(frame.kind));        try appendBytes(allocator, &bytes, frame.payload);    }    return try bytes.toOwnedSlice(allocator);}const PackSections = struct {    refs: []RefObject,    commits: []CommitObject,    frame_count: usize,    fn deinit(self: *PackSections, allocator: Allocator) void {        for (self.refs) |*ref_value| ref_value.deinit(allocator);        for (self.commits) |*commit| commit.deinit(allocator);        if (self.refs.len != 0) allocator.free(self.refs);        if (self.commits.len != 0) allocator.free(self.commits);        self.* = undefined;    }};fn decodePackSections(allocator: Allocator, reader: *ByteReader) Error!PackSections {    if (try reader.readU32() != pack_magic) return error.InvalidPack;    if (try reader.readU32() != pack_format_version) return error.InvalidPack;    const ref_count = try reader.readCount();    const commit_count = try reader.readCount();    const frame_count = try reader.readCount();    const refs = try allocator.alloc(RefObject, ref_count);    var refs_read: usize = 0;    errdefer {        for (refs[0..refs_read]) |*ref_value| ref_value.deinit(allocator);        if (refs.len != 0) allocator.free(refs);    }    for (refs) |*ref_value| {        const name = try reader.readOwnedBytes(allocator);        errdefer allocator.free(name);        ref_value.* = .{            .name = name,            .target = try reader.hash(),        };        refs_read += 1;    }    const commits = try allocator.alloc(CommitObject, commit_count);    var commits_read: usize = 0;    errdefer {        for (commits[0..commits_read]) |*commit| commit.deinit(allocator);        if (commits.len != 0) allocator.free(commits);    }    for (commits) |*commit| {        const hash = try reader.hash();        const root = try reader.hash();        const parent_count = try reader.readCount();        const parents = try allocator.alloc(version.Hash, parent_count);        errdefer if (parents.len != 0) allocator.free(parents);        for (parents) |*parent| parent.* = try reader.hash();        const canonical = version.Commit.init(root, parents);        if (!version.same(canonical.hash, hash)) return error.InvalidPack;        commit.* = .{            .hash = hash,            .root = root,            .parents = parents,        };        commits_read += 1;    }    return .{        .refs = refs,        .commits = commits,        .frame_count = frame_count,    };}fn decodeFrameKind(value: u32) Error!history_mod.PackRecordKind {    return std.enums.fromInt(history_mod.PackRecordKind, value) orelse error.InvalidPack;}pub fn decodePack(allocator: Allocator, bytes: []const u8) Error!Pack {    var reader = ByteReader.init(bytes);    var sections = try decodePackSections(allocator, &reader);    errdefer sections.deinit(allocator);    const frames = try allocator.alloc(PackFrame, sections.frame_count);    var frames_read: usize = 0;    errdefer {        for (frames[0..frames_read]) |*frame| frame.deinit(allocator);        if (frames.len != 0) allocator.free(frames);    }    for (frames) |*frame| {        const kind = try decodeFrameKind(try reader.readU32());        frame.* = .{            .kind = kind,            .payload = try reader.readOwnedBytes(allocator),        };        frames_read += 1;    }    try reader.finish();    return .{        .allocator = allocator,        .refs = sections.refs,        .commits = sections.commits,        .frames = frames,    };}pub fn encodeRemoteConfig(allocator: Allocator, config: *const RemoteConfig) Error![]u8 {    try validateRemoteEntries(config.remotes);    var bytes: std.ArrayList(u8) = .empty;    errdefer bytes.deinit(allocator);    try appendU32(allocator, &bytes, remote_config_magic);    try appendU32(allocator, &bytes, remote_config_format_version);    try appendCount(allocator, &bytes, config.remotes.len);    for (config.remotes) |remote| {        try appendBytes(allocator, &bytes, remote.name);        try appendBytes(allocator, &bytes, remote.history_path);        if (remote.lock_path) |lock_path| {            try appendU8(allocator, &bytes, 1);            try appendBytes(allocator, &bytes, lock_path);        } else {            try appendU8(allocator, &bytes, 0);        }        if (remote.default_branch) |default_branch| {            try appendU8(allocator, &bytes, 1);            try appendBytes(allocator, &bytes, default_branch);        } else {            try appendU8(allocator, &bytes, 0);        }    }    return try bytes.toOwnedSlice(allocator);}pub fn decodeRemoteConfig(allocator: Allocator, bytes: []const u8) Error!RemoteConfig {    var reader = RemoteConfigReader.init(bytes);    if (try reader.readU32() != remote_config_magic) return error.InvalidRemoteConfig;    const format_version = try reader.readU32();    if (format_version != 1 and format_version != remote_config_format_version) return error.InvalidRemoteConfig;    const count = try reader.readCount();    const remotes = try allocator.alloc(RemoteConfigEntry, count);    var read: usize = 0;    errdefer {        for (remotes[0..read]) |*remote| remote.deinit(allocator);        if (remotes.len != 0) allocator.free(remotes);    }    for (remotes) |*remote| {        const name = try reader.readBytes();        const history_path = try reader.readBytes();        const lock_path = switch (try reader.readU8()) {            0 => null,            1 => try reader.readBytes(),            else => return error.InvalidRemoteConfig,        };        const default_branch = if (format_version >= 2) switch (try reader.readU8()) {            0 => null,            1 => try reader.readBytes(),            else => return error.InvalidRemoteConfig,        } else null;        remote.* = try RemoteConfigEntry.initWithDefaultBranch(allocator, name, history_path, lock_path, default_branch);        read += 1;    }    try reader.finish();    try validateRemoteEntries(remotes);    return .{        .allocator = allocator,        .remotes = remotes,    };}pub fn readRemoteConfig(allocator: Allocator, dir: std.Io.Dir, options: RemoteConfigOptions) Error!RemoteConfig {    const bytes = dir.readFileAlloc(options.io, options.path, allocator, .limited(options.max_bytes)) catch |err| switch (err) {        error.FileNotFound => {            return .{                .allocator = allocator,                .remotes = &.{},            };        },        else => return err,    };    defer allocator.free(bytes);    return try decodeRemoteConfig(allocator, bytes);}pub fn writeRemoteConfig(allocator: Allocator, dir: std.Io.Dir, options: RemoteConfigOptions, config: *const RemoteConfig) Error!void {    const bytes = try encodeRemoteConfig(allocator, config);    defer allocator.free(bytes);    var out = try dir.createFile(options.io, options.path, .{ .read = true, .truncate = true });    defer out.close(options.io);    try out.writePositionalAll(options.io, bytes, 0);    try out.setLength(options.io, bytes.len);    try out.sync(options.io);}pub fn encodeBranchUpstreamConfig(allocator: Allocator, config: *const BranchUpstreamConfig) Error![]u8 {    try validateBranchUpstreamEntries(config.upstreams);    var bytes: std.ArrayList(u8) = .empty;    errdefer bytes.deinit(allocator);    try appendU32(allocator, &bytes, branch_upstream_config_magic);    try appendU32(allocator, &bytes, branch_upstream_config_format_version);    try appendCount(allocator, &bytes, config.upstreams.len);    for (config.upstreams) |upstream| {        try appendBytes(allocator, &bytes, upstream.branch);        try appendBytes(allocator, &bytes, upstream.remote);        try appendBytes(allocator, &bytes, upstream.remote_branch);    }    return try bytes.toOwnedSlice(allocator);}pub fn decodeBranchUpstreamConfig(allocator: Allocator, bytes: []const u8) Error!BranchUpstreamConfig {    var reader = BranchUpstreamConfigReader.init(bytes);    if (try reader.readU32() != branch_upstream_config_magic) return error.InvalidBranchUpstreamConfig;    if (try reader.readU32() != branch_upstream_config_format_version) return error.InvalidBranchUpstreamConfig;    const count = try reader.readCount();    const upstreams = try allocator.alloc(BranchUpstreamEntry, count);    var read: usize = 0;    errdefer {        for (upstreams[0..read]) |*upstream| upstream.deinit(allocator);        if (upstreams.len != 0) allocator.free(upstreams);    }    for (upstreams) |*upstream| {        const branch_name = try reader.readBytes();        const remote_name = try reader.readBytes();        const remote_branch_name = try reader.readBytes();        upstream.* = try BranchUpstreamEntry.init(allocator, branch_name, remote_name, remote_branch_name);        read += 1;    }    try reader.finish();    try validateBranchUpstreamEntries(upstreams);    return .{        .allocator = allocator,        .upstreams = upstreams,    };}pub fn readBranchUpstreamConfig(allocator: Allocator, dir: std.Io.Dir, options: BranchUpstreamOptions) Error!BranchUpstreamConfig {    const bytes = dir.readFileAlloc(options.io, options.path, allocator, .limited(options.max_bytes)) catch |err| switch (err) {        error.FileNotFound => {            return .{                .allocator = allocator,                .upstreams = &.{},            };        },        else => return err,    };    defer allocator.free(bytes);    return try decodeBranchUpstreamConfig(allocator, bytes);}pub fn writeBranchUpstreamConfig(allocator: Allocator, dir: std.Io.Dir, options: BranchUpstreamOptions, config: *const BranchUpstreamConfig) Error!void {    const bytes = try encodeBranchUpstreamConfig(allocator, config);    defer allocator.free(bytes);    var out = try dir.createFile(options.io, options.path, .{ .read = true, .truncate = true });    defer out.close(options.io);    try out.writePositionalAll(options.io, bytes, 0);    try out.setLength(options.io, bytes.len);    try out.sync(options.io);}pub fn lockFileRemote(allocator: Allocator, remote: FileRemote) Error!FileRemoteLock {    const lock_path = if (remote.lock_path) |path| path else try std.fmt.allocPrint(allocator, "{s}.lock", .{remote.path});    defer if (remote.lock_path == null) allocator.free(lock_path);    const lock_file = remote.dir.createFile(remote.io, lock_path, .{        .read = true,        .truncate = false,        .lock = .exclusive,        .lock_nonblocking = true,    }) catch |err| switch (err) {        error.WouldBlock => return error.RemoteBusy,        else => return err,    };    return .{ .io = remote.io, .file = lock_file };}pub fn advertiseRefs(allocator: Allocator, history: *const history_mod.History) Error!Advertisement {    const refs = try history.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    const owned = try allocator.alloc(RefObject, refs.len);    var count: usize = 0;    errdefer {        for (owned[0..count]) |*ref_value| ref_value.deinit(allocator);        if (owned.len != 0) allocator.free(owned);    }    for (refs, owned) |ref_value, *target| {        const name = try allocator.dupe(u8, ref_value.name);        errdefer allocator.free(name);        target.* = .{            .name = name,            .target = ref_value.target,        };        count += 1;    }    return .{        .allocator = allocator,        .refs = owned,    };}pub fn encodeAdvertisement(allocator: Allocator, advertisement: *const Advertisement) Error![]u8 {    var bytes: std.ArrayList(u8) = .empty;    errdefer bytes.deinit(allocator);    try appendU32(allocator, &bytes, advertisement_magic);    try appendU32(allocator, &bytes, advertisement_format_version);    try appendCount(allocator, &bytes, advertisement.refs.len);    for (advertisement.refs) |ref_value| {        try appendBytes(allocator, &bytes, ref_value.name);        try appendHash(allocator, &bytes, ref_value.target);    }    return try bytes.toOwnedSlice(allocator);}pub fn decodeAdvertisement(allocator: Allocator, bytes: []const u8) Error!Advertisement {    var reader = AdvertisementReader.init(bytes);    if (try reader.readU32() != advertisement_magic) return error.InvalidAdvertisement;    if (try reader.readU32() != advertisement_format_version) return error.InvalidAdvertisement;    const count = try reader.readCount();    const refs = try allocator.alloc(RefObject, count);    var read: usize = 0;    errdefer {        for (refs[0..read]) |*ref_value| ref_value.deinit(allocator);        if (refs.len != 0) allocator.free(refs);    }    for (refs) |*ref_value| {        const name = try reader.readOwnedBytes(allocator);        errdefer allocator.free(name);        ref_value.* = .{            .name = name,            .target = try reader.hash(),        };        read += 1;    }    try reader.finish();    return .{        .allocator = allocator,        .refs = refs,    };}pub fn fetchRequestFromHistory(allocator: Allocator, local: *const history_mod.History, wants: []const []const u8) Error!FetchRequest {    const entries = try local.commitEntries(allocator);    defer allocator.free(entries);    const haves = try allocator.alloc(version.Hash, entries.len);    defer allocator.free(haves);    for (entries, haves) |entry, *hash| hash.* = entry.hash;    return try FetchRequest.init(allocator, wants, haves);}pub fn encodeFetchRequest(allocator: Allocator, request: *const FetchRequest) Error![]u8 {    var bytes: std.ArrayList(u8) = .empty;    errdefer bytes.deinit(allocator);    try appendU32(allocator, &bytes, fetch_request_magic);    try appendU32(allocator, &bytes, fetch_request_format_version);    try appendCount(allocator, &bytes, request.wants.len);    try appendCount(allocator, &bytes, request.haves.len);    for (request.wants) |want| try appendBytes(allocator, &bytes, want);    for (request.haves) |have| try appendHash(allocator, &bytes, have);    return try bytes.toOwnedSlice(allocator);}pub fn decodeFetchRequest(allocator: Allocator, bytes: []const u8) Error!FetchRequest {    var reader = FetchRequestReader.init(bytes);    if (try reader.readU32() != fetch_request_magic) return error.InvalidFetchRequest;    if (try reader.readU32() != fetch_request_format_version) return error.InvalidFetchRequest;    const want_count = try reader.readCount();    const have_count = try reader.readCount();    const wants = try allocator.alloc([]u8, want_count);    var wants_read: usize = 0;    errdefer {        for (wants[0..wants_read]) |want| allocator.free(want);        if (wants.len != 0) allocator.free(wants);    }    for (wants) |*want| {        want.* = try reader.readOwnedBytes(allocator);        wants_read += 1;    }    const haves = try allocator.alloc(version.Hash, have_count);    errdefer if (haves.len != 0) allocator.free(haves);    for (haves) |*have| have.* = try reader.hash();    try reader.finish();    return .{        .allocator = allocator,        .wants = wants,        .haves = haves,    };}const HashSet = std.AutoHashMapUnmanaged(version.Hash, void);const FrameGroup = struct {    kind: history_mod.PackRecordKind,    hashes: []const version.Hash,};const PackTreeTraversalEntry = struct {    key: version.Hash,    expanded: bool,};const PackBuilder = struct {    allocator: Allocator,    source: *const history_mod.History,    have: ?*const history_mod.History = null,    have_commits: []const version.Hash = &.{},    refs: std.ArrayList(RefObject) = .empty,    commits: std.ArrayList(CommitObject) = .empty,    seen_commits: HashSet = .empty,    seen_database_roots: HashSet = .empty,    seen_relation_roots: HashSet = .empty,    seen_relation_rows: HashSet = .empty,    seen_pages: HashSet = .empty,    seen_chunks: HashSet = .empty,    seen_nodes: HashSet = .empty,    seen_conflicts: HashSet = .empty,    seen_conflict_roots: HashSet = .empty,    chunks: std.ArrayList(version.Hash) = .empty,    pages: std.ArrayList(version.Hash) = .empty,    nodes: std.ArrayList(version.Hash) = .empty,    relation_rows: std.ArrayList(version.Hash) = .empty,    relation_roots: std.ArrayList(version.Hash) = .empty,    conflicts: std.ArrayList(version.Hash) = .empty,    conflict_roots: std.ArrayList(version.Hash) = .empty,    database_roots: std.ArrayList(version.Hash) = .empty,    fn init(allocator: Allocator, source: *const history_mod.History) PackBuilder {        return .{            .allocator = allocator,            .source = source,        };    }    fn initMissing(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History) PackBuilder {        return .{            .allocator = allocator,            .source = source,            .have = have,        };    }    fn initWithHaveCommits(allocator: Allocator, source: *const history_mod.History, haves: []const version.Hash) PackBuilder {        return .{            .allocator = allocator,            .source = source,            .have_commits = haves,        };    }    fn deinit(self: *PackBuilder) void {        for (self.refs.items) |*ref_value| ref_value.deinit(self.allocator);        for (self.commits.items) |*commit| commit.deinit(self.allocator);        self.refs.deinit(self.allocator);        self.commits.deinit(self.allocator);        self.seen_commits.deinit(self.allocator);        self.seen_database_roots.deinit(self.allocator);        self.seen_relation_roots.deinit(self.allocator);        self.seen_relation_rows.deinit(self.allocator);        self.seen_pages.deinit(self.allocator);        self.seen_chunks.deinit(self.allocator);        self.seen_nodes.deinit(self.allocator);        self.seen_conflicts.deinit(self.allocator);        self.seen_conflict_roots.deinit(self.allocator);        self.chunks.deinit(self.allocator);        self.pages.deinit(self.allocator);        self.nodes.deinit(self.allocator);        self.relation_rows.deinit(self.allocator);        self.relation_roots.deinit(self.allocator);        self.conflicts.deinit(self.allocator);        self.conflict_roots.deinit(self.allocator);        self.database_roots.deinit(self.allocator);        self.* = undefined;    }    fn frameGroups(self: *const PackBuilder) [8]FrameGroup {        return .{            .{ .kind = .row_chunk, .hashes = self.chunks.items },            .{ .kind = .chunk_index_page, .hashes = self.pages.items },            .{ .kind = .tree_node, .hashes = self.nodes.items },            .{ .kind = .relation_rows, .hashes = self.relation_rows.items },            .{ .kind = .relation_root, .hashes = self.relation_roots.items },            .{ .kind = .conflict, .hashes = self.conflicts.items },            .{ .kind = .conflict_root, .hashes = self.conflict_roots.items },            .{ .kind = .database_root, .hashes = self.database_roots.items },        };    }    fn frameCount(self: *const PackBuilder) usize {        var count: usize = 0;        for (self.frameGroups()) |group| count += group.hashes.len;        return count;    }    fn finish(self: *PackBuilder) Error!Pack {        var frames: std.ArrayList(PackFrame) = .empty;        errdefer {            for (frames.items) |*frame| frame.deinit(self.allocator);            frames.deinit(self.allocator);        }        try frames.ensureTotalCapacity(self.allocator, self.frameCount());        var payload: std.ArrayList(u8) = .empty;        defer payload.deinit(self.allocator);        for (self.frameGroups()) |group| {            for (group.hashes) |hash| {                payload.clearRetainingCapacity();                try self.source.appendPackRecordPayload(self.allocator, &payload, group.kind, hash);                const owned = try self.allocator.dupe(u8, payload.items);                frames.appendAssumeCapacity(.{                    .kind = group.kind,                    .payload = owned,                });            }        }        const owned_frames = try frames.toOwnedSlice(self.allocator);        errdefer {            for (owned_frames) |*frame| frame.deinit(self.allocator);            if (owned_frames.len != 0) self.allocator.free(owned_frames);        }        const refs = try self.refs.toOwnedSlice(self.allocator);        errdefer {            for (refs) |*ref_value| ref_value.deinit(self.allocator);            if (refs.len != 0) self.allocator.free(refs);        }        const commits = try self.commits.toOwnedSlice(self.allocator);        return .{            .allocator = self.allocator,            .refs = refs,            .commits = commits,            .frames = owned_frames,        };    }    fn encodeBytes(self: *PackBuilder) Error![]u8 {        var bytes: std.ArrayList(u8) = .empty;        errdefer bytes.deinit(self.allocator);        try appendU32(self.allocator, &bytes, pack_magic);        try appendU32(self.allocator, &bytes, pack_format_version);        try appendCount(self.allocator, &bytes, self.refs.items.len);        try appendCount(self.allocator, &bytes, self.commits.items.len);        try appendCount(self.allocator, &bytes, self.frameCount());        for (self.refs.items) |ref_value| {            try appendBytes(self.allocator, &bytes, ref_value.name);            try appendHash(self.allocator, &bytes, ref_value.target);        }        for (self.commits.items) |commit| {            try appendHash(self.allocator, &bytes, commit.hash);            try appendHash(self.allocator, &bytes, commit.root);            try appendCount(self.allocator, &bytes, commit.parents.len);            for (commit.parents) |parent| try appendHash(self.allocator, &bytes, parent);        }        var payload: std.ArrayList(u8) = .empty;        defer payload.deinit(self.allocator);        for (self.frameGroups()) |group| {            for (group.hashes) |hash| {                payload.clearRetainingCapacity();                try self.source.appendPackRecordPayload(self.allocator, &payload, group.kind, hash);                try appendU32(self.allocator, &bytes, @backingInt(group.kind));                try appendBytes(self.allocator, &bytes, payload.items);            }        }        return try bytes.toOwnedSlice(self.allocator);    }    fn addRef(self: *PackBuilder, name: []const u8, target: version.Hash) Error!void {        if (containsRef(self.refs.items, name)) return;        const owned_name = try self.allocator.dupe(u8, name);        errdefer self.allocator.free(owned_name);        try self.refs.append(self.allocator, .{            .name = owned_name,            .target = target,        });    }    fn addReachableCommit(self: *PackBuilder, hash: version.Hash) Error!void {        var pending: std.ArrayList(version.Hash) = .empty;        defer pending.deinit(self.allocator);        try pending.append(self.allocator, hash);        while (pending.pop()) |next| {            if (self.seen_commits.contains(next)) continue;            try self.seen_commits.put(self.allocator, next, {});            if (containsHash(self.have_commits, next)) continue;            if (self.have) |have| {                if (have.hasCommit(next)) continue;            }            const commit = try self.source.commitValue(next);            try self.commits.ensureUnusedCapacity(self.allocator, 1);            const parents = try self.allocator.dupe(version.Hash, commit.parents);            self.commits.appendAssumeCapacity(.{                .hash = commit.hash,                .root = commit.root,                .parents = parents,            });            try self.addDatabaseRoot(commit.root);            var index = commit.parents.len;            while (index > 0) {                index -= 1;                try pending.append(self.allocator, commit.parents[index]);            }        }    }    fn addDatabaseRoot(self: *PackBuilder, hash: version.Hash) Error!void {        if (self.seen_database_roots.contains(hash)) return;        try self.seen_database_roots.put(self.allocator, hash, {});        if (self.have) |have| {            if (have.hasDatabaseRoot(hash)) return;        }        const view = self.source.databaseRootView(hash) orelse return error.DatabaseRootNotFound;        for (view.entries) |entry| {            try self.addRelationRoot(entry.hash);            try self.addRelationRows(entry.hash);        }        try self.addConflictRoot(view.conflicts);        try self.database_roots.append(self.allocator, hash);    }    fn addRelationRoot(self: *PackBuilder, hash: version.Hash) Error!void {        if (self.seen_relation_roots.contains(hash)) return;        try self.seen_relation_roots.put(self.allocator, hash, {});        if (self.have) |have| {            if (have.hasRelationRoot(hash)) return;        }        var keys = (try self.source.relationKeysView(self.allocator, hash)) orelse return error.RelationRootNotFound;        defer keys.deinit();        if (keys.table_key) |key| try self.addTreeNode(key);        for (keys.index_keys) |index_key| {            const key = index_key orelse continue;            try self.addTreeNode(key);        }        try self.relation_roots.append(self.allocator, hash);    }    fn addTreeNode(self: *PackBuilder, key: version.Hash) Error!void {        var stack: std.ArrayList(PackTreeTraversalEntry) = .empty;        defer stack.deinit(self.allocator);        try stack.append(self.allocator, .{ .key = key, .expanded = false });        while (stack.pop()) |entry| {            if (entry.expanded) {                try self.nodes.append(self.allocator, entry.key);                continue;            }            if (self.seen_nodes.contains(entry.key)) continue;            try self.seen_nodes.put(self.allocator, entry.key, {});            if (self.have) |have| {                if (have.hasTreeNode(entry.key)) continue;            }            try stack.append(self.allocator, .{ .key = entry.key, .expanded = true });            const children = try self.source.treeNodeChildren(self.allocator, entry.key);            defer self.allocator.free(children);            for (children) |child| try stack.append(self.allocator, .{ .key = child, .expanded = false });        }    }    fn addRelationRows(self: *PackBuilder, root: version.Hash) Error!void {        if (self.seen_relation_rows.contains(root)) return;        try self.seen_relation_rows.put(self.allocator, root, {});        if (self.have) |have| {            if (have.hasRelationRows(root)) return;        }        var pages = (try self.source.relationRowsPages(self.allocator, root)) orelse return error.RelationRowsNotFound;        defer pages.deinit();        for (pages.items) |digest| try self.addPage(digest);        try self.relation_rows.append(self.allocator, root);    }    fn addPage(self: *PackBuilder, digest: version.Hash) Error!void {        if (self.seen_pages.contains(digest)) return;        try self.seen_pages.put(self.allocator, digest, {});        if (self.have) |have| {            if (have.hasIndexPage(digest)) return;        }        var chunks = (try self.source.indexPageChunks(self.allocator, digest)) orelse return error.RelationRowsNotFound;        defer chunks.deinit();        for (chunks.items) |chunk_digest| try self.addChunk(chunk_digest);        try self.pages.append(self.allocator, digest);    }    fn addChunk(self: *PackBuilder, digest: version.Hash) Error!void {        if (self.seen_chunks.contains(digest)) return;        try self.seen_chunks.put(self.allocator, digest, {});        if (self.have) |have| {            if (have.hasRowChunk(digest)) return;        }        try self.chunks.append(self.allocator, digest);    }    fn addConflictRoot(self: *PackBuilder, root_hash: version.Hash) Error!void {        if (version.same(root_hash, version.ConflictRoot.empty().hash)) return;        if (self.seen_conflict_roots.contains(root_hash)) return;        try self.seen_conflict_roots.put(self.allocator, root_hash, {});        if (self.have) |have| {            if (try have.hasConflictRoot(root_hash)) return;        }        var artifacts = try self.source.conflictArtifacts(self.allocator, root_hash);        defer artifacts.deinit();        for (artifacts.artifacts) |artifact| try self.addConflict(artifact.hash);        try self.conflict_roots.append(self.allocator, root_hash);    }    fn addConflict(self: *PackBuilder, hash: version.Hash) Error!void {        if (self.seen_conflicts.contains(hash)) return;        try self.seen_conflicts.put(self.allocator, hash, {});        if (self.have) |have| {            if (have.hasConflict(hash)) return;        }        try self.conflicts.append(self.allocator, hash);    }};fn builderForRefs(allocator: Allocator, source: *const history_mod.History, refs: []const version.Ref) Error!PackBuilder {    var builder = PackBuilder.init(allocator, source);    errdefer builder.deinit();    for (refs) |ref_value| {        try builder.addRef(ref_value.name, ref_value.target);        try builder.addReachableCommit(ref_value.target);    }    return builder;}fn builderForMissingRefs(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History, refs: []const version.Ref) Error!PackBuilder {    var builder = PackBuilder.initMissing(allocator, source, have);    errdefer builder.deinit();    for (refs) |ref_value| {        try builder.addRef(ref_value.name, ref_value.target);        try builder.addReachableCommit(ref_value.target);    }    return builder;}fn refsForNames(allocator: Allocator, source: *const history_mod.History, names: []const []const u8) Error![]version.Ref {    const refs = try allocator.alloc(version.Ref, names.len);    errdefer allocator.free(refs);    for (names, refs) |name, *ref_value| {        ref_value.* = (try source.ref(name)) orelse return error.RefNotFound;    }    return refs;}pub fn exportAll(allocator: Allocator, source: *const history_mod.History) Error!Pack {    const refs = try source.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    return try exportRefs(allocator, source, refs);}pub fn exportRefNames(allocator: Allocator, source: *const history_mod.History, names: []const []const u8) Error!Pack {    const refs = try refsForNames(allocator, source, names);    defer allocator.free(refs);    return try exportRefs(allocator, source, refs);}pub fn exportRefs(allocator: Allocator, source: *const history_mod.History, refs: []const version.Ref) Error!Pack {    var builder = try builderForRefs(allocator, source, refs);    defer builder.deinit();    return try builder.finish();}pub fn exportMissingRefs(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History, refs: []const version.Ref) Error!Pack {    var builder = try builderForMissingRefs(allocator, source, have, refs);    defer builder.deinit();    return try builder.finish();}pub fn exportMissingRefNames(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History, names: []const []const u8) Error!Pack {    const refs = try refsForNames(allocator, source, names);    defer allocator.free(refs);    return try exportMissingRefs(allocator, source, have, refs);}pub fn exportForFetchRequest(allocator: Allocator, source: *const history_mod.History, request: *const FetchRequest) Error!Pack {    var builder = PackBuilder.initWithHaveCommits(allocator, source, request.haves);    defer builder.deinit();    for (request.wants) |name| {        const ref_value = (try source.ref(name)) orelse return error.RefNotFound;        try builder.addRef(ref_value.name, ref_value.target);        try builder.addReachableCommit(ref_value.target);    }    return try builder.finish();}pub fn exportBytesForFetchRequest(allocator: Allocator, source: *const history_mod.History, request: *const FetchRequest) Error![]u8 {    var builder = PackBuilder.initWithHaveCommits(allocator, source, request.haves);    defer builder.deinit();    for (request.wants) |name| {        const ref_value = (try source.ref(name)) orelse return error.RefNotFound;        try builder.addRef(ref_value.name, ref_value.target);        try builder.addReachableCommit(ref_value.target);    }    return try builder.encodeBytes();}pub fn exportMissingBytesAll(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History) Error![]u8 {    const refs = try source.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    var builder = try builderForMissingRefs(allocator, source, have, refs);    defer builder.deinit();    return try builder.encodeBytes();}pub fn exportMissingRefNameBytes(allocator: Allocator, source: *const history_mod.History, have: *const history_mod.History, names: []const []const u8) Error![]u8 {    const refs = try refsForNames(allocator, source, names);    defer allocator.free(refs);    var builder = try builderForMissingRefs(allocator, source, have, refs);    defer builder.deinit();    return try builder.encodeBytes();}pub fn exportBytesAll(allocator: Allocator, source: *const history_mod.History) Error![]u8 {    const refs = try source.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    var builder = try builderForRefs(allocator, source, refs);    defer builder.deinit();    return try builder.encodeBytes();}pub fn exportRefNameBytes(allocator: Allocator, source: *const history_mod.History, names: []const []const u8) Error![]u8 {    const refs = try refsForNames(allocator, source, names);    defer allocator.free(refs);    var builder = try builderForRefs(allocator, source, refs);    defer builder.deinit();    return try builder.encodeBytes();}pub fn importObjects(target: *history_mod.History, pack: *const Pack) Error!Stats {    var stats = Stats{};    for (pack.frames) |frame| {        if (try target.importPackRecord(frame.kind, frame.payload)) stats.records += 1;    }    for (pack.commits) |commit_object| {        const commit = version.Commit.init(commit_object.root, commit_object.parents);        if (!version.same(commit.hash, commit_object.hash)) return error.InvalidPack;        if (try target.importPackCommit(commit_object.root, commit_object.parents)) stats.commits += 1;    }    try target.flushSync();    return stats;}pub fn importPack(target: *history_mod.History, pack: *const Pack) Error!Stats {    var stats = try importObjects(target, pack);    for (pack.refs) |ref_value| {        try target.putRef(.{            .name = ref_value.name,            .target = ref_value.target,        });        stats.refs += 1;    }    return stats;}pub fn importBytes(allocator: Allocator, target: *history_mod.History, bytes: []const u8) Error!Stats {    var reader = ByteReader.init(bytes);    var sections = try decodePackSections(allocator, &reader);    defer sections.deinit(allocator);    var stats = Stats{};    var index: usize = 0;    while (index < sections.frame_count) : (index += 1) {        const kind = try decodeFrameKind(try reader.readU32());        const payload = try reader.readBytes();        if (try target.importPackRecord(kind, payload)) stats.records += 1;    }    try reader.finish();    for (sections.commits) |commit_object| {        const commit = version.Commit.init(commit_object.root, commit_object.parents);        if (!version.same(commit.hash, commit_object.hash)) return error.InvalidPack;        if (try target.importPackCommit(commit_object.root, commit_object.parents)) stats.commits += 1;    }    for (sections.refs) |ref_value| {        try target.putRef(.{            .name = ref_value.name,            .target = ref_value.target,        });        stats.refs += 1;    }    try target.flushSync();    return stats;}pub fn headHex(hash: version.Hash) HeadHex {    return std.fmt.bytesToHex(hash, .lower);}pub fn packRefTarget(pack: *const Pack, ref_name: []const u8) ?version.Hash {    for (pack.refs) |ref_value| {        if (std.mem.eql(u8, ref_value.name, ref_name)) return ref_value.target;    }    return null;}pub fn missingPackCounts(local: *const history_mod.History, pack: *const Pack) Error!HistoryTransferPlan {    var counts = HistoryTransferPlan{};    for (pack.commits) |commit| {        if (!local.hasCommit(commit.hash)) counts.commits += 1;    }    for (pack.frames) |frame| {        if (!try local.packRecordPresent(frame.kind, frame.payload)) counts.records += 1;    }    return counts;}pub fn missingHistoryCommitCount(allocator: Allocator, source: *const history_mod.History, head: version.Hash, other: *const history_mod.History) Error!usize {    var visited = std.AutoHashMap(version.Hash, void).init(allocator);    defer visited.deinit();    var stack: std.ArrayList(version.Hash) = .empty;    defer stack.deinit(allocator);    try stack.append(allocator, head);    var missing: usize = 0;    while (stack.pop()) |hash| {        if (visited.contains(hash)) continue;        try visited.put(hash, {});        if (other.hasCommit(hash)) continue;        missing += 1;        const commit = try source.commitValue(hash);        for (commit.parents) |parent| try stack.append(allocator, parent);    }    return missing;}pub fn historyRelation(allocator: Allocator, local: *const history_mod.History, remote: *const history_mod.History, ref_name: []const u8) Error!HistoryRelation {    const local_ref = (try local.ref(ref_name)) orelse return error.RefNotFound;    const remote_ref = (try remote.ref(ref_name));    const ahead = try missingHistoryCommitCount(allocator, local, local_ref.target, remote);    const behind = if (remote_ref) |ref_value| try missingHistoryCommitCount(allocator, remote, ref_value.target, local) else 0;    return .{        .local_head = local_ref.target,        .remote_head = if (remote_ref) |ref_value| ref_value.target else null,        .ahead = ahead,        .behind = behind,    };}pub fn planHistoryAdopt(allocator: Allocator, local: *const history_mod.History, remote: *const history_mod.History, ref_name: []const u8) Error!HistoryTransferPlan {    var names = [_][]const u8{ref_name};    var pack = try exportMissingRefNames(allocator, remote, local, names[0..]);    defer pack.deinit();    return .{ .commits = pack.commits.len, .records = pack.frames.len };}pub fn planHistoryTransfer(    allocator: Allocator,    local: *const history_mod.History,    remote: *const history_mod.History,    ref_name: []const u8,    related: HistoryRelation,    direction: HistoryDirection,) HistoryPlanError!HistoryTransferPlan {    if (related.upToDate()) return .{};    if (related.diverged()) return error.HistoryDiverged;    switch (direction) {        .push => {            if (related.behind != 0) return error.HistoryRemoteAhead;            if (related.ahead == 0 and related.remote_head != null) return .{};            var names = [_][]const u8{ref_name};            var pack = try exportMissingRefNames(allocator, local, remote, names[0..]);            defer pack.deinit();            return .{ .commits = pack.commits.len, .records = pack.frames.len };        },        .pull => {            if (related.remote_head == null or related.behind == 0) return .{};            var names = [_][]const u8{ref_name};            var pack = try exportMissingRefNames(allocator, remote, local, names[0..]);            defer pack.deinit();            return .{ .commits = pack.commits.len, .records = pack.frames.len };        },    }}pub fn cloneAll(allocator: Allocator, source: *const history_mod.History, target: *history_mod.History) Error!Stats {    var pack = try exportAll(allocator, source);    defer pack.deinit();    return try importPack(target, &pack);}pub fn fetch(allocator: Allocator, local: *history_mod.History, remote: *const history_mod.History, options: FetchOptions) Error!Stats {    const refs = try remote.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    var pack = try exportMissingRefs(allocator, remote, local, refs);    defer pack.deinit();    var stats = try fetchPack(allocator, local, &pack, options);    if (options.prune) stats.refs += try pruneRemoteTrackingRefs(allocator, local, options.remote, refs);    return stats;}pub fn fetchPack(allocator: Allocator, local: *history_mod.History, pack: *const Pack, options: FetchOptions) Error!Stats {    var stats = try importObjects(local, pack);    for (pack.refs) |ref_value| {        const name = try fetchRefName(allocator, options.remote, ref_value.name);        defer allocator.free(name);        try local.putRef(.{            .name = name,            .target = ref_value.target,        });        stats.refs += 1;    }    return stats;}pub fn fetchBytes(allocator: Allocator, local: *history_mod.History, bytes: []const u8, options: FetchOptions) Error!Stats {    var pack = try decodePack(allocator, bytes);    defer pack.deinit();    return try fetchPack(allocator, local, &pack, options);}pub fn pushFastForward(allocator: Allocator, local: *const history_mod.History, remote: *history_mod.History, ref_name: []const u8) Error!Stats {    return try pushFastForwardTo(allocator, local, remote, ref_name, ref_name);}pub fn pushFastForwardTo(allocator: Allocator, local: *const history_mod.History, remote: *history_mod.History, local_ref_name: []const u8, remote_ref_name: []const u8) Error!Stats {    const ref_value = (try local.ref(local_ref_name)) orelse return error.RefNotFound;    var names = [_][]const u8{local_ref_name};    var pack = try exportMissingRefNames(allocator, local, remote, names[0..]);    defer pack.deinit();    const expected = if ((try remote.ref(remote_ref_name))) |remote_ref| remote_ref.target else null;    if (expected) |remote_target| {        if (!try canFastForwardWithPack(allocator, remote, &pack, remote_target, ref_value.target)) return error.NonFastForward;    }    var stats = try importObjects(remote, &pack);    try remote.putRefIfMatches(.{        .name = remote_ref_name,        .target = ref_value.target,    }, expected);    stats.refs += 1;    return stats;}pub fn deleteRemoteRef(remote: *history_mod.History, ref_name: []const u8) Error!Stats {    try remote.deleteRef(ref_name);    return .{ .refs = 1 };}pub fn pullFastForward(allocator: Allocator, local: *history_mod.History, remote: *const history_mod.History, ref_name: []const u8) Error!Stats {    const ref_value = (try remote.ref(ref_name)) orelse return error.RefNotFound;    var names = [_][]const u8{ref_name};    var pack = try exportMissingRefNames(allocator, remote, local, names[0..]);    defer pack.deinit();    const expected = if ((try local.ref(ref_name))) |local_ref| local_ref.target else null;    if (expected) |local_target| {        if (!try canFastForwardWithPack(allocator, local, &pack, local_target, ref_value.target)) return error.NonFastForward;    }    var stats = try importObjects(local, &pack);    try local.putRefIfMatches(.{        .name = ref_name,        .target = ref_value.target,    }, expected);    stats.refs += 1;    return stats;}pub fn cloneFile(allocator: Allocator, remote: FileRemote, target: *history_mod.History) Error!Stats {    var lock = try lockFileRemote(allocator, remote);    defer lock.release();    var remote_history = try history_mod.History.open(allocator, remote.dir, .{ .io = remote.io, .path = remote.path, .create = false, .recovery = .reject });    defer remote_history.deinit();    return try cloneAll(allocator, &remote_history, target);}pub fn fetchFile(allocator: Allocator, local: *history_mod.History, remote: FileRemote, options: FetchOptions) Error!Stats {    var lock = try lockFileRemote(allocator, remote);    defer lock.release();    var remote_history = try history_mod.History.open(allocator, remote.dir, .{ .io = remote.io, .path = remote.path, .create = false, .recovery = .reject });    defer remote_history.deinit();    return try fetch(allocator, local, &remote_history, options);}pub fn pushFileFastForward(allocator: Allocator, local: *const history_mod.History, remote: FileRemote, ref_name: []const u8) Error!Stats {    return try pushFileFastForwardTo(allocator, local, remote, ref_name, ref_name);}pub fn pushFileFastForwardTo(allocator: Allocator, local: *const history_mod.History, remote: FileRemote, local_ref_name: []const u8, remote_ref_name: []const u8) Error!Stats {    var lock = try lockFileRemote(allocator, remote);    defer lock.release();    var remote_history = try history_mod.History.open(allocator, remote.dir, .{ .io = remote.io, .path = remote.path, .recovery = .reject });    defer remote_history.deinit();    return try pushFastForwardTo(allocator, local, &remote_history, local_ref_name, remote_ref_name);}pub fn deleteFileRemoteRef(allocator: Allocator, remote: FileRemote, ref_name: []const u8) Error!Stats {    var lock = try lockFileRemote(allocator, remote);    defer lock.release();    var remote_history = try history_mod.History.open(allocator, remote.dir, .{ .io = remote.io, .path = remote.path, .create = false, .recovery = .reject });    defer remote_history.deinit();    return try deleteRemoteRef(&remote_history, ref_name);}pub fn pullFileFastForward(allocator: Allocator, local: *history_mod.History, remote: FileRemote, ref_name: []const u8) Error!Stats {    var lock = try lockFileRemote(allocator, remote);    defer lock.release();    var remote_history = try history_mod.History.open(allocator, remote.dir, .{ .io = remote.io, .path = remote.path, .create = false, .recovery = .reject });    defer remote_history.deinit();    return try pullFastForward(allocator, local, &remote_history, ref_name);}fn remoteTrackingName(allocator: Allocator, remote: []const u8, name: []const u8) Allocator.Error![]u8 {    return try std.fmt.allocPrint(allocator, "refs/remotes/{s}/{s}", .{ remote, name });}fn fetchRefName(allocator: Allocator, remote: []const u8, name: []const u8) Allocator.Error![]u8 {    if (isTagRef(name)) return try allocator.dupe(u8, name);    return try remoteTrackingName(allocator, remote, name);}fn pruneRemoteTrackingRefs(allocator: Allocator, local: *history_mod.History, remote: []const u8, remote_refs: []const version.Ref) Error!usize {    const prefix = try std.fmt.allocPrint(allocator, "refs/remotes/{s}/", .{remote});    defer allocator.free(prefix);    const refs = try local.refList(allocator);    defer history_mod.freeRefList(allocator, refs);    var pruned: usize = 0;    for (refs) |ref_value| {        if (!std.mem.startsWith(u8, ref_value.name, prefix)) continue;        const remote_ref_name = ref_value.name[prefix.len..];        if (containsVersionRef(remote_refs, remote_ref_name)) continue;        try local.deleteRef(ref_value.name);        pruned += 1;    }    return pruned;}fn containsVersionRef(refs: []const version.Ref, name: []const u8) bool {    for (refs) |ref_value| {        if (std.mem.eql(u8, ref_value.name, name)) return true;    }    return false;}fn isTagRef(name: []const u8) bool {    return std.mem.startsWith(u8, name, "refs/tags/");}const ByteReader = struct {    bytes_value: []const u8,    cursor: usize = 0,    fn init(bytes: []const u8) ByteReader {        return .{ .bytes_value = bytes };    }    fn finish(self: *const ByteReader) Error!void {        if (self.cursor != self.bytes_value.len) return error.InvalidPack;    }    fn hash(self: *ByteReader) Error!version.Hash {        if (version.hash_bytes > self.bytes_value.len - self.cursor) return error.InvalidPack;        const value = self.bytes_value[self.cursor..][0..version.hash_bytes].*;        self.cursor += version.hash_bytes;        return value;    }    fn readBytes(self: *ByteReader) Error![]const u8 {        const len = try self.readCount();        if (len > self.bytes_value.len - self.cursor) return error.InvalidPack;        const value = self.bytes_value[self.cursor..][0..len];        self.cursor += len;        return value;    }    fn readOwnedBytes(self: *ByteReader, allocator: Allocator) Error![]u8 {        return try allocator.dupe(u8, try self.readBytes());    }    fn readCount(self: *ByteReader) Error!usize {        return @intCast(try self.readU32());    }    fn readU32(self: *ByteReader) Error!u32 {        if (4 > self.bytes_value.len - self.cursor) return error.InvalidPack;        const value = std.mem.readInt(u32, self.bytes_value[self.cursor..][0..4], .big);        self.cursor += 4;        return value;    }};const RemoteConfigReader = struct {    bytes_value: []const u8,    cursor: usize = 0,    fn init(bytes: []const u8) RemoteConfigReader {        return .{ .bytes_value = bytes };    }    fn finish(self: *const RemoteConfigReader) Error!void {        if (self.cursor != self.bytes_value.len) return error.InvalidRemoteConfig;    }    fn readBytes(self: *RemoteConfigReader) Error![]const u8 {        const len = try self.readCount();        if (len > self.bytes_value.len - self.cursor) return error.InvalidRemoteConfig;        const value = self.bytes_value[self.cursor..][0..len];        self.cursor += len;        return value;    }    fn readCount(self: *RemoteConfigReader) Error!usize {        return @intCast(try self.readU32());    }    fn readU8(self: *RemoteConfigReader) Error!u8 {        if (self.cursor >= self.bytes_value.len) return error.InvalidRemoteConfig;        const value = self.bytes_value[self.cursor];        self.cursor += 1;        return value;    }    fn readU32(self: *RemoteConfigReader) Error!u32 {        if (4 > self.bytes_value.len - self.cursor) return error.InvalidRemoteConfig;        const value = std.mem.readInt(u32, self.bytes_value[self.cursor..][0..4], .big);        self.cursor += 4;        return value;    }};const BranchUpstreamConfigReader = struct {    bytes_value: []const u8,    cursor: usize = 0,    fn init(bytes: []const u8) BranchUpstreamConfigReader {        return .{ .bytes_value = bytes };    }    fn finish(self: *const BranchUpstreamConfigReader) Error!void {        if (self.cursor != self.bytes_value.len) return error.InvalidBranchUpstreamConfig;    }    fn readBytes(self: *BranchUpstreamConfigReader) Error![]const u8 {        const len = try self.readCount();        if (len > self.bytes_value.len - self.cursor) return error.InvalidBranchUpstreamConfig;        const value = self.bytes_value[self.cursor..][0..len];        self.cursor += len;        return value;    }    fn readCount(self: *BranchUpstreamConfigReader) Error!usize {        return @intCast(try self.readU32());    }    fn readU32(self: *BranchUpstreamConfigReader) Error!u32 {        if (4 > self.bytes_value.len - self.cursor) return error.InvalidBranchUpstreamConfig;        const value = std.mem.readInt(u32, self.bytes_value[self.cursor..][0..4], .big);        self.cursor += 4;        return value;    }};const AdvertisementReader = struct {    bytes_value: []const u8,    cursor: usize = 0,    fn init(bytes: []const u8) AdvertisementReader {        return .{ .bytes_value = bytes };    }    fn finish(self: *const AdvertisementReader) Error!void {        if (self.cursor != self.bytes_value.len) return error.InvalidAdvertisement;    }    fn hash(self: *AdvertisementReader) Error!version.Hash {        if (version.hash_bytes > self.bytes_value.len - self.cursor) return error.InvalidAdvertisement;        const value = self.bytes_value[self.cursor..][0..version.hash_bytes].*;        self.cursor += version.hash_bytes;        return value;    }    fn readOwnedBytes(self: *AdvertisementReader, allocator: Allocator) Error![]u8 {        return try allocator.dupe(u8, try self.readBytes());    }    fn readBytes(self: *AdvertisementReader) Error![]const u8 {        const len = try self.readCount();        if (len > self.bytes_value.len - self.cursor) return error.InvalidAdvertisement;        const value = self.bytes_value[self.cursor..][0..len];        self.cursor += len;        return value;    }    fn readCount(self: *AdvertisementReader) Error!usize {        return @intCast(try self.readU32());    }    fn readU32(self: *AdvertisementReader) Error!u32 {        if (4 > self.bytes_value.len - self.cursor) return error.InvalidAdvertisement;        const value = std.mem.readInt(u32, self.bytes_value[self.cursor..][0..4], .big);        self.cursor += 4;        return value;    }};const FetchRequestReader = struct {    bytes_value: []const u8,    cursor: usize = 0,    fn init(bytes: []const u8) FetchRequestReader {        return .{ .bytes_value = bytes };    }    fn finish(self: *const FetchRequestReader) Error!void {        if (self.cursor != self.bytes_value.len) return error.InvalidFetchRequest;    }    fn hash(self: *FetchRequestReader) Error!version.Hash {        if (version.hash_bytes > self.bytes_value.len - self.cursor) return error.InvalidFetchRequest;        const value = self.bytes_value[self.cursor..][0..version.hash_bytes].*;        self.cursor += version.hash_bytes;        return value;    }    fn readOwnedBytes(self: *FetchRequestReader, allocator: Allocator) Error![]u8 {        return try allocator.dupe(u8, try self.readBytes());    }    fn readBytes(self: *FetchRequestReader) Error![]const u8 {        const len = try self.readCount();        if (len > self.bytes_value.len - self.cursor) return error.InvalidFetchRequest;        const value = self.bytes_value[self.cursor..][0..len];        self.cursor += len;        return value;    }    fn readCount(self: *FetchRequestReader) Error!usize {        return @intCast(try self.readU32());    }    fn readU32(self: *FetchRequestReader) Error!u32 {        if (4 > self.bytes_value.len - self.cursor) return error.InvalidFetchRequest;        const value = std.mem.readInt(u32, self.bytes_value[self.cursor..][0..4], .big);        self.cursor += 4;        return value;    }};fn validRemoteName(name: []const u8) bool {    if (name.len == 0) return false;    return std.mem.indexOfAny(u8, name, " \t\n\r./\\!@#$%^&*(){}[],.<>'\"?=+|") == null;}fn validateRemoteEntries(remotes: []const RemoteConfigEntry) Error!void {    for (remotes, 0..) |remote, index| {        if (!validRemoteName(remote.name)) return error.InvalidRemoteName;        if (remote.history_path.len == 0) return error.InvalidRemoteConfig;        if (remote.default_branch) |branch_name| {            if (branch_name.len == 0) return error.InvalidRemoteConfig;        }        for (remotes[0..index]) |previous| {            if (std.mem.eql(u8, previous.name, remote.name)) return error.RemoteExists;        }    }}fn validateBranchUpstreamEntries(upstreams: []const BranchUpstreamEntry) Error!void {    for (upstreams, 0..) |upstream, index| {        if (upstream.branch.len == 0 or upstream.remote_branch.len == 0) return error.InvalidBranchUpstreamConfig;        if (!validRemoteName(upstream.remote)) return error.InvalidRemoteName;        for (upstreams[0..index]) |previous| {            if (std.mem.eql(u8, previous.branch, upstream.branch)) return error.BranchUpstreamExists;        }    }}fn appendHash(allocator: Allocator, target: *std.ArrayList(u8), hash: version.Hash) Allocator.Error!void {    try target.appendSlice(allocator, hash[0..]);}fn appendU8(allocator: Allocator, target: *std.ArrayList(u8), value: u8) Allocator.Error!void {    try target.append(allocator, value);}fn appendBytes(allocator: Allocator, target: *std.ArrayList(u8), bytes: []const u8) Error!void {    if (bytes.len > std.math.maxInt(u32)) return error.InvalidPack;    try appendU32(allocator, target, @intCast(bytes.len));    try target.appendSlice(allocator, bytes);}fn appendCount(allocator: Allocator, target: *std.ArrayList(u8), count: usize) Error!void {    if (count > std.math.maxInt(u32)) return error.InvalidPack;    try appendU32(allocator, target, @intCast(count));}fn appendU32(allocator: Allocator, target: *std.ArrayList(u8), value: u32) Allocator.Error!void {    var encoded: [4]u8 = undefined;    std.mem.writeInt(u32, encoded[0..], value, .big);    try target.appendSlice(allocator, encoded[0..]);}fn canFastForwardWithPack(allocator: Allocator, history: *const history_mod.History, pack: *const Pack, current: version.Hash, target: version.Hash) Error!bool {    const history_entries = try history.commitEntries(allocator);    defer allocator.free(history_entries);    var entries: std.ArrayList(branch.CommitEntry) = .empty;    defer entries.deinit(allocator);    try entries.appendSlice(allocator, history_entries);    for (pack.commits) |commit| {        try entries.append(allocator, .{            .hash = commit.hash,            .parents = commit.parents,        });    }    return try branch.canFastForward(allocator, entries.items, current, target);}fn containsRef(refs: []const RefObject, name: []const u8) bool {    for (refs) |ref_value| {        if (std.mem.eql(u8, ref_value.name, name)) return true;    }    return false;}fn containsHash(hashes: []const version.Hash, hash: version.Hash) bool {    for (hashes) |value| {        if (version.same(value, hash)) return true;    }    return false;}fn deinitConflictValue(allocator: Allocator, value: ?version.ConflictValue) void {    if (value) |conflict_value| switch (conflict_value) {        .row => |bytes| allocator.free(bytes),        .relation => {},    };}fn conflictRowValue(value: ?version.ConflictValue) ?[]const u8 {    const conflict_value = value orelse return null;    return switch (conflict_value) {        .row => |bytes| bytes,        .relation => unreachable,    };}fn conflictRelationValue(value: ?version.ConflictValue) ?version.Hash {    const conflict_value = value orelse return null;    return switch (conflict_value) {        .row => unreachable,        .relation => |hash| hash,    };}fn testingHeader(sequence: u32) wal.Header {    return .{        .sequence = sequence,        .salt = .{            .first = 0x7379_6e63,            .second = 0x6869_7374 + sequence,        },    };}fn executeStatement(connection: *connection_mod.Connection, sql: []const u8) !void {    var result = try connection.execute(std.testing.allocator, sql, .{ .durability = .buffered });    defer result.deinit(std.testing.allocator);}fn relationRows(history: *const history_mod.History, commit_hash: version.Hash, name: []const u8) ![]version.RelationRow {    const commit = try history.commitValue(commit_hash);    var value = try history.databaseValue(std.testing.allocator, commit.root);    defer value.deinit();    const relation = value.findRelation(name) orelse return error.RelationRootNotFound;    return try version.cloneRelationRows(std.testing.allocator, relation.rows);}fn createStore(tmp: std.testing.TmpDir, database_name: []const u8, wal_name: []const u8, history_name: []const u8, sequence: u32) !struct {    database: file.Database,    history: history_mod.History,} {    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = database_name, .wal = wal_name },        .header = testingHeader(sequence),    });    errdefer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = history_name, .recovery = .reject });    errdefer history.deinit();    return .{        .database = database,        .history = history,    };}test "sync clone copies reachable history and refs" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var source_store = try createStore(tmp, "clone-source.db", "clone-source.wal", "clone-source.history", 1);    defer source_store.history.deinit();    defer source_store.database.deinit();    var source = try connection_mod.Connection.create(std.testing.allocator, &source_store.database, &source_store.history, .{});    defer source.deinit();    try executeStatement(&source, "CREATE TABLE items (name)");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try source.stage();    const main_commit = try source.commit(&source_store.history);    _ = try source.createBranch(&source_store.history, "side");    try source.checkoutBranch(std.testing.allocator, &source_store.history, "side");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (2, 'side')");    try source.stage();    const side_commit = try source.commit(&source_store.history);    var target_store = try createStore(tmp, "clone-target.db", "clone-target.wal", "clone-target.history", 2);    defer target_store.history.deinit();    defer target_store.database.deinit();    const stats = try cloneAll(std.testing.allocator, &source_store.history, &target_store.history);    try std.testing.expectEqual(@as(usize, 2), stats.refs);    try std.testing.expect(version.same(main_commit, (try target_store.history.ref("main")).?.target));    try std.testing.expect(version.same(side_commit, (try target_store.history.ref("side")).?.target));    const main_rows = try relationRows(&target_store.history, main_commit, "items");    defer version.freeRelationRows(std.testing.allocator, main_rows);    try std.testing.expectEqual(@as(usize, 1), main_rows.len);    const side_rows = try relationRows(&target_store.history, side_commit, "items");    defer version.freeRelationRows(std.testing.allocator, side_rows);    try std.testing.expectEqual(@as(usize, 2), side_rows.len);}test "sync history planning reports relation and missing pack counts" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var local_store = try createStore(tmp, "plan-local.db", "plan-local.wal", "plan-local.history", 11);    defer local_store.history.deinit();    defer local_store.database.deinit();    var remote_store = try createStore(tmp, "plan-remote.db", "plan-remote.wal", "plan-remote.history", 12);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var local = try connection_mod.Connection.create(std.testing.allocator, &local_store.database, &local_store.history, .{});    defer local.deinit();    try executeStatement(&local, "CREATE TABLE items (name)");    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try local.stage();    _ = try local.commit(&local_store.history);    _ = try cloneAll(std.testing.allocator, &local_store.history, &remote_store.history);    const current = try historyRelation(std.testing.allocator, &local_store.history, &remote_store.history, "main");    try std.testing.expect(current.upToDate());    try std.testing.expectEqual(@as(usize, 0), current.ahead);    try std.testing.expectEqual(@as(usize, 0), current.behind);    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (2, 'tip')");    try local.stage();    const tip = try local.commit(&local_store.history);    const related = try historyRelation(std.testing.allocator, &local_store.history, &remote_store.history, "main");    try std.testing.expect(!related.upToDate());    try std.testing.expect(!related.diverged());    try std.testing.expectEqual(@as(usize, 1), related.ahead);    try std.testing.expectEqual(@as(usize, 0), related.behind);    const push_plan = try planHistoryTransfer(std.testing.allocator, &local_store.history, &remote_store.history, "main", related, .push);    try std.testing.expectEqual(@as(usize, 1), push_plan.commits);    try std.testing.expect(push_plan.records != 0);    const pull_plan = try planHistoryTransfer(std.testing.allocator, &local_store.history, &remote_store.history, "main", related, .pull);    try std.testing.expectEqual(@as(usize, 0), pull_plan.commits);    try std.testing.expectEqual(@as(usize, 0), pull_plan.records);    var names = [_][]const u8{"main"};    var pack = try exportMissingRefNames(std.testing.allocator, &local_store.history, &remote_store.history, names[0..]);    defer pack.deinit();    try std.testing.expect(version.same(tip, packRefTarget(&pack, "main").?));    const missing = try missingPackCounts(&remote_store.history, &pack);    try std.testing.expectEqual(push_plan.commits, missing.commits);    try std.testing.expectEqual(push_plan.records, missing.records);    var fresh_store = try createStore(tmp, "plan-fresh.db", "plan-fresh.wal", "plan-fresh.history", 13);    defer fresh_store.history.deinit();    defer fresh_store.database.deinit();    const adopt_plan = try planHistoryAdopt(std.testing.allocator, &fresh_store.history, &local_store.history, "main");    try std.testing.expect(adopt_plan.commits >= 2);    try std.testing.expect(adopt_plan.records >= push_plan.records);}test "sync record packs stay within history size" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = try createStore(tmp, "bound.db", "bound.wal", "bound.history", 70);    defer store.history.deinit();    defer store.database.deinit();    var connection = try connection_mod.Connection.create(std.testing.allocator, &store.database, &store.history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try connection.stage();    _ = try connection.commit(&store.history);    var sequence: usize = 0;    var statement_buffer: [128]u8 = undefined;    while (sequence < 24) : (sequence += 1) {        const statement = try std.fmt.bufPrint(statement_buffer[0..], "INSERT INTO items (rowid, name) VALUES ({d}, 'value-{d}')", .{ sequence + 1, sequence });        try executeStatement(&connection, statement);        try connection.stage();        _ = try connection.commit(&store.history);    }    const bytes = try exportBytesAll(std.testing.allocator, &store.history);    defer std.testing.allocator.free(bytes);    try std.testing.expect(bytes.len <= store.history.len());}test "sync pack import rejects tampered row chunks" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = try createStore(tmp, "tamper.db", "tamper.wal", "tamper.history", 71);    defer store.history.deinit();    defer store.database.deinit();    var connection = try connection_mod.Connection.create(std.testing.allocator, &store.database, &store.history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (1, 'payload')");    try connection.stage();    _ = try connection.commit(&store.history);    var pack = try exportAll(std.testing.allocator, &store.history);    defer pack.deinit();    var tampered = false;    for (pack.frames) |frame| {        if (frame.kind != .row_chunk) continue;        frame.payload[frame.payload.len - 1] +%= 1;        tampered = true;        break;    }    try std.testing.expect(tampered);    const bytes = try encodePack(std.testing.allocator, &pack);    defer std.testing.allocator.free(bytes);    var target_store = try createStore(tmp, "tamper-target.db", "tamper-target.wal", "tamper-target.history", 72);    defer target_store.history.deinit();    defer target_store.database.deinit();    try std.testing.expectError(error.InvalidHistory, importBytes(std.testing.allocator, &target_store.history, bytes));}test "sync record packs re-export from imported stores" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var source_store = try createStore(tmp, "relay-source.db", "relay-source.wal", "relay-source.history", 73);    defer source_store.history.deinit();    defer source_store.database.deinit();    var source = try connection_mod.Connection.create(std.testing.allocator, &source_store.database, &source_store.history, .{});    defer source.deinit();    try executeStatement(&source, "CREATE TABLE items (name)");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try source.stage();    _ = try source.commit(&source_store.history);    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (2, 'tip')");    try source.stage();    const tip_commit = try source.commit(&source_store.history);    var middle_store = try createStore(tmp, "relay-middle.db", "relay-middle.wal", "relay-middle.history", 74);    defer middle_store.history.deinit();    defer middle_store.database.deinit();    const source_bytes = try exportBytesAll(std.testing.allocator, &source_store.history);    defer std.testing.allocator.free(source_bytes);    _ = try importBytes(std.testing.allocator, &middle_store.history, source_bytes);    var final_store = try createStore(tmp, "relay-final.db", "relay-final.wal", "relay-final.history", 75);    defer final_store.history.deinit();    defer final_store.database.deinit();    const middle_bytes = try exportBytesAll(std.testing.allocator, &middle_store.history);    defer std.testing.allocator.free(middle_bytes);    _ = try importBytes(std.testing.allocator, &final_store.history, middle_bytes);    try std.testing.expect(version.same(tip_commit, (try final_store.history.ref("main")).?.target));    const rows = try relationRows(&final_store.history, tip_commit, "items");    defer version.freeRelationRows(std.testing.allocator, rows);    try std.testing.expectEqual(@as(usize, 2), rows.len);}test "sync fetch writes remote tracking refs" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "fetch-remote.db", "fetch-remote.wal", "fetch-remote.history", 10);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    try remote_store.history.putRef(.{        .name = "refs/tags/v1",        .target = remote_commit,    });    try remote_store.history.putRef(.{        .name = "side",        .target = remote_commit,    });    var local_store = try createStore(tmp, "fetch-local.db", "fetch-local.wal", "fetch-local.history", 11);    defer local_store.history.deinit();    defer local_store.database.deinit();    const stats = try fetch(std.testing.allocator, &local_store.history, &remote_store.history, .{ .remote = "upstream" });    try std.testing.expectEqual(@as(usize, 3), stats.refs);    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/upstream/main")).?.target));    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/upstream/side")).?.target));    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/tags/v1")).?.target));    try std.testing.expect((try local_store.history.ref("refs/remotes/upstream/refs/tags/v1")) == null);    const rows = try relationRows(&local_store.history, remote_commit, "items");    defer version.freeRelationRows(std.testing.allocator, rows);    try std.testing.expectEqual(@as(usize, 1), rows.len);    try remote_store.history.deleteRef("side");    const stale_stats = try fetch(std.testing.allocator, &local_store.history, &remote_store.history, .{ .remote = "upstream" });    try std.testing.expectEqual(@as(usize, 2), stale_stats.refs);    try std.testing.expect((try local_store.history.ref("refs/remotes/upstream/side")) != null);    const prune_stats = try fetch(std.testing.allocator, &local_store.history, &remote_store.history, .{ .remote = "upstream", .prune = true });    try std.testing.expectEqual(@as(usize, 3), prune_stats.refs);    try std.testing.expect((try local_store.history.ref("refs/remotes/upstream/side")) == null);    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/upstream/main")).?.target));    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/tags/v1")).?.target));}test "sync fetch and push transfer only missing objects" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "missing-remote.db", "missing-remote.wal", "missing-remote.history", 11);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try remote.stage();    _ = try remote.commit(&remote_store.history);    var local_store = try createStore(tmp, "missing-local.db", "missing-local.wal", "missing-local.history", 12);    defer local_store.history.deinit();    defer local_store.database.deinit();    _ = try cloneAll(std.testing.allocator, &remote_store.history, &local_store.history);    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (2, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    const fetch_stats = try fetch(std.testing.allocator, &local_store.history, &remote_store.history, .{ .remote = "origin" });    try std.testing.expectEqual(@as(usize, 1), fetch_stats.refs);    try std.testing.expectEqual(@as(usize, 1), fetch_stats.commits);    try std.testing.expect(fetch_stats.records != 0);    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/origin/main")).?.target));    const fetch_again = try fetch(std.testing.allocator, &local_store.history, &remote_store.history, .{ .remote = "origin" });    try std.testing.expectEqual(@as(usize, 1), fetch_again.refs);    try std.testing.expectEqual(@as(usize, 0), fetch_again.commits);    try std.testing.expectEqual(@as(usize, 0), fetch_again.records);    try local_store.history.fastForwardBranch(std.testing.allocator, "main", remote_commit);    var local = try connection_mod.Connection.open(std.testing.allocator, &local_store.database, &local_store.history, .{});    defer local.deinit();    try local.checkoutBranch(std.testing.allocator, &local_store.history, "main");    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (3, 'local')");    try local.stage();    const local_commit = try local.commit(&local_store.history);    const push_stats = try pushFastForward(std.testing.allocator, &local_store.history, &remote_store.history, "main");    try std.testing.expectEqual(@as(usize, 1), push_stats.refs);    try std.testing.expectEqual(@as(usize, 1), push_stats.commits);    try std.testing.expect(push_stats.records != 0);    try std.testing.expect(version.same(local_commit, (try remote_store.history.ref("main")).?.target));}test "sync negotiated fetch advertises refs and returns requested missing pack" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "negotiated-remote.db", "negotiated-remote.wal", "negotiated-remote.history", 12);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try remote.stage();    _ = try remote.commit(&remote_store.history);    var local_store = try createStore(tmp, "negotiated-local.db", "negotiated-local.wal", "negotiated-local.history", 13);    defer local_store.history.deinit();    defer local_store.database.deinit();    _ = try cloneAll(std.testing.allocator, &remote_store.history, &local_store.history);    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (2, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    var advertisement = try advertiseRefs(std.testing.allocator, &remote_store.history);    defer advertisement.deinit();    const advertisement_bytes = try encodeAdvertisement(std.testing.allocator, &advertisement);    defer std.testing.allocator.free(advertisement_bytes);    var decoded_advertisement = try decodeAdvertisement(std.testing.allocator, advertisement_bytes);    defer decoded_advertisement.deinit();    try std.testing.expect(version.same(remote_commit, decoded_advertisement.ref("main").?.target));    const wants = [_][]const u8{"main"};    var request = try fetchRequestFromHistory(std.testing.allocator, &local_store.history, wants[0..]);    defer request.deinit();    const request_bytes = try encodeFetchRequest(std.testing.allocator, &request);    defer std.testing.allocator.free(request_bytes);    var decoded_request = try decodeFetchRequest(std.testing.allocator, request_bytes);    defer decoded_request.deinit();    var pack = try exportForFetchRequest(std.testing.allocator, &remote_store.history, &decoded_request);    defer pack.deinit();    try std.testing.expectEqual(@as(usize, 1), pack.refs.len);    try std.testing.expectEqual(@as(usize, 1), pack.commits.len);    try std.testing.expect(pack.frames.len != 0);    const pack_bytes = try exportBytesForFetchRequest(std.testing.allocator, &remote_store.history, &decoded_request);    defer std.testing.allocator.free(pack_bytes);    _ = try fetchBytes(std.testing.allocator, &local_store.history, pack_bytes, .{ .remote = "origin" });    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/origin/main")).?.target));    const broken_advertisement = try std.testing.allocator.dupe(u8, advertisement_bytes);    defer std.testing.allocator.free(broken_advertisement);    broken_advertisement[0] ^= 0xff;    try std.testing.expectError(error.InvalidAdvertisement, decodeAdvertisement(std.testing.allocator, broken_advertisement));    const broken_request = try std.testing.allocator.dupe(u8, request_bytes);    defer std.testing.allocator.free(broken_request);    broken_request[0] ^= 0xff;    try std.testing.expectError(error.InvalidFetchRequest, decodeFetchRequest(std.testing.allocator, broken_request));}test "sync byte packs import and fetch remote refs" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "byte-remote.db", "byte-remote.wal", "byte-remote.history", 12);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    const bytes = try exportBytesAll(std.testing.allocator, &remote_store.history);    defer std.testing.allocator.free(bytes);    var imported_store = try createStore(tmp, "byte-imported.db", "byte-imported.wal", "byte-imported.history", 13);    defer imported_store.history.deinit();    defer imported_store.database.deinit();    _ = try importBytes(std.testing.allocator, &imported_store.history, bytes);    try std.testing.expect(version.same(remote_commit, (try imported_store.history.ref("main")).?.target));    const imported_rows = try relationRows(&imported_store.history, remote_commit, "items");    defer version.freeRelationRows(std.testing.allocator, imported_rows);    try std.testing.expectEqual(@as(usize, 1), imported_rows.len);    var fetched_store = try createStore(tmp, "byte-fetched.db", "byte-fetched.wal", "byte-fetched.history", 14);    defer fetched_store.history.deinit();    defer fetched_store.database.deinit();    _ = try fetchBytes(std.testing.allocator, &fetched_store.history, bytes, .{ .remote = "origin" });    try std.testing.expect(version.same(remote_commit, (try fetched_store.history.ref("refs/remotes/origin/main")).?.target));    const broken = try std.testing.allocator.dupe(u8, bytes);    defer std.testing.allocator.free(broken);    broken[0] ^= 0xff;    try std.testing.expectError(error.InvalidPack, importBytes(std.testing.allocator, &fetched_store.history, broken));}test "sync remote config stores named file remotes" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var origin = try RemoteConfigEntry.init(std.testing.allocator, "origin", "origin.history", null);    defer origin.deinit(std.testing.allocator);    var backup = try RemoteConfigEntry.initWithDefaultBranch(std.testing.allocator, "backup", "backup.history", "backup.lock", "main");    defer backup.deinit(std.testing.allocator);    const entries = [_]RemoteConfigEntry{ origin, backup };    var config = try RemoteConfig.init(std.testing.allocator, entries[0..]);    defer config.deinit();    try writeRemoteConfig(std.testing.allocator, tmp.dir, .{ .path = "remotes.bin" }, &config);    var read = try readRemoteConfig(std.testing.allocator, tmp.dir, .{ .path = "remotes.bin" });    defer read.deinit();    try std.testing.expectEqual(@as(usize, 2), read.remotes.len);    try std.testing.expectEqualStrings("origin.history", read.find("origin").?.history_path);    try std.testing.expect(read.find("origin").?.default_branch == null);    try std.testing.expect(read.find("missing") == null);    const remote = try read.fileRemote(testing_io, tmp.dir, "backup");    try std.testing.expectEqualStrings("backup.history", remote.path);    try std.testing.expectEqualStrings("backup.lock", remote.lock_path.?);    try std.testing.expectEqualStrings("main", read.find("backup").?.default_branch.?);    try std.testing.expectError(error.RemoteNotFound, read.fileRemote(testing_io, tmp.dir, "missing"));    const duplicate = [_]RemoteConfigEntry{ origin, origin };    try std.testing.expectError(error.RemoteExists, RemoteConfig.init(std.testing.allocator, duplicate[0..]));    try std.testing.expectError(error.InvalidRemoteName, RemoteConfigEntry.init(std.testing.allocator, "bad/name", "bad.history", null));    try std.testing.expectError(error.InvalidRemoteConfig, RemoteConfigEntry.initWithDefaultBranch(std.testing.allocator, "bad-branch", "bad.history", null, ""));    var empty = try readRemoteConfig(std.testing.allocator, tmp.dir, .{ .path = "missing-remotes.bin" });    defer empty.deinit();    try std.testing.expectEqual(@as(usize, 0), empty.remotes.len);    var legacy_bytes: std.ArrayList(u8) = .empty;    defer legacy_bytes.deinit(std.testing.allocator);    try appendU32(std.testing.allocator, &legacy_bytes, remote_config_magic);    try appendU32(std.testing.allocator, &legacy_bytes, 1);    try appendCount(std.testing.allocator, &legacy_bytes, 1);    try appendBytes(std.testing.allocator, &legacy_bytes, "legacy");    try appendBytes(std.testing.allocator, &legacy_bytes, "legacy.history");    try appendU8(std.testing.allocator, &legacy_bytes, 0);    var legacy = try decodeRemoteConfig(std.testing.allocator, legacy_bytes.items);    defer legacy.deinit();    try std.testing.expectEqualStrings("legacy.history", legacy.find("legacy").?.history_path);    try std.testing.expect(legacy.find("legacy").?.default_branch == null);}test "sync branch upstream config stores tracking metadata" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var main_origin = try BranchUpstreamEntry.init(std.testing.allocator, "main", "origin", "trunk");    defer main_origin.deinit(std.testing.allocator);    var feature_backup = try BranchUpstreamEntry.init(std.testing.allocator, "feature", "backup", "feature");    defer feature_backup.deinit(std.testing.allocator);    const entries = [_]BranchUpstreamEntry{ main_origin, feature_backup };    var config = try BranchUpstreamConfig.init(std.testing.allocator, entries[0..]);    defer config.deinit();    try writeBranchUpstreamConfig(std.testing.allocator, tmp.dir, .{ .path = "upstreams.bin" }, &config);    var read = try readBranchUpstreamConfig(std.testing.allocator, tmp.dir, .{ .path = "upstreams.bin" });    defer read.deinit();    try std.testing.expectEqual(@as(usize, 2), read.upstreams.len);    try std.testing.expectEqualStrings("origin", read.find("main").?.remote);    try std.testing.expectEqualStrings("trunk", read.find("main").?.remote_branch);    try std.testing.expectEqualStrings("backup", read.find("feature").?.remote);    try std.testing.expect(read.find("missing") == null);    var duplicate = try BranchUpstreamEntry.init(std.testing.allocator, "main", "backup", "main");    defer duplicate.deinit(std.testing.allocator);    const duplicate_entries = [_]BranchUpstreamEntry{ main_origin, duplicate };    try std.testing.expectError(error.BranchUpstreamExists, BranchUpstreamConfig.init(std.testing.allocator, duplicate_entries[0..]));    try std.testing.expectError(error.InvalidRemoteName, BranchUpstreamEntry.init(std.testing.allocator, "main", "bad/name", "main"));    try std.testing.expectError(error.InvalidBranchUpstreamConfig, BranchUpstreamEntry.init(std.testing.allocator, "", "origin", "main"));    try std.testing.expectError(error.InvalidBranchUpstreamConfig, BranchUpstreamEntry.init(std.testing.allocator, "main", "origin", ""));    var empty = try readBranchUpstreamConfig(std.testing.allocator, tmp.dir, .{ .path = "missing-upstreams.bin" });    defer empty.deinit();    try std.testing.expectEqual(@as(usize, 0), empty.upstreams.len);    const bytes = try encodeBranchUpstreamConfig(std.testing.allocator, &config);    defer std.testing.allocator.free(bytes);    const broken = try std.testing.allocator.dupe(u8, bytes);    defer std.testing.allocator.free(broken);    broken[0] ^= 0xff;    try std.testing.expectError(error.InvalidBranchUpstreamConfig, decodeBranchUpstreamConfig(std.testing.allocator, broken));}test "sync file remote lock rejects concurrent access" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    const remote = FileRemote{        .dir = tmp.dir,        .path = "locked.history",    };    var first = try lockFileRemote(std.testing.allocator, remote);    try std.testing.expectError(error.RemoteBusy, lockFileRemote(std.testing.allocator, remote));    first.release();    var second = try lockFileRemote(std.testing.allocator, remote);    second.release();}test "sync file transfers require an existing remote history" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    const remote = FileRemote{        .dir = tmp.dir,        .path = "absent.history",    };    var target = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "transfer-target.history", .recovery = .reject });    defer target.deinit();    try std.testing.expectError(error.HistoryNotFound, cloneFile(std.testing.allocator, remote, &target));    try std.testing.expectError(error.HistoryNotFound, fetchFile(std.testing.allocator, &target, remote, .{}));    try std.testing.expectError(error.HistoryNotFound, pullFileFastForward(std.testing.allocator, &target, remote, "main"));    try std.testing.expectError(error.HistoryNotFound, deleteFileRemoteRef(std.testing.allocator, remote, "main"));    try std.testing.expectError(error.FileNotFound, tmp.dir.readFileAlloc(testing_io, "absent.history", std.testing.allocator, .unlimited));}test "sync file remotes reject truncated history without repairing it" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var clean_length: usize = 0;    {        var remote = try history_mod.History.open(std.testing.allocator, tmp.dir, .{            .path = "truncated-remote.history",            .recovery = .reject,        });        defer remote.deinit();        try remote.putCommit(version.Commit.init(version.emptyHash("truncated-remote"), &.{}));        clean_length = remote.len();    }    const corrupted_length = clean_length + "corrupt".len;    {        var remote_file = try tmp.dir.createFile(testing_io, "truncated-remote.history", .{ .read = true, .truncate = false });        try remote_file.writePositionalAll(testing_io, "corrupt", clean_length);        try remote_file.setLength(testing_io, corrupted_length);        remote_file.close(testing_io);    }    var target = try history_mod.History.open(std.testing.allocator, tmp.dir, .{        .path = "truncated-target.history",        .recovery = .reject,    });    defer target.deinit();    try std.testing.expectError(error.TruncatedHistory, cloneFile(std.testing.allocator, .{        .dir = tmp.dir,        .path = "truncated-remote.history",    }, &target));    try std.testing.expectEqual(corrupted_length, @as(usize, @intCast((try tmp.dir.statFile(testing_io, "truncated-remote.history", .{})).size)));}test "sync clone copies committed conflict roots" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var source_store = try createStore(tmp, "conflict-source.db", "conflict-source.wal", "conflict-source.history", 15);    defer source_store.history.deinit();    defer source_store.database.deinit();    var source = try connection_mod.Connection.create(std.testing.allocator, &source_store.database, &source_store.history, .{});    defer source.deinit();    try executeStatement(&source, "CREATE TABLE items (name)");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try source.stage();    _ = try source.commit(&source_store.history);    _ = try source.createBranch(&source_store.history, "side");    try source.checkoutBranch(std.testing.allocator, &source_store.history, "side");    try executeStatement(&source, "DELETE FROM items WHERE rowid = 1");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (1, 'side')");    try source.stage();    const side_commit = try source.commit(&source_store.history);    try source.checkoutBranch(std.testing.allocator, &source_store.history, "main");    try executeStatement(&source, "DELETE FROM items WHERE rowid = 1");    try executeStatement(&source, "INSERT INTO items (rowid, name) VALUES (1, 'main')");    try source.stage();    _ = try source.commit(&source_store.history);    var merged = try source.mergeBranch(std.testing.allocator, &source_store.history, "side", .{});    defer merged.deinit();    try std.testing.expect(merged.hasConflicts());    const conflict_root = merged.conflict_root.hash;    const conflict_hash = merged.artifacts[0].hash;    try source.stage();    const merge_commit = try source.mergeCommit(&source_store.history, side_commit);    var target_store = try createStore(tmp, "conflict-target.db", "conflict-target.wal", "conflict-target.history", 16);    defer target_store.history.deinit();    defer target_store.database.deinit();    _ = try cloneAll(std.testing.allocator, &source_store.history, &target_store.history);    try std.testing.expect(version.same(merge_commit, (try target_store.history.ref("main")).?.target));    const commit = try target_store.history.commitValue(merge_commit);    var clone = try connection_mod.Connection.open(std.testing.allocator, &target_store.database, &target_store.history, .{});    defer clone.deinit();    try clone.checkoutBranch(std.testing.allocator, &target_store.history, "main");    try std.testing.expect(version.same(commit.root, (try clone.workingRoot())));    var artifacts = try clone.conflictArtifacts(std.testing.allocator, &target_store.history);    defer artifacts.deinit();    try std.testing.expect(version.same(conflict_root, artifacts.root.hash));    try std.testing.expectEqual(@as(usize, 1), artifacts.artifacts.len);    try std.testing.expect(version.same(conflict_hash, artifacts.artifacts[0].hash));}test "sync pack import rejects a conflict root without its artifact" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var source = try history_mod.History.open(std.testing.allocator, tmp.dir, .{        .path = "missing-conflict-source.history",        .recovery = .reject,    });    defer source.deinit();    const artifact = version.ConflictArtifact.init("items", 1, "base", "ours", "theirs");    try source.putConflict(artifact);    const root = try source.putConflictRoot(&.{artifact.entry()});    var payload: std.ArrayList(u8) = .empty;    defer payload.deinit(std.testing.allocator);    try source.appendPackRecordPayload(std.testing.allocator, &payload, .conflict_root, root.hash);    var frames = [_]PackFrame{.{ .kind = .conflict_root, .payload = payload.items }};    const incomplete = Pack{        .allocator = std.testing.allocator,        .refs = &.{},        .commits = &.{},        .frames = frames[0..],    };    var target = try history_mod.History.open(std.testing.allocator, tmp.dir, .{        .path = "missing-conflict-target.history",        .recovery = .reject,    });    defer target.deinit();    try std.testing.expectError(error.InvalidHistory, importObjects(&target, &incomplete));}test "sync push fast forwards remote refs and rejects divergence" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "push-remote.db", "push-remote.wal", "push-remote.history", 20);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try remote.stage();    _ = try remote.commit(&remote_store.history);    var local_store = try createStore(tmp, "push-local.db", "push-local.wal", "push-local.history", 21);    defer local_store.history.deinit();    defer local_store.database.deinit();    _ = try cloneAll(std.testing.allocator, &remote_store.history, &local_store.history);    var local = try connection_mod.Connection.open(std.testing.allocator, &local_store.database, &local_store.history, .{});    defer local.deinit();    try local.checkoutBranch(std.testing.allocator, &local_store.history, "main");    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (2, 'local')");    try local.stage();    const local_commit = try local.commit(&local_store.history);    _ = try pushFastForward(std.testing.allocator, &local_store.history, &remote_store.history, "main");    try std.testing.expect(version.same(local_commit, (try remote_store.history.ref("main")).?.target));    _ = try pushFastForwardTo(std.testing.allocator, &local_store.history, &remote_store.history, "main", "trunk");    try std.testing.expect(version.same(local_commit, (try remote_store.history.ref("trunk")).?.target));    try local_store.history.putRef(.{        .name = "refs/tags/v1",        .target = local_commit,    });    _ = try pushFastForward(std.testing.allocator, &local_store.history, &remote_store.history, "refs/tags/v1");    try std.testing.expect(version.same(local_commit, (try remote_store.history.ref("refs/tags/v1")).?.target));    const pushed_rows = try relationRows(&remote_store.history, local_commit, "items");    defer version.freeRelationRows(std.testing.allocator, pushed_rows);    try std.testing.expectEqual(@as(usize, 2), pushed_rows.len);    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (3, 'local-again')");    try local.stage();    const local_divergent = try local.commit(&local_store.history);    try remote.checkoutBranch(std.testing.allocator, &remote_store.history, "main");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (4, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    try std.testing.expectError(error.NonFastForward, pushFastForward(std.testing.allocator, &local_store.history, &remote_store.history, "main"));    try std.testing.expect(version.same(remote_commit, (try remote_store.history.ref("main")).?.target));    try std.testing.expectError(error.CommitNotFound, remote_store.history.commitValue(local_divergent));}test "sync file remotes clone fetch push and pull refs" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var remote_store = try createStore(tmp, "file-remote.db", "file-remote.wal", "file-remote.history", 30);    defer remote_store.history.deinit();    defer remote_store.database.deinit();    var remote = try connection_mod.Connection.create(std.testing.allocator, &remote_store.database, &remote_store.history, .{});    defer remote.deinit();    try executeStatement(&remote, "CREATE TABLE items (name)");    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try remote.stage();    const base_commit = try remote.commit(&remote_store.history);    var local_store = try createStore(tmp, "file-local.db", "file-local.wal", "file-local.history", 31);    defer local_store.history.deinit();    defer local_store.database.deinit();    const remote_file = FileRemote{        .dir = tmp.dir,        .path = "file-remote.history",    };    _ = try cloneFile(std.testing.allocator, remote_file, &local_store.history);    try std.testing.expect(version.same(base_commit, (try local_store.history.ref("main")).?.target));    try executeStatement(&remote, "INSERT INTO items (rowid, name) VALUES (2, 'remote')");    try remote.stage();    const remote_commit = try remote.commit(&remote_store.history);    _ = try fetchFile(std.testing.allocator, &local_store.history, remote_file, .{ .remote = "origin" });    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("refs/remotes/origin/main")).?.target));    _ = try pullFileFastForward(std.testing.allocator, &local_store.history, remote_file, "main");    try std.testing.expect(version.same(remote_commit, (try local_store.history.ref("main")).?.target));    var local = try connection_mod.Connection.open(std.testing.allocator, &local_store.database, &local_store.history, .{});    defer local.deinit();    try local.checkoutBranch(std.testing.allocator, &local_store.history, "main");    try executeStatement(&local, "INSERT INTO items (rowid, name) VALUES (3, 'local')");    try local.stage();    const local_commit = try local.commit(&local_store.history);    _ = try pushFileFastForward(std.testing.allocator, &local_store.history, remote_file, "main");    {        var verified_remote = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "file-remote.history", .recovery = .reject });        defer verified_remote.deinit();        try std.testing.expect(version.same(local_commit, (try verified_remote.ref("main")).?.target));        const rows = try relationRows(&verified_remote, local_commit, "items");        defer version.freeRelationRows(std.testing.allocator, rows);        try std.testing.expectEqual(@as(usize, 3), rows.len);    }    try local_store.history.putRef(.{        .name = "refs/tags/file-v1",        .target = local_commit,    });    _ = try pushFileFastForward(std.testing.allocator, &local_store.history, remote_file, "refs/tags/file-v1");    {        var verified_remote = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "file-remote.history", .recovery = .reject });        defer verified_remote.deinit();        try std.testing.expect(version.same(local_commit, (try verified_remote.ref("refs/tags/file-v1")).?.target));    }    const delete_stats = try deleteFileRemoteRef(std.testing.allocator, remote_file, "refs/tags/file-v1");    try std.testing.expectEqual(@as(usize, 1), delete_stats.refs);    {        var verified_remote = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "file-remote.history", .recovery = .reject });        defer verified_remote.deinit();        try std.testing.expect((try verified_remote.ref("refs/tags/file-v1")) == null);    }    try std.testing.expectError(error.RefNotFound, deleteFileRemoteRef(std.testing.allocator, remote_file, "refs/tags/file-v1"));}

Audit

Definitions23
Public names23
Members17
Version26.7.0
Revisiondaab053ee433