]> gitweb.ps.run Git - ziggit/blobdiff - git.zig
add arena allocator to tests
[ziggit] / git.zig
diff --git a/git.zig b/git.zig
index 7d0ccdbfc64cb2258687a34452b98ba01a2e6106..80e9aace87a0e8b0b28a05593101251170723ded 100644 (file)
--- a/git.zig
+++ b/git.zig
@@ -4,16 +4,30 @@ const Alloc = std.mem.Allocator;
 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,
@@ -24,17 +38,74 @@ const Object = struct {
             .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,
@@ -45,6 +116,8 @@ const PackFile = struct {
         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")) {
@@ -60,9 +133,14 @@ const PackFile = struct {
                 self.pckFile = try packDir.openFile(pckFilename.constSlice(), .{});
 
                 try self.parseIndex();
+
+                packFileFound = true;
             }
         }
 
+        if (!packFileFound)
+            return null;
+
         return self;
     }
 
@@ -174,18 +252,6 @@ const PackFile = struct {
         };
     }
 
-    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();
@@ -218,6 +284,9 @@ const PackFile = struct {
                 }
                 counter += bitsSet;
 
+                if (dataSize == 0)
+                    dataSize = 0x10000;
+
                 std.mem.copyForwards(
                     u8,
                     result[resultCounter..result.len],
@@ -233,6 +302,7 @@ const PackFile = struct {
                     result[resultCounter..result.len],
                     deltData[deltaDataOffset + counter + 1 .. deltaDataOffset + counter + 1 + dataSize],
                 );
+
                 resultCounter += dataSize;
                 counter += dataSize;
             }
@@ -245,7 +315,7 @@ const PackFile = struct {
         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();
@@ -255,7 +325,7 @@ const PackFile = struct {
         defer self.alloc.free(baseObject.data);
 
         try self.pckFile.seekTo(pos);
-        const deltaData = try decompress(self.alloc, pckReader, size);
+        const deltaData = try decompress(self.alloc, pckReader);
         defer self.alloc.free(deltaData);
 
         const objectData = try applyDelta(self.alloc, baseObject.data, deltaData);
@@ -270,12 +340,12 @@ const PackFile = struct {
 
         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);
         }
     }
@@ -285,7 +355,9 @@ const PackFile = struct {
             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;
     }
@@ -294,7 +366,7 @@ const PackFile = struct {
 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, .{});
@@ -310,7 +382,9 @@ const Repo = struct {
 
     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 {
@@ -328,12 +402,19 @@ const Repo = struct {
     }
 
     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();
@@ -342,71 +423,177 @@ test "print HEAD" {
 }
 
 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 alloc.free(o.data);
+
+        std.debug.print("object({}): {s}\n", .{ o.kind, 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| {
-        defer std.testing.allocator.free(o.data);
+        defer alloc.free(o.data);
 
-        std.debug.print("object: {s}\n", .{o.data});
+        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| {
+        defer alloc.free(o.data);
 
-        std.debug.print("tree: {s}\n", .{o.data});
+        // std.debug.print("tree({}): {any}\n", .{ o.kind, o.data });
     }
 }
-// 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 "parse tree" {
+    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(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| {
+        defer alloc.free(o.data);
 
-//     var c = head.getCommit();
-//     for (0..3) |_| {
-//         std.debug.print("{}\n", .{c});
-//         c = c.parent;
-//     }
-// }
+        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 "tree" {
-//     var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
-//     defer repo.close();
+test "list commits" {
+    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+    defer arena.deinit();
+    const alloc = arena.allocator();
 
-//     const head = repo.getObject(repo.head);
-//     defer head.deinit();
+    var repo = try Repo.open(alloc, "../imgui/.git");
+    defer repo.close();
 
-//     const commit = head.getCommit();
+    const head = try repo.getHead();
 
-//     std.debug.print("{}\n", .{commit.tree});
-// }
+    var id = head;
 
-// test "blob" {
-//     var repo = Repo.open(std.testing.allocator, "../microwindows/.git");
-//     defer repo.close();
+    for (0..3) |_| {
+        if (try repo.getObject(id)) |o| {
+            defer alloc.free(o.data);
 
-//     const head = repo.getObject(repo.head);
-//     defer head.deinit();
+            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 commit = head.getCommit();
-//     const blob = repo.getBlob(commit.files[0].id);
+test "list blobs" {
+    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+    defer arena.deinit();
+    const alloc = arena.allocator();
 
-//     std.debug.print("{}\n", .{blob});
-// }
+    var repo = try Repo.open(alloc, "../imgui/.git");
+    defer repo.close();
+
+    if (try repo.getObject(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| {
+        defer alloc.free(o.data);
+
+        switch (try o.parse(alloc)) {
+            .t => |t| {
+                defer t.deinit();
+                for (t.items) |treeEntry| {
+                    if (try repo.getObject(treeEntry.id)) |bo| {
+                        defer alloc.free(bo.data);
+
+                        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 => {},
+        }
+    }
+}
+
+test "basic frontend" {
+    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 id = head;
+
+    for (0..3) |_| {
+        if (try repo.getObject(id)) |o| {
+            defer alloc.free(o.data);
+
+            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 => {},
+            }
+        }
+    }
+}