use std::path::Path;
use anyhow::Result;
use super::run_git_stdout;
const DISPLAY: &[&str] = &["--no-color", "--no-ext-diff"];
const REPLAY: &[&str] = &[
"--no-color",
"--no-ext-diff",
"--no-textconv",
"--unified=3",
"--submodule=short",
"--full-index",
];
fn diff_stdout(workdir: &Path, args: &[&str]) -> Result<String> {
patch_stdout(workdir, "diff", REPLAY, args)
}
fn restore_stdout(workdir: &Path, args: &[&str]) -> Result<String> {
let mut full = vec!["--binary"];
full.extend(args);
patch_stdout(workdir, "diff", REPLAY, &full)
}
fn patch_stdout(workdir: &Path, command: &str, flags: &[&str], args: &[&str]) -> Result<String> {
let mut full = vec![command];
full.extend(flags);
full.extend(args);
run_git_stdout(workdir, &full)
}
pub fn diff_commit(workdir: &Path, oid: &str) -> Result<String> {
diff_stdout(workdir, &[&format!("{}^..{}", oid, oid)])
}
pub fn diff_commit_file(workdir: &Path, oid: &str, path: &str) -> Result<String> {
diff_stdout(
workdir,
&[
&format!("{}^..{}", oid, oid),
"--",
&format!(":(literal){path}"),
],
)
}
pub fn diff_trees(workdir: &Path, from: &str, to: &str) -> Result<String> {
restore_stdout(workdir, &[from, to])
}
pub fn diff_cached(workdir: &Path) -> Result<String> {
diff_cached_files(workdir, &[])
}
pub fn diff_cached_files(workdir: &Path, files: &[&str]) -> Result<String> {
let mut args = vec!["--cached", "--"];
args.extend(files);
restore_stdout(workdir, &args)
}
pub fn diff_head_name_only(workdir: &Path) -> Result<String> {
diff_stdout(workdir, &["HEAD", "--name-only"])
}
pub fn diff_head_file(workdir: &Path, path: &str) -> Result<String> {
diff_stdout(workdir, &["HEAD", "--", path])
}
pub fn diff_head_file_is_binary(workdir: &Path, path: &str) -> Result<bool> {
let out = diff_stdout(workdir, &["--numstat", "HEAD", "--", path])?;
Ok(out.starts_with("-\t"))
}
pub fn diff_file(workdir: &Path, path: &str) -> Result<String> {
diff_stdout(workdir, &["--", path])
}
pub fn diff_cached_file(workdir: &Path, path: &str) -> Result<String> {
diff_stdout(workdir, &["--cached", "--", path])
}
pub fn diff_file_is_binary(workdir: &Path, path: &str) -> Result<bool> {
let out = diff_stdout(workdir, &["--numstat", "--", path])?;
Ok(out.starts_with("-\t"))
}
pub fn diff_cached_file_is_binary(workdir: &Path, path: &str) -> Result<bool> {
let out = diff_stdout(workdir, &["--cached", "--numstat", "--", path])?;
Ok(out.starts_with("-\t"))
}
pub fn diff_commit_file_is_binary(workdir: &Path, oid: &str, path: &str) -> Result<bool> {
let out = diff_stdout(
workdir,
&["--numstat", &format!("{}^..{}", oid, oid), "--", path],
)?;
Ok(out.starts_with("-\t"))
}
pub fn diff_commit_name_status(workdir: &Path, oid: &str) -> Result<Vec<(char, String)>> {
let out = diff_stdout(workdir, &["--name-status", &format!("{}^..{}", oid, oid)])?;
let mut result = Vec::new();
for line in out.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let mut fields = line.splitn(3, '\t');
let status_field = fields.next().unwrap_or("");
let path1 = fields.next().unwrap_or("").trim();
let path2 = fields.next().map(str::trim);
if status_field.is_empty() || path1.is_empty() {
continue;
}
let status = status_field.chars().next().unwrap_or('M');
let path = match (status, path2) {
('R' | 'C', Some(dest)) => dest.to_string(),
_ => path1.to_string(),
};
result.push((status, path));
}
Ok(result)
}
pub fn diff_head(workdir: &Path) -> Result<String> {
restore_stdout(workdir, &["HEAD"])
}
pub fn diff_head_files(workdir: &Path, files: &[&str]) -> Result<String> {
let mut args = vec!["HEAD", "--"];
args.extend(files);
restore_stdout(workdir, &args)
}
pub fn diff_head_display(workdir: &Path) -> Result<String> {
patch_stdout(workdir, "diff", DISPLAY, &["HEAD"])
}
pub fn diff_cached_display(workdir: &Path) -> Result<String> {
patch_stdout(workdir, "diff", DISPLAY, &["--cached"])
}
pub fn diff_head_file_display(workdir: &Path, path: &str) -> Result<String> {
diff_head_files_display(workdir, &[path])
}
pub fn diff_head_files_display(workdir: &Path, paths: &[&str]) -> Result<String> {
if paths.is_empty() {
return Ok(String::new());
}
let mut args = vec!["HEAD", "--"];
args.extend(paths);
patch_stdout(workdir, "diff", DISPLAY, &args)
}
pub fn diff_range(workdir: &Path, base: &str, tip: &str) -> Result<String> {
patch_stdout(workdir, "diff", DISPLAY, &[base, tip])
}
pub fn show_commit_patch(workdir: &Path, oid: &str) -> Result<String> {
patch_stdout(workdir, "show", DISPLAY, &["--stat", "--patch", oid])
}
pub fn show_commit_file(workdir: &Path, oid: &str, path: &str) -> Result<String> {
patch_stdout(workdir, "show", DISPLAY, &["--format=", oid, "--", path])
}
#[cfg(test)]
#[path = "git_diff_test.rs"]
mod tests;