##// END OF EJS Templates
rust-dirstate: make the reliable timestamp comparison more usable from outside...
Raphaël Gomès -
r52948:0529e1a4 default
parent child Browse files
Show More
@@ -1,722 +1,727
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::fs;
4 use std::fs;
5 use std::io;
5 use std::io;
6 use std::time::{SystemTime, UNIX_EPOCH};
6 use std::time::{SystemTime, UNIX_EPOCH};
7
7
8 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
8 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
9 pub enum EntryState {
9 pub enum EntryState {
10 Normal,
10 Normal,
11 Added,
11 Added,
12 Removed,
12 Removed,
13 Merged,
13 Merged,
14 }
14 }
15
15
16 /// `size` and `mtime.seconds` are truncated to 31 bits.
16 /// `size` and `mtime.seconds` are truncated to 31 bits.
17 ///
17 ///
18 /// TODO: double-check status algorithm correctness for files
18 /// TODO: double-check status algorithm correctness for files
19 /// larger than 2 GiB or modified after 2038.
19 /// larger than 2 GiB or modified after 2038.
20 #[derive(Debug, Copy, Clone)]
20 #[derive(Debug, Copy, Clone)]
21 pub struct DirstateEntry {
21 pub struct DirstateEntry {
22 pub(crate) flags: Flags,
22 pub(crate) flags: Flags,
23 mode_size: Option<(u32, u32)>,
23 mode_size: Option<(u32, u32)>,
24 mtime: Option<TruncatedTimestamp>,
24 mtime: Option<TruncatedTimestamp>,
25 }
25 }
26
26
27 bitflags! {
27 bitflags! {
28 pub(crate) struct Flags: u8 {
28 pub(crate) struct Flags: u8 {
29 const WDIR_TRACKED = 1 << 0;
29 const WDIR_TRACKED = 1 << 0;
30 const P1_TRACKED = 1 << 1;
30 const P1_TRACKED = 1 << 1;
31 const P2_INFO = 1 << 2;
31 const P2_INFO = 1 << 2;
32 const HAS_FALLBACK_EXEC = 1 << 3;
32 const HAS_FALLBACK_EXEC = 1 << 3;
33 const FALLBACK_EXEC = 1 << 4;
33 const FALLBACK_EXEC = 1 << 4;
34 const HAS_FALLBACK_SYMLINK = 1 << 5;
34 const HAS_FALLBACK_SYMLINK = 1 << 5;
35 const FALLBACK_SYMLINK = 1 << 6;
35 const FALLBACK_SYMLINK = 1 << 6;
36 }
36 }
37 }
37 }
38
38
39 /// A Unix timestamp with nanoseconds precision
39 /// A Unix timestamp with nanoseconds precision
40 #[derive(Debug, Copy, Clone)]
40 #[derive(Debug, Copy, Clone)]
41 pub struct TruncatedTimestamp {
41 pub struct TruncatedTimestamp {
42 truncated_seconds: u32,
42 truncated_seconds: u32,
43 /// Always in the `0 .. 1_000_000_000` range.
43 /// Always in the `0 .. 1_000_000_000` range.
44 nanoseconds: u32,
44 nanoseconds: u32,
45 /// TODO this should be in DirstateEntry, but the current code needs
45 /// TODO this should be in DirstateEntry, but the current code needs
46 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
46 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
47 /// comparison.
47 /// comparison.
48 pub second_ambiguous: bool,
48 pub second_ambiguous: bool,
49 }
49 }
50
50
51 impl TruncatedTimestamp {
51 impl TruncatedTimestamp {
52 /// Constructs from a timestamp potentially outside of the supported range,
52 /// Constructs from a timestamp potentially outside of the supported range,
53 /// and truncate the seconds components to its lower 31 bits.
53 /// and truncate the seconds components to its lower 31 bits.
54 ///
54 ///
55 /// Panics if the nanoseconds components is not in the expected range.
55 /// Panics if the nanoseconds components is not in the expected range.
56 pub fn new_truncate(
56 pub fn new_truncate(
57 seconds: i64,
57 seconds: i64,
58 nanoseconds: u32,
58 nanoseconds: u32,
59 second_ambiguous: bool,
59 second_ambiguous: bool,
60 ) -> Self {
60 ) -> Self {
61 assert!(nanoseconds < NSEC_PER_SEC);
61 assert!(nanoseconds < NSEC_PER_SEC);
62 Self {
62 Self {
63 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
63 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
64 nanoseconds,
64 nanoseconds,
65 second_ambiguous,
65 second_ambiguous,
66 }
66 }
67 }
67 }
68
68
69 /// Construct from components. Returns an error if they are not in the
69 /// Construct from components. Returns an error if they are not in the
70 /// expcted range.
70 /// expcted range.
71 pub fn from_already_truncated(
71 pub fn from_already_truncated(
72 truncated_seconds: u32,
72 truncated_seconds: u32,
73 nanoseconds: u32,
73 nanoseconds: u32,
74 second_ambiguous: bool,
74 second_ambiguous: bool,
75 ) -> Result<Self, DirstateV2ParseError> {
75 ) -> Result<Self, DirstateV2ParseError> {
76 if truncated_seconds & !RANGE_MASK_31BIT == 0
76 if truncated_seconds & !RANGE_MASK_31BIT == 0
77 && nanoseconds < NSEC_PER_SEC
77 && nanoseconds < NSEC_PER_SEC
78 {
78 {
79 Ok(Self {
79 Ok(Self {
80 truncated_seconds,
80 truncated_seconds,
81 nanoseconds,
81 nanoseconds,
82 second_ambiguous,
82 second_ambiguous,
83 })
83 })
84 } else {
84 } else {
85 Err(DirstateV2ParseError::new("when reading datetime"))
85 Err(DirstateV2ParseError::new("when reading datetime"))
86 }
86 }
87 }
87 }
88
88
89 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
89 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
90 ///
90 ///
91 /// Propagates errors from `std` on platforms where modification time
91 /// Propagates errors from `std` on platforms where modification time
92 /// is not available at all.
92 /// is not available at all.
93 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
93 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
94 #[cfg(unix)]
94 #[cfg(unix)]
95 {
95 {
96 use std::os::unix::fs::MetadataExt;
96 use std::os::unix::fs::MetadataExt;
97 let seconds = metadata.mtime();
97 let seconds = metadata.mtime();
98 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
98 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
99 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
99 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
100 Ok(Self::new_truncate(seconds, nanoseconds, false))
100 Ok(Self::new_truncate(seconds, nanoseconds, false))
101 }
101 }
102 #[cfg(not(unix))]
102 #[cfg(not(unix))]
103 {
103 {
104 metadata.modified().map(Self::from)
104 metadata.modified().map(Self::from)
105 }
105 }
106 }
106 }
107
107
108 /// Like `for_mtime_of`, but may return `None` or a value with
108 /// Like `for_mtime_of`, but may return `None` or a value with
109 /// `second_ambiguous` set if the mtime is not "reliable".
109 /// `second_ambiguous` set if the mtime is not "reliable".
110 ///
110 ///
111 /// A modification time is reliable if it is older than `boundary` (or
111 /// A modification time is reliable if it is older than `boundary` (or
112 /// sufficiently in the future).
112 /// sufficiently in the future).
113 ///
113 ///
114 /// Otherwise a concurrent modification might happens with the same mtime.
114 /// Otherwise a concurrent modification might happens with the same mtime.
115 pub fn for_reliable_mtime_of(
115 pub fn for_reliable_mtime_of(
116 metadata: &fs::Metadata,
116 metadata: &fs::Metadata,
117 boundary: &Self,
117 boundary: &Self,
118 ) -> io::Result<Option<Self>> {
118 ) -> io::Result<Option<Self>> {
119 let mut mtime = Self::for_mtime_of(metadata)?;
119 Ok(Self::for_mtime_of(metadata)?.for_reliable_mtime_of_self(boundary))
120 }
121
122 /// See [`Self::for_reliable_mtime_of`]
123 pub fn for_reliable_mtime_of_self(&self, boundary: &Self) -> Option<Self> {
124 let mut new = *self;
120 // If the mtime of the ambiguous file is younger (or equal) to the
125 // If the mtime of the ambiguous file is younger (or equal) to the
121 // starting point of the `status` walk, we cannot garantee that
126 // starting point of the `status` walk, we cannot garantee that
122 // another, racy, write will not happen right after with the same mtime
127 // another, racy, write will not happen right after with the same mtime
123 // and we cannot cache the information.
128 // and we cannot cache the information.
124 //
129 //
125 // However if the mtime is far away in the future, this is likely some
130 // However if the mtime is far away in the future, this is likely some
126 // mismatch between the current clock and previous file system
131 // mismatch between the current clock and previous file system
127 // operation. So mtime more than one days in the future are considered
132 // operation. So mtime more than one days in the future are considered
128 // fine.
133 // fine.
129 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
134 let reliable = if self.truncated_seconds == boundary.truncated_seconds
130 {
135 {
131 mtime.second_ambiguous = true;
136 new.second_ambiguous = true;
132 mtime.nanoseconds != 0
137 self.nanoseconds != 0
133 && boundary.nanoseconds != 0
138 && boundary.nanoseconds != 0
134 && mtime.nanoseconds < boundary.nanoseconds
139 && self.nanoseconds < boundary.nanoseconds
135 } else {
140 } else {
136 // `truncated_seconds` is less than 2**31,
141 // `truncated_seconds` is less than 2**31,
137 // so this does not overflow `u32`:
142 // so this does not overflow `u32`:
138 let one_day_later = boundary.truncated_seconds + 24 * 3600;
143 let one_day_later = boundary.truncated_seconds + 24 * 3600;
139 mtime.truncated_seconds < boundary.truncated_seconds
144 self.truncated_seconds < boundary.truncated_seconds
140 || mtime.truncated_seconds > one_day_later
145 || self.truncated_seconds > one_day_later
141 };
146 };
142 if reliable {
147 if reliable {
143 Ok(Some(mtime))
148 Some(new)
144 } else {
149 } else {
145 Ok(None)
150 None
146 }
151 }
147 }
152 }
148
153
149 /// The lower 31 bits of the number of seconds since the epoch.
154 /// The lower 31 bits of the number of seconds since the epoch.
150 pub fn truncated_seconds(&self) -> u32 {
155 pub fn truncated_seconds(&self) -> u32 {
151 self.truncated_seconds
156 self.truncated_seconds
152 }
157 }
153
158
154 /// The sub-second component of this timestamp, in nanoseconds.
159 /// The sub-second component of this timestamp, in nanoseconds.
155 /// Always in the `0 .. 1_000_000_000` range.
160 /// Always in the `0 .. 1_000_000_000` range.
156 ///
161 ///
157 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
162 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
158 pub fn nanoseconds(&self) -> u32 {
163 pub fn nanoseconds(&self) -> u32 {
159 self.nanoseconds
164 self.nanoseconds
160 }
165 }
161
166
162 /// Returns whether two timestamps are equal modulo 2**31 seconds.
167 /// Returns whether two timestamps are equal modulo 2**31 seconds.
163 ///
168 ///
164 /// If this returns `true`, the original values converted from `SystemTime`
169 /// If this returns `true`, the original values converted from `SystemTime`
165 /// or given to `new_truncate` were very likely equal. A false positive is
170 /// or given to `new_truncate` were very likely equal. A false positive is
166 /// possible if they were exactly a multiple of 2**31 seconds apart (around
171 /// possible if they were exactly a multiple of 2**31 seconds apart (around
167 /// 68 years). This is deemed very unlikely to happen by chance, especially
172 /// 68 years). This is deemed very unlikely to happen by chance, especially
168 /// on filesystems that support sub-second precision.
173 /// on filesystems that support sub-second precision.
169 ///
174 ///
170 /// If someone is manipulating the modification times of some files to
175 /// If someone is manipulating the modification times of some files to
171 /// intentionally make `hg status` return incorrect results, not truncating
176 /// intentionally make `hg status` return incorrect results, not truncating
172 /// wouldn’t help much since they can set exactly the expected timestamp.
177 /// wouldn’t help much since they can set exactly the expected timestamp.
173 ///
178 ///
174 /// Sub-second precision is ignored if it is zero in either value.
179 /// Sub-second precision is ignored if it is zero in either value.
175 /// Some APIs simply return zero when more precision is not available.
180 /// Some APIs simply return zero when more precision is not available.
176 /// When comparing values from different sources, if only one is truncated
181 /// When comparing values from different sources, if only one is truncated
177 /// in that way, doing a simple comparison would cause many false
182 /// in that way, doing a simple comparison would cause many false
178 /// negatives.
183 /// negatives.
179 pub fn likely_equal(self, other: Self) -> bool {
184 pub fn likely_equal(self, other: Self) -> bool {
180 if self.truncated_seconds != other.truncated_seconds {
185 if self.truncated_seconds != other.truncated_seconds {
181 false
186 false
182 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
187 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
183 !self.second_ambiguous
188 !self.second_ambiguous
184 } else {
189 } else {
185 self.nanoseconds == other.nanoseconds
190 self.nanoseconds == other.nanoseconds
186 }
191 }
187 }
192 }
188
193
189 pub fn likely_equal_to_mtime_of(
194 pub fn likely_equal_to_mtime_of(
190 self,
195 self,
191 metadata: &fs::Metadata,
196 metadata: &fs::Metadata,
192 ) -> io::Result<bool> {
197 ) -> io::Result<bool> {
193 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
194 }
199 }
195 }
200 }
196
201
197 impl From<SystemTime> for TruncatedTimestamp {
202 impl From<SystemTime> for TruncatedTimestamp {
198 fn from(system_time: SystemTime) -> Self {
203 fn from(system_time: SystemTime) -> Self {
199 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
200 // 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
201 // We want to effectively access its fields, but the Rust standard
206 // We want to effectively access its fields, but the Rust standard
202 // library does not expose them. The best we can do is:
207 // library does not expose them. The best we can do is:
203 let seconds;
208 let seconds;
204 let nanoseconds;
209 let nanoseconds;
205 match system_time.duration_since(UNIX_EPOCH) {
210 match system_time.duration_since(UNIX_EPOCH) {
206 Ok(duration) => {
211 Ok(duration) => {
207 seconds = duration.as_secs() as i64;
212 seconds = duration.as_secs() as i64;
208 nanoseconds = duration.subsec_nanos();
213 nanoseconds = duration.subsec_nanos();
209 }
214 }
210 Err(error) => {
215 Err(error) => {
211 // `system_time` is before `UNIX_EPOCH`.
216 // `system_time` is before `UNIX_EPOCH`.
212 // We need to undo this algorithm:
217 // We need to undo this algorithm:
213 // 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
214 let negative = error.duration();
219 let negative = error.duration();
215 let negative_secs = negative.as_secs() as i64;
220 let negative_secs = negative.as_secs() as i64;
216 let negative_nanos = negative.subsec_nanos();
221 let negative_nanos = negative.subsec_nanos();
217 if negative_nanos == 0 {
222 if negative_nanos == 0 {
218 seconds = -negative_secs;
223 seconds = -negative_secs;
219 nanoseconds = 0;
224 nanoseconds = 0;
220 } else {
225 } else {
221 // For example if `system_time` was 4.3 seconds before
226 // For example if `system_time` was 4.3 seconds before
222 // the Unix epoch we get a Duration that represents
227 // the Unix epoch we get a Duration that represents
223 // `(-4, -0.3)` but we want `(-5, +0.7)`:
228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
224 seconds = -1 - negative_secs;
229 seconds = -1 - negative_secs;
225 nanoseconds = NSEC_PER_SEC - negative_nanos;
230 nanoseconds = NSEC_PER_SEC - negative_nanos;
226 }
231 }
227 }
232 }
228 };
233 };
229 Self::new_truncate(seconds, nanoseconds, false)
234 Self::new_truncate(seconds, nanoseconds, false)
230 }
235 }
231 }
236 }
232
237
233 const NSEC_PER_SEC: u32 = 1_000_000_000;
238 const NSEC_PER_SEC: u32 = 1_000_000_000;
234 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
235
240
236 pub const MTIME_UNSET: i32 = -1;
241 pub const MTIME_UNSET: i32 = -1;
237
242
238 /// 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
239 /// 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
240 /// merge.
245 /// merge.
241 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
242 /// A special value used for internal representation of special case in
247 /// A special value used for internal representation of special case in
243 /// dirstate v1 format.
248 /// dirstate v1 format.
244 pub const SIZE_NON_NORMAL: i32 = -1;
249 pub const SIZE_NON_NORMAL: i32 = -1;
245
250
246 #[derive(Debug, Default, Copy, Clone)]
251 #[derive(Debug, Default, Copy, Clone)]
247 pub struct DirstateV2Data {
252 pub struct DirstateV2Data {
248 pub wc_tracked: bool,
253 pub wc_tracked: bool,
249 pub p1_tracked: bool,
254 pub p1_tracked: bool,
250 pub p2_info: bool,
255 pub p2_info: bool,
251 pub mode_size: Option<(u32, u32)>,
256 pub mode_size: Option<(u32, u32)>,
252 pub mtime: Option<TruncatedTimestamp>,
257 pub mtime: Option<TruncatedTimestamp>,
253 pub fallback_exec: Option<bool>,
258 pub fallback_exec: Option<bool>,
254 pub fallback_symlink: Option<bool>,
259 pub fallback_symlink: Option<bool>,
255 }
260 }
256
261
257 #[derive(Debug, Default, Copy, Clone)]
262 #[derive(Debug, Default, Copy, Clone)]
258 pub struct ParentFileData {
263 pub struct ParentFileData {
259 pub mode_size: Option<(u32, u32)>,
264 pub mode_size: Option<(u32, u32)>,
260 pub mtime: Option<TruncatedTimestamp>,
265 pub mtime: Option<TruncatedTimestamp>,
261 }
266 }
262
267
263 impl DirstateEntry {
268 impl DirstateEntry {
264 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
269 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
265 let DirstateV2Data {
270 let DirstateV2Data {
266 wc_tracked,
271 wc_tracked,
267 p1_tracked,
272 p1_tracked,
268 p2_info,
273 p2_info,
269 mode_size,
274 mode_size,
270 mtime,
275 mtime,
271 fallback_exec,
276 fallback_exec,
272 fallback_symlink,
277 fallback_symlink,
273 } = v2_data;
278 } = v2_data;
274 if let Some((mode, size)) = mode_size {
279 if let Some((mode, size)) = mode_size {
275 // TODO: return an error for out of range values?
280 // TODO: return an error for out of range values?
276 assert!(mode & !RANGE_MASK_31BIT == 0);
281 assert!(mode & !RANGE_MASK_31BIT == 0);
277 assert!(size & !RANGE_MASK_31BIT == 0);
282 assert!(size & !RANGE_MASK_31BIT == 0);
278 }
283 }
279 let mut flags = Flags::empty();
284 let mut flags = Flags::empty();
280 flags.set(Flags::WDIR_TRACKED, wc_tracked);
285 flags.set(Flags::WDIR_TRACKED, wc_tracked);
281 flags.set(Flags::P1_TRACKED, p1_tracked);
286 flags.set(Flags::P1_TRACKED, p1_tracked);
282 flags.set(Flags::P2_INFO, p2_info);
287 flags.set(Flags::P2_INFO, p2_info);
283 if let Some(exec) = fallback_exec {
288 if let Some(exec) = fallback_exec {
284 flags.insert(Flags::HAS_FALLBACK_EXEC);
289 flags.insert(Flags::HAS_FALLBACK_EXEC);
285 if exec {
290 if exec {
286 flags.insert(Flags::FALLBACK_EXEC);
291 flags.insert(Flags::FALLBACK_EXEC);
287 }
292 }
288 }
293 }
289 if let Some(exec) = fallback_symlink {
294 if let Some(exec) = fallback_symlink {
290 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
295 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
291 if exec {
296 if exec {
292 flags.insert(Flags::FALLBACK_SYMLINK);
297 flags.insert(Flags::FALLBACK_SYMLINK);
293 }
298 }
294 }
299 }
295 Self {
300 Self {
296 flags,
301 flags,
297 mode_size,
302 mode_size,
298 mtime,
303 mtime,
299 }
304 }
300 }
305 }
301
306
302 pub fn from_v1_data(
307 pub fn from_v1_data(
303 state: EntryState,
308 state: EntryState,
304 mode: i32,
309 mode: i32,
305 size: i32,
310 size: i32,
306 mtime: i32,
311 mtime: i32,
307 ) -> Self {
312 ) -> Self {
308 match state {
313 match state {
309 EntryState::Normal => {
314 EntryState::Normal => {
310 if size == SIZE_FROM_OTHER_PARENT {
315 if size == SIZE_FROM_OTHER_PARENT {
311 Self {
316 Self {
312 // might be missing P1_TRACKED
317 // might be missing P1_TRACKED
313 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
318 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
314 mode_size: None,
319 mode_size: None,
315 mtime: None,
320 mtime: None,
316 }
321 }
317 } else if size == SIZE_NON_NORMAL {
322 } else if size == SIZE_NON_NORMAL {
318 Self {
323 Self {
319 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
324 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
320 mode_size: None,
325 mode_size: None,
321 mtime: None,
326 mtime: None,
322 }
327 }
323 } else if mtime == MTIME_UNSET {
328 } else if mtime == MTIME_UNSET {
324 // TODO: return an error for negative values?
329 // TODO: return an error for negative values?
325 let mode = u32::try_from(mode).unwrap();
330 let mode = u32::try_from(mode).unwrap();
326 let size = u32::try_from(size).unwrap();
331 let size = u32::try_from(size).unwrap();
327 Self {
332 Self {
328 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
333 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
329 mode_size: Some((mode, size)),
334 mode_size: Some((mode, size)),
330 mtime: None,
335 mtime: None,
331 }
336 }
332 } else {
337 } else {
333 // TODO: return an error for negative values?
338 // TODO: return an error for negative values?
334 let mode = u32::try_from(mode).unwrap();
339 let mode = u32::try_from(mode).unwrap();
335 let size = u32::try_from(size).unwrap();
340 let size = u32::try_from(size).unwrap();
336 let mtime = u32::try_from(mtime).unwrap();
341 let mtime = u32::try_from(mtime).unwrap();
337 let mtime = TruncatedTimestamp::from_already_truncated(
342 let mtime = TruncatedTimestamp::from_already_truncated(
338 mtime, 0, false,
343 mtime, 0, false,
339 )
344 )
340 .unwrap();
345 .unwrap();
341 Self {
346 Self {
342 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
347 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
343 mode_size: Some((mode, size)),
348 mode_size: Some((mode, size)),
344 mtime: Some(mtime),
349 mtime: Some(mtime),
345 }
350 }
346 }
351 }
347 }
352 }
348 EntryState::Added => Self {
353 EntryState::Added => Self {
349 flags: Flags::WDIR_TRACKED,
354 flags: Flags::WDIR_TRACKED,
350 mode_size: None,
355 mode_size: None,
351 mtime: None,
356 mtime: None,
352 },
357 },
353 EntryState::Removed => Self {
358 EntryState::Removed => Self {
354 flags: if size == SIZE_NON_NORMAL {
359 flags: if size == SIZE_NON_NORMAL {
355 Flags::P1_TRACKED | Flags::P2_INFO
360 Flags::P1_TRACKED | Flags::P2_INFO
356 } else if size == SIZE_FROM_OTHER_PARENT {
361 } else if size == SIZE_FROM_OTHER_PARENT {
357 // We don’t know if P1_TRACKED should be set (file history)
362 // We don’t know if P1_TRACKED should be set (file history)
358 Flags::P2_INFO
363 Flags::P2_INFO
359 } else {
364 } else {
360 Flags::P1_TRACKED
365 Flags::P1_TRACKED
361 },
366 },
362 mode_size: None,
367 mode_size: None,
363 mtime: None,
368 mtime: None,
364 },
369 },
365 EntryState::Merged => Self {
370 EntryState::Merged => Self {
366 flags: Flags::WDIR_TRACKED
371 flags: Flags::WDIR_TRACKED
367 | Flags::P1_TRACKED // might not be true because of rename ?
372 | Flags::P1_TRACKED // might not be true because of rename ?
368 | Flags::P2_INFO, // might not be true because of rename ?
373 | Flags::P2_INFO, // might not be true because of rename ?
369 mode_size: None,
374 mode_size: None,
370 mtime: None,
375 mtime: None,
371 },
376 },
372 }
377 }
373 }
378 }
374
379
375 /// Creates a new entry in "removed" state.
380 /// Creates a new entry in "removed" state.
376 ///
381 ///
377 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
382 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
378 /// `SIZE_FROM_OTHER_PARENT`
383 /// `SIZE_FROM_OTHER_PARENT`
379 pub fn new_removed(size: i32) -> Self {
384 pub fn new_removed(size: i32) -> Self {
380 Self::from_v1_data(EntryState::Removed, 0, size, 0)
385 Self::from_v1_data(EntryState::Removed, 0, size, 0)
381 }
386 }
382
387
383 pub fn new_tracked() -> Self {
388 pub fn new_tracked() -> Self {
384 let data = DirstateV2Data {
389 let data = DirstateV2Data {
385 wc_tracked: true,
390 wc_tracked: true,
386 ..Default::default()
391 ..Default::default()
387 };
392 };
388 Self::from_v2_data(data)
393 Self::from_v2_data(data)
389 }
394 }
390
395
391 pub fn tracked(&self) -> bool {
396 pub fn tracked(&self) -> bool {
392 self.flags.contains(Flags::WDIR_TRACKED)
397 self.flags.contains(Flags::WDIR_TRACKED)
393 }
398 }
394
399
395 pub fn p1_tracked(&self) -> bool {
400 pub fn p1_tracked(&self) -> bool {
396 self.flags.contains(Flags::P1_TRACKED)
401 self.flags.contains(Flags::P1_TRACKED)
397 }
402 }
398
403
399 fn in_either_parent(&self) -> bool {
404 fn in_either_parent(&self) -> bool {
400 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
405 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
401 }
406 }
402
407
403 pub fn removed(&self) -> bool {
408 pub fn removed(&self) -> bool {
404 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
409 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
405 }
410 }
406
411
407 pub fn p2_info(&self) -> bool {
412 pub fn p2_info(&self) -> bool {
408 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
413 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
409 }
414 }
410
415
411 pub fn added(&self) -> bool {
416 pub fn added(&self) -> bool {
412 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
417 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
413 }
418 }
414
419
415 pub fn modified(&self) -> bool {
420 pub fn modified(&self) -> bool {
416 self.flags
421 self.flags
417 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
422 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
418 }
423 }
419
424
420 pub fn maybe_clean(&self) -> bool {
425 pub fn maybe_clean(&self) -> bool {
421 #[allow(clippy::if_same_then_else)]
426 #[allow(clippy::if_same_then_else)]
422 #[allow(clippy::needless_bool)]
427 #[allow(clippy::needless_bool)]
423 if !self.flags.contains(Flags::WDIR_TRACKED) {
428 if !self.flags.contains(Flags::WDIR_TRACKED) {
424 false
429 false
425 } else if !self.flags.contains(Flags::P1_TRACKED) {
430 } else if !self.flags.contains(Flags::P1_TRACKED) {
426 false
431 false
427 } else if self.flags.contains(Flags::P2_INFO) {
432 } else if self.flags.contains(Flags::P2_INFO) {
428 false
433 false
429 } else {
434 } else {
430 true
435 true
431 }
436 }
432 }
437 }
433
438
434 pub fn any_tracked(&self) -> bool {
439 pub fn any_tracked(&self) -> bool {
435 self.flags.intersects(
440 self.flags.intersects(
436 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
441 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
437 )
442 )
438 }
443 }
439
444
440 pub(crate) fn v2_data(&self) -> DirstateV2Data {
445 pub(crate) fn v2_data(&self) -> DirstateV2Data {
441 if !self.any_tracked() {
446 if !self.any_tracked() {
442 // TODO: return an Option instead?
447 // TODO: return an Option instead?
443 panic!("Accessing v2_data of an untracked DirstateEntry")
448 panic!("Accessing v2_data of an untracked DirstateEntry")
444 }
449 }
445 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
450 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
446 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
451 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
447 let p2_info = self.flags.contains(Flags::P2_INFO);
452 let p2_info = self.flags.contains(Flags::P2_INFO);
448 let mode_size = self.mode_size;
453 let mode_size = self.mode_size;
449 let mtime = self.mtime;
454 let mtime = self.mtime;
450 DirstateV2Data {
455 DirstateV2Data {
451 wc_tracked,
456 wc_tracked,
452 p1_tracked,
457 p1_tracked,
453 p2_info,
458 p2_info,
454 mode_size,
459 mode_size,
455 mtime,
460 mtime,
456 fallback_exec: self.get_fallback_exec(),
461 fallback_exec: self.get_fallback_exec(),
457 fallback_symlink: self.get_fallback_symlink(),
462 fallback_symlink: self.get_fallback_symlink(),
458 }
463 }
459 }
464 }
460
465
461 fn v1_state(&self) -> EntryState {
466 fn v1_state(&self) -> EntryState {
462 if !self.any_tracked() {
467 if !self.any_tracked() {
463 // TODO: return an Option instead?
468 // TODO: return an Option instead?
464 panic!("Accessing v1_state of an untracked DirstateEntry")
469 panic!("Accessing v1_state of an untracked DirstateEntry")
465 }
470 }
466 if self.removed() {
471 if self.removed() {
467 EntryState::Removed
472 EntryState::Removed
468 } else if self.modified() {
473 } else if self.modified() {
469 EntryState::Merged
474 EntryState::Merged
470 } else if self.added() {
475 } else if self.added() {
471 EntryState::Added
476 EntryState::Added
472 } else {
477 } else {
473 EntryState::Normal
478 EntryState::Normal
474 }
479 }
475 }
480 }
476
481
477 fn v1_mode(&self) -> i32 {
482 fn v1_mode(&self) -> i32 {
478 if let Some((mode, _size)) = self.mode_size {
483 if let Some((mode, _size)) = self.mode_size {
479 i32::try_from(mode).unwrap()
484 i32::try_from(mode).unwrap()
480 } else {
485 } else {
481 0
486 0
482 }
487 }
483 }
488 }
484
489
485 fn v1_size(&self) -> i32 {
490 fn v1_size(&self) -> i32 {
486 if !self.any_tracked() {
491 if !self.any_tracked() {
487 // TODO: return an Option instead?
492 // TODO: return an Option instead?
488 panic!("Accessing v1_size of an untracked DirstateEntry")
493 panic!("Accessing v1_size of an untracked DirstateEntry")
489 }
494 }
490 if self.removed()
495 if self.removed()
491 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
496 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
492 {
497 {
493 SIZE_NON_NORMAL
498 SIZE_NON_NORMAL
494 } else if self.flags.contains(Flags::P2_INFO) {
499 } else if self.flags.contains(Flags::P2_INFO) {
495 SIZE_FROM_OTHER_PARENT
500 SIZE_FROM_OTHER_PARENT
496 } else if self.removed() {
501 } else if self.removed() {
497 0
502 0
498 } else if self.added() {
503 } else if self.added() {
499 SIZE_NON_NORMAL
504 SIZE_NON_NORMAL
500 } else if let Some((_mode, size)) = self.mode_size {
505 } else if let Some((_mode, size)) = self.mode_size {
501 i32::try_from(size).unwrap()
506 i32::try_from(size).unwrap()
502 } else {
507 } else {
503 SIZE_NON_NORMAL
508 SIZE_NON_NORMAL
504 }
509 }
505 }
510 }
506
511
507 fn v1_mtime(&self) -> i32 {
512 fn v1_mtime(&self) -> i32 {
508 if !self.any_tracked() {
513 if !self.any_tracked() {
509 // TODO: return an Option instead?
514 // TODO: return an Option instead?
510 panic!("Accessing v1_mtime of an untracked DirstateEntry")
515 panic!("Accessing v1_mtime of an untracked DirstateEntry")
511 }
516 }
512
517
513 #[allow(clippy::if_same_then_else)]
518 #[allow(clippy::if_same_then_else)]
514 if self.removed() {
519 if self.removed() {
515 0
520 0
516 } else if self.flags.contains(Flags::P2_INFO) {
521 } else if self.flags.contains(Flags::P2_INFO) {
517 MTIME_UNSET
522 MTIME_UNSET
518 } else if !self.flags.contains(Flags::P1_TRACKED) {
523 } else if !self.flags.contains(Flags::P1_TRACKED) {
519 MTIME_UNSET
524 MTIME_UNSET
520 } else if let Some(mtime) = self.mtime {
525 } else if let Some(mtime) = self.mtime {
521 if mtime.second_ambiguous {
526 if mtime.second_ambiguous {
522 MTIME_UNSET
527 MTIME_UNSET
523 } else {
528 } else {
524 i32::try_from(mtime.truncated_seconds()).unwrap()
529 i32::try_from(mtime.truncated_seconds()).unwrap()
525 }
530 }
526 } else {
531 } else {
527 MTIME_UNSET
532 MTIME_UNSET
528 }
533 }
529 }
534 }
530
535
531 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
536 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
532 pub fn state(&self) -> EntryState {
537 pub fn state(&self) -> EntryState {
533 self.v1_state()
538 self.v1_state()
534 }
539 }
535
540
536 // TODO: return Option?
541 // TODO: return Option?
537 pub fn mode(&self) -> i32 {
542 pub fn mode(&self) -> i32 {
538 self.v1_mode()
543 self.v1_mode()
539 }
544 }
540
545
541 // TODO: return Option?
546 // TODO: return Option?
542 pub fn size(&self) -> i32 {
547 pub fn size(&self) -> i32 {
543 self.v1_size()
548 self.v1_size()
544 }
549 }
545
550
546 // TODO: return Option?
551 // TODO: return Option?
547 pub fn mtime(&self) -> i32 {
552 pub fn mtime(&self) -> i32 {
548 self.v1_mtime()
553 self.v1_mtime()
549 }
554 }
550
555
551 pub fn get_fallback_exec(&self) -> Option<bool> {
556 pub fn get_fallback_exec(&self) -> Option<bool> {
552 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
557 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
553 Some(self.flags.contains(Flags::FALLBACK_EXEC))
558 Some(self.flags.contains(Flags::FALLBACK_EXEC))
554 } else {
559 } else {
555 None
560 None
556 }
561 }
557 }
562 }
558
563
559 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
564 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
560 match value {
565 match value {
561 None => {
566 None => {
562 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
567 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
563 self.flags.remove(Flags::FALLBACK_EXEC);
568 self.flags.remove(Flags::FALLBACK_EXEC);
564 }
569 }
565 Some(exec) => {
570 Some(exec) => {
566 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
571 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
567 if exec {
572 if exec {
568 self.flags.insert(Flags::FALLBACK_EXEC);
573 self.flags.insert(Flags::FALLBACK_EXEC);
569 }
574 }
570 }
575 }
571 }
576 }
572 }
577 }
573
578
574 pub fn get_fallback_symlink(&self) -> Option<bool> {
579 pub fn get_fallback_symlink(&self) -> Option<bool> {
575 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
580 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
576 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
581 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
577 } else {
582 } else {
578 None
583 None
579 }
584 }
580 }
585 }
581
586
582 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
587 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
583 match value {
588 match value {
584 None => {
589 None => {
585 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
590 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
586 self.flags.remove(Flags::FALLBACK_SYMLINK);
591 self.flags.remove(Flags::FALLBACK_SYMLINK);
587 }
592 }
588 Some(symlink) => {
593 Some(symlink) => {
589 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
594 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
590 if symlink {
595 if symlink {
591 self.flags.insert(Flags::FALLBACK_SYMLINK);
596 self.flags.insert(Flags::FALLBACK_SYMLINK);
592 }
597 }
593 }
598 }
594 }
599 }
595 }
600 }
596
601
597 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
602 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
598 self.mtime
603 self.mtime
599 }
604 }
600
605
601 pub fn drop_merge_data(&mut self) {
606 pub fn drop_merge_data(&mut self) {
602 if self.flags.contains(Flags::P2_INFO) {
607 if self.flags.contains(Flags::P2_INFO) {
603 self.flags.remove(Flags::P2_INFO);
608 self.flags.remove(Flags::P2_INFO);
604 self.mode_size = None;
609 self.mode_size = None;
605 self.mtime = None;
610 self.mtime = None;
606 }
611 }
607 }
612 }
608
613
609 pub fn set_possibly_dirty(&mut self) {
614 pub fn set_possibly_dirty(&mut self) {
610 self.mtime = None
615 self.mtime = None
611 }
616 }
612
617
613 pub fn set_clean(
618 pub fn set_clean(
614 &mut self,
619 &mut self,
615 mode: u32,
620 mode: u32,
616 size: u32,
621 size: u32,
617 mtime: TruncatedTimestamp,
622 mtime: TruncatedTimestamp,
618 ) {
623 ) {
619 let size = size & RANGE_MASK_31BIT;
624 let size = size & RANGE_MASK_31BIT;
620 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
625 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
621 self.mode_size = Some((mode, size));
626 self.mode_size = Some((mode, size));
622 self.mtime = Some(mtime);
627 self.mtime = Some(mtime);
623 }
628 }
624
629
625 pub fn set_tracked(&mut self) {
630 pub fn set_tracked(&mut self) {
626 self.flags.insert(Flags::WDIR_TRACKED);
631 self.flags.insert(Flags::WDIR_TRACKED);
627 // `set_tracked` is replacing various `normallookup` call. So we mark
632 // `set_tracked` is replacing various `normallookup` call. So we mark
628 // the files as needing lookup
633 // the files as needing lookup
629 //
634 //
630 // Consider dropping this in the future in favor of something less
635 // Consider dropping this in the future in favor of something less
631 // broad.
636 // broad.
632 self.mtime = None;
637 self.mtime = None;
633 }
638 }
634
639
635 pub fn set_untracked(&mut self) {
640 pub fn set_untracked(&mut self) {
636 self.flags.remove(Flags::WDIR_TRACKED);
641 self.flags.remove(Flags::WDIR_TRACKED);
637 self.mode_size = None;
642 self.mode_size = None;
638 self.mtime = None;
643 self.mtime = None;
639 }
644 }
640
645
641 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
646 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
642 /// in the dirstate-v1 format.
647 /// in the dirstate-v1 format.
643 ///
648 ///
644 /// This includes marker values such as `mtime == -1`. In the future we may
649 /// This includes marker values such as `mtime == -1`. In the future we may
645 /// want to not represent these cases that way in memory, but serialization
650 /// want to not represent these cases that way in memory, but serialization
646 /// will need to keep the same format.
651 /// will need to keep the same format.
647 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
652 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
648 (
653 (
649 self.v1_state().into(),
654 self.v1_state().into(),
650 self.v1_mode(),
655 self.v1_mode(),
651 self.v1_size(),
656 self.v1_size(),
652 self.v1_mtime(),
657 self.v1_mtime(),
653 )
658 )
654 }
659 }
655
660
656 pub(crate) fn is_from_other_parent(&self) -> bool {
661 pub(crate) fn is_from_other_parent(&self) -> bool {
657 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
662 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
658 }
663 }
659
664
660 // TODO: other platforms
665 // TODO: other platforms
661 #[cfg(unix)]
666 #[cfg(unix)]
662 pub fn mode_changed(
667 pub fn mode_changed(
663 &self,
668 &self,
664 filesystem_metadata: &std::fs::Metadata,
669 filesystem_metadata: &std::fs::Metadata,
665 ) -> bool {
670 ) -> bool {
666 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
671 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
667 let fs_exec_bit = has_exec_bit(filesystem_metadata);
672 let fs_exec_bit = has_exec_bit(filesystem_metadata);
668 dirstate_exec_bit != fs_exec_bit
673 dirstate_exec_bit != fs_exec_bit
669 }
674 }
670
675
671 /// Returns a `(state, mode, size, mtime)` tuple as for
676 /// Returns a `(state, mode, size, mtime)` tuple as for
672 /// `DirstateMapMethods::debug_iter`.
677 /// `DirstateMapMethods::debug_iter`.
673 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
678 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
674 (self.state().into(), self.mode(), self.size(), self.mtime())
679 (self.state().into(), self.mode(), self.size(), self.mtime())
675 }
680 }
676 }
681 }
677
682
678 impl EntryState {
683 impl EntryState {
679 pub fn is_tracked(self) -> bool {
684 pub fn is_tracked(self) -> bool {
680 use EntryState::*;
685 use EntryState::*;
681 match self {
686 match self {
682 Normal | Added | Merged => true,
687 Normal | Added | Merged => true,
683 Removed => false,
688 Removed => false,
684 }
689 }
685 }
690 }
686 }
691 }
687
692
688 impl TryFrom<u8> for EntryState {
693 impl TryFrom<u8> for EntryState {
689 type Error = HgError;
694 type Error = HgError;
690
695
691 fn try_from(value: u8) -> Result<Self, Self::Error> {
696 fn try_from(value: u8) -> Result<Self, Self::Error> {
692 match value {
697 match value {
693 b'n' => Ok(EntryState::Normal),
698 b'n' => Ok(EntryState::Normal),
694 b'a' => Ok(EntryState::Added),
699 b'a' => Ok(EntryState::Added),
695 b'r' => Ok(EntryState::Removed),
700 b'r' => Ok(EntryState::Removed),
696 b'm' => Ok(EntryState::Merged),
701 b'm' => Ok(EntryState::Merged),
697 _ => Err(HgError::CorruptedRepository(format!(
702 _ => Err(HgError::CorruptedRepository(format!(
698 "Incorrect dirstate entry state {}",
703 "Incorrect dirstate entry state {}",
699 value
704 value
700 ))),
705 ))),
701 }
706 }
702 }
707 }
703 }
708 }
704
709
705 impl From<EntryState> for u8 {
710 impl From<EntryState> for u8 {
706 fn from(val: EntryState) -> Self {
711 fn from(val: EntryState) -> Self {
707 match val {
712 match val {
708 EntryState::Normal => b'n',
713 EntryState::Normal => b'n',
709 EntryState::Added => b'a',
714 EntryState::Added => b'a',
710 EntryState::Removed => b'r',
715 EntryState::Removed => b'r',
711 EntryState::Merged => b'm',
716 EntryState::Merged => b'm',
712 }
717 }
713 }
718 }
714 }
719 }
715
720
716 const EXEC_BIT_MASK: u32 = 0o100;
721 const EXEC_BIT_MASK: u32 = 0o100;
717
722
718 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
723 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
719 // TODO: How to handle executable permissions on Windows?
724 // TODO: How to handle executable permissions on Windows?
720 use std::os::unix::fs::MetadataExt;
725 use std::os::unix::fs::MetadataExt;
721 (metadata.mode() & EXEC_BIT_MASK) != 0
726 (metadata.mode() & EXEC_BIT_MASK) != 0
722 }
727 }
General Comments 0
You need to be logged in to leave comments. Login now