|
@@
-1,907
+1,906
b''
|
|
1
|
// status.rs
|
|
1
|
// status.rs
|
|
2
|
//
|
|
2
|
//
|
|
3
|
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
|
|
3
|
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
|
|
4
|
//
|
|
4
|
//
|
|
5
|
// This software may be used and distributed according to the terms of the
|
|
5
|
// This software may be used and distributed according to the terms of the
|
|
6
|
// GNU General Public License version 2 or any later version.
|
|
6
|
// GNU General Public License version 2 or any later version.
|
|
7
|
|
|
7
|
|
|
8
|
//! Rust implementation of dirstate.status (dirstate.py).
|
|
8
|
//! Rust implementation of dirstate.status (dirstate.py).
|
|
9
|
//! It is currently missing a lot of functionality compared to the Python one
|
|
9
|
//! It is currently missing a lot of functionality compared to the Python one
|
|
10
|
//! and will only be triggered in narrow cases.
|
|
10
|
//! and will only be triggered in narrow cases.
|
|
11
|
|
|
11
|
|
|
12
|
use crate::{
|
|
12
|
use crate::{
|
|
13
|
dirstate::SIZE_FROM_OTHER_PARENT,
|
|
13
|
dirstate::SIZE_FROM_OTHER_PARENT,
|
|
14
|
filepatterns::PatternFileWarning,
|
|
14
|
filepatterns::PatternFileWarning,
|
|
15
|
matchers::{get_ignore_function, Matcher, VisitChildrenSet},
|
|
15
|
matchers::{get_ignore_function, Matcher, VisitChildrenSet},
|
|
16
|
operations::Operation,
|
|
|
|
|
17
|
utils::{
|
|
16
|
utils::{
|
|
18
|
files::{find_dirs, HgMetadata},
|
|
17
|
files::{find_dirs, HgMetadata},
|
|
19
|
hg_path::{
|
|
18
|
hg_path::{
|
|
20
|
hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
|
|
19
|
hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
|
|
21
|
HgPathError,
|
|
20
|
HgPathError,
|
|
22
|
},
|
|
21
|
},
|
|
23
|
path_auditor::PathAuditor,
|
|
22
|
path_auditor::PathAuditor,
|
|
24
|
},
|
|
23
|
},
|
|
25
|
CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
|
|
24
|
CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
|
|
26
|
PatternError,
|
|
25
|
PatternError,
|
|
27
|
};
|
|
26
|
};
|
|
28
|
use lazy_static::lazy_static;
|
|
27
|
use lazy_static::lazy_static;
|
|
29
|
use micro_timer::timed;
|
|
28
|
use micro_timer::timed;
|
|
30
|
use rayon::prelude::*;
|
|
29
|
use rayon::prelude::*;
|
|
31
|
use std::{
|
|
30
|
use std::{
|
|
32
|
borrow::Cow,
|
|
31
|
borrow::Cow,
|
|
33
|
collections::HashSet,
|
|
32
|
collections::HashSet,
|
|
34
|
fs::{read_dir, DirEntry},
|
|
33
|
fs::{read_dir, DirEntry},
|
|
35
|
io::ErrorKind,
|
|
34
|
io::ErrorKind,
|
|
36
|
ops::Deref,
|
|
35
|
ops::Deref,
|
|
37
|
path::{Path, PathBuf},
|
|
36
|
path::{Path, PathBuf},
|
|
38
|
};
|
|
37
|
};
|
|
39
|
|
|
38
|
|
|
40
|
/// Wrong type of file from a `BadMatch`
|
|
39
|
/// Wrong type of file from a `BadMatch`
|
|
41
|
/// Note: a lot of those don't exist on all platforms.
|
|
40
|
/// Note: a lot of those don't exist on all platforms.
|
|
42
|
#[derive(Debug, Copy, Clone)]
|
|
41
|
#[derive(Debug, Copy, Clone)]
|
|
43
|
pub enum BadType {
|
|
42
|
pub enum BadType {
|
|
44
|
CharacterDevice,
|
|
43
|
CharacterDevice,
|
|
45
|
BlockDevice,
|
|
44
|
BlockDevice,
|
|
46
|
FIFO,
|
|
45
|
FIFO,
|
|
47
|
Socket,
|
|
46
|
Socket,
|
|
48
|
Directory,
|
|
47
|
Directory,
|
|
49
|
Unknown,
|
|
48
|
Unknown,
|
|
50
|
}
|
|
49
|
}
|
|
51
|
|
|
50
|
|
|
52
|
impl ToString for BadType {
|
|
51
|
impl ToString for BadType {
|
|
53
|
fn to_string(&self) -> String {
|
|
52
|
fn to_string(&self) -> String {
|
|
54
|
match self {
|
|
53
|
match self {
|
|
55
|
BadType::CharacterDevice => "character device",
|
|
54
|
BadType::CharacterDevice => "character device",
|
|
56
|
BadType::BlockDevice => "block device",
|
|
55
|
BadType::BlockDevice => "block device",
|
|
57
|
BadType::FIFO => "fifo",
|
|
56
|
BadType::FIFO => "fifo",
|
|
58
|
BadType::Socket => "socket",
|
|
57
|
BadType::Socket => "socket",
|
|
59
|
BadType::Directory => "directory",
|
|
58
|
BadType::Directory => "directory",
|
|
60
|
BadType::Unknown => "unknown",
|
|
59
|
BadType::Unknown => "unknown",
|
|
61
|
}
|
|
60
|
}
|
|
62
|
.to_string()
|
|
61
|
.to_string()
|
|
63
|
}
|
|
62
|
}
|
|
64
|
}
|
|
63
|
}
|
|
65
|
|
|
64
|
|
|
66
|
/// Was explicitly matched but cannot be found/accessed
|
|
65
|
/// Was explicitly matched but cannot be found/accessed
|
|
67
|
#[derive(Debug, Copy, Clone)]
|
|
66
|
#[derive(Debug, Copy, Clone)]
|
|
68
|
pub enum BadMatch {
|
|
67
|
pub enum BadMatch {
|
|
69
|
OsError(i32),
|
|
68
|
OsError(i32),
|
|
70
|
BadType(BadType),
|
|
69
|
BadType(BadType),
|
|
71
|
}
|
|
70
|
}
|
|
72
|
|
|
71
|
|
|
73
|
/// Enum used to dispatch new status entries into the right collections.
|
|
72
|
/// Enum used to dispatch new status entries into the right collections.
|
|
74
|
/// Is similar to `crate::EntryState`, but represents the transient state of
|
|
73
|
/// Is similar to `crate::EntryState`, but represents the transient state of
|
|
75
|
/// entries during the lifetime of a command.
|
|
74
|
/// entries during the lifetime of a command.
|
|
76
|
#[derive(Debug, Copy, Clone)]
|
|
75
|
#[derive(Debug, Copy, Clone)]
|
|
77
|
pub enum Dispatch {
|
|
76
|
pub enum Dispatch {
|
|
78
|
Unsure,
|
|
77
|
Unsure,
|
|
79
|
Modified,
|
|
78
|
Modified,
|
|
80
|
Added,
|
|
79
|
Added,
|
|
81
|
Removed,
|
|
80
|
Removed,
|
|
82
|
Deleted,
|
|
81
|
Deleted,
|
|
83
|
Clean,
|
|
82
|
Clean,
|
|
84
|
Unknown,
|
|
83
|
Unknown,
|
|
85
|
Ignored,
|
|
84
|
Ignored,
|
|
86
|
/// Empty dispatch, the file is not worth listing
|
|
85
|
/// Empty dispatch, the file is not worth listing
|
|
87
|
None,
|
|
86
|
None,
|
|
88
|
/// Was explicitly matched but cannot be found/accessed
|
|
87
|
/// Was explicitly matched but cannot be found/accessed
|
|
89
|
Bad(BadMatch),
|
|
88
|
Bad(BadMatch),
|
|
90
|
Directory {
|
|
89
|
Directory {
|
|
91
|
/// True if the directory used to be a file in the dmap so we can say
|
|
90
|
/// True if the directory used to be a file in the dmap so we can say
|
|
92
|
/// that it's been removed.
|
|
91
|
/// that it's been removed.
|
|
93
|
was_file: bool,
|
|
92
|
was_file: bool,
|
|
94
|
},
|
|
93
|
},
|
|
95
|
}
|
|
94
|
}
|
|
96
|
|
|
95
|
|
|
97
|
type IoResult<T> = std::io::Result<T>;
|
|
96
|
type IoResult<T> = std::io::Result<T>;
|
|
98
|
|
|
97
|
|
|
99
|
/// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
|
|
98
|
/// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
|
|
100
|
/// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
|
|
99
|
/// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
|
|
101
|
type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
|
|
100
|
type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
|
|
102
|
|
|
101
|
|
|
103
|
/// We have a good mix of owned (from directory traversal) and borrowed (from
|
|
102
|
/// We have a good mix of owned (from directory traversal) and borrowed (from
|
|
104
|
/// the dirstate/explicit) paths, this comes up a lot.
|
|
103
|
/// the dirstate/explicit) paths, this comes up a lot.
|
|
105
|
pub type HgPathCow<'a> = Cow<'a, HgPath>;
|
|
104
|
pub type HgPathCow<'a> = Cow<'a, HgPath>;
|
|
106
|
|
|
105
|
|
|
107
|
/// A path with its computed ``Dispatch`` information
|
|
106
|
/// A path with its computed ``Dispatch`` information
|
|
108
|
type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
|
|
107
|
type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
|
|
109
|
|
|
108
|
|
|
110
|
/// Dates and times that are outside the 31-bit signed range are compared
|
|
109
|
/// Dates and times that are outside the 31-bit signed range are compared
|
|
111
|
/// modulo 2^31. This should prevent hg from behaving badly with very large
|
|
110
|
/// modulo 2^31. This should prevent hg from behaving badly with very large
|
|
112
|
/// files or corrupt dates while still having a high probability of detecting
|
|
111
|
/// files or corrupt dates while still having a high probability of detecting
|
|
113
|
/// changes. (issue2608)
|
|
112
|
/// changes. (issue2608)
|
|
114
|
/// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
|
|
113
|
/// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
|
|
115
|
/// is not defined for `i32`, and there is no `As` trait. This forces the
|
|
114
|
/// is not defined for `i32`, and there is no `As` trait. This forces the
|
|
116
|
/// caller to cast `b` as `i32`.
|
|
115
|
/// caller to cast `b` as `i32`.
|
|
117
|
fn mod_compare(a: i32, b: i32) -> bool {
|
|
116
|
fn mod_compare(a: i32, b: i32) -> bool {
|
|
118
|
a & i32::max_value() != b & i32::max_value()
|
|
117
|
a & i32::max_value() != b & i32::max_value()
|
|
119
|
}
|
|
118
|
}
|
|
120
|
|
|
119
|
|
|
121
|
/// Return a sorted list containing information about the entries
|
|
120
|
/// Return a sorted list containing information about the entries
|
|
122
|
/// in the directory.
|
|
121
|
/// in the directory.
|
|
123
|
///
|
|
122
|
///
|
|
124
|
/// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
|
|
123
|
/// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
|
|
125
|
fn list_directory(
|
|
124
|
fn list_directory(
|
|
126
|
path: impl AsRef<Path>,
|
|
125
|
path: impl AsRef<Path>,
|
|
127
|
skip_dot_hg: bool,
|
|
126
|
skip_dot_hg: bool,
|
|
128
|
) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
|
|
127
|
) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
|
|
129
|
let mut results = vec![];
|
|
128
|
let mut results = vec![];
|
|
130
|
let entries = read_dir(path.as_ref())?;
|
|
129
|
let entries = read_dir(path.as_ref())?;
|
|
131
|
|
|
130
|
|
|
132
|
for entry in entries {
|
|
131
|
for entry in entries {
|
|
133
|
let entry = entry?;
|
|
132
|
let entry = entry?;
|
|
134
|
let filename = os_string_to_hg_path_buf(entry.file_name())?;
|
|
133
|
let filename = os_string_to_hg_path_buf(entry.file_name())?;
|
|
135
|
let file_type = entry.file_type()?;
|
|
134
|
let file_type = entry.file_type()?;
|
|
136
|
if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
|
|
135
|
if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
|
|
137
|
return Ok(vec![]);
|
|
136
|
return Ok(vec![]);
|
|
138
|
} else {
|
|
137
|
} else {
|
|
139
|
results.push((filename, entry))
|
|
138
|
results.push((filename, entry))
|
|
140
|
}
|
|
139
|
}
|
|
141
|
}
|
|
140
|
}
|
|
142
|
|
|
141
|
|
|
143
|
results.sort_unstable_by_key(|e| e.0.clone());
|
|
142
|
results.sort_unstable_by_key(|e| e.0.clone());
|
|
144
|
Ok(results)
|
|
143
|
Ok(results)
|
|
145
|
}
|
|
144
|
}
|
|
146
|
|
|
145
|
|
|
147
|
/// The file corresponding to the dirstate entry was found on the filesystem.
|
|
146
|
/// The file corresponding to the dirstate entry was found on the filesystem.
|
|
148
|
fn dispatch_found(
|
|
147
|
fn dispatch_found(
|
|
149
|
filename: impl AsRef<HgPath>,
|
|
148
|
filename: impl AsRef<HgPath>,
|
|
150
|
entry: DirstateEntry,
|
|
149
|
entry: DirstateEntry,
|
|
151
|
metadata: HgMetadata,
|
|
150
|
metadata: HgMetadata,
|
|
152
|
copy_map: &CopyMap,
|
|
151
|
copy_map: &CopyMap,
|
|
153
|
options: StatusOptions,
|
|
152
|
options: StatusOptions,
|
|
154
|
) -> Dispatch {
|
|
153
|
) -> Dispatch {
|
|
155
|
let DirstateEntry {
|
|
154
|
let DirstateEntry {
|
|
156
|
state,
|
|
155
|
state,
|
|
157
|
mode,
|
|
156
|
mode,
|
|
158
|
mtime,
|
|
157
|
mtime,
|
|
159
|
size,
|
|
158
|
size,
|
|
160
|
} = entry;
|
|
159
|
} = entry;
|
|
161
|
|
|
160
|
|
|
162
|
let HgMetadata {
|
|
161
|
let HgMetadata {
|
|
163
|
st_mode,
|
|
162
|
st_mode,
|
|
164
|
st_size,
|
|
163
|
st_size,
|
|
165
|
st_mtime,
|
|
164
|
st_mtime,
|
|
166
|
..
|
|
165
|
..
|
|
167
|
} = metadata;
|
|
166
|
} = metadata;
|
|
168
|
|
|
167
|
|
|
169
|
match state {
|
|
168
|
match state {
|
|
170
|
EntryState::Normal => {
|
|
169
|
EntryState::Normal => {
|
|
171
|
let size_changed = mod_compare(size, st_size as i32);
|
|
170
|
let size_changed = mod_compare(size, st_size as i32);
|
|
172
|
let mode_changed =
|
|
171
|
let mode_changed =
|
|
173
|
(mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
|
|
172
|
(mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
|
|
174
|
let metadata_changed = size >= 0 && (size_changed || mode_changed);
|
|
173
|
let metadata_changed = size >= 0 && (size_changed || mode_changed);
|
|
175
|
let other_parent = size == SIZE_FROM_OTHER_PARENT;
|
|
174
|
let other_parent = size == SIZE_FROM_OTHER_PARENT;
|
|
176
|
|
|
175
|
|
|
177
|
if metadata_changed
|
|
176
|
if metadata_changed
|
|
178
|
|| other_parent
|
|
177
|
|| other_parent
|
|
179
|
|| copy_map.contains_key(filename.as_ref())
|
|
178
|
|| copy_map.contains_key(filename.as_ref())
|
|
180
|
{
|
|
179
|
{
|
|
181
|
Dispatch::Modified
|
|
180
|
Dispatch::Modified
|
|
182
|
} else if mod_compare(mtime, st_mtime as i32)
|
|
181
|
} else if mod_compare(mtime, st_mtime as i32)
|
|
183
|
|| st_mtime == options.last_normal_time
|
|
182
|
|| st_mtime == options.last_normal_time
|
|
184
|
{
|
|
183
|
{
|
|
185
|
// the file may have just been marked as normal and
|
|
184
|
// the file may have just been marked as normal and
|
|
186
|
// it may have changed in the same second without
|
|
185
|
// it may have changed in the same second without
|
|
187
|
// changing its size. This can happen if we quickly
|
|
186
|
// changing its size. This can happen if we quickly
|
|
188
|
// do multiple commits. Force lookup, so we don't
|
|
187
|
// do multiple commits. Force lookup, so we don't
|
|
189
|
// miss such a racy file change.
|
|
188
|
// miss such a racy file change.
|
|
190
|
Dispatch::Unsure
|
|
189
|
Dispatch::Unsure
|
|
191
|
} else if options.list_clean {
|
|
190
|
} else if options.list_clean {
|
|
192
|
Dispatch::Clean
|
|
191
|
Dispatch::Clean
|
|
193
|
} else {
|
|
192
|
} else {
|
|
194
|
Dispatch::None
|
|
193
|
Dispatch::None
|
|
195
|
}
|
|
194
|
}
|
|
196
|
}
|
|
195
|
}
|
|
197
|
EntryState::Merged => Dispatch::Modified,
|
|
196
|
EntryState::Merged => Dispatch::Modified,
|
|
198
|
EntryState::Added => Dispatch::Added,
|
|
197
|
EntryState::Added => Dispatch::Added,
|
|
199
|
EntryState::Removed => Dispatch::Removed,
|
|
198
|
EntryState::Removed => Dispatch::Removed,
|
|
200
|
EntryState::Unknown => Dispatch::Unknown,
|
|
199
|
EntryState::Unknown => Dispatch::Unknown,
|
|
201
|
}
|
|
200
|
}
|
|
202
|
}
|
|
201
|
}
|
|
203
|
|
|
202
|
|
|
204
|
/// The file corresponding to this Dirstate entry is missing.
|
|
203
|
/// The file corresponding to this Dirstate entry is missing.
|
|
205
|
fn dispatch_missing(state: EntryState) -> Dispatch {
|
|
204
|
fn dispatch_missing(state: EntryState) -> Dispatch {
|
|
206
|
match state {
|
|
205
|
match state {
|
|
207
|
// File was removed from the filesystem during commands
|
|
206
|
// File was removed from the filesystem during commands
|
|
208
|
EntryState::Normal | EntryState::Merged | EntryState::Added => {
|
|
207
|
EntryState::Normal | EntryState::Merged | EntryState::Added => {
|
|
209
|
Dispatch::Deleted
|
|
208
|
Dispatch::Deleted
|
|
210
|
}
|
|
209
|
}
|
|
211
|
// File was removed, everything is normal
|
|
210
|
// File was removed, everything is normal
|
|
212
|
EntryState::Removed => Dispatch::Removed,
|
|
211
|
EntryState::Removed => Dispatch::Removed,
|
|
213
|
// File is unknown to Mercurial, everything is normal
|
|
212
|
// File is unknown to Mercurial, everything is normal
|
|
214
|
EntryState::Unknown => Dispatch::Unknown,
|
|
213
|
EntryState::Unknown => Dispatch::Unknown,
|
|
215
|
}
|
|
214
|
}
|
|
216
|
}
|
|
215
|
}
|
|
217
|
|
|
216
|
|
|
218
|
lazy_static! {
|
|
217
|
lazy_static! {
|
|
219
|
static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
|
|
218
|
static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
|
|
220
|
let mut h = HashSet::new();
|
|
219
|
let mut h = HashSet::new();
|
|
221
|
h.insert(HgPath::new(b""));
|
|
220
|
h.insert(HgPath::new(b""));
|
|
222
|
h
|
|
221
|
h
|
|
223
|
};
|
|
222
|
};
|
|
224
|
}
|
|
223
|
}
|
|
225
|
|
|
224
|
|
|
226
|
#[derive(Debug, Copy, Clone)]
|
|
225
|
#[derive(Debug, Copy, Clone)]
|
|
227
|
pub struct StatusOptions {
|
|
226
|
pub struct StatusOptions {
|
|
228
|
/// Remember the most recent modification timeslot for status, to make
|
|
227
|
/// Remember the most recent modification timeslot for status, to make
|
|
229
|
/// sure we won't miss future size-preserving file content modifications
|
|
228
|
/// sure we won't miss future size-preserving file content modifications
|
|
230
|
/// that happen within the same timeslot.
|
|
229
|
/// that happen within the same timeslot.
|
|
231
|
pub last_normal_time: i64,
|
|
230
|
pub last_normal_time: i64,
|
|
232
|
/// Whether we are on a filesystem with UNIX-like exec flags
|
|
231
|
/// Whether we are on a filesystem with UNIX-like exec flags
|
|
233
|
pub check_exec: bool,
|
|
232
|
pub check_exec: bool,
|
|
234
|
pub list_clean: bool,
|
|
233
|
pub list_clean: bool,
|
|
235
|
pub list_unknown: bool,
|
|
234
|
pub list_unknown: bool,
|
|
236
|
pub list_ignored: bool,
|
|
235
|
pub list_ignored: bool,
|
|
237
|
/// Whether to collect traversed dirs for applying a callback later.
|
|
236
|
/// Whether to collect traversed dirs for applying a callback later.
|
|
238
|
/// Used by `hg purge` for example.
|
|
237
|
/// Used by `hg purge` for example.
|
|
239
|
pub collect_traversed_dirs: bool,
|
|
238
|
pub collect_traversed_dirs: bool,
|
|
240
|
}
|
|
239
|
}
|
|
241
|
|
|
240
|
|
|
242
|
#[derive(Debug)]
|
|
241
|
#[derive(Debug)]
|
|
243
|
pub struct DirstateStatus<'a> {
|
|
242
|
pub struct DirstateStatus<'a> {
|
|
244
|
pub modified: Vec<HgPathCow<'a>>,
|
|
243
|
pub modified: Vec<HgPathCow<'a>>,
|
|
245
|
pub added: Vec<HgPathCow<'a>>,
|
|
244
|
pub added: Vec<HgPathCow<'a>>,
|
|
246
|
pub removed: Vec<HgPathCow<'a>>,
|
|
245
|
pub removed: Vec<HgPathCow<'a>>,
|
|
247
|
pub deleted: Vec<HgPathCow<'a>>,
|
|
246
|
pub deleted: Vec<HgPathCow<'a>>,
|
|
248
|
pub clean: Vec<HgPathCow<'a>>,
|
|
247
|
pub clean: Vec<HgPathCow<'a>>,
|
|
249
|
pub ignored: Vec<HgPathCow<'a>>,
|
|
248
|
pub ignored: Vec<HgPathCow<'a>>,
|
|
250
|
pub unknown: Vec<HgPathCow<'a>>,
|
|
249
|
pub unknown: Vec<HgPathCow<'a>>,
|
|
251
|
pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
|
|
250
|
pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
|
|
252
|
/// Only filled if `collect_traversed_dirs` is `true`
|
|
251
|
/// Only filled if `collect_traversed_dirs` is `true`
|
|
253
|
pub traversed: Vec<HgPathBuf>,
|
|
252
|
pub traversed: Vec<HgPathBuf>,
|
|
254
|
}
|
|
253
|
}
|
|
255
|
|
|
254
|
|
|
256
|
#[derive(Debug)]
|
|
255
|
#[derive(Debug)]
|
|
257
|
pub enum StatusError {
|
|
256
|
pub enum StatusError {
|
|
258
|
/// Generic IO error
|
|
257
|
/// Generic IO error
|
|
259
|
IO(std::io::Error),
|
|
258
|
IO(std::io::Error),
|
|
260
|
/// An invalid path that cannot be represented in Mercurial was found
|
|
259
|
/// An invalid path that cannot be represented in Mercurial was found
|
|
261
|
Path(HgPathError),
|
|
260
|
Path(HgPathError),
|
|
262
|
/// An invalid "ignore" pattern was found
|
|
261
|
/// An invalid "ignore" pattern was found
|
|
263
|
Pattern(PatternError),
|
|
262
|
Pattern(PatternError),
|
|
264
|
}
|
|
263
|
}
|
|
265
|
|
|
264
|
|
|
266
|
pub type StatusResult<T> = Result<T, StatusError>;
|
|
265
|
pub type StatusResult<T> = Result<T, StatusError>;
|
|
267
|
|
|
266
|
|
|
268
|
impl From<PatternError> for StatusError {
|
|
267
|
impl From<PatternError> for StatusError {
|
|
269
|
fn from(e: PatternError) -> Self {
|
|
268
|
fn from(e: PatternError) -> Self {
|
|
270
|
StatusError::Pattern(e)
|
|
269
|
StatusError::Pattern(e)
|
|
271
|
}
|
|
270
|
}
|
|
272
|
}
|
|
271
|
}
|
|
273
|
impl From<HgPathError> for StatusError {
|
|
272
|
impl From<HgPathError> for StatusError {
|
|
274
|
fn from(e: HgPathError) -> Self {
|
|
273
|
fn from(e: HgPathError) -> Self {
|
|
275
|
StatusError::Path(e)
|
|
274
|
StatusError::Path(e)
|
|
276
|
}
|
|
275
|
}
|
|
277
|
}
|
|
276
|
}
|
|
278
|
impl From<std::io::Error> for StatusError {
|
|
277
|
impl From<std::io::Error> for StatusError {
|
|
279
|
fn from(e: std::io::Error) -> Self {
|
|
278
|
fn from(e: std::io::Error) -> Self {
|
|
280
|
StatusError::IO(e)
|
|
279
|
StatusError::IO(e)
|
|
281
|
}
|
|
280
|
}
|
|
282
|
}
|
|
281
|
}
|
|
283
|
|
|
282
|
|
|
284
|
impl ToString for StatusError {
|
|
283
|
impl ToString for StatusError {
|
|
285
|
fn to_string(&self) -> String {
|
|
284
|
fn to_string(&self) -> String {
|
|
286
|
match self {
|
|
285
|
match self {
|
|
287
|
StatusError::IO(e) => e.to_string(),
|
|
286
|
StatusError::IO(e) => e.to_string(),
|
|
288
|
StatusError::Path(e) => e.to_string(),
|
|
287
|
StatusError::Path(e) => e.to_string(),
|
|
289
|
StatusError::Pattern(e) => e.to_string(),
|
|
288
|
StatusError::Pattern(e) => e.to_string(),
|
|
290
|
}
|
|
289
|
}
|
|
291
|
}
|
|
290
|
}
|
|
292
|
}
|
|
291
|
}
|
|
293
|
|
|
292
|
|
|
294
|
/// Gives information about which files are changed in the working directory
|
|
293
|
/// Gives information about which files are changed in the working directory
|
|
295
|
/// and how, compared to the revision we're based on
|
|
294
|
/// and how, compared to the revision we're based on
|
|
296
|
pub struct Status<'a, M: Matcher + Sync> {
|
|
295
|
pub struct Status<'a, M: Matcher + Sync> {
|
|
297
|
dmap: &'a DirstateMap,
|
|
296
|
dmap: &'a DirstateMap,
|
|
298
|
pub(crate) matcher: &'a M,
|
|
297
|
pub(crate) matcher: &'a M,
|
|
299
|
root_dir: PathBuf,
|
|
298
|
root_dir: PathBuf,
|
|
300
|
pub(crate) options: StatusOptions,
|
|
299
|
pub(crate) options: StatusOptions,
|
|
301
|
ignore_fn: IgnoreFnType<'a>,
|
|
300
|
ignore_fn: IgnoreFnType<'a>,
|
|
302
|
}
|
|
301
|
}
|
|
303
|
|
|
302
|
|
|
304
|
impl<'a, M> Status<'a, M>
|
|
303
|
impl<'a, M> Status<'a, M>
|
|
305
|
where
|
|
304
|
where
|
|
306
|
M: Matcher + Sync,
|
|
305
|
M: Matcher + Sync,
|
|
307
|
{
|
|
306
|
{
|
|
308
|
pub fn new(
|
|
307
|
pub fn new(
|
|
309
|
dmap: &'a DirstateMap,
|
|
308
|
dmap: &'a DirstateMap,
|
|
310
|
matcher: &'a M,
|
|
309
|
matcher: &'a M,
|
|
311
|
root_dir: PathBuf,
|
|
310
|
root_dir: PathBuf,
|
|
312
|
ignore_files: Vec<PathBuf>,
|
|
311
|
ignore_files: Vec<PathBuf>,
|
|
313
|
options: StatusOptions,
|
|
312
|
options: StatusOptions,
|
|
314
|
) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
|
|
313
|
) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
|
|
315
|
// Needs to outlive `dir_ignore_fn` since it's captured.
|
|
314
|
// Needs to outlive `dir_ignore_fn` since it's captured.
|
|
316
|
|
|
315
|
|
|
317
|
let (ignore_fn, warnings): (IgnoreFnType, _) =
|
|
316
|
let (ignore_fn, warnings): (IgnoreFnType, _) =
|
|
318
|
if options.list_ignored || options.list_unknown {
|
|
317
|
if options.list_ignored || options.list_unknown {
|
|
319
|
get_ignore_function(ignore_files, &root_dir)?
|
|
318
|
get_ignore_function(ignore_files, &root_dir)?
|
|
320
|
} else {
|
|
319
|
} else {
|
|
321
|
(Box::new(|&_| true), vec![])
|
|
320
|
(Box::new(|&_| true), vec![])
|
|
322
|
};
|
|
321
|
};
|
|
323
|
|
|
322
|
|
|
324
|
Ok((
|
|
323
|
Ok((
|
|
325
|
Self {
|
|
324
|
Self {
|
|
326
|
dmap,
|
|
325
|
dmap,
|
|
327
|
matcher,
|
|
326
|
matcher,
|
|
328
|
root_dir,
|
|
327
|
root_dir,
|
|
329
|
options,
|
|
328
|
options,
|
|
330
|
ignore_fn,
|
|
329
|
ignore_fn,
|
|
331
|
},
|
|
330
|
},
|
|
332
|
warnings,
|
|
331
|
warnings,
|
|
333
|
))
|
|
332
|
))
|
|
334
|
}
|
|
333
|
}
|
|
335
|
|
|
334
|
|
|
336
|
/// Is the path ignored?
|
|
335
|
/// Is the path ignored?
|
|
337
|
pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
|
|
336
|
pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
|
|
338
|
(self.ignore_fn)(path.as_ref())
|
|
337
|
(self.ignore_fn)(path.as_ref())
|
|
339
|
}
|
|
338
|
}
|
|
340
|
|
|
339
|
|
|
341
|
/// Is the path or one of its ancestors ignored?
|
|
340
|
/// Is the path or one of its ancestors ignored?
|
|
342
|
pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
|
|
341
|
pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
|
|
343
|
// Only involve ignore mechanism if we're listing unknowns or ignored.
|
|
342
|
// Only involve ignore mechanism if we're listing unknowns or ignored.
|
|
344
|
if self.options.list_ignored || self.options.list_unknown {
|
|
343
|
if self.options.list_ignored || self.options.list_unknown {
|
|
345
|
if self.is_ignored(&dir) {
|
|
344
|
if self.is_ignored(&dir) {
|
|
346
|
true
|
|
345
|
true
|
|
347
|
} else {
|
|
346
|
} else {
|
|
348
|
for p in find_dirs(dir.as_ref()) {
|
|
347
|
for p in find_dirs(dir.as_ref()) {
|
|
349
|
if self.is_ignored(p) {
|
|
348
|
if self.is_ignored(p) {
|
|
350
|
return true;
|
|
349
|
return true;
|
|
351
|
}
|
|
350
|
}
|
|
352
|
}
|
|
351
|
}
|
|
353
|
false
|
|
352
|
false
|
|
354
|
}
|
|
353
|
}
|
|
355
|
} else {
|
|
354
|
} else {
|
|
356
|
true
|
|
355
|
true
|
|
357
|
}
|
|
356
|
}
|
|
358
|
}
|
|
357
|
}
|
|
359
|
|
|
358
|
|
|
360
|
/// Get stat data about the files explicitly specified by the matcher.
|
|
359
|
/// Get stat data about the files explicitly specified by the matcher.
|
|
361
|
/// Returns a tuple of the directories that need to be traversed and the
|
|
360
|
/// Returns a tuple of the directories that need to be traversed and the
|
|
362
|
/// files with their corresponding `Dispatch`.
|
|
361
|
/// files with their corresponding `Dispatch`.
|
|
363
|
/// TODO subrepos
|
|
362
|
/// TODO subrepos
|
|
364
|
#[timed]
|
|
363
|
#[timed]
|
|
365
|
pub fn walk_explicit(
|
|
364
|
pub fn walk_explicit(
|
|
366
|
&self,
|
|
365
|
&self,
|
|
367
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
366
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
368
|
) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
|
|
367
|
) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
|
|
369
|
self.matcher
|
|
368
|
self.matcher
|
|
370
|
.file_set()
|
|
369
|
.file_set()
|
|
371
|
.unwrap_or(&DEFAULT_WORK)
|
|
370
|
.unwrap_or(&DEFAULT_WORK)
|
|
372
|
.par_iter()
|
|
371
|
.par_iter()
|
|
373
|
.map(|&filename| -> Option<IoResult<_>> {
|
|
372
|
.map(|&filename| -> Option<IoResult<_>> {
|
|
374
|
// TODO normalization
|
|
373
|
// TODO normalization
|
|
375
|
let normalized = filename;
|
|
374
|
let normalized = filename;
|
|
376
|
|
|
375
|
|
|
377
|
let buf = match hg_path_to_path_buf(normalized) {
|
|
376
|
let buf = match hg_path_to_path_buf(normalized) {
|
|
378
|
Ok(x) => x,
|
|
377
|
Ok(x) => x,
|
|
379
|
Err(e) => return Some(Err(e.into())),
|
|
378
|
Err(e) => return Some(Err(e.into())),
|
|
380
|
};
|
|
379
|
};
|
|
381
|
let target = self.root_dir.join(buf);
|
|
380
|
let target = self.root_dir.join(buf);
|
|
382
|
let st = target.symlink_metadata();
|
|
381
|
let st = target.symlink_metadata();
|
|
383
|
let in_dmap = self.dmap.get(normalized);
|
|
382
|
let in_dmap = self.dmap.get(normalized);
|
|
384
|
match st {
|
|
383
|
match st {
|
|
385
|
Ok(meta) => {
|
|
384
|
Ok(meta) => {
|
|
386
|
let file_type = meta.file_type();
|
|
385
|
let file_type = meta.file_type();
|
|
387
|
return if file_type.is_file() || file_type.is_symlink()
|
|
386
|
return if file_type.is_file() || file_type.is_symlink()
|
|
388
|
{
|
|
387
|
{
|
|
389
|
if let Some(entry) = in_dmap {
|
|
388
|
if let Some(entry) = in_dmap {
|
|
390
|
return Some(Ok((
|
|
389
|
return Some(Ok((
|
|
391
|
Cow::Borrowed(normalized),
|
|
390
|
Cow::Borrowed(normalized),
|
|
392
|
dispatch_found(
|
|
391
|
dispatch_found(
|
|
393
|
&normalized,
|
|
392
|
&normalized,
|
|
394
|
*entry,
|
|
393
|
*entry,
|
|
395
|
HgMetadata::from_metadata(meta),
|
|
394
|
HgMetadata::from_metadata(meta),
|
|
396
|
&self.dmap.copy_map,
|
|
395
|
&self.dmap.copy_map,
|
|
397
|
self.options,
|
|
396
|
self.options,
|
|
398
|
),
|
|
397
|
),
|
|
399
|
)));
|
|
398
|
)));
|
|
400
|
}
|
|
399
|
}
|
|
401
|
Some(Ok((
|
|
400
|
Some(Ok((
|
|
402
|
Cow::Borrowed(normalized),
|
|
401
|
Cow::Borrowed(normalized),
|
|
403
|
Dispatch::Unknown,
|
|
402
|
Dispatch::Unknown,
|
|
404
|
)))
|
|
403
|
)))
|
|
405
|
} else if file_type.is_dir() {
|
|
404
|
} else if file_type.is_dir() {
|
|
406
|
if self.options.collect_traversed_dirs {
|
|
405
|
if self.options.collect_traversed_dirs {
|
|
407
|
traversed_sender
|
|
406
|
traversed_sender
|
|
408
|
.send(normalized.to_owned())
|
|
407
|
.send(normalized.to_owned())
|
|
409
|
.expect("receiver should outlive sender");
|
|
408
|
.expect("receiver should outlive sender");
|
|
410
|
}
|
|
409
|
}
|
|
411
|
Some(Ok((
|
|
410
|
Some(Ok((
|
|
412
|
Cow::Borrowed(normalized),
|
|
411
|
Cow::Borrowed(normalized),
|
|
413
|
Dispatch::Directory {
|
|
412
|
Dispatch::Directory {
|
|
414
|
was_file: in_dmap.is_some(),
|
|
413
|
was_file: in_dmap.is_some(),
|
|
415
|
},
|
|
414
|
},
|
|
416
|
)))
|
|
415
|
)))
|
|
417
|
} else {
|
|
416
|
} else {
|
|
418
|
Some(Ok((
|
|
417
|
Some(Ok((
|
|
419
|
Cow::Borrowed(normalized),
|
|
418
|
Cow::Borrowed(normalized),
|
|
420
|
Dispatch::Bad(BadMatch::BadType(
|
|
419
|
Dispatch::Bad(BadMatch::BadType(
|
|
421
|
// TODO do more than unknown
|
|
420
|
// TODO do more than unknown
|
|
422
|
// Support for all `BadType` variant
|
|
421
|
// Support for all `BadType` variant
|
|
423
|
// varies greatly between platforms.
|
|
422
|
// varies greatly between platforms.
|
|
424
|
// So far, no tests check the type and
|
|
423
|
// So far, no tests check the type and
|
|
425
|
// this should be good enough for most
|
|
424
|
// this should be good enough for most
|
|
426
|
// users.
|
|
425
|
// users.
|
|
427
|
BadType::Unknown,
|
|
426
|
BadType::Unknown,
|
|
428
|
)),
|
|
427
|
)),
|
|
429
|
)))
|
|
428
|
)))
|
|
430
|
};
|
|
429
|
};
|
|
431
|
}
|
|
430
|
}
|
|
432
|
Err(_) => {
|
|
431
|
Err(_) => {
|
|
433
|
if let Some(entry) = in_dmap {
|
|
432
|
if let Some(entry) = in_dmap {
|
|
434
|
return Some(Ok((
|
|
433
|
return Some(Ok((
|
|
435
|
Cow::Borrowed(normalized),
|
|
434
|
Cow::Borrowed(normalized),
|
|
436
|
dispatch_missing(entry.state),
|
|
435
|
dispatch_missing(entry.state),
|
|
437
|
)));
|
|
436
|
)));
|
|
438
|
}
|
|
437
|
}
|
|
439
|
}
|
|
438
|
}
|
|
440
|
};
|
|
439
|
};
|
|
441
|
None
|
|
440
|
None
|
|
442
|
})
|
|
441
|
})
|
|
443
|
.flatten()
|
|
442
|
.flatten()
|
|
444
|
.filter_map(Result::ok)
|
|
443
|
.filter_map(Result::ok)
|
|
445
|
.partition(|(_, dispatch)| match dispatch {
|
|
444
|
.partition(|(_, dispatch)| match dispatch {
|
|
446
|
Dispatch::Directory { .. } => true,
|
|
445
|
Dispatch::Directory { .. } => true,
|
|
447
|
_ => false,
|
|
446
|
_ => false,
|
|
448
|
})
|
|
447
|
})
|
|
449
|
}
|
|
448
|
}
|
|
450
|
|
|
449
|
|
|
451
|
/// Walk the working directory recursively to look for changes compared to
|
|
450
|
/// Walk the working directory recursively to look for changes compared to
|
|
452
|
/// the current `DirstateMap`.
|
|
451
|
/// the current `DirstateMap`.
|
|
453
|
///
|
|
452
|
///
|
|
454
|
/// This takes a mutable reference to the results to account for the
|
|
453
|
/// This takes a mutable reference to the results to account for the
|
|
455
|
/// `extend` in timings
|
|
454
|
/// `extend` in timings
|
|
456
|
#[timed]
|
|
455
|
#[timed]
|
|
457
|
pub fn traverse(
|
|
456
|
pub fn traverse(
|
|
458
|
&self,
|
|
457
|
&self,
|
|
459
|
path: impl AsRef<HgPath>,
|
|
458
|
path: impl AsRef<HgPath>,
|
|
460
|
old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
|
|
459
|
old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
|
|
461
|
results: &mut Vec<DispatchedPath<'a>>,
|
|
460
|
results: &mut Vec<DispatchedPath<'a>>,
|
|
462
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
461
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
463
|
) -> IoResult<()> {
|
|
462
|
) -> IoResult<()> {
|
|
464
|
// The traversal is done in parallel, so use a channel to gather
|
|
463
|
// The traversal is done in parallel, so use a channel to gather
|
|
465
|
// entries. `crossbeam::Sender` is `Sync`, while `mpsc::Sender`
|
|
464
|
// entries. `crossbeam::Sender` is `Sync`, while `mpsc::Sender`
|
|
466
|
// is not.
|
|
465
|
// is not.
|
|
467
|
let (files_transmitter, files_receiver) =
|
|
466
|
let (files_transmitter, files_receiver) =
|
|
468
|
crossbeam::channel::unbounded();
|
|
467
|
crossbeam::channel::unbounded();
|
|
469
|
|
|
468
|
|
|
470
|
self.traverse_dir(
|
|
469
|
self.traverse_dir(
|
|
471
|
&files_transmitter,
|
|
470
|
&files_transmitter,
|
|
472
|
path,
|
|
471
|
path,
|
|
473
|
&old_results,
|
|
472
|
&old_results,
|
|
474
|
traversed_sender,
|
|
473
|
traversed_sender,
|
|
475
|
)?;
|
|
474
|
)?;
|
|
476
|
|
|
475
|
|
|
477
|
// Disconnect the channel so the receiver stops waiting
|
|
476
|
// Disconnect the channel so the receiver stops waiting
|
|
478
|
drop(files_transmitter);
|
|
477
|
drop(files_transmitter);
|
|
479
|
|
|
478
|
|
|
480
|
// TODO don't collect. Find a way of replicating the behavior of
|
|
479
|
// TODO don't collect. Find a way of replicating the behavior of
|
|
481
|
// `itertools::process_results`, but for `rayon::ParallelIterator`
|
|
480
|
// `itertools::process_results`, but for `rayon::ParallelIterator`
|
|
482
|
let new_results: IoResult<Vec<(Cow<HgPath>, Dispatch)>> =
|
|
481
|
let new_results: IoResult<Vec<(Cow<HgPath>, Dispatch)>> =
|
|
483
|
files_receiver
|
|
482
|
files_receiver
|
|
484
|
.into_iter()
|
|
483
|
.into_iter()
|
|
485
|
.map(|item| {
|
|
484
|
.map(|item| {
|
|
486
|
let (f, d) = item?;
|
|
485
|
let (f, d) = item?;
|
|
487
|
Ok((Cow::Owned(f), d))
|
|
486
|
Ok((Cow::Owned(f), d))
|
|
488
|
})
|
|
487
|
})
|
|
489
|
.collect();
|
|
488
|
.collect();
|
|
490
|
|
|
489
|
|
|
491
|
results.par_extend(new_results?);
|
|
490
|
results.par_extend(new_results?);
|
|
492
|
|
|
491
|
|
|
493
|
Ok(())
|
|
492
|
Ok(())
|
|
494
|
}
|
|
493
|
}
|
|
495
|
|
|
494
|
|
|
496
|
/// Dispatch a single entry (file, folder, symlink...) found during
|
|
495
|
/// Dispatch a single entry (file, folder, symlink...) found during
|
|
497
|
/// `traverse`. If the entry is a folder that needs to be traversed, it
|
|
496
|
/// `traverse`. If the entry is a folder that needs to be traversed, it
|
|
498
|
/// will be handled in a separate thread.
|
|
497
|
/// will be handled in a separate thread.
|
|
499
|
fn handle_traversed_entry<'b>(
|
|
498
|
fn handle_traversed_entry<'b>(
|
|
500
|
&'a self,
|
|
499
|
&'a self,
|
|
501
|
scope: &rayon::Scope<'b>,
|
|
500
|
scope: &rayon::Scope<'b>,
|
|
502
|
files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
501
|
files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
503
|
old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
|
|
502
|
old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
|
|
504
|
filename: HgPathBuf,
|
|
503
|
filename: HgPathBuf,
|
|
505
|
dir_entry: DirEntry,
|
|
504
|
dir_entry: DirEntry,
|
|
506
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
505
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
507
|
) -> IoResult<()>
|
|
506
|
) -> IoResult<()>
|
|
508
|
where
|
|
507
|
where
|
|
509
|
'a: 'b,
|
|
508
|
'a: 'b,
|
|
510
|
{
|
|
509
|
{
|
|
511
|
let file_type = dir_entry.file_type()?;
|
|
510
|
let file_type = dir_entry.file_type()?;
|
|
512
|
let entry_option = self.dmap.get(&filename);
|
|
511
|
let entry_option = self.dmap.get(&filename);
|
|
513
|
|
|
512
|
|
|
514
|
if filename.as_bytes() == b".hg" {
|
|
513
|
if filename.as_bytes() == b".hg" {
|
|
515
|
// Could be a directory or a symlink
|
|
514
|
// Could be a directory or a symlink
|
|
516
|
return Ok(());
|
|
515
|
return Ok(());
|
|
517
|
}
|
|
516
|
}
|
|
518
|
|
|
517
|
|
|
519
|
if file_type.is_dir() {
|
|
518
|
if file_type.is_dir() {
|
|
520
|
self.handle_traversed_dir(
|
|
519
|
self.handle_traversed_dir(
|
|
521
|
scope,
|
|
520
|
scope,
|
|
522
|
files_sender,
|
|
521
|
files_sender,
|
|
523
|
old_results,
|
|
522
|
old_results,
|
|
524
|
entry_option,
|
|
523
|
entry_option,
|
|
525
|
filename,
|
|
524
|
filename,
|
|
526
|
traversed_sender,
|
|
525
|
traversed_sender,
|
|
527
|
);
|
|
526
|
);
|
|
528
|
} else if file_type.is_file() || file_type.is_symlink() {
|
|
527
|
} else if file_type.is_file() || file_type.is_symlink() {
|
|
529
|
if let Some(entry) = entry_option {
|
|
528
|
if let Some(entry) = entry_option {
|
|
530
|
if self.matcher.matches_everything()
|
|
529
|
if self.matcher.matches_everything()
|
|
531
|
|| self.matcher.matches(&filename)
|
|
530
|
|| self.matcher.matches(&filename)
|
|
532
|
{
|
|
531
|
{
|
|
533
|
let metadata = dir_entry.metadata()?;
|
|
532
|
let metadata = dir_entry.metadata()?;
|
|
534
|
files_sender
|
|
533
|
files_sender
|
|
535
|
.send(Ok((
|
|
534
|
.send(Ok((
|
|
536
|
filename.to_owned(),
|
|
535
|
filename.to_owned(),
|
|
537
|
dispatch_found(
|
|
536
|
dispatch_found(
|
|
538
|
&filename,
|
|
537
|
&filename,
|
|
539
|
*entry,
|
|
538
|
*entry,
|
|
540
|
HgMetadata::from_metadata(metadata),
|
|
539
|
HgMetadata::from_metadata(metadata),
|
|
541
|
&self.dmap.copy_map,
|
|
540
|
&self.dmap.copy_map,
|
|
542
|
self.options,
|
|
541
|
self.options,
|
|
543
|
),
|
|
542
|
),
|
|
544
|
)))
|
|
543
|
)))
|
|
545
|
.unwrap();
|
|
544
|
.unwrap();
|
|
546
|
}
|
|
545
|
}
|
|
547
|
} else if (self.matcher.matches_everything()
|
|
546
|
} else if (self.matcher.matches_everything()
|
|
548
|
|| self.matcher.matches(&filename))
|
|
547
|
|| self.matcher.matches(&filename))
|
|
549
|
&& !self.is_ignored(&filename)
|
|
548
|
&& !self.is_ignored(&filename)
|
|
550
|
{
|
|
549
|
{
|
|
551
|
if (self.options.list_ignored
|
|
550
|
if (self.options.list_ignored
|
|
552
|
|| self.matcher.exact_match(&filename))
|
|
551
|
|| self.matcher.exact_match(&filename))
|
|
553
|
&& self.dir_ignore(&filename)
|
|
552
|
&& self.dir_ignore(&filename)
|
|
554
|
{
|
|
553
|
{
|
|
555
|
if self.options.list_ignored {
|
|
554
|
if self.options.list_ignored {
|
|
556
|
files_sender
|
|
555
|
files_sender
|
|
557
|
.send(Ok((filename.to_owned(), Dispatch::Ignored)))
|
|
556
|
.send(Ok((filename.to_owned(), Dispatch::Ignored)))
|
|
558
|
.unwrap();
|
|
557
|
.unwrap();
|
|
559
|
}
|
|
558
|
}
|
|
560
|
} else if self.options.list_unknown {
|
|
559
|
} else if self.options.list_unknown {
|
|
561
|
files_sender
|
|
560
|
files_sender
|
|
562
|
.send(Ok((filename.to_owned(), Dispatch::Unknown)))
|
|
561
|
.send(Ok((filename.to_owned(), Dispatch::Unknown)))
|
|
563
|
.unwrap();
|
|
562
|
.unwrap();
|
|
564
|
}
|
|
563
|
}
|
|
565
|
} else if self.is_ignored(&filename) && self.options.list_ignored {
|
|
564
|
} else if self.is_ignored(&filename) && self.options.list_ignored {
|
|
566
|
files_sender
|
|
565
|
files_sender
|
|
567
|
.send(Ok((filename.to_owned(), Dispatch::Ignored)))
|
|
566
|
.send(Ok((filename.to_owned(), Dispatch::Ignored)))
|
|
568
|
.unwrap();
|
|
567
|
.unwrap();
|
|
569
|
}
|
|
568
|
}
|
|
570
|
} else if let Some(entry) = entry_option {
|
|
569
|
} else if let Some(entry) = entry_option {
|
|
571
|
// Used to be a file or a folder, now something else.
|
|
570
|
// Used to be a file or a folder, now something else.
|
|
572
|
if self.matcher.matches_everything()
|
|
571
|
if self.matcher.matches_everything()
|
|
573
|
|| self.matcher.matches(&filename)
|
|
572
|
|| self.matcher.matches(&filename)
|
|
574
|
{
|
|
573
|
{
|
|
575
|
files_sender
|
|
574
|
files_sender
|
|
576
|
.send(Ok((
|
|
575
|
.send(Ok((
|
|
577
|
filename.to_owned(),
|
|
576
|
filename.to_owned(),
|
|
578
|
dispatch_missing(entry.state),
|
|
577
|
dispatch_missing(entry.state),
|
|
579
|
)))
|
|
578
|
)))
|
|
580
|
.unwrap();
|
|
579
|
.unwrap();
|
|
581
|
}
|
|
580
|
}
|
|
582
|
}
|
|
581
|
}
|
|
583
|
|
|
582
|
|
|
584
|
Ok(())
|
|
583
|
Ok(())
|
|
585
|
}
|
|
584
|
}
|
|
586
|
|
|
585
|
|
|
587
|
/// A directory was found in the filesystem and needs to be traversed
|
|
586
|
/// A directory was found in the filesystem and needs to be traversed
|
|
588
|
fn handle_traversed_dir<'b>(
|
|
587
|
fn handle_traversed_dir<'b>(
|
|
589
|
&'a self,
|
|
588
|
&'a self,
|
|
590
|
scope: &rayon::Scope<'b>,
|
|
589
|
scope: &rayon::Scope<'b>,
|
|
591
|
files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
590
|
files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
592
|
old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
|
|
591
|
old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
|
|
593
|
entry_option: Option<&'a DirstateEntry>,
|
|
592
|
entry_option: Option<&'a DirstateEntry>,
|
|
594
|
directory: HgPathBuf,
|
|
593
|
directory: HgPathBuf,
|
|
595
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
594
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
596
|
) where
|
|
595
|
) where
|
|
597
|
'a: 'b,
|
|
596
|
'a: 'b,
|
|
598
|
{
|
|
597
|
{
|
|
599
|
scope.spawn(move |_| {
|
|
598
|
scope.spawn(move |_| {
|
|
600
|
// Nested `if` until `rust-lang/rust#53668` is stable
|
|
599
|
// Nested `if` until `rust-lang/rust#53668` is stable
|
|
601
|
if let Some(entry) = entry_option {
|
|
600
|
if let Some(entry) = entry_option {
|
|
602
|
// Used to be a file, is now a folder
|
|
601
|
// Used to be a file, is now a folder
|
|
603
|
if self.matcher.matches_everything()
|
|
602
|
if self.matcher.matches_everything()
|
|
604
|
|| self.matcher.matches(&directory)
|
|
603
|
|| self.matcher.matches(&directory)
|
|
605
|
{
|
|
604
|
{
|
|
606
|
files_sender
|
|
605
|
files_sender
|
|
607
|
.send(Ok((
|
|
606
|
.send(Ok((
|
|
608
|
directory.to_owned(),
|
|
607
|
directory.to_owned(),
|
|
609
|
dispatch_missing(entry.state),
|
|
608
|
dispatch_missing(entry.state),
|
|
610
|
)))
|
|
609
|
)))
|
|
611
|
.unwrap();
|
|
610
|
.unwrap();
|
|
612
|
}
|
|
611
|
}
|
|
613
|
}
|
|
612
|
}
|
|
614
|
// Do we need to traverse it?
|
|
613
|
// Do we need to traverse it?
|
|
615
|
if !self.is_ignored(&directory) || self.options.list_ignored {
|
|
614
|
if !self.is_ignored(&directory) || self.options.list_ignored {
|
|
616
|
self.traverse_dir(
|
|
615
|
self.traverse_dir(
|
|
617
|
files_sender,
|
|
616
|
files_sender,
|
|
618
|
directory,
|
|
617
|
directory,
|
|
619
|
&old_results,
|
|
618
|
&old_results,
|
|
620
|
traversed_sender,
|
|
619
|
traversed_sender,
|
|
621
|
)
|
|
620
|
)
|
|
622
|
.unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
|
|
621
|
.unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
|
|
623
|
}
|
|
622
|
}
|
|
624
|
});
|
|
623
|
});
|
|
625
|
}
|
|
624
|
}
|
|
626
|
|
|
625
|
|
|
627
|
/// Decides whether the directory needs to be listed, and if so handles the
|
|
626
|
/// Decides whether the directory needs to be listed, and if so handles the
|
|
628
|
/// entries in a separate thread.
|
|
627
|
/// entries in a separate thread.
|
|
629
|
fn traverse_dir(
|
|
628
|
fn traverse_dir(
|
|
630
|
&self,
|
|
629
|
&self,
|
|
631
|
files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
630
|
files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
|
|
632
|
directory: impl AsRef<HgPath>,
|
|
631
|
directory: impl AsRef<HgPath>,
|
|
633
|
old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
|
|
632
|
old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
|
|
634
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
633
|
traversed_sender: crossbeam::Sender<HgPathBuf>,
|
|
635
|
) -> IoResult<()> {
|
|
634
|
) -> IoResult<()> {
|
|
636
|
let directory = directory.as_ref();
|
|
635
|
let directory = directory.as_ref();
|
|
637
|
|
|
636
|
|
|
638
|
if self.options.collect_traversed_dirs {
|
|
637
|
if self.options.collect_traversed_dirs {
|
|
639
|
traversed_sender
|
|
638
|
traversed_sender
|
|
640
|
.send(directory.to_owned())
|
|
639
|
.send(directory.to_owned())
|
|
641
|
.expect("receiver should outlive sender");
|
|
640
|
.expect("receiver should outlive sender");
|
|
642
|
}
|
|
641
|
}
|
|
643
|
|
|
642
|
|
|
644
|
let visit_entries = match self.matcher.visit_children_set(directory) {
|
|
643
|
let visit_entries = match self.matcher.visit_children_set(directory) {
|
|
645
|
VisitChildrenSet::Empty => return Ok(()),
|
|
644
|
VisitChildrenSet::Empty => return Ok(()),
|
|
646
|
VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
|
|
645
|
VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
|
|
647
|
VisitChildrenSet::Set(set) => Some(set),
|
|
646
|
VisitChildrenSet::Set(set) => Some(set),
|
|
648
|
};
|
|
647
|
};
|
|
649
|
let buf = hg_path_to_path_buf(directory)?;
|
|
648
|
let buf = hg_path_to_path_buf(directory)?;
|
|
650
|
let dir_path = self.root_dir.join(buf);
|
|
649
|
let dir_path = self.root_dir.join(buf);
|
|
651
|
|
|
650
|
|
|
652
|
let skip_dot_hg = !directory.as_bytes().is_empty();
|
|
651
|
let skip_dot_hg = !directory.as_bytes().is_empty();
|
|
653
|
let entries = match list_directory(dir_path, skip_dot_hg) {
|
|
652
|
let entries = match list_directory(dir_path, skip_dot_hg) {
|
|
654
|
Err(e) => {
|
|
653
|
Err(e) => {
|
|
655
|
return match e.kind() {
|
|
654
|
return match e.kind() {
|
|
656
|
ErrorKind::NotFound | ErrorKind::PermissionDenied => {
|
|
655
|
ErrorKind::NotFound | ErrorKind::PermissionDenied => {
|
|
657
|
files_sender
|
|
656
|
files_sender
|
|
658
|
.send(Ok((
|
|
657
|
.send(Ok((
|
|
659
|
directory.to_owned(),
|
|
658
|
directory.to_owned(),
|
|
660
|
Dispatch::Bad(BadMatch::OsError(
|
|
659
|
Dispatch::Bad(BadMatch::OsError(
|
|
661
|
// Unwrapping here is OK because the error
|
|
660
|
// Unwrapping here is OK because the error
|
|
662
|
// always is a
|
|
661
|
// always is a
|
|
663
|
// real os error
|
|
662
|
// real os error
|
|
664
|
e.raw_os_error().unwrap(),
|
|
663
|
e.raw_os_error().unwrap(),
|
|
665
|
)),
|
|
664
|
)),
|
|
666
|
)))
|
|
665
|
)))
|
|
667
|
.expect("receiver should outlive sender");
|
|
666
|
.expect("receiver should outlive sender");
|
|
668
|
Ok(())
|
|
667
|
Ok(())
|
|
669
|
}
|
|
668
|
}
|
|
670
|
_ => Err(e),
|
|
669
|
_ => Err(e),
|
|
671
|
};
|
|
670
|
};
|
|
672
|
}
|
|
671
|
}
|
|
673
|
Ok(entries) => entries,
|
|
672
|
Ok(entries) => entries,
|
|
674
|
};
|
|
673
|
};
|
|
675
|
|
|
674
|
|
|
676
|
rayon::scope(|scope| -> IoResult<()> {
|
|
675
|
rayon::scope(|scope| -> IoResult<()> {
|
|
677
|
for (filename, dir_entry) in entries {
|
|
676
|
for (filename, dir_entry) in entries {
|
|
678
|
if let Some(ref set) = visit_entries {
|
|
677
|
if let Some(ref set) = visit_entries {
|
|
679
|
if !set.contains(filename.deref()) {
|
|
678
|
if !set.contains(filename.deref()) {
|
|
680
|
continue;
|
|
679
|
continue;
|
|
681
|
}
|
|
680
|
}
|
|
682
|
}
|
|
681
|
}
|
|
683
|
// TODO normalize
|
|
682
|
// TODO normalize
|
|
684
|
let filename = if directory.is_empty() {
|
|
683
|
let filename = if directory.is_empty() {
|
|
685
|
filename.to_owned()
|
|
684
|
filename.to_owned()
|
|
686
|
} else {
|
|
685
|
} else {
|
|
687
|
directory.join(&filename)
|
|
686
|
directory.join(&filename)
|
|
688
|
};
|
|
687
|
};
|
|
689
|
|
|
688
|
|
|
690
|
if !old_results.contains_key(filename.deref()) {
|
|
689
|
if !old_results.contains_key(filename.deref()) {
|
|
691
|
self.handle_traversed_entry(
|
|
690
|
self.handle_traversed_entry(
|
|
692
|
scope,
|
|
691
|
scope,
|
|
693
|
files_sender,
|
|
692
|
files_sender,
|
|
694
|
old_results,
|
|
693
|
old_results,
|
|
695
|
filename,
|
|
694
|
filename,
|
|
696
|
dir_entry,
|
|
695
|
dir_entry,
|
|
697
|
traversed_sender.clone(),
|
|
696
|
traversed_sender.clone(),
|
|
698
|
)?;
|
|
697
|
)?;
|
|
699
|
}
|
|
698
|
}
|
|
700
|
}
|
|
699
|
}
|
|
701
|
Ok(())
|
|
700
|
Ok(())
|
|
702
|
})
|
|
701
|
})
|
|
703
|
}
|
|
702
|
}
|
|
704
|
|
|
703
|
|
|
705
|
/// Checks all files that are in the dirstate but were not found during the
|
|
704
|
/// Checks all files that are in the dirstate but were not found during the
|
|
706
|
/// working directory traversal. This means that the rest must
|
|
705
|
/// working directory traversal. This means that the rest must
|
|
707
|
/// be either ignored, under a symlink or under a new nested repo.
|
|
706
|
/// be either ignored, under a symlink or under a new nested repo.
|
|
708
|
///
|
|
707
|
///
|
|
709
|
/// This takes a mutable reference to the results to account for the
|
|
708
|
/// This takes a mutable reference to the results to account for the
|
|
710
|
/// `extend` in timings
|
|
709
|
/// `extend` in timings
|
|
711
|
#[timed]
|
|
710
|
#[timed]
|
|
712
|
pub fn handle_unknowns(
|
|
711
|
pub fn handle_unknowns(
|
|
713
|
&self,
|
|
712
|
&self,
|
|
714
|
results: &mut Vec<DispatchedPath<'a>>,
|
|
713
|
results: &mut Vec<DispatchedPath<'a>>,
|
|
715
|
) -> IoResult<()> {
|
|
714
|
) -> IoResult<()> {
|
|
716
|
let to_visit: Vec<(&HgPath, &DirstateEntry)> =
|
|
715
|
let to_visit: Vec<(&HgPath, &DirstateEntry)> =
|
|
717
|
if results.is_empty() && self.matcher.matches_everything() {
|
|
716
|
if results.is_empty() && self.matcher.matches_everything() {
|
|
718
|
self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
|
|
717
|
self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
|
|
719
|
} else {
|
|
718
|
} else {
|
|
720
|
// Only convert to a hashmap if needed.
|
|
719
|
// Only convert to a hashmap if needed.
|
|
721
|
let old_results: FastHashMap<_, _> =
|
|
720
|
let old_results: FastHashMap<_, _> =
|
|
722
|
results.iter().cloned().collect();
|
|
721
|
results.iter().cloned().collect();
|
|
723
|
self.dmap
|
|
722
|
self.dmap
|
|
724
|
.iter()
|
|
723
|
.iter()
|
|
725
|
.filter_map(move |(f, e)| {
|
|
724
|
.filter_map(move |(f, e)| {
|
|
726
|
if !old_results.contains_key(f.deref())
|
|
725
|
if !old_results.contains_key(f.deref())
|
|
727
|
&& self.matcher.matches(f)
|
|
726
|
&& self.matcher.matches(f)
|
|
728
|
{
|
|
727
|
{
|
|
729
|
Some((f.deref(), e))
|
|
728
|
Some((f.deref(), e))
|
|
730
|
} else {
|
|
729
|
} else {
|
|
731
|
None
|
|
730
|
None
|
|
732
|
}
|
|
731
|
}
|
|
733
|
})
|
|
732
|
})
|
|
734
|
.collect()
|
|
733
|
.collect()
|
|
735
|
};
|
|
734
|
};
|
|
736
|
|
|
735
|
|
|
737
|
let path_auditor = PathAuditor::new(&self.root_dir);
|
|
736
|
let path_auditor = PathAuditor::new(&self.root_dir);
|
|
738
|
|
|
737
|
|
|
739
|
// TODO don't collect. Find a way of replicating the behavior of
|
|
738
|
// TODO don't collect. Find a way of replicating the behavior of
|
|
740
|
// `itertools::process_results`, but for `rayon::ParallelIterator`
|
|
739
|
// `itertools::process_results`, but for `rayon::ParallelIterator`
|
|
741
|
let new_results: IoResult<Vec<_>> = to_visit
|
|
740
|
let new_results: IoResult<Vec<_>> = to_visit
|
|
742
|
.into_par_iter()
|
|
741
|
.into_par_iter()
|
|
743
|
.filter_map(|(filename, entry)| -> Option<IoResult<_>> {
|
|
742
|
.filter_map(|(filename, entry)| -> Option<IoResult<_>> {
|
|
744
|
// Report ignored items in the dmap as long as they are not
|
|
743
|
// Report ignored items in the dmap as long as they are not
|
|
745
|
// under a symlink directory.
|
|
744
|
// under a symlink directory.
|
|
746
|
if path_auditor.check(filename) {
|
|
745
|
if path_auditor.check(filename) {
|
|
747
|
// TODO normalize for case-insensitive filesystems
|
|
746
|
// TODO normalize for case-insensitive filesystems
|
|
748
|
let buf = match hg_path_to_path_buf(filename) {
|
|
747
|
let buf = match hg_path_to_path_buf(filename) {
|
|
749
|
Ok(x) => x,
|
|
748
|
Ok(x) => x,
|
|
750
|
Err(e) => return Some(Err(e.into())),
|
|
749
|
Err(e) => return Some(Err(e.into())),
|
|
751
|
};
|
|
750
|
};
|
|
752
|
Some(Ok((
|
|
751
|
Some(Ok((
|
|
753
|
Cow::Borrowed(filename),
|
|
752
|
Cow::Borrowed(filename),
|
|
754
|
match self.root_dir.join(&buf).symlink_metadata() {
|
|
753
|
match self.root_dir.join(&buf).symlink_metadata() {
|
|
755
|
// File was just ignored, no links, and exists
|
|
754
|
// File was just ignored, no links, and exists
|
|
756
|
Ok(meta) => {
|
|
755
|
Ok(meta) => {
|
|
757
|
let metadata = HgMetadata::from_metadata(meta);
|
|
756
|
let metadata = HgMetadata::from_metadata(meta);
|
|
758
|
dispatch_found(
|
|
757
|
dispatch_found(
|
|
759
|
filename,
|
|
758
|
filename,
|
|
760
|
*entry,
|
|
759
|
*entry,
|
|
761
|
metadata,
|
|
760
|
metadata,
|
|
762
|
&self.dmap.copy_map,
|
|
761
|
&self.dmap.copy_map,
|
|
763
|
self.options,
|
|
762
|
self.options,
|
|
764
|
)
|
|
763
|
)
|
|
765
|
}
|
|
764
|
}
|
|
766
|
// File doesn't exist
|
|
765
|
// File doesn't exist
|
|
767
|
Err(_) => dispatch_missing(entry.state),
|
|
766
|
Err(_) => dispatch_missing(entry.state),
|
|
768
|
},
|
|
767
|
},
|
|
769
|
)))
|
|
768
|
)))
|
|
770
|
} else {
|
|
769
|
} else {
|
|
771
|
// It's either missing or under a symlink directory which
|
|
770
|
// It's either missing or under a symlink directory which
|
|
772
|
// we, in this case, report as missing.
|
|
771
|
// we, in this case, report as missing.
|
|
773
|
Some(Ok((
|
|
772
|
Some(Ok((
|
|
774
|
Cow::Borrowed(filename),
|
|
773
|
Cow::Borrowed(filename),
|
|
775
|
dispatch_missing(entry.state),
|
|
774
|
dispatch_missing(entry.state),
|
|
776
|
)))
|
|
775
|
)))
|
|
777
|
}
|
|
776
|
}
|
|
778
|
})
|
|
777
|
})
|
|
779
|
.collect();
|
|
778
|
.collect();
|
|
780
|
|
|
779
|
|
|
781
|
results.par_extend(new_results?);
|
|
780
|
results.par_extend(new_results?);
|
|
782
|
|
|
781
|
|
|
783
|
Ok(())
|
|
782
|
Ok(())
|
|
784
|
}
|
|
783
|
}
|
|
785
|
|
|
784
|
|
|
786
|
/// Add the files in the dirstate to the results.
|
|
785
|
/// Add the files in the dirstate to the results.
|
|
787
|
///
|
|
786
|
///
|
|
788
|
/// This takes a mutable reference to the results to account for the
|
|
787
|
/// This takes a mutable reference to the results to account for the
|
|
789
|
/// `extend` in timings
|
|
788
|
/// `extend` in timings
|
|
790
|
#[timed]
|
|
789
|
#[timed]
|
|
791
|
pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
|
|
790
|
pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
|
|
792
|
results.par_extend(self.dmap.par_iter().flat_map(
|
|
791
|
results.par_extend(self.dmap.par_iter().flat_map(
|
|
793
|
move |(filename, entry)| {
|
|
792
|
move |(filename, entry)| {
|
|
794
|
let filename: &HgPath = filename;
|
|
793
|
let filename: &HgPath = filename;
|
|
795
|
let filename_as_path = hg_path_to_path_buf(filename)?;
|
|
794
|
let filename_as_path = hg_path_to_path_buf(filename)?;
|
|
796
|
let meta =
|
|
795
|
let meta =
|
|
797
|
self.root_dir.join(filename_as_path).symlink_metadata();
|
|
796
|
self.root_dir.join(filename_as_path).symlink_metadata();
|
|
798
|
|
|
797
|
|
|
799
|
match meta {
|
|
798
|
match meta {
|
|
800
|
Ok(ref m)
|
|
799
|
Ok(ref m)
|
|
801
|
if !(m.file_type().is_file()
|
|
800
|
if !(m.file_type().is_file()
|
|
802
|
|| m.file_type().is_symlink()) =>
|
|
801
|
|| m.file_type().is_symlink()) =>
|
|
803
|
{
|
|
802
|
{
|
|
804
|
Ok((
|
|
803
|
Ok((
|
|
805
|
Cow::Borrowed(filename),
|
|
804
|
Cow::Borrowed(filename),
|
|
806
|
dispatch_missing(entry.state),
|
|
805
|
dispatch_missing(entry.state),
|
|
807
|
))
|
|
806
|
))
|
|
808
|
}
|
|
807
|
}
|
|
809
|
Ok(m) => Ok((
|
|
808
|
Ok(m) => Ok((
|
|
810
|
Cow::Borrowed(filename),
|
|
809
|
Cow::Borrowed(filename),
|
|
811
|
dispatch_found(
|
|
810
|
dispatch_found(
|
|
812
|
filename,
|
|
811
|
filename,
|
|
813
|
*entry,
|
|
812
|
*entry,
|
|
814
|
HgMetadata::from_metadata(m),
|
|
813
|
HgMetadata::from_metadata(m),
|
|
815
|
&self.dmap.copy_map,
|
|
814
|
&self.dmap.copy_map,
|
|
816
|
self.options,
|
|
815
|
self.options,
|
|
817
|
),
|
|
816
|
),
|
|
818
|
)),
|
|
817
|
)),
|
|
819
|
Err(ref e)
|
|
818
|
Err(ref e)
|
|
820
|
if e.kind() == ErrorKind::NotFound
|
|
819
|
if e.kind() == ErrorKind::NotFound
|
|
821
|
|| e.raw_os_error() == Some(20) =>
|
|
820
|
|| e.raw_os_error() == Some(20) =>
|
|
822
|
{
|
|
821
|
{
|
|
823
|
// Rust does not yet have an `ErrorKind` for
|
|
822
|
// Rust does not yet have an `ErrorKind` for
|
|
824
|
// `NotADirectory` (errno 20)
|
|
823
|
// `NotADirectory` (errno 20)
|
|
825
|
// It happens if the dirstate contains `foo/bar`
|
|
824
|
// It happens if the dirstate contains `foo/bar`
|
|
826
|
// and foo is not a
|
|
825
|
// and foo is not a
|
|
827
|
// directory
|
|
826
|
// directory
|
|
828
|
Ok((
|
|
827
|
Ok((
|
|
829
|
Cow::Borrowed(filename),
|
|
828
|
Cow::Borrowed(filename),
|
|
830
|
dispatch_missing(entry.state),
|
|
829
|
dispatch_missing(entry.state),
|
|
831
|
))
|
|
830
|
))
|
|
832
|
}
|
|
831
|
}
|
|
833
|
Err(e) => Err(e),
|
|
832
|
Err(e) => Err(e),
|
|
834
|
}
|
|
833
|
}
|
|
835
|
},
|
|
834
|
},
|
|
836
|
));
|
|
835
|
));
|
|
837
|
}
|
|
836
|
}
|
|
838
|
}
|
|
837
|
}
|
|
839
|
|
|
838
|
|
|
840
|
#[timed]
|
|
839
|
#[timed]
|
|
841
|
pub fn build_response<'a>(
|
|
840
|
pub fn build_response<'a>(
|
|
842
|
results: impl IntoIterator<Item = DispatchedPath<'a>>,
|
|
841
|
results: impl IntoIterator<Item = DispatchedPath<'a>>,
|
|
843
|
traversed: Vec<HgPathBuf>,
|
|
842
|
traversed: Vec<HgPathBuf>,
|
|
844
|
) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
|
|
843
|
) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
|
|
845
|
let mut lookup = vec![];
|
|
844
|
let mut lookup = vec![];
|
|
846
|
let mut modified = vec![];
|
|
845
|
let mut modified = vec![];
|
|
847
|
let mut added = vec![];
|
|
846
|
let mut added = vec![];
|
|
848
|
let mut removed = vec![];
|
|
847
|
let mut removed = vec![];
|
|
849
|
let mut deleted = vec![];
|
|
848
|
let mut deleted = vec![];
|
|
850
|
let mut clean = vec![];
|
|
849
|
let mut clean = vec![];
|
|
851
|
let mut ignored = vec![];
|
|
850
|
let mut ignored = vec![];
|
|
852
|
let mut unknown = vec![];
|
|
851
|
let mut unknown = vec![];
|
|
853
|
let mut bad = vec![];
|
|
852
|
let mut bad = vec![];
|
|
854
|
|
|
853
|
|
|
855
|
for (filename, dispatch) in results.into_iter() {
|
|
854
|
for (filename, dispatch) in results.into_iter() {
|
|
856
|
match dispatch {
|
|
855
|
match dispatch {
|
|
857
|
Dispatch::Unknown => unknown.push(filename),
|
|
856
|
Dispatch::Unknown => unknown.push(filename),
|
|
858
|
Dispatch::Unsure => lookup.push(filename),
|
|
857
|
Dispatch::Unsure => lookup.push(filename),
|
|
859
|
Dispatch::Modified => modified.push(filename),
|
|
858
|
Dispatch::Modified => modified.push(filename),
|
|
860
|
Dispatch::Added => added.push(filename),
|
|
859
|
Dispatch::Added => added.push(filename),
|
|
861
|
Dispatch::Removed => removed.push(filename),
|
|
860
|
Dispatch::Removed => removed.push(filename),
|
|
862
|
Dispatch::Deleted => deleted.push(filename),
|
|
861
|
Dispatch::Deleted => deleted.push(filename),
|
|
863
|
Dispatch::Clean => clean.push(filename),
|
|
862
|
Dispatch::Clean => clean.push(filename),
|
|
864
|
Dispatch::Ignored => ignored.push(filename),
|
|
863
|
Dispatch::Ignored => ignored.push(filename),
|
|
865
|
Dispatch::None => {}
|
|
864
|
Dispatch::None => {}
|
|
866
|
Dispatch::Bad(reason) => bad.push((filename, reason)),
|
|
865
|
Dispatch::Bad(reason) => bad.push((filename, reason)),
|
|
867
|
Dispatch::Directory { .. } => {}
|
|
866
|
Dispatch::Directory { .. } => {}
|
|
868
|
}
|
|
867
|
}
|
|
869
|
}
|
|
868
|
}
|
|
870
|
|
|
869
|
|
|
871
|
(
|
|
870
|
(
|
|
872
|
lookup,
|
|
871
|
lookup,
|
|
873
|
DirstateStatus {
|
|
872
|
DirstateStatus {
|
|
874
|
modified,
|
|
873
|
modified,
|
|
875
|
added,
|
|
874
|
added,
|
|
876
|
removed,
|
|
875
|
removed,
|
|
877
|
deleted,
|
|
876
|
deleted,
|
|
878
|
clean,
|
|
877
|
clean,
|
|
879
|
ignored,
|
|
878
|
ignored,
|
|
880
|
unknown,
|
|
879
|
unknown,
|
|
881
|
bad,
|
|
880
|
bad,
|
|
882
|
traversed,
|
|
881
|
traversed,
|
|
883
|
},
|
|
882
|
},
|
|
884
|
)
|
|
883
|
)
|
|
885
|
}
|
|
884
|
}
|
|
886
|
|
|
885
|
|
|
887
|
/// Get the status of files in the working directory.
|
|
886
|
/// Get the status of files in the working directory.
|
|
888
|
///
|
|
887
|
///
|
|
889
|
/// This is the current entry-point for `hg-core` and is realistically unusable
|
|
888
|
/// This is the current entry-point for `hg-core` and is realistically unusable
|
|
890
|
/// outside of a Python context because its arguments need to provide a lot of
|
|
889
|
/// outside of a Python context because its arguments need to provide a lot of
|
|
891
|
/// information that will not be necessary in the future.
|
|
890
|
/// information that will not be necessary in the future.
|
|
892
|
#[timed]
|
|
891
|
#[timed]
|
|
893
|
pub fn status<'a>(
|
|
892
|
pub fn status<'a>(
|
|
894
|
dmap: &'a DirstateMap,
|
|
893
|
dmap: &'a DirstateMap,
|
|
895
|
matcher: &'a (impl Matcher + Sync),
|
|
894
|
matcher: &'a (impl Matcher + Sync),
|
|
896
|
root_dir: PathBuf,
|
|
895
|
root_dir: PathBuf,
|
|
897
|
ignore_files: Vec<PathBuf>,
|
|
896
|
ignore_files: Vec<PathBuf>,
|
|
898
|
options: StatusOptions,
|
|
897
|
options: StatusOptions,
|
|
899
|
) -> StatusResult<(
|
|
898
|
) -> StatusResult<(
|
|
900
|
(Vec<HgPathCow<'a>>, DirstateStatus<'a>),
|
|
899
|
(Vec<HgPathCow<'a>>, DirstateStatus<'a>),
|
|
901
|
Vec<PatternFileWarning>,
|
|
900
|
Vec<PatternFileWarning>,
|
|
902
|
)> {
|
|
901
|
)> {
|
|
903
|
let (status, warnings) =
|
|
902
|
let (status, warnings) =
|
|
904
|
Status::new(dmap, matcher, root_dir, ignore_files, options)?;
|
|
903
|
Status::new(dmap, matcher, root_dir, ignore_files, options)?;
|
|
905
|
|
|
904
|
|
|
906
|
Ok((status.run()?, warnings))
|
|
905
|
Ok((status.run()?, warnings))
|
|
907
|
}
|
|
906
|
}
|