@@ -7,6 +7,8 @@ const indexOf = std.mem.indexOf;
const zlib = std.compress.zlib;
const hexLower = std.fmt.fmtSliceHexLower;
const bufPrint = std.fmt.bufPrint;
const parseInt = std.fmt.parseInt;
const allocPrint = std.fmt.allocPrint;
const AnyReader = std.io.AnyReader;
pub const Actor = @import("git/actor.zig");
@@ -21,10 +23,14 @@ pub const Error = error{
NotAGitRepo,
RefMissing,
CommitMissing,
InvalidCommit,
BlobMissing,
TreeMissing,
InvalidTree,
ObjectMissing,
IncompleteObject,
OutOfMemory,
NoSpaceLeft,
NotImplemented,
EndOfStream,
PackCorrupt,
@@ -32,82 +38,59 @@ pub const Error = error{
AmbiguousRef,
};
const Types = enum {
commit,
blob,
tree,
pub const SHA = struct {
pub const Bin = [20]u8;
pub const Hex = [40]u8;
bin: Bin,
hex: Hex,
partial: bool = false,
pub fn init(sha: []const u8) SHA {
if (sha.len == 20) {
return .{
.bin = sha[0..20].*,
.hex = toHex(sha[0..20].*),
};
} else if (sha.len == 40) {
return .{
.bin = toBin(sha[0..40].*),
.hex = sha[0..40].*,
};
} else unreachable;
}
pub fn initPartial(_: []const u8) !SHA {}
pub fn toHex(sha: Bin) Hex {
var hex: Hex = undefined;
_ = bufPrint(&hex, "{}", .{hexLower(sha[0..])}) catch unreachable;
return hex;
}
pub fn toBin(sha: Hex) Bin {
var bin: Bin = undefined;
for (0..20) |i| {
bin[i] = parseInt(u8, sha[i * 2 .. (i + 1) * 2], 16) catch unreachable;
}
return bin;
}
};
pub const SHA = []const u8; // SUPERBAD, I'm sorry!
pub fn shaToHex(sha: []const u8, hex: []u8) void {
std.debug.assert(sha.len == 20);
std.debug.assert(hex.len == 40);
const out = std.fmt.bufPrint(hex, "{}", .{hexLower(sha)}) catch unreachable;
std.debug.assert(out.len == 40);
}
pub fn shaToBin(sha: []const u8, bin: []u8) void {
std.debug.assert(sha.len == 40);
std.debug.assert(bin.len == 20);
for (0..20) |i| {
bin[i] = std.fmt.parseInt(u8, sha[i * 2 .. (i + 1) * 2], 16) catch unreachable;
}
}
const Object = struct {
ctx: std.io.FixedBufferStream([]u8),
kind: ?Kind = null,
pub const Object = struct {
pub const Kind = enum {
blob,
tree,
commit,
ref,
tag,
};
pub const thing = union(Kind) {
blob: Blob,
tree: Tree,
commit: Commit,
ref: Ref,
};
const FBS = std.io.fixedBufferStream;
pub fn init(data: []u8) Object {
return Object{ .ctx = FBS(data) };
}
pub const ReadError = error{
Unknown,
};
pub const Reader = std.io.Reader(*Object, ReadError, read);
fn read(self: *Object, dest: []u8) ReadError!usize {
return self.ctx.read(dest) catch return ReadError.Unknown;
}
pub fn reader(self: *Object) Object.Reader {
return .{ .context = self };
}
pub fn reset(self: *Object) void {
self.ctx.pos = 0;
}
pub fn raze(self: Object, a: Allocator) void {
a.free(self.ctx.buffer);
}
kind: Kind,
memory: []u8,
header: []u8,
body: []u8,
};
// TODO AnyReader
pub const Reader = Object.Reader;
pub const FBSReader = std.io.FixedBufferStream([]u8).Reader;
const FsReader = std.fs.File.Reader;
pub const Repo = struct {
alloc: ?Allocator = null,
bare: bool,
dir: std.fs.Dir,
packs: []Pack,
@@ -162,109 +145,146 @@ pub const Repo = struct {
}
pub fn loadData(self: *Repo, a: Allocator) !void {
if (self.packs.len == 0) try self.loadPacks(a);
try self.loadRefs(a);
if (self.alloc != null) unreachable;
self.alloc = a;
try self.loadPacks();
try self.loadRefs();
try self.loadTags();
_ = try self.HEAD(a);
}
const empty_sha = [_]u8{0} ** 20;
fn loadFileObj(self: Repo, in_sha: SHA) !std.fs.File {
var sha: [40]u8 = undefined;
if (in_sha.len == 20) {
shaToHex(in_sha, &sha);
} else if (in_sha.len > 40) {
unreachable;
} else {
@memcpy(&sha, in_sha);
}
fn loadFile(self: Repo, a: Allocator, sha: SHA) !Object {
var fb = [_]u8{0} ** 2048;
var filename = try std.fmt.bufPrint(&fb, "./objects/{s}/{s}", .{ sha[0..2], sha[2..] });
return self.dir.openFile(filename, .{}) catch {
filename = try std.fmt.bufPrint(&fb, "./objects/{s}", .{sha});
return self.dir.openFile(filename, .{}) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("unable to find commit '{s}'\n", .{sha});
return err;
},
else => return err,
};
const grouped = try bufPrint(&fb, "./objects/{s}/{s}", .{ sha.hex[0..2], sha.hex[2..] });
const compressed: []u8 = self.dir.readFileAlloc(a, grouped, 0xffffff) catch |err| switch (err) {
error.FileNotFound => data: {
const exact = try bufPrint(&fb, "./objects/{s}", .{sha.hex[0..]});
break :data self.dir.readFileAlloc(a, exact, 0xffffff) catch |err2| switch (err2) {
error.FileNotFound => {
std.debug.print("unable to find commit '{s}'\n", .{sha.hex[0..]});
return error.ObjectMissing;
},
else => return err2,
};
},
else => return err,
};
defer a.free(compressed);
var fbs = std.io.fixedBufferStream(compressed);
const fbsr = fbs.reader();
var decom = zlib.decompressor(fbsr);
const decomr = decom.reader();
const data = try decomr.readAllAlloc(a, 0xffffff);
errdefer a.free(data);
if (indexOf(u8, data, "\x00")) |i| {
return .{
.memory = data,
.header = data[0..i],
.body = data[i + 1 ..],
.kind = if (startsWith(u8, data, "blob "))
.blob
else if (startsWith(u8, data, "tree "))
.tree
else if (startsWith(u8, data, "commit "))
.commit
else if (startsWith(u8, data, "tag "))
.tag
else
return error.InvalidObject,
};
} else return error.InvalidObject;
}
fn findBlobPack(self: Repo, a: Allocator, sha: SHA) !?[]u8 {
fn loadPacked(self: Repo, a: Allocator, sha: SHA) !?Object {
for (self.packs) |pack| {
if (pack.contains(sha)) |offset| {
return try pack.loadObj(a, offset, self);
return try pack.resolveObject(a, offset, &self);
}
}
return null;
}
fn findBlobPackPartial(self: Repo, a: Allocator, sha: SHA) !?[]u8 {
fn loadPackedPartial(self: Repo, a: Allocator, sha: SHA) !?Object {
std.debug.assert(sha.partial == true);
for (self.packs) |pack| {
if (try pack.containsPrefix(sha)) |offset| {
return try pack.loadObj(a, offset, self);
if (try pack.containsPrefix(sha.bin[0..])) |offset| {
return try pack.resolveObject(a, offset, &self);
}
}
return null;
}
fn findBlobFile(self: Repo, a: Allocator, sha: SHA) !?[]u8 {
if (self.loadFileObj(sha)) |fd| {
defer fd.close();
fn loadPartial(self: Repo, a: Allocator, sha: SHA) !Pack.PackedObject {
if (try self.loadPackedPartial(a, sha)) |pack| return pack;
return error.ObjectMissing;
}
var decom = zlib.decompressor(fd.reader());
var reader = decom.reader();
return try reader.readAllAlloc(a, 0xffff);
} else |_| {}
fn loadObjPartial(self: Repo, a: Allocator, sha: SHA) !?Object {
std.debug.assert(sha.partial);
if (try self.loadPackedPartial(a, sha)) |pack| return pack;
return null;
}
fn findBlobPartial(self: Repo, a: Allocator, sha: SHA) ![]u8 {
if (try self.findBlobPackPartial(a, sha)) |pack| return pack;
//if (try self.findBlobFile(a, sha)) |file| return file;
return error.ObjectMissing;
}
pub fn findBlob(self: Repo, a: Allocator, sha: SHA) ![]u8 {
std.debug.assert(sha.len == 20);
if (try self.findBlobPack(a, sha)) |pack| return pack;
if (try self.findBlobFile(a, sha)) |file| return file;
return error.ObjectMissing;
}
fn findObjPartial(self: Repo, a: Allocator, sha: SHA) !Object {
std.debug.assert(sha.len % 2 == 0);
std.debug.assert(sha.len <= 40);
var shabuffer: [20]u8 = undefined;
for (shabuffer[0 .. sha.len / 2], 0..sha.len / 2) |*s, i| {
s.* = try std.fmt.parseInt(u8, sha[i * 2 ..][0..2], 16);
pub fn loadObjectOrDelta(self: Repo, a: Allocator, sha: SHA) !union(enum) {
pack: Pack.PackedObject,
file: Object,
} {
for (self.packs) |pack| {
if (pack.contains(sha)) |offset| {
return .{ .pack = try pack.loadData(a, offset, &self) };
}
}
const shabin = shabuffer[0 .. sha.len / 2];
if (try self.findBlobPackPartial(a, shabin)) |pack| return Object.init(pack);
//if (try self.findBlobFile(a, shabin)) |file| return Object.init(file);
return error.ObjectMissing;
return .{ .file = try self.loadFile(a, sha) };
}
/// TODO binary search lol
pub fn findObj(self: Repo, a: Allocator, in_sha: SHA) !Object {
var shabin: [20]u8 = in_sha[0..20].*;
if (in_sha.len == 40) {
for (&shabin, 0..) |*s, i| {
s.* = try std.fmt.parseInt(u8, in_sha[i * 2 .. (i + 1) * 2], 16);
}
}
if (try self.findBlobPack(a, &shabin)) |pack| return Object.init(pack);
if (try self.findBlobFile(a, &shabin)) |file| return Object.init(file);
return error.ObjectMissing;
pub fn loadObject(self: Repo, a: Allocator, sha: SHA) !Object {
if (try self.loadPacked(a, sha)) |pack| return pack;
return try self.loadFile(a, sha);
}
pub fn loadPacks(self: *Repo, a: Allocator) !void {
pub fn loadBlob(self: Repo, a: Allocator, sha: SHA) !Blob {
const obj = try self.loadObject(a, sha);
switch (obj.kind) {
.blob => {
return Blob{
.memory = obj.memory,
.sha = sha,
.mode = undefined,
.name = undefined,
.data = obj.body,
};
},
.tree, .commit, .tag => unreachable,
}
}
pub fn loadTree(self: Repo, a: Allocator, sha: SHA) !Tree {
const obj = try self.loadObject(a, sha);
switch (obj.kind) {
.tree => return try Tree.initOwned(sha, a, obj),
.blob, .commit, .tag => unreachable,
}
}
pub fn loadCommit(self: Repo, a: Allocator, sha: SHA) !Commit {
const obj = try self.loadObject(a, sha);
switch (obj.kind) {
.blob, .tree, .tag => unreachable,
.commit => return try Commit.initOwned(sha, a, obj),
}
}
fn loadTag(self: *Repo, a: Allocator, sha: SHA) !Tag {
const obj = try self.loadObject(a, sha);
switch (obj.kind) {
.blob, .tree, .commit => unreachable,
.tag => return try Tag.fromSlice(sha, obj.body),
}
}
pub fn loadPacks(self: *Repo) !void {
const a = self.alloc orelse unreachable;
var dir = try self.dir.openDir("./objects/pack", .{ .iterate = true });
defer dir.close();
var itr = dir.iterate();
@@ -279,14 +299,13 @@ pub const Repo = struct {
while (try itr.next()) |file| {
if (!std.mem.eql(u8, file.name[file.name.len - 4 ..], ".idx")) continue;
self.packs[i] = try Pack.init(dir, try a.dupe(u8, file.name[0 .. file.name.len - 4]));
self.packs[i] = try Pack.init(dir, file.name[0 .. file.name.len - 4]);
i += 1;
}
}
pub fn deref() Object {}
pub fn loadRefs(self: *Repo, a: Allocator) !void {
pub fn loadRefs(self: *Repo) !void {
const a = self.alloc orelse unreachable;
var list = std.ArrayList(Ref).init(a);
var idir = try self.dir.openDir("refs/heads", .{ .iterate = true });
defer idir.close();
@@ -303,7 +322,7 @@ pub const Repo = struct {
std.debug.assert(read == 40);
try list.append(Ref{ .branch = .{
.name = try a.dupe(u8, file.name),
.sha = try a.dupe(u8, &buf),
.sha = SHA.init(&buf),
.repo = self,
} });
}
@@ -317,7 +336,7 @@ pub const Repo = struct {
if (std.mem.indexOf(u8, line, "refs/heads")) |_| {
try list.append(Ref{ .branch = .{
.name = try a.dupe(u8, line[52..]),
.sha = try a.dupe(u8, line[0..40]),
.sha = SHA.init(line[0..40]),
.repo = self,
} });
}
@@ -365,14 +384,14 @@ pub const Repo = struct {
if (std.mem.eql(u8, head[0..5], "ref: ")) {
self.head = Ref{
.branch = Branch{
.sha = self.ref(head[16 .. head.len - 1]) catch &[_]u8{0} ** 20,
.sha = self.ref(head[16 .. head.len - 1]) catch SHA.init(&[_]u8{0} ** 20),
.name = try a.dupe(u8, head[5 .. head.len - 1]),
.repo = self,
},
};
} else if (head.len == 41 and head[40] == '\n') {
self.head = Ref{
.sha = try a.dupe(u8, head[0..40]), // We don't want that \n char
.sha = SHA.init(head[0..40]), // We don't want that \n char
};
} else {
std.debug.print("unexpected HEAD {s}\n", .{head});
@@ -381,18 +400,8 @@ pub const Repo = struct {
return self.head.?;
}
fn loadTag(self: *Repo, a: Allocator, lsha: SHA) !Tag {
var sha: [20]u8 = lsha[0..20].*;
if (lsha.len == 40) {
for (&sha, 0..) |*s, i| {
s.* = try std.fmt.parseInt(u8, lsha[i * 2 .. (i + 1) * 2], 16);
}
} else if (lsha.len != 20) return error.InvalidSha;
const tag_blob = try self.findBlob(a, sha[0..]);
return try Tag.fromSlice(lsha, tag_blob);
}
pub fn loadTags(self: *Repo, a: Allocator) !void {
fn loadTags(self: *Repo) !void {
const a = self.alloc orelse unreachable;
var tagdir = try self.dir.openDir("refs/tags", .{ .iterate = true });
const pk_refs: ?[]const u8 = self.dir.readFileAlloc(a, "packed-refs", 0xffff) catch |err| switch (err) {
error.FileNotFound => null,
@@ -420,7 +429,7 @@ pub const Repo = struct {
var lines = splitScalar(u8, pkrefs, '\n');
while (lines.next()) |line| {
if (indexOf(u8, line, "refs/tags/") != null) {
self.tags.?[index] = try self.loadTag(a, line[0..40]);
self.tags.?[index] = try self.loadTag(a, SHA.init(line[0..40]));
index += 1;
}
}
@@ -440,7 +449,7 @@ pub const Repo = struct {
std.debug.print("unexpected tag format for {s}\n", .{fname});
return error.InvalidTagFound;
}
self.tags.?[index] = try self.loadTag(a, contents[0..40]);
self.tags.?[index] = try self.loadTag(a, SHA.init(contents[0..40]));
index += 1;
}
if (index != self.tags.?.len) return error.UnexpectedError;
@@ -450,20 +459,17 @@ pub const Repo = struct {
return error.NotImplemented;
}
pub fn commit(self: *const Repo, a: Allocator, request: SHA) !Commit {
const target = request;
var obj = if (request.len == 40)
try self.findObj(a, target)
pub fn commit(self: *const Repo, a: Allocator, sha: SHA) !Commit {
const obj = if (sha.partial)
try self.loadObjPartial(a, sha)
else
try self.findObjPartial(a, target);
defer obj.raze(a);
var cmt = try Commit.fromReader(a, target, obj.reader());
cmt.repo = self;
return cmt;
try self.loadObject(a, sha);
if (obj == null) return error.CommitMissing;
return try Commit.initOwned(sha, a, obj.?);
}
pub fn headCommit(self: *const Repo, a: Allocator) !Commit {
const resolv = switch (self.head.?) {
const resolv: SHA = switch (self.head.?) {
.sha => |s| s,
.branch => |b| try self.ref(b.name["refs/heads/".len..]),
.tag => return error.CommitMissing,
@@ -472,13 +478,8 @@ pub const Repo = struct {
return try self.commit(a, resolv);
}
pub fn blob(self: Repo, a: Allocator, sha: SHA) !Object {
var obj = try self.findObj(a, sha);
if (std.mem.indexOf(u8, obj.ctx.buffer, "\x00")) |i| {
return Object.init(obj.ctx.buffer[i + 1 ..]);
}
return obj;
pub fn blob(self: Repo, a: Allocator, sha: SHA) !Blob {
return try self.loadBlob(a, sha);
}
pub fn description(self: Repo, a: Allocator) ![]u8 {
@@ -489,27 +490,27 @@ pub const Repo = struct {
return error.NoDescription;
}
pub fn raze(self: *Repo, a: Allocator) void {
self.dir.close();
for (self.packs) |pack| {
pack.raze(a);
}
a.free(self.packs);
for (self.refs) |r| switch (r) {
.branch => |b| {
a.free(b.name);
a.free(b.sha);
},
else => unreachable,
};
a.free(self.refs);
if (self.current) |c| a.free(c);
if (self.head) |h| switch (h) {
.branch => |b| a.free(b.name),
else => {}, //a.free(h);
};
pub fn raze(self: *Repo) void {
if (self.alloc) |a| {
self.dir.close();
for (self.packs) |pack| {
pack.raze();
}
a.free(self.packs);
for (self.refs) |r| switch (r) {
.branch => |b| {
a.free(b.name);
},
else => unreachable,
};
a.free(self.refs);
if (self.current) |c| a.free(c);
if (self.head) |h| switch (h) {
.branch => |b| a.free(b.name),
else => {}, //a.free(h);
};
} else unreachable;
// TODO self.tags leaks, badly
}
@@ -547,11 +548,8 @@ pub const Branch = struct {
pub fn toCommit(self: Branch, a: Allocator) !Commit {
const repo = self.repo orelse return error.NoConnectedRepo;
var obj = try repo.findObj(a, self.sha);
defer obj.raze(a);
var cmt = try Commit.fromReader(a, self.sha, obj.reader());
cmt.repo = repo;
return cmt;
const obj = try repo.loadObject(a, self.sha);
return Commit.initOwned(self.sha, a, obj);
}
};
@@ -602,7 +600,7 @@ pub const Tag = struct {
return .{
.name = "[lightweight tag]",
.sha = sha,
.object = sha,
.object = sha.hex[0..],
.type = .lightweight,
.tagger = actor orelse unreachable,
.message = "",
@@ -649,13 +647,6 @@ pub const Tag = struct {
};
}
/// LOL, don't use this
fn fromReader(sha: SHA, reader: AnyReader) !Tag {
var buffer: [0xFFFF]u8 = undefined;
const len = try reader.readAll(&buffer);
return try fromSlice(sha, buffer[0..len]);
}
test fromSlice {
const blob =
\\object 73751d1c0e9eaeaafbf38a938afd652d98ee9772
@@ -683,7 +674,7 @@ pub const Tag = struct {
\\
;
const t_msg = "Yet another bugfix release for 0.7.0, especially for Samsung phones.\n";
const t = try fromSlice("c66fba80f3351a94432a662b1ecc55a21898f830", blob);
const t = try fromSlice(SHA.init("c66fba80f3351a94432a662b1ecc55a21898f830"), blob);
try std.testing.expectEqualStrings("v0.7.3", t.name);
try std.testing.expectEqualStrings("73751d1c0e9eaeaafbf38a938afd652d98ee9772", t.object);
try std.testing.expectEqual(TagType.commit, t.type);
@@ -737,44 +728,43 @@ pub fn commitishRepo(rev: []const u8, repo: Repo) bool {
}
pub const ChangeSet = struct {
alloc: Allocator,
name: []const u8,
sha: []const u8,
sha: SHA,
// Index into commit slice
commit_title: []const u8,
commit: []const u8,
timestamp: i64,
pub fn init(a: Allocator, name: []const u8, sha: []const u8, msg: []const u8, ts: i64) !ChangeSet {
pub fn init(a: Allocator, name: []const u8, sha: SHA, msg: []const u8, ts: i64) !ChangeSet {
const commit = try a.dupe(u8, msg);
return ChangeSet{
.alloc = a,
.name = try a.dupe(u8, name),
.sha = try a.dupe(u8, sha),
.sha = sha,
.commit = commit,
.commit_title = if (std.mem.indexOf(u8, commit, "\n\n")) |i| commit[0..i] else commit,
.timestamp = ts,
};
}
pub fn raze(self: ChangeSet, a: Allocator) void {
a.free(self.name);
a.free(self.sha);
a.free(self.commit);
pub fn raze(self: ChangeSet) void {
self.alloc.free(self.name);
self.alloc.free(self.commit);
}
};
test "hex tranlations" {
var hexbuf: [40]u8 = undefined;
var binbuf: [20]u8 = undefined;
const one = "370303630b3fc631a0cb3942860fb6f77446e9c1";
shaToBin(one, &binbuf);
shaToHex(&binbuf, &hexbuf);
var binbuf: [20]u8 = SHA.toBin(one.*);
var hexbuf: [40]u8 = SHA.toHex(binbuf);
try std.testing.expectEqualStrings(&binbuf, "\x37\x03\x03\x63\x0b\x3f\xc6\x31\xa0\xcb\x39\x42\x86\x0f\xb6\xf7\x74\x46\xe9\xc1");
try std.testing.expectEqualStrings(&hexbuf, one);
const two = "0000000000000000000000000000000000000000";
shaToBin(two, &binbuf);
shaToHex(&binbuf, &hexbuf);
binbuf = SHA.toBin(two.*);
hexbuf = SHA.toHex(binbuf);
try std.testing.expectEqualStrings(&binbuf, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
try std.testing.expectEqualStrings(&hexbuf, two);
@@ -788,10 +778,10 @@ test "read" {
var d = zlib.decompressor(file.reader());
const count = try d.read(&b);
//std.debug.print("{s}\n", .{b[0..count]});
const commit = try Commit.make("370303630b3fc631a0cb3942860fb6f77446e9c1", b[0..count], null);
const commit = try Commit.init(SHA.init("370303630b3fc631a0cb3942860fb6f77446e9c1"), b[11 .. count - 11]);
//std.debug.print("{}\n", .{commit});
try std.testing.expectEqualStrings("fcb6817b0efc397f1525ff7ee375e08703ed17a9", commit.tree);
try std.testing.expectEqualStrings("370303630b3fc631a0cb3942860fb6f77446e9c1", commit.sha);
try std.testing.expectEqualStrings("fcb6817b0efc397f1525ff7ee375e08703ed17a9", commit.tree.hex[0..]);
try std.testing.expectEqualStrings("370303630b3fc631a0cb3942860fb6f77446e9c1", commit.sha.hex[0..]);
}
test "file" {
@@ -801,20 +791,20 @@ test "file" {
var file = try cwd.openFile("./.git/objects/37/0303630b3fc631a0cb3942860fb6f77446e9c1", .{});
var d = zlib.decompressor(file.reader());
const dz = try d.reader().readAllAlloc(a, 0xffff);
var buffer = Object.init(dz);
defer buffer.raze(a);
const commit = try Commit.fromReader(a, "370303630b3fc631a0cb3942860fb6f77446e9c1", buffer.reader());
defer commit.raze();
defer a.free(dz);
const blob = dz[(indexOf(u8, dz, "\x00") orelse unreachable) + 1 ..];
var commit = try Commit.init(SHA.init("370303630b3fc631a0cb3942860fb6f77446e9c1"), blob);
//defer commit.raze();
//std.debug.print("{}\n", .{commit});
try std.testing.expectEqualStrings("fcb6817b0efc397f1525ff7ee375e08703ed17a9", commit.tree);
try std.testing.expectEqualStrings("370303630b3fc631a0cb3942860fb6f77446e9c1", commit.sha);
try std.testing.expectEqualStrings("fcb6817b0efc397f1525ff7ee375e08703ed17a9", commit.tree.hex[0..]);
try std.testing.expectEqualStrings("370303630b3fc631a0cb3942860fb6f77446e9c1", commit.sha.hex[0..]);
}
test "not gpg" {
const null_sha = "0000000000000000000000000000000000000000";
const null_sha = SHA.init("0000000000000000000000000000000000000000");
const blob_invalid_0 =
\\tree 0000bb21f5276fd4f3611a890d12312312415434
\\parent ffffff8bd96b1abaceaa3298374abo82f4239948
\\parent ffffff8bd96b1abaceaa3298374ab082f4239948
\\author Some Dude <some@email.com> 1687200000 -0700
\\committer Some Dude <some@email.com> 1687200000 -0700
\\gpgsig -----BEGIN SSH SIGNATURE-----
@@ -826,8 +816,8 @@ test "not gpg" {
\\
\\commit message
;
const commit = try Commit.make(null_sha, blob_invalid_0, null);
try std.testing.expect(commit.sha.ptr == null_sha.ptr);
const commit = try Commit.init(null_sha, blob_invalid_0);
try std.testing.expect(eql(u8, commit.sha.bin[0..], null_sha.bin[0..]));
}
test "toParent" {
@@ -835,7 +825,7 @@ test "toParent" {
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
var commit = try repo.headCommit(a);
@@ -843,8 +833,9 @@ test "toParent" {
while (true) {
count += 1;
if (commit.parent[0]) |_| {
const parent = try commit.toParent(a, 0);
const parent = try commit.toParent(a, 0, &repo);
commit.raze();
commit = parent;
} else break;
}
@@ -852,21 +843,6 @@ test "toParent" {
try std.testing.expect(count >= 31); // LOL SORRY!
}
test "tree" {
var a = std.testing.allocator;
var cwd = std.fs.cwd();
var file = try cwd.openFile("./.git/objects/37/0303630b3fc631a0cb3942860fb6f77446e9c1", .{});
var _zlib = zlib.decompressor(file.reader());
var reader = _zlib.reader();
const data = try reader.readAllAlloc(a, 0xffff);
defer a.free(data);
var buffer = Object.init(data);
const commit = try Commit.fromReader(a, "370303630b3fc631a0cb3942860fb6f77446e9c1", buffer.reader());
defer commit.raze();
//std.debug.print("tree {s}\n", .{commit.sha});
}
test "tree decom" {
var a = std.testing.allocator;
@@ -877,10 +853,13 @@ test "tree decom" {
var d = zlib.decompressor(file.reader());
const count = try d.read(&b);
const buf = try a.dupe(u8, b[0..count]);
var tree = try Tree.make(a, "5edabf724389ef87fa5a5ddb2ebe6dbd888885ae", buf);
defer tree.raze(a);
for (tree.objects) |obj| {
if (false) std.debug.print("{s} {s} {s}\n", .{ obj.mode, obj.hash, obj.name });
defer a.free(buf);
const blob = buf[(indexOf(u8, buf, "\x00") orelse unreachable) + 1 ..];
//std.debug.print("{s}\n", .{buf});
const tree = try Tree.init(SHA.init("5edabf724389ef87fa5a5ddb2ebe6dbd888885ae"), a, blob);
defer tree.raze();
for (tree.blobs) |tobj| {
if (false) std.debug.print("{s} {s} {s}\n", .{ tobj.mode, tobj.hash, tobj.name });
}
if (false) std.debug.print("{}\n", .{tree});
}
@@ -906,9 +885,9 @@ test "read pack" {
var cwd = std.fs.cwd();
const dir = try cwd.openDir("repos/hastur", .{});
var repo = try Repo.init(dir);
defer repo.raze(a);
defer repo.raze();
try repo.loadPacks(a);
try repo.loadData(a);
var lol: []u8 = "";
for (repo.packs, 0..) |pack, pi| {
@@ -922,11 +901,10 @@ test "read pack" {
}
}
}
var obj = try repo.findObj(a, lol);
defer obj.raze(a);
const commit = try Commit.fromReader(a, lol, obj.reader());
defer commit.raze();
if (false) std.debug.print("{}\n", .{commit});
const obj = try repo.loadObject(a, SHA.init(lol));
defer a.free(obj.memory);
try std.testing.expect(obj.kind == .commit);
if (false) std.debug.print("{}\n", .{obj});
}
test "pack contains" {
@@ -934,30 +912,26 @@ test "pack contains" {
var cwd = std.fs.cwd();
const dir = try cwd.openDir("repos/srctree", .{});
var repo = try Repo.init(dir);
try repo.loadPacks(a);
defer repo.raze(a);
try repo.loadData(a);
defer repo.raze();
const sha = "7d4786ded56e1ee6cfe72c7986218e234961d03c";
var shabin: [20]u8 = undefined;
for (&shabin, 0..) |*s, i| {
s.* = try std.fmt.parseInt(u8, sha[i * 2 ..][0..2], 16);
}
const sha = SHA.init("7d4786ded56e1ee6cfe72c7986218e234961d03c");
var found: bool = false;
for (repo.packs) |pack| {
found = pack.contains(shabin[0..20]) != null;
found = pack.contains(sha) != null;
if (found) break;
}
try std.testing.expect(found);
found = false;
for (repo.packs) |pack| {
found = try pack.containsPrefix(shabin[0..10]) != null;
found = try pack.containsPrefix(sha.bin[0..10]) != null;
if (found) break;
}
try std.testing.expect(found);
const err = repo.packs[0].containsPrefix(shabin[0..1]);
const err = repo.packs[0].containsPrefix(sha.bin[0..1]);
try std.testing.expectError(error.AmbiguousRef, err);
//var long_obj = try repo.findObj(a, lol);
@@ -968,18 +942,16 @@ test "hopefully a delta" {
var cwd = std.fs.cwd();
const dir = try cwd.openDir("repos/hastur", .{});
var repo = try Repo.init(dir);
defer repo.raze(a);
try repo.loadData(a);
defer repo.raze();
var head = try repo.headCommit(a);
defer head.raze();
//std.debug.print("{}\n", .{head});
if (false) std.debug.print("{}\n", .{head});
var obj = try repo.findObj(a, head.tree);
defer obj.raze(a);
const tree = try Tree.fromReader(a, head.tree, obj.reader());
tree.raze(a);
const obj = try repo.loadPacked(a, head.tree);
const tree = try Tree.initOwned(head.tree, a, obj.?);
tree.raze();
if (false) std.debug.print("{}\n", .{tree});
}
@@ -987,14 +959,14 @@ test "commit to tree" {
const a = std.testing.allocator;
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
const cmt = try repo.headCommit(a);
defer cmt.raze();
const tree = try cmt.mkTree(a);
defer tree.raze(a);
const tree = try cmt.mkTree(a, &repo);
defer tree.raze();
if (false) std.debug.print("tree {}\n", .{tree});
if (false) for (tree.objects) |obj| std.debug.print(" {}\n", .{obj});
}
@@ -1004,21 +976,20 @@ test "blob to commit" {
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
try repo.loadData(a);
defer repo.raze();
const cmtt = try repo.headCommit(a);
defer cmtt.raze();
const tree = try cmtt.mkTree(a);
defer tree.raze(a);
const tree = try cmtt.mkTree(a, &repo);
defer tree.raze();
var timer = try std.time.Timer.start();
var lap = timer.lap();
const found = try tree.changedSet(a, &repo);
if (false) std.debug.print("found {any}\n", .{found});
for (found) |f| f.raze(a);
for (found) |f| f.raze();
a.free(found);
lap = timer.lap();
if (false) std.debug.print("timer {}\n", .{lap});
@@ -1029,26 +1000,26 @@ test "mk sub tree" {
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
const cmtt = try repo.headCommit(a);
defer cmtt.raze();
const tree = try cmtt.mkTree(a);
defer tree.raze(a);
var tree = try cmtt.mkTree(a, &repo);
defer tree.raze();
var blob: Blob = blb: for (tree.objects) |obj| {
var blob: Blob = blb: for (tree.blobs) |obj| {
if (std.mem.eql(u8, obj.name, "src")) break :blb obj;
} else return error.ExpectedBlobMissing;
var subtree = try blob.toTree(a, repo);
var subtree = try blob.toTree(a, &repo);
if (false) std.debug.print("{any}\n", .{subtree});
for (subtree.objects) |obj| {
for (subtree.blobs) |obj| {
if (false) std.debug.print("{any}\n", .{obj});
}
subtree.raze(a);
subtree.raze();
}
test "commit mk sub tree" {
@@ -1056,40 +1027,40 @@ test "commit mk sub tree" {
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
const cmtt = try repo.headCommit(a);
defer cmtt.raze();
const tree = try cmtt.mkTree(a);
defer tree.raze(a);
var tree = try cmtt.mkTree(a, &repo);
defer tree.raze();
var blob: Blob = blb: for (tree.objects) |obj| {
var blob: Blob = blb: for (tree.blobs) |obj| {
if (std.mem.eql(u8, obj.name, "src")) break :blb obj;
} else return error.ExpectedBlobMissing;
var subtree = try blob.toTree(a, repo);
var subtree = try blob.toTree(a, &repo);
if (false) std.debug.print("{any}\n", .{subtree});
for (subtree.objects) |obj| {
for (subtree.blobs) |obj| {
if (false) std.debug.print("{any}\n", .{obj});
}
defer subtree.raze(a);
defer subtree.raze();
const csubtree = try cmtt.mkSubTree(a, "src");
const csubtree = try cmtt.mkSubTree(a, "src", &repo);
if (false) std.debug.print("{any}\n", .{csubtree});
csubtree.raze(a);
csubtree.raze();
const csubtree2 = try cmtt.mkSubTree(a, "src/endpoints");
const csubtree2 = try cmtt.mkSubTree(a, "src/endpoints", &repo);
if (false) std.debug.print("{any}\n", .{csubtree2});
if (false) for (csubtree2.objects) |obj|
std.debug.print("{any}\n", .{obj});
defer csubtree2.raze(a);
defer csubtree2.raze();
const changed = try csubtree2.changedSet(a, &repo);
for (csubtree2.objects, changed) |o, c| {
for (csubtree2.blobs, changed) |o, c| {
if (false) std.debug.print("{s} {s}\n", .{ o.name, c.sha });
c.raze(a);
c.raze();
}
a.free(changed);
}
@@ -1102,7 +1073,7 @@ test "considering optimizing blob to commit" {
////var repo = try Repo.init(cwd);
//var timer = try std.time.Timer.start();
//defer repo.raze(a);
//defer repo.raze();
//try repo.loadPacks(a);
@@ -1209,21 +1180,21 @@ test "ref delta" {
const dir = cwd.openDir("repos/hastur", .{}) catch return error.skip;
var repo = try Repo.init(dir);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
const cmtt = try repo.headCommit(a);
defer cmtt.raze();
const tree = try cmtt.mkTree(a);
defer tree.raze(a);
const tree = try cmtt.mkTree(a, &repo);
defer tree.raze();
var timer = try std.time.Timer.start();
var lap = timer.lap();
const found = try tree.changedSet(a, &repo);
if (false) std.debug.print("found {any}\n", .{found});
for (found) |f| f.raze(a);
for (found) |f| f.raze();
a.free(found);
lap = timer.lap();
if (false) std.debug.print("timer {}\n", .{lap});
@@ -1252,7 +1223,7 @@ test "new repo" {
var new_repo = try Repo.createNew(a, tdir.dir, "new_repo");
_ = try tdir.dir.openDir("new_repo", .{});
try new_repo.loadData(a);
defer new_repo.raze(a);
defer new_repo.raze();
}
test "updated at" {
@@ -1260,7 +1231,7 @@ test "updated at" {
const cwd = try std.fs.cwd().openDir(".", .{});
var repo = try Repo.init(cwd);
defer repo.raze(a);
defer repo.raze();
try repo.loadData(a);
const oldest = try repo.updatedAt(a);