use std::path::{Path, PathBuf};
use std::process::Command;
use crate::error::{JjHooksError, Result};
#[derive(Debug, Clone)]
pub struct JjCli {
cwd: PathBuf,
}
impl JjCli {
pub fn new(cwd: impl Into<PathBuf>) -> Self {
Self { cwd: cwd.into() }
}
pub fn cwd(&self) -> &Path {
&self.cwd
}
pub fn run(&self, args: &[&str]) -> Result<String> {
self.run_inner(args, false)
}
pub fn run_capture_stderr(&self, args: &[&str]) -> Result<String> {
self.run_inner(args, true)
}
fn run_inner(&self, args: &[&str], capture_stderr_always: bool) -> Result<String> {
let output = Command::new("jj")
.args(args)
.args(["--color", "never"])
.current_dir(&self.cwd)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
return Err(JjHooksError::JjFailed {
status: output.status.code().unwrap_or(-1),
stderr,
});
}
if capture_stderr_always {
let mut combined = String::from_utf8_lossy(&output.stdout).into_owned();
combined.push_str(&String::from_utf8_lossy(&output.stderr));
Ok(combined)
} else {
if !output.stderr.is_empty() {
eprint!("{}", String::from_utf8_lossy(&output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
}
pub fn workspace_root(&self) -> Result<PathBuf> {
let out = self.run(&["workspace", "root", "--ignore-working-copy"])?;
Ok(PathBuf::from(out.trim()).canonicalize()?)
}
}
pub fn primary_git_dir(workspace_root: &Path) -> Result<PathBuf> {
let repo = resolve_repo_dir(workspace_root)?;
let store = repo.join("store");
let target_file = store.join("git_target");
let relative = std::fs::read_to_string(&target_file)?;
let relative = relative.trim();
let resolved = store.join(relative).canonicalize().map_err(|e| {
JjHooksError::Io(std::io::Error::new(
e.kind(),
format!(
"could not resolve primary git dir from {} (contents: {relative:?}): {e}",
target_file.display()
),
))
})?;
Ok(resolved)
}
fn resolve_repo_dir(workspace_root: &Path) -> Result<PathBuf> {
let repo = workspace_root.join(".jj/repo");
let meta = std::fs::metadata(&repo)?;
if meta.is_dir() {
return Ok(repo.canonicalize()?);
}
let pointer = std::fs::read_to_string(&repo)?;
let pointer = pointer.trim();
Ok(repo
.parent()
.expect(".jj/repo always has a parent")
.join(pointer)
.canonicalize()?)
}