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 self.sources
69 .entry(r.source.clone())
70 .and_modify(|value| *value = usize::MAX)
72 .or_insert(self.records.len());
73 self.targets
74 .entry(r.target.clone())
75 .and_modify(|value| *value = usize::MAX)
77 .or_insert(self.records.len());
78 self.records.push(r);
79 }
80 }
81
82 pub fn has_source(&self, source: &RepoPath) -> bool {
84 self.sources.contains_key(source)
85 }
86
87 pub fn for_source(&self, source: &RepoPath) -> Option<&CopyRecord> {
89 self.sources.get(source).and_then(|&i| self.records.get(i))
90 }
91
92 pub fn has_target(&self, target: &RepoPath) -> bool {
94 self.targets.contains_key(target)
95 }
96
97 pub fn for_target(&self, target: &RepoPath) -> Option<&CopyRecord> {
99 self.targets.get(target).and_then(|&i| self.records.get(i))
100 }
101
102 pub fn iter(&self) -> impl Iterator<Item = &CopyRecord> {
104 self.records.iter()
105 }
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110pub enum CopyOperation {
111 Copy,
113 Rename,
115}
116
117#[derive(Debug)]
119pub struct CopiesTreeDiffEntry {
120 pub path: CopiesTreeDiffEntryPath,
122 pub values: BackendResult<Diff<MergedTreeValue>>,
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
128pub struct CopiesTreeDiffEntryPath {
129 pub source: Option<(RepoPathBuf, CopyOperation)>,
131 pub target: RepoPathBuf,
133}
134
135impl CopiesTreeDiffEntryPath {
136 pub fn source(&self) -> &RepoPath {
138 self.source.as_ref().map_or(&self.target, |(path, _)| path)
139 }
140
141 pub fn target(&self) -> &RepoPath {
143 &self.target
144 }
145
146 pub fn copy_operation(&self) -> Option<CopyOperation> {
149 self.source.as_ref().map(|(_, op)| *op)
150 }
151
152 pub fn to_diff(&self) -> Option<Diff<&RepoPath>> {
154 let (source, _) = self.source.as_ref()?;
155 Some(Diff::new(source, &self.target))
156 }
157}
158
159pub struct CopiesTreeDiffStream<'a> {
161 inner: TreeDiffStream<'a>,
162 source_tree: MergedTree,
163 target_tree: MergedTree,
164 copy_records: &'a CopyRecords,
165}
166
167impl<'a> CopiesTreeDiffStream<'a> {
168 pub fn new(
170 inner: TreeDiffStream<'a>,
171 source_tree: MergedTree,
172 target_tree: MergedTree,
173 copy_records: &'a CopyRecords,
174 ) -> Self {
175 Self {
176 inner,
177 source_tree,
178 target_tree,
179 copy_records,
180 }
181 }
182
183 async fn resolve_copy_source(
184 &self,
185 source: &RepoPath,
186 values: BackendResult<Diff<MergedTreeValue>>,
187 ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {
188 let target_value = values?.after;
189 let source_value = self.source_tree.path_value(source).await?;
190 let source_value_at_target = self.target_tree.path_value(source).await?;
192 let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {
193 CopyOperation::Rename
194 } else {
195 CopyOperation::Copy
196 };
197 Ok((copy_op, Diff::new(source_value, target_value)))
198 }
199}
200
201impl Stream for CopiesTreeDiffStream<'_> {
202 type Item = CopiesTreeDiffEntry;
203
204 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
205 while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {
206 let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)
207 else {
208 let target_deleted =
209 matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());
210 if target_deleted && self.copy_records.has_source(&diff_entry.path) {
211 continue;
213 }
214 return Poll::Ready(Some(CopiesTreeDiffEntry {
215 path: CopiesTreeDiffEntryPath {
216 source: None,
217 target: diff_entry.path,
218 },
219 values: diff_entry.values,
220 }));
221 };
222
223 let (copy_op, values) = match self
224 .resolve_copy_source(source, diff_entry.values)
225 .block_on()
226 {
227 Ok((copy_op, values)) => (copy_op, Ok(values)),
228 Err(err) => (CopyOperation::Copy, Err(err)),
230 };
231 return Poll::Ready(Some(CopiesTreeDiffEntry {
232 path: CopiesTreeDiffEntryPath {
233 source: Some((source.clone(), copy_op)),
234 target: diff_entry.path,
235 },
236 values,
237 }));
238 }
239
240 Poll::Ready(None)
241 }
242}
243
244pub type CopyGraph = IndexMap<CopyId, CopyHistory>;
246
247fn collect_descendants(copy_graph: &CopyGraph) -> IndexMap<CopyId, IndexSet<CopyId>> {
248 let mut ancestor_map: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
249
250 let heads = dag_walk::heads(
256 copy_graph.keys(),
257 |id| *id,
258 |id| copy_graph[*id].parents.iter(),
259 )
260 .into_iter()
261 .sorted()
262 .collect_vec();
263 for id in dag_walk::topo_order_forward(
264 heads,
265 |id| *id,
266 |id| copy_graph[*id].parents.iter(),
267 |id| panic!("Cycle detected in copy history graph involving CopyId {id}"),
268 )
269 .expect("Could not walk CopyGraph")
270 {
271 let mut ancestors = IndexSet::new();
273 for parent in ©_graph[id].parents {
274 ancestors.extend(ancestor_map[parent].iter().cloned());
275 ancestors.insert(parent.clone());
276 }
277 ancestor_map.insert(id.clone(), ancestors);
278 }
279
280 let mut result: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
282 for (id, ancestors) in ancestor_map {
283 for ancestor in ancestors {
284 result.entry(ancestor).or_default().insert(id.clone());
285 }
286 result.entry(id.clone()).or_default();
289 }
290 result
291}
292
293fn iterate_ancestors<'a>(
296 copies: &'a CopyGraph,
297 initial_id: &'a CopyId,
298) -> impl Iterator<Item = &'a CopyId> {
299 let mut valid = HashSet::from([initial_id]);
300 copies.iter().filter_map(move |(id, history)| {
301 if valid.contains(id) {
302 valid.extend(history.parents.iter());
303 Some(id)
304 } else {
305 None
306 }
307 })
308}
309
310pub fn is_ancestor(copies: &CopyGraph, ancestor: &CopyId, descendant: &CopyId) -> bool {
312 for history in dag_walk::dfs(
313 [descendant],
314 |id| *id,
315 |id| copies.get(*id).unwrap().parents.iter(),
316 ) {
317 if history == ancestor {
318 return true;
319 }
320 }
321 false
322}
323
324#[derive(Clone, Debug, Eq, Hash, PartialEq)]
326pub enum CopyHistorySource {
327 Copy(RepoPathBuf),
329 Rename(RepoPathBuf),
331 Normal,
333}
334
335#[derive(Debug, Eq, Hash, PartialEq)]
337pub struct CopyHistoryDiffTerm {
338 pub target_value: Option<TreeValue>,
340 pub sources: Vec<(CopyHistorySource, MergedTreeValue)>,
343}
344
345#[derive(Debug)]
347pub struct CopyHistoryTreeDiffEntry {
348 pub target_path: RepoPathBuf,
350 pub diffs: BackendResult<Merge<CopyHistoryDiffTerm>>,
352}
353
354impl CopyHistoryTreeDiffEntry {
355 fn normal(diff_entry: TreeDiffEntry) -> Self {
357 let target_path = diff_entry.path;
358 let diffs = diff_entry.values.map(|diff| {
359 let sources = if diff.before.is_absent() {
360 vec![]
361 } else {
362 vec![(CopyHistorySource::Normal, diff.before)]
363 };
364 diff.after.into_map(|target_value| CopyHistoryDiffTerm {
365 target_value,
366 sources: sources.clone(),
367 })
368 });
369 Self { target_path, diffs }
370 }
371}
372
373pub struct CopyHistoryDiffStream<'a> {
375 inner: Fuse<TreeDiffStream<'a>>,
376 before_tree: &'a MergedTree,
377 after_tree: &'a MergedTree,
378 pending: FuturesOrdered<BoxFuture<'static, CopyHistoryTreeDiffEntry>>,
379}
380
381impl<'a> CopyHistoryDiffStream<'a> {
382 pub fn new(
387 inner: TreeDiffStream<'a>,
388 before_tree: &'a MergedTree,
389 after_tree: &'a MergedTree,
390 ) -> Self {
391 Self {
392 inner: inner.fuse(),
393 before_tree,
394 after_tree,
395 pending: FuturesOrdered::new(),
396 }
397 }
398}
399
400impl Stream for CopyHistoryDiffStream<'_> {
401 type Item = CopyHistoryTreeDiffEntry;
402
403 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
404 loop {
405 if let Poll::Ready(Some(next)) = self.pending.poll_next_unpin(cx) {
408 return Poll::Ready(Some(next));
409 }
410
411 let next_diff_entry = match ready!(self.inner.poll_next_unpin(cx)) {
414 Some(diff_entry) => diff_entry,
415 None if self.pending.is_empty() => return Poll::Ready(None),
416 _ => return Poll::Pending,
417 };
418
419 let Ok(Diff { before, after }) = &next_diff_entry.values else {
420 self.pending
421 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
422 next_diff_entry,
423 ))));
424 continue;
425 };
426
427 let Some(before) = before.as_resolved() else {
431 self.pending
432 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
433 next_diff_entry,
434 ))));
435 continue;
436 };
437 let Some(after) = after.as_resolved() else {
438 self.pending
439 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
440 next_diff_entry,
441 ))));
442 continue;
443 };
444
445 match (before, after) {
446 (
448 Some(TreeValue::File { copy_id: id1, .. }),
449 Some(TreeValue::File { copy_id: id2, .. }),
450 ) if id1 == id2 => {
451 self.pending
452 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
453 next_diff_entry,
454 ))));
455 }
456
457 (other, Some(f @ TreeValue::File { .. })) => {
458 if let Some(other) = other {
459 self.pending
476 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry {
477 target_path: next_diff_entry.path.clone(),
478 diffs: Ok(Merge::resolved(CopyHistoryDiffTerm {
479 target_value: None,
480 sources: vec![(
481 CopyHistorySource::Normal,
482 Merge::resolved(Some(other.clone())),
483 )],
484 })),
485 })));
486 }
487
488 let future = tree_diff_entry_from_copies(
489 self.before_tree.clone(),
490 self.after_tree.clone(),
491 f.clone(),
492 next_diff_entry.path.clone(),
493 );
494 self.pending.push_back(Box::pin(future));
495 }
496
497 _ => self
502 .pending
503 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
504 next_diff_entry,
505 )))),
506 }
507 }
508 }
509}
510
511async fn tree_diff_entry_from_copies(
512 before_tree: MergedTree,
513 after_tree: MergedTree,
514 file: TreeValue,
515 target_path: RepoPathBuf,
516) -> CopyHistoryTreeDiffEntry {
517 CopyHistoryTreeDiffEntry {
518 target_path,
519 diffs: diffs_from_copies(before_tree, after_tree, file).await,
520 }
521}
522
523async fn diffs_from_copies(
524 before_tree: MergedTree,
525 after_tree: MergedTree,
526 after_file: TreeValue,
527) -> BackendResult<Merge<CopyHistoryDiffTerm>> {
528 let copy_id = after_file.copy_id().ok_or(BackendError::Other(
529 "Expected TreeValue::File with a CopyId".into(),
530 ))?;
531 let copy_graph: CopyGraph = before_tree
532 .store()
533 .backend()
534 .get_related_copies(copy_id)
535 .await?
536 .into_iter()
537 .map(|related| (related.id, related.history))
538 .collect();
539
540 let descendants = collect_descendants(©_graph);
541 let copies =
542 find_diff_sources_from_copies(&before_tree, copy_id, ©_graph, &descendants).await?;
543
544 try_join_all(copies.into_iter().map(async |(before_path, before_val)| {
545 classify_source(
546 &after_tree,
547 copy_id,
548 before_path,
549 before_val
550 .copy_id()
551 .expect("expected TreeValue::File with a CopyId"),
552 ©_graph,
553 )
554 .await
555 .map(|source| (source, Merge::resolved(Some(before_val))))
556 }))
557 .await
558 .map(|sources| {
559 Merge::resolved(CopyHistoryDiffTerm {
560 target_value: Some(after_file),
561 sources,
562 })
563 })
564}
565
566async fn classify_source(
567 after_tree: &MergedTree,
568 after_id: &CopyId,
569 before_path: RepoPathBuf,
570 before_id: &CopyId,
571 copy_graph: &CopyGraph,
572) -> BackendResult<CopyHistorySource> {
573 let history = copy_graph
574 .get(after_id)
575 .expect("copy_graph should already include after_id");
576 let after_path = &history.current_path;
577
578 if *after_path == before_path
582 && (is_ancestor(copy_graph, after_id, before_id)
583 || is_ancestor(copy_graph, before_id, after_id))
584 {
585 return Ok(CopyHistorySource::Normal);
586 }
587
588 let after_tree_before_path_val = after_tree.path_value(&before_path).await?;
589 let Some(after_tree_before_path_id) = after_tree_before_path_val
593 .to_copy_id_merge()
594 .expect("expected merge of `TreeValue::File`s")
595 .resolve_trivial(SameChange::Accept)
596 .expect("expected no CopyId conflicts")
597 .clone()
598 else {
599 return Ok(CopyHistorySource::Rename(before_path));
601 };
602
603 if is_ancestor(copy_graph, before_id, &after_tree_before_path_id)
604 || is_ancestor(copy_graph, &after_tree_before_path_id, before_id)
605 {
606 Ok(CopyHistorySource::Copy(before_path))
607 } else {
608 Ok(CopyHistorySource::Rename(before_path))
611 }
612}
613
614async fn find_diff_sources_from_copies(
615 tree: &MergedTree,
616 copy_id: &CopyId,
617 copy_graph: &CopyGraph,
618 descendants: &IndexMap<CopyId, IndexSet<CopyId>>,
619) -> BackendResult<Vec<(RepoPathBuf, TreeValue)>> {
620 let history = copy_graph.get(copy_id).ok_or(BackendError::Other(
623 "CopyId should be present in `get_related_copies()` result".into(),
624 ))?;
625
626 if history.parents.is_empty() {
627 for descendant_id in &descendants[copy_id] {
630 if let Some(descendant) = tree.copy_value(descendant_id).await? {
631 return Ok(vec![(
632 copy_graph[descendant_id].current_path.clone(),
633 descendant,
634 )]);
635 }
636 }
637 }
638
639 let mut sources = vec![];
640
641 'parents: for parent_copy_id in &history.parents {
661 let mut absent_ancestors = vec![];
662
663 for ancestor_id in iterate_ancestors(copy_graph, parent_copy_id) {
665 let ancestor_history = copy_graph.get(ancestor_id).ok_or(BackendError::Other(
666 "Ancestor CopyId should be present in `get_related_copies()` result".into(),
667 ))?;
668 if let Some(ancestor) = tree.copy_value(ancestor_id).await? {
669 sources.push((ancestor_history.current_path.clone(), ancestor));
670 continue 'parents;
671 } else {
672 absent_ancestors.push(ancestor_id);
673 }
674 }
675
676 for descendant_id in &descendants[parent_copy_id] {
681 if let Some(descendant) = tree.copy_value(descendant_id).await? {
682 sources.push((copy_graph[descendant_id].current_path.clone(), descendant));
683 continue 'parents;
684 }
685 }
686
687 for ancestor_id in absent_ancestors {
692 for descendant_id in descendants[ancestor_id].difference(&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 }
700 Ok(sources)
701}