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