##// END OF EJS Templates
copies-rust: add a macro-based unit-testing framework...
Simon Sapin -
r47405:b92083ad default draft
parent child Browse files
Show More
@@ -0,0 +1,141 b''
1 use super::*;
2
3 /// Unit tests for:
4 ///
5 /// ```ignore
6 /// fn compare_value(
7 /// current_merge: Revision,
8 /// merge_case_for_dest: impl Fn() -> MergeCase,
9 /// src_minor: &CopySource,
10 /// src_major: &CopySource,
11 /// ) -> (MergePick, /* overwrite: */ bool)
12 /// ```
13 #[test]
14 fn test_compare_value() {
15 // The `compare_value!` macro calls the `compare_value` function with
16 // arguments given in pseudo-syntax:
17 //
18 // * For `merge_case_for_dest` it takes a plain `MergeCase` value instead
19 // of a closure.
20 // * `CopySource` values are represented as `(rev, path, overwritten)`
21 // tuples of type `(Revision, Option<PathToken>, OrdSet<Revision>)`.
22 // * `PathToken` is an integer not read by `compare_value`. It only checks
23 // for `Some(_)` indicating a file copy v.s. `None` for a file deletion.
24 // * `OrdSet<Revision>` is represented as a Python-like set literal.
25
26 use MergeCase::*;
27 use MergePick::*;
28
29 assert_eq!(
30 compare_value!(1, Normal, (1, None, { 1 }), (1, None, { 1 })),
31 (Any, false)
32 );
33 }
34
35 /// Unit tests for:
36 ///
37 /// ```ignore
38 /// fn merge_copies_dict(
39 /// path_map: &TwoWayPathMap, // Not visible in test cases
40 /// current_merge: Revision,
41 /// minor: InternalPathCopies,
42 /// major: InternalPathCopies,
43 /// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
44 /// ) -> InternalPathCopies
45 /// ```
46 #[test]
47 fn test_merge_copies_dict() {
48 // The `merge_copies_dict!` macro calls the `merge_copies_dict` function
49 // with arguments given in pseudo-syntax:
50 //
51 // * `TwoWayPathMap` and path tokenization are implicitly taken care of.
52 // All paths are given as string literals.
53 // * Key-value maps are represented with `{key1 => value1, key2 => value2}`
54 // pseudo-syntax.
55 // * `InternalPathCopies` is a map of copy destination path keys to
56 // `CopySource` values.
57 // - `CopySource` is represented as a `(rev, source_path, overwritten)`
58 // tuple of type `(Revision, Option<Path>, OrdSet<Revision>)`.
59 // - Unlike in `test_compare_value`, source paths are string literals.
60 // - `OrdSet<Revision>` is again represented as a Python-like set
61 // literal.
62 // * `get_merge_case` is represented as a map of copy destination path to
63 // `MergeCase`. The default for paths not in the map is
64 // `MergeCase::Normal`.
65 //
66 // `internal_path_copies!` creates an `InternalPathCopies` value with the
67 // same pseudo-syntax as in `merge_copies_dict!`.
68
69 use MergeCase::*;
70
71 assert_eq!(
72 merge_copies_dict!(
73 1,
74 {"foo" => (1, None, {})},
75 {},
76 {"foo" => Merged}
77 ),
78 internal_path_copies!("foo" => (1, None, {}))
79 );
80 }
81
82 /// Unit tests for:
83 ///
84 /// ```ignore
85 /// impl CombineChangesetCopies {
86 /// fn new(children_count: HashMap<Revision, usize>) -> Self
87 ///
88 /// // Called repeatedly:
89 /// fn add_revision_inner<'a>(
90 /// &mut self,
91 /// rev: Revision,
92 /// p1: Revision,
93 /// p2: Revision,
94 /// copy_actions: impl Iterator<Item = Action<'a>>,
95 /// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
96 /// )
97 ///
98 /// fn finish(mut self, target_rev: Revision) -> PathCopies
99 /// }
100 /// ```
101 #[test]
102 fn test_combine_changeset_copies() {
103 // `combine_changeset_copies!` creates a `CombineChangesetCopies` with
104 // `new`, then calls `add_revision_inner` repeatedly, then calls `finish`
105 // for its return value.
106 //
107 // All paths given as string literals.
108 //
109 // * Key-value maps are represented with `{key1 => value1, key2 => value2}`
110 // pseudo-syntax.
111 // * `children_count` is a map of revision numbers to count of children in
112 // the DAG. It includes all revisions that should be considered by the
113 // algorithm.
114 // * Calls to `add_revision_inner` are represented as an array of anonymous
115 // structs with named fields, one pseudo-struct per call.
116 //
117 // `path_copies!` creates a `PathCopies` value, a map of copy destination
118 // keys to copy source values. Note: the arrows for map literal syntax
119 // point **backwards** compared to the logical direction of copy!
120
121 use crate::NULL_REVISION as NULL;
122 use Action::*;
123 use MergeCase::*;
124
125 assert_eq!(
126 combine_changeset_copies!(
127 { 1 => 1, 2 => 1 },
128 [
129 { rev: 1, p1: NULL, p2: NULL, actions: [], merge_cases: {}, },
130 { rev: 2, p1: NULL, p2: NULL, actions: [], merge_cases: {}, },
131 {
132 rev: 3, p1: 1, p2: 2,
133 actions: [CopiedFromP1("destination.txt", "source.txt")],
134 merge_cases: {"destination.txt" => Merged},
135 },
136 ],
137 3,
138 ),
139 path_copies!("destination.txt" => "source.txt")
140 );
141 }
@@ -0,0 +1,199 b''
1 //! Supporting macros for `tests.rs` in the same directory.
2 //! See comments there for usage.
3
4 /// Python-like set literal
5 macro_rules! set {
6 (
7 $Type: ty {
8 $( $value: expr ),* $(,)?
9 }
10 ) => {{
11 #[allow(unused_mut)]
12 let mut set = <$Type>::new();
13 $( set.insert($value); )*
14 set
15 }}
16 }
17
18 /// `{key => value}` map literal
19 macro_rules! map {
20 (
21 $Type: ty {
22 $( $key: expr => $value: expr ),* $(,)?
23 }
24 ) => {{
25 #[allow(unused_mut)]
26 let mut set = <$Type>::new();
27 $( set.insert($key, $value); )*
28 set
29 }}
30 }
31
32 macro_rules! copy_source {
33 ($rev: expr, $path: expr, $overwritten: tt) => {
34 CopySource {
35 rev: $rev,
36 path: $path,
37 overwritten: set!(OrdSet<Revision> $overwritten),
38 }
39 };
40 }
41
42 macro_rules! compare_value {
43 (
44 $merge_revision: expr,
45 $merge_case_for_dest: ident,
46 ($min_rev: expr, $min_path: expr, $min_overwrite: tt),
47 ($maj_rev: expr, $maj_path: expr, $maj_overwrite: tt) $(,)?
48 ) => {
49 compare_value(
50 $merge_revision,
51 || $merge_case_for_dest,
52 &copy_source!($min_rev, $min_path, $min_overwrite),
53 &copy_source!($maj_rev, $maj_path, $maj_overwrite),
54 )
55 };
56 }
57
58 macro_rules! tokenized_path_copies {
59 (
60 $path_map: ident, {$(
61 $dest: expr => (
62 $src_rev: expr,
63 $src_path: expr,
64 $src_overwrite: tt
65 )
66 ),*}
67 $(,)*
68 ) => {
69 map!(InternalPathCopies {$(
70 $path_map.tokenize(HgPath::new($dest)) =>
71 copy_source!(
72 $src_rev,
73 Option::map($src_path, |p: &str| {
74 $path_map.tokenize(HgPath::new(p))
75 }),
76 $src_overwrite
77 )
78 )*})
79 }
80 }
81
82 macro_rules! merge_case_callback {
83 (
84 $( $merge_path: expr => $merge_case: ident ),*
85 $(,)?
86 ) => {
87 #[allow(unused)]
88 |merge_path| -> MergeCase {
89 $(
90 if (merge_path == HgPath::new($merge_path)) {
91 return $merge_case
92 }
93 )*
94 MergeCase::Normal
95 }
96 };
97 }
98
99 macro_rules! merge_copies_dict {
100 (
101 $current_merge: expr,
102 $minor_copies: tt,
103 $major_copies: tt,
104 $get_merge_case: tt $(,)?
105 ) => {
106 {
107 #[allow(unused_mut)]
108 let mut map = TwoWayPathMap::default();
109 let minor = tokenized_path_copies!(map, $minor_copies);
110 let major = tokenized_path_copies!(map, $major_copies);
111 merge_copies_dict(
112 &map, $current_merge, minor, major,
113 merge_case_callback! $get_merge_case,
114 )
115 .into_iter()
116 .map(|(token, source)| {
117 (
118 map.untokenize(token).to_string(),
119 (
120 source.rev,
121 source.path.map(|t| map.untokenize(t).to_string()),
122 source.overwritten.into_iter().collect(),
123 ),
124 )
125 })
126 .collect::<OrdMap<_, _>>()
127 }
128 };
129 }
130
131 macro_rules! internal_path_copies {
132 (
133 $(
134 $dest: expr => (
135 $src_rev: expr,
136 $src_path: expr,
137 $src_overwrite: tt $(,)?
138 )
139 ),*
140 $(,)*
141 ) => {
142 map!(OrdMap<_, _> {$(
143 String::from($dest) => (
144 $src_rev,
145 $src_path,
146 set!(OrdSet<Revision> $src_overwrite)
147 )
148 ),*})
149 };
150 }
151
152 macro_rules! combine_changeset_copies {
153 (
154 $children_count: tt,
155 [
156 $(
157 {
158 rev: $rev: expr,
159 p1: $p1: expr,
160 p2: $p2: expr,
161 actions: [
162 $(
163 $Action: ident($( $action_path: expr ),+)
164 ),*
165 $(,)?
166 ],
167 merge_cases: $merge: tt
168 $(,)?
169 }
170 ),*
171 $(,)?
172 ],
173 $target_rev: expr $(,)*
174 ) => {{
175 let count = map!(HashMap<Revision, usize> $children_count);
176 let mut combine_changeset_copies = CombineChangesetCopies::new(count);
177 $(
178 let actions = vec![$(
179 $Action($( HgPath::new($action_path) ),*)
180 ),*];
181 combine_changeset_copies.add_revision_inner(
182 $rev, $p1, $p2, actions.into_iter(),
183 merge_case_callback! $merge
184 );
185 )*
186 combine_changeset_copies.finish($target_rev)
187 }};
188 }
189
190 macro_rules! path_copies {
191 (
192 $( $expected_destination: expr => $expected_source: expr ),* $(,)?
193 ) => {
194 map!(PathCopies {$(
195 HgPath::new($expected_destination).to_owned()
196 => HgPath::new($expected_source).to_owned(),
197 ),*})
198 };
199 }
@@ -1,673 +1,680 b''
1 #[cfg(test)]
2 #[macro_use]
3 mod tests_support;
4
5 #[cfg(test)]
6 mod tests;
7
1 8 use crate::utils::hg_path::HgPath;
2 9 use crate::utils::hg_path::HgPathBuf;
3 10 use crate::Revision;
4 11 use crate::NULL_REVISION;
5 12
6 13 use bytes_cast::{unaligned, BytesCast};
7 14 use im_rc::ordmap::Entry;
8 15 use im_rc::ordmap::OrdMap;
9 16 use im_rc::OrdSet;
10 17
11 18 use std::cmp::Ordering;
12 19 use std::collections::HashMap;
13 20
14 21 pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>;
15 22
16 23 type PathToken = usize;
17 24
18 25 #[derive(Clone, Debug)]
19 26 struct CopySource {
20 27 /// revision at which the copy information was added
21 28 rev: Revision,
22 29 /// the copy source, (Set to None in case of deletion of the associated
23 30 /// key)
24 31 path: Option<PathToken>,
25 32 /// a set of previous `CopySource.rev` value directly or indirectly
26 33 /// overwritten by this one.
27 34 overwritten: OrdSet<Revision>,
28 35 }
29 36
30 37 impl CopySource {
31 38 /// create a new CopySource
32 39 ///
33 40 /// Use this when no previous copy source existed.
34 41 fn new(rev: Revision, path: Option<PathToken>) -> Self {
35 42 Self {
36 43 rev,
37 44 path,
38 45 overwritten: OrdSet::new(),
39 46 }
40 47 }
41 48
42 49 /// create a new CopySource from merging two others
43 50 ///
44 51 /// Use this when merging two InternalPathCopies requires active merging of
45 52 /// some entries.
46 53 fn new_from_merge(rev: Revision, winner: &Self, loser: &Self) -> Self {
47 54 let mut overwritten = OrdSet::new();
48 55 overwritten.extend(winner.overwritten.iter().copied());
49 56 overwritten.extend(loser.overwritten.iter().copied());
50 57 overwritten.insert(winner.rev);
51 58 overwritten.insert(loser.rev);
52 59 Self {
53 60 rev,
54 61 path: winner.path,
55 62 overwritten: overwritten,
56 63 }
57 64 }
58 65
59 66 /// Update the value of a pre-existing CopySource
60 67 ///
61 68 /// Use this when recording copy information from parent β†’ child edges
62 69 fn overwrite(&mut self, rev: Revision, path: Option<PathToken>) {
63 70 self.overwritten.insert(self.rev);
64 71 self.rev = rev;
65 72 self.path = path;
66 73 }
67 74
68 75 /// Mark pre-existing copy information as "dropped" by a file deletion
69 76 ///
70 77 /// Use this when recording copy information from parent β†’ child edges
71 78 fn mark_delete(&mut self, rev: Revision) {
72 79 self.overwritten.insert(self.rev);
73 80 self.rev = rev;
74 81 self.path = None;
75 82 }
76 83
77 84 /// Mark pre-existing copy information as "dropped" by a file deletion
78 85 ///
79 86 /// Use this when recording copy information from parent β†’ child edges
80 87 fn mark_delete_with_pair(&mut self, rev: Revision, other: &Self) {
81 88 self.overwritten.insert(self.rev);
82 89 if other.rev != rev {
83 90 self.overwritten.insert(other.rev);
84 91 }
85 92 self.overwritten.extend(other.overwritten.iter().copied());
86 93 self.rev = rev;
87 94 self.path = None;
88 95 }
89 96
90 97 fn is_overwritten_by(&self, other: &Self) -> bool {
91 98 other.overwritten.contains(&self.rev)
92 99 }
93 100 }
94 101
95 102 // For the same "dest", content generated for a given revision will always be
96 103 // the same.
97 104 impl PartialEq for CopySource {
98 105 fn eq(&self, other: &Self) -> bool {
99 106 #[cfg(debug_assertions)]
100 107 {
101 108 if self.rev == other.rev {
102 109 debug_assert!(self.path == other.path);
103 110 debug_assert!(self.overwritten == other.overwritten);
104 111 }
105 112 }
106 113 self.rev == other.rev
107 114 }
108 115 }
109 116
110 117 /// maps CopyDestination to Copy Source (+ a "timestamp" for the operation)
111 118 type InternalPathCopies = OrdMap<PathToken, CopySource>;
112 119
113 120 /// Represent active changes that affect the copy tracing.
114 121 enum Action<'a> {
115 122 /// The parent ? children edge is removing a file
116 123 ///
117 124 /// (actually, this could be the edge from the other parent, but it does
118 125 /// not matters)
119 126 Removed(&'a HgPath),
120 127 /// The parent ? children edge introduce copy information between (dest,
121 128 /// source)
122 129 CopiedFromP1(&'a HgPath, &'a HgPath),
123 130 CopiedFromP2(&'a HgPath, &'a HgPath),
124 131 }
125 132
126 133 /// This express the possible "special" case we can get in a merge
127 134 ///
128 135 /// See mercurial/metadata.py for details on these values.
129 136 #[derive(PartialEq)]
130 137 enum MergeCase {
131 138 /// Merged: file had history on both side that needed to be merged
132 139 Merged,
133 140 /// Salvaged: file was candidate for deletion, but survived the merge
134 141 Salvaged,
135 142 /// Normal: Not one of the two cases above
136 143 Normal,
137 144 }
138 145
139 146 const COPY_MASK: u8 = 3;
140 147 const P1_COPY: u8 = 2;
141 148 const P2_COPY: u8 = 3;
142 149 const ACTION_MASK: u8 = 28;
143 150 const REMOVED: u8 = 12;
144 151 const MERGED: u8 = 8;
145 152 const SALVAGED: u8 = 16;
146 153
147 154 #[derive(BytesCast)]
148 155 #[repr(C)]
149 156 struct ChangedFilesIndexEntry {
150 157 flags: u8,
151 158
152 159 /// Only the end position is stored. The start is at the end of the
153 160 /// previous entry.
154 161 destination_path_end_position: unaligned::U32Be,
155 162
156 163 source_index_entry_position: unaligned::U32Be,
157 164 }
158 165
159 166 fn _static_assert_size_of() {
160 167 let _ = std::mem::transmute::<ChangedFilesIndexEntry, [u8; 9]>;
161 168 }
162 169
163 170 /// Represents the files affected by a changeset.
164 171 ///
165 172 /// This holds a subset of `mercurial.metadata.ChangingFiles` as we do not need
166 173 /// all the data categories tracked by it.
167 174 pub struct ChangedFiles<'a> {
168 175 index: &'a [ChangedFilesIndexEntry],
169 176 paths: &'a [u8],
170 177 }
171 178
172 179 impl<'a> ChangedFiles<'a> {
173 180 pub fn new(data: &'a [u8]) -> Self {
174 181 let (header, rest) = unaligned::U32Be::from_bytes(data).unwrap();
175 182 let nb_index_entries = header.get() as usize;
176 183 let (index, paths) =
177 184 ChangedFilesIndexEntry::slice_from_bytes(rest, nb_index_entries)
178 185 .unwrap();
179 186 Self { index, paths }
180 187 }
181 188
182 189 pub fn new_empty() -> Self {
183 190 ChangedFiles {
184 191 index: &[],
185 192 paths: &[],
186 193 }
187 194 }
188 195
189 196 /// Internal function to return the filename of the entry at a given index
190 197 fn path(&self, idx: usize) -> &HgPath {
191 198 let start = if idx == 0 {
192 199 0
193 200 } else {
194 201 self.index[idx - 1].destination_path_end_position.get() as usize
195 202 };
196 203 let end = self.index[idx].destination_path_end_position.get() as usize;
197 204 HgPath::new(&self.paths[start..end])
198 205 }
199 206
200 207 /// Return an iterator over all the `Action` in this instance.
201 208 fn iter_actions(&self) -> impl Iterator<Item = Action> {
202 209 self.index.iter().enumerate().flat_map(move |(idx, entry)| {
203 210 let path = self.path(idx);
204 211 if (entry.flags & ACTION_MASK) == REMOVED {
205 212 Some(Action::Removed(path))
206 213 } else if (entry.flags & COPY_MASK) == P1_COPY {
207 214 let source_idx =
208 215 entry.source_index_entry_position.get() as usize;
209 216 Some(Action::CopiedFromP1(path, self.path(source_idx)))
210 217 } else if (entry.flags & COPY_MASK) == P2_COPY {
211 218 let source_idx =
212 219 entry.source_index_entry_position.get() as usize;
213 220 Some(Action::CopiedFromP2(path, self.path(source_idx)))
214 221 } else {
215 222 None
216 223 }
217 224 })
218 225 }
219 226
220 227 /// return the MergeCase value associated with a filename
221 228 fn get_merge_case(&self, path: &HgPath) -> MergeCase {
222 229 if self.index.is_empty() {
223 230 return MergeCase::Normal;
224 231 }
225 232 let mut low_part = 0;
226 233 let mut high_part = self.index.len();
227 234
228 235 while low_part < high_part {
229 236 let cursor = (low_part + high_part - 1) / 2;
230 237 match path.cmp(self.path(cursor)) {
231 238 Ordering::Less => low_part = cursor + 1,
232 239 Ordering::Greater => high_part = cursor,
233 240 Ordering::Equal => {
234 241 return match self.index[cursor].flags & ACTION_MASK {
235 242 MERGED => MergeCase::Merged,
236 243 SALVAGED => MergeCase::Salvaged,
237 244 _ => MergeCase::Normal,
238 245 };
239 246 }
240 247 }
241 248 }
242 249 MergeCase::Normal
243 250 }
244 251 }
245 252
246 253 /// A small "tokenizer" responsible of turning full HgPath into lighter
247 254 /// PathToken
248 255 ///
249 256 /// Dealing with small object, like integer is much faster, so HgPath input are
250 257 /// turned into integer "PathToken" and converted back in the end.
251 258 #[derive(Clone, Debug, Default)]
252 259 struct TwoWayPathMap {
253 260 token: HashMap<HgPathBuf, PathToken>,
254 261 path: Vec<HgPathBuf>,
255 262 }
256 263
257 264 impl TwoWayPathMap {
258 265 fn tokenize(&mut self, path: &HgPath) -> PathToken {
259 266 match self.token.get(path) {
260 267 Some(a) => *a,
261 268 None => {
262 269 let a = self.token.len();
263 270 let buf = path.to_owned();
264 271 self.path.push(buf.clone());
265 272 self.token.insert(buf, a);
266 273 a
267 274 }
268 275 }
269 276 }
270 277
271 278 fn untokenize(&self, token: PathToken) -> &HgPathBuf {
272 279 assert!(token < self.path.len(), "Unknown token: {}", token);
273 280 &self.path[token]
274 281 }
275 282 }
276 283
277 284 /// Same as mercurial.copies._combine_changeset_copies, but in Rust.
278 285 pub struct CombineChangesetCopies {
279 286 all_copies: HashMap<Revision, InternalPathCopies>,
280 287 path_map: TwoWayPathMap,
281 288 children_count: HashMap<Revision, usize>,
282 289 }
283 290
284 291 impl CombineChangesetCopies {
285 292 pub fn new(children_count: HashMap<Revision, usize>) -> Self {
286 293 Self {
287 294 all_copies: HashMap::new(),
288 295 path_map: TwoWayPathMap::default(),
289 296 children_count,
290 297 }
291 298 }
292 299
293 300 /// Combined the given `changes` data specific to `rev` with the data
294 301 /// previously given for its parents (and transitively, its ancestors).
295 302 pub fn add_revision(
296 303 &mut self,
297 304 rev: Revision,
298 305 p1: Revision,
299 306 p2: Revision,
300 307 changes: ChangedFiles<'_>,
301 308 ) {
302 309 self.add_revision_inner(rev, p1, p2, changes.iter_actions(), |path| {
303 310 changes.get_merge_case(path)
304 311 })
305 312 }
306 313
307 314 /// Separated out from `add_revsion` so that unit tests can call this
308 315 /// without synthetizing a `ChangedFiles` in binary format.
309 316 fn add_revision_inner<'a>(
310 317 &mut self,
311 318 rev: Revision,
312 319 p1: Revision,
313 320 p2: Revision,
314 321 copy_actions: impl Iterator<Item = Action<'a>>,
315 322 get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
316 323 ) {
317 324 // Retrieve data computed in a previous iteration
318 325 let p1_copies = match p1 {
319 326 NULL_REVISION => None,
320 327 _ => get_and_clean_parent_copies(
321 328 &mut self.all_copies,
322 329 &mut self.children_count,
323 330 p1,
324 331 ), // will be None if the vertex is not to be traversed
325 332 };
326 333 let p2_copies = match p2 {
327 334 NULL_REVISION => None,
328 335 _ => get_and_clean_parent_copies(
329 336 &mut self.all_copies,
330 337 &mut self.children_count,
331 338 p2,
332 339 ), // will be None if the vertex is not to be traversed
333 340 };
334 341 // combine it with data for that revision
335 342 let (p1_copies, p2_copies) = chain_changes(
336 343 &mut self.path_map,
337 344 p1_copies,
338 345 p2_copies,
339 346 copy_actions,
340 347 rev,
341 348 );
342 349 let copies = match (p1_copies, p2_copies) {
343 350 (None, None) => None,
344 351 (c, None) => c,
345 352 (None, c) => c,
346 353 (Some(p1_copies), Some(p2_copies)) => Some(merge_copies_dict(
347 354 &self.path_map,
348 355 rev,
349 356 p2_copies,
350 357 p1_copies,
351 358 get_merge_case,
352 359 )),
353 360 };
354 361 if let Some(c) = copies {
355 362 self.all_copies.insert(rev, c);
356 363 }
357 364 }
358 365
359 366 /// Drop intermediate data (such as which revision a copy was from) and
360 367 /// return the final mapping.
361 368 pub fn finish(mut self, target_rev: Revision) -> PathCopies {
362 369 let tt_result = self
363 370 .all_copies
364 371 .remove(&target_rev)
365 372 .expect("target revision was not processed");
366 373 let mut result = PathCopies::default();
367 374 for (dest, tt_source) in tt_result {
368 375 if let Some(path) = tt_source.path {
369 376 let path_dest = self.path_map.untokenize(dest).to_owned();
370 377 let path_path = self.path_map.untokenize(path).to_owned();
371 378 result.insert(path_dest, path_path);
372 379 }
373 380 }
374 381 result
375 382 }
376 383 }
377 384
378 385 /// fetch previous computed information
379 386 ///
380 387 /// If no other children are expected to need this information, we drop it from
381 388 /// the cache.
382 389 ///
383 390 /// If parent is not part of the set we are expected to walk, return None.
384 391 fn get_and_clean_parent_copies(
385 392 all_copies: &mut HashMap<Revision, InternalPathCopies>,
386 393 children_count: &mut HashMap<Revision, usize>,
387 394 parent_rev: Revision,
388 395 ) -> Option<InternalPathCopies> {
389 396 let count = children_count.get_mut(&parent_rev)?;
390 397 *count -= 1;
391 398 if *count == 0 {
392 399 match all_copies.remove(&parent_rev) {
393 400 Some(c) => Some(c),
394 401 None => Some(InternalPathCopies::default()),
395 402 }
396 403 } else {
397 404 match all_copies.get(&parent_rev) {
398 405 Some(c) => Some(c.clone()),
399 406 None => Some(InternalPathCopies::default()),
400 407 }
401 408 }
402 409 }
403 410
404 411 /// Combine ChangedFiles with some existing PathCopies information and return
405 412 /// the result
406 413 fn chain_changes<'a>(
407 414 path_map: &mut TwoWayPathMap,
408 415 base_p1_copies: Option<InternalPathCopies>,
409 416 base_p2_copies: Option<InternalPathCopies>,
410 417 copy_actions: impl Iterator<Item = Action<'a>>,
411 418 current_rev: Revision,
412 419 ) -> (Option<InternalPathCopies>, Option<InternalPathCopies>) {
413 420 // Fast path the "nothing to do" case.
414 421 if let (None, None) = (&base_p1_copies, &base_p2_copies) {
415 422 return (None, None);
416 423 }
417 424
418 425 let mut p1_copies = base_p1_copies.clone();
419 426 let mut p2_copies = base_p2_copies.clone();
420 427 for action in copy_actions {
421 428 match action {
422 429 Action::CopiedFromP1(path_dest, path_source) => {
423 430 match &mut p1_copies {
424 431 None => (), // This is not a vertex we should proceed.
425 432 Some(copies) => add_one_copy(
426 433 current_rev,
427 434 path_map,
428 435 copies,
429 436 base_p1_copies.as_ref().unwrap(),
430 437 path_dest,
431 438 path_source,
432 439 ),
433 440 }
434 441 }
435 442 Action::CopiedFromP2(path_dest, path_source) => {
436 443 match &mut p2_copies {
437 444 None => (), // This is not a vertex we should proceed.
438 445 Some(copies) => add_one_copy(
439 446 current_rev,
440 447 path_map,
441 448 copies,
442 449 base_p2_copies.as_ref().unwrap(),
443 450 path_dest,
444 451 path_source,
445 452 ),
446 453 }
447 454 }
448 455 Action::Removed(deleted_path) => {
449 456 // We must drop copy information for removed file.
450 457 //
451 458 // We need to explicitly record them as dropped to
452 459 // propagate this information when merging two
453 460 // InternalPathCopies object.
454 461 let deleted = path_map.tokenize(deleted_path);
455 462
456 463 let p1_entry = match &mut p1_copies {
457 464 None => None,
458 465 Some(copies) => match copies.entry(deleted) {
459 466 Entry::Occupied(e) => Some(e),
460 467 Entry::Vacant(_) => None,
461 468 },
462 469 };
463 470 let p2_entry = match &mut p2_copies {
464 471 None => None,
465 472 Some(copies) => match copies.entry(deleted) {
466 473 Entry::Occupied(e) => Some(e),
467 474 Entry::Vacant(_) => None,
468 475 },
469 476 };
470 477
471 478 match (p1_entry, p2_entry) {
472 479 (None, None) => (),
473 480 (Some(mut e), None) => {
474 481 e.get_mut().mark_delete(current_rev)
475 482 }
476 483 (None, Some(mut e)) => {
477 484 e.get_mut().mark_delete(current_rev)
478 485 }
479 486 (Some(mut e1), Some(mut e2)) => {
480 487 let cs1 = e1.get_mut();
481 488 let cs2 = e2.get();
482 489 if cs1 == cs2 {
483 490 cs1.mark_delete(current_rev);
484 491 } else {
485 492 cs1.mark_delete_with_pair(current_rev, &cs2);
486 493 }
487 494 e2.insert(cs1.clone());
488 495 }
489 496 }
490 497 }
491 498 }
492 499 }
493 500 (p1_copies, p2_copies)
494 501 }
495 502
496 503 // insert one new copy information in an InternalPathCopies
497 504 //
498 505 // This deal with chaining and overwrite.
499 506 fn add_one_copy(
500 507 current_rev: Revision,
501 508 path_map: &mut TwoWayPathMap,
502 509 copies: &mut InternalPathCopies,
503 510 base_copies: &InternalPathCopies,
504 511 path_dest: &HgPath,
505 512 path_source: &HgPath,
506 513 ) {
507 514 let dest = path_map.tokenize(path_dest);
508 515 let source = path_map.tokenize(path_source);
509 516 let entry;
510 517 if let Some(v) = base_copies.get(&source) {
511 518 entry = match &v.path {
512 519 Some(path) => Some((*(path)).to_owned()),
513 520 None => Some(source.to_owned()),
514 521 }
515 522 } else {
516 523 entry = Some(source.to_owned());
517 524 }
518 525 // Each new entry is introduced by the children, we
519 526 // record this information as we will need it to take
520 527 // the right decision when merging conflicting copy
521 528 // information. See merge_copies_dict for details.
522 529 match copies.entry(dest) {
523 530 Entry::Vacant(slot) => {
524 531 let ttpc = CopySource::new(current_rev, entry);
525 532 slot.insert(ttpc);
526 533 }
527 534 Entry::Occupied(mut slot) => {
528 535 let ttpc = slot.get_mut();
529 536 ttpc.overwrite(current_rev, entry);
530 537 }
531 538 }
532 539 }
533 540
534 541 /// merge two copies-mapping together, minor and major
535 542 ///
536 543 /// In case of conflict, value from "major" will be picked, unless in some
537 544 /// cases. See inline documentation for details.
538 545 fn merge_copies_dict(
539 546 path_map: &TwoWayPathMap,
540 547 current_merge: Revision,
541 548 minor: InternalPathCopies,
542 549 major: InternalPathCopies,
543 550 get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
544 551 ) -> InternalPathCopies {
545 552 use crate::utils::{ordmap_union_with_merge, MergeResult};
546 553
547 554 ordmap_union_with_merge(minor, major, |&dest, src_minor, src_major| {
548 555 let (pick, overwrite) = compare_value(
549 556 current_merge,
550 557 || get_merge_case(path_map.untokenize(dest)),
551 558 src_minor,
552 559 src_major,
553 560 );
554 561 if overwrite {
555 562 let (winner, loser) = match pick {
556 563 MergePick::Major | MergePick::Any => (src_major, src_minor),
557 564 MergePick::Minor => (src_minor, src_major),
558 565 };
559 566 MergeResult::UseNewValue(CopySource::new_from_merge(
560 567 current_merge,
561 568 winner,
562 569 loser,
563 570 ))
564 571 } else {
565 572 match pick {
566 573 MergePick::Any | MergePick::Major => {
567 574 MergeResult::UseRightValue
568 575 }
569 576 MergePick::Minor => MergeResult::UseLeftValue,
570 577 }
571 578 }
572 579 })
573 580 }
574 581
575 582 /// represent the side that should prevail when merging two
576 583 /// InternalPathCopies
577 584 #[derive(Debug, PartialEq)]
578 585 enum MergePick {
579 586 /// The "major" (p1) side prevails
580 587 Major,
581 588 /// The "minor" (p2) side prevails
582 589 Minor,
583 590 /// Any side could be used (because they are the same)
584 591 Any,
585 592 }
586 593
587 594 /// decide which side prevails in case of conflicting values
588 595 #[allow(clippy::if_same_then_else)]
589 596 fn compare_value(
590 597 current_merge: Revision,
591 598 merge_case_for_dest: impl Fn() -> MergeCase,
592 599 src_minor: &CopySource,
593 600 src_major: &CopySource,
594 601 ) -> (MergePick, bool) {
595 602 if src_major == src_minor {
596 603 (MergePick::Any, false)
597 604 } else if src_major.rev == current_merge {
598 605 // minor is different according to per minor == major check earlier
599 606 debug_assert!(src_minor.rev != current_merge);
600 607
601 608 // The last value comes the current merge, this value -will- win
602 609 // eventually.
603 610 (MergePick::Major, true)
604 611 } else if src_minor.rev == current_merge {
605 612 // The last value comes the current merge, this value -will- win
606 613 // eventually.
607 614 (MergePick::Minor, true)
608 615 } else if src_major.path == src_minor.path {
609 616 debug_assert!(src_major.rev != src_major.rev);
610 617 // we have the same value, but from other source;
611 618 if src_major.is_overwritten_by(src_minor) {
612 619 (MergePick::Minor, false)
613 620 } else if src_minor.is_overwritten_by(src_major) {
614 621 (MergePick::Major, false)
615 622 } else {
616 623 (MergePick::Any, true)
617 624 }
618 625 } else {
619 626 debug_assert!(src_major.rev != src_major.rev);
620 627 let action = merge_case_for_dest();
621 628 if src_minor.path.is_some()
622 629 && src_major.path.is_none()
623 630 && action == MergeCase::Salvaged
624 631 {
625 632 // If the file is "deleted" in the major side but was
626 633 // salvaged by the merge, we keep the minor side alive
627 634 (MergePick::Minor, true)
628 635 } else if src_major.path.is_some()
629 636 && src_minor.path.is_none()
630 637 && action == MergeCase::Salvaged
631 638 {
632 639 // If the file is "deleted" in the minor side but was
633 640 // salvaged by the merge, unconditionnaly preserve the
634 641 // major side.
635 642 (MergePick::Major, true)
636 643 } else if src_minor.is_overwritten_by(src_major) {
637 644 // The information from the minor version are strictly older than
638 645 // the major version
639 646 if action == MergeCase::Merged {
640 647 // If the file was actively merged, its means some non-copy
641 648 // activity happened on the other branch. It
642 649 // mean the older copy information are still relevant.
643 650 //
644 651 // The major side wins such conflict.
645 652 (MergePick::Major, true)
646 653 } else {
647 654 // No activity on the minor branch, pick the newer one.
648 655 (MergePick::Major, false)
649 656 }
650 657 } else if src_major.is_overwritten_by(src_minor) {
651 658 if action == MergeCase::Merged {
652 659 // If the file was actively merged, its means some non-copy
653 660 // activity happened on the other branch. It
654 661 // mean the older copy information are still relevant.
655 662 //
656 663 // The major side wins such conflict.
657 664 (MergePick::Major, true)
658 665 } else {
659 666 // No activity on the minor branch, pick the newer one.
660 667 (MergePick::Minor, false)
661 668 }
662 669 } else if src_minor.path.is_none() {
663 670 // the minor side has no relevant information, pick the alive one
664 671 (MergePick::Major, true)
665 672 } else if src_major.path.is_none() {
666 673 // the major side has no relevant information, pick the alive one
667 674 (MergePick::Minor, true)
668 675 } else {
669 676 // by default the major side wins
670 677 (MergePick::Major, true)
671 678 }
672 679 }
673 680 }
General Comments 0
You need to be logged in to leave comments. Login now