repoforge 0.1.6

Safe repository archive discovery, GitHub slugging, and fast-forward refresh helpers
Documentation

RepoForge

RepoForge provides repository archive discovery and safe refresh helpers used by thesa.

Made by Trevor Knott for Knott Dynamics. RepoForge is part of the thesa archive stack alongside modelforge, siteforge, Scrin, and Aisling.

The crate is intentionally focused. It scans an archive root for Git worktrees, derives stable owner/repo slugs from GitHub remotes or archive paths, and refreshes clones with git pull --ff-only without touching dirty worktrees.

RepoForge is a library crate, not a full CLI. It gives applications the safe Git operations and deterministic archive identifiers they need while leaving UI, manifest writing, and policy decisions to the caller.

What It Does

  • Finds Git worktrees recursively under an archive root.
  • Converts GitHub remotes into stable owner/repo slugs.
  • Falls back to archive path slugs when remotes are missing or non-GitHub.
  • Restricts discovery to one GitHub owner/user/org when requested.
  • Filters discovered archives by slug or path text.
  • Refuses dirty worktrees before refresh.
  • Runs git pull --ff-only for clean worktrees.
  • Returns structured updated, unchanged, and failed outcomes.

What It Does Not Do

  • Does not delete repositories.
  • Does not reset, checkout, rebase, merge, stash, or force-pull.
  • Does not write archive manifests or checksums.
  • Does not assume a terminal UI.
  • Does not require GitHub API credentials.

Install

[dependencies]
repoforge = "0.1"

The package and library crate name are both repoforge.

Archive Layout

RepoForge is designed for archive trees like this:

archives/
  octocat/
    Hello-World/
      .git/
  rust-lang/
    rust/
      .git/

When remote.origin.url is a GitHub URL, the slug is derived from the remote. For example https://github.com/octocat/Hello-World.git becomes octocat/Hello-World.

If there is no GitHub remote, RepoForge falls back to the path layout. For example archives/octocat/Hello-World becomes octocat/Hello-World.

Discovery

use std::path::Path;

let archives = repoforge::discover_git_archives(Path::new("./archives"), None)?;
for archive in archives {
    println!("{} -> {}", archive.slug, archive.path.display());
}
# Ok::<(), repoforge::RepoForgeError>(())

Use a filter to limit results by slug or path:

let archives = repoforge::discover_git_archives(
    std::path::Path::new("./archives"),
    Some("octocat"),
)?;
# Ok::<(), repoforge::RepoForgeError>(())

Discovery is recursive but stops descending when it finds a Git worktree. .git, .thesa, and target directories are skipped.

Discover only one GitHub owner/user/org:

let archives = repoforge::discover_git_archives_for_owner(
    std::path::Path::new("./archives"),
    "octocat",
    None,
)?;

assert!(archives.iter().all(|archive| archive.matches_owner("octocat")));
# Ok::<(), repoforge::RepoForgeError>(())

Owner matching is case-insensitive and uses the derived owner/repo slug. This works for GitHub remote-derived slugs and archive layouts like archives/<owner>/<repo>.

API Surface

  • discover_git_archives(root, filter): find archive worktrees.
  • discover_git_archives_for_owner(root, owner, filter): find worktrees for one GitHub owner/user/org.
  • refresh_git_archives(archives, concurrency): refresh many worktrees.
  • refresh_one_git_archive(archive): refresh one worktree.
  • parse_github_remote_slug(remote_url): parse GitHub remotes into owner/repo.
  • split_owner_repo_slug(slug): borrow validated owner and repo parts from an owner/repo slug.
  • fallback_archive_slug(root, repo_path): derive a slug from archive layout.
  • ensure_clean_git_worktree(archive): fail if local changes exist.
  • git_head(archive): read the current HEAD hash.
  • run_git_capture(repo_path, args): run low-level Git commands with RepoForge errors.

Core data types:

  • GitArchiveCandidate: discovered worktree path, slug, and optional remote.
  • GitArchiveCandidate::slug_parts(): borrow validated (owner, repo) slug parts.
  • GitArchiveCandidate::owner(): borrow the slug owner.
  • GitArchiveCandidate::repo_name(): borrow the slug repository name.
  • GitArchiveCandidate::matches_owner(owner): case-insensitive owner match.
  • GitRefreshSummary: aggregate counts, successful entries, and failures.
  • GitRefreshStatus: Updated or Unchanged.
  • RepoForgeError: typed errors for non-directory roots, missing Git, dirty trees, Git command failures, and I/O.

Refresh

let archives = repoforge::discover_git_archives(std::path::Path::new("./archives"), None)?;
let summary = repoforge::refresh_git_archives(&archives, 4);

println!(
    "updated={} unchanged={} failed={}",
    summary.updated,
    summary.unchanged,
    summary.failed.len()
);

if summary.has_failures() {
    for failure in &summary.failed {
        eprintln!("{}: {}", failure.slug, failure.message);
    }
}
# Ok::<(), repoforge::RepoForgeError>(())

Refresh behavior:

  • Runs git status --porcelain before pulling.
  • Dirty worktrees fail fast and are not pulled.
  • Runs git pull --ff-only for clean worktrees.
  • Reports Updated when HEAD changes.
  • Reports Unchanged when the pull succeeds without changing HEAD.
  • Uses bounded concurrency with refresh_git_archives.

Slug Helpers

assert_eq!(
    repoforge::parse_github_remote_slug("git@github.com:octocat/Hello-World.git"),
    Some("octocat/Hello-World".to_string())
);

Supported GitHub remote forms include HTTPS and SSH:

  • https://github.com/owner/repo.git
  • http://github.com/owner/repo.git
  • git@github.com:owner/repo.git
  • ssh://git@github.com/owner/repo.git

Safety Contract

RepoForge does not delete, reset, checkout, stash, or force-pull anything. It only uses read-only Git inspection commands plus git pull --ff-only after confirming the worktree is clean.

Callers such as thesa remain responsible for archive metadata sidecars, checksums, and higher-level reporting.

Package Contents

Cargo releases are explicitly whitelisted to avoid shipping sessions, notes, build output, or local archives. The package contains only:

  • Cargo.toml
  • Cargo.lock
  • README.md
  • LICENSE
  • CHANGELOG.md
  • src/**

Role In Thesa

thesa uses RepoForge for Git archive discovery, GitHub slugging, dirty-tree protection, and fast-forward-only refreshes. thesa then wraps those outcomes with Scrin/Aisling terminal reporting and .thesa integrity manifests.