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