use crate::repo::{
Driver, DriverError, HistoryRefId, HistoryRefName, QueryDir, Validator, VcsAvailable,
ERROR_REPO_NOT_CLEAN, ERROR_REPO_NOT_DIRTY,
};
use std::path::PathBuf;
use std::process::{Command, Stdio};
#[derive(Debug)]
pub struct Repo {
dir: QueryDir,
}
static VCS_BIN_NAME: &str = "hg";
const HG_LOGID_DIRTY_SUFFIX: &str = "+";
fn start_vcs_shellout() -> Command {
let mut cmd = Command::new(VCS_BIN_NAME);
cmd.env("HGPLAIN", "1");
cmd
}
#[derive(Debug)]
pub struct Loader
where
Self: Sized;
impl Validator for Loader {
fn new_driver(&self, dir: QueryDir) -> Result<Option<Box<dyn Driver>>, DriverError> {
let repo = Repo { dir };
let is_ok = DriverError::unwrap_cmd_lossy(
"hg cli".to_string(),
repo.hg_root()
.stdout(Stdio::null())
.stderr(Stdio::null())
.output(),
)?
.status
.success();
if is_ok {
let repo: Box<dyn Driver> = Box::from(repo);
Ok(Some(repo))
} else {
Ok(None)
}
}
fn check_health(&self) -> Result<VcsAvailable, DriverError> {
let mut cmd = start_vcs_shellout();
cmd.arg("--version");
DriverError::expect_cmd_lossy("hg cli: exec".to_string(), cmd.output())
}
}
impl Repo {
fn start_shellout(&self) -> Command {
let mut cmd = start_vcs_shellout();
cmd.current_dir(self.dir.clone());
cmd
}
fn hg_root(&self) -> Command {
let mut cmd = self.start_shellout();
cmd.arg("root");
cmd
}
fn hg_dirty_files(&self) -> Command {
let mut cmd = self.start_shellout();
cmd.arg("status")
.arg("--modified")
.arg("--added")
.arg("--removed")
.arg("--deleted")
.arg("--unknown");
cmd
}
fn hg_tracked_files(&self) -> Command {
let mut cmd = self.start_shellout();
cmd.arg("status").arg("--all");
cmd
}
fn hg_current_id(&self) -> Command {
let mut cmd = self.start_shellout();
cmd.arg("--debug").arg("id").arg("--id");
cmd
}
fn hg_current_name(&self) -> Command {
let mut cmd = self.start_shellout();
cmd.arg("log")
.arg("--rev")
.arg(".")
.arg("--template")
.arg("{latesttag}");
cmd
}
}
impl Driver for Repo {
fn root(&self) -> Result<QueryDir, DriverError> {
let output =
DriverError::expect_cmd_lossy("hg cli: exec".to_string(), self.hg_root().output())?;
Ok(PathBuf::from(DriverError::expect_cmd_line(
"hg cli", &output,
)?))
}
fn dirty_files(&self, clean_ok: bool) -> Result<Vec<QueryDir>, DriverError> {
let min_lines = u8::from(!clean_ok);
let lines = DriverError::expect_cmd_lines(
self.hg_dirty_files().output(),
min_lines,
"hg cli: exec",
Some(ERROR_REPO_NOT_DIRTY.to_string()),
)?;
let dirty_files = lines
.into_iter()
.map(|ln| {
ln.chars().skip(2).collect::<String>()
})
.map(PathBuf::from)
.collect();
Ok(dirty_files)
}
fn tracked_files(&self) -> Result<Vec<QueryDir>, DriverError> {
let lines = DriverError::expect_cmd_lines(
self.hg_tracked_files().output(),
0,
"hg cli: exec",
None,
)?;
let files = lines
.into_iter()
.filter(|ln| ln.starts_with("C "))
.map(|ln| ln.chars().skip(2).collect::<String>())
.map(PathBuf::from)
.collect();
Ok(files)
}
fn current_ref_id(&self, dirty_ok: bool) -> Result<HistoryRefId, DriverError> {
if !dirty_ok && !self.is_clean()? {
return Err(ERROR_REPO_NOT_CLEAN.to_string().into());
}
let output = DriverError::expect_cmd_lossy(
"hg cli: exec".to_string(),
self.hg_current_id().output(),
)?;
let current_id = DriverError::expect_cmd_line("hg cli: exec", &output)?;
if !current_id.ends_with(HG_LOGID_DIRTY_SUFFIX) {
return Ok(current_id);
}
Ok(current_id
.strip_suffix(HG_LOGID_DIRTY_SUFFIX)
.ok_or_else(|| {
DriverError::Unknown(format!("hg bug? got just a lone '{HG_LOGID_DIRTY_SUFFIX}'"))
})?
.to_string())
}
fn current_ref_name(&self, dirty_ok: bool) -> Result<Option<HistoryRefName>, DriverError> {
if !dirty_ok && !self.is_clean()? {
return Err(ERROR_REPO_NOT_CLEAN.to_string().into());
}
let output = DriverError::expect_cmd_lossy(
"hg cli: exec".to_string(),
self.hg_current_name().output(),
)?;
let hg_tag = DriverError::expect_cmd_line("hg cli: exec", &output)?;
if hg_tag.is_empty() || hg_tag == "null" {
Ok(None)
} else {
Ok(Some(hg_tag))
}
}
}