Skip to content

Commit

Permalink
implement linkRef and complete git info
Browse files Browse the repository at this point in the history
The code contributed to implement $build.git() was
missing integration with the caching system, which
was added in this commit.
  • Loading branch information
kristoff-it committed Oct 25, 2024
1 parent 2bcddc3 commit 0be4403
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 140 deletions.
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
.version = "0.0.0",
.dependencies = .{
.supermd = .{
.url = "git+https://github.com/kristoff-it/supermd#e82051668fa0fdc4da065fcb245e98712b4454dc",
.hash = "1220c00c9be44387976c3da3fe991ae21692407f6c85c6b6903e239fe14654bcf0f4",
.url = "git+https://github.com/kristoff-it/supermd#b6fc13bd9de0fa076abafeb1cba46da3be85b0eb",
.hash = "122053d5f01101b05bbeb13b600f53cf7bbc70c9173af674f1986580301aacbf99d2",
},
.scripty = .{
.url = "git+https://github.com/kristoff-it/scripty#df8c11380f9e9bec34809f2242fb116d27cf39d6",
Expand Down
162 changes: 162 additions & 0 deletions build/Git.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const Git = @This();

const std = @import("std");
const builtin = @import("builtin");
const ziggy = @import("ziggy");
const zeit = @import("zeit");
const Allocator = std.mem.Allocator;

_in_repo: bool = false,

commit_hash: []const u8 = undefined,
commit_date: struct {
unix: i64 = undefined,

const Self = @This();
pub const ziggy_options = struct {
pub fn stringify(
value: Self,
opts: ziggy.serializer.StringifyOptions,
indent_level: usize,
depth: usize,
writer: anytype,
) !void {
_ = opts;
_ = indent_level;
_ = depth;

const date = zeit.instant(.{
.source = .{ .unix_timestamp = value.unix },
}) catch unreachable;

try writer.print("@date(\"", .{});
date.time().gofmt(writer, "2006-01-02T15:04:05") catch unreachable;
try writer.print("\")", .{});
}
};
} = undefined,
commit_message: []const u8 = undefined,
author_name: []const u8 = undefined,
author_email: []const u8 = undefined,

_tag: ?[]const u8 = null,
_branch: ?[]const u8 = null,

pub const gitCommitHashLen = 40;

pub fn init(gpa: Allocator, path: []const u8) !Git {
var git = Git{};

var p = path;
const git_dir = while (true) {
var dir = try std.fs.openDirAbsolute(p, .{});
defer dir.close();
break dir.openDir(".git", .{}) catch |err| switch (err) {
error.FileNotFound => {
p = std.fs.path.dirname(p) orelse return git;
continue;
},
else => return err,
};
};

git._in_repo = true;

const head = readHead(gpa, git_dir) catch return Git{};
switch (head) {
.commit_hash => |hash| git.commit_hash = hash,
.branch => |branch| {
git._branch = branch;
git.commit_hash = readCommitOfBranch(gpa, git_dir, branch) catch return Git{};
},
}

git._tag = getTagForCommitHash(gpa, git_dir, git.commit_hash) catch return Git{};

git.setAdditionalMetadata(gpa, git_dir) catch return Git{};
return git;
}

fn readHead(arena: Allocator, git_dir: std.fs.Dir) !union(enum) { commit_hash: []const u8, branch: []const u8 } {
var head_file = try git_dir.openFile("HEAD", .{});
defer head_file.close();
const buf = try head_file.readToEndAlloc(arena, 4096);

if (std.mem.startsWith(u8, buf, "ref:")) {
return .{ .branch = buf[16 .. buf.len - 1] };
} else {
return .{ .commit_hash = buf[0 .. buf.len - 1] };
}
}

fn readCommitOfBranch(arena: Allocator, git_dir: std.fs.Dir, branch: []const u8) ![]const u8 {
const rel_path = switch (builtin.os.tag) {
.windows => win: {
const duped_branch = try arena.dupe(u8, branch);
defer arena.free(duped_branch);
std.mem.replaceScalar(u8, duped_branch, '/', '\\');
break :win try std.fs.path.join(arena, &.{ "refs", "heads", duped_branch });
},
else => try std.fs.path.join(arena, &.{ "refs", "heads", branch }),
};
defer arena.free(rel_path);

const content = try git_dir.readFileAlloc(arena, rel_path, gitCommitHashLen + 1);
return content[0..gitCommitHashLen];
}

fn getTagForCommitHash(arena: Allocator, git_dir: std.fs.Dir, commit_hash: []const u8) !?[]const u8 {
const rel_path = try std.fs.path.join(arena, &.{ "refs", "tags" });
defer arena.free(rel_path);

var tags = try git_dir.openDir(rel_path, .{ .iterate = true });
defer tags.close();

var iter = tags.iterate();
while (try iter.next()) |tag| {
const content = try tags.readFileAlloc(arena, tag.name, gitCommitHashLen + 1);
const tag_hash = content[0..gitCommitHashLen];

if (std.mem.eql(u8, tag_hash, commit_hash)) {
return try arena.dupe(u8, tag.name);
}
}
return null;
}

// NOTE: Does not support packed objects
fn setAdditionalMetadata(git: *Git, arena: Allocator, git_dir: std.fs.Dir) !void {
const commit_path = try std.fs.path.join(arena, &.{ "objects", git.commit_hash[0..2], git.commit_hash[2..] });
defer arena.free(commit_path);

const content = try git_dir.openFile(commit_path, .{});
var decompressed = std.compress.zlib.decompressor(content.reader());
const reader = decompressed.reader();
const data = try reader.readAllAlloc(arena, 100000);

var attributes = std.mem.splitScalar(u8, data, '\n');

_ = attributes.next(); // tree hash
_ = attributes.next(); // parent commit hash
_ = attributes.next(); // author

if (attributes.next()) |committer| {
const @"<_index" = std.mem.indexOfScalar(u8, committer, '<').?;
const @">_index" = std.mem.indexOfScalar(u8, committer, '>').?;

git.author_name = committer[10 .. @"<_index" - 1];
git.author_email = committer[@"<_index" + 1 .. @">_index"];

const unix_time = try std.fmt.parseInt(i64, committer[@">_index" + 2 .. committer.len - 6], 10);
const offset_hour = try std.fmt.parseInt(i64, committer[committer.len - 4 .. committer.len - 2], 10);
const offset = switch (committer[committer.len - 5]) {
'-' => -offset_hour,
'+' => offset_hour,
else => unreachable,
};
git.commit_date = .{ .unix = unix_time + offset * 3600 };
}

_ = attributes.next(); // empty line
git.commit_message = attributes.rest();
}
25 changes: 25 additions & 0 deletions build/content.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ fn scan(
// parent section index
const ps_index_dir = index_dir.makeOpenPath("ps", .{}) catch unreachable;

collectGitInfo(project, index_dir, project.build_root.path.?);

const assets_updater = zine_dep.artifact("update-assets");
const update_assets = project.addRunArtifact(assets_updater);
update_assets.addArg(project.install_path);
Expand Down Expand Up @@ -1105,3 +1107,26 @@ pub fn ensureDir(project: *std.Build, path: []const u8) void {
std.process.exit(1);
};
}

const Git = @import("Git.zig");
fn collectGitInfo(project: *std.Build, dir: std.fs.Dir, path: []const u8) void {
const g = Git.init(project.allocator, path) catch |err| {
std.debug.print("error while collecting git info: {s}", .{
@errorName(err),
});
std.process.exit(1);
};

const f = dir.createFile("git.ziggy", .{}) catch |err| {
std.debug.print("error while creating .zig-cache/zine/git.ziggy: {s}", .{
@errorName(err),
});
std.process.exit(1);
};
defer f.close();

var buf = std.ArrayList(u8).init(project.allocator);
ziggy.stringify(g, .{}, buf.writer()) catch unreachable;

f.writeAll(buf.items) catch unreachable;
}
30 changes: 24 additions & 6 deletions src/context/Build.zig
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
const Build = @This();

const std = @import("std");
const Allocator = std.mem.Allocator;
const scripty = @import("scripty");
const utils = @import("utils.zig");
const context = @import("../context.zig");
const DepWriter = @import("../root.zig").DepWriter;
const Signature = @import("doctypes.zig").Signature;
const Allocator = std.mem.Allocator;
const Value = context.Value;
const Optional = context.Optional;
const Signature = @import("doctypes.zig").Signature;
const uninitialized = utils.uninitialized;

pub const dot = scripty.defaultDot(Build, Value, false);
pub const PassByRef = true;

generated: context.DateTime,
_dep_writer: DepWriter,
_git_data_path: []const u8,
_git: context.Git,

pub fn init(arena: Allocator) Build {
pub fn init(
dep_writer: DepWriter,
git_data_path: []const u8,
git: context.Git,
) Build {
return .{
.generated = context.DateTime.initNow(),
._git = context.Git.init(arena),
._dep_writer = dep_writer,
._git_data_path = git_data_path,
._git = git,
};
}

Expand Down Expand Up @@ -92,7 +101,13 @@ pub const Builtins = struct {
};
if (args.len != 0) return bad_arg;

return if (build._git._in_repo) .{ .git = build._git } else .{ .err = "Not in a git repository" };
build._dep_writer.writePrereq(build._git_data_path) catch @panic("unexpected error while addind a dependency");

return if (build._git._in_repo) .{
.git = build._git,
} else .{
.err = "Not in a git repository",
};
}
};

Expand All @@ -116,7 +131,10 @@ pub const Builtins = struct {
};
if (args.len != 0) return bad_arg;

return if (build._git._in_repo) Optional.init(gpa, build._git) else Optional.Null;
return if (build._git._in_repo)
Optional.init(gpa, build._git)
else
Optional.Null;
}
};
};
109 changes: 0 additions & 109 deletions src/context/Git.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const Value = context.Value;

pub const dot = scripty.defaultDot(Git, Value, false);

pub const gitCommitHashLen = 40;

_in_repo: bool = false,

commit_hash: []const u8 = undefined,
Expand All @@ -27,113 +25,6 @@ author_email: []const u8 = undefined,
_tag: ?[]const u8 = null,
_branch: ?[]const u8 = null,

pub fn init(arena: Allocator) Git {
var git = Git{};

const git_dir = std.fs.cwd().openDir(".git", .{}) catch {
return git;
};
git._in_repo = true;

const head = readHead(arena, git_dir) catch return Git{};
switch (head) {
.commit_hash => |hash| git.commit_hash = hash,
.branch => |branch| {
git._branch = branch;
git.commit_hash = readCommitOfBranch(arena, git_dir, branch) catch return Git{};
},
}

git._tag = getTagForCommitHash(arena, git_dir, git.commit_hash) catch return Git{};

git.setAdditionalMetadata(arena, git_dir) catch return Git{};
return git;
}

fn readHead(arena: Allocator, git_dir: std.fs.Dir) !union(enum) { commit_hash: []const u8, branch: []const u8 } {
var head_file = try git_dir.openFile("HEAD", .{});
defer head_file.close();
const buf = try head_file.readToEndAlloc(arena, 4096);

if (std.mem.startsWith(u8, buf, "ref:")) {
return .{ .branch = buf[16 .. buf.len - 1] };
} else {
return .{ .commit_hash = buf[0 .. buf.len - 1] };
}
}

fn readCommitOfBranch(arena: Allocator, git_dir: std.fs.Dir, branch: []const u8) ![]const u8 {
const rel_path = switch (builtin.os.tag) {
.windows => win: {
const duped_branch = try arena.dupe(u8, branch);
defer arena.free(duped_branch);
std.mem.replaceScalar(u8, duped_branch, '/', '\\');
break :win try std.fs.path.join(arena, &.{ "refs", "heads", duped_branch });
},
else => try std.fs.path.join(arena, &.{ "refs", "heads", branch }),
};
defer arena.free(rel_path);

const content = try git_dir.readFileAlloc(arena, rel_path, gitCommitHashLen + 1);
return content[0..gitCommitHashLen];
}

fn getTagForCommitHash(arena: Allocator, git_dir: std.fs.Dir, commit_hash: []const u8) !?[]const u8 {
const rel_path = try std.fs.path.join(arena, &.{ "refs", "tags" });
defer arena.free(rel_path);

var tags = try git_dir.openDir(rel_path, .{ .iterate = true });
defer tags.close();

var iter = tags.iterate();
while (try iter.next()) |tag| {
const content = try tags.readFileAlloc(arena, tag.name, gitCommitHashLen + 1);
const tag_hash = content[0..gitCommitHashLen];

if (std.mem.eql(u8, tag_hash, commit_hash)) {
return try arena.dupe(u8, tag.name);
}
}
return null;
}

// NOTE: Does not support packed objects
fn setAdditionalMetadata(git: *Git, arena: Allocator, git_dir: std.fs.Dir) !void {
const commit_path = try std.fs.path.join(arena, &.{ "objects", git.commit_hash[0..2], git.commit_hash[2..] });
defer arena.free(commit_path);

const content = try git_dir.openFile(commit_path, .{});
var decompressed = std.compress.zlib.decompressor(content.reader());
const reader = decompressed.reader();
const data = try reader.readAllAlloc(arena, 100000);

var attributes = std.mem.splitScalar(u8, data, '\n');

_ = attributes.next(); // tree hash
_ = attributes.next(); // parent commit hash
_ = attributes.next(); // author

if (attributes.next()) |committer| {
const @"<_index" = std.mem.indexOfScalar(u8, committer, '<').?;
const @">_index" = std.mem.indexOfScalar(u8, committer, '>').?;

git.author_name = committer[10 .. @"<_index" - 1];
git.author_email = committer[@"<_index" + 1 .. @">_index"];

const unix_time = try std.fmt.parseInt(i64, committer[@">_index" + 2 .. committer.len - 6], 10);
const offset_hour = try std.fmt.parseInt(i64, committer[committer.len - 4 .. committer.len - 2], 10);
const offset = switch (committer[committer.len - 5]) {
'-' => -offset_hour,
'+' => offset_hour,
else => unreachable,
};
git.commit_date = try DateTime.initUnix(unix_time + offset * 3600);
}

_ = attributes.next(); // empty line
git.commit_message = attributes.rest();
}

pub const description =
\\Information about the current git repository.
;
Expand Down
Loading

0 comments on commit 0be4403

Please sign in to comment.