vcs-core 0.6.0

One async Rust API over Git and Jujutsu: repo auto-detection plus a unified, backend-agnostic VCS handle (status, branches, worktrees, snapshots).
Documentation

vcs-core — one repository API for git and jj

crates.io docs.rs downloads

Part of the vcs-toolkit-rs workspace.

What you can do: hold one Repo handle that auto-detects whether a directory is a git or a jj checkout, then run whatever both tools support — current branch, a batched status snapshot, changed files & diff, commit paths, fetch/push/checkout/ rebase, a conflict-probe merge, in-progress merge/rebase state, and worktrees — all returning plain result types that don't mention the backend.

How it works: it drives the vcs-git / vcs-jj clients under the hood and exposes only the shared operations. Tool-specific power (a full merge, jj's op restore, range/revset queries) stays on the raw client, reachable via Repo::git() / Repo::jj().

📖 Full guide: on docs.rs — detection, the unified facade surface, the DTOs, and when to drop to the raw client.

What it gives you

  • detect(dir) -> Option<Located> — walk up from dir to find a .git/.jj repository. A valid .jj (one containing its repo store) wins over .git (colocated repos are driven through jj); both markers are validated, so a stray .jj/.git doesn't shadow a real repo. Pure filesystem probing, no subprocess.
  • Repo — a cwd-bound handle. Open it once, then call the common surface without threading a directory through every call:
use vcs_core::Repo;

# fn main() -> vcs_core::Result<()> {
let repo = Repo::open(".")?;
println!("backend: {}", repo.kind().as_str());
# Ok(())
# }

Common surface

current_branch, trunk, changed_files, diff_stat, commit_paths, fetch, push, list_worktrees, create_worktree, remove_worktree — each returning plain result types (FileChange, DiffStat, WorktreeInfo, RepoSnapshot with its bundled tracking, …) that don't mention git or jj. Re-anchor a handle to a sibling directory with repo.at(other_dir).

The error type wraps processkit::Error (re-exported as vcs_core::processkit, so you can match it without a direct dependency) and carries intent classifiers — is_merge_conflict / is_nothing_to_commit / is_transient_fetch_error / is_transient / is_not_found.

Testing

Repo is generic over processkit::ProcessRunner. Build one from an explicit client (Repo::from_git / Repo::from_jj) with a ScriptedRunner to test dispatch hermetically, exactly as the underlying crates do.