move git out of contributions
This commit is contained in:
parent
eb97480820
commit
281a49cdde
1 changed files with 10 additions and 107 deletions
|
|
@ -21,6 +21,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zfin = @import("../root.zig");
|
const zfin = @import("../root.zig");
|
||||||
const cli = @import("common.zig");
|
const cli = @import("common.zig");
|
||||||
|
const git = @import("../git.zig");
|
||||||
const fmt = cli.fmt;
|
const fmt = cli.fmt;
|
||||||
const Date = zfin.Date;
|
const Date = zfin.Date;
|
||||||
const Lot = zfin.Lot;
|
const Lot = zfin.Lot;
|
||||||
|
|
@ -44,7 +45,7 @@ pub fn run(
|
||||||
const arena = arena_state.allocator();
|
const arena = arena_state.allocator();
|
||||||
|
|
||||||
// 1. Figure out the git repo and the portfolio's path inside it.
|
// 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) {
|
switch (err) {
|
||||||
error.NotInGitRepo => try cli.stderrPrint("Error: contributions requires portfolio.srf to be in a git repo.\n"),
|
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"),
|
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.
|
// 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) {
|
if (status == .untracked) {
|
||||||
try cli.stderrPrint("Error: portfolio.srf is not tracked in git. Add and commit it first.\n");
|
try cli.stderrPrint("Error: portfolio.srf is not tracked in git. Add and commit it first.\n");
|
||||||
return;
|
return;
|
||||||
|
|
@ -63,7 +64,7 @@ pub fn run(
|
||||||
|
|
||||||
// 3. Pull both snapshots.
|
// 3. Pull both snapshots.
|
||||||
const before = if (dirty)
|
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) {
|
switch (err) {
|
||||||
error.PathMissingInRev => try cli.stderrPrint("Error: portfolio.srf not present at HEAD.\n"),
|
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"),
|
else => try cli.stderrPrint("Error reading HEAD:portfolio.srf from git.\n"),
|
||||||
|
|
@ -71,7 +72,7 @@ pub fn run(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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) {
|
switch (err) {
|
||||||
error.PathMissingInRev => try cli.stderrPrint("Error: portfolio.srf not present at HEAD~1.\n"),
|
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"),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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");
|
try cli.stderrPrint("Error reading HEAD:portfolio.srf from git.\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -141,108 +142,10 @@ pub fn run(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Git discovery / invocation ───────────────────────────────
|
// ── Git discovery / invocation ───────────────────────────────
|
||||||
|
//
|
||||||
const RepoInfo = struct {
|
// Git plumbing lives in `src/git.zig` (shared with future snapshot
|
||||||
/// Absolute path to the repo root.
|
// features). This module only classifies which revisions to diff and
|
||||||
root: []const u8,
|
// how to interpret the result.
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Diff algorithm ───────────────────────────────────────────
|
// ── Diff algorithm ───────────────────────────────────────────
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue