##// END OF EJS Templates
rhg: Properly format warnings related to ignore patterns...
Simon Sapin -
r49342:c9abfb80 default
parent child Browse files
Show More
@@ -1,505 +1,527 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@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 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::dirstate::RANGE_MASK_31BIT;
18 use hg::dirstate::RANGE_MASK_31BIT;
19 use hg::errors::{HgError, IoResultExt};
19 use hg::errors::{HgError, IoResultExt};
20 use hg::lock::LockError;
20 use hg::lock::LockError;
21 use hg::manifest::Manifest;
21 use hg::manifest::Manifest;
22 use hg::matchers::AlwaysMatcher;
22 use hg::matchers::AlwaysMatcher;
23 use hg::repo::Repo;
23 use hg::repo::Repo;
24 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_os_string;
25 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::StatusOptions;
28 use hg::StatusOptions;
28 use log::{info, warn};
29 use log::info;
29 use std::io;
30 use std::io;
30 use std::path::PathBuf;
31 use std::path::PathBuf;
31
32
32 pub const HELP_TEXT: &str = "
33 pub const HELP_TEXT: &str = "
33 Show changed files in the working directory
34 Show changed files in the working directory
34
35
35 This is a pure Rust version of `hg status`.
36 This is a pure Rust version of `hg status`.
36
37
37 Some options might be missing, check the list below.
38 Some options might be missing, check the list below.
38 ";
39 ";
39
40
40 pub fn args() -> clap::App<'static, 'static> {
41 pub fn args() -> clap::App<'static, 'static> {
41 SubCommand::with_name("status")
42 SubCommand::with_name("status")
42 .alias("st")
43 .alias("st")
43 .about(HELP_TEXT)
44 .about(HELP_TEXT)
44 .arg(
45 .arg(
45 Arg::with_name("all")
46 Arg::with_name("all")
46 .help("show status of all files")
47 .help("show status of all files")
47 .short("-A")
48 .short("-A")
48 .long("--all"),
49 .long("--all"),
49 )
50 )
50 .arg(
51 .arg(
51 Arg::with_name("modified")
52 Arg::with_name("modified")
52 .help("show only modified files")
53 .help("show only modified files")
53 .short("-m")
54 .short("-m")
54 .long("--modified"),
55 .long("--modified"),
55 )
56 )
56 .arg(
57 .arg(
57 Arg::with_name("added")
58 Arg::with_name("added")
58 .help("show only added files")
59 .help("show only added files")
59 .short("-a")
60 .short("-a")
60 .long("--added"),
61 .long("--added"),
61 )
62 )
62 .arg(
63 .arg(
63 Arg::with_name("removed")
64 Arg::with_name("removed")
64 .help("show only removed files")
65 .help("show only removed files")
65 .short("-r")
66 .short("-r")
66 .long("--removed"),
67 .long("--removed"),
67 )
68 )
68 .arg(
69 .arg(
69 Arg::with_name("clean")
70 Arg::with_name("clean")
70 .help("show only clean files")
71 .help("show only clean files")
71 .short("-c")
72 .short("-c")
72 .long("--clean"),
73 .long("--clean"),
73 )
74 )
74 .arg(
75 .arg(
75 Arg::with_name("deleted")
76 Arg::with_name("deleted")
76 .help("show only deleted files")
77 .help("show only deleted files")
77 .short("-d")
78 .short("-d")
78 .long("--deleted"),
79 .long("--deleted"),
79 )
80 )
80 .arg(
81 .arg(
81 Arg::with_name("unknown")
82 Arg::with_name("unknown")
82 .help("show only unknown (not tracked) files")
83 .help("show only unknown (not tracked) files")
83 .short("-u")
84 .short("-u")
84 .long("--unknown"),
85 .long("--unknown"),
85 )
86 )
86 .arg(
87 .arg(
87 Arg::with_name("ignored")
88 Arg::with_name("ignored")
88 .help("show only ignored files")
89 .help("show only ignored files")
89 .short("-i")
90 .short("-i")
90 .long("--ignored"),
91 .long("--ignored"),
91 )
92 )
92 .arg(
93 .arg(
93 Arg::with_name("copies")
94 Arg::with_name("copies")
94 .help("show source of copied files (DEFAULT: ui.statuscopies)")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
95 .short("-C")
96 .short("-C")
96 .long("--copies"),
97 .long("--copies"),
97 )
98 )
98 .arg(
99 .arg(
99 Arg::with_name("no-status")
100 Arg::with_name("no-status")
100 .help("hide status prefix")
101 .help("hide status prefix")
101 .short("-n")
102 .short("-n")
102 .long("--no-status"),
103 .long("--no-status"),
103 )
104 )
104 }
105 }
105
106
106 /// Pure data type allowing the caller to specify file states to display
107 /// Pure data type allowing the caller to specify file states to display
107 #[derive(Copy, Clone, Debug)]
108 #[derive(Copy, Clone, Debug)]
108 pub struct DisplayStates {
109 pub struct DisplayStates {
109 pub modified: bool,
110 pub modified: bool,
110 pub added: bool,
111 pub added: bool,
111 pub removed: bool,
112 pub removed: bool,
112 pub clean: bool,
113 pub clean: bool,
113 pub deleted: bool,
114 pub deleted: bool,
114 pub unknown: bool,
115 pub unknown: bool,
115 pub ignored: bool,
116 pub ignored: bool,
116 }
117 }
117
118
118 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
119 modified: true,
120 modified: true,
120 added: true,
121 added: true,
121 removed: true,
122 removed: true,
122 clean: false,
123 clean: false,
123 deleted: true,
124 deleted: true,
124 unknown: true,
125 unknown: true,
125 ignored: false,
126 ignored: false,
126 };
127 };
127
128
128 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
129 modified: true,
130 modified: true,
130 added: true,
131 added: true,
131 removed: true,
132 removed: true,
132 clean: true,
133 clean: true,
133 deleted: true,
134 deleted: true,
134 unknown: true,
135 unknown: true,
135 ignored: true,
136 ignored: true,
136 };
137 };
137
138
138 impl DisplayStates {
139 impl DisplayStates {
139 pub fn is_empty(&self) -> bool {
140 pub fn is_empty(&self) -> bool {
140 !(self.modified
141 !(self.modified
141 || self.added
142 || self.added
142 || self.removed
143 || self.removed
143 || self.clean
144 || self.clean
144 || self.deleted
145 || self.deleted
145 || self.unknown
146 || self.unknown
146 || self.ignored)
147 || self.ignored)
147 }
148 }
148 }
149 }
149
150
150 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
151 let status_enabled_default = false;
152 let status_enabled_default = false;
152 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
153 if !status_enabled.unwrap_or(status_enabled_default) {
154 if !status_enabled.unwrap_or(status_enabled_default) {
154 return Err(CommandError::unsupported(
155 return Err(CommandError::unsupported(
155 "status is experimental in rhg (enable it with 'rhg.status = true' \
156 "status is experimental in rhg (enable it with 'rhg.status = true' \
156 or enable fallback with 'rhg.on-unsupported = fallback')"
157 or enable fallback with 'rhg.on-unsupported = fallback')"
157 ));
158 ));
158 }
159 }
159
160
160 // TODO: lift these limitations
161 // TODO: lift these limitations
161 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
162 return Err(CommandError::unsupported(
163 return Err(CommandError::unsupported(
163 "ui.tweakdefaults is not yet supported with rhg status",
164 "ui.tweakdefaults is not yet supported with rhg status",
164 ));
165 ));
165 }
166 }
166 if invocation.config.get_bool(b"ui", b"statuscopies")? {
167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
167 return Err(CommandError::unsupported(
168 return Err(CommandError::unsupported(
168 "ui.statuscopies is not yet supported with rhg status",
169 "ui.statuscopies is not yet supported with rhg status",
169 ));
170 ));
170 }
171 }
171 if invocation
172 if invocation
172 .config
173 .config
173 .get(b"commands", b"status.terse")
174 .get(b"commands", b"status.terse")
174 .is_some()
175 .is_some()
175 {
176 {
176 return Err(CommandError::unsupported(
177 return Err(CommandError::unsupported(
177 "status.terse is not yet supported with rhg status",
178 "status.terse is not yet supported with rhg status",
178 ));
179 ));
179 }
180 }
180
181
181 let ui = invocation.ui;
182 let ui = invocation.ui;
182 let config = invocation.config;
183 let config = invocation.config;
183 let args = invocation.subcommand_args;
184 let args = invocation.subcommand_args;
184 let all = args.is_present("all");
185 let all = args.is_present("all");
185 let display_states = if all {
186 let display_states = if all {
186 // TODO when implementing `--quiet`: it excludes clean files
187 // TODO when implementing `--quiet`: it excludes clean files
187 // from `--all`
188 // from `--all`
188 ALL_DISPLAY_STATES
189 ALL_DISPLAY_STATES
189 } else {
190 } else {
190 let requested = DisplayStates {
191 let requested = DisplayStates {
191 modified: args.is_present("modified"),
192 modified: args.is_present("modified"),
192 added: args.is_present("added"),
193 added: args.is_present("added"),
193 removed: args.is_present("removed"),
194 removed: args.is_present("removed"),
194 clean: args.is_present("clean"),
195 clean: args.is_present("clean"),
195 deleted: args.is_present("deleted"),
196 deleted: args.is_present("deleted"),
196 unknown: args.is_present("unknown"),
197 unknown: args.is_present("unknown"),
197 ignored: args.is_present("ignored"),
198 ignored: args.is_present("ignored"),
198 };
199 };
199 if requested.is_empty() {
200 if requested.is_empty() {
200 DEFAULT_DISPLAY_STATES
201 DEFAULT_DISPLAY_STATES
201 } else {
202 } else {
202 requested
203 requested
203 }
204 }
204 };
205 };
205 let no_status = args.is_present("no-status");
206 let no_status = args.is_present("no-status");
206 let list_copies = all
207 let list_copies = all
207 || args.is_present("copies")
208 || args.is_present("copies")
208 || config.get_bool(b"ui", b"statuscopies")?;
209 || config.get_bool(b"ui", b"statuscopies")?;
209
210
210 let repo = invocation.repo?;
211 let repo = invocation.repo?;
211
212
212 if repo.has_sparse() || repo.has_narrow() {
213 if repo.has_sparse() || repo.has_narrow() {
213 return Err(CommandError::unsupported(
214 return Err(CommandError::unsupported(
214 "rhg status is not supported for sparse checkouts or narrow clones yet"
215 "rhg status is not supported for sparse checkouts or narrow clones yet"
215 ));
216 ));
216 }
217 }
217
218
218 let mut dmap = repo.dirstate_map_mut()?;
219 let mut dmap = repo.dirstate_map_mut()?;
219
220
220 let options = StatusOptions {
221 let options = StatusOptions {
221 // we're currently supporting file systems with exec flags only
222 // we're currently supporting file systems with exec flags only
222 // anyway
223 // anyway
223 check_exec: true,
224 check_exec: true,
224 list_clean: display_states.clean,
225 list_clean: display_states.clean,
225 list_unknown: display_states.unknown,
226 list_unknown: display_states.unknown,
226 list_ignored: display_states.ignored,
227 list_ignored: display_states.ignored,
227 list_copies,
228 list_copies,
228 collect_traversed_dirs: false,
229 collect_traversed_dirs: false,
229 };
230 };
230 let (mut ds_status, pattern_warnings) = dmap.status(
231 let (mut ds_status, pattern_warnings) = dmap.status(
231 &AlwaysMatcher,
232 &AlwaysMatcher,
232 repo.working_directory_path().to_owned(),
233 repo.working_directory_path().to_owned(),
233 ignore_files(repo, config),
234 ignore_files(repo, config),
234 options,
235 options,
235 )?;
236 )?;
236 if !pattern_warnings.is_empty() {
237 for warning in pattern_warnings {
237 warn!("Pattern warnings: {:?}", &pattern_warnings);
238 match warning {
239 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
240 .write_stderr(&format_bytes!(
241 b"{}: ignoring invalid syntax '{}'\n",
242 get_bytes_from_path(path),
243 &*syntax
244 ))?,
245 hg::PatternFileWarning::NoSuchFile(path) => {
246 let path = if let Ok(relative) =
247 path.strip_prefix(repo.working_directory_path())
248 {
249 relative
250 } else {
251 &*path
252 };
253 ui.write_stderr(&format_bytes!(
254 b"skipping unreadable pattern file '{}': \
255 No such file or directory\n",
256 get_bytes_from_path(path),
257 ))?
258 }
259 }
238 }
260 }
239
261
240 for (path, error) in ds_status.bad {
262 for (path, error) in ds_status.bad {
241 let error = match error {
263 let error = match error {
242 hg::BadMatch::OsError(code) => {
264 hg::BadMatch::OsError(code) => {
243 std::io::Error::from_raw_os_error(code).to_string()
265 std::io::Error::from_raw_os_error(code).to_string()
244 }
266 }
245 hg::BadMatch::BadType(ty) => {
267 hg::BadMatch::BadType(ty) => {
246 format!("unsupported file type (type is {})", ty)
268 format!("unsupported file type (type is {})", ty)
247 }
269 }
248 };
270 };
249 ui.write_stderr(&format_bytes!(
271 ui.write_stderr(&format_bytes!(
250 b"{}: {}\n",
272 b"{}: {}\n",
251 path.as_bytes(),
273 path.as_bytes(),
252 error.as_bytes()
274 error.as_bytes()
253 ))?
275 ))?
254 }
276 }
255 if !ds_status.unsure.is_empty() {
277 if !ds_status.unsure.is_empty() {
256 info!(
278 info!(
257 "Files to be rechecked by retrieval from filelog: {:?}",
279 "Files to be rechecked by retrieval from filelog: {:?}",
258 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
280 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
259 );
281 );
260 }
282 }
261 let mut fixup = Vec::new();
283 let mut fixup = Vec::new();
262 if !ds_status.unsure.is_empty()
284 if !ds_status.unsure.is_empty()
263 && (display_states.modified || display_states.clean)
285 && (display_states.modified || display_states.clean)
264 {
286 {
265 let p1 = repo.dirstate_parents()?.p1;
287 let p1 = repo.dirstate_parents()?.p1;
266 let manifest = repo.manifest_for_node(p1).map_err(|e| {
288 let manifest = repo.manifest_for_node(p1).map_err(|e| {
267 CommandError::from((e, &*format!("{:x}", p1.short())))
289 CommandError::from((e, &*format!("{:x}", p1.short())))
268 })?;
290 })?;
269 for to_check in ds_status.unsure {
291 for to_check in ds_status.unsure {
270 if unsure_is_modified(repo, &manifest, &to_check.path)? {
292 if unsure_is_modified(repo, &manifest, &to_check.path)? {
271 if display_states.modified {
293 if display_states.modified {
272 ds_status.modified.push(to_check);
294 ds_status.modified.push(to_check);
273 }
295 }
274 } else {
296 } else {
275 if display_states.clean {
297 if display_states.clean {
276 ds_status.clean.push(to_check.clone());
298 ds_status.clean.push(to_check.clone());
277 }
299 }
278 fixup.push(to_check.path.into_owned())
300 fixup.push(to_check.path.into_owned())
279 }
301 }
280 }
302 }
281 }
303 }
282 let relative_paths = (!ui.plain())
304 let relative_paths = (!ui.plain())
283 && config
305 && config
284 .get_option(b"commands", b"status.relative")?
306 .get_option(b"commands", b"status.relative")?
285 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
307 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
286 let output = DisplayStatusPaths {
308 let output = DisplayStatusPaths {
287 ui,
309 ui,
288 no_status,
310 no_status,
289 relativize: if relative_paths {
311 relativize: if relative_paths {
290 Some(RelativizePaths::new(repo)?)
312 Some(RelativizePaths::new(repo)?)
291 } else {
313 } else {
292 None
314 None
293 },
315 },
294 };
316 };
295 if display_states.modified {
317 if display_states.modified {
296 output.display(b"M", ds_status.modified)?;
318 output.display(b"M", ds_status.modified)?;
297 }
319 }
298 if display_states.added {
320 if display_states.added {
299 output.display(b"A", ds_status.added)?;
321 output.display(b"A", ds_status.added)?;
300 }
322 }
301 if display_states.removed {
323 if display_states.removed {
302 output.display(b"R", ds_status.removed)?;
324 output.display(b"R", ds_status.removed)?;
303 }
325 }
304 if display_states.deleted {
326 if display_states.deleted {
305 output.display(b"!", ds_status.deleted)?;
327 output.display(b"!", ds_status.deleted)?;
306 }
328 }
307 if display_states.unknown {
329 if display_states.unknown {
308 output.display(b"?", ds_status.unknown)?;
330 output.display(b"?", ds_status.unknown)?;
309 }
331 }
310 if display_states.ignored {
332 if display_states.ignored {
311 output.display(b"I", ds_status.ignored)?;
333 output.display(b"I", ds_status.ignored)?;
312 }
334 }
313 if display_states.clean {
335 if display_states.clean {
314 output.display(b"C", ds_status.clean)?;
336 output.display(b"C", ds_status.clean)?;
315 }
337 }
316
338
317 let mut dirstate_write_needed = ds_status.dirty;
339 let mut dirstate_write_needed = ds_status.dirty;
318 let filesystem_time_at_status_start =
340 let filesystem_time_at_status_start =
319 ds_status.filesystem_time_at_status_start;
341 ds_status.filesystem_time_at_status_start;
320
342
321 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
343 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
322 && !dirstate_write_needed
344 && !dirstate_write_needed
323 {
345 {
324 // Nothing to update
346 // Nothing to update
325 return Ok(());
347 return Ok(());
326 }
348 }
327
349
328 // Update the dirstate on disk if we can
350 // Update the dirstate on disk if we can
329 let with_lock_result =
351 let with_lock_result =
330 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
352 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
331 if let Some(mtime_boundary) = filesystem_time_at_status_start {
353 if let Some(mtime_boundary) = filesystem_time_at_status_start {
332 for hg_path in fixup {
354 for hg_path in fixup {
333 use std::os::unix::fs::MetadataExt;
355 use std::os::unix::fs::MetadataExt;
334 let fs_path = hg_path_to_path_buf(&hg_path)
356 let fs_path = hg_path_to_path_buf(&hg_path)
335 .expect("HgPath conversion");
357 .expect("HgPath conversion");
336 // Specifically do not reuse `fs_metadata` from
358 // Specifically do not reuse `fs_metadata` from
337 // `unsure_is_clean` which was needed before reading
359 // `unsure_is_clean` which was needed before reading
338 // contents. Here we access metadata again after reading
360 // contents. Here we access metadata again after reading
339 // content, in case it changed in the meantime.
361 // content, in case it changed in the meantime.
340 let fs_metadata = repo
362 let fs_metadata = repo
341 .working_directory_vfs()
363 .working_directory_vfs()
342 .symlink_metadata(&fs_path)?;
364 .symlink_metadata(&fs_path)?;
343 if let Some(mtime) =
365 if let Some(mtime) =
344 TruncatedTimestamp::for_reliable_mtime_of(
366 TruncatedTimestamp::for_reliable_mtime_of(
345 &fs_metadata,
367 &fs_metadata,
346 &mtime_boundary,
368 &mtime_boundary,
347 )
369 )
348 .when_reading_file(&fs_path)?
370 .when_reading_file(&fs_path)?
349 {
371 {
350 let mode = fs_metadata.mode();
372 let mode = fs_metadata.mode();
351 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
373 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
352 let mut entry = dmap
374 let mut entry = dmap
353 .get(&hg_path)?
375 .get(&hg_path)?
354 .expect("ambiguous file not in dirstate");
376 .expect("ambiguous file not in dirstate");
355 entry.set_clean(mode, size, mtime);
377 entry.set_clean(mode, size, mtime);
356 dmap.add_file(&hg_path, entry)?;
378 dmap.add_file(&hg_path, entry)?;
357 dirstate_write_needed = true
379 dirstate_write_needed = true
358 }
380 }
359 }
381 }
360 }
382 }
361 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
383 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
362 if dirstate_write_needed {
384 if dirstate_write_needed {
363 repo.write_dirstate()?
385 repo.write_dirstate()?
364 }
386 }
365 Ok(())
387 Ok(())
366 });
388 });
367 match with_lock_result {
389 match with_lock_result {
368 Ok(closure_result) => closure_result?,
390 Ok(closure_result) => closure_result?,
369 Err(LockError::AlreadyHeld) => {
391 Err(LockError::AlreadyHeld) => {
370 // Not updating the dirstate is not ideal but not critical:
392 // Not updating the dirstate is not ideal but not critical:
371 // don’t keep our caller waiting until some other Mercurial
393 // don’t keep our caller waiting until some other Mercurial
372 // process releases the lock.
394 // process releases the lock.
373 }
395 }
374 Err(LockError::Other(HgError::IoError { error, .. }))
396 Err(LockError::Other(HgError::IoError { error, .. }))
375 if error.kind() == io::ErrorKind::PermissionDenied =>
397 if error.kind() == io::ErrorKind::PermissionDenied =>
376 {
398 {
377 // `hg status` on a read-only repository is fine
399 // `hg status` on a read-only repository is fine
378 }
400 }
379 Err(LockError::Other(error)) => {
401 Err(LockError::Other(error)) => {
380 // Report other I/O errors
402 // Report other I/O errors
381 Err(error)?
403 Err(error)?
382 }
404 }
383 }
405 }
384 Ok(())
406 Ok(())
385 }
407 }
386
408
387 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
409 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
388 let mut ignore_files = Vec::new();
410 let mut ignore_files = Vec::new();
389 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
411 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
390 if repo_ignore.exists() {
412 if repo_ignore.exists() {
391 ignore_files.push(repo_ignore)
413 ignore_files.push(repo_ignore)
392 }
414 }
393 for (key, value) in config.iter_section(b"ui") {
415 for (key, value) in config.iter_section(b"ui") {
394 if key == b"ignore" || key.starts_with(b"ignore.") {
416 if key == b"ignore" || key.starts_with(b"ignore.") {
395 let path = get_path_from_bytes(value);
417 let path = get_path_from_bytes(value);
396 // TODO: expand "~/" and environment variable here, like Python
418 // TODO: expand "~/" and environment variable here, like Python
397 // does with `os.path.expanduser` and `os.path.expandvars`
419 // does with `os.path.expanduser` and `os.path.expandvars`
398
420
399 let joined = repo.working_directory_path().join(path);
421 let joined = repo.working_directory_path().join(path);
400 ignore_files.push(joined);
422 ignore_files.push(joined);
401 }
423 }
402 }
424 }
403 ignore_files
425 ignore_files
404 }
426 }
405
427
406 struct DisplayStatusPaths<'a> {
428 struct DisplayStatusPaths<'a> {
407 ui: &'a Ui,
429 ui: &'a Ui,
408 no_status: bool,
430 no_status: bool,
409 relativize: Option<RelativizePaths>,
431 relativize: Option<RelativizePaths>,
410 }
432 }
411
433
412 impl DisplayStatusPaths<'_> {
434 impl DisplayStatusPaths<'_> {
413 // Probably more elegant to use a Deref or Borrow trait rather than
435 // Probably more elegant to use a Deref or Borrow trait rather than
414 // harcode HgPathBuf, but probably not really useful at this point
436 // harcode HgPathBuf, but probably not really useful at this point
415 fn display(
437 fn display(
416 &self,
438 &self,
417 status_prefix: &[u8],
439 status_prefix: &[u8],
418 mut paths: Vec<StatusPath<'_>>,
440 mut paths: Vec<StatusPath<'_>>,
419 ) -> Result<(), CommandError> {
441 ) -> Result<(), CommandError> {
420 paths.sort_unstable();
442 paths.sort_unstable();
421 for StatusPath { path, copy_source } in paths {
443 for StatusPath { path, copy_source } in paths {
422 let relative;
444 let relative;
423 let path = if let Some(relativize) = &self.relativize {
445 let path = if let Some(relativize) = &self.relativize {
424 relative = relativize.relativize(&path);
446 relative = relativize.relativize(&path);
425 &*relative
447 &*relative
426 } else {
448 } else {
427 path.as_bytes()
449 path.as_bytes()
428 };
450 };
429 // TODO optim, probably lots of unneeded copies here, especially
451 // TODO optim, probably lots of unneeded copies here, especially
430 // if out stream is buffered
452 // if out stream is buffered
431 if self.no_status {
453 if self.no_status {
432 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
454 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
433 } else {
455 } else {
434 self.ui.write_stdout(&format_bytes!(
456 self.ui.write_stdout(&format_bytes!(
435 b"{} {}\n",
457 b"{} {}\n",
436 status_prefix,
458 status_prefix,
437 path
459 path
438 ))?
460 ))?
439 }
461 }
440 if let Some(source) = copy_source {
462 if let Some(source) = copy_source {
441 self.ui.write_stdout(&format_bytes!(
463 self.ui.write_stdout(&format_bytes!(
442 b" {}\n",
464 b" {}\n",
443 source.as_bytes()
465 source.as_bytes()
444 ))?
466 ))?
445 }
467 }
446 }
468 }
447 Ok(())
469 Ok(())
448 }
470 }
449 }
471 }
450
472
451 /// Check if a file is modified by comparing actual repo store and file system.
473 /// Check if a file is modified by comparing actual repo store and file system.
452 ///
474 ///
453 /// This meant to be used for those that the dirstate cannot resolve, due
475 /// This meant to be used for those that the dirstate cannot resolve, due
454 /// to time resolution limits.
476 /// to time resolution limits.
455 fn unsure_is_modified(
477 fn unsure_is_modified(
456 repo: &Repo,
478 repo: &Repo,
457 manifest: &Manifest,
479 manifest: &Manifest,
458 hg_path: &HgPath,
480 hg_path: &HgPath,
459 ) -> Result<bool, HgError> {
481 ) -> Result<bool, HgError> {
460 let vfs = repo.working_directory_vfs();
482 let vfs = repo.working_directory_vfs();
461 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
483 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
462 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
484 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
463 let is_symlink = fs_metadata.file_type().is_symlink();
485 let is_symlink = fs_metadata.file_type().is_symlink();
464 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
486 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
465 // dirstate
487 // dirstate
466 let fs_flags = if is_symlink {
488 let fs_flags = if is_symlink {
467 Some(b'l')
489 Some(b'l')
468 } else if has_exec_bit(&fs_metadata) {
490 } else if has_exec_bit(&fs_metadata) {
469 Some(b'x')
491 Some(b'x')
470 } else {
492 } else {
471 None
493 None
472 };
494 };
473
495
474 let entry = manifest
496 let entry = manifest
475 .find_by_path(hg_path)?
497 .find_by_path(hg_path)?
476 .expect("ambgious file not in p1");
498 .expect("ambgious file not in p1");
477 if entry.flags != fs_flags {
499 if entry.flags != fs_flags {
478 return Ok(true);
500 return Ok(true);
479 }
501 }
480 let filelog = repo.filelog(hg_path)?;
502 let filelog = repo.filelog(hg_path)?;
481 let fs_len = fs_metadata.len();
503 let fs_len = fs_metadata.len();
482 // TODO: check `fs_len` here like below, but based on
504 // TODO: check `fs_len` here like below, but based on
483 // `RevlogEntry::uncompressed_len` without decompressing the full filelog
505 // `RevlogEntry::uncompressed_len` without decompressing the full filelog
484 // contents where possible. This is only valid if the revlog data does not
506 // contents where possible. This is only valid if the revlog data does not
485 // contain metadata. See how Python’s `revlog.rawsize` calls
507 // contain metadata. See how Python’s `revlog.rawsize` calls
486 // `storageutil.filerevisioncopied`.
508 // `storageutil.filerevisioncopied`.
487 // (Maybe also check for content-modifying flags? See `revlog.size`.)
509 // (Maybe also check for content-modifying flags? See `revlog.size`.)
488 let filelog_entry =
510 let filelog_entry =
489 filelog.data_for_node(entry.node_id()?).map_err(|_| {
511 filelog.data_for_node(entry.node_id()?).map_err(|_| {
490 HgError::corrupted("filelog missing node from manifest")
512 HgError::corrupted("filelog missing node from manifest")
491 })?;
513 })?;
492 let contents_in_p1 = filelog_entry.data()?;
514 let contents_in_p1 = filelog_entry.data()?;
493 if contents_in_p1.len() as u64 != fs_len {
515 if contents_in_p1.len() as u64 != fs_len {
494 // No need to read the file contents:
516 // No need to read the file contents:
495 // it cannot be equal if it has a different length.
517 // it cannot be equal if it has a different length.
496 return Ok(true);
518 return Ok(true);
497 }
519 }
498
520
499 let fs_contents = if is_symlink {
521 let fs_contents = if is_symlink {
500 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
522 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
501 } else {
523 } else {
502 vfs.read(fs_path)?
524 vfs.read(fs_path)?
503 };
525 };
504 Ok(contents_in_p1 != &*fs_contents)
526 Ok(contents_in_p1 != &*fs_contents)
505 }
527 }
General Comments 0
You need to be logged in to leave comments. Login now