##// END OF EJS Templates
rust-clippy: tell clippy we care about keeping those `if` clauses separate...
Raphaël Gomès -
r50813:4158608f default
parent child Browse files
Show More
@@ -1,722 +1,725 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 if !self.flags.contains(Flags::WDIR_TRACKED) {
426 if !self.flags.contains(Flags::WDIR_TRACKED) {
426 false
427 false
427 } else if !self.flags.contains(Flags::P1_TRACKED) {
428 } else if !self.flags.contains(Flags::P1_TRACKED) {
428 false
429 false
429 } else if self.flags.contains(Flags::P2_INFO) {
430 } else if self.flags.contains(Flags::P2_INFO) {
430 false
431 false
431 } else {
432 } else {
432 true
433 true
433 }
434 }
434 }
435 }
435
436
436 pub fn any_tracked(&self) -> bool {
437 pub fn any_tracked(&self) -> bool {
437 self.flags.intersects(
438 self.flags.intersects(
438 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
439 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
439 )
440 )
440 }
441 }
441
442
442 pub(crate) fn v2_data(&self) -> DirstateV2Data {
443 pub(crate) fn v2_data(&self) -> DirstateV2Data {
443 if !self.any_tracked() {
444 if !self.any_tracked() {
444 // TODO: return an Option instead?
445 // TODO: return an Option instead?
445 panic!("Accessing v2_data of an untracked DirstateEntry")
446 panic!("Accessing v2_data of an untracked DirstateEntry")
446 }
447 }
447 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
448 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
448 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
449 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
449 let p2_info = self.flags.contains(Flags::P2_INFO);
450 let p2_info = self.flags.contains(Flags::P2_INFO);
450 let mode_size = self.mode_size;
451 let mode_size = self.mode_size;
451 let mtime = self.mtime;
452 let mtime = self.mtime;
452 DirstateV2Data {
453 DirstateV2Data {
453 wc_tracked,
454 wc_tracked,
454 p1_tracked,
455 p1_tracked,
455 p2_info,
456 p2_info,
456 mode_size,
457 mode_size,
457 mtime,
458 mtime,
458 fallback_exec: self.get_fallback_exec(),
459 fallback_exec: self.get_fallback_exec(),
459 fallback_symlink: self.get_fallback_symlink(),
460 fallback_symlink: self.get_fallback_symlink(),
460 }
461 }
461 }
462 }
462
463
463 fn v1_state(&self) -> EntryState {
464 fn v1_state(&self) -> EntryState {
464 if !self.any_tracked() {
465 if !self.any_tracked() {
465 // TODO: return an Option instead?
466 // TODO: return an Option instead?
466 panic!("Accessing v1_state of an untracked DirstateEntry")
467 panic!("Accessing v1_state of an untracked DirstateEntry")
467 }
468 }
468 if self.removed() {
469 if self.removed() {
469 EntryState::Removed
470 EntryState::Removed
470 } else if self.modified() {
471 } else if self.modified() {
471 EntryState::Merged
472 EntryState::Merged
472 } else if self.added() {
473 } else if self.added() {
473 EntryState::Added
474 EntryState::Added
474 } else {
475 } else {
475 EntryState::Normal
476 EntryState::Normal
476 }
477 }
477 }
478 }
478
479
479 fn v1_mode(&self) -> i32 {
480 fn v1_mode(&self) -> i32 {
480 if let Some((mode, _size)) = self.mode_size {
481 if let Some((mode, _size)) = self.mode_size {
481 i32::try_from(mode).unwrap()
482 i32::try_from(mode).unwrap()
482 } else {
483 } else {
483 0
484 0
484 }
485 }
485 }
486 }
486
487
487 fn v1_size(&self) -> i32 {
488 fn v1_size(&self) -> i32 {
488 if !self.any_tracked() {
489 if !self.any_tracked() {
489 // TODO: return an Option instead?
490 // TODO: return an Option instead?
490 panic!("Accessing v1_size of an untracked DirstateEntry")
491 panic!("Accessing v1_size of an untracked DirstateEntry")
491 }
492 }
492 if self.removed()
493 if self.removed()
493 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
494 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
494 {
495 {
495 SIZE_NON_NORMAL
496 SIZE_NON_NORMAL
496 } else if self.flags.contains(Flags::P2_INFO) {
497 } else if self.flags.contains(Flags::P2_INFO) {
497 SIZE_FROM_OTHER_PARENT
498 SIZE_FROM_OTHER_PARENT
498 } else if self.removed() {
499 } else if self.removed() {
499 0
500 0
500 } else if self.added() {
501 } else if self.added() {
501 SIZE_NON_NORMAL
502 SIZE_NON_NORMAL
502 } else if let Some((_mode, size)) = self.mode_size {
503 } else if let Some((_mode, size)) = self.mode_size {
503 i32::try_from(size).unwrap()
504 i32::try_from(size).unwrap()
504 } else {
505 } else {
505 SIZE_NON_NORMAL
506 SIZE_NON_NORMAL
506 }
507 }
507 }
508 }
508
509
509 fn v1_mtime(&self) -> i32 {
510 fn v1_mtime(&self) -> i32 {
510 if !self.any_tracked() {
511 if !self.any_tracked() {
511 // TODO: return an Option instead?
512 // TODO: return an Option instead?
512 panic!("Accessing v1_mtime of an untracked DirstateEntry")
513 panic!("Accessing v1_mtime of an untracked DirstateEntry")
513 }
514 }
515
516 #[allow(clippy::if_same_then_else)]
514 if self.removed() {
517 if self.removed() {
515 0
518 0
516 } else if self.flags.contains(Flags::P2_INFO) {
519 } else if self.flags.contains(Flags::P2_INFO) {
517 MTIME_UNSET
520 MTIME_UNSET
518 } else if !self.flags.contains(Flags::P1_TRACKED) {
521 } else if !self.flags.contains(Flags::P1_TRACKED) {
519 MTIME_UNSET
522 MTIME_UNSET
520 } else if let Some(mtime) = self.mtime {
523 } else if let Some(mtime) = self.mtime {
521 if mtime.second_ambiguous {
524 if mtime.second_ambiguous {
522 MTIME_UNSET
525 MTIME_UNSET
523 } else {
526 } else {
524 i32::try_from(mtime.truncated_seconds()).unwrap()
527 i32::try_from(mtime.truncated_seconds()).unwrap()
525 }
528 }
526 } else {
529 } else {
527 MTIME_UNSET
530 MTIME_UNSET
528 }
531 }
529 }
532 }
530
533
531 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
534 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
532 pub fn state(&self) -> EntryState {
535 pub fn state(&self) -> EntryState {
533 self.v1_state()
536 self.v1_state()
534 }
537 }
535
538
536 // TODO: return Option?
539 // TODO: return Option?
537 pub fn mode(&self) -> i32 {
540 pub fn mode(&self) -> i32 {
538 self.v1_mode()
541 self.v1_mode()
539 }
542 }
540
543
541 // TODO: return Option?
544 // TODO: return Option?
542 pub fn size(&self) -> i32 {
545 pub fn size(&self) -> i32 {
543 self.v1_size()
546 self.v1_size()
544 }
547 }
545
548
546 // TODO: return Option?
549 // TODO: return Option?
547 pub fn mtime(&self) -> i32 {
550 pub fn mtime(&self) -> i32 {
548 self.v1_mtime()
551 self.v1_mtime()
549 }
552 }
550
553
551 pub fn get_fallback_exec(&self) -> Option<bool> {
554 pub fn get_fallback_exec(&self) -> Option<bool> {
552 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
555 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
553 Some(self.flags.contains(Flags::FALLBACK_EXEC))
556 Some(self.flags.contains(Flags::FALLBACK_EXEC))
554 } else {
557 } else {
555 None
558 None
556 }
559 }
557 }
560 }
558
561
559 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
562 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
560 match value {
563 match value {
561 None => {
564 None => {
562 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
565 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
563 self.flags.remove(Flags::FALLBACK_EXEC);
566 self.flags.remove(Flags::FALLBACK_EXEC);
564 }
567 }
565 Some(exec) => {
568 Some(exec) => {
566 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
569 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
567 if exec {
570 if exec {
568 self.flags.insert(Flags::FALLBACK_EXEC);
571 self.flags.insert(Flags::FALLBACK_EXEC);
569 }
572 }
570 }
573 }
571 }
574 }
572 }
575 }
573
576
574 pub fn get_fallback_symlink(&self) -> Option<bool> {
577 pub fn get_fallback_symlink(&self) -> Option<bool> {
575 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
578 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
576 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
579 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
577 } else {
580 } else {
578 None
581 None
579 }
582 }
580 }
583 }
581
584
582 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
585 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
583 match value {
586 match value {
584 None => {
587 None => {
585 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
588 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
586 self.flags.remove(Flags::FALLBACK_SYMLINK);
589 self.flags.remove(Flags::FALLBACK_SYMLINK);
587 }
590 }
588 Some(symlink) => {
591 Some(symlink) => {
589 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
592 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
590 if symlink {
593 if symlink {
591 self.flags.insert(Flags::FALLBACK_SYMLINK);
594 self.flags.insert(Flags::FALLBACK_SYMLINK);
592 }
595 }
593 }
596 }
594 }
597 }
595 }
598 }
596
599
597 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
600 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
598 self.mtime
601 self.mtime
599 }
602 }
600
603
601 pub fn drop_merge_data(&mut self) {
604 pub fn drop_merge_data(&mut self) {
602 if self.flags.contains(Flags::P2_INFO) {
605 if self.flags.contains(Flags::P2_INFO) {
603 self.flags.remove(Flags::P2_INFO);
606 self.flags.remove(Flags::P2_INFO);
604 self.mode_size = None;
607 self.mode_size = None;
605 self.mtime = None;
608 self.mtime = None;
606 }
609 }
607 }
610 }
608
611
609 pub fn set_possibly_dirty(&mut self) {
612 pub fn set_possibly_dirty(&mut self) {
610 self.mtime = None
613 self.mtime = None
611 }
614 }
612
615
613 pub fn set_clean(
616 pub fn set_clean(
614 &mut self,
617 &mut self,
615 mode: u32,
618 mode: u32,
616 size: u32,
619 size: u32,
617 mtime: TruncatedTimestamp,
620 mtime: TruncatedTimestamp,
618 ) {
621 ) {
619 let size = size & RANGE_MASK_31BIT;
622 let size = size & RANGE_MASK_31BIT;
620 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
623 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
621 self.mode_size = Some((mode, size));
624 self.mode_size = Some((mode, size));
622 self.mtime = Some(mtime);
625 self.mtime = Some(mtime);
623 }
626 }
624
627
625 pub fn set_tracked(&mut self) {
628 pub fn set_tracked(&mut self) {
626 self.flags.insert(Flags::WDIR_TRACKED);
629 self.flags.insert(Flags::WDIR_TRACKED);
627 // `set_tracked` is replacing various `normallookup` call. So we mark
630 // `set_tracked` is replacing various `normallookup` call. So we mark
628 // the files as needing lookup
631 // the files as needing lookup
629 //
632 //
630 // Consider dropping this in the future in favor of something less
633 // Consider dropping this in the future in favor of something less
631 // broad.
634 // broad.
632 self.mtime = None;
635 self.mtime = None;
633 }
636 }
634
637
635 pub fn set_untracked(&mut self) {
638 pub fn set_untracked(&mut self) {
636 self.flags.remove(Flags::WDIR_TRACKED);
639 self.flags.remove(Flags::WDIR_TRACKED);
637 self.mode_size = None;
640 self.mode_size = None;
638 self.mtime = None;
641 self.mtime = None;
639 }
642 }
640
643
641 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
644 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
642 /// in the dirstate-v1 format.
645 /// in the dirstate-v1 format.
643 ///
646 ///
644 /// This includes marker values such as `mtime == -1`. In the future we may
647 /// 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
648 /// want to not represent these cases that way in memory, but serialization
646 /// will need to keep the same format.
649 /// will need to keep the same format.
647 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
650 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
648 (
651 (
649 self.v1_state().into(),
652 self.v1_state().into(),
650 self.v1_mode(),
653 self.v1_mode(),
651 self.v1_size(),
654 self.v1_size(),
652 self.v1_mtime(),
655 self.v1_mtime(),
653 )
656 )
654 }
657 }
655
658
656 pub(crate) fn is_from_other_parent(&self) -> bool {
659 pub(crate) fn is_from_other_parent(&self) -> bool {
657 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
660 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
658 }
661 }
659
662
660 // TODO: other platforms
663 // TODO: other platforms
661 #[cfg(unix)]
664 #[cfg(unix)]
662 pub fn mode_changed(
665 pub fn mode_changed(
663 &self,
666 &self,
664 filesystem_metadata: &std::fs::Metadata,
667 filesystem_metadata: &std::fs::Metadata,
665 ) -> bool {
668 ) -> bool {
666 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
669 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
667 let fs_exec_bit = has_exec_bit(filesystem_metadata);
670 let fs_exec_bit = has_exec_bit(filesystem_metadata);
668 dirstate_exec_bit != fs_exec_bit
671 dirstate_exec_bit != fs_exec_bit
669 }
672 }
670
673
671 /// Returns a `(state, mode, size, mtime)` tuple as for
674 /// Returns a `(state, mode, size, mtime)` tuple as for
672 /// `DirstateMapMethods::debug_iter`.
675 /// `DirstateMapMethods::debug_iter`.
673 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
676 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
674 (self.state().into(), self.mode(), self.size(), self.mtime())
677 (self.state().into(), self.mode(), self.size(), self.mtime())
675 }
678 }
676 }
679 }
677
680
678 impl EntryState {
681 impl EntryState {
679 pub fn is_tracked(self) -> bool {
682 pub fn is_tracked(self) -> bool {
680 use EntryState::*;
683 use EntryState::*;
681 match self {
684 match self {
682 Normal | Added | Merged => true,
685 Normal | Added | Merged => true,
683 Removed => false,
686 Removed => false,
684 }
687 }
685 }
688 }
686 }
689 }
687
690
688 impl TryFrom<u8> for EntryState {
691 impl TryFrom<u8> for EntryState {
689 type Error = HgError;
692 type Error = HgError;
690
693
691 fn try_from(value: u8) -> Result<Self, Self::Error> {
694 fn try_from(value: u8) -> Result<Self, Self::Error> {
692 match value {
695 match value {
693 b'n' => Ok(EntryState::Normal),
696 b'n' => Ok(EntryState::Normal),
694 b'a' => Ok(EntryState::Added),
697 b'a' => Ok(EntryState::Added),
695 b'r' => Ok(EntryState::Removed),
698 b'r' => Ok(EntryState::Removed),
696 b'm' => Ok(EntryState::Merged),
699 b'm' => Ok(EntryState::Merged),
697 _ => Err(HgError::CorruptedRepository(format!(
700 _ => Err(HgError::CorruptedRepository(format!(
698 "Incorrect dirstate entry state {}",
701 "Incorrect dirstate entry state {}",
699 value
702 value
700 ))),
703 ))),
701 }
704 }
702 }
705 }
703 }
706 }
704
707
705 impl Into<u8> for EntryState {
708 impl Into<u8> for EntryState {
706 fn into(self) -> u8 {
709 fn into(self) -> u8 {
707 match self {
710 match self {
708 EntryState::Normal => b'n',
711 EntryState::Normal => b'n',
709 EntryState::Added => b'a',
712 EntryState::Added => b'a',
710 EntryState::Removed => b'r',
713 EntryState::Removed => b'r',
711 EntryState::Merged => b'm',
714 EntryState::Merged => b'm',
712 }
715 }
713 }
716 }
714 }
717 }
715
718
716 const EXEC_BIT_MASK: u32 = 0o100;
719 const EXEC_BIT_MASK: u32 = 0o100;
717
720
718 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
721 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
719 // TODO: How to handle executable permissions on Windows?
722 // TODO: How to handle executable permissions on Windows?
720 use std::os::unix::fs::MetadataExt;
723 use std::os::unix::fs::MetadataExt;
721 (metadata.mode() & EXEC_BIT_MASK) != 0
724 (metadata.mode() & EXEC_BIT_MASK) != 0
722 }
725 }
General Comments 0
You need to be logged in to leave comments. Login now