1use super::{Action, ChangeRef, Error, RewriteOptions};
2use crate::rewrites;
3use bstr::BStr;
4use gix_filter::attributes::glob::pattern::Case;
5use std::borrow::Cow;
6use std::cell::RefCell;
7use std::cmp::Ordering;
8
9pub fn diff<'rhs, 'lhs: 'rhs, E, Find>(
30 lhs: &'lhs gix_index::State,
31 rhs: &'rhs gix_index::State,
32 mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
33 rewrite_options: Option<RewriteOptions<'_, Find>>,
34 pathspec: &mut gix_pathspec::Search,
35 pathspec_attributes: &mut dyn FnMut(&BStr, Case, bool, &mut gix_attributes::search::Outcome) -> bool,
36) -> Result<Option<rewrites::Outcome>, Error>
37where
38 E: Into<Box<dyn std::error::Error + Send + Sync>>,
39 Find: gix_object::FindObjectOrHeader,
40{
41 if lhs.is_sparse() || rhs.is_sparse() {
42 return Err(Error::IsSparse);
43 }
44 if lhs
45 .entries()
46 .iter()
47 .any(|e| e.stage() != gix_index::entry::Stage::Unconflicted)
48 {
49 return Err(Error::LhsHasUnmerged);
50 }
51
52 let lhs_range = lhs
53 .prefixed_entries_range(pathspec.common_prefix())
54 .unwrap_or_else(|| 0..lhs.entries().len());
55 let rhs_range = rhs
56 .prefixed_entries_range(pathspec.common_prefix())
57 .unwrap_or_else(|| 0..rhs.entries().len());
58
59 let pattern_matches = RefCell::new(|relative_path, entry: &gix_index::Entry| {
60 pathspec
61 .pattern_matching_relative_path(relative_path, Some(entry.mode.is_submodule()), pathspec_attributes)
62 .is_some_and(|m| !m.is_excluded())
63 });
64
65 let (mut lhs_iter, mut rhs_iter) = (
66 lhs.entries()[lhs_range.clone()]
67 .iter()
68 .enumerate()
69 .map(|(idx, e)| (idx + lhs_range.start, e.path(lhs), e))
70 .filter(|(_, path, e)| pattern_matches.borrow_mut()(path, e)),
71 rhs.entries()[rhs_range.clone()]
72 .iter()
73 .enumerate()
74 .map(|(idx, e)| (idx + rhs_range.start, e.path(rhs), e))
75 .filter(|(_, path, e)| pattern_matches.borrow_mut()(path, e)),
76 );
77
78 let mut resource_cache_storage = None;
79 let mut tracker = rewrite_options.map(
80 |RewriteOptions {
81 resource_cache,
82 rewrites,
83 find,
84 }| {
85 resource_cache_storage = Some((resource_cache, find));
86 rewrites::Tracker::<ChangeRef<'lhs, 'rhs>>::new(rewrites)
87 },
88 );
89
90 let (mut lhs_storage, mut rhs_storage) = (lhs_iter.next(), rhs_iter.next());
91 loop {
92 match (lhs_storage, rhs_storage) {
93 (Some(lhs), Some(rhs)) => {
94 let (lhs_idx, lhs_path, lhs_entry) = lhs;
95 let (rhs_idx, rhs_path, rhs_entry) = rhs;
96 match lhs_path.cmp(rhs_path) {
97 Ordering::Less => match emit_deletion(lhs, &mut cb, tracker.as_mut())? {
98 Action::Continue => {
99 lhs_storage = lhs_iter.next();
100 }
101 Action::Cancel => return Ok(None),
102 },
103 Ordering::Equal => {
104 if ignore_unmerged_and_intent_to_add(rhs) {
105 rhs_storage = rhs_iter.next();
106 lhs_storage = lhs_iter.next();
107 continue;
108 }
109 if lhs_entry.id != rhs_entry.id || lhs_entry.mode != rhs_entry.mode {
110 let change = ChangeRef::Modification {
111 location: Cow::Borrowed(rhs_path),
112 previous_index: lhs_idx,
113 previous_entry_mode: lhs_entry.mode,
114 previous_id: Cow::Borrowed(lhs_entry.id.as_ref()),
115 index: rhs_idx,
116 entry_mode: rhs_entry.mode,
117 id: Cow::Borrowed(rhs_entry.id.as_ref()),
118 };
119
120 let change = match tracker.as_mut() {
121 None => Some(change),
122 Some(tracker) => tracker.try_push_change(change, rhs_path),
123 };
124 if let Some(change) = change {
125 match cb(change).map_err(|err| Error::Callback(err.into()))? {
126 Action::Continue => {}
127 Action::Cancel => return Ok(None),
128 }
129 }
130 }
131 lhs_storage = lhs_iter.next();
132 rhs_storage = rhs_iter.next();
133 }
134 Ordering::Greater => match emit_addition(rhs, &mut cb, tracker.as_mut())? {
135 Action::Continue => {
136 rhs_storage = rhs_iter.next();
137 }
138 Action::Cancel => return Ok(None),
139 },
140 }
141 }
142 (Some(lhs), None) => match emit_deletion(lhs, &mut cb, tracker.as_mut())? {
143 Action::Cancel => return Ok(None),
144 Action::Continue => {
145 lhs_storage = lhs_iter.next();
146 }
147 },
148 (None, Some(rhs)) => match emit_addition(rhs, &mut cb, tracker.as_mut())? {
149 Action::Cancel => return Ok(None),
150 Action::Continue => {
151 rhs_storage = rhs_iter.next();
152 }
153 },
154 (None, None) => break,
155 }
156 }
157
158 if let Some((mut tracker, (resource_cache, find))) = tracker.zip(resource_cache_storage) {
159 let mut cb_err = None;
160 let out = tracker.emit(
161 |dst, src| {
162 let change = if let Some(src) = src {
163 let (lhs_path, lhs_index, lhs_mode, lhs_id) = src.change.fields();
164 let (rhs_path, rhs_index, rhs_mode, rhs_id) = dst.change.fields();
165 ChangeRef::Rewrite {
166 source_location: Cow::Owned(lhs_path.into()),
167 source_index: lhs_index,
168 source_entry_mode: lhs_mode,
169 source_id: Cow::Owned(lhs_id.into()),
170 location: Cow::Owned(rhs_path.into()),
171 index: rhs_index,
172 entry_mode: rhs_mode,
173 id: Cow::Owned(rhs_id.into()),
174 copy: match src.kind {
175 rewrites::tracker::visit::SourceKind::Rename => false,
176 rewrites::tracker::visit::SourceKind::Copy => true,
177 },
178 }
179 } else {
180 dst.change
181 };
182 match cb(change) {
183 Ok(Action::Continue) => crate::tree::visit::Action::Continue,
184 Ok(Action::Cancel) => crate::tree::visit::Action::Cancel,
185 Err(err) => {
186 cb_err = Some(Error::Callback(err.into()));
187 crate::tree::visit::Action::Cancel
188 }
189 }
190 },
191 resource_cache,
192 find,
193 |push| {
194 for (index, entry) in lhs.entries().iter().enumerate() {
195 let path = entry.path(rhs);
196 push(
197 ChangeRef::Modification {
198 location: Cow::Borrowed(path),
199 previous_index: 0, previous_entry_mode: entry.mode,
201 previous_id: Cow::Owned(entry.id.kind().null()),
202 index,
203 entry_mode: entry.mode,
204 id: Cow::Borrowed(entry.id.as_ref()),
205 },
206 path,
207 );
208 }
209 Ok::<_, std::convert::Infallible>(())
210 },
211 )?;
212
213 if let Some(err) = cb_err {
214 Err(err)
215 } else {
216 Ok(Some(out))
217 }
218 } else {
219 Ok(None)
220 }
221}
222
223fn emit_deletion<'rhs, 'lhs: 'rhs, E>(
224 (idx, path, entry): (usize, &'lhs BStr, &'lhs gix_index::Entry),
225 mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
226 tracker: Option<&mut rewrites::Tracker<ChangeRef<'lhs, 'rhs>>>,
227) -> Result<Action, Error>
228where
229 E: Into<Box<dyn std::error::Error + Send + Sync>>,
230{
231 let change = ChangeRef::Deletion {
232 location: Cow::Borrowed(path),
233 index: idx,
234 entry_mode: entry.mode,
235 id: Cow::Borrowed(entry.id.as_ref()),
236 };
237
238 let change = match tracker {
239 None => change,
240 Some(tracker) => match tracker.try_push_change(change, path) {
241 Some(change) => change,
242 None => return Ok(Action::Continue),
243 },
244 };
245
246 cb(change).map_err(|err| Error::Callback(err.into()))
247}
248
249fn emit_addition<'rhs, 'lhs: 'rhs, E>(
250 (idx, path, entry): (usize, &'rhs BStr, &'rhs gix_index::Entry),
251 mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
252 tracker: Option<&mut rewrites::Tracker<ChangeRef<'lhs, 'rhs>>>,
253) -> Result<Action, Error>
254where
255 E: Into<Box<dyn std::error::Error + Send + Sync>>,
256{
257 if ignore_unmerged_and_intent_to_add((idx, path, entry)) {
258 return Ok(Action::Continue);
259 }
260
261 let change = ChangeRef::Addition {
262 location: Cow::Borrowed(path),
263 index: idx,
264 entry_mode: entry.mode,
265 id: Cow::Borrowed(entry.id.as_ref()),
266 };
267
268 let change = match tracker {
269 None => change,
270 Some(tracker) => match tracker.try_push_change(change, path) {
271 Some(change) => change,
272 None => return Ok(Action::Continue),
273 },
274 };
275
276 cb(change).map_err(|err| Error::Callback(err.into()))
277}
278
279fn ignore_unmerged_and_intent_to_add<'rhs, 'lhs: 'rhs>(
280 (_idx, _path, entry): (usize, &'rhs BStr, &'rhs gix_index::Entry),
281) -> bool {
282 let stage = entry.stage();
283 entry.flags.contains(gix_index::entry::Flags::INTENT_TO_ADD) || stage != gix_index::entry::Stage::Unconflicted
284}