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.
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