A Git object database backed by a packfile. A packfile index is also used for efficient access to objects in the packfile.
The format of the packfile and its associated index are documented in pack-format.
const Odb = struct
const Odb = struct {
format: Oid.Format,
pack_file: *Io.File.Reader,
index_header: IndexHeader,
index_file: *Io.File.Reader,
cache: ObjectCache = .{},
allocator: Allocator,
/// Initializes the database from open pack and index files.
fn init(
odb: *Odb,
allocator: Allocator,
format: Oid.Format,
pack_file: *Io.File.Reader,
index_file: *Io.File.Reader,
) !void {
try pack_file.seekTo(0);
try index_file.seekTo(0);
odb.* = .{
.format = format,
.pack_file = pack_file,
.index_header = undefined,
.index_file = index_file,
.allocator = allocator,
};
try odb.index_header.read(&index_file.interface);
}
fn deinit(odb: *Odb) void {
odb.cache.deinit(odb.allocator);
odb.* = undefined;
}
/// Reads the object at the current position in the database.
fn readObject(odb: *Odb) !Object {
var base_offset = odb.pack_file.logicalPos();
var base_header: EntryHeader = undefined;
var delta_offsets: std.ArrayList(u64) = .empty;
defer delta_offsets.deinit(odb.allocator);
const base_object = while (true) {
if (odb.cache.get(base_offset)) |base_object| break base_object;
base_header = try EntryHeader.read(odb.format, &odb.pack_file.interface);
switch (base_header) {
.ofs_delta => |ofs_delta| {
try delta_offsets.append(odb.allocator, base_offset);
base_offset = std.math.sub(u64, base_offset, ofs_delta.offset) catch return error.InvalidFormat;
try odb.pack_file.seekTo(base_offset);
},
.ref_delta => |ref_delta| {
try delta_offsets.append(odb.allocator, base_offset);
try odb.seekOid(ref_delta.base_object);
base_offset = odb.pack_file.logicalPos();
},
else => {
const base_data = try readObjectRaw(odb.allocator, &odb.pack_file.interface, base_header.uncompressedLength());
errdefer odb.allocator.free(base_data);
const base_object: Object = .{ .type = base_header.objectType(), .data = base_data };
try odb.cache.put(odb.allocator, base_offset, base_object);
break base_object;
},
}
};
const base_data = try resolveDeltaChain(
odb.allocator,
odb.format,
odb.pack_file,
base_object,
delta_offsets.items,
&odb.cache,
);
return .{ .type = base_object.type, .data = base_data };
}
/// Seeks to the beginning of the object with the given ID.
fn seekOid(odb: *Odb, oid: Oid) !void {
const oid_length = odb.format.byteLength();
const key = oid.slice()[0];
var start_index = if (key > 0) odb.index_header.fan_out_table[key - 1] else 0;
var end_index = odb.index_header.fan_out_table[key];
const found_index = while (start_index < end_index) {
const mid_index = start_index + (end_index - start_index) / 2;
try odb.index_file.seekTo(IndexHeader.size + mid_index * oid_length);
const mid_oid = try Oid.readBytes(odb.format, &odb.index_file.interface);
switch (mem.order(u8, mid_oid.slice(), oid.slice())) {
.lt => start_index = mid_index + 1,
.gt => end_index = mid_index,
.eq => break mid_index,
}
} else return error.ObjectNotFound;
const n_objects = odb.index_header.fan_out_table[255];
const offset_values_start = IndexHeader.size + n_objects * (oid_length + 4);
try odb.index_file.seekTo(offset_values_start + found_index * 4);
const l1_offset: packed struct { value: u31, big: bool } = @bitCast(try odb.index_file.interface.takeInt(u32, .big));
const pack_offset = pack_offset: {
if (l1_offset.big) {
const l2_offset_values_start = offset_values_start + n_objects * 4;
try odb.index_file.seekTo(l2_offset_values_start + l1_offset.value * 4);
break :pack_offset try odb.index_file.interface.takeInt(u64, .big);
} else {
break :pack_offset l1_offset.value;
}
};
try odb.pack_file.seekTo(pack_offset);
}
}