| ... | @@ -38,6 +38,32 @@ test parseOid { | ... | @@ -38,6 +38,32 @@ test parseOid { |
| 38 | try testing.expectError(error.InvalidOid, parseOid("HEAD")); | 38 | try testing.expectError(error.InvalidOid, parseOid("HEAD")); |
| 39 | } | 39 | } |
| 40 | | 40 | |
| | 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 | |
| 41 | pub const Repository = struct { | 67 | pub const Repository = struct { |
| 42 | odb: Odb, | 68 | odb: Odb, |
| 43 | | 69 | |
| ... | @@ -55,6 +81,7 @@ pub const Repository = struct { | ... | @@ -55,6 +81,7 @@ pub const Repository = struct { |
| 55 | repository: *Repository, | 81 | repository: *Repository, |
| 56 | worktree: std.fs.Dir, | 82 | worktree: std.fs.Dir, |
| 57 | commit_oid: Oid, | 83 | commit_oid: Oid, |
| | 84 | diagnostics: *Diagnostics, |
| 58 | ) !void { | 85 | ) !void { |
| 59 | try repository.odb.seekOid(commit_oid); | 86 | try repository.odb.seekOid(commit_oid); |
| 60 | const tree_oid = tree_oid: { | 87 | const tree_oid = tree_oid: { |
| ... | @@ -62,7 +89,7 @@ pub const Repository = struct { | ... | @@ -62,7 +89,7 @@ pub const Repository = struct { |
| 62 | if (commit_object.type != .commit) return error.NotACommit; | 89 | if (commit_object.type != .commit) return error.NotACommit; |
| 63 | break :tree_oid try getCommitTree(commit_object.data); | 90 | break :tree_oid try getCommitTree(commit_object.data); |
| 64 | }; | 91 | }; |
| 65 | try repository.checkoutTree(worktree, tree_oid); | 92 | try repository.checkoutTree(worktree, tree_oid, "", diagnostics); |
| 66 | } | 93 | } |
| 67 | | 94 | |
| 68 | /// Checks out the tree at `tree_oid` to `worktree`. | 95 | /// Checks out the tree at `tree_oid` to `worktree`. |
| ... | @@ -70,6 +97,8 @@ pub const Repository = struct { | ... | @@ -70,6 +97,8 @@ pub const Repository = struct { |
| 70 | repository: *Repository, | 97 | repository: *Repository, |
| 71 | dir: std.fs.Dir, | 98 | dir: std.fs.Dir, |
| 72 | tree_oid: Oid, | 99 | tree_oid: Oid, |
| | 100 | current_path: []const u8, |
| | 101 | diagnostics: *Diagnostics, |
| 73 | ) !void { | 102 | ) !void { |
| 74 | try repository.odb.seekOid(tree_oid); | 103 | try repository.odb.seekOid(tree_oid); |
| 75 | const tree_object = try repository.odb.readObject(); | 104 | const tree_object = try repository.odb.readObject(); |
| ... | @@ -87,7 +116,9 @@ pub const Repository = struct { | ... | @@ -87,7 +116,9 @@ pub const Repository = struct { |
| 87 | try dir.makeDir(entry.name); | 116 | try dir.makeDir(entry.name); |
| 88 | var subdir = try dir.openDir(entry.name, .{}); | 117 | var subdir = try dir.openDir(entry.name, .{}); |
| 89 | defer subdir.close(); | 118 | 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); |
| 91 | }, | 122 | }, |
| 92 | .file => { | 123 | .file => { |
| 93 | var file = try dir.createFile(entry.name, .{}); | 124 | var file = try dir.createFile(entry.name, .{}); |
| ... | @@ -98,7 +129,23 @@ pub const Repository = struct { | ... | @@ -98,7 +129,23 @@ pub const Repository = struct { |
| 98 | try file.writeAll(file_object.data); | 129 | try file.writeAll(file_object.data); |
| 99 | try file.sync(); | 130 | try file.sync(); |
| 100 | }, | 131 | }, |
| 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 | }, |
| 102 | .gitlink => { | 149 | .gitlink => { |
| 103 | // Consistent with git archive behavior, create the directory but | 150 | // Consistent with git archive behavior, create the directory but |
| 104 | // do nothing else | 151 | // do nothing else |