1use std::collections::HashMap;
18use std::pin::Pin;
19use std::task::Context;
20use std::task::Poll;
21use std::task::ready;
22
23use futures::Stream;
24
25use crate::backend::BackendResult;
26use crate::backend::CopyRecord;
27use crate::merge::Diff;
28use crate::merge::MergedTreeValue;
29use crate::merged_tree::MergedTree;
30use crate::merged_tree::TreeDiffStream;
31use crate::repo_path::RepoPath;
32use crate::repo_path::RepoPathBuf;
33
34#[derive(Default, Debug)]
36pub struct CopyRecords {
37 records: Vec<CopyRecord>,
38 sources: HashMap<RepoPathBuf, usize>,
41 targets: HashMap<RepoPathBuf, usize>,
42}
43
44impl CopyRecords {
45 pub fn add_records(
48 &mut self,
49 copy_records: impl IntoIterator<Item = BackendResult<CopyRecord>>,
50 ) -> BackendResult<()> {
51 for record in copy_records {
52 let r = record?;
53 self.sources
54 .entry(r.source.clone())
55 .and_modify(|value| *value = usize::MAX)
57 .or_insert(self.records.len());
58 self.targets
59 .entry(r.target.clone())
60 .and_modify(|value| *value = usize::MAX)
62 .or_insert(self.records.len());
63 self.records.push(r);
64 }
65 Ok(())
66 }
67
68 pub fn has_source(&self, source: &RepoPath) -> bool {
70 self.sources.contains_key(source)
71 }
72
73 pub fn for_source(&self, source: &RepoPath) -> Option<&CopyRecord> {
75 self.sources.get(source).and_then(|&i| self.records.get(i))
76 }
77
78 pub fn has_target(&self, target: &RepoPath) -> bool {
80 self.targets.contains_key(target)
81 }
82
83 pub fn for_target(&self, target: &RepoPath) -> Option<&CopyRecord> {
85 self.targets.get(target).and_then(|&i| self.records.get(i))
86 }
87
88 pub fn iter(&self) -> impl Iterator<Item = &CopyRecord> {
90 self.records.iter()
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub enum CopyOperation {
97 Copy,
99 Rename,
101}
102
103#[derive(Debug)]
105pub struct CopiesTreeDiffEntry {
106 pub path: CopiesTreeDiffEntryPath,
108 pub values: BackendResult<Diff<MergedTreeValue>>,
110}
111
112#[derive(Clone, Debug, Eq, PartialEq)]
114pub struct CopiesTreeDiffEntryPath {
115 pub source: Option<(RepoPathBuf, CopyOperation)>,
117 pub target: RepoPathBuf,
119}
120
121impl CopiesTreeDiffEntryPath {
122 pub fn source(&self) -> &RepoPath {
124 self.source.as_ref().map_or(&self.target, |(path, _)| path)
125 }
126
127 pub fn target(&self) -> &RepoPath {
129 &self.target
130 }
131
132 pub fn copy_operation(&self) -> Option<CopyOperation> {
135 self.source.as_ref().map(|(_, op)| *op)
136 }
137
138 pub fn to_diff(&self) -> Option<Diff<&RepoPath>> {
140 let (source, _) = self.source.as_ref()?;
141 Some(Diff::new(source, &self.target))
142 }
143}
144
145pub struct CopiesTreeDiffStream<'a> {
147 inner: TreeDiffStream<'a>,
148 source_tree: MergedTree,
149 target_tree: MergedTree,
150 copy_records: &'a CopyRecords,
151}
152
153impl<'a> CopiesTreeDiffStream<'a> {
154 pub fn new(
156 inner: TreeDiffStream<'a>,
157 source_tree: MergedTree,
158 target_tree: MergedTree,
159 copy_records: &'a CopyRecords,
160 ) -> Self {
161 Self {
162 inner,
163 source_tree,
164 target_tree,
165 copy_records,
166 }
167 }
168
169 fn resolve_copy_source(
170 &self,
171 source: &RepoPath,
172 values: BackendResult<Diff<MergedTreeValue>>,
173 ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {
174 let target_value = values?.after;
175 let source_value = self.source_tree.path_value(source)?;
176 let source_value_at_target = self.target_tree.path_value(source)?;
178 let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {
179 CopyOperation::Rename
180 } else {
181 CopyOperation::Copy
182 };
183 Ok((copy_op, Diff::new(source_value, target_value)))
184 }
185}
186
187impl Stream for CopiesTreeDiffStream<'_> {
188 type Item = CopiesTreeDiffEntry;
189
190 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
191 while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {
192 let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)
193 else {
194 let target_deleted =
195 matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());
196 if target_deleted && self.copy_records.has_source(&diff_entry.path) {
197 continue;
199 }
200 return Poll::Ready(Some(CopiesTreeDiffEntry {
201 path: CopiesTreeDiffEntryPath {
202 source: None,
203 target: diff_entry.path,
204 },
205 values: diff_entry.values,
206 }));
207 };
208
209 let (copy_op, values) = match self.resolve_copy_source(source, diff_entry.values) {
210 Ok((copy_op, values)) => (copy_op, Ok(values)),
211 Err(err) => (CopyOperation::Copy, Err(err)),
213 };
214 return Poll::Ready(Some(CopiesTreeDiffEntry {
215 path: CopiesTreeDiffEntryPath {
216 source: Some((source.clone(), copy_op)),
217 target: diff_entry.path,
218 },
219 values,
220 }));
221 }
222
223 Poll::Ready(None)
224 }
225}