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