Skip to content

Commit 489b102

Browse files
committed
package: move unpack logic to a single place
Tar pipeToFileSystem and git checkout have repeating logic: collecting diagnostic on errors, writing that errors to error bundle. Recurisive directory copy and pipeToFileSystem are both creating dir when writing file while dir still don't exists. Implementing new logic, thinking about file executable bit, requires changes on few places. This collects file handlling logic on single place and leaves other places with their job: unpacking tarball or git pack.
1 parent dbb1191 commit 489b102

File tree

3 files changed

+491
-143
lines changed

3 files changed

+491
-143
lines changed

src/Package/Fetch.zig

Lines changed: 27 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,61 +1143,23 @@ fn unpackResource(
11431143
}
11441144
}
11451145

1146+
const Unpack = @import("Unpack.zig");
1147+
11461148
fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!void {
11471149
const eb = &f.error_bundle;
11481150
const gpa = f.arena.child_allocator;
11491151

1150-
var diagnostics: std.tar.Diagnostics = .{ .allocator = gpa };
1151-
defer diagnostics.deinit();
1152-
1153-
std.tar.pipeToFileSystem(out_dir, reader, .{
1154-
.diagnostics = &diagnostics,
1155-
.strip_components = 1,
1156-
// TODO: we would like to set this to executable_bit_only, but two
1157-
// things need to happen before that:
1158-
// 1. the tar implementation needs to support it
1159-
// 2. the hashing algorithm here needs to support detecting the is_executable
1160-
// bit on Windows from the ACLs (see the isExecutable function).
1161-
.mode_mode = .ignore,
1162-
.exclude_empty_directories = true,
1163-
}) catch |err| return f.fail(f.location_tok, try eb.printString(
1164-
"unable to unpack tarball to temporary directory: {s}",
1165-
.{@errorName(err)},
1166-
));
1167-
1168-
if (diagnostics.errors.items.len > 0) {
1169-
const notes_len: u32 = @intCast(diagnostics.errors.items.len);
1170-
try eb.addRootErrorMessage(.{
1171-
.msg = try eb.addString("unable to unpack tarball"),
1172-
.src_loc = try f.srcLoc(f.location_tok),
1173-
.notes_len = notes_len,
1174-
});
1175-
const notes_start = try eb.reserveNotes(notes_len);
1176-
for (diagnostics.errors.items, notes_start..) |item, note_i| {
1177-
switch (item) {
1178-
.unable_to_create_sym_link => |info| {
1179-
eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
1180-
.msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{
1181-
info.file_name, info.link_name, @errorName(info.code),
1182-
}),
1183-
}));
1184-
},
1185-
.unable_to_create_file => |info| {
1186-
eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
1187-
.msg = try eb.printString("unable to create file '{s}': {s}", .{
1188-
info.file_name, @errorName(info.code),
1189-
}),
1190-
}));
1191-
},
1192-
.unsupported_file_type => |info| {
1193-
eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
1194-
.msg = try eb.printString("file '{s}' has unsupported type '{c}'", .{
1195-
info.file_name, @intFromEnum(info.file_type),
1196-
}),
1197-
}));
1198-
},
1199-
}
1200-
}
1152+
var unpack = Unpack{ .allocator = gpa, .root = out_dir };
1153+
defer unpack.deinit();
1154+
unpack.tarball(reader) catch |err| return f.fail(
1155+
f.location_tok,
1156+
try eb.printString(
1157+
"unable to unpack tarball to temporary directory: {s}",
1158+
.{@errorName(err)},
1159+
),
1160+
);
1161+
if (unpack.hasErrors()) {
1162+
try unpack.bundleErrors(eb, try f.srcLoc(f.location_tok));
12011163
return error.FetchFailed;
12021164
}
12031165
}
@@ -1207,104 +1169,26 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void
12071169
const gpa = f.arena.child_allocator;
12081170
const want_oid = resource.git.want_oid;
12091171
const reader = resource.git.fetch_stream.reader();
1210-
// The .git directory is used to store the packfile and associated index, but
1211-
// we do not attempt to replicate the exact structure of a real .git
1212-
// directory, since that isn't relevant for fetching a package.
1213-
{
1214-
var pack_dir = try out_dir.makeOpenPath(".git", .{});
1215-
defer pack_dir.close();
1216-
var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true });
1217-
defer pack_file.close();
1218-
var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1219-
try fifo.pump(reader, pack_file.writer());
1220-
try pack_file.sync();
1221-
1222-
var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });
1223-
defer index_file.close();
1224-
{
1225-
var index_prog_node = f.prog_node.start("Index pack", 0);
1226-
defer index_prog_node.end();
1227-
index_prog_node.activate();
1228-
var index_buffered_writer = std.io.bufferedWriter(index_file.writer());
1229-
try git.indexPack(gpa, pack_file, index_buffered_writer.writer());
1230-
try index_buffered_writer.flush();
1231-
try index_file.sync();
1232-
}
12331172

1234-
{
1235-
var checkout_prog_node = f.prog_node.start("Checkout", 0);
1236-
defer checkout_prog_node.end();
1237-
checkout_prog_node.activate();
1238-
var repository = try git.Repository.init(gpa, pack_file, index_file);
1239-
defer repository.deinit();
1240-
var diagnostics: git.Diagnostics = .{ .allocator = gpa };
1241-
defer diagnostics.deinit();
1242-
try repository.checkout(out_dir, want_oid, &diagnostics);
1243-
1244-
if (diagnostics.errors.items.len > 0) {
1245-
const notes_len: u32 = @intCast(diagnostics.errors.items.len);
1246-
try eb.addRootErrorMessage(.{
1247-
.msg = try eb.addString("unable to unpack packfile"),
1248-
.src_loc = try f.srcLoc(f.location_tok),
1249-
.notes_len = notes_len,
1250-
});
1251-
const notes_start = try eb.reserveNotes(notes_len);
1252-
for (diagnostics.errors.items, notes_start..) |item, note_i| {
1253-
switch (item) {
1254-
.unable_to_create_sym_link => |info| {
1255-
eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
1256-
.msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{
1257-
info.file_name, info.link_name, @errorName(info.code),
1258-
}),
1259-
}));
1260-
},
1261-
}
1262-
}
1263-
return error.InvalidGitPack;
1264-
}
1265-
}
1173+
var unpack = Unpack{ .allocator = gpa, .root = out_dir };
1174+
defer unpack.deinit();
1175+
try unpack.gitPack(want_oid, reader);
1176+
if (unpack.hasErrors()) {
1177+
try unpack.bundleErrors(eb, try f.srcLoc(f.location_tok));
1178+
return error.FetchFailed;
12661179
}
1267-
1268-
try out_dir.deleteTree(".git");
12691180
}
12701181

12711182
fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void {
1183+
const eb = &f.error_bundle;
12721184
const gpa = f.arena.child_allocator;
1273-
// Recursive directory copy.
1274-
var it = try dir.walk(gpa);
1275-
defer it.deinit();
1276-
while (try it.next()) |entry| {
1277-
switch (entry.kind) {
1278-
.directory => {}, // omit empty directories
1279-
.file => {
1280-
dir.copyFile(
1281-
entry.path,
1282-
tmp_dir,
1283-
entry.path,
1284-
.{},
1285-
) catch |err| switch (err) {
1286-
error.FileNotFound => {
1287-
if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);
1288-
try dir.copyFile(entry.path, tmp_dir, entry.path, .{});
1289-
},
1290-
else => |e| return e,
1291-
};
1292-
},
1293-
.sym_link => {
1294-
var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1295-
const link_name = try dir.readLink(entry.path, &buf);
1296-
// TODO: if this would create a symlink to outside
1297-
// the destination directory, fail with an error instead.
1298-
tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) {
1299-
error.FileNotFound => {
1300-
if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);
1301-
try tmp_dir.symLink(link_name, entry.path, .{});
1302-
},
1303-
else => |e| return e,
1304-
};
1305-
},
1306-
else => return error.IllegalFileTypeInPackage,
1307-
}
1185+
1186+
var unpack = Unpack{ .allocator = gpa, .root = tmp_dir };
1187+
defer unpack.deinit();
1188+
try unpack.directory(dir);
1189+
if (unpack.hasErrors()) {
1190+
try unpack.bundleErrors(eb, try f.srcLoc(f.location_tok));
1191+
return error.FetchFailed;
13081192
}
13091193
}
13101194

src/Package/Fetch/git.zig

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,69 @@ pub const Repository = struct {
153153
}
154154
}
155155

156+
/// Checks out the repository at `commit_oid` to `worktree`.
157+
pub fn checkout2(
158+
repository: *Repository,
159+
worktree: anytype,
160+
commit_oid: Oid,
161+
) !void {
162+
try repository.odb.seekOid(commit_oid);
163+
const tree_oid = tree_oid: {
164+
const commit_object = try repository.odb.readObject();
165+
if (commit_object.type != .commit) return error.NotACommit;
166+
break :tree_oid try getCommitTree(commit_object.data);
167+
};
168+
try repository.checkoutTree2(worktree, tree_oid, "");
169+
}
170+
171+
/// Checks out the tree at `tree_oid` to `worktree`.
172+
fn checkoutTree2(
173+
repository: *Repository,
174+
dir: anytype,
175+
tree_oid: Oid,
176+
current_path: []const u8,
177+
) !void {
178+
try repository.odb.seekOid(tree_oid);
179+
const tree_object = try repository.odb.readObject();
180+
if (tree_object.type != .tree) return error.NotATree;
181+
// The tree object may be evicted from the object cache while we're
182+
// iterating over it, so we can make a defensive copy here to make sure
183+
// it remains valid until we're done with it
184+
const allocator = repository.odb.allocator;
185+
const tree_data = try allocator.dupe(u8, tree_object.data);
186+
defer repository.odb.allocator.free(tree_data);
187+
188+
var tree_iter: TreeIterator = .{ .data = tree_data };
189+
while (try tree_iter.next()) |entry| {
190+
const sub_path = try std.fs.path.join(allocator, &.{ current_path, entry.name });
191+
defer allocator.free(sub_path);
192+
switch (entry.type) {
193+
.directory => {
194+
try repository.checkoutTree2(dir, entry.oid, sub_path);
195+
},
196+
.file => {
197+
try repository.odb.seekOid(entry.oid);
198+
const file_object = try repository.odb.readObject();
199+
if (file_object.type != .blob) return error.InvalidFile;
200+
201+
if (try dir.createFile(sub_path)) |file| {
202+
defer file.close();
203+
try file.writeAll(file_object.data);
204+
try file.sync();
205+
}
206+
},
207+
.symlink => {
208+
try repository.odb.seekOid(entry.oid);
209+
const symlink_object = try repository.odb.readObject();
210+
if (symlink_object.type != .blob) return error.InvalidFile;
211+
212+
try dir.symLink(symlink_object.data, sub_path);
213+
},
214+
.gitlink => {},
215+
}
216+
}
217+
}
218+
156219
/// Returns the ID of the tree associated with the given commit (provided as
157220
/// raw object data).
158221
fn getCommitTree(commit_data: []const u8) !Oid {

0 commit comments

Comments
 (0)