move git out of contributions
This commit is contained in:
parent
b717c9fa3d
commit
f134e701d0
1 changed files with 10 additions and 107 deletions
|
|
@ -21,6 +21,7 @@
|
|||
const std = @import("std");
|
||||
const zfin = @import("../root.zig");
|
||||
const cli = @import("common.zig");
|
||||
const git = @import("../git.zig");
|
||||
const fmt = cli.fmt;
|
||||
const Date = zfin.Date;
|
||||
const Lot = zfin.Lot;
|
||||
|
|
@ -44,7 +45,7 @@ pub fn run(
|
|||
const arena = arena_state.allocator();
|
||||
|
||||
// 1. Figure out the git repo and the portfolio's path inside it.
|
||||
const repo = findGitRepo(arena, portfolio_path) catch |err| {
|
||||
const repo = git.findRepo(arena, portfolio_path) catch |err| {
|
||||
switch (err) {
|
||||
error.NotInGitRepo => try cli.stderrPrint("Error: contributions requires portfolio.srf to be in a git repo.\n"),
|
||||
error.GitUnavailable => try cli.stderrPrint("Error: could not run 'git'. Is git installed and on PATH?\n"),
|
||||
|
|
@ -54,7 +55,7 @@ pub fn run(
|
|||
};
|
||||
|
||||
// 2. Decide which snapshots to compare.
|
||||
const status = try pathStatus(arena, repo.root, repo.rel_path);
|
||||
const status = try git.pathStatus(arena, repo.root, repo.rel_path);
|
||||
if (status == .untracked) {
|
||||
try cli.stderrPrint("Error: portfolio.srf is not tracked in git. Add and commit it first.\n");
|
||||
return;
|
||||
|
|
@ -63,7 +64,7 @@ pub fn run(
|
|||
|
||||
// 3. Pull both snapshots.
|
||||
const before = if (dirty)
|
||||
gitShow(arena, repo.root, "HEAD", repo.rel_path) catch |err| {
|
||||
git.show(arena, repo.root, "HEAD", repo.rel_path) catch |err| {
|
||||
switch (err) {
|
||||
error.PathMissingInRev => try cli.stderrPrint("Error: portfolio.srf not present at HEAD.\n"),
|
||||
else => try cli.stderrPrint("Error reading HEAD:portfolio.srf from git.\n"),
|
||||
|
|
@ -71,7 +72,7 @@ pub fn run(
|
|||
return;
|
||||
}
|
||||
else
|
||||
gitShow(arena, repo.root, "HEAD~1", repo.rel_path) catch |err| {
|
||||
git.show(arena, repo.root, "HEAD~1", repo.rel_path) catch |err| {
|
||||
switch (err) {
|
||||
error.PathMissingInRev => try cli.stderrPrint("Error: portfolio.srf not present at HEAD~1.\n"),
|
||||
error.UnknownRevision => try cli.stderrPrint("Error: no prior commit to compare against (HEAD~1 does not exist).\n"),
|
||||
|
|
@ -86,7 +87,7 @@ pub fn run(
|
|||
return;
|
||||
}
|
||||
else
|
||||
gitShow(arena, repo.root, "HEAD", repo.rel_path) catch {
|
||||
git.show(arena, repo.root, "HEAD", repo.rel_path) catch {
|
||||
try cli.stderrPrint("Error reading HEAD:portfolio.srf from git.\n");
|
||||
return;
|
||||
};
|
||||
|
|
@ -141,108 +142,10 @@ pub fn run(
|
|||
}
|
||||
|
||||
// ── Git discovery / invocation ───────────────────────────────
|
||||
|
||||
const RepoInfo = struct {
|
||||
/// Absolute path to the repo root.
|
||||
root: []const u8,
|
||||
/// Relative path from root to the portfolio file (using '/'-style separators).
|
||||
rel_path: []const u8,
|
||||
};
|
||||
|
||||
fn findGitRepo(allocator: std.mem.Allocator, portfolio_path: []const u8) !RepoInfo {
|
||||
// Resolve the portfolio file's directory.
|
||||
const abs_path = try std.fs.cwd().realpathAlloc(allocator, portfolio_path);
|
||||
const dir = std.fs.path.dirname(abs_path) orelse "/";
|
||||
|
||||
// git -C <dir> rev-parse --show-toplevel
|
||||
const result = std.process.Child.run(.{
|
||||
.allocator = allocator,
|
||||
.argv = &.{ "git", "-C", dir, "rev-parse", "--show-toplevel" },
|
||||
.max_output_bytes = 64 * 1024,
|
||||
}) catch {
|
||||
return error.GitUnavailable;
|
||||
};
|
||||
|
||||
switch (result.term) {
|
||||
.Exited => |code| if (code != 0) return error.NotInGitRepo,
|
||||
else => return error.NotInGitRepo,
|
||||
}
|
||||
|
||||
const root_raw = std.mem.trim(u8, result.stdout, " \t\r\n");
|
||||
const root = try allocator.dupe(u8, root_raw);
|
||||
|
||||
// Relative path from root to the portfolio file.
|
||||
const rel_raw = if (std.mem.startsWith(u8, abs_path, root) and abs_path.len > root.len)
|
||||
std.mem.trimLeft(u8, abs_path[root.len..], "/")
|
||||
else
|
||||
std.fs.path.basename(abs_path);
|
||||
const rel = try allocator.dupe(u8, rel_raw);
|
||||
|
||||
return .{ .root = root, .rel_path = rel };
|
||||
}
|
||||
|
||||
/// Inspect the portfolio file's git status. Only the portfolio file is
|
||||
/// considered (via pathspec); untracked files elsewhere in the repo are
|
||||
/// ignored. Returns one of:
|
||||
/// - `.modified`: tracked and has unstaged and/or staged changes
|
||||
/// - `.clean`: tracked and matches HEAD (no uncommitted changes)
|
||||
/// - `.untracked`: not yet added to the repo (no HEAD version to diff against)
|
||||
const PathStatus = enum { modified, clean, untracked };
|
||||
|
||||
fn pathStatus(allocator: std.mem.Allocator, root: []const u8, rel_path: []const u8) !PathStatus {
|
||||
const result = std.process.Child.run(.{
|
||||
.allocator = allocator,
|
||||
.argv = &.{ "git", "-C", root, "status", "--porcelain", "--", rel_path },
|
||||
.max_output_bytes = 64 * 1024,
|
||||
}) catch return error.GitUnavailable;
|
||||
|
||||
switch (result.term) {
|
||||
.Exited => |code| if (code != 0) return error.GitStatusFailed,
|
||||
else => return error.GitStatusFailed,
|
||||
}
|
||||
|
||||
const trimmed = std.mem.trim(u8, result.stdout, " \t\r\n");
|
||||
if (trimmed.len == 0) return .clean;
|
||||
// Porcelain format: "XY <path>" where X is index status, Y is worktree status.
|
||||
// "??" means untracked. Anything else with at least one non-space is a modification.
|
||||
if (trimmed.len >= 2 and trimmed[0] == '?' and trimmed[1] == '?') return .untracked;
|
||||
return .modified;
|
||||
}
|
||||
|
||||
/// Run `git show <rev>:<path>` and return the stdout bytes.
|
||||
fn gitShow(allocator: std.mem.Allocator, root: []const u8, rev: []const u8, rel_path: []const u8) ![]const u8 {
|
||||
// Build "<rev>:<rel_path>".
|
||||
const spec = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ rev, rel_path });
|
||||
|
||||
const result = std.process.Child.run(.{
|
||||
.allocator = allocator,
|
||||
.argv = &.{ "git", "-C", root, "show", spec },
|
||||
.max_output_bytes = 32 * 1024 * 1024,
|
||||
}) catch return error.GitUnavailable;
|
||||
|
||||
switch (result.term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
// Distinguish "no such revision" from "path missing".
|
||||
if (std.mem.indexOf(u8, result.stderr, "unknown revision") != null or
|
||||
std.mem.indexOf(u8, result.stderr, "bad revision") != null or
|
||||
std.mem.indexOf(u8, result.stderr, "ambiguous argument") != null)
|
||||
{
|
||||
return error.UnknownRevision;
|
||||
}
|
||||
if (std.mem.indexOf(u8, result.stderr, "does not exist") != null or
|
||||
std.mem.indexOf(u8, result.stderr, "exists on disk, but not in") != null)
|
||||
{
|
||||
return error.PathMissingInRev;
|
||||
}
|
||||
return error.GitShowFailed;
|
||||
}
|
||||
},
|
||||
else => return error.GitShowFailed,
|
||||
}
|
||||
|
||||
return result.stdout;
|
||||
}
|
||||
//
|
||||
// Git plumbing lives in `src/git.zig` (shared with future snapshot
|
||||
// features). This module only classifies which revisions to diff and
|
||||
// how to interpret the result.
|
||||
|
||||
// ── Diff algorithm ───────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue