1use std::collections::HashMap;
18use std::collections::HashSet;
19use std::pin::Pin;
20use std::task::Context;
21use std::task::Poll;
22use std::task::ready;
23
24use futures::Stream;
25use futures::StreamExt as _;
26use futures::future::BoxFuture;
27use futures::future::ready;
28use futures::future::try_join_all;
29use futures::stream::Fuse;
30use futures::stream::FuturesOrdered;
31use indexmap::IndexMap;
32use indexmap::IndexSet;
33use itertools::Itertools as _;
34use pollster::FutureExt as _;
35
36use crate::backend::BackendError;
37use crate::backend::BackendResult;
38use crate::backend::CopyHistory;
39use crate::backend::CopyId;
40use crate::backend::CopyRecord;
41use crate::backend::TreeValue;
42use crate::dag_walk;
43use crate::merge::Diff;
44use crate::merge::Merge;
45use crate::merge::MergedTreeValue;
46use crate::merge::SameChange;
47use crate::merged_tree::MergedTree;
48use crate::merged_tree::TreeDiffEntry;
49use crate::merged_tree::TreeDiffStream;
50use crate::repo_path::RepoPath;
51use crate::repo_path::RepoPathBuf;
52
53#[derive(Default, Debug)]
55pub struct CopyRecords {
56 records: Vec<CopyRecord>,
57 sources: HashMap<RepoPathBuf, usize>,
60 targets: HashMap<RepoPathBuf, usize>,
61}
62
63impl CopyRecords {
64 pub fn add_records(&mut self, copy_records: impl IntoIterator<Item = CopyRecord>) {
67 for r in copy_records {
68 let is_duplicate = self
73 .targets
74 .get(&r.target)
75 .and_then(|&i| self.records.get(i))
76 .is_some_and(|existing| existing.source == r.source);
77 if is_duplicate {
78 continue;
79 }
80 self.sources
81 .entry(r.source.clone())
82 .and_modify(|value| *value = usize::MAX)
84 .or_insert(self.records.len());
85 self.targets
86 .entry(r.target.clone())
87 .and_modify(|value| *value = usize::MAX)
89 .or_insert(self.records.len());
90 self.records.push(r);
91 }
92 }
93
94 pub fn has_source(&self, source: &RepoPath) -> bool {
96 self.sources.contains_key(source)
97 }
98
99 pub fn for_source(&self, source: &RepoPath) -> Option<&CopyRecord> {
101 self.sources.get(source).and_then(|&i| self.records.get(i))
102 }
103
104 pub fn has_target(&self, target: &RepoPath) -> bool {
106 self.targets.contains_key(target)
107 }
108
109 pub fn for_target(&self, target: &RepoPath) -> Option<&CopyRecord> {
111 self.targets.get(target).and_then(|&i| self.records.get(i))
112 }
113
114 pub fn iter(&self) -> impl Iterator<Item = &CopyRecord> {
116 self.records.iter()
117 }
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub enum CopyOperation {
123 Copy,
125 Rename,
127}
128
129#[derive(Debug)]
131pub struct CopiesTreeDiffEntry {
132 pub path: CopiesTreeDiffEntryPath,
134 pub values: BackendResult<Diff<MergedTreeValue>>,
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
140pub struct CopiesTreeDiffEntryPath {
141 pub source: Option<(RepoPathBuf, CopyOperation)>,
143 pub target: RepoPathBuf,
145}
146
147impl CopiesTreeDiffEntryPath {
148 pub fn source(&self) -> &RepoPath {
150 self.source.as_ref().map_or(&self.target, |(path, _)| path)
151 }
152
153 pub fn target(&self) -> &RepoPath {
155 &self.target
156 }
157
158 pub fn copy_operation(&self) -> Option<CopyOperation> {
161 self.source.as_ref().map(|(_, op)| *op)
162 }
163
164 pub fn to_diff(&self) -> Option<Diff<&RepoPath>> {
166 let (source, _) = self.source.as_ref()?;
167 Some(Diff::new(source, &self.target))
168 }
169}
170
171pub struct CopiesTreeDiffStream<'a> {
173 inner: TreeDiffStream<'a>,
174 source_tree: MergedTree,
175 target_tree: MergedTree,
176 copy_records: &'a CopyRecords,
177}
178
179impl<'a> CopiesTreeDiffStream<'a> {
180 pub fn new(
182 inner: TreeDiffStream<'a>,
183 source_tree: MergedTree,
184 target_tree: MergedTree,
185 copy_records: &'a CopyRecords,
186 ) -> Self {
187 Self {
188 inner,
189 source_tree,
190 target_tree,
191 copy_records,
192 }
193 }
194
195 async fn resolve_copy_source(
196 &self,
197 source: &RepoPath,
198 values: BackendResult<Diff<MergedTreeValue>>,
199 ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {
200 let target_value = values?.after;
201 let source_value = self.source_tree.path_value(source).await?;
202 let source_value_at_target = self.target_tree.path_value(source).await?;
204 let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {
205 CopyOperation::Rename
206 } else {
207 CopyOperation::Copy
208 };
209 Ok((copy_op, Diff::new(source_value, target_value)))
210 }
211}
212
213impl Stream for CopiesTreeDiffStream<'_> {
214 type Item = CopiesTreeDiffEntry;
215
216 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
217 while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {
218 let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)
219 else {
220 let target_deleted =
221 matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());
222 if target_deleted && self.copy_records.has_source(&diff_entry.path) {
223 continue;
225 }
226 return Poll::Ready(Some(CopiesTreeDiffEntry {
227 path: CopiesTreeDiffEntryPath {
228 source: None,
229 target: diff_entry.path,
230 },
231 values: diff_entry.values,
232 }));
233 };
234
235 let (copy_op, values) = match self
236 .resolve_copy_source(source, diff_entry.values)
237 .block_on()
238 {
239 Ok((copy_op, values)) => (copy_op, Ok(values)),
240 Err(err) => (CopyOperation::Copy, Err(err)),
242 };
243 return Poll::Ready(Some(CopiesTreeDiffEntry {
244 path: CopiesTreeDiffEntryPath {
245 source: Some((source.clone(), copy_op)),
246 target: diff_entry.path,
247 },
248 values,
249 }));
250 }
251
252 Poll::Ready(None)
253 }
254}
255
256pub type CopyGraph = IndexMap<CopyId, CopyHistory>;
258
259fn collect_descendants(copy_graph: &CopyGraph) -> IndexMap<CopyId, IndexSet<CopyId>> {
260 let mut ancestor_map: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
261
262 let heads = dag_walk::heads(
268 copy_graph.keys(),
269 |id| *id,
270 |id| copy_graph[*id].parents.iter(),
271 )
272 .into_iter()
273 .sorted()
274 .collect_vec();
275 for id in dag_walk::topo_order_forward(
276 heads,
277 |id| *id,
278 |id| copy_graph[*id].parents.iter(),
279 |id| panic!("Cycle detected in copy history graph involving CopyId {id}"),
280 )
281 .expect("Could not walk CopyGraph")
282 {
283 let mut ancestors = IndexSet::new();
285 for parent in ©_graph[id].parents {
286 ancestors.extend(ancestor_map[parent].iter().cloned());
287 ancestors.insert(parent.clone());
288 }
289 ancestor_map.insert(id.clone(), ancestors);
290 }
291
292 let mut result: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
294 for (id, ancestors) in ancestor_map {
295 for ancestor in ancestors {
296 result.entry(ancestor).or_default().insert(id.clone());
297 }
298 result.entry(id.clone()).or_default();
301 }
302 result
303}
304
305fn iterate_ancestors<'a>(
308 copies: &'a CopyGraph,
309 initial_id: &'a CopyId,
310) -> impl Iterator<Item = &'a CopyId> {
311 let mut valid = HashSet::from([initial_id]);
312 copies.iter().filter_map(move |(id, history)| {
313 if valid.contains(id) {
314 valid.extend(history.parents.iter());
315 Some(id)
316 } else {
317 None
318 }
319 })
320}
321
322pub fn is_ancestor(copies: &CopyGraph, ancestor: &CopyId, descendant: &CopyId) -> bool {
324 for history in dag_walk::dfs(
325 [descendant],
326 |id| *id,
327 |id| copies.get(*id).unwrap().parents.iter(),
328 ) {
329 if history == ancestor {
330 return true;
331 }
332 }
333 false
334}
335
336#[derive(Clone, Debug, Eq, Hash, PartialEq)]
338pub enum CopyHistorySource {
339 Copy(RepoPathBuf),
341 Rename(RepoPathBuf),
343 Normal,
345}
346
347#[derive(Debug, Eq, Hash, PartialEq)]
349pub struct CopyHistoryDiffTerm {
350 pub target_value: Option<TreeValue>,
352 pub sources: Vec<(CopyHistorySource, MergedTreeValue)>,
355}
356
357#[derive(Debug)]
359pub struct CopyHistoryTreeDiffEntry {
360 pub target_path: RepoPathBuf,
362 pub diffs: BackendResult<Merge<CopyHistoryDiffTerm>>,
364}
365
366impl CopyHistoryTreeDiffEntry {
367 fn normal(diff_entry: TreeDiffEntry) -> Self {
369 let target_path = diff_entry.path;
370 let diffs = diff_entry.values.map(|diff| {
371 let sources = if diff.before.is_absent() {
372 vec![]
373 } else {
374 vec![(CopyHistorySource::Normal, diff.before)]
375 };
376 diff.after.into_map(|target_value| CopyHistoryDiffTerm {
377 target_value,
378 sources: sources.clone(),
379 })
380 });
381 Self { target_path, diffs }
382 }
383}
384
385pub struct CopyHistoryDiffStream<'a> {
387 inner: Fuse<TreeDiffStream<'a>>,
388 before_tree: &'a MergedTree,
389 after_tree: &'a MergedTree,
390 pending: FuturesOrdered<BoxFuture<'static, CopyHistoryTreeDiffEntry>>,
391}
392
393impl<'a> CopyHistoryDiffStream<'a> {
394 pub fn new(
399 inner: TreeDiffStream<'a>,
400 before_tree: &'a MergedTree,
401 after_tree: &'a MergedTree,
402 ) -> Self {
403 Self {
404 inner: inner.fuse(),
405 before_tree,
406 after_tree,
407 pending: FuturesOrdered::new(),
408 }
409 }
410}
411
412impl Stream for CopyHistoryDiffStream<'_> {
413 type Item = CopyHistoryTreeDiffEntry;
414
415 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
416 loop {
417 if let Poll::Ready(Some(next)) = self.pending.poll_next_unpin(cx) {
420 return Poll::Ready(Some(next));
421 }
422
423 let next_diff_entry = match ready!(self.inner.poll_next_unpin(cx)) {
426 Some(diff_entry) => diff_entry,
427 None if self.pending.is_empty() => return Poll::Ready(None),
428 _ => return Poll::Pending,
429 };
430
431 let Ok(Diff { before, after }) = &next_diff_entry.values else {
432 self.pending
433 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
434 next_diff_entry,
435 ))));
436 continue;
437 };
438
439 let Some(before) = before.as_resolved() else {
443 self.pending
444 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
445 next_diff_entry,
446 ))));
447 continue;
448 };
449 let Some(after) = after.as_resolved() else {
450 self.pending
451 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
452 next_diff_entry,
453 ))));
454 continue;
455 };
456
457 match (before, after) {
458 (
460 Some(TreeValue::File { copy_id: id1, .. }),
461 Some(TreeValue::File { copy_id: id2, .. }),
462 ) if id1 == id2 => {
463 self.pending
464 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
465 next_diff_entry,
466 ))));
467 }
468
469 (other, Some(f @ TreeValue::File { .. })) => {
470 if let Some(other) = other {
471 self.pending
488 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry {
489 target_path: next_diff_entry.path.clone(),
490 diffs: Ok(Merge::resolved(CopyHistoryDiffTerm {
491 target_value: None,
492 sources: vec![(
493 CopyHistorySource::Normal,
494 Merge::resolved(Some(other.clone())),
495 )],
496 })),
497 })));
498 }
499
500 let future = tree_diff_entry_from_copies(
501 self.before_tree.clone(),
502 self.after_tree.clone(),
503 f.clone(),
504 next_diff_entry.path.clone(),
505 );
506 self.pending.push_back(Box::pin(future));
507 }
508
509 _ => self
514 .pending
515 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
516 next_diff_entry,
517 )))),
518 }
519 }
520 }
521}
522
523async fn tree_diff_entry_from_copies(
524 before_tree: MergedTree,
525 after_tree: MergedTree,
526 file: TreeValue,
527 target_path: RepoPathBuf,
528) -> CopyHistoryTreeDiffEntry {
529 CopyHistoryTreeDiffEntry {
530 target_path,
531 diffs: diffs_from_copies(before_tree, after_tree, file).await,
532 }
533}
534
535async fn diffs_from_copies(
536 before_tree: MergedTree,
537 after_tree: MergedTree,
538 after_file: TreeValue,
539) -> BackendResult<Merge<CopyHistoryDiffTerm>> {
540 let copy_id = after_file.copy_id().ok_or(BackendError::Other(
541 "Expected TreeValue::File with a CopyId".into(),
542 ))?;
543 let copy_graph: CopyGraph = before_tree
544 .store()
545 .backend()
546 .get_related_copies(copy_id)
547 .await?
548 .into_iter()
549 .map(|related| (related.id, related.history))
550 .collect();
551
552 let descendants = collect_descendants(©_graph);
553 let copies =
554 find_diff_sources_from_copies(&before_tree, copy_id, ©_graph, &descendants).await?;
555
556 try_join_all(copies.into_iter().map(async |(before_path, before_val)| {
557 classify_source(
558 &after_tree,
559 copy_id,
560 before_path,
561 before_val
562 .copy_id()
563 .expect("expected TreeValue::File with a CopyId"),
564 ©_graph,
565 )
566 .await
567 .map(|source| (source, Merge::resolved(Some(before_val))))
568 }))
569 .await
570 .map(|sources| {
571 Merge::resolved(CopyHistoryDiffTerm {
572 target_value: Some(after_file),
573 sources,
574 })
575 })
576}
577
578async fn classify_source(
579 after_tree: &MergedTree,
580 after_id: &CopyId,
581 before_path: RepoPathBuf,
582 before_id: &CopyId,
583 copy_graph: &CopyGraph,
584) -> BackendResult<CopyHistorySource> {
585 let history = copy_graph
586 .get(after_id)
587 .expect("copy_graph should already include after_id");
588 let after_path = &history.current_path;
589
590 if *after_path == before_path
594 && (is_ancestor(copy_graph, after_id, before_id)
595 || is_ancestor(copy_graph, before_id, after_id))
596 {
597 return Ok(CopyHistorySource::Normal);
598 }
599
600 let after_tree_before_path_val = after_tree.path_value(&before_path).await?;
601 let Some(after_tree_before_path_id) = after_tree_before_path_val
605 .to_copy_id_merge()
606 .expect("expected merge of `TreeValue::File`s")
607 .resolve_trivial(SameChange::Accept)
608 .expect("expected no CopyId conflicts")
609 .clone()
610 else {
611 return Ok(CopyHistorySource::Rename(before_path));
613 };
614
615 if is_ancestor(copy_graph, before_id, &after_tree_before_path_id)
616 || is_ancestor(copy_graph, &after_tree_before_path_id, before_id)
617 {
618 Ok(CopyHistorySource::Copy(before_path))
619 } else {
620 Ok(CopyHistorySource::Rename(before_path))
623 }
624}
625
626async fn find_diff_sources_from_copies(
627 tree: &MergedTree,
628 copy_id: &CopyId,
629 copy_graph: &CopyGraph,
630 descendants: &IndexMap<CopyId, IndexSet<CopyId>>,
631) -> BackendResult<Vec<(RepoPathBuf, TreeValue)>> {
632 let history = copy_graph.get(copy_id).ok_or(BackendError::Other(
635 "CopyId should be present in `get_related_copies()` result".into(),
636 ))?;
637
638 if history.parents.is_empty() {
639 for descendant_id in &descendants[copy_id] {
642 if let Some(descendant) = tree.copy_value(descendant_id).await? {
643 return Ok(vec![(
644 copy_graph[descendant_id].current_path.clone(),
645 descendant,
646 )]);
647 }
648 }
649 }
650
651 let mut sources = vec![];
652
653 'parents: for parent_copy_id in &history.parents {
673 let mut absent_ancestors = vec![];
674
675 for ancestor_id in iterate_ancestors(copy_graph, parent_copy_id) {
677 let ancestor_history = copy_graph.get(ancestor_id).ok_or(BackendError::Other(
678 "Ancestor CopyId should be present in `get_related_copies()` result".into(),
679 ))?;
680 if let Some(ancestor) = tree.copy_value(ancestor_id).await? {
681 sources.push((ancestor_history.current_path.clone(), ancestor));
682 continue 'parents;
683 } else {
684 absent_ancestors.push(ancestor_id);
685 }
686 }
687
688 for descendant_id in &descendants[parent_copy_id] {
693 if let Some(descendant) = tree.copy_value(descendant_id).await? {
694 sources.push((copy_graph[descendant_id].current_path.clone(), descendant));
695 continue 'parents;
696 }
697 }
698
699 for ancestor_id in absent_ancestors {
704 for descendant_id in descendants[ancestor_id].difference(&descendants[parent_copy_id]) {
705 if let Some(descendant) = tree.copy_value(descendant_id).await? {
706 sources.push((copy_graph[descendant_id].current_path.clone(), descendant));
707 continue 'parents;
708 }
709 }
710 }
711 }
712 Ok(sources)
713}