X-Git-Url: https://gitweb.ps.run/ziggit/blobdiff_plain/1eff0cba79ccb40715c7d1257f8b0743bf2b6b0b..HEAD:/git.zig?ds=sidebyside diff --git a/git.zig b/git.zig index 7dd1d96..07be390 100644 --- a/git.zig +++ b/git.zig @@ -8,14 +8,26 @@ 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, @@ -26,10 +38,67 @@ 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, @@ -215,6 +284,9 @@ const PackFile = struct { } counter += bitsSet; + if (dataSize == 0) + dataSize = 0x10000; + std.mem.copyForwards( u8, result[resultCounter..result.len], @@ -230,6 +302,7 @@ const PackFile = struct { result[resultCounter..result.len], deltData[deltaDataOffset + counter + 1 .. deltaDataOffset + counter + 1 + dataSize], ); + resultCounter += dataSize; counter += dataSize; } @@ -249,11 +322,9 @@ const PackFile = struct { 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); - defer self.alloc.free(deltaData); const objectData = try applyDelta(self.alloc, baseObject.data, deltaData); return Object.init(baseObject.kind, objectData); @@ -267,6 +338,7 @@ const PackFile = struct { if (objectKind == 6) { const offset = try getOffset(reader); + return try self.ofsDelta( @intCast(offset.offset + objectSize.bytelen + offset.bytelen), ); @@ -281,7 +353,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; } @@ -314,7 +388,6 @@ const Repo = struct { 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]; @@ -326,12 +399,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(); @@ -340,71 +420,161 @@ 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 std.testing.allocator.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(); - std.debug.print("object: {s}\n", .{o.data}); + 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, "../imgui/.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(0xceb2b2c62d6f8f3686dcacecd5be931839b02c77)) |o| { - defer std.testing.allocator.free(o.data); + std.debug.print("tree({}): {any}\n", .{ o.kind, o.data }); + } +} - std.debug.print("tree: {s}\n", .{o.data}); +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| { + 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, "../imgui/.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 id = head; -// var c = head.getCommit(); -// for (0..3) |_| { -// std.debug.print("{}\n", .{c}); -// c = c.parent; -// } -// } + 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 => {}, + } + } + } +} -// test "tree" { -// var repo = Repo.open(std.testing.allocator, "../imgui/.git"); -// defer repo.close(); +test "list blobs" { + 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(); + 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, "../imgui/.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 => {}, + } + } + } +}