1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use anyhow::{Context, Result};
5
6use crate::format::Format;
7use crate::sdiff::sentence_diff;
8
9fn changed_files(git_ref: &str) -> Result<Vec<PathBuf>> {
11 let output = Command::new("git")
12 .args(["diff", "--name-only", git_ref])
13 .output()
14 .context("failed to run git diff --name-only")?;
15 if !output.status.success() {
16 let stderr = String::from_utf8_lossy(&output.stderr);
17 anyhow::bail!("git diff failed: {stderr}");
18 }
19 let stdout = String::from_utf8_lossy(&output.stdout);
20 Ok(stdout
21 .lines()
22 .filter(|l| !l.is_empty())
23 .map(PathBuf::from)
24 .collect())
25}
26
27fn file_at_ref(git_ref: &str, path: &Path) -> Result<String> {
29 let spec = format!("{git_ref}:{}", path.display());
30 let output = Command::new("git")
31 .args(["show", &spec])
32 .output()
33 .with_context(|| format!("failed to run git show {spec}"))?;
34 if !output.status.success() {
35 let stderr = String::from_utf8_lossy(&output.stderr);
36 anyhow::bail!("git show {spec} failed: {stderr}");
37 }
38 Ok(String::from_utf8_lossy(&output.stdout).into_owned())
39}
40
41pub fn run_git_diff(
43 git_ref: &str,
44 files: &[PathBuf],
45 format: Option<Format>,
46 color: bool,
47) -> Result<bool> {
48 let files_to_diff = if files.is_empty() {
49 let changed = changed_files(git_ref)?;
51 changed
53 .into_iter()
54 .filter(|p| {
55 matches!(
56 Format::from_path(p),
57 Format::Org | Format::Latex | Format::Markdown
58 )
59 })
60 .collect::<Vec<_>>()
61 } else {
62 files.to_vec()
63 };
64
65 if files_to_diff.is_empty() {
66 eprintln!("No prose files changed.");
67 return Ok(false);
68 }
69
70 let mut any_diff = false;
71
72 for path in &files_to_diff {
73 let old_content = match file_at_ref(git_ref, path) {
75 Ok(c) => c,
76 Err(_) => {
77 continue;
79 }
80 };
81
82 let tmp = tempfile::NamedTempFile::new().context("failed to create temp file")?;
84 std::fs::write(tmp.path(), &old_content)?;
85
86 let fmt = format.or_else(|| Some(Format::from_path(path)));
87 let diff_output = sentence_diff(tmp.path(), path, fmt, color)?;
88
89 if !diff_output.is_empty() {
90 let display = diff_output
92 .replace(
93 &format!("a/{}", tmp.path().display()),
94 &format!("a/{} ({git_ref})", path.display()),
95 )
96 .replace(
97 &format!("b/{}", tmp.path().display()),
98 &format!("b/{}", path.display()),
99 );
100 print!("{display}");
101 any_diff = true;
102 }
103 }
104
105 if !any_diff {
106 eprintln!("No sentence-level differences.");
107 }
108
109 Ok(any_diff)
110}