feature. See also
. The project being documented here (as the example) is the Zig library itself.
git.Repository
pub const Repository = struct
File
Code
pub const Repository = struct {
odb: Odb,
pub fn init(
repo: *Repository,
allocator: Allocator,
format: Oid.Format,
pack_file: *Io.File.Reader,
index_file: *Io.File.Reader,
) !void {
repo.* = .{ .odb = undefined };
try repo.odb.init(allocator, format, pack_file, index_file);
}
pub fn deinit(repository: *Repository) void {
repository.odb.deinit();
repository.* = undefined;
}
pub fn checkout(
repository: *Repository,
io: Io,
worktree: Io.Dir,
commit_oid: Oid,
diagnostics: *Diagnostics,
) !void {
try repository.odb.seekOid(commit_oid);
const tree_oid = tree_oid: {
const commit_object = try repository.odb.readObject();
if (commit_object.type != .commit) return error.NotACommit;
break :tree_oid try getCommitTree(repository.odb.format, commit_object.data);
};
try repository.checkoutTree(io, worktree, tree_oid, "", diagnostics);
}
fn checkoutTree(
repository: *Repository,
io: Io,
dir: Io.Dir,
tree_oid: Oid,
current_path: []const u8,
diagnostics: *Diagnostics,
) !void {
try repository.odb.seekOid(tree_oid);
const tree_object = try repository.odb.readObject();
if (tree_object.type != .tree) return error.NotATree;
// iterating over it, so we can make a defensive copy here to make sure
// it remains valid until we're done with it
const tree_data = try repository.odb.allocator.dupe(u8, tree_object.data);
defer repository.odb.allocator.free(tree_data);
var tree_iter: TreeIterator = .{
.format = repository.odb.format,
.data = tree_data,
.pos = 0,
};
while (try tree_iter.next()) |entry| {
switch (entry.type) {
.directory => {
try dir.createDir(io, entry.name, .default_dir);
var subdir = try dir.openDir(io, entry.name, .{});
defer subdir.close(io);
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(io, subdir, entry.oid, sub_path, diagnostics);
},
.file => {
try repository.odb.seekOid(entry.oid);
const file_object = try repository.odb.readObject();
if (file_object.type != .blob) return error.InvalidFile;
var file = dir.createFile(io, entry.name, .{ .exclusive = true }) catch |e| {
const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
errdefer diagnostics.allocator.free(file_name);
try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{
.code = e,
.file_name = file_name,
} });
continue;
};
defer file.close(io);
try file.writePositionalAll(io, file_object.data, 0);
},
.symlink => {
try repository.odb.seekOid(entry.oid);
const symlink_object = try repository.odb.readObject();
if (symlink_object.type != .blob) return error.InvalidFile;
const link_name = symlink_object.data;
dir.symLink(io, 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 => {
// do nothing else
try dir.createDir(io, entry.name, .default_dir);
},
}
}
}
fn getCommitTree(format: Oid.Format, commit_data: []const u8) !Oid {
if (!mem.startsWith(u8, commit_data, "tree ") or
commit_data.len < "tree ".len + format.formattedLength() + "\n".len or
commit_data["tree ".len + format.formattedLength()] != '\n')
{
return error.InvalidCommit;
}
return try .parse(format, commit_data["tree ".len..][0..format.formattedLength()]);
}
const TreeIterator = struct {
format: Oid.Format,
data: []const u8,
pos: usize,
const Entry = struct {
type: Type,
executable: bool,
name: [:0]const u8,
oid: Oid,
const Type = enum(u4) {
directory = 0o4,
file = 0o10,
symlink = 0o12,
gitlink = 0o16,
};
};
fn next(iterator: *TreeIterator) !?Entry {
if (iterator.pos == iterator.data.len) return null;
const mode_end = mem.indexOfScalarPos(u8, iterator.data, iterator.pos, ' ') orelse return error.InvalidTree;
const mode: packed struct {
permission: u9,
unused: u3,
type: u4,
} = @bitCast(std.fmt.parseUnsigned(u16, iterator.data[iterator.pos..mode_end], 8) catch return error.InvalidTree);
const @"type" = std.enums.fromInt(Entry.Type, mode.type) orelse return error.InvalidTree;
const executable = switch (mode.permission) {
0 => if (@"type" == .file) return error.InvalidTree else false,
0o644 => if (@"type" != .file) return error.InvalidTree else false,
0o755 => if (@"type" != .file) return error.InvalidTree else true,
else => return error.InvalidTree,
};
iterator.pos = mode_end + 1;
const name_end = mem.indexOfScalarPos(u8, iterator.data, iterator.pos, 0) orelse return error.InvalidTree;
const name = iterator.data[iterator.pos..name_end :0];
iterator.pos = name_end + 1;
const oid_length = iterator.format.byteLength();
if (iterator.pos + oid_length > iterator.data.len) return error.InvalidTree;
const oid: Oid = .fromBytes(iterator.format, iterator.data[iterator.pos..][0..oid_length]);
iterator.pos += oid_length;
return .{ .type = @"type", .executable = executable, .name = name, .oid = oid };
}
};
}