Skip to content

Commit

Permalink
Support symlinks for git+http(s) dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
ianprime0509 authored and andrewrk committed Oct 3, 2023
1 parent 2118118 commit 573a13f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 5 deletions.
31 changes: 29 additions & 2 deletions src/Package.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
}

Expand Down
53 changes: 50 additions & 3 deletions src/git.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -55,21 +81,24 @@ 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: {
var commit_object = try repository.odb.readObject();
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`.
fn checkoutTree(
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();
Expand All @@ -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, .{});
Expand All @@ -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
Expand Down

0 comments on commit 573a13f

Please sign in to comment.