use crate::error::{Error, Result};
use std::path::Path;
use std::process::Command;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VcsType {
Git,
Jj,
JjColocated,
}
pub(crate) fn detect_vcs() -> Result<VcsType> {
let current_dir = std::env::current_dir()?;
detect_vcs_at(¤t_dir)
}
pub(crate) fn detect_vcs_at(path: &Path) -> Result<VcsType> {
if let Some(vcs_type) = find_vcs_root(path) {
return Ok(vcs_type);
}
if is_jj_repo_by_command(path) {
if is_git_repo_by_command(path) {
return Ok(VcsType::JjColocated);
}
return Ok(VcsType::Jj);
}
if is_git_repo_by_command(path) {
return Ok(VcsType::Git);
}
Err(Error::NotInAnyRepo)
}
fn find_vcs_root(start: &Path) -> Option<VcsType> {
let mut current = start.to_path_buf();
loop {
let has_jj = current.join(".jj").is_dir();
let has_git = current.join(".git").exists();
if has_jj {
if has_git {
return Some(VcsType::JjColocated);
}
return Some(VcsType::Jj);
}
if has_git {
return Some(VcsType::Git);
}
match current.parent() {
Some(parent) => current = parent.to_path_buf(),
None => break,
}
}
None
}
fn is_jj_repo_by_command(path: &Path) -> bool {
Command::new("jj")
.args(["root"])
.current_dir(path)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn is_git_repo_by_command(path: &Path) -> bool {
Command::new("git")
.args(["rev-parse", "--git-dir"])
.current_dir(path)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
impl VcsType {
fn is_jj(&self) -> bool {
matches!(self, VcsType::Jj | VcsType::JjColocated)
}
}
#[test]
fn test_vcs_type_is_jj() {
assert!(!VcsType::Git.is_jj());
assert!(VcsType::Jj.is_jj());
assert!(VcsType::JjColocated.is_jj());
}
}
#[cfg(all(test, feature = "impure-test"))]
mod impure_tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_detect_git_repo() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path();
let init_result = Command::new("git")
.args(["init"])
.current_dir(path)
.output();
if init_result.is_err() {
eprintln!("Skipping test: git not available");
return;
}
let result = detect_vcs_at(path);
assert!(result.is_ok());
assert_eq!(result.unwrap(), VcsType::Git);
}
#[test]
fn test_detect_not_in_repo() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path();
let result = detect_vcs_at(path);
assert!(result.is_err());
}
}