const Reader = std.io.AnyReader;
const Writer = std.io.AnyWriter;
+const MaxFileSize = 1024 * 1024;
+
const Id = u160;
const Commit = struct {
+ tree: Id,
+ parent: Id,
author: []u8,
+ committer: []u8,
message: []u8,
- parent: Id,
- tree: Id,
};
+const TreeEntry = struct {
+ permissions: []u8,
+ name: []u8,
+ id: Id,
+};
+const Tree = std.ArrayList(TreeEntry);
const Blob = struct {
data: []u8,
};
+const ParsedObject = union(enum) {
+ c: Commit,
+ t: Tree,
+ b: Blob,
+};
const Object = struct {
kind: u3,
data: []u8,
.data = data,
};
}
+ pub fn parse(self: Object, alloc: Alloc) !ParsedObject {
+ switch (self.kind) {
+ 1 => {
+ const authorOffset = std.mem.indexOf(u8, self.data, "author ") orelse return error.InvalidCommitFormat;
+ const authorNewline = std.mem.indexOfScalarPos(u8, self.data, authorOffset, '\n') orelse return error.InvalidCommitFormat;
+ const committerOffset = std.mem.indexOf(u8, self.data, "committer ") orelse return error.InvalidCommitFormat;
+ const committerNewline = std.mem.indexOfScalarPos(u8, self.data, committerOffset, '\n') orelse return error.InvalidCommitFormat;
+
+ return .{
+ .c = Commit{
+ .tree = try std.fmt.parseUnsigned(Id, self.data[5..45], 16),
+ .parent = try std.fmt.parseUnsigned(Id, self.data[53..93], 16),
+ .author = self.data[authorOffset..authorNewline],
+ .committer = self.data[committerOffset..committerNewline],
+ .message = self.data[committerNewline + 1 .. self.data.len],
+ },
+ };
+ },
+ 2 => {
+ var t = Tree.init(alloc);
+
+ var offset: usize = 0;
+
+ while (offset < self.data.len - 1) {
+ const spaceOffset = std.mem.indexOfScalarPos(u8, self.data, offset, ' ') orelse return error.InvalidTreeFormat;
+ const zeroOffset = std.mem.indexOfScalarPos(u8, self.data, spaceOffset, 0) orelse return error.InvalidTreeFormat;
+
+ try t.append(.{
+ .permissions = self.data[offset..spaceOffset],
+ .name = self.data[spaceOffset + 1 .. zeroOffset],
+ .id = std.mem.readVarInt(Id, self.data[zeroOffset + 1 .. zeroOffset + 21], .big),
+ });
+
+ offset = zeroOffset + 21;
+ }
+
+ return .{ .t = t };
+ },
+ 3 => {
+ return .{
+ .b = Blob{ .data = self.data },
+ };
+ },
+ 4 => {
+ return error.TagNotImplemented;
+ },
+ else => return error.UnknownGitObjectType,
+ }
+ }
// pub fn getCommit(self: *Object) Commit {}
// pub fn getBlob(self: *Object) Blob {}
};
+fn decompress(alloc: Alloc, r: Reader) ![]u8 {
+ var buffer = std.ArrayList(u8).init(alloc);
+
+ try std.compress.zlib.decompress(r, buffer.writer().any());
+
+ return alloc.realloc(buffer.allocatedSlice(), buffer.items.len);
+}
+
const PackFile = struct {
alloc: Alloc,
idxFile: std.fs.File,
pckFile: std.fs.File,
objectOffsets: std.AutoArrayHashMap(Id, u32),
- pub fn open(alloc: Alloc, dir: std.fs.Dir) !PackFile {
+ pub fn open(alloc: Alloc, dir: std.fs.Dir) !?PackFile {
var self = PackFile{
.alloc = alloc,
.idxFile = undefined,
var packDir = try dir.openDir("objects/pack", .{ .iterate = true });
defer packDir.close();
+ var packFileFound = false;
+
var packIt = packDir.iterate();
while (try packIt.next()) |f| {
if (std.mem.endsWith(u8, f.name, ".idx")) {
self.pckFile = try packDir.openFile(pckFilename.constSlice(), .{});
try self.parseIndex();
+
+ packFileFound = true;
}
}
+ if (!packFileFound)
+ return null;
+
return self;
}
};
}
- fn decompress(alloc: Alloc, reader: Reader, size: usize) ![]u8 {
- const outBuffer = try alloc.alloc(u8, size);
- errdefer alloc.free(outBuffer);
-
- var outFbs = std.io.fixedBufferStream(outBuffer);
- const writer = outFbs.writer();
-
- try std.compress.zlib.decompress(reader, writer);
-
- return outBuffer;
- }
-
fn applyDelta(alloc: Alloc, baseData: []const u8, deltData: []const u8) ![]u8 {
var fbs = std.io.fixedBufferStream(deltData);
const deltDataReader = fbs.reader().any();
}
counter += bitsSet;
+ if (dataSize == 0)
+ dataSize = 0x10000;
+
std.mem.copyForwards(
u8,
result[resultCounter..result.len],
result[resultCounter..result.len],
deltData[deltaDataOffset + counter + 1 .. deltaDataOffset + counter + 1 + dataSize],
);
+
resultCounter += dataSize;
counter += dataSize;
}
return result;
}
- fn ofsDelta(self: *PackFile, offset: i64, size: usize) anyerror!Object {
+ fn ofsDelta(self: *PackFile, offset: i64) anyerror!Object {
const pckReader = self.pckFile.reader().any();
const pos = try self.pckFile.getPos();
try self.pckFile.seekBy(-offset);
const baseObject = try self.readObject(pckReader);
- defer self.alloc.free(baseObject.data);
try self.pckFile.seekTo(pos);
- const deltaData = try decompress(self.alloc, pckReader, size);
- defer self.alloc.free(deltaData);
+ const deltaData = try decompress(self.alloc, pckReader);
const objectData = try applyDelta(self.alloc, baseObject.data, deltaData);
return Object.init(baseObject.kind, objectData);
if (objectKind == 6) {
const offset = try getOffset(reader);
+
return try self.ofsDelta(
@intCast(offset.offset + objectSize.bytelen + offset.bytelen),
- objectSize.size,
);
} else {
- const objectData = try decompress(self.alloc, reader, objectSize.size);
+ const objectData = try decompress(self.alloc, reader);
return Object.init(objectKind, objectData);
}
}
const pckReader = self.pckFile.reader().any();
try self.pckFile.seekTo(offset);
- return try self.readObject(pckReader);
+ const o = try self.readObject(pckReader);
+
+ return o;
}
return null;
}
const Repo = struct {
alloc: Alloc,
dir: std.fs.Dir,
- packfile: PackFile,
+ packfile: ?PackFile,
pub fn open(alloc: Alloc, path: []const u8) !Repo {
const dir = try std.fs.cwd().openDir(path, .{});
pub fn close(self: *Repo) void {
self.dir.close();
- self.packfile.close();
+ if (self.packfile != null) {
+ self.packfile.?.close();
+ }
}
pub fn getHead(self: *Repo) !Id {
// read file HEAD
const head = try self.dir.readFileAlloc(self.alloc, "HEAD", 1024);
- defer self.alloc.free(head);
// read file pointed at by HEAD
const headPath = head[5 .. head.len - 1];
}
pub fn getObject(self: *Repo, id: Id) !?Object {
- return self.packfile.getObject(id);
+ if (self.packfile) |*packfile| {
+ return packfile.getObject(id);
+ }
+ return null;
}
};
test "print HEAD" {
- var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
defer repo.close();
const head = try repo.getHead();
}
test "parse idx" {
- var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
defer repo.close();
- std.debug.print("{}\n", .{repo.packfile.objectOffsets.keys().len});
- std.debug.print("{}\n", .{repo.packfile.objectOffsets.values().len});
+ if (repo.packfile) |packfile| {
+ std.debug.print("{}\n", .{packfile.objectOffsets.keys().len});
+ std.debug.print("{}\n", .{packfile.objectOffsets.values().len});
+ }
}
test "get object" {
- var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
defer repo.close();
const head = try repo.getHead();
if (try repo.getObject(head)) |o| {
- defer std.testing.allocator.free(o.data);
+ std.debug.print("object({}): {s}\n", .{ o.kind, o.data });
+ }
+}
- std.debug.print("object: {s}\n", .{o.data});
+test "parse commit" {
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
+ defer repo.close();
+
+ const head = try repo.getHead();
+
+ if (try repo.getObject(head)) |o| {
+ switch (try o.parse(alloc)) {
+ .c => |c| {
+ std.debug.print("commit:\n tree: {x}\n parent: {x}\n author: {s}\n committer: {s}\n message: {s}\n", .{ c.tree, c.parent, c.author, c.committer, c.message });
+ },
+ else => {},
+ }
}
}
test "get tree" {
- var repo = try Repo.open(std.testing.allocator, "../microwindows/.git");
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
defer repo.close();
- if (try repo.getObject(0xe59b68a950b643f9ea50997b3cf359a5956e852c)) |o| {
- defer std.testing.allocator.free(o.data);
+ if (try repo.getObject(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| {
+ std.debug.print("tree({}): {any}\n", .{ o.kind, o.data });
+ }
+}
+
+test "parse tree" {
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
- std.debug.print("tree: {s}\n", .{o.data});
+ var repo = try Repo.open(alloc, "../imgui/.git");
+ defer repo.close();
+
+ if (try repo.getObject(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| {
+ switch (try o.parse(alloc)) {
+ .t => |t| {
+ defer t.deinit();
+ for (t.items) |treeEntry| {
+ std.debug.print("{s} {s} {x}\n", .{ treeEntry.permissions, treeEntry.name, treeEntry.id });
+ }
+ },
+ else => {},
+ }
}
}
-// test "list commits" {
-// var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
-// defer repo.close();
-// const head = repo.getObject(repo.head);
-// defer head.deinit();
+test "list commits" {
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var repo = try Repo.open(alloc, "../imgui/.git");
+ defer repo.close();
+
+ const head = try repo.getHead();
-// var c = head.getCommit();
-// for (0..3) |_| {
-// std.debug.print("{}\n", .{c});
-// c = c.parent;
-// }
-// }
+ var id = head;
-// test "tree" {
-// var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
-// defer repo.close();
+ for (0..3) |_| {
+ if (try repo.getObject(id)) |o| {
+ switch (try o.parse(alloc)) {
+ .c => |c| {
+ std.debug.print("commit {x}:\n tree: {x}\n parent: {x}\n author: {s}\n committer: {s}\n message: {s}\n", .{ id, c.tree, c.parent, c.author, c.committer, c.message });
+ id = c.parent;
+ },
+ else => {},
+ }
+ }
+ }
+}
-// const head = repo.getObject(repo.head);
-// defer head.deinit();
+test "list blobs" {
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
-// const commit = head.getCommit();
+ var repo = try Repo.open(alloc, "../imgui/.git");
+ defer repo.close();
+
+ if (try repo.getObject(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| {
+ switch (try o.parse(alloc)) {
+ .t => |t| {
+ defer t.deinit();
+ for (t.items) |treeEntry| {
+ if (try repo.getObject(treeEntry.id)) |bo| {
+ if (treeEntry.permissions.len == 6) {
+ std.debug.print("{s}: [{x} {}]{s}\n", .{ treeEntry.name, treeEntry.id, bo.data.len, bo.data[0..50] });
+ } else {
+ std.debug.print("[{s}]\n", .{treeEntry.name});
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ }
+}
-// std.debug.print("{}\n", .{commit.tree});
-// }
+test "basic frontend" {
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
-// test "blob" {
-// var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
-// defer repo.close();
+ var repo = try Repo.open(alloc, "../imgui/.git");
+ defer repo.close();
-// const head = repo.getObject(repo.head);
-// defer head.deinit();
+ const head = try repo.getHead();
-// const commit = head.getCommit();
-// const blob = repo.getBlob(commit.files[0].id);
+ var id = head;
-// std.debug.print("{}\n", .{blob});
-// }
+ for (0..3) |_| {
+ if (try repo.getObject(id)) |o| {
+ switch (try o.parse(alloc)) {
+ .c => |c| {
+ std.debug.print("commit {x}:\n tree: {x}\n parent: {x}\n author: {s}\n committer: {s}\n message: {s}\n", .{ id, c.tree, c.parent, c.author, c.committer, c.message });
+ id = c.parent;
+ },
+ else => {},
+ }
+ }
+ }
+}