1 const std = @import("std");
3 const Alloc = std.mem.Allocator;
4 const Reader = std.io.AnyReader;
5 const Writer = std.io.AnyWriter;
8 const Commit = struct {
17 const Object = struct {
21 pub fn init(kind: u3, data: []u8) Object {
27 // pub fn getCommit(self: *Object) Commit {}
28 // pub fn getBlob(self: *Object) Blob {}
31 const PackFile = struct {
35 objectOffsets: std.AutoArrayHashMap(Id, u32),
37 pub fn open(alloc: Alloc, dir: std.fs.Dir) !PackFile {
42 .objectOffsets = std.AutoArrayHashMap(Id, u32).init(alloc),
45 var packDir = try dir.openDir("objects/pack", .{ .iterate = true });
46 defer packDir.close();
48 var packIt = packDir.iterate();
49 while (try packIt.next()) |f| {
50 if (std.mem.endsWith(u8, f.name, ".idx")) {
51 const idxFilename = f.name;
52 var pckFilename = try std.BoundedArray(u8, std.fs.max_path_bytes).init(0);
56 .{idxFilename[0 .. idxFilename.len - 4]},
59 self.idxFile = try packDir.openFile(idxFilename, .{});
60 self.pckFile = try packDir.openFile(pckFilename.constSlice(), .{});
62 try self.parseIndex();
69 pub fn close(self: *PackFile) void {
70 self.objectOffsets.deinit();
75 pub fn parseIndex(self: *PackFile) !void {
76 const idxReader = self.idxFile.reader().any();
78 var fanoutTable: [256]u32 = undefined;
81 try self.idxFile.seekTo(8 + i * 4);
82 fanoutTable[i] = try idxReader.readVarInt(u32, .big, 4);
85 if (i > 0) fanoutTable[i] - fanoutTable[i - 1] else fanoutTable[i];
87 for (0..numObjects) |j| {
89 4 + 4 + 4 * 256 + (j + if (i > 0) fanoutTable[i - 1] else 0) * 20;
90 try self.idxFile.seekTo(idOffset);
91 const id = try idxReader.readVarInt(Id, .big, 20);
93 try self.objectOffsets.put(id, 0);
97 const numObjects = self.objectOffsets.keys().len;
98 for (0..numObjects) |i| {
100 4 + 4 + 4 * 256 + numObjects * (20 + 4) + i * 4;
101 try self.idxFile.seekTo(offsetOffset);
102 const offset = try idxReader.readVarInt(u32, .big, 4);
104 self.objectOffsets.values()[i] = offset;
108 fn getSize(reader: Reader, ignoreTypeBits: bool) !struct { size: u64, bytelen: u64 } {
112 const byte = try reader.readByte();
115 if (ignoreTypeBits) {
116 const bits: u4 = @truncate(byte);
119 const bits: u7 = @truncate(byte);
123 if (ignoreTypeBits) {
124 const bits: u7 = @truncate(byte);
125 size += @as(u64, bits) << (7 * (counter - 1) + 4);
127 const bits: u7 = @truncate(byte);
128 size += @as(u64, bits) << (7 * (counter));
132 if (byte & 0b10000000 == 0) {
139 const nBytes = counter + 1;
147 fn getOffset(reader: Reader) !struct { offset: u64, bytelen: u64 } {
151 const byte = try reader.readByte();
153 const bits: u7 = @truncate(byte);
155 offset += @as(u64, bits);
157 if (byte & 0b10000000 == 0) {
164 const nBytes = counter + 1;
167 for (1..nBytes) |i| {
168 offset += std.math.pow(u64, 2, 7 * i);
177 fn decompress(alloc: Alloc, reader: Reader, size: usize) ![]u8 {
178 const outBuffer = try alloc.alloc(u8, size);
179 errdefer alloc.free(outBuffer);
181 var outFbs = std.io.fixedBufferStream(outBuffer);
182 const writer = outFbs.writer();
184 try std.compress.zlib.decompress(reader, writer);
189 fn applyDelta(alloc: Alloc, baseData: []const u8, deltData: []const u8) ![]u8 {
190 var fbs = std.io.fixedBufferStream(deltData);
191 const deltDataReader = fbs.reader().any();
192 const baseObjectSize = try getSize(deltDataReader, false);
193 const resultObjectSize = try getSize(deltDataReader, false);
194 const deltaDataOffset = baseObjectSize.bytelen + resultObjectSize.bytelen;
196 const result = try alloc.alloc(u8, resultObjectSize.size);
197 var resultCounter: u64 = 0;
199 var counter: u64 = 0;
201 const b = deltData[deltaDataOffset + counter];
203 if (b & 0b10000000 != 0) {
204 var dataOffset: u64 = 0;
205 var dataSize: u64 = 0;
207 for (0..4) |i| { // offset bits
208 if (b & (@as(u64, 1) << @min(3, i)) != 0) {
209 dataOffset += @as(u64, deltData[deltaDataOffset + counter + 1 + bitsSet]) << @min(3 * 8, i * 8);
213 for (4..7) |i| { // size bits
214 if (b & (@as(u64, 1) << @min(6, i)) != 0) {
215 dataSize += @as(u64, deltData[deltaDataOffset + counter + 1 + bitsSet]) << @min(6 * 8, (i - 4) * 8);
221 std.mem.copyForwards(
223 result[resultCounter..result.len],
224 baseData[dataOffset .. dataOffset + dataSize],
227 resultCounter += dataSize;
229 const dataSize: u7 = @truncate(b);
231 std.mem.copyForwards(
233 result[resultCounter..result.len],
234 deltData[deltaDataOffset + counter + 1 .. deltaDataOffset + counter + 1 + dataSize],
236 resultCounter += dataSize;
241 if (deltaDataOffset + counter >= deltData.len)
248 fn ofsDelta(self: *PackFile, offset: i64, size: usize) anyerror!Object {
249 const pckReader = self.pckFile.reader().any();
251 const pos = try self.pckFile.getPos();
253 try self.pckFile.seekBy(-offset);
254 const baseObject = try self.readObject(pckReader);
255 defer self.alloc.free(baseObject.data);
257 try self.pckFile.seekTo(pos);
258 const deltaData = try decompress(self.alloc, pckReader, size);
259 defer self.alloc.free(deltaData);
261 const objectData = try applyDelta(self.alloc, baseObject.data, deltaData);
262 return Object.init(baseObject.kind, objectData);
265 fn readObject(self: *PackFile, reader: Reader) anyerror!Object {
266 const firstByte = try reader.readByte();
267 const objectKind: u3 = @truncate(firstByte >> 4);
268 try self.pckFile.seekBy(-1);
269 const objectSize = try getSize(reader, true);
271 if (objectKind == 6) {
272 const offset = try getOffset(reader);
273 return try self.ofsDelta(
274 @intCast(offset.offset + objectSize.bytelen + offset.bytelen),
278 const objectData = try decompress(self.alloc, reader, objectSize.size);
279 return Object.init(objectKind, objectData);
283 pub fn getObject(self: *PackFile, id: Id) !?Object {
284 if (self.objectOffsets.get(id)) |offset| {
285 const pckReader = self.pckFile.reader().any();
286 try self.pckFile.seekTo(offset);
288 return try self.readObject(pckReader);
294 const Repo = struct {
299 pub fn open(alloc: Alloc, path: []const u8) !Repo {
300 const dir = try std.fs.cwd().openDir(path, .{});
302 const packfile = try PackFile.open(alloc, dir);
307 .packfile = packfile,
311 pub fn close(self: *Repo) void {
313 self.packfile.close();
316 pub fn getHead(self: *Repo) !Id {
318 const head = try self.dir.readFileAlloc(self.alloc, "HEAD", 1024);
319 defer self.alloc.free(head);
321 // read file pointed at by HEAD
322 const headPath = head[5 .. head.len - 1];
323 var idBuffer: [40]u8 = undefined;
324 const idStr = try self.dir.readFile(headPath, &idBuffer);
326 // parse id from file
327 return try std.fmt.parseUnsigned(u160, idStr, 16);
330 pub fn getObject(self: *Repo, id: Id) !?Object {
331 return self.packfile.getObject(id);
336 var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
339 const head = try repo.getHead();
341 std.debug.print("HEAD: {}\n", .{head});
345 var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
348 std.debug.print("{}\n", .{repo.packfile.objectOffsets.keys().len});
349 std.debug.print("{}\n", .{repo.packfile.objectOffsets.values().len});
353 var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
356 const head = try repo.getHead();
358 if (try repo.getObject(head)) |o| {
359 defer std.testing.allocator.free(o.data);
361 std.debug.print("object: {s}\n", .{o.data});
366 var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
369 if (try repo.getObject(0xe59b68a950b643f9ea50997b3cf359a5956e852c)) |o| {
370 defer std.testing.allocator.free(o.data);
372 std.debug.print("tree: {s}\n", .{o.data});
375 // test "list commits" {
376 // var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
377 // defer repo.close();
379 // const head = repo.getObject(repo.head);
380 // defer head.deinit();
382 // var c = head.getCommit();
384 // std.debug.print("{}\n", .{c});
390 // var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
391 // defer repo.close();
393 // const head = repo.getObject(repo.head);
394 // defer head.deinit();
396 // const commit = head.getCommit();
398 // std.debug.print("{}\n", .{commit.tree});
402 // var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
403 // defer repo.close();
405 // const head = repo.getObject(repo.head);
406 // defer head.deinit();
408 // const commit = head.getCommit();
409 // const blob = repo.getBlob(commit.files[0].id);
411 // std.debug.print("{}\n", .{blob});