##// END OF EJS Templates
rust-dirstate: introduce intermediate struct for dirstate-v2 data...
Raphaël Gomès -
r49991:38e5bb14 default
parent child Browse files
Show More
@@ -1,711 +1,716 b''
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
2 use crate::errors::HgError;
2 use crate::errors::HgError;
3 use bitflags::bitflags;
3 use bitflags::bitflags;
4 use std::convert::{TryFrom, TryInto};
4 use std::convert::{TryFrom, TryInto};
5 use std::fs;
5 use std::fs;
6 use std::io;
6 use std::io;
7 use std::time::{SystemTime, UNIX_EPOCH};
7 use std::time::{SystemTime, UNIX_EPOCH};
8
8
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
10 pub enum EntryState {
10 pub enum EntryState {
11 Normal,
11 Normal,
12 Added,
12 Added,
13 Removed,
13 Removed,
14 Merged,
14 Merged,
15 }
15 }
16
16
17 /// `size` and `mtime.seconds` are truncated to 31 bits.
17 /// `size` and `mtime.seconds` are truncated to 31 bits.
18 ///
18 ///
19 /// TODO: double-check status algorithm correctness for files
19 /// TODO: double-check status algorithm correctness for files
20 /// larger than 2 GiB or modified after 2038.
20 /// larger than 2 GiB or modified after 2038.
21 #[derive(Debug, Copy, Clone)]
21 #[derive(Debug, Copy, Clone)]
22 pub struct DirstateEntry {
22 pub struct DirstateEntry {
23 pub(crate) flags: Flags,
23 pub(crate) flags: Flags,
24 mode_size: Option<(u32, u32)>,
24 mode_size: Option<(u32, u32)>,
25 mtime: Option<TruncatedTimestamp>,
25 mtime: Option<TruncatedTimestamp>,
26 }
26 }
27
27
28 bitflags! {
28 bitflags! {
29 pub(crate) struct Flags: u8 {
29 pub(crate) struct Flags: u8 {
30 const WDIR_TRACKED = 1 << 0;
30 const WDIR_TRACKED = 1 << 0;
31 const P1_TRACKED = 1 << 1;
31 const P1_TRACKED = 1 << 1;
32 const P2_INFO = 1 << 2;
32 const P2_INFO = 1 << 2;
33 const HAS_FALLBACK_EXEC = 1 << 3;
33 const HAS_FALLBACK_EXEC = 1 << 3;
34 const FALLBACK_EXEC = 1 << 4;
34 const FALLBACK_EXEC = 1 << 4;
35 const HAS_FALLBACK_SYMLINK = 1 << 5;
35 const HAS_FALLBACK_SYMLINK = 1 << 5;
36 const FALLBACK_SYMLINK = 1 << 6;
36 const FALLBACK_SYMLINK = 1 << 6;
37 }
37 }
38 }
38 }
39
39
40 /// A Unix timestamp with nanoseconds precision
40 /// A Unix timestamp with nanoseconds precision
41 #[derive(Debug, Copy, Clone)]
41 #[derive(Debug, Copy, Clone)]
42 pub struct TruncatedTimestamp {
42 pub struct TruncatedTimestamp {
43 truncated_seconds: u32,
43 truncated_seconds: u32,
44 /// Always in the `0 .. 1_000_000_000` range.
44 /// Always in the `0 .. 1_000_000_000` range.
45 nanoseconds: u32,
45 nanoseconds: u32,
46 /// TODO this should be in DirstateEntry, but the current code needs
46 /// TODO this should be in DirstateEntry, but the current code needs
47 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
47 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
48 /// comparison.
48 /// comparison.
49 pub second_ambiguous: bool,
49 pub second_ambiguous: bool,
50 }
50 }
51
51
52 impl TruncatedTimestamp {
52 impl TruncatedTimestamp {
53 /// Constructs from a timestamp potentially outside of the supported range,
53 /// Constructs from a timestamp potentially outside of the supported range,
54 /// and truncate the seconds components to its lower 31 bits.
54 /// and truncate the seconds components to its lower 31 bits.
55 ///
55 ///
56 /// Panics if the nanoseconds components is not in the expected range.
56 /// Panics if the nanoseconds components is not in the expected range.
57 pub fn new_truncate(
57 pub fn new_truncate(
58 seconds: i64,
58 seconds: i64,
59 nanoseconds: u32,
59 nanoseconds: u32,
60 second_ambiguous: bool,
60 second_ambiguous: bool,
61 ) -> Self {
61 ) -> Self {
62 assert!(nanoseconds < NSEC_PER_SEC);
62 assert!(nanoseconds < NSEC_PER_SEC);
63 Self {
63 Self {
64 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
64 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
65 nanoseconds,
65 nanoseconds,
66 second_ambiguous,
66 second_ambiguous,
67 }
67 }
68 }
68 }
69
69
70 /// Construct from components. Returns an error if they are not in the
70 /// Construct from components. Returns an error if they are not in the
71 /// expcted range.
71 /// expcted range.
72 pub fn from_already_truncated(
72 pub fn from_already_truncated(
73 truncated_seconds: u32,
73 truncated_seconds: u32,
74 nanoseconds: u32,
74 nanoseconds: u32,
75 second_ambiguous: bool,
75 second_ambiguous: bool,
76 ) -> Result<Self, DirstateV2ParseError> {
76 ) -> Result<Self, DirstateV2ParseError> {
77 if truncated_seconds & !RANGE_MASK_31BIT == 0
77 if truncated_seconds & !RANGE_MASK_31BIT == 0
78 && nanoseconds < NSEC_PER_SEC
78 && nanoseconds < NSEC_PER_SEC
79 {
79 {
80 Ok(Self {
80 Ok(Self {
81 truncated_seconds,
81 truncated_seconds,
82 nanoseconds,
82 nanoseconds,
83 second_ambiguous,
83 second_ambiguous,
84 })
84 })
85 } else {
85 } else {
86 Err(DirstateV2ParseError)
86 Err(DirstateV2ParseError)
87 }
87 }
88 }
88 }
89
89
90 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
90 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
91 ///
91 ///
92 /// Propagates errors from `std` on platforms where modification time
92 /// Propagates errors from `std` on platforms where modification time
93 /// is not available at all.
93 /// is not available at all.
94 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
94 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
95 #[cfg(unix)]
95 #[cfg(unix)]
96 {
96 {
97 use std::os::unix::fs::MetadataExt;
97 use std::os::unix::fs::MetadataExt;
98 let seconds = metadata.mtime();
98 let seconds = metadata.mtime();
99 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
99 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
100 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
100 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
101 Ok(Self::new_truncate(seconds, nanoseconds, false))
101 Ok(Self::new_truncate(seconds, nanoseconds, false))
102 }
102 }
103 #[cfg(not(unix))]
103 #[cfg(not(unix))]
104 {
104 {
105 metadata.modified().map(Self::from)
105 metadata.modified().map(Self::from)
106 }
106 }
107 }
107 }
108
108
109 /// Like `for_mtime_of`, but may return `None` or a value with
109 /// Like `for_mtime_of`, but may return `None` or a value with
110 /// `second_ambiguous` set if the mtime is not "reliable".
110 /// `second_ambiguous` set if the mtime is not "reliable".
111 ///
111 ///
112 /// A modification time is reliable if it is older than `boundary` (or
112 /// A modification time is reliable if it is older than `boundary` (or
113 /// sufficiently in the future).
113 /// sufficiently in the future).
114 ///
114 ///
115 /// Otherwise a concurrent modification might happens with the same mtime.
115 /// Otherwise a concurrent modification might happens with the same mtime.
116 pub fn for_reliable_mtime_of(
116 pub fn for_reliable_mtime_of(
117 metadata: &fs::Metadata,
117 metadata: &fs::Metadata,
118 boundary: &Self,
118 boundary: &Self,
119 ) -> io::Result<Option<Self>> {
119 ) -> io::Result<Option<Self>> {
120 let mut mtime = Self::for_mtime_of(metadata)?;
120 let mut mtime = Self::for_mtime_of(metadata)?;
121 // If the mtime of the ambiguous file is younger (or equal) to the
121 // If the mtime of the ambiguous file is younger (or equal) to the
122 // starting point of the `status` walk, we cannot garantee that
122 // starting point of the `status` walk, we cannot garantee that
123 // another, racy, write will not happen right after with the same mtime
123 // another, racy, write will not happen right after with the same mtime
124 // and we cannot cache the information.
124 // and we cannot cache the information.
125 //
125 //
126 // However if the mtime is far away in the future, this is likely some
126 // However if the mtime is far away in the future, this is likely some
127 // mismatch between the current clock and previous file system
127 // mismatch between the current clock and previous file system
128 // operation. So mtime more than one days in the future are considered
128 // operation. So mtime more than one days in the future are considered
129 // fine.
129 // fine.
130 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
130 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
131 {
131 {
132 mtime.second_ambiguous = true;
132 mtime.second_ambiguous = true;
133 mtime.nanoseconds != 0
133 mtime.nanoseconds != 0
134 && boundary.nanoseconds != 0
134 && boundary.nanoseconds != 0
135 && mtime.nanoseconds < boundary.nanoseconds
135 && mtime.nanoseconds < boundary.nanoseconds
136 } else {
136 } else {
137 // `truncated_seconds` is less than 2**31,
137 // `truncated_seconds` is less than 2**31,
138 // so this does not overflow `u32`:
138 // so this does not overflow `u32`:
139 let one_day_later = boundary.truncated_seconds + 24 * 3600;
139 let one_day_later = boundary.truncated_seconds + 24 * 3600;
140 mtime.truncated_seconds < boundary.truncated_seconds
140 mtime.truncated_seconds < boundary.truncated_seconds
141 || mtime.truncated_seconds > one_day_later
141 || mtime.truncated_seconds > one_day_later
142 };
142 };
143 if reliable {
143 if reliable {
144 Ok(Some(mtime))
144 Ok(Some(mtime))
145 } else {
145 } else {
146 Ok(None)
146 Ok(None)
147 }
147 }
148 }
148 }
149
149
150 /// The lower 31 bits of the number of seconds since the epoch.
150 /// The lower 31 bits of the number of seconds since the epoch.
151 pub fn truncated_seconds(&self) -> u32 {
151 pub fn truncated_seconds(&self) -> u32 {
152 self.truncated_seconds
152 self.truncated_seconds
153 }
153 }
154
154
155 /// The sub-second component of this timestamp, in nanoseconds.
155 /// The sub-second component of this timestamp, in nanoseconds.
156 /// Always in the `0 .. 1_000_000_000` range.
156 /// Always in the `0 .. 1_000_000_000` range.
157 ///
157 ///
158 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
158 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
159 pub fn nanoseconds(&self) -> u32 {
159 pub fn nanoseconds(&self) -> u32 {
160 self.nanoseconds
160 self.nanoseconds
161 }
161 }
162
162
163 /// Returns whether two timestamps are equal modulo 2**31 seconds.
163 /// Returns whether two timestamps are equal modulo 2**31 seconds.
164 ///
164 ///
165 /// If this returns `true`, the original values converted from `SystemTime`
165 /// If this returns `true`, the original values converted from `SystemTime`
166 /// or given to `new_truncate` were very likely equal. A false positive is
166 /// or given to `new_truncate` were very likely equal. A false positive is
167 /// possible if they were exactly a multiple of 2**31 seconds apart (around
167 /// possible if they were exactly a multiple of 2**31 seconds apart (around
168 /// 68 years). This is deemed very unlikely to happen by chance, especially
168 /// 68 years). This is deemed very unlikely to happen by chance, especially
169 /// on filesystems that support sub-second precision.
169 /// on filesystems that support sub-second precision.
170 ///
170 ///
171 /// If someone is manipulating the modification times of some files to
171 /// If someone is manipulating the modification times of some files to
172 /// intentionally make `hg status` return incorrect results, not truncating
172 /// intentionally make `hg status` return incorrect results, not truncating
173 /// wouldn’t help much since they can set exactly the expected timestamp.
173 /// wouldn’t help much since they can set exactly the expected timestamp.
174 ///
174 ///
175 /// Sub-second precision is ignored if it is zero in either value.
175 /// Sub-second precision is ignored if it is zero in either value.
176 /// Some APIs simply return zero when more precision is not available.
176 /// Some APIs simply return zero when more precision is not available.
177 /// When comparing values from different sources, if only one is truncated
177 /// When comparing values from different sources, if only one is truncated
178 /// in that way, doing a simple comparison would cause many false
178 /// in that way, doing a simple comparison would cause many false
179 /// negatives.
179 /// negatives.
180 pub fn likely_equal(self, other: Self) -> bool {
180 pub fn likely_equal(self, other: Self) -> bool {
181 if self.truncated_seconds != other.truncated_seconds {
181 if self.truncated_seconds != other.truncated_seconds {
182 false
182 false
183 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
183 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
184 if self.second_ambiguous {
184 if self.second_ambiguous {
185 false
185 false
186 } else {
186 } else {
187 true
187 true
188 }
188 }
189 } else {
189 } else {
190 self.nanoseconds == other.nanoseconds
190 self.nanoseconds == other.nanoseconds
191 }
191 }
192 }
192 }
193
193
194 pub fn likely_equal_to_mtime_of(
194 pub fn likely_equal_to_mtime_of(
195 self,
195 self,
196 metadata: &fs::Metadata,
196 metadata: &fs::Metadata,
197 ) -> io::Result<bool> {
197 ) -> io::Result<bool> {
198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
199 }
199 }
200 }
200 }
201
201
202 impl From<SystemTime> for TruncatedTimestamp {
202 impl From<SystemTime> for TruncatedTimestamp {
203 fn from(system_time: SystemTime) -> Self {
203 fn from(system_time: SystemTime) -> Self {
204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
205 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
205 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
206 // We want to effectively access its fields, but the Rust standard
206 // We want to effectively access its fields, but the Rust standard
207 // library does not expose them. The best we can do is:
207 // library does not expose them. The best we can do is:
208 let seconds;
208 let seconds;
209 let nanoseconds;
209 let nanoseconds;
210 match system_time.duration_since(UNIX_EPOCH) {
210 match system_time.duration_since(UNIX_EPOCH) {
211 Ok(duration) => {
211 Ok(duration) => {
212 seconds = duration.as_secs() as i64;
212 seconds = duration.as_secs() as i64;
213 nanoseconds = duration.subsec_nanos();
213 nanoseconds = duration.subsec_nanos();
214 }
214 }
215 Err(error) => {
215 Err(error) => {
216 // `system_time` is before `UNIX_EPOCH`.
216 // `system_time` is before `UNIX_EPOCH`.
217 // We need to undo this algorithm:
217 // We need to undo this algorithm:
218 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
218 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
219 let negative = error.duration();
219 let negative = error.duration();
220 let negative_secs = negative.as_secs() as i64;
220 let negative_secs = negative.as_secs() as i64;
221 let negative_nanos = negative.subsec_nanos();
221 let negative_nanos = negative.subsec_nanos();
222 if negative_nanos == 0 {
222 if negative_nanos == 0 {
223 seconds = -negative_secs;
223 seconds = -negative_secs;
224 nanoseconds = 0;
224 nanoseconds = 0;
225 } else {
225 } else {
226 // For example if `system_time` was 4.3 seconds before
226 // For example if `system_time` was 4.3 seconds before
227 // the Unix epoch we get a Duration that represents
227 // the Unix epoch we get a Duration that represents
228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
229 seconds = -1 - negative_secs;
229 seconds = -1 - negative_secs;
230 nanoseconds = NSEC_PER_SEC - negative_nanos;
230 nanoseconds = NSEC_PER_SEC - negative_nanos;
231 }
231 }
232 }
232 }
233 };
233 };
234 Self::new_truncate(seconds, nanoseconds, false)
234 Self::new_truncate(seconds, nanoseconds, false)
235 }
235 }
236 }
236 }
237
237
238 const NSEC_PER_SEC: u32 = 1_000_000_000;
238 const NSEC_PER_SEC: u32 = 1_000_000_000;
239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
240
240
241 pub const MTIME_UNSET: i32 = -1;
241 pub const MTIME_UNSET: i32 = -1;
242
242
243 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
243 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
244 /// other parent. This allows revert to pick the right status back during a
244 /// other parent. This allows revert to pick the right status back during a
245 /// merge.
245 /// merge.
246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
247 /// A special value used for internal representation of special case in
247 /// A special value used for internal representation of special case in
248 /// dirstate v1 format.
248 /// dirstate v1 format.
249 pub const SIZE_NON_NORMAL: i32 = -1;
249 pub const SIZE_NON_NORMAL: i32 = -1;
250
250
251 #[derive(Debug, Default, Copy, Clone)]
252 pub struct DirstateV2Data {
253 pub wc_tracked: bool,
254 pub p1_tracked: bool,
255 pub p2_info: bool,
256 pub mode_size: Option<(u32, u32)>,
257 pub mtime: Option<TruncatedTimestamp>,
258 pub fallback_exec: Option<bool>,
259 pub fallback_symlink: Option<bool>,
260 }
261
251 impl DirstateEntry {
262 impl DirstateEntry {
252 pub fn from_v2_data(
263 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
253 wdir_tracked: bool,
264 let DirstateV2Data {
254 p1_tracked: bool,
265 wc_tracked,
255 p2_info: bool,
266 p1_tracked,
256 mode_size: Option<(u32, u32)>,
267 p2_info,
257 mtime: Option<TruncatedTimestamp>,
268 mode_size,
258 fallback_exec: Option<bool>,
269 mtime,
259 fallback_symlink: Option<bool>,
270 fallback_exec,
260 ) -> Self {
271 fallback_symlink,
272 } = v2_data;
261 if let Some((mode, size)) = mode_size {
273 if let Some((mode, size)) = mode_size {
262 // TODO: return an error for out of range values?
274 // TODO: return an error for out of range values?
263 assert!(mode & !RANGE_MASK_31BIT == 0);
275 assert!(mode & !RANGE_MASK_31BIT == 0);
264 assert!(size & !RANGE_MASK_31BIT == 0);
276 assert!(size & !RANGE_MASK_31BIT == 0);
265 }
277 }
266 let mut flags = Flags::empty();
278 let mut flags = Flags::empty();
267 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
279 flags.set(Flags::WDIR_TRACKED, wc_tracked);
268 flags.set(Flags::P1_TRACKED, p1_tracked);
280 flags.set(Flags::P1_TRACKED, p1_tracked);
269 flags.set(Flags::P2_INFO, p2_info);
281 flags.set(Flags::P2_INFO, p2_info);
270 if let Some(exec) = fallback_exec {
282 if let Some(exec) = fallback_exec {
271 flags.insert(Flags::HAS_FALLBACK_EXEC);
283 flags.insert(Flags::HAS_FALLBACK_EXEC);
272 if exec {
284 if exec {
273 flags.insert(Flags::FALLBACK_EXEC);
285 flags.insert(Flags::FALLBACK_EXEC);
274 }
286 }
275 }
287 }
276 if let Some(exec) = fallback_symlink {
288 if let Some(exec) = fallback_symlink {
277 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
289 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
278 if exec {
290 if exec {
279 flags.insert(Flags::FALLBACK_SYMLINK);
291 flags.insert(Flags::FALLBACK_SYMLINK);
280 }
292 }
281 }
293 }
282 Self {
294 Self {
283 flags,
295 flags,
284 mode_size,
296 mode_size,
285 mtime,
297 mtime,
286 }
298 }
287 }
299 }
288
300
289 pub fn from_v1_data(
301 pub fn from_v1_data(
290 state: EntryState,
302 state: EntryState,
291 mode: i32,
303 mode: i32,
292 size: i32,
304 size: i32,
293 mtime: i32,
305 mtime: i32,
294 ) -> Self {
306 ) -> Self {
295 match state {
307 match state {
296 EntryState::Normal => {
308 EntryState::Normal => {
297 if size == SIZE_FROM_OTHER_PARENT {
309 if size == SIZE_FROM_OTHER_PARENT {
298 Self {
310 Self {
299 // might be missing P1_TRACKED
311 // might be missing P1_TRACKED
300 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
312 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
301 mode_size: None,
313 mode_size: None,
302 mtime: None,
314 mtime: None,
303 }
315 }
304 } else if size == SIZE_NON_NORMAL {
316 } else if size == SIZE_NON_NORMAL {
305 Self {
317 Self {
306 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
318 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
307 mode_size: None,
319 mode_size: None,
308 mtime: None,
320 mtime: None,
309 }
321 }
310 } else if mtime == MTIME_UNSET {
322 } else if mtime == MTIME_UNSET {
311 // TODO: return an error for negative values?
323 // TODO: return an error for negative values?
312 let mode = u32::try_from(mode).unwrap();
324 let mode = u32::try_from(mode).unwrap();
313 let size = u32::try_from(size).unwrap();
325 let size = u32::try_from(size).unwrap();
314 Self {
326 Self {
315 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
327 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
316 mode_size: Some((mode, size)),
328 mode_size: Some((mode, size)),
317 mtime: None,
329 mtime: None,
318 }
330 }
319 } else {
331 } else {
320 // TODO: return an error for negative values?
332 // TODO: return an error for negative values?
321 let mode = u32::try_from(mode).unwrap();
333 let mode = u32::try_from(mode).unwrap();
322 let size = u32::try_from(size).unwrap();
334 let size = u32::try_from(size).unwrap();
323 let mtime = u32::try_from(mtime).unwrap();
335 let mtime = u32::try_from(mtime).unwrap();
324 let mtime = TruncatedTimestamp::from_already_truncated(
336 let mtime = TruncatedTimestamp::from_already_truncated(
325 mtime, 0, false,
337 mtime, 0, false,
326 )
338 )
327 .unwrap();
339 .unwrap();
328 Self {
340 Self {
329 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
341 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
330 mode_size: Some((mode, size)),
342 mode_size: Some((mode, size)),
331 mtime: Some(mtime),
343 mtime: Some(mtime),
332 }
344 }
333 }
345 }
334 }
346 }
335 EntryState::Added => Self {
347 EntryState::Added => Self {
336 flags: Flags::WDIR_TRACKED,
348 flags: Flags::WDIR_TRACKED,
337 mode_size: None,
349 mode_size: None,
338 mtime: None,
350 mtime: None,
339 },
351 },
340 EntryState::Removed => Self {
352 EntryState::Removed => Self {
341 flags: if size == SIZE_NON_NORMAL {
353 flags: if size == SIZE_NON_NORMAL {
342 Flags::P1_TRACKED | Flags::P2_INFO
354 Flags::P1_TRACKED | Flags::P2_INFO
343 } else if size == SIZE_FROM_OTHER_PARENT {
355 } else if size == SIZE_FROM_OTHER_PARENT {
344 // We don’t know if P1_TRACKED should be set (file history)
356 // We don’t know if P1_TRACKED should be set (file history)
345 Flags::P2_INFO
357 Flags::P2_INFO
346 } else {
358 } else {
347 Flags::P1_TRACKED
359 Flags::P1_TRACKED
348 },
360 },
349 mode_size: None,
361 mode_size: None,
350 mtime: None,
362 mtime: None,
351 },
363 },
352 EntryState::Merged => Self {
364 EntryState::Merged => Self {
353 flags: Flags::WDIR_TRACKED
365 flags: Flags::WDIR_TRACKED
354 | Flags::P1_TRACKED // might not be true because of rename ?
366 | Flags::P1_TRACKED // might not be true because of rename ?
355 | Flags::P2_INFO, // might not be true because of rename ?
367 | Flags::P2_INFO, // might not be true because of rename ?
356 mode_size: None,
368 mode_size: None,
357 mtime: None,
369 mtime: None,
358 },
370 },
359 }
371 }
360 }
372 }
361
373
362 /// Creates a new entry in "removed" state.
374 /// Creates a new entry in "removed" state.
363 ///
375 ///
364 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
376 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
365 /// `SIZE_FROM_OTHER_PARENT`
377 /// `SIZE_FROM_OTHER_PARENT`
366 pub fn new_removed(size: i32) -> Self {
378 pub fn new_removed(size: i32) -> Self {
367 Self::from_v1_data(EntryState::Removed, 0, size, 0)
379 Self::from_v1_data(EntryState::Removed, 0, size, 0)
368 }
380 }
369
381
370 pub fn new_tracked() -> Self {
382 pub fn new_tracked() -> Self {
371 Self::from_v2_data(true, false, false, None, None, None, None)
383 let data = DirstateV2Data {
384 wc_tracked: true,
385 ..Default::default()
386 };
387 Self::from_v2_data(data)
372 }
388 }
373
389
374 pub fn tracked(&self) -> bool {
390 pub fn tracked(&self) -> bool {
375 self.flags.contains(Flags::WDIR_TRACKED)
391 self.flags.contains(Flags::WDIR_TRACKED)
376 }
392 }
377
393
378 pub fn p1_tracked(&self) -> bool {
394 pub fn p1_tracked(&self) -> bool {
379 self.flags.contains(Flags::P1_TRACKED)
395 self.flags.contains(Flags::P1_TRACKED)
380 }
396 }
381
397
382 fn in_either_parent(&self) -> bool {
398 fn in_either_parent(&self) -> bool {
383 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
399 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
384 }
400 }
385
401
386 pub fn removed(&self) -> bool {
402 pub fn removed(&self) -> bool {
387 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
403 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
388 }
404 }
389
405
390 pub fn p2_info(&self) -> bool {
406 pub fn p2_info(&self) -> bool {
391 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
407 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
392 }
408 }
393
409
394 pub fn added(&self) -> bool {
410 pub fn added(&self) -> bool {
395 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
411 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
396 }
412 }
397
413
398 pub fn maybe_clean(&self) -> bool {
414 pub fn maybe_clean(&self) -> bool {
399 if !self.flags.contains(Flags::WDIR_TRACKED) {
415 if !self.flags.contains(Flags::WDIR_TRACKED) {
400 false
416 false
401 } else if !self.flags.contains(Flags::P1_TRACKED) {
417 } else if !self.flags.contains(Flags::P1_TRACKED) {
402 false
418 false
403 } else if self.flags.contains(Flags::P2_INFO) {
419 } else if self.flags.contains(Flags::P2_INFO) {
404 false
420 false
405 } else {
421 } else {
406 true
422 true
407 }
423 }
408 }
424 }
409
425
410 pub fn any_tracked(&self) -> bool {
426 pub fn any_tracked(&self) -> bool {
411 self.flags.intersects(
427 self.flags.intersects(
412 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
428 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
413 )
429 )
414 }
430 }
415
431
416 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
432 pub(crate) fn v2_data(&self) -> DirstateV2Data {
417 pub(crate) fn v2_data(
418 &self,
419 ) -> (
420 bool,
421 bool,
422 bool,
423 Option<(u32, u32)>,
424 Option<TruncatedTimestamp>,
425 Option<bool>,
426 Option<bool>,
427 ) {
428 if !self.any_tracked() {
433 if !self.any_tracked() {
429 // TODO: return an Option instead?
434 // TODO: return an Option instead?
430 panic!("Accessing v2_data of an untracked DirstateEntry")
435 panic!("Accessing v2_data of an untracked DirstateEntry")
431 }
436 }
432 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
437 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
433 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
438 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
434 let p2_info = self.flags.contains(Flags::P2_INFO);
439 let p2_info = self.flags.contains(Flags::P2_INFO);
435 let mode_size = self.mode_size;
440 let mode_size = self.mode_size;
436 let mtime = self.mtime;
441 let mtime = self.mtime;
437 (
442 DirstateV2Data {
438 wdir_tracked,
443 wc_tracked,
439 p1_tracked,
444 p1_tracked,
440 p2_info,
445 p2_info,
441 mode_size,
446 mode_size,
442 mtime,
447 mtime,
443 self.get_fallback_exec(),
448 fallback_exec: self.get_fallback_exec(),
444 self.get_fallback_symlink(),
449 fallback_symlink: self.get_fallback_symlink(),
445 )
450 }
446 }
451 }
447
452
448 fn v1_state(&self) -> EntryState {
453 fn v1_state(&self) -> EntryState {
449 if !self.any_tracked() {
454 if !self.any_tracked() {
450 // TODO: return an Option instead?
455 // TODO: return an Option instead?
451 panic!("Accessing v1_state of an untracked DirstateEntry")
456 panic!("Accessing v1_state of an untracked DirstateEntry")
452 }
457 }
453 if self.removed() {
458 if self.removed() {
454 EntryState::Removed
459 EntryState::Removed
455 } else if self
460 } else if self
456 .flags
461 .flags
457 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
462 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
458 {
463 {
459 EntryState::Merged
464 EntryState::Merged
460 } else if self.added() {
465 } else if self.added() {
461 EntryState::Added
466 EntryState::Added
462 } else {
467 } else {
463 EntryState::Normal
468 EntryState::Normal
464 }
469 }
465 }
470 }
466
471
467 fn v1_mode(&self) -> i32 {
472 fn v1_mode(&self) -> i32 {
468 if let Some((mode, _size)) = self.mode_size {
473 if let Some((mode, _size)) = self.mode_size {
469 i32::try_from(mode).unwrap()
474 i32::try_from(mode).unwrap()
470 } else {
475 } else {
471 0
476 0
472 }
477 }
473 }
478 }
474
479
475 fn v1_size(&self) -> i32 {
480 fn v1_size(&self) -> i32 {
476 if !self.any_tracked() {
481 if !self.any_tracked() {
477 // TODO: return an Option instead?
482 // TODO: return an Option instead?
478 panic!("Accessing v1_size of an untracked DirstateEntry")
483 panic!("Accessing v1_size of an untracked DirstateEntry")
479 }
484 }
480 if self.removed()
485 if self.removed()
481 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
486 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
482 {
487 {
483 SIZE_NON_NORMAL
488 SIZE_NON_NORMAL
484 } else if self.flags.contains(Flags::P2_INFO) {
489 } else if self.flags.contains(Flags::P2_INFO) {
485 SIZE_FROM_OTHER_PARENT
490 SIZE_FROM_OTHER_PARENT
486 } else if self.removed() {
491 } else if self.removed() {
487 0
492 0
488 } else if self.added() {
493 } else if self.added() {
489 SIZE_NON_NORMAL
494 SIZE_NON_NORMAL
490 } else if let Some((_mode, size)) = self.mode_size {
495 } else if let Some((_mode, size)) = self.mode_size {
491 i32::try_from(size).unwrap()
496 i32::try_from(size).unwrap()
492 } else {
497 } else {
493 SIZE_NON_NORMAL
498 SIZE_NON_NORMAL
494 }
499 }
495 }
500 }
496
501
497 fn v1_mtime(&self) -> i32 {
502 fn v1_mtime(&self) -> i32 {
498 if !self.any_tracked() {
503 if !self.any_tracked() {
499 // TODO: return an Option instead?
504 // TODO: return an Option instead?
500 panic!("Accessing v1_mtime of an untracked DirstateEntry")
505 panic!("Accessing v1_mtime of an untracked DirstateEntry")
501 }
506 }
502 if self.removed() {
507 if self.removed() {
503 0
508 0
504 } else if self.flags.contains(Flags::P2_INFO) {
509 } else if self.flags.contains(Flags::P2_INFO) {
505 MTIME_UNSET
510 MTIME_UNSET
506 } else if !self.flags.contains(Flags::P1_TRACKED) {
511 } else if !self.flags.contains(Flags::P1_TRACKED) {
507 MTIME_UNSET
512 MTIME_UNSET
508 } else if let Some(mtime) = self.mtime {
513 } else if let Some(mtime) = self.mtime {
509 if mtime.second_ambiguous {
514 if mtime.second_ambiguous {
510 MTIME_UNSET
515 MTIME_UNSET
511 } else {
516 } else {
512 i32::try_from(mtime.truncated_seconds()).unwrap()
517 i32::try_from(mtime.truncated_seconds()).unwrap()
513 }
518 }
514 } else {
519 } else {
515 MTIME_UNSET
520 MTIME_UNSET
516 }
521 }
517 }
522 }
518
523
519 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
524 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
520 pub fn state(&self) -> EntryState {
525 pub fn state(&self) -> EntryState {
521 self.v1_state()
526 self.v1_state()
522 }
527 }
523
528
524 // TODO: return Option?
529 // TODO: return Option?
525 pub fn mode(&self) -> i32 {
530 pub fn mode(&self) -> i32 {
526 self.v1_mode()
531 self.v1_mode()
527 }
532 }
528
533
529 // TODO: return Option?
534 // TODO: return Option?
530 pub fn size(&self) -> i32 {
535 pub fn size(&self) -> i32 {
531 self.v1_size()
536 self.v1_size()
532 }
537 }
533
538
534 // TODO: return Option?
539 // TODO: return Option?
535 pub fn mtime(&self) -> i32 {
540 pub fn mtime(&self) -> i32 {
536 self.v1_mtime()
541 self.v1_mtime()
537 }
542 }
538
543
539 pub fn get_fallback_exec(&self) -> Option<bool> {
544 pub fn get_fallback_exec(&self) -> Option<bool> {
540 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
545 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
541 Some(self.flags.contains(Flags::FALLBACK_EXEC))
546 Some(self.flags.contains(Flags::FALLBACK_EXEC))
542 } else {
547 } else {
543 None
548 None
544 }
549 }
545 }
550 }
546
551
547 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
552 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
548 match value {
553 match value {
549 None => {
554 None => {
550 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
555 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
551 self.flags.remove(Flags::FALLBACK_EXEC);
556 self.flags.remove(Flags::FALLBACK_EXEC);
552 }
557 }
553 Some(exec) => {
558 Some(exec) => {
554 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
559 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
555 if exec {
560 if exec {
556 self.flags.insert(Flags::FALLBACK_EXEC);
561 self.flags.insert(Flags::FALLBACK_EXEC);
557 }
562 }
558 }
563 }
559 }
564 }
560 }
565 }
561
566
562 pub fn get_fallback_symlink(&self) -> Option<bool> {
567 pub fn get_fallback_symlink(&self) -> Option<bool> {
563 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
568 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
564 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
569 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
565 } else {
570 } else {
566 None
571 None
567 }
572 }
568 }
573 }
569
574
570 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
575 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
571 match value {
576 match value {
572 None => {
577 None => {
573 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
578 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
574 self.flags.remove(Flags::FALLBACK_SYMLINK);
579 self.flags.remove(Flags::FALLBACK_SYMLINK);
575 }
580 }
576 Some(symlink) => {
581 Some(symlink) => {
577 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
582 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
578 if symlink {
583 if symlink {
579 self.flags.insert(Flags::FALLBACK_SYMLINK);
584 self.flags.insert(Flags::FALLBACK_SYMLINK);
580 }
585 }
581 }
586 }
582 }
587 }
583 }
588 }
584
589
585 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
590 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
586 self.mtime
591 self.mtime
587 }
592 }
588
593
589 pub fn drop_merge_data(&mut self) {
594 pub fn drop_merge_data(&mut self) {
590 if self.flags.contains(Flags::P2_INFO) {
595 if self.flags.contains(Flags::P2_INFO) {
591 self.flags.remove(Flags::P2_INFO);
596 self.flags.remove(Flags::P2_INFO);
592 self.mode_size = None;
597 self.mode_size = None;
593 self.mtime = None;
598 self.mtime = None;
594 }
599 }
595 }
600 }
596
601
597 pub fn set_possibly_dirty(&mut self) {
602 pub fn set_possibly_dirty(&mut self) {
598 self.mtime = None
603 self.mtime = None
599 }
604 }
600
605
601 pub fn set_clean(
606 pub fn set_clean(
602 &mut self,
607 &mut self,
603 mode: u32,
608 mode: u32,
604 size: u32,
609 size: u32,
605 mtime: TruncatedTimestamp,
610 mtime: TruncatedTimestamp,
606 ) {
611 ) {
607 let size = size & RANGE_MASK_31BIT;
612 let size = size & RANGE_MASK_31BIT;
608 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
613 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
609 self.mode_size = Some((mode, size));
614 self.mode_size = Some((mode, size));
610 self.mtime = Some(mtime);
615 self.mtime = Some(mtime);
611 }
616 }
612
617
613 pub fn set_tracked(&mut self) {
618 pub fn set_tracked(&mut self) {
614 self.flags.insert(Flags::WDIR_TRACKED);
619 self.flags.insert(Flags::WDIR_TRACKED);
615 // `set_tracked` is replacing various `normallookup` call. So we mark
620 // `set_tracked` is replacing various `normallookup` call. So we mark
616 // the files as needing lookup
621 // the files as needing lookup
617 //
622 //
618 // Consider dropping this in the future in favor of something less
623 // Consider dropping this in the future in favor of something less
619 // broad.
624 // broad.
620 self.mtime = None;
625 self.mtime = None;
621 }
626 }
622
627
623 pub fn set_untracked(&mut self) {
628 pub fn set_untracked(&mut self) {
624 self.flags.remove(Flags::WDIR_TRACKED);
629 self.flags.remove(Flags::WDIR_TRACKED);
625 self.mode_size = None;
630 self.mode_size = None;
626 self.mtime = None;
631 self.mtime = None;
627 }
632 }
628
633
629 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
634 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
630 /// in the dirstate-v1 format.
635 /// in the dirstate-v1 format.
631 ///
636 ///
632 /// This includes marker values such as `mtime == -1`. In the future we may
637 /// This includes marker values such as `mtime == -1`. In the future we may
633 /// want to not represent these cases that way in memory, but serialization
638 /// want to not represent these cases that way in memory, but serialization
634 /// will need to keep the same format.
639 /// will need to keep the same format.
635 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
640 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
636 (
641 (
637 self.v1_state().into(),
642 self.v1_state().into(),
638 self.v1_mode(),
643 self.v1_mode(),
639 self.v1_size(),
644 self.v1_size(),
640 self.v1_mtime(),
645 self.v1_mtime(),
641 )
646 )
642 }
647 }
643
648
644 pub(crate) fn is_from_other_parent(&self) -> bool {
649 pub(crate) fn is_from_other_parent(&self) -> bool {
645 self.state() == EntryState::Normal
650 self.state() == EntryState::Normal
646 && self.size() == SIZE_FROM_OTHER_PARENT
651 && self.size() == SIZE_FROM_OTHER_PARENT
647 }
652 }
648
653
649 // TODO: other platforms
654 // TODO: other platforms
650 #[cfg(unix)]
655 #[cfg(unix)]
651 pub fn mode_changed(
656 pub fn mode_changed(
652 &self,
657 &self,
653 filesystem_metadata: &std::fs::Metadata,
658 filesystem_metadata: &std::fs::Metadata,
654 ) -> bool {
659 ) -> bool {
655 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
660 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
656 let fs_exec_bit = has_exec_bit(filesystem_metadata);
661 let fs_exec_bit = has_exec_bit(filesystem_metadata);
657 dirstate_exec_bit != fs_exec_bit
662 dirstate_exec_bit != fs_exec_bit
658 }
663 }
659
664
660 /// Returns a `(state, mode, size, mtime)` tuple as for
665 /// Returns a `(state, mode, size, mtime)` tuple as for
661 /// `DirstateMapMethods::debug_iter`.
666 /// `DirstateMapMethods::debug_iter`.
662 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
667 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
663 (self.state().into(), self.mode(), self.size(), self.mtime())
668 (self.state().into(), self.mode(), self.size(), self.mtime())
664 }
669 }
665 }
670 }
666
671
667 impl EntryState {
672 impl EntryState {
668 pub fn is_tracked(self) -> bool {
673 pub fn is_tracked(self) -> bool {
669 use EntryState::*;
674 use EntryState::*;
670 match self {
675 match self {
671 Normal | Added | Merged => true,
676 Normal | Added | Merged => true,
672 Removed => false,
677 Removed => false,
673 }
678 }
674 }
679 }
675 }
680 }
676
681
677 impl TryFrom<u8> for EntryState {
682 impl TryFrom<u8> for EntryState {
678 type Error = HgError;
683 type Error = HgError;
679
684
680 fn try_from(value: u8) -> Result<Self, Self::Error> {
685 fn try_from(value: u8) -> Result<Self, Self::Error> {
681 match value {
686 match value {
682 b'n' => Ok(EntryState::Normal),
687 b'n' => Ok(EntryState::Normal),
683 b'a' => Ok(EntryState::Added),
688 b'a' => Ok(EntryState::Added),
684 b'r' => Ok(EntryState::Removed),
689 b'r' => Ok(EntryState::Removed),
685 b'm' => Ok(EntryState::Merged),
690 b'm' => Ok(EntryState::Merged),
686 _ => Err(HgError::CorruptedRepository(format!(
691 _ => Err(HgError::CorruptedRepository(format!(
687 "Incorrect dirstate entry state {}",
692 "Incorrect dirstate entry state {}",
688 value
693 value
689 ))),
694 ))),
690 }
695 }
691 }
696 }
692 }
697 }
693
698
694 impl Into<u8> for EntryState {
699 impl Into<u8> for EntryState {
695 fn into(self) -> u8 {
700 fn into(self) -> u8 {
696 match self {
701 match self {
697 EntryState::Normal => b'n',
702 EntryState::Normal => b'n',
698 EntryState::Added => b'a',
703 EntryState::Added => b'a',
699 EntryState::Removed => b'r',
704 EntryState::Removed => b'r',
700 EntryState::Merged => b'm',
705 EntryState::Merged => b'm',
701 }
706 }
702 }
707 }
703 }
708 }
704
709
705 const EXEC_BIT_MASK: u32 = 0o100;
710 const EXEC_BIT_MASK: u32 = 0o100;
706
711
707 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
712 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
708 // TODO: How to handle executable permissions on Windows?
713 // TODO: How to handle executable permissions on Windows?
709 use std::os::unix::fs::MetadataExt;
714 use std::os::unix::fs::MetadataExt;
710 (metadata.mode() & EXEC_BIT_MASK) != 0
715 (metadata.mode() & EXEC_BIT_MASK) != 0
711 }
716 }
@@ -1,843 +1,843 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::TruncatedTimestamp;
5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
7 use crate::dirstate_tree::path_with_basename::WithBasename;
7 use crate::dirstate_tree::path_with_basename::WithBasename;
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::DirstateEntry;
10 use crate::DirstateEntry;
11 use crate::DirstateError;
11 use crate::DirstateError;
12 use crate::DirstateParents;
12 use crate::DirstateParents;
13 use bitflags::bitflags;
13 use bitflags::bitflags;
14 use bytes_cast::unaligned::{U16Be, U32Be};
14 use bytes_cast::unaligned::{U16Be, U32Be};
15 use bytes_cast::BytesCast;
15 use bytes_cast::BytesCast;
16 use format_bytes::format_bytes;
16 use format_bytes::format_bytes;
17 use rand::Rng;
17 use rand::Rng;
18 use std::borrow::Cow;
18 use std::borrow::Cow;
19 use std::convert::{TryFrom, TryInto};
19 use std::convert::{TryFrom, TryInto};
20 use std::fmt::Write;
20 use std::fmt::Write;
21
21
22 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
22 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
23 /// This a redundant sanity check more than an actual "magic number" since
23 /// This a redundant sanity check more than an actual "magic number" since
24 /// `.hg/requires` already governs which format should be used.
24 /// `.hg/requires` already governs which format should be used.
25 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
25 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
26
26
27 /// Keep space for 256-bit hashes
27 /// Keep space for 256-bit hashes
28 const STORED_NODE_ID_BYTES: usize = 32;
28 const STORED_NODE_ID_BYTES: usize = 32;
29
29
30 /// … even though only 160 bits are used for now, with SHA-1
30 /// … even though only 160 bits are used for now, with SHA-1
31 const USED_NODE_ID_BYTES: usize = 20;
31 const USED_NODE_ID_BYTES: usize = 20;
32
32
33 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
33 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
34 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
34 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
35
35
36 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
36 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
37 const TREE_METADATA_SIZE: usize = 44;
37 const TREE_METADATA_SIZE: usize = 44;
38 const NODE_SIZE: usize = 44;
38 const NODE_SIZE: usize = 44;
39
39
40 /// Make sure that size-affecting changes are made knowingly
40 /// Make sure that size-affecting changes are made knowingly
41 #[allow(unused)]
41 #[allow(unused)]
42 fn static_assert_size_of() {
42 fn static_assert_size_of() {
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
46 }
46 }
47
47
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
49 #[derive(BytesCast)]
49 #[derive(BytesCast)]
50 #[repr(C)]
50 #[repr(C)]
51 struct DocketHeader {
51 struct DocketHeader {
52 marker: [u8; V2_FORMAT_MARKER.len()],
52 marker: [u8; V2_FORMAT_MARKER.len()],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
55
55
56 metadata: TreeMetadata,
56 metadata: TreeMetadata,
57
57
58 /// Counted in bytes
58 /// Counted in bytes
59 data_size: Size,
59 data_size: Size,
60
60
61 uuid_size: u8,
61 uuid_size: u8,
62 }
62 }
63
63
64 pub struct Docket<'on_disk> {
64 pub struct Docket<'on_disk> {
65 header: &'on_disk DocketHeader,
65 header: &'on_disk DocketHeader,
66 pub uuid: &'on_disk [u8],
66 pub uuid: &'on_disk [u8],
67 }
67 }
68
68
69 /// Fields are documented in the *Tree metadata in the docket file*
69 /// Fields are documented in the *Tree metadata in the docket file*
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
71 #[derive(BytesCast)]
71 #[derive(BytesCast)]
72 #[repr(C)]
72 #[repr(C)]
73 pub struct TreeMetadata {
73 pub struct TreeMetadata {
74 root_nodes: ChildNodes,
74 root_nodes: ChildNodes,
75 nodes_with_entry_count: Size,
75 nodes_with_entry_count: Size,
76 nodes_with_copy_source_count: Size,
76 nodes_with_copy_source_count: Size,
77 unreachable_bytes: Size,
77 unreachable_bytes: Size,
78 unused: [u8; 4],
78 unused: [u8; 4],
79
79
80 /// See *Optional hash of ignore patterns* section of
80 /// See *Optional hash of ignore patterns* section of
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
82 ignore_patterns_hash: IgnorePatternsHash,
82 ignore_patterns_hash: IgnorePatternsHash,
83 }
83 }
84
84
85 /// Fields are documented in the *The data file format*
85 /// Fields are documented in the *The data file format*
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
87 #[derive(BytesCast)]
87 #[derive(BytesCast)]
88 #[repr(C)]
88 #[repr(C)]
89 pub(super) struct Node {
89 pub(super) struct Node {
90 full_path: PathSlice,
90 full_path: PathSlice,
91
91
92 /// In bytes from `self.full_path.start`
92 /// In bytes from `self.full_path.start`
93 base_name_start: PathSize,
93 base_name_start: PathSize,
94
94
95 copy_source: OptPathSlice,
95 copy_source: OptPathSlice,
96 children: ChildNodes,
96 children: ChildNodes,
97 pub(super) descendants_with_entry_count: Size,
97 pub(super) descendants_with_entry_count: Size,
98 pub(super) tracked_descendants_count: Size,
98 pub(super) tracked_descendants_count: Size,
99 flags: U16Be,
99 flags: U16Be,
100 size: U32Be,
100 size: U32Be,
101 mtime: PackedTruncatedTimestamp,
101 mtime: PackedTruncatedTimestamp,
102 }
102 }
103
103
104 bitflags! {
104 bitflags! {
105 #[repr(C)]
105 #[repr(C)]
106 struct Flags: u16 {
106 struct Flags: u16 {
107 const WDIR_TRACKED = 1 << 0;
107 const WDIR_TRACKED = 1 << 0;
108 const P1_TRACKED = 1 << 1;
108 const P1_TRACKED = 1 << 1;
109 const P2_INFO = 1 << 2;
109 const P2_INFO = 1 << 2;
110 const MODE_EXEC_PERM = 1 << 3;
110 const MODE_EXEC_PERM = 1 << 3;
111 const MODE_IS_SYMLINK = 1 << 4;
111 const MODE_IS_SYMLINK = 1 << 4;
112 const HAS_FALLBACK_EXEC = 1 << 5;
112 const HAS_FALLBACK_EXEC = 1 << 5;
113 const FALLBACK_EXEC = 1 << 6;
113 const FALLBACK_EXEC = 1 << 6;
114 const HAS_FALLBACK_SYMLINK = 1 << 7;
114 const HAS_FALLBACK_SYMLINK = 1 << 7;
115 const FALLBACK_SYMLINK = 1 << 8;
115 const FALLBACK_SYMLINK = 1 << 8;
116 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
116 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
117 const HAS_MODE_AND_SIZE = 1 <<10;
117 const HAS_MODE_AND_SIZE = 1 <<10;
118 const HAS_MTIME = 1 <<11;
118 const HAS_MTIME = 1 <<11;
119 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
119 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
120 const DIRECTORY = 1 <<13;
120 const DIRECTORY = 1 <<13;
121 const ALL_UNKNOWN_RECORDED = 1 <<14;
121 const ALL_UNKNOWN_RECORDED = 1 <<14;
122 const ALL_IGNORED_RECORDED = 1 <<15;
122 const ALL_IGNORED_RECORDED = 1 <<15;
123 }
123 }
124 }
124 }
125
125
126 /// Duration since the Unix epoch
126 /// Duration since the Unix epoch
127 #[derive(BytesCast, Copy, Clone)]
127 #[derive(BytesCast, Copy, Clone)]
128 #[repr(C)]
128 #[repr(C)]
129 struct PackedTruncatedTimestamp {
129 struct PackedTruncatedTimestamp {
130 truncated_seconds: U32Be,
130 truncated_seconds: U32Be,
131 nanoseconds: U32Be,
131 nanoseconds: U32Be,
132 }
132 }
133
133
134 /// Counted in bytes from the start of the file
134 /// Counted in bytes from the start of the file
135 ///
135 ///
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
137 type Offset = U32Be;
137 type Offset = U32Be;
138
138
139 /// Counted in number of items
139 /// Counted in number of items
140 ///
140 ///
141 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
141 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
142 type Size = U32Be;
142 type Size = U32Be;
143
143
144 /// Counted in bytes
144 /// Counted in bytes
145 ///
145 ///
146 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
146 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
147 type PathSize = U16Be;
147 type PathSize = U16Be;
148
148
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
150 /// of either some other node or of the repository root.
150 /// of either some other node or of the repository root.
151 ///
151 ///
152 /// Always sorted by ascending `full_path`, to allow binary search.
152 /// Always sorted by ascending `full_path`, to allow binary search.
153 /// Since nodes with the same parent nodes also have the same parent path,
153 /// Since nodes with the same parent nodes also have the same parent path,
154 /// only the `base_name`s need to be compared during binary search.
154 /// only the `base_name`s need to be compared during binary search.
155 #[derive(BytesCast, Copy, Clone)]
155 #[derive(BytesCast, Copy, Clone)]
156 #[repr(C)]
156 #[repr(C)]
157 struct ChildNodes {
157 struct ChildNodes {
158 start: Offset,
158 start: Offset,
159 len: Size,
159 len: Size,
160 }
160 }
161
161
162 /// A `HgPath` of `len` bytes
162 /// A `HgPath` of `len` bytes
163 #[derive(BytesCast, Copy, Clone)]
163 #[derive(BytesCast, Copy, Clone)]
164 #[repr(C)]
164 #[repr(C)]
165 struct PathSlice {
165 struct PathSlice {
166 start: Offset,
166 start: Offset,
167 len: PathSize,
167 len: PathSize,
168 }
168 }
169
169
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
171 type OptPathSlice = PathSlice;
171 type OptPathSlice = PathSlice;
172
172
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
174 ///
174 ///
175 /// This should only happen if Mercurial is buggy or a repository is corrupted.
175 /// This should only happen if Mercurial is buggy or a repository is corrupted.
176 #[derive(Debug)]
176 #[derive(Debug)]
177 pub struct DirstateV2ParseError;
177 pub struct DirstateV2ParseError;
178
178
179 impl From<DirstateV2ParseError> for HgError {
179 impl From<DirstateV2ParseError> for HgError {
180 fn from(_: DirstateV2ParseError) -> Self {
180 fn from(_: DirstateV2ParseError) -> Self {
181 HgError::corrupted("dirstate-v2 parse error")
181 HgError::corrupted("dirstate-v2 parse error")
182 }
182 }
183 }
183 }
184
184
185 impl From<DirstateV2ParseError> for crate::DirstateError {
185 impl From<DirstateV2ParseError> for crate::DirstateError {
186 fn from(error: DirstateV2ParseError) -> Self {
186 fn from(error: DirstateV2ParseError) -> Self {
187 HgError::from(error).into()
187 HgError::from(error).into()
188 }
188 }
189 }
189 }
190
190
191 impl TreeMetadata {
191 impl TreeMetadata {
192 pub fn as_bytes(&self) -> &[u8] {
192 pub fn as_bytes(&self) -> &[u8] {
193 BytesCast::as_bytes(self)
193 BytesCast::as_bytes(self)
194 }
194 }
195 }
195 }
196
196
197 impl<'on_disk> Docket<'on_disk> {
197 impl<'on_disk> Docket<'on_disk> {
198 /// Generate the identifier for a new data file
198 /// Generate the identifier for a new data file
199 ///
199 ///
200 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
200 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
201 /// See `mercurial/revlogutils/docket.py`
201 /// See `mercurial/revlogutils/docket.py`
202 pub fn new_uid() -> String {
202 pub fn new_uid() -> String {
203 const ID_LENGTH: usize = 8;
203 const ID_LENGTH: usize = 8;
204 let mut id = String::with_capacity(ID_LENGTH);
204 let mut id = String::with_capacity(ID_LENGTH);
205 let mut rng = rand::thread_rng();
205 let mut rng = rand::thread_rng();
206 for _ in 0..ID_LENGTH {
206 for _ in 0..ID_LENGTH {
207 // One random hexadecimal digit.
207 // One random hexadecimal digit.
208 // `unwrap` never panics because `impl Write for String`
208 // `unwrap` never panics because `impl Write for String`
209 // never returns an error.
209 // never returns an error.
210 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
210 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
211 }
211 }
212 id
212 id
213 }
213 }
214
214
215 pub fn serialize(
215 pub fn serialize(
216 parents: DirstateParents,
216 parents: DirstateParents,
217 tree_metadata: TreeMetadata,
217 tree_metadata: TreeMetadata,
218 data_size: u64,
218 data_size: u64,
219 uuid: &[u8],
219 uuid: &[u8],
220 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
220 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
221 let header = DocketHeader {
221 let header = DocketHeader {
222 marker: *V2_FORMAT_MARKER,
222 marker: *V2_FORMAT_MARKER,
223 parent_1: parents.p1.pad_to_256_bits(),
223 parent_1: parents.p1.pad_to_256_bits(),
224 parent_2: parents.p2.pad_to_256_bits(),
224 parent_2: parents.p2.pad_to_256_bits(),
225 metadata: tree_metadata,
225 metadata: tree_metadata,
226 data_size: u32::try_from(data_size)?.into(),
226 data_size: u32::try_from(data_size)?.into(),
227 uuid_size: uuid.len().try_into()?,
227 uuid_size: uuid.len().try_into()?,
228 };
228 };
229 let header = header.as_bytes();
229 let header = header.as_bytes();
230 let mut docket = Vec::with_capacity(header.len() + uuid.len());
230 let mut docket = Vec::with_capacity(header.len() + uuid.len());
231 docket.extend_from_slice(header);
231 docket.extend_from_slice(header);
232 docket.extend_from_slice(uuid);
232 docket.extend_from_slice(uuid);
233 Ok(docket)
233 Ok(docket)
234 }
234 }
235
235
236 pub fn parents(&self) -> DirstateParents {
236 pub fn parents(&self) -> DirstateParents {
237 use crate::Node;
237 use crate::Node;
238 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
238 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
239 .unwrap()
239 .unwrap()
240 .clone();
240 .clone();
241 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
241 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
242 .unwrap()
242 .unwrap()
243 .clone();
243 .clone();
244 DirstateParents { p1, p2 }
244 DirstateParents { p1, p2 }
245 }
245 }
246
246
247 pub fn tree_metadata(&self) -> &[u8] {
247 pub fn tree_metadata(&self) -> &[u8] {
248 self.header.metadata.as_bytes()
248 self.header.metadata.as_bytes()
249 }
249 }
250
250
251 pub fn data_size(&self) -> usize {
251 pub fn data_size(&self) -> usize {
252 // This `unwrap` could only panic on a 16-bit CPU
252 // This `unwrap` could only panic on a 16-bit CPU
253 self.header.data_size.get().try_into().unwrap()
253 self.header.data_size.get().try_into().unwrap()
254 }
254 }
255
255
256 pub fn data_filename(&self) -> String {
256 pub fn data_filename(&self) -> String {
257 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
257 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
258 }
258 }
259 }
259 }
260
260
261 pub fn read_docket(
261 pub fn read_docket(
262 on_disk: &[u8],
262 on_disk: &[u8],
263 ) -> Result<Docket<'_>, DirstateV2ParseError> {
263 ) -> Result<Docket<'_>, DirstateV2ParseError> {
264 let (header, uuid) =
264 let (header, uuid) =
265 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
265 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
266 let uuid_size = header.uuid_size as usize;
266 let uuid_size = header.uuid_size as usize;
267 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
267 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
268 Ok(Docket { header, uuid })
268 Ok(Docket { header, uuid })
269 } else {
269 } else {
270 Err(DirstateV2ParseError)
270 Err(DirstateV2ParseError)
271 }
271 }
272 }
272 }
273
273
274 pub(super) fn read<'on_disk>(
274 pub(super) fn read<'on_disk>(
275 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
276 metadata: &[u8],
276 metadata: &[u8],
277 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
277 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
278 if on_disk.is_empty() {
278 if on_disk.is_empty() {
279 return Ok(DirstateMap::empty(on_disk));
279 return Ok(DirstateMap::empty(on_disk));
280 }
280 }
281 let (meta, _) = TreeMetadata::from_bytes(metadata)
281 let (meta, _) = TreeMetadata::from_bytes(metadata)
282 .map_err(|_| DirstateV2ParseError)?;
282 .map_err(|_| DirstateV2ParseError)?;
283 let dirstate_map = DirstateMap {
283 let dirstate_map = DirstateMap {
284 on_disk,
284 on_disk,
285 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
285 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
286 on_disk,
286 on_disk,
287 meta.root_nodes,
287 meta.root_nodes,
288 )?),
288 )?),
289 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
289 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
290 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
290 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
291 ignore_patterns_hash: meta.ignore_patterns_hash,
291 ignore_patterns_hash: meta.ignore_patterns_hash,
292 unreachable_bytes: meta.unreachable_bytes.get(),
292 unreachable_bytes: meta.unreachable_bytes.get(),
293 };
293 };
294 Ok(dirstate_map)
294 Ok(dirstate_map)
295 }
295 }
296
296
297 impl Node {
297 impl Node {
298 pub(super) fn full_path<'on_disk>(
298 pub(super) fn full_path<'on_disk>(
299 &self,
299 &self,
300 on_disk: &'on_disk [u8],
300 on_disk: &'on_disk [u8],
301 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
301 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
302 read_hg_path(on_disk, self.full_path)
302 read_hg_path(on_disk, self.full_path)
303 }
303 }
304
304
305 pub(super) fn base_name_start<'on_disk>(
305 pub(super) fn base_name_start<'on_disk>(
306 &self,
306 &self,
307 ) -> Result<usize, DirstateV2ParseError> {
307 ) -> Result<usize, DirstateV2ParseError> {
308 let start = self.base_name_start.get();
308 let start = self.base_name_start.get();
309 if start < self.full_path.len.get() {
309 if start < self.full_path.len.get() {
310 let start = usize::try_from(start)
310 let start = usize::try_from(start)
311 // u32 -> usize, could only panic on a 16-bit CPU
311 // u32 -> usize, could only panic on a 16-bit CPU
312 .expect("dirstate-v2 base_name_start out of bounds");
312 .expect("dirstate-v2 base_name_start out of bounds");
313 Ok(start)
313 Ok(start)
314 } else {
314 } else {
315 Err(DirstateV2ParseError)
315 Err(DirstateV2ParseError)
316 }
316 }
317 }
317 }
318
318
319 pub(super) fn base_name<'on_disk>(
319 pub(super) fn base_name<'on_disk>(
320 &self,
320 &self,
321 on_disk: &'on_disk [u8],
321 on_disk: &'on_disk [u8],
322 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
322 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
323 let full_path = self.full_path(on_disk)?;
323 let full_path = self.full_path(on_disk)?;
324 let base_name_start = self.base_name_start()?;
324 let base_name_start = self.base_name_start()?;
325 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
325 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
326 }
326 }
327
327
328 pub(super) fn path<'on_disk>(
328 pub(super) fn path<'on_disk>(
329 &self,
329 &self,
330 on_disk: &'on_disk [u8],
330 on_disk: &'on_disk [u8],
331 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
331 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
332 Ok(WithBasename::from_raw_parts(
332 Ok(WithBasename::from_raw_parts(
333 Cow::Borrowed(self.full_path(on_disk)?),
333 Cow::Borrowed(self.full_path(on_disk)?),
334 self.base_name_start()?,
334 self.base_name_start()?,
335 ))
335 ))
336 }
336 }
337
337
338 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
338 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
339 self.copy_source.start.get() != 0
339 self.copy_source.start.get() != 0
340 }
340 }
341
341
342 pub(super) fn copy_source<'on_disk>(
342 pub(super) fn copy_source<'on_disk>(
343 &self,
343 &self,
344 on_disk: &'on_disk [u8],
344 on_disk: &'on_disk [u8],
345 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
345 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
346 Ok(if self.has_copy_source() {
346 Ok(if self.has_copy_source() {
347 Some(read_hg_path(on_disk, self.copy_source)?)
347 Some(read_hg_path(on_disk, self.copy_source)?)
348 } else {
348 } else {
349 None
349 None
350 })
350 })
351 }
351 }
352
352
353 fn flags(&self) -> Flags {
353 fn flags(&self) -> Flags {
354 Flags::from_bits_truncate(self.flags.get())
354 Flags::from_bits_truncate(self.flags.get())
355 }
355 }
356
356
357 fn has_entry(&self) -> bool {
357 fn has_entry(&self) -> bool {
358 self.flags().intersects(
358 self.flags().intersects(
359 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
359 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
360 )
360 )
361 }
361 }
362
362
363 pub(super) fn node_data(
363 pub(super) fn node_data(
364 &self,
364 &self,
365 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
365 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
366 if self.has_entry() {
366 if self.has_entry() {
367 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
367 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
368 } else if let Some(mtime) = self.cached_directory_mtime()? {
368 } else if let Some(mtime) = self.cached_directory_mtime()? {
369 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
369 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
370 } else {
370 } else {
371 Ok(dirstate_map::NodeData::None)
371 Ok(dirstate_map::NodeData::None)
372 }
372 }
373 }
373 }
374
374
375 pub(super) fn cached_directory_mtime(
375 pub(super) fn cached_directory_mtime(
376 &self,
376 &self,
377 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
377 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
378 // For now we do not have code to handle the absence of
378 // For now we do not have code to handle the absence of
379 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
379 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
380 // unset.
380 // unset.
381 if self.flags().contains(Flags::DIRECTORY)
381 if self.flags().contains(Flags::DIRECTORY)
382 && self.flags().contains(Flags::HAS_MTIME)
382 && self.flags().contains(Flags::HAS_MTIME)
383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
384 {
384 {
385 Ok(Some(self.mtime()?))
385 Ok(Some(self.mtime()?))
386 } else {
386 } else {
387 Ok(None)
387 Ok(None)
388 }
388 }
389 }
389 }
390
390
391 fn synthesize_unix_mode(&self) -> u32 {
391 fn synthesize_unix_mode(&self) -> u32 {
392 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
392 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
393 libc::S_IFLNK
393 libc::S_IFLNK
394 } else {
394 } else {
395 libc::S_IFREG
395 libc::S_IFREG
396 };
396 };
397 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
397 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
398 0o755
398 0o755
399 } else {
399 } else {
400 0o644
400 0o644
401 };
401 };
402 (file_type | permisions).into()
402 (file_type | permisions).into()
403 }
403 }
404
404
405 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
405 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
406 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
406 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
407 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
407 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
408 m.second_ambiguous = true;
408 m.second_ambiguous = true;
409 }
409 }
410 Ok(m)
410 Ok(m)
411 }
411 }
412
412
413 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
413 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
414 // TODO: convert through raw bits instead?
414 // TODO: convert through raw bits instead?
415 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
415 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
416 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
416 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
417 let p2_info = self.flags().contains(Flags::P2_INFO);
417 let p2_info = self.flags().contains(Flags::P2_INFO);
418 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
418 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
419 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
419 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
420 {
420 {
421 Some((self.synthesize_unix_mode(), self.size.into()))
421 Some((self.synthesize_unix_mode(), self.size.into()))
422 } else {
422 } else {
423 None
423 None
424 };
424 };
425 let mtime = if self.flags().contains(Flags::HAS_MTIME)
425 let mtime = if self.flags().contains(Flags::HAS_MTIME)
426 && !self.flags().contains(Flags::DIRECTORY)
426 && !self.flags().contains(Flags::DIRECTORY)
427 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
427 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
428 {
428 {
429 Some(self.mtime()?)
429 Some(self.mtime()?)
430 } else {
430 } else {
431 None
431 None
432 };
432 };
433 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
433 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
434 {
434 {
435 Some(self.flags().contains(Flags::FALLBACK_EXEC))
435 Some(self.flags().contains(Flags::FALLBACK_EXEC))
436 } else {
436 } else {
437 None
437 None
438 };
438 };
439 let fallback_symlink =
439 let fallback_symlink =
440 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
440 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
441 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
441 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
442 } else {
442 } else {
443 None
443 None
444 };
444 };
445 Ok(DirstateEntry::from_v2_data(
445 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
446 wdir_tracked,
446 wc_tracked,
447 p1_tracked,
447 p1_tracked,
448 p2_info,
448 p2_info,
449 mode_size,
449 mode_size,
450 mtime,
450 mtime,
451 fallback_exec,
451 fallback_exec,
452 fallback_symlink,
452 fallback_symlink,
453 ))
453 }))
454 }
454 }
455
455
456 pub(super) fn entry(
456 pub(super) fn entry(
457 &self,
457 &self,
458 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
458 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
459 if self.has_entry() {
459 if self.has_entry() {
460 Ok(Some(self.assume_entry()?))
460 Ok(Some(self.assume_entry()?))
461 } else {
461 } else {
462 Ok(None)
462 Ok(None)
463 }
463 }
464 }
464 }
465
465
466 pub(super) fn children<'on_disk>(
466 pub(super) fn children<'on_disk>(
467 &self,
467 &self,
468 on_disk: &'on_disk [u8],
468 on_disk: &'on_disk [u8],
469 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
469 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
470 read_nodes(on_disk, self.children)
470 read_nodes(on_disk, self.children)
471 }
471 }
472
472
473 pub(super) fn to_in_memory_node<'on_disk>(
473 pub(super) fn to_in_memory_node<'on_disk>(
474 &self,
474 &self,
475 on_disk: &'on_disk [u8],
475 on_disk: &'on_disk [u8],
476 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
476 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
477 Ok(dirstate_map::Node {
477 Ok(dirstate_map::Node {
478 children: dirstate_map::ChildNodes::OnDisk(
478 children: dirstate_map::ChildNodes::OnDisk(
479 self.children(on_disk)?,
479 self.children(on_disk)?,
480 ),
480 ),
481 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
481 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
482 data: self.node_data()?,
482 data: self.node_data()?,
483 descendants_with_entry_count: self
483 descendants_with_entry_count: self
484 .descendants_with_entry_count
484 .descendants_with_entry_count
485 .get(),
485 .get(),
486 tracked_descendants_count: self.tracked_descendants_count.get(),
486 tracked_descendants_count: self.tracked_descendants_count.get(),
487 })
487 })
488 }
488 }
489
489
490 fn from_dirstate_entry(
490 fn from_dirstate_entry(
491 entry: &DirstateEntry,
491 entry: &DirstateEntry,
492 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
492 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
493 let (
493 let DirstateV2Data {
494 wdir_tracked,
494 wc_tracked,
495 p1_tracked,
495 p1_tracked,
496 p2_info,
496 p2_info,
497 mode_size_opt,
497 mode_size: mode_size_opt,
498 mtime_opt,
498 mtime: mtime_opt,
499 fallback_exec,
499 fallback_exec,
500 fallback_symlink,
500 fallback_symlink,
501 ) = entry.v2_data();
501 } = entry.v2_data();
502 // TODO: convert throug raw flag bits instead?
502 // TODO: convert through raw flag bits instead?
503 let mut flags = Flags::empty();
503 let mut flags = Flags::empty();
504 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
504 flags.set(Flags::WDIR_TRACKED, wc_tracked);
505 flags.set(Flags::P1_TRACKED, p1_tracked);
505 flags.set(Flags::P1_TRACKED, p1_tracked);
506 flags.set(Flags::P2_INFO, p2_info);
506 flags.set(Flags::P2_INFO, p2_info);
507 let size = if let Some((m, s)) = mode_size_opt {
507 let size = if let Some((m, s)) = mode_size_opt {
508 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
508 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
509 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
509 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
510 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
510 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
511 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
511 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
512 flags.insert(Flags::HAS_MODE_AND_SIZE);
512 flags.insert(Flags::HAS_MODE_AND_SIZE);
513 s.into()
513 s.into()
514 } else {
514 } else {
515 0.into()
515 0.into()
516 };
516 };
517 let mtime = if let Some(m) = mtime_opt {
517 let mtime = if let Some(m) = mtime_opt {
518 flags.insert(Flags::HAS_MTIME);
518 flags.insert(Flags::HAS_MTIME);
519 if m.second_ambiguous {
519 if m.second_ambiguous {
520 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
520 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
521 };
521 };
522 m.into()
522 m.into()
523 } else {
523 } else {
524 PackedTruncatedTimestamp::null()
524 PackedTruncatedTimestamp::null()
525 };
525 };
526 if let Some(f_exec) = fallback_exec {
526 if let Some(f_exec) = fallback_exec {
527 flags.insert(Flags::HAS_FALLBACK_EXEC);
527 flags.insert(Flags::HAS_FALLBACK_EXEC);
528 if f_exec {
528 if f_exec {
529 flags.insert(Flags::FALLBACK_EXEC);
529 flags.insert(Flags::FALLBACK_EXEC);
530 }
530 }
531 }
531 }
532 if let Some(f_symlink) = fallback_symlink {
532 if let Some(f_symlink) = fallback_symlink {
533 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
533 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
534 if f_symlink {
534 if f_symlink {
535 flags.insert(Flags::FALLBACK_SYMLINK);
535 flags.insert(Flags::FALLBACK_SYMLINK);
536 }
536 }
537 }
537 }
538 (flags, size, mtime)
538 (flags, size, mtime)
539 }
539 }
540 }
540 }
541
541
542 fn read_hg_path(
542 fn read_hg_path(
543 on_disk: &[u8],
543 on_disk: &[u8],
544 slice: PathSlice,
544 slice: PathSlice,
545 ) -> Result<&HgPath, DirstateV2ParseError> {
545 ) -> Result<&HgPath, DirstateV2ParseError> {
546 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
546 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
547 }
547 }
548
548
549 fn read_nodes(
549 fn read_nodes(
550 on_disk: &[u8],
550 on_disk: &[u8],
551 slice: ChildNodes,
551 slice: ChildNodes,
552 ) -> Result<&[Node], DirstateV2ParseError> {
552 ) -> Result<&[Node], DirstateV2ParseError> {
553 read_slice(on_disk, slice.start, slice.len.get())
553 read_slice(on_disk, slice.start, slice.len.get())
554 }
554 }
555
555
556 fn read_slice<T, Len>(
556 fn read_slice<T, Len>(
557 on_disk: &[u8],
557 on_disk: &[u8],
558 start: Offset,
558 start: Offset,
559 len: Len,
559 len: Len,
560 ) -> Result<&[T], DirstateV2ParseError>
560 ) -> Result<&[T], DirstateV2ParseError>
561 where
561 where
562 T: BytesCast,
562 T: BytesCast,
563 Len: TryInto<usize>,
563 Len: TryInto<usize>,
564 {
564 {
565 // Either `usize::MAX` would result in "out of bounds" error since a single
565 // Either `usize::MAX` would result in "out of bounds" error since a single
566 // `&[u8]` cannot occupy the entire addess space.
566 // `&[u8]` cannot occupy the entire addess space.
567 let start = start.get().try_into().unwrap_or(std::usize::MAX);
567 let start = start.get().try_into().unwrap_or(std::usize::MAX);
568 let len = len.try_into().unwrap_or(std::usize::MAX);
568 let len = len.try_into().unwrap_or(std::usize::MAX);
569 on_disk
569 on_disk
570 .get(start..)
570 .get(start..)
571 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
571 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
572 .map(|(slice, _rest)| slice)
572 .map(|(slice, _rest)| slice)
573 .ok_or_else(|| DirstateV2ParseError)
573 .ok_or_else(|| DirstateV2ParseError)
574 }
574 }
575
575
576 pub(crate) fn for_each_tracked_path<'on_disk>(
576 pub(crate) fn for_each_tracked_path<'on_disk>(
577 on_disk: &'on_disk [u8],
577 on_disk: &'on_disk [u8],
578 metadata: &[u8],
578 metadata: &[u8],
579 mut f: impl FnMut(&'on_disk HgPath),
579 mut f: impl FnMut(&'on_disk HgPath),
580 ) -> Result<(), DirstateV2ParseError> {
580 ) -> Result<(), DirstateV2ParseError> {
581 let (meta, _) = TreeMetadata::from_bytes(metadata)
581 let (meta, _) = TreeMetadata::from_bytes(metadata)
582 .map_err(|_| DirstateV2ParseError)?;
582 .map_err(|_| DirstateV2ParseError)?;
583 fn recur<'on_disk>(
583 fn recur<'on_disk>(
584 on_disk: &'on_disk [u8],
584 on_disk: &'on_disk [u8],
585 nodes: ChildNodes,
585 nodes: ChildNodes,
586 f: &mut impl FnMut(&'on_disk HgPath),
586 f: &mut impl FnMut(&'on_disk HgPath),
587 ) -> Result<(), DirstateV2ParseError> {
587 ) -> Result<(), DirstateV2ParseError> {
588 for node in read_nodes(on_disk, nodes)? {
588 for node in read_nodes(on_disk, nodes)? {
589 if let Some(entry) = node.entry()? {
589 if let Some(entry) = node.entry()? {
590 if entry.state().is_tracked() {
590 if entry.state().is_tracked() {
591 f(node.full_path(on_disk)?)
591 f(node.full_path(on_disk)?)
592 }
592 }
593 }
593 }
594 recur(on_disk, node.children, f)?
594 recur(on_disk, node.children, f)?
595 }
595 }
596 Ok(())
596 Ok(())
597 }
597 }
598 recur(on_disk, meta.root_nodes, &mut f)
598 recur(on_disk, meta.root_nodes, &mut f)
599 }
599 }
600
600
601 /// Returns new data and metadata, together with whether that data should be
601 /// Returns new data and metadata, together with whether that data should be
602 /// appended to the existing data file whose content is at
602 /// appended to the existing data file whose content is at
603 /// `dirstate_map.on_disk` (true), instead of written to a new data file
603 /// `dirstate_map.on_disk` (true), instead of written to a new data file
604 /// (false).
604 /// (false).
605 pub(super) fn write(
605 pub(super) fn write(
606 dirstate_map: &DirstateMap,
606 dirstate_map: &DirstateMap,
607 can_append: bool,
607 can_append: bool,
608 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
608 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
609 let append = can_append && dirstate_map.write_should_append();
609 let append = can_append && dirstate_map.write_should_append();
610
610
611 // This ignores the space for paths, and for nodes without an entry.
611 // This ignores the space for paths, and for nodes without an entry.
612 // TODO: better estimate? Skip the `Vec` and write to a file directly?
612 // TODO: better estimate? Skip the `Vec` and write to a file directly?
613 let size_guess = std::mem::size_of::<Node>()
613 let size_guess = std::mem::size_of::<Node>()
614 * dirstate_map.nodes_with_entry_count as usize;
614 * dirstate_map.nodes_with_entry_count as usize;
615
615
616 let mut writer = Writer {
616 let mut writer = Writer {
617 dirstate_map,
617 dirstate_map,
618 append,
618 append,
619 out: Vec::with_capacity(size_guess),
619 out: Vec::with_capacity(size_guess),
620 };
620 };
621
621
622 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
622 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
623
623
624 let meta = TreeMetadata {
624 let meta = TreeMetadata {
625 root_nodes,
625 root_nodes,
626 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
626 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
627 nodes_with_copy_source_count: dirstate_map
627 nodes_with_copy_source_count: dirstate_map
628 .nodes_with_copy_source_count
628 .nodes_with_copy_source_count
629 .into(),
629 .into(),
630 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
630 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
631 unused: [0; 4],
631 unused: [0; 4],
632 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
632 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
633 };
633 };
634 Ok((writer.out, meta, append))
634 Ok((writer.out, meta, append))
635 }
635 }
636
636
637 struct Writer<'dmap, 'on_disk> {
637 struct Writer<'dmap, 'on_disk> {
638 dirstate_map: &'dmap DirstateMap<'on_disk>,
638 dirstate_map: &'dmap DirstateMap<'on_disk>,
639 append: bool,
639 append: bool,
640 out: Vec<u8>,
640 out: Vec<u8>,
641 }
641 }
642
642
643 impl Writer<'_, '_> {
643 impl Writer<'_, '_> {
644 fn write_nodes(
644 fn write_nodes(
645 &mut self,
645 &mut self,
646 nodes: dirstate_map::ChildNodesRef,
646 nodes: dirstate_map::ChildNodesRef,
647 ) -> Result<ChildNodes, DirstateError> {
647 ) -> Result<ChildNodes, DirstateError> {
648 // Reuse already-written nodes if possible
648 // Reuse already-written nodes if possible
649 if self.append {
649 if self.append {
650 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
650 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
651 let start = self.on_disk_offset_of(nodes_slice).expect(
651 let start = self.on_disk_offset_of(nodes_slice).expect(
652 "dirstate-v2 OnDisk nodes not found within on_disk",
652 "dirstate-v2 OnDisk nodes not found within on_disk",
653 );
653 );
654 let len = child_nodes_len_from_usize(nodes_slice.len());
654 let len = child_nodes_len_from_usize(nodes_slice.len());
655 return Ok(ChildNodes { start, len });
655 return Ok(ChildNodes { start, len });
656 }
656 }
657 }
657 }
658
658
659 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
659 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
660 // undefined iteration order. Sort to enable binary search in the
660 // undefined iteration order. Sort to enable binary search in the
661 // written file.
661 // written file.
662 let nodes = nodes.sorted();
662 let nodes = nodes.sorted();
663 let nodes_len = nodes.len();
663 let nodes_len = nodes.len();
664
664
665 // First accumulate serialized nodes in a `Vec`
665 // First accumulate serialized nodes in a `Vec`
666 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
666 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
667 for node in nodes {
667 for node in nodes {
668 let children =
668 let children =
669 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
669 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
670 let full_path = node.full_path(self.dirstate_map.on_disk)?;
670 let full_path = node.full_path(self.dirstate_map.on_disk)?;
671 let full_path = self.write_path(full_path.as_bytes());
671 let full_path = self.write_path(full_path.as_bytes());
672 let copy_source = if let Some(source) =
672 let copy_source = if let Some(source) =
673 node.copy_source(self.dirstate_map.on_disk)?
673 node.copy_source(self.dirstate_map.on_disk)?
674 {
674 {
675 self.write_path(source.as_bytes())
675 self.write_path(source.as_bytes())
676 } else {
676 } else {
677 PathSlice {
677 PathSlice {
678 start: 0.into(),
678 start: 0.into(),
679 len: 0.into(),
679 len: 0.into(),
680 }
680 }
681 };
681 };
682 on_disk_nodes.push(match node {
682 on_disk_nodes.push(match node {
683 NodeRef::InMemory(path, node) => {
683 NodeRef::InMemory(path, node) => {
684 let (flags, size, mtime) = match &node.data {
684 let (flags, size, mtime) = match &node.data {
685 dirstate_map::NodeData::Entry(entry) => {
685 dirstate_map::NodeData::Entry(entry) => {
686 Node::from_dirstate_entry(entry)
686 Node::from_dirstate_entry(entry)
687 }
687 }
688 dirstate_map::NodeData::CachedDirectory { mtime } => {
688 dirstate_map::NodeData::CachedDirectory { mtime } => {
689 // we currently never set a mtime if unknown file
689 // we currently never set a mtime if unknown file
690 // are present.
690 // are present.
691 // So if we have a mtime for a directory, we know
691 // So if we have a mtime for a directory, we know
692 // they are no unknown
692 // they are no unknown
693 // files and we
693 // files and we
694 // blindly set ALL_UNKNOWN_RECORDED.
694 // blindly set ALL_UNKNOWN_RECORDED.
695 //
695 //
696 // We never set ALL_IGNORED_RECORDED since we
696 // We never set ALL_IGNORED_RECORDED since we
697 // don't track that case
697 // don't track that case
698 // currently.
698 // currently.
699 let mut flags = Flags::DIRECTORY
699 let mut flags = Flags::DIRECTORY
700 | Flags::HAS_MTIME
700 | Flags::HAS_MTIME
701 | Flags::ALL_UNKNOWN_RECORDED;
701 | Flags::ALL_UNKNOWN_RECORDED;
702 if mtime.second_ambiguous {
702 if mtime.second_ambiguous {
703 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
703 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
704 }
704 }
705 (flags, 0.into(), (*mtime).into())
705 (flags, 0.into(), (*mtime).into())
706 }
706 }
707 dirstate_map::NodeData::None => (
707 dirstate_map::NodeData::None => (
708 Flags::DIRECTORY,
708 Flags::DIRECTORY,
709 0.into(),
709 0.into(),
710 PackedTruncatedTimestamp::null(),
710 PackedTruncatedTimestamp::null(),
711 ),
711 ),
712 };
712 };
713 Node {
713 Node {
714 children,
714 children,
715 copy_source,
715 copy_source,
716 full_path,
716 full_path,
717 base_name_start: u16::try_from(path.base_name_start())
717 base_name_start: u16::try_from(path.base_name_start())
718 // Could only panic for paths over 64 KiB
718 // Could only panic for paths over 64 KiB
719 .expect("dirstate-v2 path length overflow")
719 .expect("dirstate-v2 path length overflow")
720 .into(),
720 .into(),
721 descendants_with_entry_count: node
721 descendants_with_entry_count: node
722 .descendants_with_entry_count
722 .descendants_with_entry_count
723 .into(),
723 .into(),
724 tracked_descendants_count: node
724 tracked_descendants_count: node
725 .tracked_descendants_count
725 .tracked_descendants_count
726 .into(),
726 .into(),
727 flags: flags.bits().into(),
727 flags: flags.bits().into(),
728 size,
728 size,
729 mtime,
729 mtime,
730 }
730 }
731 }
731 }
732 NodeRef::OnDisk(node) => Node {
732 NodeRef::OnDisk(node) => Node {
733 children,
733 children,
734 copy_source,
734 copy_source,
735 full_path,
735 full_path,
736 ..*node
736 ..*node
737 },
737 },
738 })
738 })
739 }
739 }
740 // … so we can write them contiguously, after writing everything else
740 // … so we can write them contiguously, after writing everything else
741 // they refer to.
741 // they refer to.
742 let start = self.current_offset();
742 let start = self.current_offset();
743 let len = child_nodes_len_from_usize(nodes_len);
743 let len = child_nodes_len_from_usize(nodes_len);
744 self.out.extend(on_disk_nodes.as_bytes());
744 self.out.extend(on_disk_nodes.as_bytes());
745 Ok(ChildNodes { start, len })
745 Ok(ChildNodes { start, len })
746 }
746 }
747
747
748 /// If the given slice of items is within `on_disk`, returns its offset
748 /// If the given slice of items is within `on_disk`, returns its offset
749 /// from the start of `on_disk`.
749 /// from the start of `on_disk`.
750 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
750 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
751 where
751 where
752 T: BytesCast,
752 T: BytesCast,
753 {
753 {
754 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
754 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
755 let start = slice.as_ptr() as usize;
755 let start = slice.as_ptr() as usize;
756 let end = start + slice.len();
756 let end = start + slice.len();
757 start..=end
757 start..=end
758 }
758 }
759 let slice_addresses = address_range(slice.as_bytes());
759 let slice_addresses = address_range(slice.as_bytes());
760 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
760 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
761 if on_disk_addresses.contains(slice_addresses.start())
761 if on_disk_addresses.contains(slice_addresses.start())
762 && on_disk_addresses.contains(slice_addresses.end())
762 && on_disk_addresses.contains(slice_addresses.end())
763 {
763 {
764 let offset = slice_addresses.start() - on_disk_addresses.start();
764 let offset = slice_addresses.start() - on_disk_addresses.start();
765 Some(offset_from_usize(offset))
765 Some(offset_from_usize(offset))
766 } else {
766 } else {
767 None
767 None
768 }
768 }
769 }
769 }
770
770
771 fn current_offset(&mut self) -> Offset {
771 fn current_offset(&mut self) -> Offset {
772 let mut offset = self.out.len();
772 let mut offset = self.out.len();
773 if self.append {
773 if self.append {
774 offset += self.dirstate_map.on_disk.len()
774 offset += self.dirstate_map.on_disk.len()
775 }
775 }
776 offset_from_usize(offset)
776 offset_from_usize(offset)
777 }
777 }
778
778
779 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
779 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
780 let len = path_len_from_usize(slice.len());
780 let len = path_len_from_usize(slice.len());
781 // Reuse an already-written path if possible
781 // Reuse an already-written path if possible
782 if self.append {
782 if self.append {
783 if let Some(start) = self.on_disk_offset_of(slice) {
783 if let Some(start) = self.on_disk_offset_of(slice) {
784 return PathSlice { start, len };
784 return PathSlice { start, len };
785 }
785 }
786 }
786 }
787 let start = self.current_offset();
787 let start = self.current_offset();
788 self.out.extend(slice.as_bytes());
788 self.out.extend(slice.as_bytes());
789 PathSlice { start, len }
789 PathSlice { start, len }
790 }
790 }
791 }
791 }
792
792
793 fn offset_from_usize(x: usize) -> Offset {
793 fn offset_from_usize(x: usize) -> Offset {
794 u32::try_from(x)
794 u32::try_from(x)
795 // Could only panic for a dirstate file larger than 4 GiB
795 // Could only panic for a dirstate file larger than 4 GiB
796 .expect("dirstate-v2 offset overflow")
796 .expect("dirstate-v2 offset overflow")
797 .into()
797 .into()
798 }
798 }
799
799
800 fn child_nodes_len_from_usize(x: usize) -> Size {
800 fn child_nodes_len_from_usize(x: usize) -> Size {
801 u32::try_from(x)
801 u32::try_from(x)
802 // Could only panic with over 4 billion nodes
802 // Could only panic with over 4 billion nodes
803 .expect("dirstate-v2 slice length overflow")
803 .expect("dirstate-v2 slice length overflow")
804 .into()
804 .into()
805 }
805 }
806
806
807 fn path_len_from_usize(x: usize) -> PathSize {
807 fn path_len_from_usize(x: usize) -> PathSize {
808 u16::try_from(x)
808 u16::try_from(x)
809 // Could only panic for paths over 64 KiB
809 // Could only panic for paths over 64 KiB
810 .expect("dirstate-v2 path length overflow")
810 .expect("dirstate-v2 path length overflow")
811 .into()
811 .into()
812 }
812 }
813
813
814 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
814 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
815 fn from(timestamp: TruncatedTimestamp) -> Self {
815 fn from(timestamp: TruncatedTimestamp) -> Self {
816 Self {
816 Self {
817 truncated_seconds: timestamp.truncated_seconds().into(),
817 truncated_seconds: timestamp.truncated_seconds().into(),
818 nanoseconds: timestamp.nanoseconds().into(),
818 nanoseconds: timestamp.nanoseconds().into(),
819 }
819 }
820 }
820 }
821 }
821 }
822
822
823 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
823 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
824 type Error = DirstateV2ParseError;
824 type Error = DirstateV2ParseError;
825
825
826 fn try_from(
826 fn try_from(
827 timestamp: PackedTruncatedTimestamp,
827 timestamp: PackedTruncatedTimestamp,
828 ) -> Result<Self, Self::Error> {
828 ) -> Result<Self, Self::Error> {
829 Self::from_already_truncated(
829 Self::from_already_truncated(
830 timestamp.truncated_seconds.get(),
830 timestamp.truncated_seconds.get(),
831 timestamp.nanoseconds.get(),
831 timestamp.nanoseconds.get(),
832 false,
832 false,
833 )
833 )
834 }
834 }
835 }
835 }
836 impl PackedTruncatedTimestamp {
836 impl PackedTruncatedTimestamp {
837 fn null() -> Self {
837 fn null() -> Self {
838 Self {
838 Self {
839 truncated_seconds: 0.into(),
839 truncated_seconds: 0.into(),
840 nanoseconds: 0.into(),
840 nanoseconds: 0.into(),
841 }
841 }
842 }
842 }
843 }
843 }
@@ -1,246 +1,247 b''
1 use cpython::exc;
1 use cpython::exc;
2 use cpython::ObjectProtocol;
2 use cpython::ObjectProtocol;
3 use cpython::PyBytes;
3 use cpython::PyBytes;
4 use cpython::PyErr;
4 use cpython::PyErr;
5 use cpython::PyNone;
5 use cpython::PyNone;
6 use cpython::PyObject;
6 use cpython::PyObject;
7 use cpython::PyResult;
7 use cpython::PyResult;
8 use cpython::Python;
8 use cpython::Python;
9 use cpython::PythonObject;
9 use cpython::PythonObject;
10 use hg::dirstate::DirstateEntry;
10 use hg::dirstate::DirstateEntry;
11 use hg::dirstate::DirstateV2Data;
11 use hg::dirstate::TruncatedTimestamp;
12 use hg::dirstate::TruncatedTimestamp;
12 use std::cell::Cell;
13 use std::cell::Cell;
13
14
14 py_class!(pub class DirstateItem |py| {
15 py_class!(pub class DirstateItem |py| {
15 data entry: Cell<DirstateEntry>;
16 data entry: Cell<DirstateEntry>;
16
17
17 def __new__(
18 def __new__(
18 _cls,
19 _cls,
19 wc_tracked: bool = false,
20 wc_tracked: bool = false,
20 p1_tracked: bool = false,
21 p1_tracked: bool = false,
21 p2_info: bool = false,
22 p2_info: bool = false,
22 has_meaningful_data: bool = true,
23 has_meaningful_data: bool = true,
23 has_meaningful_mtime: bool = true,
24 has_meaningful_mtime: bool = true,
24 parentfiledata: Option<(u32, u32, Option<(u32, u32, bool)>)> = None,
25 parentfiledata: Option<(u32, u32, Option<(u32, u32, bool)>)> = None,
25 fallback_exec: Option<bool> = None,
26 fallback_exec: Option<bool> = None,
26 fallback_symlink: Option<bool> = None,
27 fallback_symlink: Option<bool> = None,
27
28
28 ) -> PyResult<DirstateItem> {
29 ) -> PyResult<DirstateItem> {
29 let mut mode_size_opt = None;
30 let mut mode_size_opt = None;
30 let mut mtime_opt = None;
31 let mut mtime_opt = None;
31 if let Some((mode, size, mtime)) = parentfiledata {
32 if let Some((mode, size, mtime)) = parentfiledata {
32 if has_meaningful_data {
33 if has_meaningful_data {
33 mode_size_opt = Some((mode, size))
34 mode_size_opt = Some((mode, size))
34 }
35 }
35 if has_meaningful_mtime {
36 if has_meaningful_mtime {
36 if let Some(m) = mtime {
37 if let Some(m) = mtime {
37 mtime_opt = Some(timestamp(py, m)?);
38 mtime_opt = Some(timestamp(py, m)?);
38 }
39 }
39 }
40 }
40 }
41 }
41 let entry = DirstateEntry::from_v2_data(
42 let entry = DirstateEntry::from_v2_data(DirstateV2Data {
42 wc_tracked,
43 wc_tracked: wc_tracked,
43 p1_tracked,
44 p1_tracked,
44 p2_info,
45 p2_info,
45 mode_size_opt,
46 mode_size: mode_size_opt,
46 mtime_opt,
47 mtime: mtime_opt,
47 fallback_exec,
48 fallback_exec,
48 fallback_symlink,
49 fallback_symlink,
49 );
50 });
50 DirstateItem::create_instance(py, Cell::new(entry))
51 DirstateItem::create_instance(py, Cell::new(entry))
51 }
52 }
52
53
53 @property
54 @property
54 def state(&self) -> PyResult<PyBytes> {
55 def state(&self) -> PyResult<PyBytes> {
55 let state_byte: u8 = self.entry(py).get().state().into();
56 let state_byte: u8 = self.entry(py).get().state().into();
56 Ok(PyBytes::new(py, &[state_byte]))
57 Ok(PyBytes::new(py, &[state_byte]))
57 }
58 }
58
59
59 @property
60 @property
60 def mode(&self) -> PyResult<i32> {
61 def mode(&self) -> PyResult<i32> {
61 Ok(self.entry(py).get().mode())
62 Ok(self.entry(py).get().mode())
62 }
63 }
63
64
64 @property
65 @property
65 def size(&self) -> PyResult<i32> {
66 def size(&self) -> PyResult<i32> {
66 Ok(self.entry(py).get().size())
67 Ok(self.entry(py).get().size())
67 }
68 }
68
69
69 @property
70 @property
70 def mtime(&self) -> PyResult<i32> {
71 def mtime(&self) -> PyResult<i32> {
71 Ok(self.entry(py).get().mtime())
72 Ok(self.entry(py).get().mtime())
72 }
73 }
73
74
74 @property
75 @property
75 def has_fallback_exec(&self) -> PyResult<bool> {
76 def has_fallback_exec(&self) -> PyResult<bool> {
76 match self.entry(py).get().get_fallback_exec() {
77 match self.entry(py).get().get_fallback_exec() {
77 Some(_) => Ok(true),
78 Some(_) => Ok(true),
78 None => Ok(false),
79 None => Ok(false),
79 }
80 }
80 }
81 }
81
82
82 @property
83 @property
83 def fallback_exec(&self) -> PyResult<Option<bool>> {
84 def fallback_exec(&self) -> PyResult<Option<bool>> {
84 match self.entry(py).get().get_fallback_exec() {
85 match self.entry(py).get().get_fallback_exec() {
85 Some(exec) => Ok(Some(exec)),
86 Some(exec) => Ok(Some(exec)),
86 None => Ok(None),
87 None => Ok(None),
87 }
88 }
88 }
89 }
89
90
90 @fallback_exec.setter
91 @fallback_exec.setter
91 def set_fallback_exec(&self, value: Option<PyObject>) -> PyResult<()> {
92 def set_fallback_exec(&self, value: Option<PyObject>) -> PyResult<()> {
92 match value {
93 match value {
93 None => {self.entry(py).get().set_fallback_exec(None);},
94 None => {self.entry(py).get().set_fallback_exec(None);},
94 Some(value) => {
95 Some(value) => {
95 if value.is_none(py) {
96 if value.is_none(py) {
96 self.entry(py).get().set_fallback_exec(None);
97 self.entry(py).get().set_fallback_exec(None);
97 } else {
98 } else {
98 self.entry(py).get().set_fallback_exec(
99 self.entry(py).get().set_fallback_exec(
99 Some(value.is_true(py)?)
100 Some(value.is_true(py)?)
100 );
101 );
101 }},
102 }},
102 }
103 }
103 Ok(())
104 Ok(())
104 }
105 }
105
106
106 @property
107 @property
107 def has_fallback_symlink(&self) -> PyResult<bool> {
108 def has_fallback_symlink(&self) -> PyResult<bool> {
108 match self.entry(py).get().get_fallback_symlink() {
109 match self.entry(py).get().get_fallback_symlink() {
109 Some(_) => Ok(true),
110 Some(_) => Ok(true),
110 None => Ok(false),
111 None => Ok(false),
111 }
112 }
112 }
113 }
113
114
114 @property
115 @property
115 def fallback_symlink(&self) -> PyResult<Option<bool>> {
116 def fallback_symlink(&self) -> PyResult<Option<bool>> {
116 match self.entry(py).get().get_fallback_symlink() {
117 match self.entry(py).get().get_fallback_symlink() {
117 Some(symlink) => Ok(Some(symlink)),
118 Some(symlink) => Ok(Some(symlink)),
118 None => Ok(None),
119 None => Ok(None),
119 }
120 }
120 }
121 }
121
122
122 @fallback_symlink.setter
123 @fallback_symlink.setter
123 def set_fallback_symlink(&self, value: Option<PyObject>) -> PyResult<()> {
124 def set_fallback_symlink(&self, value: Option<PyObject>) -> PyResult<()> {
124 match value {
125 match value {
125 None => {self.entry(py).get().set_fallback_symlink(None);},
126 None => {self.entry(py).get().set_fallback_symlink(None);},
126 Some(value) => {
127 Some(value) => {
127 if value.is_none(py) {
128 if value.is_none(py) {
128 self.entry(py).get().set_fallback_symlink(None);
129 self.entry(py).get().set_fallback_symlink(None);
129 } else {
130 } else {
130 self.entry(py).get().set_fallback_symlink(
131 self.entry(py).get().set_fallback_symlink(
131 Some(value.is_true(py)?)
132 Some(value.is_true(py)?)
132 );
133 );
133 }},
134 }},
134 }
135 }
135 Ok(())
136 Ok(())
136 }
137 }
137
138
138 @property
139 @property
139 def tracked(&self) -> PyResult<bool> {
140 def tracked(&self) -> PyResult<bool> {
140 Ok(self.entry(py).get().tracked())
141 Ok(self.entry(py).get().tracked())
141 }
142 }
142
143
143 @property
144 @property
144 def p1_tracked(&self) -> PyResult<bool> {
145 def p1_tracked(&self) -> PyResult<bool> {
145 Ok(self.entry(py).get().p1_tracked())
146 Ok(self.entry(py).get().p1_tracked())
146 }
147 }
147
148
148 @property
149 @property
149 def added(&self) -> PyResult<bool> {
150 def added(&self) -> PyResult<bool> {
150 Ok(self.entry(py).get().added())
151 Ok(self.entry(py).get().added())
151 }
152 }
152
153
153
154
154 @property
155 @property
155 def p2_info(&self) -> PyResult<bool> {
156 def p2_info(&self) -> PyResult<bool> {
156 Ok(self.entry(py).get().p2_info())
157 Ok(self.entry(py).get().p2_info())
157 }
158 }
158
159
159 @property
160 @property
160 def removed(&self) -> PyResult<bool> {
161 def removed(&self) -> PyResult<bool> {
161 Ok(self.entry(py).get().removed())
162 Ok(self.entry(py).get().removed())
162 }
163 }
163
164
164 @property
165 @property
165 def maybe_clean(&self) -> PyResult<bool> {
166 def maybe_clean(&self) -> PyResult<bool> {
166 Ok(self.entry(py).get().maybe_clean())
167 Ok(self.entry(py).get().maybe_clean())
167 }
168 }
168
169
169 @property
170 @property
170 def any_tracked(&self) -> PyResult<bool> {
171 def any_tracked(&self) -> PyResult<bool> {
171 Ok(self.entry(py).get().any_tracked())
172 Ok(self.entry(py).get().any_tracked())
172 }
173 }
173
174
174 def mtime_likely_equal_to(&self, other: (u32, u32, bool))
175 def mtime_likely_equal_to(&self, other: (u32, u32, bool))
175 -> PyResult<bool> {
176 -> PyResult<bool> {
176 if let Some(mtime) = self.entry(py).get().truncated_mtime() {
177 if let Some(mtime) = self.entry(py).get().truncated_mtime() {
177 Ok(mtime.likely_equal(timestamp(py, other)?))
178 Ok(mtime.likely_equal(timestamp(py, other)?))
178 } else {
179 } else {
179 Ok(false)
180 Ok(false)
180 }
181 }
181 }
182 }
182
183
183 def drop_merge_data(&self) -> PyResult<PyNone> {
184 def drop_merge_data(&self) -> PyResult<PyNone> {
184 self.update(py, |entry| entry.drop_merge_data());
185 self.update(py, |entry| entry.drop_merge_data());
185 Ok(PyNone)
186 Ok(PyNone)
186 }
187 }
187
188
188 def set_clean(
189 def set_clean(
189 &self,
190 &self,
190 mode: u32,
191 mode: u32,
191 size: u32,
192 size: u32,
192 mtime: (u32, u32, bool),
193 mtime: (u32, u32, bool),
193 ) -> PyResult<PyNone> {
194 ) -> PyResult<PyNone> {
194 let mtime = timestamp(py, mtime)?;
195 let mtime = timestamp(py, mtime)?;
195 self.update(py, |entry| entry.set_clean(mode, size, mtime));
196 self.update(py, |entry| entry.set_clean(mode, size, mtime));
196 Ok(PyNone)
197 Ok(PyNone)
197 }
198 }
198
199
199 def set_possibly_dirty(&self) -> PyResult<PyNone> {
200 def set_possibly_dirty(&self) -> PyResult<PyNone> {
200 self.update(py, |entry| entry.set_possibly_dirty());
201 self.update(py, |entry| entry.set_possibly_dirty());
201 Ok(PyNone)
202 Ok(PyNone)
202 }
203 }
203
204
204 def set_tracked(&self) -> PyResult<PyNone> {
205 def set_tracked(&self) -> PyResult<PyNone> {
205 self.update(py, |entry| entry.set_tracked());
206 self.update(py, |entry| entry.set_tracked());
206 Ok(PyNone)
207 Ok(PyNone)
207 }
208 }
208
209
209 def set_untracked(&self) -> PyResult<PyNone> {
210 def set_untracked(&self) -> PyResult<PyNone> {
210 self.update(py, |entry| entry.set_untracked());
211 self.update(py, |entry| entry.set_untracked());
211 Ok(PyNone)
212 Ok(PyNone)
212 }
213 }
213 });
214 });
214
215
215 impl DirstateItem {
216 impl DirstateItem {
216 pub fn new_as_pyobject(
217 pub fn new_as_pyobject(
217 py: Python<'_>,
218 py: Python<'_>,
218 entry: DirstateEntry,
219 entry: DirstateEntry,
219 ) -> PyResult<PyObject> {
220 ) -> PyResult<PyObject> {
220 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
221 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
221 }
222 }
222
223
223 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
224 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
224 self.entry(py).get()
225 self.entry(py).get()
225 }
226 }
226
227
227 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
228 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
228 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
229 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
229 let mut entry = self.entry(py).get();
230 let mut entry = self.entry(py).get();
230 f(&mut entry);
231 f(&mut entry);
231 self.entry(py).set(entry)
232 self.entry(py).set(entry)
232 }
233 }
233 }
234 }
234
235
235 pub(crate) fn timestamp(
236 pub(crate) fn timestamp(
236 py: Python<'_>,
237 py: Python<'_>,
237 (s, ns, second_ambiguous): (u32, u32, bool),
238 (s, ns, second_ambiguous): (u32, u32, bool),
238 ) -> PyResult<TruncatedTimestamp> {
239 ) -> PyResult<TruncatedTimestamp> {
239 TruncatedTimestamp::from_already_truncated(s, ns, second_ambiguous)
240 TruncatedTimestamp::from_already_truncated(s, ns, second_ambiguous)
240 .map_err(|_| {
241 .map_err(|_| {
241 PyErr::new::<exc::ValueError, _>(
242 PyErr::new::<exc::ValueError, _>(
242 py,
243 py,
243 "expected mtime truncated to 31 bits",
244 "expected mtime truncated to 31 bits",
244 )
245 )
245 })
246 })
246 }
247 }
General Comments 0
You need to be logged in to leave comments. Login now