##// END OF EJS Templates
rhg: fix race when a fixup file is deleted on disk...
Raphaël Gomès -
r51120:53ca3e3b stable
parent child Browse files
Show More
@@ -1,634 +1,646 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::config::Config;
13 use hg::config::Config;
14 use hg::dirstate::has_exec_bit;
14 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::status::StatusPath;
15 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::TruncatedTimestamp;
16 use hg::dirstate::TruncatedTimestamp;
17 use hg::errors::{HgError, IoResultExt};
17 use hg::errors::{HgError, IoResultExt};
18 use hg::lock::LockError;
18 use hg::lock::LockError;
19 use hg::manifest::Manifest;
19 use hg::manifest::Manifest;
20 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
20 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
21 use hg::repo::Repo;
21 use hg::repo::Repo;
22 use hg::utils::debug::debug_wait_for_file;
22 use hg::utils::debug::debug_wait_for_file;
23 use hg::utils::files::get_bytes_from_os_string;
23 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_path;
24 use hg::utils::files::get_bytes_from_path;
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::DirstateStatus;
27 use hg::DirstateStatus;
28 use hg::PatternFileWarning;
28 use hg::PatternFileWarning;
29 use hg::StatusError;
29 use hg::StatusError;
30 use hg::StatusOptions;
30 use hg::StatusOptions;
31 use hg::{self, narrow, sparse};
31 use hg::{self, narrow, sparse};
32 use log::info;
32 use log::info;
33 use rayon::prelude::*;
33 use rayon::prelude::*;
34 use std::io;
34 use std::io;
35 use std::path::PathBuf;
35 use std::path::PathBuf;
36
36
37 pub const HELP_TEXT: &str = "
37 pub const HELP_TEXT: &str = "
38 Show changed files in the working directory
38 Show changed files in the working directory
39
39
40 This is a pure Rust version of `hg status`.
40 This is a pure Rust version of `hg status`.
41
41
42 Some options might be missing, check the list below.
42 Some options might be missing, check the list below.
43 ";
43 ";
44
44
45 pub fn args() -> clap::App<'static, 'static> {
45 pub fn args() -> clap::App<'static, 'static> {
46 SubCommand::with_name("status")
46 SubCommand::with_name("status")
47 .alias("st")
47 .alias("st")
48 .about(HELP_TEXT)
48 .about(HELP_TEXT)
49 .arg(
49 .arg(
50 Arg::with_name("all")
50 Arg::with_name("all")
51 .help("show status of all files")
51 .help("show status of all files")
52 .short("-A")
52 .short("-A")
53 .long("--all"),
53 .long("--all"),
54 )
54 )
55 .arg(
55 .arg(
56 Arg::with_name("modified")
56 Arg::with_name("modified")
57 .help("show only modified files")
57 .help("show only modified files")
58 .short("-m")
58 .short("-m")
59 .long("--modified"),
59 .long("--modified"),
60 )
60 )
61 .arg(
61 .arg(
62 Arg::with_name("added")
62 Arg::with_name("added")
63 .help("show only added files")
63 .help("show only added files")
64 .short("-a")
64 .short("-a")
65 .long("--added"),
65 .long("--added"),
66 )
66 )
67 .arg(
67 .arg(
68 Arg::with_name("removed")
68 Arg::with_name("removed")
69 .help("show only removed files")
69 .help("show only removed files")
70 .short("-r")
70 .short("-r")
71 .long("--removed"),
71 .long("--removed"),
72 )
72 )
73 .arg(
73 .arg(
74 Arg::with_name("clean")
74 Arg::with_name("clean")
75 .help("show only clean files")
75 .help("show only clean files")
76 .short("-c")
76 .short("-c")
77 .long("--clean"),
77 .long("--clean"),
78 )
78 )
79 .arg(
79 .arg(
80 Arg::with_name("deleted")
80 Arg::with_name("deleted")
81 .help("show only deleted files")
81 .help("show only deleted files")
82 .short("-d")
82 .short("-d")
83 .long("--deleted"),
83 .long("--deleted"),
84 )
84 )
85 .arg(
85 .arg(
86 Arg::with_name("unknown")
86 Arg::with_name("unknown")
87 .help("show only unknown (not tracked) files")
87 .help("show only unknown (not tracked) files")
88 .short("-u")
88 .short("-u")
89 .long("--unknown"),
89 .long("--unknown"),
90 )
90 )
91 .arg(
91 .arg(
92 Arg::with_name("ignored")
92 Arg::with_name("ignored")
93 .help("show only ignored files")
93 .help("show only ignored files")
94 .short("-i")
94 .short("-i")
95 .long("--ignored"),
95 .long("--ignored"),
96 )
96 )
97 .arg(
97 .arg(
98 Arg::with_name("copies")
98 Arg::with_name("copies")
99 .help("show source of copied files (DEFAULT: ui.statuscopies)")
99 .help("show source of copied files (DEFAULT: ui.statuscopies)")
100 .short("-C")
100 .short("-C")
101 .long("--copies"),
101 .long("--copies"),
102 )
102 )
103 .arg(
103 .arg(
104 Arg::with_name("no-status")
104 Arg::with_name("no-status")
105 .help("hide status prefix")
105 .help("hide status prefix")
106 .short("-n")
106 .short("-n")
107 .long("--no-status"),
107 .long("--no-status"),
108 )
108 )
109 .arg(
109 .arg(
110 Arg::with_name("verbose")
110 Arg::with_name("verbose")
111 .help("enable additional output")
111 .help("enable additional output")
112 .short("-v")
112 .short("-v")
113 .long("--verbose"),
113 .long("--verbose"),
114 )
114 )
115 }
115 }
116
116
117 /// Pure data type allowing the caller to specify file states to display
117 /// Pure data type allowing the caller to specify file states to display
118 #[derive(Copy, Clone, Debug)]
118 #[derive(Copy, Clone, Debug)]
119 pub struct DisplayStates {
119 pub struct DisplayStates {
120 pub modified: bool,
120 pub modified: bool,
121 pub added: bool,
121 pub added: bool,
122 pub removed: bool,
122 pub removed: bool,
123 pub clean: bool,
123 pub clean: bool,
124 pub deleted: bool,
124 pub deleted: bool,
125 pub unknown: bool,
125 pub unknown: bool,
126 pub ignored: bool,
126 pub ignored: bool,
127 }
127 }
128
128
129 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
129 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
130 modified: true,
130 modified: true,
131 added: true,
131 added: true,
132 removed: true,
132 removed: true,
133 clean: false,
133 clean: false,
134 deleted: true,
134 deleted: true,
135 unknown: true,
135 unknown: true,
136 ignored: false,
136 ignored: false,
137 };
137 };
138
138
139 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
139 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
140 modified: true,
140 modified: true,
141 added: true,
141 added: true,
142 removed: true,
142 removed: true,
143 clean: true,
143 clean: true,
144 deleted: true,
144 deleted: true,
145 unknown: true,
145 unknown: true,
146 ignored: true,
146 ignored: true,
147 };
147 };
148
148
149 impl DisplayStates {
149 impl DisplayStates {
150 pub fn is_empty(&self) -> bool {
150 pub fn is_empty(&self) -> bool {
151 !(self.modified
151 !(self.modified
152 || self.added
152 || self.added
153 || self.removed
153 || self.removed
154 || self.clean
154 || self.clean
155 || self.deleted
155 || self.deleted
156 || self.unknown
156 || self.unknown
157 || self.ignored)
157 || self.ignored)
158 }
158 }
159 }
159 }
160
160
161 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
161 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
162 return Ok(repo.dirstate_parents()?.is_merge());
162 return Ok(repo.dirstate_parents()?.is_merge());
163 }
163 }
164
164
165 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
165 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
166 // These are all the known values for the [fname] argument of
166 // These are all the known values for the [fname] argument of
167 // [addunfinished] function in [state.py]
167 // [addunfinished] function in [state.py]
168 let known_state_files: &[&str] = &[
168 let known_state_files: &[&str] = &[
169 "bisect.state",
169 "bisect.state",
170 "graftstate",
170 "graftstate",
171 "histedit-state",
171 "histedit-state",
172 "rebasestate",
172 "rebasestate",
173 "shelvedstate",
173 "shelvedstate",
174 "transplant/journal",
174 "transplant/journal",
175 "updatestate",
175 "updatestate",
176 ];
176 ];
177 if has_unfinished_merge(repo)? {
177 if has_unfinished_merge(repo)? {
178 return Ok(true);
178 return Ok(true);
179 };
179 };
180 for f in known_state_files {
180 for f in known_state_files {
181 if repo.hg_vfs().join(f).exists() {
181 if repo.hg_vfs().join(f).exists() {
182 return Ok(true);
182 return Ok(true);
183 }
183 }
184 }
184 }
185 return Ok(false);
185 return Ok(false);
186 }
186 }
187
187
188 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
188 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
189 // TODO: lift these limitations
189 // TODO: lift these limitations
190 if invocation
190 if invocation
191 .config
191 .config
192 .get(b"commands", b"status.terse")
192 .get(b"commands", b"status.terse")
193 .is_some()
193 .is_some()
194 {
194 {
195 return Err(CommandError::unsupported(
195 return Err(CommandError::unsupported(
196 "status.terse is not yet supported with rhg status",
196 "status.terse is not yet supported with rhg status",
197 ));
197 ));
198 }
198 }
199
199
200 let ui = invocation.ui;
200 let ui = invocation.ui;
201 let config = invocation.config;
201 let config = invocation.config;
202 let args = invocation.subcommand_args;
202 let args = invocation.subcommand_args;
203
203
204 let verbose = !args.is_present("print0")
204 let verbose = !args.is_present("print0")
205 && (args.is_present("verbose")
205 && (args.is_present("verbose")
206 || config.get_bool(b"ui", b"verbose")?
206 || config.get_bool(b"ui", b"verbose")?
207 || config.get_bool(b"commands", b"status.verbose")?);
207 || config.get_bool(b"commands", b"status.verbose")?);
208
208
209 let all = args.is_present("all");
209 let all = args.is_present("all");
210 let display_states = if all {
210 let display_states = if all {
211 // TODO when implementing `--quiet`: it excludes clean files
211 // TODO when implementing `--quiet`: it excludes clean files
212 // from `--all`
212 // from `--all`
213 ALL_DISPLAY_STATES
213 ALL_DISPLAY_STATES
214 } else {
214 } else {
215 let requested = DisplayStates {
215 let requested = DisplayStates {
216 modified: args.is_present("modified"),
216 modified: args.is_present("modified"),
217 added: args.is_present("added"),
217 added: args.is_present("added"),
218 removed: args.is_present("removed"),
218 removed: args.is_present("removed"),
219 clean: args.is_present("clean"),
219 clean: args.is_present("clean"),
220 deleted: args.is_present("deleted"),
220 deleted: args.is_present("deleted"),
221 unknown: args.is_present("unknown"),
221 unknown: args.is_present("unknown"),
222 ignored: args.is_present("ignored"),
222 ignored: args.is_present("ignored"),
223 };
223 };
224 if requested.is_empty() {
224 if requested.is_empty() {
225 DEFAULT_DISPLAY_STATES
225 DEFAULT_DISPLAY_STATES
226 } else {
226 } else {
227 requested
227 requested
228 }
228 }
229 };
229 };
230 let no_status = args.is_present("no-status");
230 let no_status = args.is_present("no-status");
231 let list_copies = all
231 let list_copies = all
232 || args.is_present("copies")
232 || args.is_present("copies")
233 || config.get_bool(b"ui", b"statuscopies")?;
233 || config.get_bool(b"ui", b"statuscopies")?;
234
234
235 let repo = invocation.repo?;
235 let repo = invocation.repo?;
236
236
237 if verbose {
237 if verbose {
238 if has_unfinished_state(repo)? {
238 if has_unfinished_state(repo)? {
239 return Err(CommandError::unsupported(
239 return Err(CommandError::unsupported(
240 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
240 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
241 ));
241 ));
242 };
242 };
243 }
243 }
244
244
245 let mut dmap = repo.dirstate_map_mut()?;
245 let mut dmap = repo.dirstate_map_mut()?;
246
246
247 let options = StatusOptions {
247 let options = StatusOptions {
248 // we're currently supporting file systems with exec flags only
248 // we're currently supporting file systems with exec flags only
249 // anyway
249 // anyway
250 check_exec: true,
250 check_exec: true,
251 list_clean: display_states.clean,
251 list_clean: display_states.clean,
252 list_unknown: display_states.unknown,
252 list_unknown: display_states.unknown,
253 list_ignored: display_states.ignored,
253 list_ignored: display_states.ignored,
254 list_copies,
254 list_copies,
255 collect_traversed_dirs: false,
255 collect_traversed_dirs: false,
256 };
256 };
257
257
258 type StatusResult<'a> =
258 type StatusResult<'a> =
259 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
259 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
260
260
261 let after_status = |res: StatusResult| -> Result<_, CommandError> {
261 let after_status = |res: StatusResult| -> Result<_, CommandError> {
262 let (mut ds_status, pattern_warnings) = res?;
262 let (mut ds_status, pattern_warnings) = res?;
263 for warning in pattern_warnings {
263 for warning in pattern_warnings {
264 ui.write_stderr(&print_pattern_file_warning(&warning, &repo))?;
264 ui.write_stderr(&print_pattern_file_warning(&warning, &repo))?;
265 }
265 }
266
266
267 for (path, error) in ds_status.bad {
267 for (path, error) in ds_status.bad {
268 let error = match error {
268 let error = match error {
269 hg::BadMatch::OsError(code) => {
269 hg::BadMatch::OsError(code) => {
270 std::io::Error::from_raw_os_error(code).to_string()
270 std::io::Error::from_raw_os_error(code).to_string()
271 }
271 }
272 hg::BadMatch::BadType(ty) => {
272 hg::BadMatch::BadType(ty) => {
273 format!("unsupported file type (type is {})", ty)
273 format!("unsupported file type (type is {})", ty)
274 }
274 }
275 };
275 };
276 ui.write_stderr(&format_bytes!(
276 ui.write_stderr(&format_bytes!(
277 b"{}: {}\n",
277 b"{}: {}\n",
278 path.as_bytes(),
278 path.as_bytes(),
279 error.as_bytes()
279 error.as_bytes()
280 ))?
280 ))?
281 }
281 }
282 if !ds_status.unsure.is_empty() {
282 if !ds_status.unsure.is_empty() {
283 info!(
283 info!(
284 "Files to be rechecked by retrieval from filelog: {:?}",
284 "Files to be rechecked by retrieval from filelog: {:?}",
285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
286 );
286 );
287 }
287 }
288 let mut fixup = Vec::new();
288 let mut fixup = Vec::new();
289 if !ds_status.unsure.is_empty()
289 if !ds_status.unsure.is_empty()
290 && (display_states.modified || display_states.clean)
290 && (display_states.modified || display_states.clean)
291 {
291 {
292 let p1 = repo.dirstate_parents()?.p1;
292 let p1 = repo.dirstate_parents()?.p1;
293 let manifest = repo.manifest_for_node(p1).map_err(|e| {
293 let manifest = repo.manifest_for_node(p1).map_err(|e| {
294 CommandError::from((e, &*format!("{:x}", p1.short())))
294 CommandError::from((e, &*format!("{:x}", p1.short())))
295 })?;
295 })?;
296 let working_directory_vfs = repo.working_directory_vfs();
296 let working_directory_vfs = repo.working_directory_vfs();
297 let store_vfs = repo.store_vfs();
297 let store_vfs = repo.store_vfs();
298 let res: Vec<_> = ds_status
298 let res: Vec<_> = ds_status
299 .unsure
299 .unsure
300 .into_par_iter()
300 .into_par_iter()
301 .map(|to_check| {
301 .map(|to_check| {
302 unsure_is_modified(
302 unsure_is_modified(
303 working_directory_vfs,
303 working_directory_vfs,
304 store_vfs,
304 store_vfs,
305 &manifest,
305 &manifest,
306 &to_check.path,
306 &to_check.path,
307 )
307 )
308 .map(|modified| (to_check, modified))
308 .map(|modified| (to_check, modified))
309 })
309 })
310 .collect::<Result<_, _>>()?;
310 .collect::<Result<_, _>>()?;
311 for (status_path, is_modified) in res.into_iter() {
311 for (status_path, is_modified) in res.into_iter() {
312 if is_modified {
312 if is_modified {
313 if display_states.modified {
313 if display_states.modified {
314 ds_status.modified.push(status_path);
314 ds_status.modified.push(status_path);
315 }
315 }
316 } else {
316 } else {
317 if display_states.clean {
317 if display_states.clean {
318 ds_status.clean.push(status_path.clone());
318 ds_status.clean.push(status_path.clone());
319 }
319 }
320 fixup.push(status_path.path.into_owned())
320 fixup.push(status_path.path.into_owned())
321 }
321 }
322 }
322 }
323 }
323 }
324 let relative_paths = config
324 let relative_paths = config
325 .get_option(b"commands", b"status.relative")?
325 .get_option(b"commands", b"status.relative")?
326 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
326 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
327 let output = DisplayStatusPaths {
327 let output = DisplayStatusPaths {
328 ui,
328 ui,
329 no_status,
329 no_status,
330 relativize: if relative_paths {
330 relativize: if relative_paths {
331 Some(RelativizePaths::new(repo)?)
331 Some(RelativizePaths::new(repo)?)
332 } else {
332 } else {
333 None
333 None
334 },
334 },
335 };
335 };
336 if display_states.modified {
336 if display_states.modified {
337 output.display(b"M ", "status.modified", ds_status.modified)?;
337 output.display(b"M ", "status.modified", ds_status.modified)?;
338 }
338 }
339 if display_states.added {
339 if display_states.added {
340 output.display(b"A ", "status.added", ds_status.added)?;
340 output.display(b"A ", "status.added", ds_status.added)?;
341 }
341 }
342 if display_states.removed {
342 if display_states.removed {
343 output.display(b"R ", "status.removed", ds_status.removed)?;
343 output.display(b"R ", "status.removed", ds_status.removed)?;
344 }
344 }
345 if display_states.deleted {
345 if display_states.deleted {
346 output.display(b"! ", "status.deleted", ds_status.deleted)?;
346 output.display(b"! ", "status.deleted", ds_status.deleted)?;
347 }
347 }
348 if display_states.unknown {
348 if display_states.unknown {
349 output.display(b"? ", "status.unknown", ds_status.unknown)?;
349 output.display(b"? ", "status.unknown", ds_status.unknown)?;
350 }
350 }
351 if display_states.ignored {
351 if display_states.ignored {
352 output.display(b"I ", "status.ignored", ds_status.ignored)?;
352 output.display(b"I ", "status.ignored", ds_status.ignored)?;
353 }
353 }
354 if display_states.clean {
354 if display_states.clean {
355 output.display(b"C ", "status.clean", ds_status.clean)?;
355 output.display(b"C ", "status.clean", ds_status.clean)?;
356 }
356 }
357
357
358 let dirstate_write_needed = ds_status.dirty;
358 let dirstate_write_needed = ds_status.dirty;
359 let filesystem_time_at_status_start =
359 let filesystem_time_at_status_start =
360 ds_status.filesystem_time_at_status_start;
360 ds_status.filesystem_time_at_status_start;
361
361
362 Ok((
362 Ok((
363 fixup,
363 fixup,
364 dirstate_write_needed,
364 dirstate_write_needed,
365 filesystem_time_at_status_start,
365 filesystem_time_at_status_start,
366 ))
366 ))
367 };
367 };
368 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
368 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
369 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
369 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
370 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
370 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
371 (true, true) => {
371 (true, true) => {
372 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
372 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
373 }
373 }
374 (true, false) => narrow_matcher,
374 (true, false) => narrow_matcher,
375 (false, true) => sparse_matcher,
375 (false, true) => sparse_matcher,
376 (false, false) => Box::new(AlwaysMatcher),
376 (false, false) => Box::new(AlwaysMatcher),
377 };
377 };
378
378
379 for warning in narrow_warnings.into_iter().chain(sparse_warnings) {
379 for warning in narrow_warnings.into_iter().chain(sparse_warnings) {
380 match &warning {
380 match &warning {
381 sparse::SparseWarning::RootWarning { context, line } => {
381 sparse::SparseWarning::RootWarning { context, line } => {
382 let msg = format_bytes!(
382 let msg = format_bytes!(
383 b"warning: {} profile cannot use paths \"
383 b"warning: {} profile cannot use paths \"
384 starting with /, ignoring {}\n",
384 starting with /, ignoring {}\n",
385 context,
385 context,
386 line
386 line
387 );
387 );
388 ui.write_stderr(&msg)?;
388 ui.write_stderr(&msg)?;
389 }
389 }
390 sparse::SparseWarning::ProfileNotFound { profile, rev } => {
390 sparse::SparseWarning::ProfileNotFound { profile, rev } => {
391 let msg = format_bytes!(
391 let msg = format_bytes!(
392 b"warning: sparse profile '{}' not found \"
392 b"warning: sparse profile '{}' not found \"
393 in rev {} - ignoring it\n",
393 in rev {} - ignoring it\n",
394 profile,
394 profile,
395 rev
395 rev
396 );
396 );
397 ui.write_stderr(&msg)?;
397 ui.write_stderr(&msg)?;
398 }
398 }
399 sparse::SparseWarning::Pattern(e) => {
399 sparse::SparseWarning::Pattern(e) => {
400 ui.write_stderr(&print_pattern_file_warning(e, &repo))?;
400 ui.write_stderr(&print_pattern_file_warning(e, &repo))?;
401 }
401 }
402 }
402 }
403 }
403 }
404 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
404 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
405 dmap.with_status(
405 dmap.with_status(
406 matcher.as_ref(),
406 matcher.as_ref(),
407 repo.working_directory_path().to_owned(),
407 repo.working_directory_path().to_owned(),
408 ignore_files(repo, config),
408 ignore_files(repo, config),
409 options,
409 options,
410 after_status,
410 after_status,
411 )?;
411 )?;
412
412
413 // Development config option to test write races
413 // Development config option to test write races
414 if let Err(e) =
414 if let Err(e) =
415 debug_wait_for_file(&config, "status.pre-dirstate-write-file")
415 debug_wait_for_file(&config, "status.pre-dirstate-write-file")
416 {
416 {
417 ui.write_stderr(e.as_bytes()).ok();
417 ui.write_stderr(e.as_bytes()).ok();
418 }
418 }
419
419
420 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
420 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
421 && !dirstate_write_needed
421 && !dirstate_write_needed
422 {
422 {
423 // Nothing to update
423 // Nothing to update
424 return Ok(());
424 return Ok(());
425 }
425 }
426
426
427 // Update the dirstate on disk if we can
427 // Update the dirstate on disk if we can
428 let with_lock_result =
428 let with_lock_result =
429 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
429 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
430 if let Some(mtime_boundary) = filesystem_time_at_status_start {
430 if let Some(mtime_boundary) = filesystem_time_at_status_start {
431 for hg_path in fixup {
431 for hg_path in fixup {
432 use std::os::unix::fs::MetadataExt;
432 use std::os::unix::fs::MetadataExt;
433 let fs_path = hg_path_to_path_buf(&hg_path)
433 let fs_path = hg_path_to_path_buf(&hg_path)
434 .expect("HgPath conversion");
434 .expect("HgPath conversion");
435 // Specifically do not reuse `fs_metadata` from
435 // Specifically do not reuse `fs_metadata` from
436 // `unsure_is_clean` which was needed before reading
436 // `unsure_is_clean` which was needed before reading
437 // contents. Here we access metadata again after reading
437 // contents. Here we access metadata again after reading
438 // content, in case it changed in the meantime.
438 // content, in case it changed in the meantime.
439 let fs_metadata = repo
439 let metadata_res = repo
440 .working_directory_vfs()
440 .working_directory_vfs()
441 .symlink_metadata(&fs_path)?;
441 .symlink_metadata(&fs_path);
442 let fs_metadata = match metadata_res {
443 Ok(meta) => meta,
444 Err(err) => match err {
445 HgError::IoError { .. } => {
446 // The file has probably been deleted. In any
447 // case, it was in the dirstate before, so
448 // let's ignore the error.
449 continue;
450 }
451 _ => return Err(err.into()),
452 },
453 };
442 if let Some(mtime) =
454 if let Some(mtime) =
443 TruncatedTimestamp::for_reliable_mtime_of(
455 TruncatedTimestamp::for_reliable_mtime_of(
444 &fs_metadata,
456 &fs_metadata,
445 &mtime_boundary,
457 &mtime_boundary,
446 )
458 )
447 .when_reading_file(&fs_path)?
459 .when_reading_file(&fs_path)?
448 {
460 {
449 let mode = fs_metadata.mode();
461 let mode = fs_metadata.mode();
450 let size = fs_metadata.len();
462 let size = fs_metadata.len();
451 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
463 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
452 dirstate_write_needed = true
464 dirstate_write_needed = true
453 }
465 }
454 }
466 }
455 }
467 }
456 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
468 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
457 if dirstate_write_needed {
469 if dirstate_write_needed {
458 repo.write_dirstate()?
470 repo.write_dirstate()?
459 }
471 }
460 Ok(())
472 Ok(())
461 });
473 });
462 match with_lock_result {
474 match with_lock_result {
463 Ok(closure_result) => closure_result?,
475 Ok(closure_result) => closure_result?,
464 Err(LockError::AlreadyHeld) => {
476 Err(LockError::AlreadyHeld) => {
465 // Not updating the dirstate is not ideal but not critical:
477 // Not updating the dirstate is not ideal but not critical:
466 // don’t keep our caller waiting until some other Mercurial
478 // don’t keep our caller waiting until some other Mercurial
467 // process releases the lock.
479 // process releases the lock.
468 log::info!("not writing dirstate from `status`: lock is held")
480 log::info!("not writing dirstate from `status`: lock is held")
469 }
481 }
470 Err(LockError::Other(HgError::IoError { error, .. }))
482 Err(LockError::Other(HgError::IoError { error, .. }))
471 if error.kind() == io::ErrorKind::PermissionDenied =>
483 if error.kind() == io::ErrorKind::PermissionDenied =>
472 {
484 {
473 // `hg status` on a read-only repository is fine
485 // `hg status` on a read-only repository is fine
474 }
486 }
475 Err(LockError::Other(error)) => {
487 Err(LockError::Other(error)) => {
476 // Report other I/O errors
488 // Report other I/O errors
477 Err(error)?
489 Err(error)?
478 }
490 }
479 }
491 }
480 Ok(())
492 Ok(())
481 }
493 }
482
494
483 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
495 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
484 let mut ignore_files = Vec::new();
496 let mut ignore_files = Vec::new();
485 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
497 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
486 if repo_ignore.exists() {
498 if repo_ignore.exists() {
487 ignore_files.push(repo_ignore)
499 ignore_files.push(repo_ignore)
488 }
500 }
489 for (key, value) in config.iter_section(b"ui") {
501 for (key, value) in config.iter_section(b"ui") {
490 if key == b"ignore" || key.starts_with(b"ignore.") {
502 if key == b"ignore" || key.starts_with(b"ignore.") {
491 let path = get_path_from_bytes(value);
503 let path = get_path_from_bytes(value);
492 // TODO: expand "~/" and environment variable here, like Python
504 // TODO: expand "~/" and environment variable here, like Python
493 // does with `os.path.expanduser` and `os.path.expandvars`
505 // does with `os.path.expanduser` and `os.path.expandvars`
494
506
495 let joined = repo.working_directory_path().join(path);
507 let joined = repo.working_directory_path().join(path);
496 ignore_files.push(joined);
508 ignore_files.push(joined);
497 }
509 }
498 }
510 }
499 ignore_files
511 ignore_files
500 }
512 }
501
513
502 struct DisplayStatusPaths<'a> {
514 struct DisplayStatusPaths<'a> {
503 ui: &'a Ui,
515 ui: &'a Ui,
504 no_status: bool,
516 no_status: bool,
505 relativize: Option<RelativizePaths>,
517 relativize: Option<RelativizePaths>,
506 }
518 }
507
519
508 impl DisplayStatusPaths<'_> {
520 impl DisplayStatusPaths<'_> {
509 // Probably more elegant to use a Deref or Borrow trait rather than
521 // Probably more elegant to use a Deref or Borrow trait rather than
510 // harcode HgPathBuf, but probably not really useful at this point
522 // harcode HgPathBuf, but probably not really useful at this point
511 fn display(
523 fn display(
512 &self,
524 &self,
513 status_prefix: &[u8],
525 status_prefix: &[u8],
514 label: &'static str,
526 label: &'static str,
515 mut paths: Vec<StatusPath<'_>>,
527 mut paths: Vec<StatusPath<'_>>,
516 ) -> Result<(), CommandError> {
528 ) -> Result<(), CommandError> {
517 paths.sort_unstable();
529 paths.sort_unstable();
518 // TODO: get the stdout lock once for the whole loop
530 // TODO: get the stdout lock once for the whole loop
519 // instead of in each write
531 // instead of in each write
520 for StatusPath { path, copy_source } in paths {
532 for StatusPath { path, copy_source } in paths {
521 let relative;
533 let relative;
522 let path = if let Some(relativize) = &self.relativize {
534 let path = if let Some(relativize) = &self.relativize {
523 relative = relativize.relativize(&path);
535 relative = relativize.relativize(&path);
524 &*relative
536 &*relative
525 } else {
537 } else {
526 path.as_bytes()
538 path.as_bytes()
527 };
539 };
528 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
540 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
529 // in order to stream to stdout instead of allocating an
541 // in order to stream to stdout instead of allocating an
530 // itermediate `Vec<u8>`.
542 // itermediate `Vec<u8>`.
531 if !self.no_status {
543 if !self.no_status {
532 self.ui.write_stdout_labelled(status_prefix, label)?
544 self.ui.write_stdout_labelled(status_prefix, label)?
533 }
545 }
534 self.ui
546 self.ui
535 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
547 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
536 if let Some(source) = copy_source {
548 if let Some(source) = copy_source {
537 let label = "status.copied";
549 let label = "status.copied";
538 self.ui.write_stdout_labelled(
550 self.ui.write_stdout_labelled(
539 &format_bytes!(b" {}\n", source.as_bytes()),
551 &format_bytes!(b" {}\n", source.as_bytes()),
540 label,
552 label,
541 )?
553 )?
542 }
554 }
543 }
555 }
544 Ok(())
556 Ok(())
545 }
557 }
546 }
558 }
547
559
548 /// Check if a file is modified by comparing actual repo store and file system.
560 /// Check if a file is modified by comparing actual repo store and file system.
549 ///
561 ///
550 /// This meant to be used for those that the dirstate cannot resolve, due
562 /// This meant to be used for those that the dirstate cannot resolve, due
551 /// to time resolution limits.
563 /// to time resolution limits.
552 fn unsure_is_modified(
564 fn unsure_is_modified(
553 working_directory_vfs: hg::vfs::Vfs,
565 working_directory_vfs: hg::vfs::Vfs,
554 store_vfs: hg::vfs::Vfs,
566 store_vfs: hg::vfs::Vfs,
555 manifest: &Manifest,
567 manifest: &Manifest,
556 hg_path: &HgPath,
568 hg_path: &HgPath,
557 ) -> Result<bool, HgError> {
569 ) -> Result<bool, HgError> {
558 let vfs = working_directory_vfs;
570 let vfs = working_directory_vfs;
559 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
571 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
560 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
572 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
561 let is_symlink = fs_metadata.file_type().is_symlink();
573 let is_symlink = fs_metadata.file_type().is_symlink();
562 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
574 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
563 // dirstate
575 // dirstate
564 let fs_flags = if is_symlink {
576 let fs_flags = if is_symlink {
565 Some(b'l')
577 Some(b'l')
566 } else if has_exec_bit(&fs_metadata) {
578 } else if has_exec_bit(&fs_metadata) {
567 Some(b'x')
579 Some(b'x')
568 } else {
580 } else {
569 None
581 None
570 };
582 };
571
583
572 let entry = manifest
584 let entry = manifest
573 .find_by_path(hg_path)?
585 .find_by_path(hg_path)?
574 .expect("ambgious file not in p1");
586 .expect("ambgious file not in p1");
575 if entry.flags != fs_flags {
587 if entry.flags != fs_flags {
576 return Ok(true);
588 return Ok(true);
577 }
589 }
578 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
590 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
579 let fs_len = fs_metadata.len();
591 let fs_len = fs_metadata.len();
580 let file_node = entry.node_id()?;
592 let file_node = entry.node_id()?;
581 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
593 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
582 HgError::corrupted(format!(
594 HgError::corrupted(format!(
583 "filelog missing node {:?} from manifest",
595 "filelog missing node {:?} from manifest",
584 file_node
596 file_node
585 ))
597 ))
586 })?;
598 })?;
587 if filelog_entry.file_data_len_not_equal_to(fs_len) {
599 if filelog_entry.file_data_len_not_equal_to(fs_len) {
588 // No need to read file contents:
600 // No need to read file contents:
589 // it cannot be equal if it has a different length.
601 // it cannot be equal if it has a different length.
590 return Ok(true);
602 return Ok(true);
591 }
603 }
592
604
593 let p1_filelog_data = filelog_entry.data()?;
605 let p1_filelog_data = filelog_entry.data()?;
594 let p1_contents = p1_filelog_data.file_data()?;
606 let p1_contents = p1_filelog_data.file_data()?;
595 if p1_contents.len() as u64 != fs_len {
607 if p1_contents.len() as u64 != fs_len {
596 // No need to read file contents:
608 // No need to read file contents:
597 // it cannot be equal if it has a different length.
609 // it cannot be equal if it has a different length.
598 return Ok(true);
610 return Ok(true);
599 }
611 }
600
612
601 let fs_contents = if is_symlink {
613 let fs_contents = if is_symlink {
602 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
614 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
603 } else {
615 } else {
604 vfs.read(fs_path)?
616 vfs.read(fs_path)?
605 };
617 };
606 Ok(p1_contents != &*fs_contents)
618 Ok(p1_contents != &*fs_contents)
607 }
619 }
608
620
609 fn print_pattern_file_warning(
621 fn print_pattern_file_warning(
610 warning: &PatternFileWarning,
622 warning: &PatternFileWarning,
611 repo: &Repo,
623 repo: &Repo,
612 ) -> Vec<u8> {
624 ) -> Vec<u8> {
613 match warning {
625 match warning {
614 PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
626 PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
615 b"{}: ignoring invalid syntax '{}'\n",
627 b"{}: ignoring invalid syntax '{}'\n",
616 get_bytes_from_path(path),
628 get_bytes_from_path(path),
617 &*syntax
629 &*syntax
618 ),
630 ),
619 PatternFileWarning::NoSuchFile(path) => {
631 PatternFileWarning::NoSuchFile(path) => {
620 let path = if let Ok(relative) =
632 let path = if let Ok(relative) =
621 path.strip_prefix(repo.working_directory_path())
633 path.strip_prefix(repo.working_directory_path())
622 {
634 {
623 relative
635 relative
624 } else {
636 } else {
625 &*path
637 &*path
626 };
638 };
627 format_bytes!(
639 format_bytes!(
628 b"skipping unreadable pattern file '{}': \
640 b"skipping unreadable pattern file '{}': \
629 No such file or directory\n",
641 No such file or directory\n",
630 get_bytes_from_path(path),
642 get_bytes_from_path(path),
631 )
643 )
632 }
644 }
633 }
645 }
634 }
646 }
@@ -1,487 +1,485 b''
1 =====================================================================
1 =====================================================================
2 Check potential race conditions between a status and other operations
2 Check potential race conditions between a status and other operations
3 =====================================================================
3 =====================================================================
4
4
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
6
6
7 The `hg status` command can run without the wlock, however it might end up
7 The `hg status` command can run without the wlock, however it might end up
8 having to update the on-disk dirstate files, for example to mark ambiguous
8 having to update the on-disk dirstate files, for example to mark ambiguous
9 files as clean, or to update directory caches information with dirstate-v2.
9 files as clean, or to update directory caches information with dirstate-v2.
10
10
11
11
12 If another process updates the dirstate in the meantime we might run into
12 If another process updates the dirstate in the meantime we might run into
13 trouble. Especially, commands doing semantic changes like `hg add` or
13 trouble. Especially, commands doing semantic changes like `hg add` or
14 `hg commit` should not see their update erased by a concurrent status.
14 `hg commit` should not see their update erased by a concurrent status.
15
15
16 Unlike commands like `add` or `commit`, `status` only writes the dirstate
16 Unlike commands like `add` or `commit`, `status` only writes the dirstate
17 to update caches, no actual information is lost if we fail to write to disk.
17 to update caches, no actual information is lost if we fail to write to disk.
18
18
19
19
20 This test file is meant to test various cases where such parallel operations
20 This test file is meant to test various cases where such parallel operations
21 between a status with reasons to update the dirstate and another semantic
21 between a status with reasons to update the dirstate and another semantic
22 changes happen.
22 changes happen.
23
23
24
24
25 Setup
25 Setup
26 =====
26 =====
27
27
28 $ cat >> $HGRCPATH << EOF
28 $ cat >> $HGRCPATH << EOF
29 > [storage]
29 > [storage]
30 > dirstate-v2.slow-path=allow
30 > dirstate-v2.slow-path=allow
31 > EOF
31 > EOF
32
32
33 #if no-dirstate-v1
33 #if no-dirstate-v1
34 $ cat >> $HGRCPATH << EOF
34 $ cat >> $HGRCPATH << EOF
35 > [format]
35 > [format]
36 > use-dirstate-v2=yes
36 > use-dirstate-v2=yes
37 > EOF
37 > EOF
38 #else
38 #else
39 $ cat >> $HGRCPATH << EOF
39 $ cat >> $HGRCPATH << EOF
40 > [format]
40 > [format]
41 > use-dirstate-v2=no
41 > use-dirstate-v2=no
42 > EOF
42 > EOF
43 #endif
43 #endif
44
44
45 #if dirstate-v2-rewrite
45 #if dirstate-v2-rewrite
46 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
46 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
47 #endif
47 #endif
48 #if dirstate-v2-append
48 #if dirstate-v2-append
49 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
49 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
50 #endif
50 #endif
51
51
52 $ directories="dir dir/nested dir2"
52 $ directories="dir dir/nested dir2"
53 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
53 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
54 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
54 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
55 $ extra_files="dir/n dir/o p q"
55 $ extra_files="dir/n dir/o p q"
56
56
57 $ hg init reference-repo
57 $ hg init reference-repo
58 $ cd reference-repo
58 $ cd reference-repo
59 $ mkdir -p dir/nested dir2
59 $ mkdir -p dir/nested dir2
60 $ touch -t 200001010000 $first_files $directories
60 $ touch -t 200001010000 $first_files $directories
61 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
61 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
62 $ touch -t 200001010010 $second_files $directories
62 $ touch -t 200001010010 $second_files $directories
63 $ hg commit -Aqm "more files to have two commits"
63 $ hg commit -Aqm "more files to have two commits"
64 $ hg log -G -v
64 $ hg log -G -v
65 @ changeset: 1:c349430a1631
65 @ changeset: 1:c349430a1631
66 | tag: tip
66 | tag: tip
67 | user: test
67 | user: test
68 | date: Thu Jan 01 00:00:00 1970 +0000
68 | date: Thu Jan 01 00:00:00 1970 +0000
69 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
69 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
70 | description:
70 | description:
71 | more files to have two commits
71 | more files to have two commits
72 |
72 |
73 |
73 |
74 o changeset: 0:4f23db756b09
74 o changeset: 0:4f23db756b09
75 user: test
75 user: test
76 date: Thu Jan 01 00:00:00 1970 +0000
76 date: Thu Jan 01 00:00:00 1970 +0000
77 files: dir/b dir/c dir/d dir/nested/a dir2/e f
77 files: dir/b dir/c dir/d dir/nested/a dir2/e f
78 description:
78 description:
79 recreate a bunch of files to facilitate dirstate-v2 append
79 recreate a bunch of files to facilitate dirstate-v2 append
80
80
81
81
82 $ hg manifest
82 $ hg manifest
83 dir/b
83 dir/b
84 dir/c
84 dir/c
85 dir/d
85 dir/d
86 dir/i
86 dir/i
87 dir/j
87 dir/j
88 dir/nested/a
88 dir/nested/a
89 dir/nested/h
89 dir/nested/h
90 dir/nested/m
90 dir/nested/m
91 dir2/e
91 dir2/e
92 dir2/k
92 dir2/k
93 dir2/l
93 dir2/l
94 f
94 f
95 g
95 g
96
96
97 Add some unknown files and refresh the dirstate
97 Add some unknown files and refresh the dirstate
98
98
99 $ touch -t 200001010020 $extra_files
99 $ touch -t 200001010020 $extra_files
100 $ hg add dir/o
100 $ hg add dir/o
101 $ hg remove dir/nested/m
101 $ hg remove dir/nested/m
102
102
103 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
103 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
104 A dir/o
104 A dir/o
105 R dir/nested/m
105 R dir/nested/m
106 ? dir/n
106 ? dir/n
107 ? p
107 ? p
108 ? q
108 ? q
109 $ hg debugstate
109 $ hg debugstate
110 n 644 0 2000-01-01 00:00:00 dir/b
110 n 644 0 2000-01-01 00:00:00 dir/b
111 n 644 0 2000-01-01 00:00:00 dir/c
111 n 644 0 2000-01-01 00:00:00 dir/c
112 n 644 0 2000-01-01 00:00:00 dir/d
112 n 644 0 2000-01-01 00:00:00 dir/d
113 n 644 0 2000-01-01 00:10:00 dir/i
113 n 644 0 2000-01-01 00:10:00 dir/i
114 n 644 0 2000-01-01 00:10:00 dir/j
114 n 644 0 2000-01-01 00:10:00 dir/j
115 n 644 0 2000-01-01 00:00:00 dir/nested/a
115 n 644 0 2000-01-01 00:00:00 dir/nested/a
116 n 644 0 2000-01-01 00:10:00 dir/nested/h
116 n 644 0 2000-01-01 00:10:00 dir/nested/h
117 r ?????????????????????????????????? dir/nested/m (glob)
117 r ?????????????????????????????????? dir/nested/m (glob)
118 a ?????????????????????????????????? dir/o (glob)
118 a ?????????????????????????????????? dir/o (glob)
119 n 644 0 2000-01-01 00:00:00 dir2/e
119 n 644 0 2000-01-01 00:00:00 dir2/e
120 n 644 0 2000-01-01 00:10:00 dir2/k
120 n 644 0 2000-01-01 00:10:00 dir2/k
121 n 644 0 2000-01-01 00:10:00 dir2/l
121 n 644 0 2000-01-01 00:10:00 dir2/l
122 n 644 0 2000-01-01 00:00:00 f
122 n 644 0 2000-01-01 00:00:00 f
123 n 644 0 2000-01-01 00:10:00 g
123 n 644 0 2000-01-01 00:10:00 g
124 $ hg debugstate > ../reference
124 $ hg debugstate > ../reference
125 $ cd ..
125 $ cd ..
126
126
127 Explain / verify the test principles
127 Explain / verify the test principles
128 ------------------------------------
128 ------------------------------------
129
129
130 First, we can properly copy the reference
130 First, we can properly copy the reference
131
131
132 $ cp -a reference-repo sanity-check
132 $ cp -a reference-repo sanity-check
133 $ cd sanity-check
133 $ cd sanity-check
134 $ hg debugstate
134 $ hg debugstate
135 n 644 0 2000-01-01 00:00:00 dir/b
135 n 644 0 2000-01-01 00:00:00 dir/b
136 n 644 0 2000-01-01 00:00:00 dir/c
136 n 644 0 2000-01-01 00:00:00 dir/c
137 n 644 0 2000-01-01 00:00:00 dir/d
137 n 644 0 2000-01-01 00:00:00 dir/d
138 n 644 0 2000-01-01 00:10:00 dir/i
138 n 644 0 2000-01-01 00:10:00 dir/i
139 n 644 0 2000-01-01 00:10:00 dir/j
139 n 644 0 2000-01-01 00:10:00 dir/j
140 n 644 0 2000-01-01 00:00:00 dir/nested/a
140 n 644 0 2000-01-01 00:00:00 dir/nested/a
141 n 644 0 2000-01-01 00:10:00 dir/nested/h
141 n 644 0 2000-01-01 00:10:00 dir/nested/h
142 r ?????????????????????????????????? dir/nested/m (glob)
142 r ?????????????????????????????????? dir/nested/m (glob)
143 a ?????????????????????????????????? dir/o (glob)
143 a ?????????????????????????????????? dir/o (glob)
144 n 644 0 2000-01-01 00:00:00 dir2/e
144 n 644 0 2000-01-01 00:00:00 dir2/e
145 n 644 0 2000-01-01 00:10:00 dir2/k
145 n 644 0 2000-01-01 00:10:00 dir2/k
146 n 644 0 2000-01-01 00:10:00 dir2/l
146 n 644 0 2000-01-01 00:10:00 dir2/l
147 n 644 0 2000-01-01 00:00:00 f
147 n 644 0 2000-01-01 00:00:00 f
148 n 644 0 2000-01-01 00:10:00 g
148 n 644 0 2000-01-01 00:10:00 g
149 $ hg debugstate > ../post-copy
149 $ hg debugstate > ../post-copy
150 $ diff ../reference ../post-copy
150 $ diff ../reference ../post-copy
151
151
152 And status thinks the cache is in a proper state
152 And status thinks the cache is in a proper state
153
153
154 $ hg st
154 $ hg st
155 A dir/o
155 A dir/o
156 R dir/nested/m
156 R dir/nested/m
157 ? dir/n
157 ? dir/n
158 ? p
158 ? p
159 ? q
159 ? q
160 $ hg debugstate
160 $ hg debugstate
161 n 644 0 2000-01-01 00:00:00 dir/b
161 n 644 0 2000-01-01 00:00:00 dir/b
162 n 644 0 2000-01-01 00:00:00 dir/c
162 n 644 0 2000-01-01 00:00:00 dir/c
163 n 644 0 2000-01-01 00:00:00 dir/d
163 n 644 0 2000-01-01 00:00:00 dir/d
164 n 644 0 2000-01-01 00:10:00 dir/i
164 n 644 0 2000-01-01 00:10:00 dir/i
165 n 644 0 2000-01-01 00:10:00 dir/j
165 n 644 0 2000-01-01 00:10:00 dir/j
166 n 644 0 2000-01-01 00:00:00 dir/nested/a
166 n 644 0 2000-01-01 00:00:00 dir/nested/a
167 n 644 0 2000-01-01 00:10:00 dir/nested/h
167 n 644 0 2000-01-01 00:10:00 dir/nested/h
168 r ?????????????????????????????????? dir/nested/m (glob)
168 r ?????????????????????????????????? dir/nested/m (glob)
169 a ?????????????????????????????????? dir/o (glob)
169 a ?????????????????????????????????? dir/o (glob)
170 n 644 0 2000-01-01 00:00:00 dir2/e
170 n 644 0 2000-01-01 00:00:00 dir2/e
171 n 644 0 2000-01-01 00:10:00 dir2/k
171 n 644 0 2000-01-01 00:10:00 dir2/k
172 n 644 0 2000-01-01 00:10:00 dir2/l
172 n 644 0 2000-01-01 00:10:00 dir2/l
173 n 644 0 2000-01-01 00:00:00 f
173 n 644 0 2000-01-01 00:00:00 f
174 n 644 0 2000-01-01 00:10:00 g
174 n 644 0 2000-01-01 00:10:00 g
175 $ hg debugstate > ../post-status
175 $ hg debugstate > ../post-status
176 $ diff ../reference ../post-status
176 $ diff ../reference ../post-status
177
177
178 Then we can start a status that:
178 Then we can start a status that:
179 - has some update to do (the touch call)
179 - has some update to do (the touch call)
180 - will wait AFTER running status, but before updating the cache on disk
180 - will wait AFTER running status, but before updating the cache on disk
181
181
182 $ touch -t 200001010001 dir/c
182 $ touch -t 200001010001 dir/c
183 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
183 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
184 > --config rhg.on-unsupported=abort \
184 > --config rhg.on-unsupported=abort \
185 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
185 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
186 > &
186 > &
187 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
187 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
188
188
189 We check it runs the status first by modifying a file and updating another timestamp
189 We check it runs the status first by modifying a file and updating another timestamp
190
190
191 $ touch -t 200001010003 dir/i
191 $ touch -t 200001010003 dir/i
192 $ echo babar > dir/j
192 $ echo babar > dir/j
193 $ touch $TESTTMP/status-race-lock
193 $ touch $TESTTMP/status-race-lock
194 $ wait
194 $ wait
195
195
196 The test process should have reported a status before the change we made,
196 The test process should have reported a status before the change we made,
197 and should have missed the timestamp update
197 and should have missed the timestamp update
198
198
199 $ cat $TESTTMP/status-race-lock.out
199 $ cat $TESTTMP/status-race-lock.out
200 A dir/o
200 A dir/o
201 R dir/nested/m
201 R dir/nested/m
202 ? dir/n
202 ? dir/n
203 ? p
203 ? p
204 ? q
204 ? q
205 $ cat $TESTTMP/status-race-lock.log
205 $ cat $TESTTMP/status-race-lock.log
206 $ hg debugstate | grep dir/c
206 $ hg debugstate | grep dir/c
207 n 644 0 2000-01-01 00:01:00 dir/c
207 n 644 0 2000-01-01 00:01:00 dir/c
208 $ hg debugstate | grep dir/i
208 $ hg debugstate | grep dir/i
209 n 644 0 2000-01-01 00:10:00 dir/i
209 n 644 0 2000-01-01 00:10:00 dir/i
210 $ hg debugstate | grep dir/j
210 $ hg debugstate | grep dir/j
211 n 644 0 2000-01-01 00:10:00 dir/j
211 n 644 0 2000-01-01 00:10:00 dir/j
212
212
213 final cleanup
213 final cleanup
214
214
215 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
215 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
216 $ cd ..
216 $ cd ..
217
217
218 Actual Testing
218 Actual Testing
219 ==============
219 ==============
220
220
221 Race with a `hg add`
221 Race with a `hg add`
222 -------------------
222 -------------------
223
223
224 $ cp -a reference-repo race-with-add
224 $ cp -a reference-repo race-with-add
225 $ cd race-with-add
225 $ cd race-with-add
226
226
227 spin a `hg status` with some caches to update
227 spin a `hg status` with some caches to update
228
228
229 $ touch -t 200001020001 f
229 $ touch -t 200001020001 f
230 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
230 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
231 > --config rhg.on-unsupported=abort \
231 > --config rhg.on-unsupported=abort \
232 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
232 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
233 > &
233 > &
234 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
234 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
235
235
236 Add a file
236 Add a file
237
237
238 $ hg $d2args add dir/n
238 $ hg $d2args add dir/n
239 $ touch $TESTTMP/status-race-lock
239 $ touch $TESTTMP/status-race-lock
240 $ wait
240 $ wait
241
241
242 The file should in a "added" state
242 The file should in a "added" state
243
243
244 $ hg status
244 $ hg status
245 A dir/n (no-rhg !)
245 A dir/n (no-rhg !)
246 A dir/n (rhg dirstate-v2-rewrite !)
246 A dir/n (rhg dirstate-v2-rewrite !)
247 A dir/n (missing-correct-output rhg dirstate-v1 !)
247 A dir/n (missing-correct-output rhg dirstate-v1 !)
248 A dir/o
248 A dir/o
249 R dir/nested/m
249 R dir/nested/m
250 ? dir/n (known-bad-output rhg no-dirstate-v2-rewrite !)
250 ? dir/n (known-bad-output rhg no-dirstate-v2-rewrite !)
251 ? p
251 ? p
252 ? q
252 ? q
253
253
254 The status process should return a consistent result and not crash.
254 The status process should return a consistent result and not crash.
255
255
256 $ cat $TESTTMP/status-race-lock.out
256 $ cat $TESTTMP/status-race-lock.out
257 A dir/o
257 A dir/o
258 R dir/nested/m
258 R dir/nested/m
259 ? dir/n
259 ? dir/n
260 ? p
260 ? p
261 ? q
261 ? q
262 $ cat $TESTTMP/status-race-lock.log
262 $ cat $TESTTMP/status-race-lock.log
263 abort: when writing $TESTTMP/race-with-add/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
263 abort: when writing $TESTTMP/race-with-add/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
264
264
265 final cleanup
265 final cleanup
266
266
267 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
267 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
268 $ cd ..
268 $ cd ..
269
269
270 Race with a `hg commit`
270 Race with a `hg commit`
271 ----------------------
271 ----------------------
272
272
273 $ cp -a reference-repo race-with-commit
273 $ cp -a reference-repo race-with-commit
274 $ cd race-with-commit
274 $ cd race-with-commit
275
275
276 spin a `hg status` with some caches to update
276 spin a `hg status` with some caches to update
277
277
278 $ touch -t 200001020001 dir/j
278 $ touch -t 200001020001 dir/j
279 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
279 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
280 > --config rhg.on-unsupported=abort \
280 > --config rhg.on-unsupported=abort \
281 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
281 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
282 > &
282 > &
283 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
283 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
284
284
285 Add a file and force the data file rewrite
285 Add a file and force the data file rewrite
286
286
287 $ hg $d2args commit -m created-during-status dir/o
287 $ hg $d2args commit -m created-during-status dir/o
288 $ touch $TESTTMP/status-race-lock
288 $ touch $TESTTMP/status-race-lock
289 $ wait
289 $ wait
290
290
291 The parent must change and the status should be clean
291 The parent must change and the status should be clean
292
292
293 # XXX rhg misbehaves here
293 # XXX rhg misbehaves here
294 #if no-rhg
294 #if no-rhg
295 $ hg summary
295 $ hg summary
296 parent: 2:2e3b442a2fd4 tip
296 parent: 2:2e3b442a2fd4 tip
297 created-during-status
297 created-during-status
298 branch: default
298 branch: default
299 commit: 1 removed, 3 unknown
299 commit: 1 removed, 3 unknown
300 update: (current)
300 update: (current)
301 phases: 3 draft
301 phases: 3 draft
302 $ hg status
302 $ hg status
303 R dir/nested/m
303 R dir/nested/m
304 ? dir/n
304 ? dir/n
305 ? p
305 ? p
306 ? q
306 ? q
307 #else
307 #else
308 $ hg summary
308 $ hg summary
309 parent: 1:c349430a1631
309 parent: 1:c349430a1631
310 more files to have two commits
310 more files to have two commits
311 branch: default
311 branch: default
312 commit: 1 added, 1 removed, 3 unknown (new branch head)
312 commit: 1 added, 1 removed, 3 unknown (new branch head)
313 update: 1 new changesets (update)
313 update: 1 new changesets (update)
314 phases: 3 draft
314 phases: 3 draft
315 $ hg status
315 $ hg status
316 A dir/o
316 A dir/o
317 R dir/nested/m
317 R dir/nested/m
318 ? dir/n
318 ? dir/n
319 ? p
319 ? p
320 ? q
320 ? q
321 #endif
321 #endif
322
322
323 The status process should return a consistent result and not crash.
323 The status process should return a consistent result and not crash.
324
324
325 $ cat $TESTTMP/status-race-lock.out
325 $ cat $TESTTMP/status-race-lock.out
326 A dir/o
326 A dir/o
327 R dir/nested/m
327 R dir/nested/m
328 ? dir/n
328 ? dir/n
329 ? p
329 ? p
330 ? q
330 ? q
331 $ cat $TESTTMP/status-race-lock.log
331 $ cat $TESTTMP/status-race-lock.log
332 abort: when removing $TESTTMP/race-with-commit/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
332 abort: when removing $TESTTMP/race-with-commit/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
333
333
334 final cleanup
334 final cleanup
335
335
336 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
336 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
337 $ cd ..
337 $ cd ..
338
338
339 Race with a `hg update`
339 Race with a `hg update`
340 ----------------------
340 ----------------------
341
341
342 $ cp -a reference-repo race-with-update
342 $ cp -a reference-repo race-with-update
343 $ cd race-with-update
343 $ cd race-with-update
344
344
345 spin a `hg status` with some caches to update
345 spin a `hg status` with some caches to update
346
346
347 $ touch -t 200001020001 dir2/k
347 $ touch -t 200001020001 dir2/k
348 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
348 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
349 > --config rhg.on-unsupported=abort \
349 > --config rhg.on-unsupported=abort \
350 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
350 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
351 > &
351 > &
352 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
352 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
353
353
354 Add a file and force the data file rewrite
354 Add a file and force the data file rewrite
355
355
356 $ hg $d2args update ".~1"
356 $ hg $d2args update ".~1"
357 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
357 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
358 $ touch $TESTTMP/status-race-lock
358 $ touch $TESTTMP/status-race-lock
359 $ wait
359 $ wait
360
360
361 The parent must change and the status should be clean
361 The parent must change and the status should be clean
362
362
363 $ hg summary
363 $ hg summary
364 parent: 0:4f23db756b09
364 parent: 0:4f23db756b09
365 recreate a bunch of files to facilitate dirstate-v2 append
365 recreate a bunch of files to facilitate dirstate-v2 append
366 branch: default
366 branch: default
367 commit: 1 added, 3 unknown (new branch head)
367 commit: 1 added, 3 unknown (new branch head)
368 update: 1 new changesets (update)
368 update: 1 new changesets (update)
369 phases: 2 draft
369 phases: 2 draft
370 $ hg status
370 $ hg status
371 A dir/o
371 A dir/o
372 ? dir/n
372 ? dir/n
373 ? p
373 ? p
374 ? q
374 ? q
375
375
376 The status process should return a consistent result and not crash.
376 The status process should return a consistent result and not crash.
377
377
378 $ cat $TESTTMP/status-race-lock.out
378 $ cat $TESTTMP/status-race-lock.out
379 A dir/o
379 A dir/o
380 R dir/nested/m
380 R dir/nested/m
381 ? dir/n
381 ? dir/n
382 ? p
382 ? p
383 ? q
383 ? q
384 $ cat $TESTTMP/status-race-lock.log
384 $ cat $TESTTMP/status-race-lock.log
385 abort: when reading $TESTTMP/race-with-update/dir2/k: $ENOENT$ (known-bad-output rhg !)
386
385
387 final cleanup
386 final cleanup
388
387
389 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
388 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
390 $ cd ..
389 $ cd ..
391
390
392 Race with another status
391 Race with another status
393 ------------------------
392 ------------------------
394
393
395 $ cp -a reference-repo race-with-status
394 $ cp -a reference-repo race-with-status
396 $ cd race-with-status
395 $ cd race-with-status
397
396
398 spin a `hg status` with some caches to update
397 spin a `hg status` with some caches to update
399
398
400 $ touch -t 200001010030 dir/nested/h
399 $ touch -t 200001010030 dir/nested/h
401 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
400 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
402 > --config rhg.on-unsupported=abort \
401 > --config rhg.on-unsupported=abort \
403 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
402 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
404 > &
403 > &
405 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
404 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
406
405
407 touch g
406 touch g
408
407
409 $ touch -t 200001010025 g
408 $ touch -t 200001010025 g
410 $ hg $d2args status
409 $ hg $d2args status
411 A dir/o
410 A dir/o
412 R dir/nested/m
411 R dir/nested/m
413 ? dir/n
412 ? dir/n
414 ? p
413 ? p
415 ? q
414 ? q
416 $ touch $TESTTMP/status-race-lock
415 $ touch $TESTTMP/status-race-lock
417 $ wait
416 $ wait
418
417
419 the first update should be on disk
418 the first update should be on disk
420
419
421 $ hg debugstate --all | grep "g"
420 $ hg debugstate --all | grep "g"
422 n 644 0 2000-01-01 00:25:00 g (no-rhg !)
421 n 644 0 2000-01-01 00:25:00 g (no-rhg !)
423 n 644 0 2000-01-01 00:25:00 g (missing-correct-output rhg !)
422 n 644 0 2000-01-01 00:25:00 g (missing-correct-output rhg !)
424 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg !)
423 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg !)
425
424
426 The status process should return a consistent result and not crash.
425 The status process should return a consistent result and not crash.
427
426
428 $ cat $TESTTMP/status-race-lock.out
427 $ cat $TESTTMP/status-race-lock.out
429 A dir/o
428 A dir/o
430 R dir/nested/m
429 R dir/nested/m
431 ? dir/n
430 ? dir/n
432 ? p
431 ? p
433 ? q
432 ? q
434 $ cat $TESTTMP/status-race-lock.log
433 $ cat $TESTTMP/status-race-lock.log
435 abort: when removing $TESTTMP/race-with-status/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
434 abort: when removing $TESTTMP/race-with-status/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
436
435
437 final cleanup
436 final cleanup
438
437
439 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
438 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
440 $ cd ..
439 $ cd ..
441
440
442 Race with the removal of an ambiguous file
441 Race with the removal of an ambiguous file
443 ----------------------è-------------------
442 ----------------------è-------------------
444
443
445 $ cp -a reference-repo race-with-remove
444 $ cp -a reference-repo race-with-remove
446 $ cd race-with-remove
445 $ cd race-with-remove
447
446
448 spin a `hg status` with some caches to update
447 spin a `hg status` with some caches to update
449
448
450 $ touch -t 200001010035 dir2/l
449 $ touch -t 200001010035 dir2/l
451 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
450 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
452 > --config rhg.on-unsupported=abort \
451 > --config rhg.on-unsupported=abort \
453 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
452 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
454 > &
453 > &
455 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
454 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
456
455
457 remove that same file
456 remove that same file
458
457
459 $ hg $d2args remove dir2/l
458 $ hg $d2args remove dir2/l
460 $ touch $TESTTMP/status-race-lock
459 $ touch $TESTTMP/status-race-lock
461 $ wait
460 $ wait
462
461
463 file should be marked as removed
462 file should be marked as removed
464
463
465 $ hg status
464 $ hg status
466 A dir/o
465 A dir/o
467 R dir/nested/m
466 R dir/nested/m
468 R dir2/l
467 R dir2/l
469 ? dir/n
468 ? dir/n
470 ? p
469 ? p
471 ? q
470 ? q
472
471
473 The status process should return a consistent result and not crash.
472 The status process should return a consistent result and not crash.
474
473
475 $ cat $TESTTMP/status-race-lock.out
474 $ cat $TESTTMP/status-race-lock.out
476 A dir/o
475 A dir/o
477 R dir/nested/m
476 R dir/nested/m
478 ? dir/n
477 ? dir/n
479 ? p
478 ? p
480 ? q
479 ? q
481 $ cat $TESTTMP/status-race-lock.log
480 $ cat $TESTTMP/status-race-lock.log
482 abort: when reading $TESTTMP/race-with-remove/dir2/l: $ENOENT$ (known-bad-output rhg !)
483
481
484 final cleanup
482 final cleanup
485
483
486 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
484 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
487 $ cd ..
485 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now