Mercurial > hg
comparison rust/hg-core/src/dirstate/entry.rs @ 48042:008959fcbfb2
rust: Align DirstateEntry internals with Python/C DirstateItem
This propagate to this Rust struct the similar change that was made recently
to the Python classe and C struct. Namely, instead of storing a four-valued
`state` field we now store seven (bit-packed) booleans that give lower-level
information.
Additionally, the marker values -1 and -2 for mtime and size should not
be used internally anymore. They are replaced by some combinations of booleans
For now, all uses of of `DirstateEntry` still use the compatibility APIs
with `state` and marker values. Later the Rust API for DirstateMap
will be increasingly updated to the new style.
Also change the expected result of the test_non_normal_other_parent_entries
unit test. Only a `DirstateEntry` with `size == -2 && mtime != -1` is
affected, but this case never occurs outside of unit tests.
`size == -2` was the marker value for "from other parent" entries,
where no meaningful mtime is stored.
Differential Revision: https://phab.mercurial-scm.org/D11484
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Mon, 20 Sep 2021 19:18:21 +0200 |
parents | 1b2ee68e85f9 |
children | 3e69bef2031a |
comparison
equal
deleted
inserted
replaced
48041:37a41267d000 | 48042:008959fcbfb2 |
---|---|
1 use crate::errors::HgError; | 1 use crate::errors::HgError; |
2 use bitflags::bitflags; | |
2 use std::convert::TryFrom; | 3 use std::convert::TryFrom; |
3 | 4 |
4 #[derive(Copy, Clone, Debug, Eq, PartialEq)] | 5 #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
5 pub enum EntryState { | 6 pub enum EntryState { |
6 Normal, | 7 Normal, |
12 /// The C implementation uses all signed types. This will be an issue | 13 /// The C implementation uses all signed types. This will be an issue |
13 /// either when 4GB+ source files are commonplace or in 2038, whichever | 14 /// either when 4GB+ source files are commonplace or in 2038, whichever |
14 /// comes first. | 15 /// comes first. |
15 #[derive(Debug, PartialEq, Copy, Clone)] | 16 #[derive(Debug, PartialEq, Copy, Clone)] |
16 pub struct DirstateEntry { | 17 pub struct DirstateEntry { |
17 state: EntryState, | 18 flags: Flags, |
18 mode: i32, | 19 mode: i32, |
19 size: i32, | 20 size: i32, |
20 mtime: i32, | 21 mtime: i32, |
22 } | |
23 | |
24 bitflags! { | |
25 struct Flags: u8 { | |
26 const WDIR_TRACKED = 1 << 0; | |
27 const P1_TRACKED = 1 << 1; | |
28 const P2_TRACKED = 1 << 2; | |
29 const POSSIBLY_DIRTY = 1 << 3; | |
30 const MERGED = 1 << 4; | |
31 const CLEAN_P1 = 1 << 5; | |
32 const CLEAN_P2 = 1 << 6; | |
33 const ENTRYLESS_TREE_NODE = 1 << 7; | |
34 } | |
21 } | 35 } |
22 | 36 |
23 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF; | 37 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF; |
24 | 38 |
25 pub const MTIME_UNSET: i32 = -1; | 39 pub const MTIME_UNSET: i32 = -1; |
37 state: EntryState, | 51 state: EntryState, |
38 mode: i32, | 52 mode: i32, |
39 size: i32, | 53 size: i32, |
40 mtime: i32, | 54 mtime: i32, |
41 ) -> Self { | 55 ) -> Self { |
56 match state { | |
57 EntryState::Normal => { | |
58 if size == SIZE_FROM_OTHER_PARENT { | |
59 Self::new_from_p2() | |
60 } else if size == SIZE_NON_NORMAL { | |
61 Self::new_possibly_dirty() | |
62 } else if mtime == MTIME_UNSET { | |
63 Self { | |
64 flags: Flags::WDIR_TRACKED | |
65 | Flags::P1_TRACKED | |
66 | Flags::POSSIBLY_DIRTY, | |
67 mode, | |
68 size, | |
69 mtime: 0, | |
70 } | |
71 } else { | |
72 Self { | |
73 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | |
74 mode, | |
75 size, | |
76 mtime, | |
77 } | |
78 } | |
79 } | |
80 EntryState::Added => Self::new_added(), | |
81 EntryState::Removed => Self { | |
82 flags: if size == SIZE_NON_NORMAL { | |
83 Flags::P1_TRACKED // might not be true because of rename ? | |
84 | Flags::P2_TRACKED // might not be true because of rename ? | |
85 | Flags::MERGED | |
86 } else if size == SIZE_FROM_OTHER_PARENT { | |
87 // We don’t know if P1_TRACKED should be set (file history) | |
88 Flags::P2_TRACKED | Flags::CLEAN_P2 | |
89 } else { | |
90 Flags::P1_TRACKED | |
91 }, | |
92 mode: 0, | |
93 size: 0, | |
94 mtime: 0, | |
95 }, | |
96 EntryState::Merged => Self::new_merged(), | |
97 } | |
98 } | |
99 | |
100 fn new_from_p2() -> Self { | |
42 Self { | 101 Self { |
43 state, | 102 // might be missing P1_TRACKED |
44 mode, | 103 flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2, |
45 size, | 104 mode: 0, |
46 mtime, | 105 size: SIZE_FROM_OTHER_PARENT, |
106 mtime: MTIME_UNSET, | |
107 } | |
108 } | |
109 | |
110 fn new_possibly_dirty() -> Self { | |
111 Self { | |
112 flags: Flags::WDIR_TRACKED | |
113 | Flags::P1_TRACKED | |
114 | Flags::POSSIBLY_DIRTY, | |
115 mode: 0, | |
116 size: SIZE_NON_NORMAL, | |
117 mtime: MTIME_UNSET, | |
118 } | |
119 } | |
120 | |
121 fn new_added() -> Self { | |
122 Self { | |
123 flags: Flags::WDIR_TRACKED, | |
124 mode: 0, | |
125 size: SIZE_NON_NORMAL, | |
126 mtime: MTIME_UNSET, | |
127 } | |
128 } | |
129 | |
130 fn new_merged() -> Self { | |
131 Self { | |
132 flags: Flags::WDIR_TRACKED | |
133 | Flags::P1_TRACKED // might not be true because of rename ? | |
134 | Flags::P2_TRACKED // might not be true because of rename ? | |
135 | Flags::MERGED, | |
136 mode: 0, | |
137 size: SIZE_NON_NORMAL, | |
138 mtime: MTIME_UNSET, | |
47 } | 139 } |
48 } | 140 } |
49 | 141 |
50 /// Creates a new entry in "removed" state. | 142 /// Creates a new entry in "removed" state. |
51 /// | 143 /// |
52 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or | 144 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or |
53 /// `SIZE_FROM_OTHER_PARENT` | 145 /// `SIZE_FROM_OTHER_PARENT` |
54 pub fn new_removed(size: i32) -> Self { | 146 pub fn new_removed(size: i32) -> Self { |
55 Self { | 147 Self::from_v1_data(EntryState::Removed, 0, size, 0) |
56 state: EntryState::Removed, | |
57 mode: 0, | |
58 size, | |
59 mtime: 0, | |
60 } | |
61 } | 148 } |
62 | 149 |
63 /// TODO: refactor `DirstateMap::add_file` to not take a `DirstateEntry` | 150 /// TODO: refactor `DirstateMap::add_file` to not take a `DirstateEntry` |
64 /// parameter and remove this constructor | 151 /// parameter and remove this constructor |
65 pub fn new_for_add_file(mode: i32, size: i32, mtime: i32) -> Self { | 152 pub fn new_for_add_file(mode: i32, size: i32, mtime: i32) -> Self { |
66 Self { | 153 // XXX Arbitrary default value since the value is determined later |
67 // XXX Arbitrary default value since the value is determined later | 154 let state = EntryState::Normal; |
68 state: EntryState::Normal, | 155 Self::from_v1_data(state, mode, size, mtime) |
69 mode, | 156 } |
70 size, | 157 |
71 mtime, | 158 fn tracked_in_any_parent(&self) -> bool { |
72 } | 159 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED) |
160 } | |
161 | |
162 fn removed(&self) -> bool { | |
163 self.tracked_in_any_parent() | |
164 && !self.flags.contains(Flags::WDIR_TRACKED) | |
165 } | |
166 | |
167 fn merged_removed(&self) -> bool { | |
168 self.removed() && self.flags.contains(Flags::MERGED) | |
169 } | |
170 | |
171 fn from_p2_removed(&self) -> bool { | |
172 self.removed() && self.flags.contains(Flags::CLEAN_P2) | |
173 } | |
174 | |
175 fn merged(&self) -> bool { | |
176 self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED) | |
177 } | |
178 | |
179 fn added(&self) -> bool { | |
180 self.flags.contains(Flags::WDIR_TRACKED) | |
181 && !self.tracked_in_any_parent() | |
182 } | |
183 | |
184 fn from_p2(&self) -> bool { | |
185 self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2) | |
73 } | 186 } |
74 | 187 |
75 pub fn state(&self) -> EntryState { | 188 pub fn state(&self) -> EntryState { |
76 self.state | 189 if self.removed() { |
190 EntryState::Removed | |
191 } else if self.merged() { | |
192 EntryState::Merged | |
193 } else if self.added() { | |
194 EntryState::Added | |
195 } else { | |
196 EntryState::Normal | |
197 } | |
77 } | 198 } |
78 | 199 |
79 pub fn mode(&self) -> i32 { | 200 pub fn mode(&self) -> i32 { |
80 self.mode | 201 self.mode |
81 } | 202 } |
82 | 203 |
83 pub fn size(&self) -> i32 { | 204 pub fn size(&self) -> i32 { |
84 self.size | 205 if self.merged_removed() { |
206 SIZE_NON_NORMAL | |
207 } else if self.from_p2_removed() { | |
208 SIZE_FROM_OTHER_PARENT | |
209 } else if self.removed() { | |
210 0 | |
211 } else if self.merged() { | |
212 SIZE_FROM_OTHER_PARENT | |
213 } else if self.added() { | |
214 SIZE_NON_NORMAL | |
215 } else if self.from_p2() { | |
216 SIZE_FROM_OTHER_PARENT | |
217 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { | |
218 self.size // TODO: SIZE_NON_NORMAL ? | |
219 } else { | |
220 self.size | |
221 } | |
85 } | 222 } |
86 | 223 |
87 pub fn mtime(&self) -> i32 { | 224 pub fn mtime(&self) -> i32 { |
88 self.mtime | 225 if self.removed() { |
226 0 | |
227 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { | |
228 MTIME_UNSET | |
229 } else if self.merged() { | |
230 MTIME_UNSET | |
231 } else if self.added() { | |
232 MTIME_UNSET | |
233 } else if self.from_p2() { | |
234 MTIME_UNSET | |
235 } else { | |
236 self.mtime | |
237 } | |
89 } | 238 } |
90 | 239 |
91 /// Returns `(state, mode, size, mtime)` for the puprose of serialization | 240 /// Returns `(state, mode, size, mtime)` for the puprose of serialization |
92 /// in the dirstate-v1 format. | 241 /// in the dirstate-v1 format. |
93 /// | 242 /// |
94 /// This includes marker values such as `mtime == -1`. In the future we may | 243 /// This includes marker values such as `mtime == -1`. In the future we may |
95 /// want to not represent these cases that way in memory, but serialization | 244 /// want to not represent these cases that way in memory, but serialization |
96 /// will need to keep the same format. | 245 /// will need to keep the same format. |
97 pub fn v1_data(&self) -> (u8, i32, i32, i32) { | 246 pub fn v1_data(&self) -> (u8, i32, i32, i32) { |
98 (self.state.into(), self.mode, self.size, self.mtime) | 247 (self.state().into(), self.mode(), self.size(), self.mtime()) |
99 } | 248 } |
100 | 249 |
101 pub fn is_non_normal(&self) -> bool { | 250 pub fn is_non_normal(&self) -> bool { |
102 self.state != EntryState::Normal || self.mtime == MTIME_UNSET | 251 self.state() != EntryState::Normal || self.mtime() == MTIME_UNSET |
103 } | 252 } |
104 | 253 |
105 pub fn is_from_other_parent(&self) -> bool { | 254 pub fn is_from_other_parent(&self) -> bool { |
106 self.state == EntryState::Normal && self.size == SIZE_FROM_OTHER_PARENT | 255 self.state() == EntryState::Normal |
256 && self.size() == SIZE_FROM_OTHER_PARENT | |
107 } | 257 } |
108 | 258 |
109 // TODO: other platforms | 259 // TODO: other platforms |
110 #[cfg(unix)] | 260 #[cfg(unix)] |
111 pub fn mode_changed( | 261 pub fn mode_changed( |
112 &self, | 262 &self, |
113 filesystem_metadata: &std::fs::Metadata, | 263 filesystem_metadata: &std::fs::Metadata, |
114 ) -> bool { | 264 ) -> bool { |
115 use std::os::unix::fs::MetadataExt; | 265 use std::os::unix::fs::MetadataExt; |
116 const EXEC_BIT_MASK: u32 = 0o100; | 266 const EXEC_BIT_MASK: u32 = 0o100; |
117 let dirstate_exec_bit = (self.mode as u32) & EXEC_BIT_MASK; | 267 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK; |
118 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK; | 268 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK; |
119 dirstate_exec_bit != fs_exec_bit | 269 dirstate_exec_bit != fs_exec_bit |
120 } | 270 } |
121 | 271 |
122 /// Returns a `(state, mode, size, mtime)` tuple as for | 272 /// Returns a `(state, mode, size, mtime)` tuple as for |
123 /// `DirstateMapMethods::debug_iter`. | 273 /// `DirstateMapMethods::debug_iter`. |
124 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { | 274 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { |
125 (self.state.into(), self.mode, self.size, self.mtime) | 275 let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) { |
276 b' ' | |
277 } else { | |
278 self.state().into() | |
279 }; | |
280 (state, self.mode(), self.size(), self.mtime()) | |
126 } | 281 } |
127 | 282 |
128 pub fn mtime_is_ambiguous(&self, now: i32) -> bool { | 283 pub fn mtime_is_ambiguous(&self, now: i32) -> bool { |
129 self.state == EntryState::Normal && self.mtime == now | 284 self.state() == EntryState::Normal && self.mtime() == now |
130 } | 285 } |
131 | 286 |
132 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool { | 287 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool { |
133 let ambiguous = self.mtime_is_ambiguous(now); | 288 let ambiguous = self.mtime_is_ambiguous(now); |
134 if ambiguous { | 289 if ambiguous { |