commit 813a8b355a68f691969c4ed9375368f0049e4f6d
parent 6f15e001fedd8c88ab6125ccb5544f182ba7ef9e
Author: Martin Ashby <martin@ashbysoft.com>
Date: Sat, 24 Aug 2024 22:29:21 +0100
Implement basic checkout
Diffstat:
M | src/root.zig | | | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
1 file changed, 74 insertions(+), 2 deletions(-)
diff --git a/src/root.zig b/src/root.zig
@@ -43,6 +43,13 @@ pub fn doMain() !void {
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);
+ var checkout: argparse.Subcommand = .{ .parent = &ap, .description = "Checkout a commit inside of a directory", .name = "checkout" };
+ try ap.addSubcommand(&checkout);
+ var checkout_commit: argparse.Positional = .{ .name = "commit", .description = "The commit to checkout" };
+ try checkout.addPositional(&checkout_commit);
+ var checkout_directory: argparse.Positional = .{ .name = "directory", .description = "The directory to checkout into" };
+ try checkout.addPositional(&checkout_directory);
+
if (!try ap.parseOrHelp()) {
return;
}
@@ -85,6 +92,16 @@ pub fn doMain() !void {
} else {
std.log.err("No commit supplied to ls-tree", .{});
}
+ } else if (checkout.wasExecuted) {
+ if (checkout_commit.value) |commit_ref| {
+ if (checkout_directory.value) |directory| {
+ try doCheckout(a, std.fs.cwd(), commit_ref, directory);
+ } else {
+ std.log.err("No directory was supplied to checkout", .{});
+ }
+ } else {
+ std.log.err("No commit supplied to checkout", .{});
+ }
} else {
if (ap.excess.items.len > 0) {
std.log.err("Unsupported sub-command {s}, have you tried implementing it yourself?", .{ap.excess.items[0]});
@@ -95,6 +112,52 @@ pub fn doMain() !void {
}
}
+fn doCheckout(a: std.mem.Allocator, dir: std.fs.Dir, commit_ref: []const u8, checkout_directory: []const u8) !void {
+ var repo = try repo_find(a, dir);
+ defer repo.deinit();
+ var checkoutDir = try dir.makeOpenPath(checkout_directory, .{ .iterate = true });
+ defer checkoutDir.close();
+ var go = try repo.read_object(a, commit_ref);
+ defer go.deinit();
+ if (go.kind != .commit) return error.NotACommit;
+ var it = checkoutDir.iterate();
+ if (try it.next() != null) return error.DirNotEmpty;
+
+ var commit = try Commit.parse(a, go.reader());
+ defer commit.deinit(a);
+ var go2 = try repo.read_object(a, commit.tree);
+ defer go2.deinit();
+ try doCheckoutInternal(a, &repo, &go2, checkoutDir);
+}
+
+fn doCheckoutInternal(a: std.mem.Allocator, repo: *GitRepository, go: *GitObject, dir: std.fs.Dir) !void {
+ if (go.kind != .tree) return error.NotATree;
+ var gt = try GitTree.parse(a, go.reader());
+ defer gt.deinit();
+ for (gt.leaves.items) |leaf| {
+ var go2 = try repo.read_object_sha(a, leaf.sha);
+ defer go2.deinit();
+ switch (leaf.filetype) {
+ .file => {
+ var f = try dir.createFile(leaf.path, .{ .mode = leaf.mode });
+ defer f.close();
+ try pump(go2.reader(), f.writer());
+ },
+ .directory => {
+ var subdir = try dir.makeOpenPath(leaf.path, .{});
+ defer subdir.close();
+ try doCheckoutInternal(a, repo, &go2, subdir);
+ },
+ .symlink => {
+ return error.Unimplemented;
+ },
+ .submodule => {
+ return error.Unimplemented;
+ },
+ }
+ }
+}
+
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();
@@ -618,7 +681,7 @@ pub const GitTree = struct {
}
};
filetype: FileType,
- mode: u16,
+ mode: u32,
path: []const u8,
sha: [20]u8,
};
@@ -643,7 +706,7 @@ pub const GitTree = struct {
_ = try reader.readAll(&sha);
try result.leaves.append(a, .{
.filetype = filetype,
- .mode = try std.fmt.parseInt(u16, mode_str[2..], 8),
+ .mode = try std.fmt.parseInt(u32, mode_str[2..], 8),
.path = path,
.sha = sha,
});
@@ -662,3 +725,12 @@ test "parse tree" {
var tree = try GitTree.parse(a, fbs.reader());
defer tree.deinit();
}
+
+fn pump(reader: anytype, writer: anytype) !void {
+ var buf = [_]u8{0} ** std.mem.page_size;
+ while (true) {
+ const sz = try reader.read(&buf);
+ if (sz == 0) break;
+ try writer.writeAll(buf[0..sz]);
+ }
+}