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| Format::recognized_from_path(p).is_some())
55 .collect::<Vec<_>>()
56 } else {
57 files.to_vec()
58 };
59
60 if files_to_diff.is_empty() {
61 eprintln!("No prose files changed.");
62 return Ok(false);
63 }
64
65 let mut any_diff = false;
66
67 for path in &files_to_diff {
68 let old_content = match file_at_ref(git_ref, path) {
70 Ok(c) => c,
71 Err(_) => {
72 continue;
74 }
75 };
76
77 let tmp = tempfile::NamedTempFile::new().context("failed to create temp file")?;
79 std::fs::write(tmp.path(), &old_content)?;
80
81 let fmt = format.or_else(|| Format::recognized_from_path(path));
82 let diff_output = sentence_diff(tmp.path(), path, fmt, color)?;
83
84 if !diff_output.is_empty() {
85 let display = diff_output
87 .replace(
88 &format!("a/{}", tmp.path().display()),
89 &format!("a/{} ({git_ref})", path.display()),
90 )
91 .replace(
92 &format!("b/{}", tmp.path().display()),
93 &format!("b/{}", path.display()),
94 );
95 print!("{display}");
96 any_diff = true;
97 }
98 }
99
100 if !any_diff {
101 eprintln!("No sentence-level differences.");
102 }
103
104 Ok(any_diff)
105}