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
103pub struct CopiesTreeDiffEntry {
105 pub path: CopiesTreeDiffEntryPath,
107 pub values: BackendResult<Diff<MergedTreeValue>>,
109}
110
111#[derive(Clone, Debug, Eq, PartialEq)]
113pub struct CopiesTreeDiffEntryPath {
114 pub source: Option<(RepoPathBuf, CopyOperation)>,
116 pub target: RepoPathBuf,
118}
119
120impl CopiesTreeDiffEntryPath {
121 pub fn source(&self) -> &RepoPath {
123 self.source.as_ref().map_or(&self.target, |(path, _)| path)
124 }
125
126 pub fn target(&self) -> &RepoPath {
128 &self.target
129 }
130
131 pub fn copy_operation(&self) -> Option<CopyOperation> {
134 self.source.as_ref().map(|(_, op)| *op)
135 }
136}
137
138pub struct CopiesTreeDiffStream<'a> {
140 inner: TreeDiffStream<'a>,
141 source_tree: MergedTree,
142 target_tree: MergedTree,
143 copy_records: &'a CopyRecords,
144}
145
146impl<'a> CopiesTreeDiffStream<'a> {
147 pub fn new(
149 inner: TreeDiffStream<'a>,
150 source_tree: MergedTree,
151 target_tree: MergedTree,
152 copy_records: &'a CopyRecords,
153 ) -> Self {
154 Self {
155 inner,
156 source_tree,
157 target_tree,
158 copy_records,
159 }
160 }
161
162 fn resolve_copy_source(
163 &self,
164 source: &RepoPath,
165 values: BackendResult<Diff<MergedTreeValue>>,
166 ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {
167 let target_value = values?.after;
168 let source_value = self.source_tree.path_value(source)?;
169 let source_value_at_target = self.target_tree.path_value(source)?;
171 let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {
172 CopyOperation::Rename
173 } else {
174 CopyOperation::Copy
175 };
176 Ok((copy_op, Diff::new(source_value, target_value)))
177 }
178}
179
180impl Stream for CopiesTreeDiffStream<'_> {
181 type Item = CopiesTreeDiffEntry;
182
183 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
184 while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {
185 let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)
186 else {
187 let target_deleted =
188 matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());
189 if target_deleted && self.copy_records.has_source(&diff_entry.path) {
190 continue;
192 }
193 return Poll::Ready(Some(CopiesTreeDiffEntry {
194 path: CopiesTreeDiffEntryPath {
195 source: None,
196 target: diff_entry.path,
197 },
198 values: diff_entry.values,
199 }));
200 };
201
202 let (copy_op, values) = match self.resolve_copy_source(source, diff_entry.values) {
203 Ok((copy_op, values)) => (copy_op, Ok(values)),
204 Err(err) => (CopyOperation::Copy, Err(err)),
206 };
207 return Poll::Ready(Some(CopiesTreeDiffEntry {
208 path: CopiesTreeDiffEntryPath {
209 source: Some((source.clone(), copy_op)),
210 target: diff_entry.path,
211 },
212 values,
213 }));
214 }
215
216 Poll::Ready(None)
217 }
218}