From 573a13f8be24276a02761c30396fd75fc10ebdb0 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 2 Oct 2023 20:59:00 -0400 Subject: [PATCH] Support symlinks for git+http(s) dependencies --- src/Package.zig | 31 +++++++++++++++++++++++++++-- src/git.zig | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/Package.zig b/src/Package.zig index 37e5d2cf30..14052e3de4 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -778,7 +778,7 @@ pub const ReadableResource = struct { .tar => try unpackTarball(allocator, prog_reader.reader(), tmp_directory.handle, dep_location_tok, report), .@"tar.gz" => try unpackTarballCompressed(allocator, prog_reader, tmp_directory.handle, dep_location_tok, report, std.compress.gzip), .@"tar.xz" => try unpackTarballCompressed(allocator, prog_reader, tmp_directory.handle, dep_location_tok, report, std.compress.xz), - .git_pack => try unpackGitPack(allocator, &prog_reader, git.parseOid(rr.path) catch unreachable, tmp_directory.handle), + .git_pack => try unpackGitPack(allocator, &prog_reader, git.parseOid(rr.path) catch unreachable, tmp_directory.handle, dep_location_tok, report), } } else { // Recursive directory copy. @@ -1220,6 +1220,8 @@ fn unpackGitPack( reader: anytype, want_oid: git.Oid, out_dir: fs.Dir, + dep_location_tok: std.zig.Ast.TokenIndex, + report: Report, ) !void { // The .git directory is used to store the packfile and associated index, but // we do not attempt to replicate the exact structure of a real .git @@ -1251,7 +1253,32 @@ fn unpackGitPack( checkout_prog_node.activate(); var repository = try git.Repository.init(gpa, pack_file, index_file); defer repository.deinit(); - try repository.checkout(out_dir, want_oid); + var diagnostics: git.Diagnostics = .{ .allocator = gpa }; + defer diagnostics.deinit(); + try repository.checkout(out_dir, want_oid, &diagnostics); + + if (diagnostics.errors.items.len > 0) { + const notes_len: u32 = @intCast(diagnostics.errors.items.len); + try report.addErrorWithNotes(notes_len, .{ + .tok = dep_location_tok, + .off = 0, + .msg = "unable to unpack packfile", + }); + const eb = report.error_bundle; + const notes_start = try eb.reserveNotes(notes_len); + for (diagnostics.errors.items, notes_start..) |item, note_i| { + switch (item) { + .unable_to_create_sym_link => |info| { + eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{ + .msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{ + info.file_name, info.link_name, @errorName(info.code), + }), + })); + }, + } + } + return error.InvalidGitPack; + } } } diff --git a/src/git.zig b/src/git.zig index c8d1040d60..fd35c5c33e 100644 --- a/src/git.zig +++ b/src/git.zig @@ -38,6 +38,32 @@ test parseOid { try testing.expectError(error.InvalidOid, parseOid("HEAD")); } +pub const Diagnostics = struct { + allocator: Allocator, + errors: std.ArrayListUnmanaged(Error) = .{}, + + pub const Error = union(enum) { + unable_to_create_sym_link: struct { + code: anyerror, + file_name: []const u8, + link_name: []const u8, + }, + }; + + pub fn deinit(d: *Diagnostics) void { + for (d.errors.items) |item| { + switch (item) { + .unable_to_create_sym_link => |info| { + d.allocator.free(info.file_name); + d.allocator.free(info.link_name); + }, + } + } + d.errors.deinit(d.allocator); + d.* = undefined; + } +}; + pub const Repository = struct { odb: Odb, @@ -55,6 +81,7 @@ pub const Repository = struct { repository: *Repository, worktree: std.fs.Dir, commit_oid: Oid, + diagnostics: *Diagnostics, ) !void { try repository.odb.seekOid(commit_oid); const tree_oid = tree_oid: { @@ -62,7 +89,7 @@ pub const Repository = struct { if (commit_object.type != .commit) return error.NotACommit; break :tree_oid try getCommitTree(commit_object.data); }; - try repository.checkoutTree(worktree, tree_oid); + try repository.checkoutTree(worktree, tree_oid, "", diagnostics); } /// Checks out the tree at `tree_oid` to `worktree`. @@ -70,6 +97,8 @@ pub const Repository = struct { repository: *Repository, dir: std.fs.Dir, tree_oid: Oid, + current_path: []const u8, + diagnostics: *Diagnostics, ) !void { try repository.odb.seekOid(tree_oid); const tree_object = try repository.odb.readObject(); @@ -87,7 +116,9 @@ pub const Repository = struct { try dir.makeDir(entry.name); var subdir = try dir.openDir(entry.name, .{}); defer subdir.close(); - try repository.checkoutTree(subdir, entry.oid); + const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); + defer repository.odb.allocator.free(sub_path); + try repository.checkoutTree(subdir, entry.oid, sub_path, diagnostics); }, .file => { var file = try dir.createFile(entry.name, .{}); @@ -98,7 +129,23 @@ pub const Repository = struct { try file.writeAll(file_object.data); try file.sync(); }, - .symlink => return error.SymlinkNotSupported, + .symlink => { + try repository.odb.seekOid(entry.oid); + var symlink_object = try repository.odb.readObject(); + if (symlink_object.type != .blob) return error.InvalidFile; + const link_name = symlink_object.data; + dir.symLink(link_name, entry.name, .{}) catch |e| { + const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name }); + errdefer diagnostics.allocator.free(file_name); + const link_name_dup = try diagnostics.allocator.dupe(u8, link_name); + errdefer diagnostics.allocator.free(link_name_dup); + try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_sym_link = .{ + .code = e, + .file_name = file_name, + .link_name = link_name_dup, + } }); + }; + }, .gitlink => { // Consistent with git archive behavior, create the directory but // do nothing else