comparison rust/hg-core/src/dirstate/entry.rs @ 48138:38488d488ec1

dirstate-item: change the internal storage and constructor value This should be closer to what we do need and what we can actually reliably record. In practice it means that we abandon the prospect of storing much more refined data for now. We don't have the necessary information nor code using it right now. So it seems safer to just use a clearer version of what we had so far. See the documentation changes for details. Differential Revision: https://phab.mercurial-scm.org/D11557
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Fri, 01 Oct 2021 20:35:30 +0200
parents 3c7db97ce541
children ab5a7fdbf75c
comparison
equal deleted inserted replaced
48137:25836b0029f5 48138:38488d488ec1
14 /// either when 4GB+ source files are commonplace or in 2038, whichever 14 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 /// comes first. 15 /// comes first.
16 #[derive(Debug, PartialEq, Copy, Clone)] 16 #[derive(Debug, PartialEq, Copy, Clone)]
17 pub struct DirstateEntry { 17 pub struct DirstateEntry {
18 flags: Flags, 18 flags: Flags,
19 mode: i32, 19 mode_size: Option<(i32, i32)>,
20 size: i32, 20 mtime: Option<i32>,
21 mtime: i32,
22 } 21 }
23 22
24 bitflags! { 23 bitflags! {
25 pub struct Flags: u8 { 24 struct Flags: u8 {
26 const WDIR_TRACKED = 1 << 0; 25 const WDIR_TRACKED = 1 << 0;
27 const P1_TRACKED = 1 << 1; 26 const P1_TRACKED = 1 << 1;
28 const P2_TRACKED = 1 << 2; 27 const P2_INFO = 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 } 28 }
35 } 29 }
36 30
37 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF; 31 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
38 32
46 /// dirstate v1 format. 40 /// dirstate v1 format.
47 pub const SIZE_NON_NORMAL: i32 = -1; 41 pub const SIZE_NON_NORMAL: i32 = -1;
48 42
49 impl DirstateEntry { 43 impl DirstateEntry {
50 pub fn new( 44 pub fn new(
51 flags: Flags, 45 wdir_tracked: bool,
52 mode_size_mtime: Option<(i32, i32, i32)>, 46 p1_tracked: bool,
47 p2_info: bool,
48 mode_size: Option<(i32, i32)>,
49 mtime: Option<i32>,
53 ) -> Self { 50 ) -> Self {
54 let (mode, size, mtime) = 51 let mut flags = Flags::empty();
55 mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET)); 52 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
53 flags.set(Flags::P1_TRACKED, p1_tracked);
54 flags.set(Flags::P2_INFO, p2_info);
56 Self { 55 Self {
57 flags, 56 flags,
58 mode, 57 mode_size,
59 size,
60 mtime, 58 mtime,
61 } 59 }
62 } 60 }
63 61
64 pub fn from_v1_data( 62 pub fn from_v1_data(
73 Self::new_from_p2() 71 Self::new_from_p2()
74 } else if size == SIZE_NON_NORMAL { 72 } else if size == SIZE_NON_NORMAL {
75 Self::new_possibly_dirty() 73 Self::new_possibly_dirty()
76 } else if mtime == MTIME_UNSET { 74 } else if mtime == MTIME_UNSET {
77 Self { 75 Self {
78 flags: Flags::WDIR_TRACKED 76 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
79 | Flags::P1_TRACKED 77 mode_size: Some((mode, size)),
80 | Flags::POSSIBLY_DIRTY, 78 mtime: None,
81 mode,
82 size,
83 mtime: 0,
84 } 79 }
85 } else { 80 } else {
86 Self::new_normal(mode, size, mtime) 81 Self::new_normal(mode, size, mtime)
87 } 82 }
88 } 83 }
89 EntryState::Added => Self::new_added(), 84 EntryState::Added => Self::new_added(),
90 EntryState::Removed => Self { 85 EntryState::Removed => Self {
91 flags: if size == SIZE_NON_NORMAL { 86 flags: if size == SIZE_NON_NORMAL {
92 Flags::P1_TRACKED // might not be true because of rename ? 87 Flags::P1_TRACKED | Flags::P2_INFO
93 | Flags::P2_TRACKED // might not be true because of rename ?
94 | Flags::MERGED
95 } else if size == SIZE_FROM_OTHER_PARENT { 88 } else if size == SIZE_FROM_OTHER_PARENT {
96 // We don’t know if P1_TRACKED should be set (file history) 89 // We don’t know if P1_TRACKED should be set (file history)
97 Flags::P2_TRACKED | Flags::CLEAN_P2 90 Flags::P2_INFO
98 } else { 91 } else {
99 Flags::P1_TRACKED 92 Flags::P1_TRACKED
100 }, 93 },
101 mode: 0, 94 mode_size: None,
102 size: 0, 95 mtime: None,
103 mtime: 0,
104 }, 96 },
105 EntryState::Merged => Self::new_merged(), 97 EntryState::Merged => Self::new_merged(),
106 } 98 }
107 } 99 }
108 100
109 pub fn new_from_p2() -> Self { 101 pub fn new_from_p2() -> Self {
110 Self { 102 Self {
111 // might be missing P1_TRACKED 103 // might be missing P1_TRACKED
112 flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2, 104 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
113 mode: 0, 105 mode_size: None,
114 size: SIZE_FROM_OTHER_PARENT, 106 mtime: None,
115 mtime: MTIME_UNSET,
116 } 107 }
117 } 108 }
118 109
119 pub fn new_possibly_dirty() -> Self { 110 pub fn new_possibly_dirty() -> Self {
120 Self { 111 Self {
121 flags: Flags::WDIR_TRACKED 112 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
122 | Flags::P1_TRACKED 113 mode_size: None,
123 | Flags::POSSIBLY_DIRTY, 114 mtime: None,
124 mode: 0,
125 size: SIZE_NON_NORMAL,
126 mtime: MTIME_UNSET,
127 } 115 }
128 } 116 }
129 117
130 pub fn new_added() -> Self { 118 pub fn new_added() -> Self {
131 Self { 119 Self {
132 flags: Flags::WDIR_TRACKED, 120 flags: Flags::WDIR_TRACKED,
133 mode: 0, 121 mode_size: None,
134 size: SIZE_NON_NORMAL, 122 mtime: None,
135 mtime: MTIME_UNSET,
136 } 123 }
137 } 124 }
138 125
139 pub fn new_merged() -> Self { 126 pub fn new_merged() -> Self {
140 Self { 127 Self {
141 flags: Flags::WDIR_TRACKED 128 flags: Flags::WDIR_TRACKED
142 | Flags::P1_TRACKED // might not be true because of rename ? 129 | Flags::P1_TRACKED // might not be true because of rename ?
143 | Flags::P2_TRACKED // might not be true because of rename ? 130 | Flags::P2_INFO, // might not be true because of rename ?
144 | Flags::MERGED, 131 mode_size: None,
145 mode: 0, 132 mtime: None,
146 size: SIZE_NON_NORMAL,
147 mtime: MTIME_UNSET,
148 } 133 }
149 } 134 }
150 135
151 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self { 136 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
152 Self { 137 Self {
153 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, 138 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
154 mode, 139 mode_size: Some((mode, size)),
155 size, 140 mtime: Some(mtime),
156 mtime,
157 } 141 }
158 } 142 }
159 143
160 /// Creates a new entry in "removed" state. 144 /// Creates a new entry in "removed" state.
161 /// 145 ///
167 151
168 pub fn tracked(&self) -> bool { 152 pub fn tracked(&self) -> bool {
169 self.flags.contains(Flags::WDIR_TRACKED) 153 self.flags.contains(Flags::WDIR_TRACKED)
170 } 154 }
171 155
172 fn tracked_in_any_parent(&self) -> bool { 156 fn in_either_parent(&self) -> bool {
173 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED) 157 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
174 } 158 }
175 159
176 pub fn removed(&self) -> bool { 160 pub fn removed(&self) -> bool {
177 self.tracked_in_any_parent() 161 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
178 && !self.flags.contains(Flags::WDIR_TRACKED)
179 } 162 }
180 163
181 pub fn merged(&self) -> bool { 164 pub fn merged(&self) -> bool {
182 self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED) 165 self.flags
166 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
183 } 167 }
184 168
185 pub fn added(&self) -> bool { 169 pub fn added(&self) -> bool {
186 self.flags.contains(Flags::WDIR_TRACKED) 170 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
187 && !self.tracked_in_any_parent()
188 } 171 }
189 172
190 pub fn from_p2(&self) -> bool { 173 pub fn from_p2(&self) -> bool {
191 self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2) 174 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
175 && !self.flags.contains(Flags::P1_TRACKED)
192 } 176 }
193 177
194 pub fn maybe_clean(&self) -> bool { 178 pub fn maybe_clean(&self) -> bool {
195 if !self.flags.contains(Flags::WDIR_TRACKED) { 179 if !self.flags.contains(Flags::WDIR_TRACKED) {
196 false 180 false
197 } else if self.added() { 181 } else if !self.flags.contains(Flags::P1_TRACKED) {
198 false 182 false
199 } else if self.flags.contains(Flags::MERGED) { 183 } else if self.flags.contains(Flags::P2_INFO) {
200 false
201 } else if self.flags.contains(Flags::CLEAN_P2) {
202 false 184 false
203 } else { 185 } else {
204 true 186 true
205 } 187 }
206 } 188 }
207 189
208 pub fn any_tracked(&self) -> bool { 190 pub fn any_tracked(&self) -> bool {
209 self.flags.intersects( 191 self.flags.intersects(
210 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_TRACKED, 192 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
211 ) 193 )
212 } 194 }
213 195
214 pub fn state(&self) -> EntryState { 196 fn v1_state(&self) -> EntryState {
197 if !self.any_tracked() {
198 // TODO: return an Option instead?
199 panic!("Accessing v1_state of an untracked DirstateEntry")
200 }
215 if self.removed() { 201 if self.removed() {
216 EntryState::Removed 202 EntryState::Removed
217 } else if self.merged() { 203 } else if self.merged() {
218 EntryState::Merged 204 EntryState::Merged
219 } else if self.added() { 205 } else if self.added() {
221 } else { 207 } else {
222 EntryState::Normal 208 EntryState::Normal
223 } 209 }
224 } 210 }
225 211
226 pub fn mode(&self) -> i32 { 212 fn v1_mode(&self) -> i32 {
227 self.mode 213 if let Some((mode, _size)) = self.mode_size {
228 } 214 mode
229 215 } else {
230 pub fn size(&self) -> i32 { 216 0
231 if self.removed() && self.flags.contains(Flags::MERGED) { 217 }
218 }
219
220 fn v1_size(&self) -> i32 {
221 if !self.any_tracked() {
222 // TODO: return an Option instead?
223 panic!("Accessing v1_size of an untracked DirstateEntry")
224 }
225 if self.removed()
226 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
227 {
232 SIZE_NON_NORMAL 228 SIZE_NON_NORMAL
233 } else if self.removed() && self.flags.contains(Flags::CLEAN_P2) { 229 } else if self.removed() && self.flags.contains(Flags::P2_INFO) {
234 SIZE_FROM_OTHER_PARENT 230 SIZE_FROM_OTHER_PARENT
235 } else if self.removed() { 231 } else if self.removed() {
236 0 232 0
237 } else if self.merged() { 233 } else if self.merged() {
238 SIZE_FROM_OTHER_PARENT 234 SIZE_FROM_OTHER_PARENT
239 } else if self.added() { 235 } else if self.added() {
240 SIZE_NON_NORMAL 236 SIZE_NON_NORMAL
241 } else if self.from_p2() { 237 } else if self.from_p2() {
242 SIZE_FROM_OTHER_PARENT 238 SIZE_FROM_OTHER_PARENT
243 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { 239 } else if let Some((_mode, size)) = self.mode_size {
244 self.size // TODO: SIZE_NON_NORMAL ? 240 size
245 } else { 241 } else {
246 self.size 242 SIZE_NON_NORMAL
247 } 243 }
248 } 244 }
249 245
250 pub fn mtime(&self) -> i32 { 246 fn v1_mtime(&self) -> i32 {
247 if !self.any_tracked() {
248 // TODO: return an Option instead?
249 panic!("Accessing v1_mtime of an untracked DirstateEntry")
250 }
251 if self.removed() { 251 if self.removed() {
252 0 252 0
253 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { 253 } else if self.flags.contains(Flags::P2_INFO) {
254 MTIME_UNSET 254 MTIME_UNSET
255 } else if self.merged() { 255 } else if !self.flags.contains(Flags::P1_TRACKED) {
256 MTIME_UNSET
257 } else if self.added() {
258 MTIME_UNSET
259 } else if self.from_p2() {
260 MTIME_UNSET 256 MTIME_UNSET
261 } else { 257 } else {
262 self.mtime 258 self.mtime.unwrap_or(MTIME_UNSET)
263 } 259 }
260 }
261
262 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
263 pub fn state(&self) -> EntryState {
264 self.v1_state()
265 }
266
267 // TODO: return Option?
268 pub fn mode(&self) -> i32 {
269 self.v1_mode()
270 }
271
272 // TODO: return Option?
273 pub fn size(&self) -> i32 {
274 self.v1_size()
275 }
276
277 // TODO: return Option?
278 pub fn mtime(&self) -> i32 {
279 self.v1_mtime()
264 } 280 }
265 281
266 pub fn drop_merge_data(&mut self) { 282 pub fn drop_merge_data(&mut self) {
267 if self.flags.contains(Flags::CLEAN_P1) 283 if self.flags.contains(Flags::P2_INFO) {
268 || self.flags.contains(Flags::CLEAN_P2) 284 self.flags.remove(Flags::P2_INFO);
269 || self.flags.contains(Flags::MERGED) 285 self.mode_size = None;
270 || self.flags.contains(Flags::P2_TRACKED) 286 self.mtime = None;
271 {
272 if self.flags.contains(Flags::MERGED) {
273 self.flags.insert(Flags::P1_TRACKED);
274 } else {
275 self.flags.remove(Flags::P1_TRACKED);
276 }
277 self.flags.remove(
278 Flags::MERGED
279 | Flags::CLEAN_P1
280 | Flags::CLEAN_P2
281 | Flags::P2_TRACKED,
282 );
283 self.flags.insert(Flags::POSSIBLY_DIRTY);
284 self.mode = 0;
285 self.mtime = 0;
286 // size = None on the python size turn into size = NON_NORMAL when
287 // accessed. So the next line is currently required, but a some
288 // future clean up would be welcome.
289 self.size = SIZE_NON_NORMAL;
290 } 287 }
291 } 288 }
292 289
293 pub fn set_possibly_dirty(&mut self) { 290 pub fn set_possibly_dirty(&mut self) {
294 self.flags.insert(Flags::POSSIBLY_DIRTY) 291 self.mtime = None
295 } 292 }
296 293
297 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) { 294 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
298 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); 295 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
299 self.flags.remove( 296 self.mode_size = Some((mode, size));
300 Flags::P2_TRACKED // This might be wrong 297 self.mtime = Some(mtime);
301 | Flags::MERGED
302 | Flags::CLEAN_P2
303 | Flags::POSSIBLY_DIRTY,
304 );
305 self.mode = mode;
306 self.size = size;
307 self.mtime = mtime;
308 } 298 }
309 299
310 pub fn set_tracked(&mut self) { 300 pub fn set_tracked(&mut self) {
311 self.flags 301 self.flags.insert(Flags::WDIR_TRACKED);
312 .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY); 302 // `set_tracked` is replacing various `normallookup` call. So we mark
313 // size = None on the python size turn into size = NON_NORMAL when 303 // the files as needing lookup
314 // accessed. So the next line is currently required, but a some future 304 //
315 // clean up would be welcome. 305 // Consider dropping this in the future in favor of something less
316 self.size = SIZE_NON_NORMAL; 306 // broad.
307 self.mtime = None;
317 } 308 }
318 309
319 pub fn set_untracked(&mut self) { 310 pub fn set_untracked(&mut self) {
320 self.flags.remove(Flags::WDIR_TRACKED); 311 self.flags.remove(Flags::WDIR_TRACKED);
321 self.mode = 0; 312 self.mode_size = None;
322 self.size = 0; 313 self.mtime = None;
323 self.mtime = 0;
324 } 314 }
325 315
326 /// Returns `(state, mode, size, mtime)` for the puprose of serialization 316 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
327 /// in the dirstate-v1 format. 317 /// in the dirstate-v1 format.
328 /// 318 ///
329 /// This includes marker values such as `mtime == -1`. In the future we may 319 /// This includes marker values such as `mtime == -1`. In the future we may
330 /// want to not represent these cases that way in memory, but serialization 320 /// want to not represent these cases that way in memory, but serialization
331 /// will need to keep the same format. 321 /// will need to keep the same format.
332 pub fn v1_data(&self) -> (u8, i32, i32, i32) { 322 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
333 (self.state().into(), self.mode(), self.size(), self.mtime()) 323 (
324 self.v1_state().into(),
325 self.v1_mode(),
326 self.v1_size(),
327 self.v1_mtime(),
328 )
334 } 329 }
335 330
336 pub(crate) fn is_from_other_parent(&self) -> bool { 331 pub(crate) fn is_from_other_parent(&self) -> bool {
337 self.state() == EntryState::Normal 332 self.state() == EntryState::Normal
338 && self.size() == SIZE_FROM_OTHER_PARENT 333 && self.size() == SIZE_FROM_OTHER_PARENT
352 } 347 }
353 348
354 /// Returns a `(state, mode, size, mtime)` tuple as for 349 /// Returns a `(state, mode, size, mtime)` tuple as for
355 /// `DirstateMapMethods::debug_iter`. 350 /// `DirstateMapMethods::debug_iter`.
356 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { 351 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
357 let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) { 352 (self.state().into(), self.mode(), self.size(), self.mtime())
358 b' '
359 } else {
360 self.state().into()
361 };
362 (state, self.mode(), self.size(), self.mtime())
363 } 353 }
364 354
365 pub fn mtime_is_ambiguous(&self, now: i32) -> bool { 355 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
366 self.state() == EntryState::Normal && self.mtime() == now 356 self.state() == EntryState::Normal && self.mtime() == now
367 } 357 }
376 // The user could change the file without changing its size 366 // The user could change the file without changing its size
377 // within the same second. Invalidate the file's mtime in 367 // within the same second. Invalidate the file's mtime in
378 // dirstate, forcing future 'status' calls to compare the 368 // dirstate, forcing future 'status' calls to compare the
379 // contents of the file if the size is the same. This prevents 369 // contents of the file if the size is the same. This prevents
380 // mistakenly treating such files as clean. 370 // mistakenly treating such files as clean.
381 self.clear_mtime() 371 self.set_possibly_dirty()
382 } 372 }
383 ambiguous 373 ambiguous
384 }
385
386 pub fn clear_mtime(&mut self) {
387 self.mtime = -1;
388 } 374 }
389 } 375 }
390 376
391 impl EntryState { 377 impl EntryState {
392 pub fn is_tracked(self) -> bool { 378 pub fn is_tracked(self) -> bool {