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