use std::path::Path;
use std::process::Command;
use crate::diff::ParsedDiff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VcsType {
Jj,
Git,
}
#[must_use]
pub fn detect_vcs(path: &Path) -> Option<VcsType> {
if path.join(".jj").exists() {
return Some(VcsType::Jj);
}
if path.join(".git").exists() {
return Some(VcsType::Git);
}
None
}
#[must_use]
pub fn get_file_diff(
repo_path: &Path,
file_path: &str,
from_commit: &str,
to_commit: Option<&str>,
) -> Option<ParsedDiff> {
let vcs = detect_vcs(repo_path)?;
let output = match vcs {
VcsType::Jj => get_jj_diff(repo_path, file_path, from_commit, to_commit),
VcsType::Git => get_git_diff(repo_path, file_path, from_commit, to_commit),
};
output.map(|diff_text| ParsedDiff::parse(&diff_text))
}
fn get_jj_diff(
repo_path: &Path,
file_path: &str,
from_commit: &str,
to_commit: Option<&str>,
) -> Option<String> {
let mut cmd = Command::new("jj");
cmd.current_dir(repo_path);
cmd.arg("diff");
cmd.arg("--git");
cmd.arg("--from").arg(from_commit);
if let Some(to) = to_commit {
cmd.arg("--to").arg(to);
}
cmd.arg(file_path);
let output = cmd.output().ok()?;
if output.status.success() {
let diff = String::from_utf8_lossy(&output.stdout).to_string();
if diff.trim().is_empty() {
None
} else {
Some(diff)
}
} else {
None
}
}
fn get_git_diff(
repo_path: &Path,
file_path: &str,
from_commit: &str,
to_commit: Option<&str>,
) -> Option<String> {
let mut cmd = Command::new("git");
cmd.current_dir(repo_path);
cmd.arg("diff");
if let Some(to) = to_commit {
cmd.arg(format!("{from_commit}..{to}"));
} else {
cmd.arg(from_commit);
}
cmd.arg("--").arg(file_path);
let output = cmd.output().ok()?;
if output.status.success() {
let diff = String::from_utf8_lossy(&output.stdout).to_string();
if diff.trim().is_empty() {
None
} else {
Some(diff)
}
} else {
None
}
}
pub fn get_file_content(repo_path: &Path, file_path: &str, commit: &str) -> Option<Vec<String>> {
let vcs = detect_vcs(repo_path)?;
let output = match vcs {
VcsType::Jj => {
let mut cmd = Command::new("jj");
cmd.current_dir(repo_path);
cmd.arg("file").arg("show").arg(file_path);
cmd.arg("-r").arg(commit);
cmd.output().ok()?
}
VcsType::Git => {
let mut cmd = Command::new("git");
cmd.current_dir(repo_path);
cmd.arg("show").arg(format!("{commit}:{file_path}"));
cmd.output().ok()?
}
};
if output.status.success() {
let content = String::from_utf8_lossy(&output.stdout);
Some(content.lines().map(String::from).collect())
} else {
None
}
}
#[must_use]
pub fn get_full_diff(
repo_path: &Path,
from_commit: &str,
to_commit: Option<&str>,
) -> Option<String> {
let vcs = detect_vcs(repo_path)?;
match vcs {
VcsType::Jj => {
let mut cmd = Command::new("jj");
cmd.current_dir(repo_path);
cmd.arg("diff").arg("--git");
cmd.arg("--from").arg(from_commit);
if let Some(to) = to_commit {
cmd.arg("--to").arg(to);
}
let output = cmd.output().ok()?;
if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).to_string())
} else {
None
}
}
VcsType::Git => {
let mut cmd = Command::new("git");
cmd.current_dir(repo_path);
cmd.arg("diff");
if let Some(to) = to_commit {
cmd.arg(format!("{from_commit}..{to}"));
} else {
cmd.arg(from_commit);
}
let output = cmd.output().ok()?;
if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).to_string())
} else {
None
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_vcs_none() {
let temp = std::env::temp_dir();
let _ = detect_vcs(&temp);
}
}