wyag

Write yourself a git
Log | Files | Refs | README

commit 6f15e001fedd8c88ab6125ccb5544f182ba7ef9e
parent 13a3cddf0c6aefd6086abc1f11840993c62e673a
Author: Martin Ashby <martin@ashbysoft.com>
Date:   Sat, 24 Aug 2024 12:21:15 +0100

Add tree parsing and ls-tree command

Diffstat:
Msrc/root.zig | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sample.tree | 0
2 files changed, 119 insertions(+), 0 deletions(-)

diff --git a/src/root.zig b/src/root.zig @@ -36,6 +36,13 @@ pub fn doMain() !void { var log_commit: argparse.Positional = .{ .name = "commit", .description = "The commit to log from" }; try log.addPositional(&log_commit); + var ls_tree: argparse.Subcommand = .{ .parent = &ap, .description = "List items in a tree", .name = "ls-tree" }; + try ap.addSubcommand(&ls_tree); + var ls_tree_recursive: argparse.Flag = .{ .long = "recursive", .short = "r", .description = "List contents recursively", .hasarg = false }; + try ls_tree.addFlag(&ls_tree_recursive); + var ls_tree_ish: argparse.Positional = .{ .name = "tree-ish", .description = "The tree-ish to log the tree for" }; + try ls_tree.addPositional(&ls_tree_ish); + if (!try ap.parseOrHelp()) { return; } @@ -72,6 +79,12 @@ pub fn doMain() !void { } else { std.log.err("No commit supplied to log", .{}); } + } else if (ls_tree.wasExecuted) { + if (ls_tree_ish.value) |ref| { + try lsTree(a, std.fs.cwd(), ref, std.io.getStdOut().writer(), ls_tree_recursive.waspresent); + } else { + std.log.err("No commit supplied to ls-tree", .{}); + } } else { if (ap.excess.items.len > 0) { std.log.err("Unsupported sub-command {s}, have you tried implementing it yourself?", .{ap.excess.items[0]}); @@ -82,6 +95,46 @@ pub fn doMain() !void { } } +fn lsTree(a: std.mem.Allocator, dir: Dir, ref: []const u8, writer: anytype, recurse: bool) !void { + var repo = try repo_find(a, dir); + defer repo.deinit(); + var go = try repo.read_object(a, ref); + defer go.deinit(); + var go2: ?GitObject = go; + defer if (go2) |*g| g.deinit(); + if (go.kind == .commit) { + var gc = try Commit.parse(a, go.reader()); + defer gc.deinit(a); + go2 = try repo.read_object(a, gc.tree); + } else if (go.kind != .tree) { + return error.NotATreeish; + } + try lsTreeInternal(a, &repo, &go2.?, writer, recurse, ""); +} + +fn lsTreeInternal(a: std.mem.Allocator, repo: *GitRepository, go: *GitObject, writer: anytype, recurse: bool, path_prefix: []const u8) !void { + if (go.kind != .tree) return error.NotATree; + var gt = try GitTree.parse(a, go.reader()); + defer gt.deinit(); + for (gt.leaves.items) |leaf| { + const filetype_str = switch (leaf.filetype) { + .file => "blob", + .directory => "tree", + .symlink => "blob", + .submodule => "submodule", + }; + const fullpath = try std.fs.path.join(a, &.{ path_prefix, leaf.path }); + defer a.free(fullpath); + if (recurse and leaf.filetype == .directory) { + var go2 = try repo.read_object_sha(a, leaf.sha); + defer go2.deinit(); + try lsTreeInternal(a, repo, &go2, writer, recurse, fullpath); + } else { + try std.fmt.format(writer, "{}{o:0>4} {s} {s} {s}\n", .{ leaf.filetype, leaf.mode, filetype_str, std.fmt.fmtSliceHexLower(&leaf.sha), fullpath }); + } + } +} + fn gitLog(a: std.mem.Allocator, dir: Dir, ref: []const u8, writer: anytype) !void { var repo = try repo_find(a, dir); defer repo.deinit(); @@ -543,3 +596,69 @@ const Kvlm = struct { a.free(self.message); } }; + +pub const GitTree = struct { + pub const Leaf = struct { + pub const FileType = enum { + file, + directory, + symlink, + submodule, + + pub fn parse(filetype_str: []const u8) !FileType { + return if (std.mem.eql(u8, filetype_str, "04")) .directory else if (std.mem.eql(u8, filetype_str, "10")) .file else if (std.mem.eql(u8, filetype_str, "12")) .symlink else if (std.mem.eql(u8, filetype_str, "16")) .submodule else return error.BadFiletype; + } + pub fn format(self: FileType, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.writeAll(switch (self) { + .file => "10", + .directory => "04", + .symlink => "12", + .submodule => "16", + }); + } + }; + filetype: FileType, + mode: u16, + path: []const u8, + sha: [20]u8, + }; + aa: std.heap.ArenaAllocator, + leaves: std.ArrayListUnmanaged(Leaf) = .{}, + + pub fn parse(ca: std.mem.Allocator, reader: anytype) !GitTree { + var result: GitTree = .{ + .aa = std.heap.ArenaAllocator.init(ca), + }; + errdefer result.aa.deinit(); + const a = result.aa.allocator(); + + while (try reader.readUntilDelimiterOrEofAlloc(a, '\x00', 1024)) |entry| { + var spl = std.mem.splitScalar(u8, entry, ' '); + const mode_str_bare = spl.first(); + const mode_str = try std.fmt.allocPrint(a, "{s:0>6}", .{mode_str_bare}); + const filetype_str = mode_str[0..2]; + const filetype: Leaf.FileType = try Leaf.FileType.parse(filetype_str); + const path = spl.rest(); + var sha: [20]u8 = undefined; + _ = try reader.readAll(&sha); + try result.leaves.append(a, .{ + .filetype = filetype, + .mode = try std.fmt.parseInt(u16, mode_str[2..], 8), + .path = path, + .sha = sha, + }); + } + return result; + } + + pub fn deinit(self: *GitTree) void { + self.aa.deinit(); + } +}; + +test "parse tree" { + const a = std.testing.allocator; + var fbs = std.io.fixedBufferStream(@embedFile("sample.tree")); + var tree = try GitTree.parse(a, fbs.reader()); + defer tree.deinit(); +} diff --git a/src/sample.tree b/src/sample.tree Binary files differ.