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 pollster::FutureExt as _;
34
35use crate::backend::BackendError;
36use crate::backend::BackendResult;
37use crate::backend::CopyHistory;
38use crate::backend::CopyId;
39use crate::backend::CopyRecord;
40use crate::backend::TreeValue;
41use crate::dag_walk;
42use crate::merge::Diff;
43use crate::merge::Merge;
44use crate::merge::MergedTreeValue;
45use crate::merge::SameChange;
46use crate::merged_tree::MergedTree;
47use crate::merged_tree::TreeDiffEntry;
48use crate::merged_tree::TreeDiffStream;
49use crate::repo_path::RepoPath;
50use crate::repo_path::RepoPathBuf;
51
52#[derive(Default, Debug)]
54pub struct CopyRecords {
55 records: Vec<CopyRecord>,
56 sources: HashMap<RepoPathBuf, usize>,
59 targets: HashMap<RepoPathBuf, usize>,
60}
61
62impl CopyRecords {
63 pub fn add_records(&mut self, copy_records: impl IntoIterator<Item = CopyRecord>) {
66 for r in copy_records {
67 self.sources
68 .entry(r.source.clone())
69 .and_modify(|value| *value = usize::MAX)
71 .or_insert(self.records.len());
72 self.targets
73 .entry(r.target.clone())
74 .and_modify(|value| *value = usize::MAX)
76 .or_insert(self.records.len());
77 self.records.push(r);
78 }
79 }
80
81 pub fn has_source(&self, source: &RepoPath) -> bool {
83 self.sources.contains_key(source)
84 }
85
86 pub fn for_source(&self, source: &RepoPath) -> Option<&CopyRecord> {
88 self.sources.get(source).and_then(|&i| self.records.get(i))
89 }
90
91 pub fn has_target(&self, target: &RepoPath) -> bool {
93 self.targets.contains_key(target)
94 }
95
96 pub fn for_target(&self, target: &RepoPath) -> Option<&CopyRecord> {
98 self.targets.get(target).and_then(|&i| self.records.get(i))
99 }
100
101 pub fn iter(&self) -> impl Iterator<Item = &CopyRecord> {
103 self.records.iter()
104 }
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum CopyOperation {
110 Copy,
112 Rename,
114}
115
116#[derive(Debug)]
118pub struct CopiesTreeDiffEntry {
119 pub path: CopiesTreeDiffEntryPath,
121 pub values: BackendResult<Diff<MergedTreeValue>>,
123}
124
125#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct CopiesTreeDiffEntryPath {
128 pub source: Option<(RepoPathBuf, CopyOperation)>,
130 pub target: RepoPathBuf,
132}
133
134impl CopiesTreeDiffEntryPath {
135 pub fn source(&self) -> &RepoPath {
137 self.source.as_ref().map_or(&self.target, |(path, _)| path)
138 }
139
140 pub fn target(&self) -> &RepoPath {
142 &self.target
143 }
144
145 pub fn copy_operation(&self) -> Option<CopyOperation> {
148 self.source.as_ref().map(|(_, op)| *op)
149 }
150
151 pub fn to_diff(&self) -> Option<Diff<&RepoPath>> {
153 let (source, _) = self.source.as_ref()?;
154 Some(Diff::new(source, &self.target))
155 }
156}
157
158pub struct CopiesTreeDiffStream<'a> {
160 inner: TreeDiffStream<'a>,
161 source_tree: MergedTree,
162 target_tree: MergedTree,
163 copy_records: &'a CopyRecords,
164}
165
166impl<'a> CopiesTreeDiffStream<'a> {
167 pub fn new(
169 inner: TreeDiffStream<'a>,
170 source_tree: MergedTree,
171 target_tree: MergedTree,
172 copy_records: &'a CopyRecords,
173 ) -> Self {
174 Self {
175 inner,
176 source_tree,
177 target_tree,
178 copy_records,
179 }
180 }
181
182 async fn resolve_copy_source(
183 &self,
184 source: &RepoPath,
185 values: BackendResult<Diff<MergedTreeValue>>,
186 ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {
187 let target_value = values?.after;
188 let source_value = self.source_tree.path_value(source).await?;
189 let source_value_at_target = self.target_tree.path_value(source).await?;
191 let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {
192 CopyOperation::Rename
193 } else {
194 CopyOperation::Copy
195 };
196 Ok((copy_op, Diff::new(source_value, target_value)))
197 }
198}
199
200impl Stream for CopiesTreeDiffStream<'_> {
201 type Item = CopiesTreeDiffEntry;
202
203 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
204 while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {
205 let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)
206 else {
207 let target_deleted =
208 matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());
209 if target_deleted && self.copy_records.has_source(&diff_entry.path) {
210 continue;
212 }
213 return Poll::Ready(Some(CopiesTreeDiffEntry {
214 path: CopiesTreeDiffEntryPath {
215 source: None,
216 target: diff_entry.path,
217 },
218 values: diff_entry.values,
219 }));
220 };
221
222 let (copy_op, values) = match self
223 .resolve_copy_source(source, diff_entry.values)
224 .block_on()
225 {
226 Ok((copy_op, values)) => (copy_op, Ok(values)),
227 Err(err) => (CopyOperation::Copy, Err(err)),
229 };
230 return Poll::Ready(Some(CopiesTreeDiffEntry {
231 path: CopiesTreeDiffEntryPath {
232 source: Some((source.clone(), copy_op)),
233 target: diff_entry.path,
234 },
235 values,
236 }));
237 }
238
239 Poll::Ready(None)
240 }
241}
242
243pub type CopyGraph = IndexMap<CopyId, CopyHistory>;
245
246fn collect_descendants(copy_graph: &CopyGraph) -> IndexMap<CopyId, IndexSet<CopyId>> {
247 let mut ancestor_map: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
248
249 let heads = dag_walk::heads(
255 copy_graph.keys(),
256 |id| *id,
257 |id| copy_graph[*id].parents.iter(),
258 );
259 for id in dag_walk::topo_order_forward(
260 heads,
261 |id| *id,
262 |id| copy_graph[*id].parents.iter(),
263 |id| panic!("Cycle detected in copy history graph involving CopyId {id}"),
264 )
265 .expect("Could not walk CopyGraph")
266 {
267 let mut ancestors = IndexSet::new();
269 for parent in ©_graph[id].parents {
270 ancestors.extend(ancestor_map[parent].iter().cloned());
271 ancestors.insert(parent.clone());
272 }
273 ancestor_map.insert(id.clone(), ancestors);
274 }
275
276 let mut result: IndexMap<CopyId, IndexSet<CopyId>> = IndexMap::new();
278 for (id, ancestors) in ancestor_map {
279 for ancestor in ancestors {
280 result.entry(ancestor).or_default().insert(id.clone());
281 }
282 result.entry(id.clone()).or_default();
285 }
286 result
287}
288
289fn iterate_ancestors<'a>(
292 copies: &'a CopyGraph,
293 initial_id: &'a CopyId,
294) -> impl Iterator<Item = &'a CopyId> {
295 let mut valid = HashSet::from([initial_id]);
296 copies.iter().filter_map(move |(id, history)| {
297 if valid.contains(id) {
298 valid.extend(history.parents.iter());
299 Some(id)
300 } else {
301 None
302 }
303 })
304}
305
306pub fn is_ancestor(copies: &CopyGraph, ancestor: &CopyId, descendant: &CopyId) -> bool {
308 for history in dag_walk::dfs(
309 [descendant],
310 |id| *id,
311 |id| copies.get(*id).unwrap().parents.iter(),
312 ) {
313 if history == ancestor {
314 return true;
315 }
316 }
317 false
318}
319
320#[derive(Clone, Debug, Eq, Hash, PartialEq)]
322pub enum CopyHistorySource {
323 Copy(RepoPathBuf),
325 Rename(RepoPathBuf),
327 Normal,
329}
330
331#[derive(Debug, Eq, Hash, PartialEq)]
333pub struct CopyHistoryDiffTerm {
334 pub target_value: Option<TreeValue>,
336 pub sources: Vec<(CopyHistorySource, MergedTreeValue)>,
339}
340
341#[derive(Debug)]
343pub struct CopyHistoryTreeDiffEntry {
344 pub target_path: RepoPathBuf,
346 pub diffs: BackendResult<Merge<CopyHistoryDiffTerm>>,
348}
349
350impl CopyHistoryTreeDiffEntry {
351 fn normal(diff_entry: TreeDiffEntry) -> Self {
353 let target_path = diff_entry.path;
354 let diffs = diff_entry.values.map(|diff| {
355 let sources = if diff.before.is_absent() {
356 vec![]
357 } else {
358 vec![(CopyHistorySource::Normal, diff.before)]
359 };
360 diff.after.into_map(|target_value| CopyHistoryDiffTerm {
361 target_value,
362 sources: sources.clone(),
363 })
364 });
365 Self { target_path, diffs }
366 }
367}
368
369pub struct CopyHistoryDiffStream<'a> {
371 inner: Fuse<TreeDiffStream<'a>>,
372 before_tree: &'a MergedTree,
373 after_tree: &'a MergedTree,
374 pending: FuturesOrdered<BoxFuture<'static, CopyHistoryTreeDiffEntry>>,
375}
376
377impl<'a> CopyHistoryDiffStream<'a> {
378 pub fn new(
383 inner: TreeDiffStream<'a>,
384 before_tree: &'a MergedTree,
385 after_tree: &'a MergedTree,
386 ) -> Self {
387 Self {
388 inner: inner.fuse(),
389 before_tree,
390 after_tree,
391 pending: FuturesOrdered::new(),
392 }
393 }
394}
395
396impl Stream for CopyHistoryDiffStream<'_> {
397 type Item = CopyHistoryTreeDiffEntry;
398
399 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
400 loop {
401 if let Poll::Ready(Some(next)) = self.pending.poll_next_unpin(cx) {
404 return Poll::Ready(Some(next));
405 }
406
407 let next_diff_entry = match ready!(self.inner.poll_next_unpin(cx)) {
410 Some(diff_entry) => diff_entry,
411 None if self.pending.is_empty() => return Poll::Ready(None),
412 _ => return Poll::Pending,
413 };
414
415 let Ok(Diff { before, after }) = &next_diff_entry.values else {
416 self.pending
417 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
418 next_diff_entry,
419 ))));
420 continue;
421 };
422
423 let Some(before) = before.as_resolved() else {
427 self.pending
428 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
429 next_diff_entry,
430 ))));
431 continue;
432 };
433 let Some(after) = after.as_resolved() else {
434 self.pending
435 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
436 next_diff_entry,
437 ))));
438 continue;
439 };
440
441 match (before, after) {
442 (
444 Some(TreeValue::File { copy_id: id1, .. }),
445 Some(TreeValue::File { copy_id: id2, .. }),
446 ) if id1 == id2 => {
447 self.pending
448 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
449 next_diff_entry,
450 ))));
451 }
452
453 (other, Some(f @ TreeValue::File { .. })) => {
454 if let Some(other) = other {
455 self.pending
472 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry {
473 target_path: next_diff_entry.path.clone(),
474 diffs: Ok(Merge::resolved(CopyHistoryDiffTerm {
475 target_value: None,
476 sources: vec![(
477 CopyHistorySource::Normal,
478 Merge::resolved(Some(other.clone())),
479 )],
480 })),
481 })));
482 }
483
484 let future = tree_diff_entry_from_copies(
485 self.before_tree.clone(),
486 self.after_tree.clone(),
487 f.clone(),
488 next_diff_entry.path.clone(),
489 );
490 self.pending.push_back(Box::pin(future));
491 }
492
493 _ => self
498 .pending
499 .push_back(Box::pin(ready(CopyHistoryTreeDiffEntry::normal(
500 next_diff_entry,
501 )))),
502 }
503 }
504 }
505}
506
507async fn tree_diff_entry_from_copies(
508 before_tree: MergedTree,
509 after_tree: MergedTree,
510 file: TreeValue,
511 target_path: RepoPathBuf,
512) -> CopyHistoryTreeDiffEntry {
513 CopyHistoryTreeDiffEntry {
514 target_path,
515 diffs: diffs_from_copies(before_tree, after_tree, file).await,
516 }
517}
518
519async fn diffs_from_copies(
520 before_tree: MergedTree,
521 after_tree: MergedTree,
522 after_file: TreeValue,
523) -> BackendResult<Merge<CopyHistoryDiffTerm>> {
524 let copy_id = after_file.copy_id().ok_or(BackendError::Other(
525 "Expected TreeValue::File with a CopyId".into(),
526 ))?;
527 let copy_graph: CopyGraph = before_tree
528 .store()
529 .backend()
530 .get_related_copies(copy_id)
531 .await?
532 .into_iter()
533 .map(|related| (related.id, related.history))
534 .collect();
535
536 let descendants = collect_descendants(©_graph);
537 let copies =
538 find_diff_sources_from_copies(&before_tree, copy_id, ©_graph, &descendants).await?;
539
540 try_join_all(copies.into_iter().map(async |(before_path, before_val)| {
541 classify_source(
542 &after_tree,
543 copy_id,
544 before_path,
545 before_val
546 .copy_id()
547 .expect("expected TreeValue::File with a CopyId"),
548 ©_graph,
549 )
550 .await
551 .map(|source| (source, Merge::resolved(Some(before_val))))
552 }))
553 .await
554 .map(|sources| {
555 Merge::resolved(CopyHistoryDiffTerm {
556 target_value: Some(after_file),
557 sources,
558 })
559 })
560}
561
562async fn classify_source(
563 after_tree: &MergedTree,
564 after_id: &CopyId,
565 before_path: RepoPathBuf,
566 before_id: &CopyId,
567 copy_graph: &CopyGraph,
568) -> BackendResult<CopyHistorySource> {
569 let history = copy_graph
570 .get(after_id)
571 .expect("copy_graph should already include after_id");
572 let after_path = &history.current_path;
573
574 if *after_path == before_path
578 && (is_ancestor(copy_graph, after_id, before_id)
579 || is_ancestor(copy_graph, before_id, after_id))
580 {
581 return Ok(CopyHistorySource::Normal);
582 }
583
584 let after_tree_before_path_val = after_tree.path_value(&before_path).await?;
585 let Some(after_tree_before_path_id) = after_tree_before_path_val
589 .to_copy_id_merge()
590 .expect("expected merge of `TreeValue::File`s")
591 .resolve_trivial(SameChange::Accept)
592 .expect("expected no CopyId conflicts")
593 .clone()
594 else {
595 return Ok(CopyHistorySource::Rename(before_path));
597 };
598
599 if is_ancestor(copy_graph, before_id, &after_tree_before_path_id)
600 || is_ancestor(copy_graph, &after_tree_before_path_id, before_id)
601 {
602 Ok(CopyHistorySource::Copy(before_path))
603 } else {
604 Ok(CopyHistorySource::Rename(before_path))
607 }
608}
609
610async fn find_diff_sources_from_copies(
611 tree: &MergedTree,
612 copy_id: &CopyId,
613 copy_graph: &CopyGraph,
614 descendants: &IndexMap<CopyId, IndexSet<CopyId>>,
615) -> BackendResult<Vec<(RepoPathBuf, TreeValue)>> {
616 let history = copy_graph.get(copy_id).ok_or(BackendError::Other(
619 "CopyId should be present in `get_related_copies()` result".into(),
620 ))?;
621
622 if history.parents.is_empty() {
623 for descendant_id in &descendants[copy_id] {
626 if let Some(descendant) = tree.copy_value(descendant_id).await? {
627 return Ok(vec![(
628 copy_graph[descendant_id].current_path.clone(),
629 descendant,
630 )]);
631 }
632 }
633 }
634
635 let mut sources = vec![];
636
637 'parents: for parent_copy_id in &history.parents {
657 let mut absent_ancestors = vec![];
658
659 for ancestor_id in iterate_ancestors(copy_graph, parent_copy_id) {
661 let ancestor_history = copy_graph.get(ancestor_id).ok_or(BackendError::Other(
662 "Ancestor CopyId should be present in `get_related_copies()` result".into(),
663 ))?;
664 if let Some(ancestor) = tree.copy_value(ancestor_id).await? {
665 sources.push((ancestor_history.current_path.clone(), ancestor));
666 continue 'parents;
667 } else {
668 absent_ancestors.push(ancestor_id);
669 }
670 }
671
672 for descendant_id in &descendants[parent_copy_id] {
677 if let Some(descendant) = tree.copy_value(descendant_id).await? {
678 sources.push((copy_graph[descendant_id].current_path.clone(), descendant));
679 continue 'parents;
680 }
681 }
682
683 for ancestor_id in absent_ancestors {
688 for descendant_id in descendants[ancestor_id].difference(&descendants[parent_copy_id]) {
689 if let Some(descendant) = tree.copy_value(descendant_id).await? {
690 sources.push((copy_graph[descendant_id].current_path.clone(), descendant));
691 continue 'parents;
692 }
693 }
694 }
695 }
696 Ok(sources)
697}