1use crate::config::ConfigSet;
4use crate::error::Result;
5use crate::odb::Odb;
6use crate::parallel::ThreadPolicy;
7
8use super::detail::{DiffDetail, DiffTotals, FileDiff, LineStat};
9use super::tree_content::{
10 tree_diff_stat_parallel, tree_diff_structured_parallel, TreeDiffStructured,
11};
12use super::{DiffEntry, DiffOptions, DiffStatus};
13
14fn mode_display(mode: &str) -> Option<String> {
15 if mode == "000000" {
16 None
17 } else {
18 Some(mode.to_owned())
19 }
20}
21
22fn path_from_entry(entry: &DiffEntry) -> String {
23 entry
24 .new_path
25 .clone()
26 .or_else(|| entry.old_path.clone())
27 .unwrap_or_default()
28}
29
30fn old_path_from_entry(entry: &DiffEntry, path: &str) -> Option<String> {
31 entry
32 .old_path
33 .as_ref()
34 .filter(|op| op.as_str() != path)
35 .cloned()
36}
37
38fn file_diff_name_only(entry: &DiffEntry) -> FileDiff {
39 let path = path_from_entry(entry);
40 FileDiff {
41 path: path.clone(),
42 old_path: old_path_from_entry(entry, &path),
43 status: entry.status,
44 old_mode: mode_display(&entry.old_mode),
45 new_mode: mode_display(&entry.new_mode),
46 binary: false,
47 similarity: entry.rename_similarity(),
48 stat: None,
49 structured: None,
50 combined: None,
51 combined_status: None,
52 }
53}
54
55fn file_diff_from_structured(t: TreeDiffStructured) -> FileDiff {
56 let path = path_from_entry(&t.entry);
57 FileDiff {
58 path: path.clone(),
59 old_path: old_path_from_entry(&t.entry, &path),
60 status: t.entry.status,
61 old_mode: t.structured.old_mode.clone(),
62 new_mode: t.structured.new_mode.clone(),
63 binary: t.binary,
64 similarity: t.entry.rename_similarity(),
65 stat: None,
66 structured: Some(t.structured),
67 combined: None,
68 combined_status: None,
69 }
70}
71
72fn file_diff_from_stat(entry: DiffEntry, binary: bool, stat: Option<LineStat>) -> FileDiff {
73 let path = path_from_entry(&entry);
74 FileDiff {
75 path: path.clone(),
76 old_path: old_path_from_entry(&entry, &path),
77 status: entry.status,
78 old_mode: mode_display(&entry.old_mode),
79 new_mode: mode_display(&entry.new_mode),
80 binary,
81 similarity: entry.rename_similarity(),
82 stat,
83 structured: None,
84 combined: None,
85 combined_status: None,
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct DiffFilesOutput {
92 pub files: Vec<FileDiff>,
94 pub totals: Option<DiffTotals>,
96}
97
98pub fn build_file_diffs(
107 odb: &Odb,
108 entries: Vec<DiffEntry>,
109 detail: DiffDetail,
110 diff_options: DiffOptions,
111 config: &ConfigSet,
112 thread_policy: &ThreadPolicy,
113) -> Result<DiffFilesOutput> {
114 match detail {
115 DiffDetail::NameOnly | DiffDetail::NameStatus => {
116 let files: Vec<FileDiff> = entries.iter().map(file_diff_name_only).collect();
117 Ok(DiffFilesOutput {
118 files,
119 totals: None,
120 })
121 }
122 DiffDetail::Stat => {
123 let rows = tree_diff_stat_parallel(odb, entries, config, thread_policy, diff_options)?;
124 let files: Vec<FileDiff> = rows
125 .into_iter()
126 .map(|(entry, binary, stat)| file_diff_from_stat(entry, binary, stat))
127 .collect();
128 let totals = Some(DiffTotals::from_files(&files));
129 Ok(DiffFilesOutput { files, totals })
130 }
131 DiffDetail::Patch => {
132 let structured =
133 tree_diff_structured_parallel(odb, entries, config, thread_policy, diff_options)?;
134 let files: Vec<FileDiff> = structured
135 .into_iter()
136 .map(file_diff_from_structured)
137 .collect();
138 Ok(DiffFilesOutput {
139 files,
140 totals: None,
141 })
142 }
143 }
144}
145
146pub fn build_file_diffs_streaming(
155 odb: &Odb,
156 entries: Vec<DiffEntry>,
157 diff_options: DiffOptions,
158 config: &ConfigSet,
159 thread_policy: &ThreadPolicy,
160 mut emit: impl FnMut(FileDiff) -> Result<()>,
161) -> Result<()> {
162 use super::tree_content::for_each_tree_diff_structured;
163 for_each_tree_diff_structured(
164 odb,
165 &entries,
166 diff_options,
167 config,
168 thread_policy,
169 |structured| emit(file_diff_from_structured(structured)),
170 )
171}
172
173#[must_use]
175pub fn name_status_line(entry: &DiffEntry) -> String {
176 let path = path_from_entry(entry);
177 match entry.status {
178 DiffStatus::Renamed => {
179 let score = entry.score.unwrap_or(100);
180 let old = entry.old_path.as_deref().unwrap_or("");
181 format!("R{score:03}\t{old}\t{path}")
182 }
183 DiffStatus::Copied => {
184 let score = entry.score.unwrap_or(100);
185 let old = entry.old_path.as_deref().unwrap_or("");
186 format!("C{score:03}\t{old}\t{path}")
187 }
188 DiffStatus::Added => format!("A\t{path}"),
189 DiffStatus::Deleted => format!("D\t{path}"),
190 DiffStatus::Modified => format!("M\t{path}"),
191 DiffStatus::TypeChanged => format!("T\t{path}"),
192 DiffStatus::Unmerged => format!("U\t{path}"),
193 }
194}