wyag

Write yourself a git
Log | Files | Refs | README

commit 813a8b355a68f691969c4ed9375368f0049e4f6d
parent 6f15e001fedd8c88ab6125ccb5544f182ba7ef9e
Author: Martin Ashby <martin@ashbysoft.com>
Date:   Sat, 24 Aug 2024 22:29:21 +0100

Implement basic checkout

Diffstat:
Msrc/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]); + } +}