Skip to content

Commit 573a13f

Browse files
ianprime0509andrewrk
authored andcommitted
Support symlinks for git+http(s) dependencies
1 parent 2118118 commit 573a13f

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

src/Package.zig

+29-2
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ pub const ReadableResource = struct {
778778
.tar => try unpackTarball(allocator, prog_reader.reader(), tmp_directory.handle, dep_location_tok, report),
779779
.@"tar.gz" => try unpackTarballCompressed(allocator, prog_reader, tmp_directory.handle, dep_location_tok, report, std.compress.gzip),
780780
.@"tar.xz" => try unpackTarballCompressed(allocator, prog_reader, tmp_directory.handle, dep_location_tok, report, std.compress.xz),
781-
.git_pack => try unpackGitPack(allocator, &prog_reader, git.parseOid(rr.path) catch unreachable, tmp_directory.handle),
781+
.git_pack => try unpackGitPack(allocator, &prog_reader, git.parseOid(rr.path) catch unreachable, tmp_directory.handle, dep_location_tok, report),
782782
}
783783
} else {
784784
// Recursive directory copy.
@@ -1220,6 +1220,8 @@ fn unpackGitPack(
12201220
reader: anytype,
12211221
want_oid: git.Oid,
12221222
out_dir: fs.Dir,
1223+
dep_location_tok: std.zig.Ast.TokenIndex,
1224+
report: Report,
12231225
) !void {
12241226
// The .git directory is used to store the packfile and associated index, but
12251227
// we do not attempt to replicate the exact structure of a real .git
@@ -1251,7 +1253,32 @@ fn unpackGitPack(
12511253
checkout_prog_node.activate();
12521254
var repository = try git.Repository.init(gpa, pack_file, index_file);
12531255
defer repository.deinit();
1254-
try repository.checkout(out_dir, want_oid);
1256+
var diagnostics: git.Diagnostics = .{ .allocator = gpa };
1257+
defer diagnostics.deinit();
1258+
try repository.checkout(out_dir, want_oid, &diagnostics);
1259+
1260+
if (diagnostics.errors.items.len > 0) {
1261+
const notes_len: u32 = @intCast(diagnostics.errors.items.len);
1262+
try report.addErrorWithNotes(notes_len, .{
1263+
.tok = dep_location_tok,
1264+
.off = 0,
1265+
.msg = "unable to unpack packfile",
1266+
});
1267+
const eb = report.error_bundle;
1268+
const notes_start = try eb.reserveNotes(notes_len);
1269+
for (diagnostics.errors.items, notes_start..) |item, note_i| {
1270+
switch (item) {
1271+
.unable_to_create_sym_link => |info| {
1272+
eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
1273+
.msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{
1274+
info.file_name, info.link_name, @errorName(info.code),
1275+
}),
1276+
}));
1277+
},
1278+
}
1279+
}
1280+
return error.InvalidGitPack;
1281+
}
12551282
}
12561283
}
12571284

src/git.zig

+50-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,32 @@ test parseOid {
3838
try testing.expectError(error.InvalidOid, parseOid("HEAD"));
3939
}
4040

41+
pub const Diagnostics = struct {
42+
allocator: Allocator,
43+
errors: std.ArrayListUnmanaged(Error) = .{},
44+
45+
pub const Error = union(enum) {
46+
unable_to_create_sym_link: struct {
47+
code: anyerror,
48+
file_name: []const u8,
49+
link_name: []const u8,
50+
},
51+
};
52+
53+
pub fn deinit(d: *Diagnostics) void {
54+
for (d.errors.items) |item| {
55+
switch (item) {
56+
.unable_to_create_sym_link => |info| {
57+
d.allocator.free(info.file_name);
58+
d.allocator.free(info.link_name);
59+
},
60+
}
61+
}
62+
d.errors.deinit(d.allocator);
63+
d.* = undefined;
64+
}
65+
};
66+
4167
pub const Repository = struct {
4268
odb: Odb,
4369

@@ -55,21 +81,24 @@ pub const Repository = struct {
5581
repository: *Repository,
5682
worktree: std.fs.Dir,
5783
commit_oid: Oid,
84+
diagnostics: *Diagnostics,
5885
) !void {
5986
try repository.odb.seekOid(commit_oid);
6087
const tree_oid = tree_oid: {
6188
var commit_object = try repository.odb.readObject();
6289
if (commit_object.type != .commit) return error.NotACommit;
6390
break :tree_oid try getCommitTree(commit_object.data);
6491
};
65-
try repository.checkoutTree(worktree, tree_oid);
92+
try repository.checkoutTree(worktree, tree_oid, "", diagnostics);
6693
}
6794

6895
/// Checks out the tree at `tree_oid` to `worktree`.
6996
fn checkoutTree(
7097
repository: *Repository,
7198
dir: std.fs.Dir,
7299
tree_oid: Oid,
100+
current_path: []const u8,
101+
diagnostics: *Diagnostics,
73102
) !void {
74103
try repository.odb.seekOid(tree_oid);
75104
const tree_object = try repository.odb.readObject();
@@ -87,7 +116,9 @@ pub const Repository = struct {
87116
try dir.makeDir(entry.name);
88117
var subdir = try dir.openDir(entry.name, .{});
89118
defer subdir.close();
90-
try repository.checkoutTree(subdir, entry.oid);
119+
const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name });
120+
defer repository.odb.allocator.free(sub_path);
121+
try repository.checkoutTree(subdir, entry.oid, sub_path, diagnostics);
91122
},
92123
.file => {
93124
var file = try dir.createFile(entry.name, .{});
@@ -98,7 +129,23 @@ pub const Repository = struct {
98129
try file.writeAll(file_object.data);
99130
try file.sync();
100131
},
101-
.symlink => return error.SymlinkNotSupported,
132+
.symlink => {
133+
try repository.odb.seekOid(entry.oid);
134+
var symlink_object = try repository.odb.readObject();
135+
if (symlink_object.type != .blob) return error.InvalidFile;
136+
const link_name = symlink_object.data;
137+
dir.symLink(link_name, entry.name, .{}) catch |e| {
138+
const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
139+
errdefer diagnostics.allocator.free(file_name);
140+
const link_name_dup = try diagnostics.allocator.dupe(u8, link_name);
141+
errdefer diagnostics.allocator.free(link_name_dup);
142+
try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_sym_link = .{
143+
.code = e,
144+
.file_name = file_name,
145+
.link_name = link_name_dup,
146+
} });
147+
};
148+
},
102149
.gitlink => {
103150
// Consistent with git archive behavior, create the directory but
104151
// do nothing else

0 commit comments

Comments
 (0)