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