vcs-jj
Automate the Jujutsu (jj) CLI from Rust through process execution. Part of
the vcs-toolkit-rs workspace.
Typed, repo-scoped, async commands over the jj binary, behind a mockable
interface. Commands run inside an OS job (via processkit) so no jj
subprocess is ever orphaned, return the structured Error, and honour an
optional timeout.
📖 Full guide: on docs.rs
— every command by theme, result types, builder/newtype APIs, and worked examples.
Inside an async context (every method is async):
use std::path::Path;
use vcs_jj::{Jj, JjApi};
let jj = Jj::new();
let head = jj.current_change(Path::new(".")).await?; jj.describe(Path::new("."), "feat: new thing").await?;
A change workflow
use std::path::Path;
use vcs_jj::{Jj, JjApi};
# async fn demo(repo: &Path) -> Result<(), processkit::Error> {
let jj = Jj::new();
jj.describe(repo, "feat: parser").await?;
jj.new_change(repo, "wip: follow-up").await?;
let head = jj.current_change(repo).await?; println!("@ = {} ({})", head.change_id, head.description);
for c in jj.log(repo, "::@", 10).await? {
println!(
"{} {}{}",
c.change_id,
if c.empty { "(empty) " } else { "" },
c.description
);
}
# Ok(()) }
Bookmarks and syncing the git remote
# use std::path::Path;
# use vcs_jj::{Jj, JjApi};
# async fn demo(repo: &Path) -> Result<(), processkit::Error> {
let jj = Jj::new();
jj.git_fetch(repo).await?; jj.bookmark_set(repo, "main", "@").await?; for b in jj.bookmarks(repo).await? {
println!("{} -> {}", b.name, b.target);
}
jj.git_push(repo, Some("main".to_string())).await?; # Ok(()) }
Workspaces
Manage workspaces (jj's worktrees) with structured results:
use vcs_jj::{Jj, JjApi, WorkspaceAdd};
use std::path::Path;
# async fn demo(repo: &Path) -> Result<(), processkit::Error> {
let jj = Jj::new();
jj.workspace_add(repo, WorkspaceAdd::new("feature", "@", "/tmp/feature"))
.await?;
for ws in jj.workspace_list(repo).await? { println!("{} @ {} {:?}", ws.name, ws.commit, ws.bookmarks);
}
jj.workspace_forget(repo, "feature").await?;
# Ok(()) }
Timeouts
# use vcs_jj::Jj;
use std::time::Duration;
let jj = Jj::new().default_timeout(Duration::from_secs(10));
# let _ = jj;
Consumers depend on the JjApi trait and substitute a fake in tests — enable
the mock feature for a mockall-generated MockJjApi, or inject a fake
process runner with Jj::with_runner(processkit::ScriptedRunner::new()…):
use processkit::{Reply, ScriptedRunner};
use std::path::Path;
use vcs_jj::{Jj, JjApi};
# async fn demo() {
let jj = Jj::with_runner(
ScriptedRunner::new().on(["log"], Reply::ok("kztuxlro\t38e00654\tfalse\thello\n")),
);
assert_eq!(
jj.current_change(Path::new(".")).await.unwrap().description,
"hello"
);
# }
Requires the jj binary on PATH.
License
MIT