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:
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.