##// END OF EJS Templates
rust-dirstate-v2: don't write dirstate if data file has changed...
Raphaël Gomès -
r51139:07d030b3 stable
parent child Browse files
Show More
@@ -1,689 +1,700 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
8 use crate::errors::{HgError, IoResultExt};
9 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
13 use crate::utils::debug::debug_wait_for_file_or_print;
13 use crate::utils::debug::debug_wait_for_file_or_print;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
16 use crate::utils::SliceExt;
16 use crate::utils::SliceExt;
17 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::vfs::{is_dir, is_file, Vfs};
18 use crate::{requirements, NodePrefix};
18 use crate::{requirements, NodePrefix};
19 use crate::{DirstateError, Revision};
19 use crate::{DirstateError, Revision};
20 use std::cell::{Ref, RefCell, RefMut};
20 use std::cell::{Ref, RefCell, RefMut};
21 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::io::Seek;
22 use std::io::Seek;
23 use std::io::SeekFrom;
23 use std::io::SeekFrom;
24 use std::io::Write as IoWrite;
24 use std::io::Write as IoWrite;
25 use std::path::{Path, PathBuf};
25 use std::path::{Path, PathBuf};
26
26
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28
28
29 /// A repository on disk
29 /// A repository on disk
30 pub struct Repo {
30 pub struct Repo {
31 working_directory: PathBuf,
31 working_directory: PathBuf,
32 dot_hg: PathBuf,
32 dot_hg: PathBuf,
33 store: PathBuf,
33 store: PathBuf,
34 requirements: HashSet<String>,
34 requirements: HashSet<String>,
35 config: Config,
35 config: Config,
36 dirstate_parents: LazyCell<DirstateParents>,
36 dirstate_parents: LazyCell<DirstateParents>,
37 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
38 dirstate_map: LazyCell<OwningDirstateMap>,
37 dirstate_map: LazyCell<OwningDirstateMap>,
39 changelog: LazyCell<Changelog>,
38 changelog: LazyCell<Changelog>,
40 manifestlog: LazyCell<Manifestlog>,
39 manifestlog: LazyCell<Manifestlog>,
41 }
40 }
42
41
43 #[derive(Debug, derive_more::From)]
42 #[derive(Debug, derive_more::From)]
44 pub enum RepoError {
43 pub enum RepoError {
45 NotFound {
44 NotFound {
46 at: PathBuf,
45 at: PathBuf,
47 },
46 },
48 #[from]
47 #[from]
49 ConfigParseError(ConfigParseError),
48 ConfigParseError(ConfigParseError),
50 #[from]
49 #[from]
51 Other(HgError),
50 Other(HgError),
52 }
51 }
53
52
54 impl From<ConfigError> for RepoError {
53 impl From<ConfigError> for RepoError {
55 fn from(error: ConfigError) -> Self {
54 fn from(error: ConfigError) -> Self {
56 match error {
55 match error {
57 ConfigError::Parse(error) => error.into(),
56 ConfigError::Parse(error) => error.into(),
58 ConfigError::Other(error) => error.into(),
57 ConfigError::Other(error) => error.into(),
59 }
58 }
60 }
59 }
61 }
60 }
62
61
63 impl Repo {
62 impl Repo {
64 /// tries to find nearest repository root in current working directory or
63 /// tries to find nearest repository root in current working directory or
65 /// its ancestors
64 /// its ancestors
66 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
65 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
67 let current_directory = crate::utils::current_dir()?;
66 let current_directory = crate::utils::current_dir()?;
68 // ancestors() is inclusive: it first yields `current_directory`
67 // ancestors() is inclusive: it first yields `current_directory`
69 // as-is.
68 // as-is.
70 for ancestor in current_directory.ancestors() {
69 for ancestor in current_directory.ancestors() {
71 if is_dir(ancestor.join(".hg"))? {
70 if is_dir(ancestor.join(".hg"))? {
72 return Ok(ancestor.to_path_buf());
71 return Ok(ancestor.to_path_buf());
73 }
72 }
74 }
73 }
75 return Err(RepoError::NotFound {
74 return Err(RepoError::NotFound {
76 at: current_directory,
75 at: current_directory,
77 });
76 });
78 }
77 }
79
78
80 /// Find a repository, either at the given path (which must contain a `.hg`
79 /// Find a repository, either at the given path (which must contain a `.hg`
81 /// sub-directory) or by searching the current directory and its
80 /// sub-directory) or by searching the current directory and its
82 /// ancestors.
81 /// ancestors.
83 ///
82 ///
84 /// A method with two very different "modes" like this usually a code smell
83 /// A method with two very different "modes" like this usually a code smell
85 /// to make two methods instead, but in this case an `Option` is what rhg
84 /// to make two methods instead, but in this case an `Option` is what rhg
86 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
85 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
87 /// Having two methods would just move that `if` to almost all callers.
86 /// Having two methods would just move that `if` to almost all callers.
88 pub fn find(
87 pub fn find(
89 config: &Config,
88 config: &Config,
90 explicit_path: Option<PathBuf>,
89 explicit_path: Option<PathBuf>,
91 ) -> Result<Self, RepoError> {
90 ) -> Result<Self, RepoError> {
92 if let Some(root) = explicit_path {
91 if let Some(root) = explicit_path {
93 if is_dir(root.join(".hg"))? {
92 if is_dir(root.join(".hg"))? {
94 Self::new_at_path(root.to_owned(), config)
93 Self::new_at_path(root.to_owned(), config)
95 } else if is_file(&root)? {
94 } else if is_file(&root)? {
96 Err(HgError::unsupported("bundle repository").into())
95 Err(HgError::unsupported("bundle repository").into())
97 } else {
96 } else {
98 Err(RepoError::NotFound {
97 Err(RepoError::NotFound {
99 at: root.to_owned(),
98 at: root.to_owned(),
100 })
99 })
101 }
100 }
102 } else {
101 } else {
103 let root = Self::find_repo_root()?;
102 let root = Self::find_repo_root()?;
104 Self::new_at_path(root, config)
103 Self::new_at_path(root, config)
105 }
104 }
106 }
105 }
107
106
108 /// To be called after checking that `.hg` is a sub-directory
107 /// To be called after checking that `.hg` is a sub-directory
109 fn new_at_path(
108 fn new_at_path(
110 working_directory: PathBuf,
109 working_directory: PathBuf,
111 config: &Config,
110 config: &Config,
112 ) -> Result<Self, RepoError> {
111 ) -> Result<Self, RepoError> {
113 let dot_hg = working_directory.join(".hg");
112 let dot_hg = working_directory.join(".hg");
114
113
115 let mut repo_config_files = Vec::new();
114 let mut repo_config_files = Vec::new();
116 repo_config_files.push(dot_hg.join("hgrc"));
115 repo_config_files.push(dot_hg.join("hgrc"));
117 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
116 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
118
117
119 let hg_vfs = Vfs { base: &dot_hg };
118 let hg_vfs = Vfs { base: &dot_hg };
120 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 let mut reqs = requirements::load_if_exists(hg_vfs)?;
121 let relative =
120 let relative =
122 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
123 let shared =
122 let shared =
124 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
125
124
126 // From `mercurial/localrepo.py`:
125 // From `mercurial/localrepo.py`:
127 //
126 //
128 // if .hg/requires contains the sharesafe requirement, it means
127 // if .hg/requires contains the sharesafe requirement, it means
129 // there exists a `.hg/store/requires` too and we should read it
128 // there exists a `.hg/store/requires` too and we should read it
130 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
131 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
132 // is not present, refer checkrequirementscompat() for that
131 // is not present, refer checkrequirementscompat() for that
133 //
132 //
134 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
135 // repository was shared the old way. We check the share source
134 // repository was shared the old way. We check the share source
136 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
137 // current repository needs to be reshared
136 // current repository needs to be reshared
138 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
139
138
140 let store_path;
139 let store_path;
141 if !shared {
140 if !shared {
142 store_path = dot_hg.join("store");
141 store_path = dot_hg.join("store");
143 } else {
142 } else {
144 let bytes = hg_vfs.read("sharedpath")?;
143 let bytes = hg_vfs.read("sharedpath")?;
145 let mut shared_path =
144 let mut shared_path =
146 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
147 .to_owned();
146 .to_owned();
148 if relative {
147 if relative {
149 shared_path = dot_hg.join(shared_path)
148 shared_path = dot_hg.join(shared_path)
150 }
149 }
151 if !is_dir(&shared_path)? {
150 if !is_dir(&shared_path)? {
152 return Err(HgError::corrupted(format!(
151 return Err(HgError::corrupted(format!(
153 ".hg/sharedpath points to nonexistent directory {}",
152 ".hg/sharedpath points to nonexistent directory {}",
154 shared_path.display()
153 shared_path.display()
155 ))
154 ))
156 .into());
155 .into());
157 }
156 }
158
157
159 store_path = shared_path.join("store");
158 store_path = shared_path.join("store");
160
159
161 let source_is_share_safe =
160 let source_is_share_safe =
162 requirements::load(Vfs { base: &shared_path })?
161 requirements::load(Vfs { base: &shared_path })?
163 .contains(requirements::SHARESAFE_REQUIREMENT);
162 .contains(requirements::SHARESAFE_REQUIREMENT);
164
163
165 if share_safe != source_is_share_safe {
164 if share_safe != source_is_share_safe {
166 return Err(HgError::unsupported("share-safe mismatch").into());
165 return Err(HgError::unsupported("share-safe mismatch").into());
167 }
166 }
168
167
169 if share_safe {
168 if share_safe {
170 repo_config_files.insert(0, shared_path.join("hgrc"))
169 repo_config_files.insert(0, shared_path.join("hgrc"))
171 }
170 }
172 }
171 }
173 if share_safe {
172 if share_safe {
174 reqs.extend(requirements::load(Vfs { base: &store_path })?);
173 reqs.extend(requirements::load(Vfs { base: &store_path })?);
175 }
174 }
176
175
177 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
176 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
178 config.combine_with_repo(&repo_config_files)?
177 config.combine_with_repo(&repo_config_files)?
179 } else {
178 } else {
180 config.clone()
179 config.clone()
181 };
180 };
182
181
183 let repo = Self {
182 let repo = Self {
184 requirements: reqs,
183 requirements: reqs,
185 working_directory,
184 working_directory,
186 store: store_path,
185 store: store_path,
187 dot_hg,
186 dot_hg,
188 config: repo_config,
187 config: repo_config,
189 dirstate_parents: LazyCell::new(),
188 dirstate_parents: LazyCell::new(),
190 dirstate_data_file_uuid: LazyCell::new(),
191 dirstate_map: LazyCell::new(),
189 dirstate_map: LazyCell::new(),
192 changelog: LazyCell::new(),
190 changelog: LazyCell::new(),
193 manifestlog: LazyCell::new(),
191 manifestlog: LazyCell::new(),
194 };
192 };
195
193
196 requirements::check(&repo)?;
194 requirements::check(&repo)?;
197
195
198 Ok(repo)
196 Ok(repo)
199 }
197 }
200
198
201 pub fn working_directory_path(&self) -> &Path {
199 pub fn working_directory_path(&self) -> &Path {
202 &self.working_directory
200 &self.working_directory
203 }
201 }
204
202
205 pub fn requirements(&self) -> &HashSet<String> {
203 pub fn requirements(&self) -> &HashSet<String> {
206 &self.requirements
204 &self.requirements
207 }
205 }
208
206
209 pub fn config(&self) -> &Config {
207 pub fn config(&self) -> &Config {
210 &self.config
208 &self.config
211 }
209 }
212
210
213 /// For accessing repository files (in `.hg`), except for the store
211 /// For accessing repository files (in `.hg`), except for the store
214 /// (`.hg/store`).
212 /// (`.hg/store`).
215 pub fn hg_vfs(&self) -> Vfs<'_> {
213 pub fn hg_vfs(&self) -> Vfs<'_> {
216 Vfs { base: &self.dot_hg }
214 Vfs { base: &self.dot_hg }
217 }
215 }
218
216
219 /// For accessing repository store files (in `.hg/store`)
217 /// For accessing repository store files (in `.hg/store`)
220 pub fn store_vfs(&self) -> Vfs<'_> {
218 pub fn store_vfs(&self) -> Vfs<'_> {
221 Vfs { base: &self.store }
219 Vfs { base: &self.store }
222 }
220 }
223
221
224 /// For accessing the working copy
222 /// For accessing the working copy
225 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
226 Vfs {
224 Vfs {
227 base: &self.working_directory,
225 base: &self.working_directory,
228 }
226 }
229 }
227 }
230
228
231 pub fn try_with_wlock_no_wait<R>(
229 pub fn try_with_wlock_no_wait<R>(
232 &self,
230 &self,
233 f: impl FnOnce() -> R,
231 f: impl FnOnce() -> R,
234 ) -> Result<R, LockError> {
232 ) -> Result<R, LockError> {
235 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
236 }
234 }
237
235
238 pub fn has_dirstate_v2(&self) -> bool {
236 pub fn has_dirstate_v2(&self) -> bool {
239 self.requirements
237 self.requirements
240 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
241 }
239 }
242
240
243 pub fn has_sparse(&self) -> bool {
241 pub fn has_sparse(&self) -> bool {
244 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
245 }
243 }
246
244
247 pub fn has_narrow(&self) -> bool {
245 pub fn has_narrow(&self) -> bool {
248 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
249 }
247 }
250
248
251 pub fn has_nodemap(&self) -> bool {
249 pub fn has_nodemap(&self) -> bool {
252 self.requirements
250 self.requirements
253 .contains(requirements::NODEMAP_REQUIREMENT)
251 .contains(requirements::NODEMAP_REQUIREMENT)
254 }
252 }
255
253
256 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
257 Ok(self
255 Ok(self
258 .hg_vfs()
256 .hg_vfs()
259 .read("dirstate")
257 .read("dirstate")
260 .io_not_found_as_none()?
258 .io_not_found_as_none()?
261 .unwrap_or(Vec::new()))
259 .unwrap_or(Vec::new()))
262 }
260 }
263
261
264 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
265 Ok(*self
263 Ok(*self
266 .dirstate_parents
264 .dirstate_parents
267 .get_or_init(|| self.read_dirstate_parents())?)
265 .get_or_init(|| self.read_dirstate_parents())?)
268 }
266 }
269
267
270 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
268 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
271 let dirstate = self.dirstate_file_contents()?;
269 let dirstate = self.dirstate_file_contents()?;
272 let parents = if dirstate.is_empty() {
270 let parents = if dirstate.is_empty() {
273 if self.has_dirstate_v2() {
274 self.dirstate_data_file_uuid.set(None);
275 }
276 DirstateParents::NULL
271 DirstateParents::NULL
277 } else if self.has_dirstate_v2() {
272 } else if self.has_dirstate_v2() {
278 let docket =
273 let docket =
279 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
274 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
280 self.dirstate_data_file_uuid
281 .set(Some(docket.uuid.to_owned()));
282 docket.parents()
275 docket.parents()
283 } else {
276 } else {
284 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
277 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
285 .clone()
278 .clone()
286 };
279 };
287 self.dirstate_parents.set(parents);
280 self.dirstate_parents.set(parents);
288 Ok(parents)
281 Ok(parents)
289 }
282 }
290
283
291 fn read_dirstate_data_file_uuid(
284 /// Returns the information read from the dirstate docket necessary to
285 /// check if the data file has been updated/deleted by another process
286 /// since we last read the dirstate.
287 /// Namely, the data file uuid and the data size.
288 fn get_dirstate_data_file_integrity(
292 &self,
289 &self,
293 ) -> Result<Option<Vec<u8>>, HgError> {
290 ) -> Result<(Option<Vec<u8>>, usize), HgError> {
294 assert!(
291 assert!(
295 self.has_dirstate_v2(),
292 self.has_dirstate_v2(),
296 "accessing dirstate data file ID without dirstate-v2"
293 "accessing dirstate data file ID without dirstate-v2"
297 );
294 );
298 let dirstate = self.dirstate_file_contents()?;
295 let dirstate = self.dirstate_file_contents()?;
299 if dirstate.is_empty() {
296 if dirstate.is_empty() {
300 self.dirstate_parents.set(DirstateParents::NULL);
297 self.dirstate_parents.set(DirstateParents::NULL);
301 Ok(None)
298 Ok((None, 0))
302 } else {
299 } else {
303 let docket =
300 let docket =
304 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
301 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
305 self.dirstate_parents.set(docket.parents());
302 self.dirstate_parents.set(docket.parents());
306 Ok(Some(docket.uuid.to_owned()))
303 Ok((Some(docket.uuid.to_owned()), docket.data_size()))
307 }
304 }
308 }
305 }
309
306
310 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
307 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
311 if self.has_dirstate_v2() {
308 if self.has_dirstate_v2() {
312 // The v2 dirstate is split into a docket and a data file.
309 // The v2 dirstate is split into a docket and a data file.
313 // Since we don't always take the `wlock` to read it
310 // Since we don't always take the `wlock` to read it
314 // (like in `hg status`), it is susceptible to races.
311 // (like in `hg status`), it is susceptible to races.
315 // A simple retry method should be enough since full rewrites
312 // A simple retry method should be enough since full rewrites
316 // only happen when too much garbage data is present and
313 // only happen when too much garbage data is present and
317 // this race is unlikely.
314 // this race is unlikely.
318 let mut tries = 0;
315 let mut tries = 0;
319
316
320 while tries < V2_MAX_READ_ATTEMPTS {
317 while tries < V2_MAX_READ_ATTEMPTS {
321 tries += 1;
318 tries += 1;
322 match self.read_docket_and_data_file() {
319 match self.read_docket_and_data_file() {
323 Ok(m) => {
320 Ok(m) => {
324 return Ok(m);
321 return Ok(m);
325 }
322 }
326 Err(e) => match e {
323 Err(e) => match e {
327 DirstateError::Common(HgError::RaceDetected(
324 DirstateError::Common(HgError::RaceDetected(
328 context,
325 context,
329 )) => {
326 )) => {
330 log::info!(
327 log::info!(
331 "dirstate read race detected {} (retry {}/{})",
328 "dirstate read race detected {} (retry {}/{})",
332 context,
329 context,
333 tries,
330 tries,
334 V2_MAX_READ_ATTEMPTS,
331 V2_MAX_READ_ATTEMPTS,
335 );
332 );
336 continue;
333 continue;
337 }
334 }
338 _ => return Err(e.into()),
335 _ => return Err(e.into()),
339 },
336 },
340 }
337 }
341 }
338 }
342 let error = HgError::abort(
339 let error = HgError::abort(
343 format!("dirstate read race happened {tries} times in a row"),
340 format!("dirstate read race happened {tries} times in a row"),
344 255,
341 255,
345 None,
342 None,
346 );
343 );
347 return Err(DirstateError::Common(error));
344 return Err(DirstateError::Common(error));
348 } else {
345 } else {
349 debug_wait_for_file_or_print(
346 debug_wait_for_file_or_print(
350 self.config(),
347 self.config(),
351 "dirstate.pre-read-file",
348 "dirstate.pre-read-file",
352 );
349 );
353 let dirstate_file_contents = self.dirstate_file_contents()?;
350 let dirstate_file_contents = self.dirstate_file_contents()?;
354 return if dirstate_file_contents.is_empty() {
351 return if dirstate_file_contents.is_empty() {
355 self.dirstate_parents.set(DirstateParents::NULL);
352 self.dirstate_parents.set(DirstateParents::NULL);
356 Ok(OwningDirstateMap::new_empty(Vec::new()))
353 Ok(OwningDirstateMap::new_empty(Vec::new()))
357 } else {
354 } else {
358 let (map, parents) =
355 let (map, parents) =
359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
356 OwningDirstateMap::new_v1(dirstate_file_contents)?;
360 self.dirstate_parents.set(parents);
357 self.dirstate_parents.set(parents);
361 Ok(map)
358 Ok(map)
362 };
359 };
363 }
360 }
364 }
361 }
365
362
366 fn read_docket_and_data_file(
363 fn read_docket_and_data_file(
367 &self,
364 &self,
368 ) -> Result<OwningDirstateMap, DirstateError> {
365 ) -> Result<OwningDirstateMap, DirstateError> {
369 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
366 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
370 let dirstate_file_contents = self.dirstate_file_contents()?;
367 let dirstate_file_contents = self.dirstate_file_contents()?;
371 if dirstate_file_contents.is_empty() {
368 if dirstate_file_contents.is_empty() {
372 self.dirstate_parents.set(DirstateParents::NULL);
369 self.dirstate_parents.set(DirstateParents::NULL);
373 self.dirstate_data_file_uuid.set(None);
374 return Ok(OwningDirstateMap::new_empty(Vec::new()));
370 return Ok(OwningDirstateMap::new_empty(Vec::new()));
375 }
371 }
376 let docket = crate::dirstate_tree::on_disk::read_docket(
372 let docket = crate::dirstate_tree::on_disk::read_docket(
377 &dirstate_file_contents,
373 &dirstate_file_contents,
378 )?;
374 )?;
379 debug_wait_for_file_or_print(
375 debug_wait_for_file_or_print(
380 self.config(),
376 self.config(),
381 "dirstate.post-docket-read-file",
377 "dirstate.post-docket-read-file",
382 );
378 );
383 self.dirstate_parents.set(docket.parents());
379 self.dirstate_parents.set(docket.parents());
384 self.dirstate_data_file_uuid
385 .set(Some(docket.uuid.to_owned()));
386 let uuid = docket.uuid.to_owned();
380 let uuid = docket.uuid.to_owned();
387 let data_size = docket.data_size();
381 let data_size = docket.data_size();
388
382
389 let context = "between reading dirstate docket and data file";
383 let context = "between reading dirstate docket and data file";
390 let race_error = HgError::RaceDetected(context.into());
384 let race_error = HgError::RaceDetected(context.into());
391 let metadata = docket.tree_metadata();
385 let metadata = docket.tree_metadata();
392
386
393 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
387 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
394 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
388 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
395 let contents = self.hg_vfs().read(docket.data_filename());
389 let contents = self.hg_vfs().read(docket.data_filename());
396 let contents = match contents {
390 let contents = match contents {
397 Ok(c) => c,
391 Ok(c) => c,
398 Err(HgError::IoError { error, context }) => {
392 Err(HgError::IoError { error, context }) => {
399 match error.raw_os_error().expect("real os error") {
393 match error.raw_os_error().expect("real os error") {
400 // 2 = ENOENT, No such file or directory
394 // 2 = ENOENT, No such file or directory
401 // 116 = ESTALE, Stale NFS file handle
395 // 116 = ESTALE, Stale NFS file handle
402 //
396 //
403 // TODO match on `error.kind()` when
397 // TODO match on `error.kind()` when
404 // `ErrorKind::StaleNetworkFileHandle` is stable.
398 // `ErrorKind::StaleNetworkFileHandle` is stable.
405 2 | 116 => {
399 2 | 116 => {
406 // Race where the data file was deleted right after
400 // Race where the data file was deleted right after
407 // we read the docket, try again
401 // we read the docket, try again
408 return Err(race_error.into());
402 return Err(race_error.into());
409 }
403 }
410 _ => {
404 _ => {
411 return Err(
405 return Err(
412 HgError::IoError { error, context }.into()
406 HgError::IoError { error, context }.into()
413 )
407 )
414 }
408 }
415 }
409 }
416 }
410 }
417 Err(e) => return Err(e.into()),
411 Err(e) => return Err(e.into()),
418 };
412 };
419 OwningDirstateMap::new_v2(contents, data_size, metadata, uuid)
413 OwningDirstateMap::new_v2(contents, data_size, metadata, uuid)
420 } else {
414 } else {
421 match self
415 match self
422 .hg_vfs()
416 .hg_vfs()
423 .mmap_open(docket.data_filename())
417 .mmap_open(docket.data_filename())
424 .io_not_found_as_none()
418 .io_not_found_as_none()
425 {
419 {
426 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
420 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
427 data_mmap, data_size, metadata, uuid,
421 data_mmap, data_size, metadata, uuid,
428 ),
422 ),
429 Ok(None) => {
423 Ok(None) => {
430 // Race where the data file was deleted right after we
424 // Race where the data file was deleted right after we
431 // read the docket, try again
425 // read the docket, try again
432 return Err(race_error.into());
426 return Err(race_error.into());
433 }
427 }
434 Err(e) => return Err(e.into()),
428 Err(e) => return Err(e.into()),
435 }
429 }
436 }?;
430 }?;
437
431
438 let write_mode_config = self
432 let write_mode_config = self
439 .config()
433 .config()
440 .get_str(b"devel", b"dirstate.v2.data_update_mode")
434 .get_str(b"devel", b"dirstate.v2.data_update_mode")
441 .unwrap_or(Some("auto"))
435 .unwrap_or(Some("auto"))
442 .unwrap_or("auto"); // don't bother for devel options
436 .unwrap_or("auto"); // don't bother for devel options
443 let write_mode = match write_mode_config {
437 let write_mode = match write_mode_config {
444 "auto" => DirstateMapWriteMode::Auto,
438 "auto" => DirstateMapWriteMode::Auto,
445 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
439 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
446 "force-append" => DirstateMapWriteMode::ForceAppend,
440 "force-append" => DirstateMapWriteMode::ForceAppend,
447 _ => DirstateMapWriteMode::Auto,
441 _ => DirstateMapWriteMode::Auto,
448 };
442 };
449
443
450 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
444 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
451
445
452 Ok(map)
446 Ok(map)
453 }
447 }
454
448
455 pub fn dirstate_map(
449 pub fn dirstate_map(
456 &self,
450 &self,
457 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
451 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
458 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
452 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
459 }
453 }
460
454
461 pub fn dirstate_map_mut(
455 pub fn dirstate_map_mut(
462 &self,
456 &self,
463 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
457 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
464 self.dirstate_map
458 self.dirstate_map
465 .get_mut_or_init(|| self.new_dirstate_map())
459 .get_mut_or_init(|| self.new_dirstate_map())
466 }
460 }
467
461
468 fn new_changelog(&self) -> Result<Changelog, HgError> {
462 fn new_changelog(&self) -> Result<Changelog, HgError> {
469 Changelog::open(&self.store_vfs(), self.has_nodemap())
463 Changelog::open(&self.store_vfs(), self.has_nodemap())
470 }
464 }
471
465
472 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
466 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
473 self.changelog.get_or_init(|| self.new_changelog())
467 self.changelog.get_or_init(|| self.new_changelog())
474 }
468 }
475
469
476 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
470 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
477 self.changelog.get_mut_or_init(|| self.new_changelog())
471 self.changelog.get_mut_or_init(|| self.new_changelog())
478 }
472 }
479
473
480 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
474 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
481 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
475 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
482 }
476 }
483
477
484 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
478 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
485 self.manifestlog.get_or_init(|| self.new_manifestlog())
479 self.manifestlog.get_or_init(|| self.new_manifestlog())
486 }
480 }
487
481
488 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
482 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
489 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
483 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
490 }
484 }
491
485
492 /// Returns the manifest of the *changeset* with the given node ID
486 /// Returns the manifest of the *changeset* with the given node ID
493 pub fn manifest_for_node(
487 pub fn manifest_for_node(
494 &self,
488 &self,
495 node: impl Into<NodePrefix>,
489 node: impl Into<NodePrefix>,
496 ) -> Result<Manifest, RevlogError> {
490 ) -> Result<Manifest, RevlogError> {
497 self.manifestlog()?.data_for_node(
491 self.manifestlog()?.data_for_node(
498 self.changelog()?
492 self.changelog()?
499 .data_for_node(node.into())?
493 .data_for_node(node.into())?
500 .manifest_node()?
494 .manifest_node()?
501 .into(),
495 .into(),
502 )
496 )
503 }
497 }
504
498
505 /// Returns the manifest of the *changeset* with the given revision number
499 /// Returns the manifest of the *changeset* with the given revision number
506 pub fn manifest_for_rev(
500 pub fn manifest_for_rev(
507 &self,
501 &self,
508 revision: Revision,
502 revision: Revision,
509 ) -> Result<Manifest, RevlogError> {
503 ) -> Result<Manifest, RevlogError> {
510 self.manifestlog()?.data_for_node(
504 self.manifestlog()?.data_for_node(
511 self.changelog()?
505 self.changelog()?
512 .data_for_rev(revision)?
506 .data_for_rev(revision)?
513 .manifest_node()?
507 .manifest_node()?
514 .into(),
508 .into(),
515 )
509 )
516 }
510 }
517
511
518 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
512 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
519 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
513 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
520 Ok(entry.tracked())
514 Ok(entry.tracked())
521 } else {
515 } else {
522 Ok(false)
516 Ok(false)
523 }
517 }
524 }
518 }
525
519
526 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
520 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
527 Filelog::open(self, path)
521 Filelog::open(self, path)
528 }
522 }
529
523
530 /// Write to disk any updates that were made through `dirstate_map_mut`.
524 /// Write to disk any updates that were made through `dirstate_map_mut`.
531 ///
525 ///
532 /// The "wlock" must be held while calling this.
526 /// The "wlock" must be held while calling this.
533 /// See for example `try_with_wlock_no_wait`.
527 /// See for example `try_with_wlock_no_wait`.
534 ///
528 ///
535 /// TODO: have a `WritableRepo` type only accessible while holding the
529 /// TODO: have a `WritableRepo` type only accessible while holding the
536 /// lock?
530 /// lock?
537 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
531 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
538 let map = self.dirstate_map()?;
532 let map = self.dirstate_map()?;
539 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
533 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
540 // it’s unset
534 // it’s unset
541 let parents = self.dirstate_parents()?;
535 let parents = self.dirstate_parents()?;
542 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
536 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
543 let uuid_opt = self
537 let (uuid, data_size) = self.get_dirstate_data_file_integrity()?;
544 .dirstate_data_file_uuid
538 let uuid_changed = uuid.as_deref() != map.old_uuid();
545 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
539 let data_length_changed = data_size != map.old_data_size();
546 let uuid_opt = uuid_opt.as_ref();
540
541 if uuid_changed || data_length_changed {
542 // If uuid or length changed since last disk read, don't write.
543 // This is fine because either we're in a command that doesn't
544 // write anything too important (like `hg status`), or we're in
545 // `hg add` and we're supposed to have taken the lock before
546 // reading anyway.
547 //
548 // TODO complain loudly if we've changed anything important
549 // without taking the lock.
550 // (see `hg help config.format.use-dirstate-tracked-hint`)
551 log::debug!(
552 "dirstate has changed since last read, not updating."
553 );
554 return Ok(());
555 }
556
557 let uuid_opt = map.old_uuid();
547 let write_mode = if uuid_opt.is_some() {
558 let write_mode = if uuid_opt.is_some() {
548 DirstateMapWriteMode::Auto
559 DirstateMapWriteMode::Auto
549 } else {
560 } else {
550 DirstateMapWriteMode::ForceNewDataFile
561 DirstateMapWriteMode::ForceNewDataFile
551 };
562 };
552 let (data, tree_metadata, append, old_data_size) =
563 let (data, tree_metadata, append, old_data_size) =
553 map.pack_v2(write_mode)?;
564 map.pack_v2(write_mode)?;
554
565
555 // Reuse the uuid, or generate a new one, keeping the old for
566 // Reuse the uuid, or generate a new one, keeping the old for
556 // deletion.
567 // deletion.
557 let (uuid, old_uuid) = match uuid_opt {
568 let (uuid, old_uuid) = match uuid_opt {
558 Some(uuid) => {
569 Some(uuid) => {
559 let as_str = std::str::from_utf8(uuid)
570 let as_str = std::str::from_utf8(uuid)
560 .map_err(|_| {
571 .map_err(|_| {
561 HgError::corrupted(
572 HgError::corrupted(
562 "non-UTF-8 dirstate data file ID",
573 "non-UTF-8 dirstate data file ID",
563 )
574 )
564 })?
575 })?
565 .to_owned();
576 .to_owned();
566 if append {
577 if append {
567 (as_str, None)
578 (as_str, None)
568 } else {
579 } else {
569 (DirstateDocket::new_uid(), Some(as_str))
580 (DirstateDocket::new_uid(), Some(as_str))
570 }
581 }
571 }
582 }
572 None => (DirstateDocket::new_uid(), None),
583 None => (DirstateDocket::new_uid(), None),
573 };
584 };
574
585
575 let data_filename = format!("dirstate.{}", uuid);
586 let data_filename = format!("dirstate.{}", uuid);
576 let data_filename = self.hg_vfs().join(data_filename);
587 let data_filename = self.hg_vfs().join(data_filename);
577 let mut options = std::fs::OpenOptions::new();
588 let mut options = std::fs::OpenOptions::new();
578 options.write(true);
589 options.write(true);
579
590
580 // Why are we not using the O_APPEND flag when appending?
591 // Why are we not using the O_APPEND flag when appending?
581 //
592 //
582 // - O_APPEND makes it trickier to deal with garbage at the end of
593 // - O_APPEND makes it trickier to deal with garbage at the end of
583 // the file, left by a previous uncommitted transaction. By
594 // the file, left by a previous uncommitted transaction. By
584 // starting the write at [old_data_size] we make sure we erase
595 // starting the write at [old_data_size] we make sure we erase
585 // all such garbage.
596 // all such garbage.
586 //
597 //
587 // - O_APPEND requires to special-case 0-byte writes, whereas we
598 // - O_APPEND requires to special-case 0-byte writes, whereas we
588 // don't need that.
599 // don't need that.
589 //
600 //
590 // - Some OSes have bugs in implementation O_APPEND:
601 // - Some OSes have bugs in implementation O_APPEND:
591 // revlog.py talks about a Solaris bug, but we also saw some ZFS
602 // revlog.py talks about a Solaris bug, but we also saw some ZFS
592 // bug: https://github.com/openzfs/zfs/pull/3124,
603 // bug: https://github.com/openzfs/zfs/pull/3124,
593 // https://github.com/openzfs/zfs/issues/13370
604 // https://github.com/openzfs/zfs/issues/13370
594 //
605 //
595 if !append {
606 if !append {
596 log::trace!("creating a new dirstate data file");
607 log::trace!("creating a new dirstate data file");
597 options.create_new(true);
608 options.create_new(true);
598 } else {
609 } else {
599 log::trace!("appending to the dirstate data file");
610 log::trace!("appending to the dirstate data file");
600 }
611 }
601
612
602 let data_size = (|| {
613 let data_size = (|| {
603 // TODO: loop and try another random ID if !append and this
614 // TODO: loop and try another random ID if !append and this
604 // returns `ErrorKind::AlreadyExists`? Collision chance of two
615 // returns `ErrorKind::AlreadyExists`? Collision chance of two
605 // random IDs is one in 2**32
616 // random IDs is one in 2**32
606 let mut file = options.open(&data_filename)?;
617 let mut file = options.open(&data_filename)?;
607 if append {
618 if append {
608 file.seek(SeekFrom::Start(old_data_size as u64))?;
619 file.seek(SeekFrom::Start(old_data_size as u64))?;
609 }
620 }
610 file.write_all(&data)?;
621 file.write_all(&data)?;
611 file.flush()?;
622 file.flush()?;
612 file.seek(SeekFrom::Current(0))
623 file.seek(SeekFrom::Current(0))
613 })()
624 })()
614 .when_writing_file(&data_filename)?;
625 .when_writing_file(&data_filename)?;
615
626
616 let packed_dirstate = DirstateDocket::serialize(
627 let packed_dirstate = DirstateDocket::serialize(
617 parents,
628 parents,
618 tree_metadata,
629 tree_metadata,
619 data_size,
630 data_size,
620 uuid.as_bytes(),
631 uuid.as_bytes(),
621 )
632 )
622 .map_err(|_: std::num::TryFromIntError| {
633 .map_err(|_: std::num::TryFromIntError| {
623 HgError::corrupted("overflow in dirstate docket serialization")
634 HgError::corrupted("overflow in dirstate docket serialization")
624 })?;
635 })?;
625
636
626 (packed_dirstate, old_uuid)
637 (packed_dirstate, old_uuid)
627 } else {
638 } else {
628 (map.pack_v1(parents)?, None)
639 (map.pack_v1(parents)?, None)
629 };
640 };
630
641
631 let vfs = self.hg_vfs();
642 let vfs = self.hg_vfs();
632 vfs.atomic_write("dirstate", &packed_dirstate)?;
643 vfs.atomic_write("dirstate", &packed_dirstate)?;
633 if let Some(uuid) = old_uuid_to_remove {
644 if let Some(uuid) = old_uuid_to_remove {
634 // Remove the old data file after the new docket pointing to the
645 // Remove the old data file after the new docket pointing to the
635 // new data file was written.
646 // new data file was written.
636 vfs.remove_file(format!("dirstate.{}", uuid))?;
647 vfs.remove_file(format!("dirstate.{}", uuid))?;
637 }
648 }
638 Ok(())
649 Ok(())
639 }
650 }
640 }
651 }
641
652
642 /// Lazily-initialized component of `Repo` with interior mutability
653 /// Lazily-initialized component of `Repo` with interior mutability
643 ///
654 ///
644 /// This differs from `OnceCell` in that the value can still be "deinitialized"
655 /// This differs from `OnceCell` in that the value can still be "deinitialized"
645 /// later by setting its inner `Option` to `None`. It also takes the
656 /// later by setting its inner `Option` to `None`. It also takes the
646 /// initialization function as an argument when the value is requested, not
657 /// initialization function as an argument when the value is requested, not
647 /// when the instance is created.
658 /// when the instance is created.
648 struct LazyCell<T> {
659 struct LazyCell<T> {
649 value: RefCell<Option<T>>,
660 value: RefCell<Option<T>>,
650 }
661 }
651
662
652 impl<T> LazyCell<T> {
663 impl<T> LazyCell<T> {
653 fn new() -> Self {
664 fn new() -> Self {
654 Self {
665 Self {
655 value: RefCell::new(None),
666 value: RefCell::new(None),
656 }
667 }
657 }
668 }
658
669
659 fn set(&self, value: T) {
670 fn set(&self, value: T) {
660 *self.value.borrow_mut() = Some(value)
671 *self.value.borrow_mut() = Some(value)
661 }
672 }
662
673
663 fn get_or_init<E>(
674 fn get_or_init<E>(
664 &self,
675 &self,
665 init: impl Fn() -> Result<T, E>,
676 init: impl Fn() -> Result<T, E>,
666 ) -> Result<Ref<T>, E> {
677 ) -> Result<Ref<T>, E> {
667 let mut borrowed = self.value.borrow();
678 let mut borrowed = self.value.borrow();
668 if borrowed.is_none() {
679 if borrowed.is_none() {
669 drop(borrowed);
680 drop(borrowed);
670 // Only use `borrow_mut` if it is really needed to avoid panic in
681 // Only use `borrow_mut` if it is really needed to avoid panic in
671 // case there is another outstanding borrow but mutation is not
682 // case there is another outstanding borrow but mutation is not
672 // needed.
683 // needed.
673 *self.value.borrow_mut() = Some(init()?);
684 *self.value.borrow_mut() = Some(init()?);
674 borrowed = self.value.borrow()
685 borrowed = self.value.borrow()
675 }
686 }
676 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
687 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
677 }
688 }
678
689
679 fn get_mut_or_init<E>(
690 fn get_mut_or_init<E>(
680 &self,
691 &self,
681 init: impl Fn() -> Result<T, E>,
692 init: impl Fn() -> Result<T, E>,
682 ) -> Result<RefMut<T>, E> {
693 ) -> Result<RefMut<T>, E> {
683 let mut borrowed = self.value.borrow_mut();
694 let mut borrowed = self.value.borrow_mut();
684 if borrowed.is_none() {
695 if borrowed.is_none() {
685 *borrowed = Some(init()?);
696 *borrowed = Some(init()?);
686 }
697 }
687 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
698 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
688 }
699 }
689 }
700 }
@@ -1,430 +1,410 b''
1 ==============================================================================
1 ==============================================================================
2 Check potential race conditions between a dirstate's read and other operations
2 Check potential race conditions between a dirstate's read 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 #testcases pre-all-read pre-some-read
6 #testcases pre-all-read pre-some-read
7
7
8 Some commands, like `hg status`, do not need to take the wlock but need to
8 Some commands, like `hg status`, do not need to take the wlock but need to
9 access dirstate data.
9 access dirstate data.
10 Other commands might update the dirstate data while this happens.
10 Other commands might update the dirstate data while this happens.
11
11
12 This can create issues if repository data is read in the wrong order, or for
12 This can create issues if repository data is read in the wrong order, or for
13 the dirstate-v2 format where the data is contained in multiple files.
13 the dirstate-v2 format where the data is contained in multiple files.
14
14
15 This test file is meant to test various cases where such parallel operations
15 This test file is meant to test various cases where such parallel operations
16 happen and make sure the reading process behaves fine. We do so with a `hg
16 happen and make sure the reading process behaves fine. We do so with a `hg
17 status` command since it is probably the most advanced of such read-only
17 status` command since it is probably the most advanced of such read-only
18 command.
18 command.
19
19
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
21 different type of race.
21 different type of race.
22
22
23 Setup
23 Setup
24 =====
24 =====
25
25
26 $ cat >> $HGRCPATH << EOF
26 $ cat >> $HGRCPATH << EOF
27 > [storage]
27 > [storage]
28 > dirstate-v2.slow-path=allow
28 > dirstate-v2.slow-path=allow
29 > EOF
29 > EOF
30
30
31 #if no-dirstate-v1
31 #if no-dirstate-v1
32 $ cat >> $HGRCPATH << EOF
32 $ cat >> $HGRCPATH << EOF
33 > [format]
33 > [format]
34 > use-dirstate-v2=yes
34 > use-dirstate-v2=yes
35 > EOF
35 > EOF
36 #else
36 #else
37 $ cat >> $HGRCPATH << EOF
37 $ cat >> $HGRCPATH << EOF
38 > [format]
38 > [format]
39 > use-dirstate-v2=no
39 > use-dirstate-v2=no
40 > EOF
40 > EOF
41 #endif
41 #endif
42
42
43 #if dirstate-v2-rewrite
43 #if dirstate-v2-rewrite
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
45 #endif
45 #endif
46 #if dirstate-v2-append
46 #if dirstate-v2-append
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
48 #endif
48 #endif
49
49
50
50
51 #if dirstate-v1
51 #if dirstate-v1
52 $ cfg="devel.sync.dirstate.pre-read-file"
52 $ cfg="devel.sync.dirstate.pre-read-file"
53 #else
53 #else
54 #if pre-all-read
54 #if pre-all-read
55 $ cfg="devel.sync.dirstate.pre-read-file"
55 $ cfg="devel.sync.dirstate.pre-read-file"
56 #else
56 #else
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
58 #endif
58 #endif
59 #endif
59 #endif
60
60
61 $ directories="dir dir/nested dir2"
61 $ directories="dir dir/nested dir2"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
64 $ extra_files="dir/n dir/o p q"
64 $ extra_files="dir/n dir/o p q"
65
65
66 $ hg init reference-repo
66 $ hg init reference-repo
67 $ cd reference-repo
67 $ cd reference-repo
68 $ mkdir -p dir/nested dir2
68 $ mkdir -p dir/nested dir2
69 $ touch -t 200001010000 $first_files $directories
69 $ touch -t 200001010000 $first_files $directories
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
71 $ touch -t 200001010010 $second_files $directories
71 $ touch -t 200001010010 $second_files $directories
72 $ hg commit -Aqm "more files to have two commit"
72 $ hg commit -Aqm "more files to have two commit"
73 $ hg log -G -v
73 $ hg log -G -v
74 @ changeset: 1:9a86dcbfb938
74 @ changeset: 1:9a86dcbfb938
75 | tag: tip
75 | tag: tip
76 | user: test
76 | user: test
77 | date: Thu Jan 01 00:00:00 1970 +0000
77 | date: Thu Jan 01 00:00:00 1970 +0000
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
79 | description:
79 | description:
80 | more files to have two commit
80 | more files to have two commit
81 |
81 |
82 |
82 |
83 o changeset: 0:4f23db756b09
83 o changeset: 0:4f23db756b09
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
87 description:
87 description:
88 recreate a bunch of files to facilitate dirstate-v2 append
88 recreate a bunch of files to facilitate dirstate-v2 append
89
89
90
90
91 $ hg manifest
91 $ hg manifest
92 dir/b
92 dir/b
93 dir/c
93 dir/c
94 dir/d
94 dir/d
95 dir/i
95 dir/i
96 dir/j
96 dir/j
97 dir/nested/a
97 dir/nested/a
98 dir/nested/h
98 dir/nested/h
99 dir/nested/m
99 dir/nested/m
100 dir2/e
100 dir2/e
101 dir2/k
101 dir2/k
102 dir2/l
102 dir2/l
103 f
103 f
104 g
104 g
105
105
106 Add some unknown files and refresh the dirstate
106 Add some unknown files and refresh the dirstate
107
107
108 $ touch -t 200001010020 $extra_files
108 $ touch -t 200001010020 $extra_files
109 $ hg add dir/o
109 $ hg add dir/o
110 $ hg remove dir/nested/m
110 $ hg remove dir/nested/m
111
111
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
113 A dir/o
113 A dir/o
114 R dir/nested/m
114 R dir/nested/m
115 ? dir/n
115 ? dir/n
116 ? p
116 ? p
117 ? q
117 ? q
118 $ hg debugstate
118 $ hg debugstate
119 n 644 0 2000-01-01 00:00:00 dir/b
119 n 644 0 2000-01-01 00:00:00 dir/b
120 n 644 0 2000-01-01 00:00:00 dir/c
120 n 644 0 2000-01-01 00:00:00 dir/c
121 n 644 0 2000-01-01 00:00:00 dir/d
121 n 644 0 2000-01-01 00:00:00 dir/d
122 n 644 0 2000-01-01 00:10:00 dir/i
122 n 644 0 2000-01-01 00:10:00 dir/i
123 n 644 0 2000-01-01 00:10:00 dir/j
123 n 644 0 2000-01-01 00:10:00 dir/j
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
126 r ?????????????????????????????????? dir/nested/m (glob)
126 r ?????????????????????????????????? dir/nested/m (glob)
127 a ?????????????????????????????????? dir/o (glob)
127 a ?????????????????????????????????? dir/o (glob)
128 n 644 0 2000-01-01 00:00:00 dir2/e
128 n 644 0 2000-01-01 00:00:00 dir2/e
129 n 644 0 2000-01-01 00:10:00 dir2/k
129 n 644 0 2000-01-01 00:10:00 dir2/k
130 n 644 0 2000-01-01 00:10:00 dir2/l
130 n 644 0 2000-01-01 00:10:00 dir2/l
131 n 644 0 2000-01-01 00:00:00 f
131 n 644 0 2000-01-01 00:00:00 f
132 n 644 0 2000-01-01 00:10:00 g
132 n 644 0 2000-01-01 00:10:00 g
133 $ hg debugstate > ../reference
133 $ hg debugstate > ../reference
134 $ cd ..
134 $ cd ..
135
135
136 Actual Testing
136 Actual Testing
137 ==============
137 ==============
138
138
139 Race with a `hg add`
139 Race with a `hg add`
140 -------------------
140 -------------------
141
141
142 $ cp -a reference-repo race-with-add
142 $ cp -a reference-repo race-with-add
143 $ cd race-with-add
143 $ cd race-with-add
144
144
145 spin a `hg status` with some caches to update
145 spin a `hg status` with some caches to update
146
146
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
148 > --config rhg.on-unsupported=abort \
148 > --config rhg.on-unsupported=abort \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
150 > &
150 > &
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
152
152
153 Add a file
153 Add a file
154
154
155 $ hg $d2args add dir/n
155 $ hg $d2args add dir/n
156 $ touch $TESTTMP/status-race-lock
156 $ touch $TESTTMP/status-race-lock
157 $ wait
157 $ wait
158
158
159 The file should in a "added" state
159 The file should in a "added" state
160
160
161 $ hg status
161 $ hg status
162 A dir/n
162 A dir/n
163 A dir/o
163 A dir/o
164 R dir/nested/m
164 R dir/nested/m
165 ? p
165 ? p
166 ? q
166 ? q
167
167
168 The status process should return a consistent result and not crash.
168 The status process should return a consistent result and not crash.
169
169
170 #if dirstate-v1
170 #if dirstate-v1
171 $ cat $TESTTMP/status-race-lock.out
171 $ cat $TESTTMP/status-race-lock.out
172 A dir/n
172 A dir/n
173 A dir/o
173 A dir/o
174 R dir/nested/m
174 R dir/nested/m
175 ? p
175 ? p
176 ? q
176 ? q
177 #else
177 #else
178 #if rhg pre-some-read dirstate-v2-append
178 #if rhg pre-some-read dirstate-v2-append
179 $ cat $TESTTMP/status-race-lock.out
179 $ cat $TESTTMP/status-race-lock.out
180 A dir/o
180 A dir/o
181 R dir/nested/m
181 R dir/nested/m
182 ? dir/n
182 ? dir/n
183 ? p
183 ? p
184 ? q
184 ? q
185 #else
185 #else
186 #if rust no-rhg dirstate-v2-append
186 #if rust no-rhg dirstate-v2-append
187 $ cat $TESTTMP/status-race-lock.out
187 $ cat $TESTTMP/status-race-lock.out
188 A dir/o
188 A dir/o
189 R dir/nested/m
189 R dir/nested/m
190 ? dir/n
190 ? dir/n
191 ? p
191 ? p
192 ? q
192 ? q
193 #else
193 #else
194 $ cat $TESTTMP/status-race-lock.out
194 $ cat $TESTTMP/status-race-lock.out
195 A dir/n
195 A dir/n
196 A dir/o
196 A dir/o
197 R dir/nested/m
197 R dir/nested/m
198 ? p
198 ? p
199 ? q
199 ? q
200 #endif
200 #endif
201 #endif
201 #endif
202 #endif
202 #endif
203 $ cat $TESTTMP/status-race-lock.log
203 $ cat $TESTTMP/status-race-lock.log
204
204
205 final cleanup
205 final cleanup
206
206
207 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
207 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
208 $ cd ..
208 $ cd ..
209
209
210 Race with a `hg commit`
210 Race with a `hg commit`
211 -----------------------
211 -----------------------
212
212
213 $ cp -a reference-repo race-with-commit
213 $ cp -a reference-repo race-with-commit
214 $ cd race-with-commit
214 $ cd race-with-commit
215
215
216 spin a `hg status with some cache to update
216 spin a `hg status with some cache to update
217
217
218 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
218 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
219 > --config rhg.on-unsupported=abort \
219 > --config rhg.on-unsupported=abort \
220 > --config ${cfg}=$TESTTMP/status-race-lock \
220 > --config ${cfg}=$TESTTMP/status-race-lock \
221 > &
221 > &
222 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
222 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
223
223
224 Add a do a commit
224 Add a do a commit
225
225
226 $ hg status
226 $ hg status
227 A dir/o
227 A dir/o
228 R dir/nested/m
228 R dir/nested/m
229 ? dir/n
229 ? dir/n
230 ? p
230 ? p
231 ? q
231 ? q
232 $ hg $d2args commit -m 'racing commit'
232 $ hg $d2args commit -m 'racing commit'
233 $ touch $TESTTMP/status-race-lock
233 $ touch $TESTTMP/status-race-lock
234 $ wait
234 $ wait
235
235
236 commit was created, and status is now clean
236 commit was created, and status is now clean
237
237
238 $ hg log -GT '{node|short} {desc}\n'
238 $ hg log -GT '{node|short} {desc}\n'
239 @ 02a67a77ee9b racing commit
239 @ 02a67a77ee9b racing commit
240 |
240 |
241 o 9a86dcbfb938 more files to have two commit
241 o 9a86dcbfb938 more files to have two commit
242 |
242 |
243 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
243 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
244
244
245 $ hg status
245 $ hg status
246 ? dir/n
246 ? dir/n
247 ? p
247 ? p
248 ? q
248 ? q
249
249
250 The status process should return a consistent result and not crash.
250 The status process should return a consistent result and not crash.
251
251
252 #if rust no-rhg dirstate-v2-append
252 #if rust no-rhg dirstate-v2-append
253 $ cat $TESTTMP/status-race-lock.out
253 $ cat $TESTTMP/status-race-lock.out
254 A dir/o
254 A dir/o
255 R dir/nested/m
255 R dir/nested/m
256 ? dir/n
256 ? dir/n
257 ? p
257 ? p
258 ? q
258 ? q
259 $ cat $TESTTMP/status-race-lock.log
259 $ cat $TESTTMP/status-race-lock.log
260 #else
260 #else
261 #if rhg pre-some-read dirstate-v2-append
261 #if rhg pre-some-read dirstate-v2-append
262 $ cat $TESTTMP/status-race-lock.out
262 $ cat $TESTTMP/status-race-lock.out
263 A dir/o
263 A dir/o
264 R dir/nested/m
264 R dir/nested/m
265 ? dir/n
265 ? dir/n
266 ? p
266 ? p
267 ? q
267 ? q
268 $ cat $TESTTMP/status-race-lock.log
268 $ cat $TESTTMP/status-race-lock.log
269 #else
269 #else
270 $ cat $TESTTMP/status-race-lock.out
270 $ cat $TESTTMP/status-race-lock.out
271 M dir/o (no-rhg known-bad-output !)
271 M dir/o (no-rhg known-bad-output !)
272 ? dir/n
272 ? dir/n
273 ? p
273 ? p
274 ? q
274 ? q
275 $ cat $TESTTMP/status-race-lock.log
275 $ cat $TESTTMP/status-race-lock.log
276 warning: ignoring unknown working parent 02a67a77ee9b! (no-rhg !)
276 warning: ignoring unknown working parent 02a67a77ee9b! (no-rhg !)
277 #endif
277 #endif
278 #endif
278 #endif
279
279
280 final cleanup
280 final cleanup
281
281
282 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
282 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
283 $ cd ..
283 $ cd ..
284
284
285 Race with a `hg update`
285 Race with a `hg update`
286 -----------------------
286 -----------------------
287
287
288 $ cp -a reference-repo race-with-update
288 $ cp -a reference-repo race-with-update
289 $ cd race-with-update
289 $ cd race-with-update
290
290
291 spin a `hg status` with some caches to update
291 spin a `hg status` with some caches to update
292
292
293 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
293 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
294 > --config rhg.on-unsupported=abort \
294 > --config rhg.on-unsupported=abort \
295 > --config ${cfg}=$TESTTMP/status-race-lock \
295 > --config ${cfg}=$TESTTMP/status-race-lock \
296 > &
296 > &
297 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
297 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
298 do an update
298 do an update
299
299
300 $ hg status
300 $ hg status
301 A dir/o
301 A dir/o
302 R dir/nested/m
302 R dir/nested/m
303 ? dir/n
303 ? dir/n
304 ? p
304 ? p
305 ? q
305 ? q
306 $ hg log -GT '{node|short} {desc}\n'
306 $ hg log -GT '{node|short} {desc}\n'
307 @ 9a86dcbfb938 more files to have two commit
307 @ 9a86dcbfb938 more files to have two commit
308 |
308 |
309 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
309 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
310
310
311 $ hg $d2args update --merge ".~1"
311 $ hg $d2args update --merge ".~1"
312 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
312 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
313 $ touch $TESTTMP/status-race-lock
313 $ touch $TESTTMP/status-race-lock
314 $ wait
314 $ wait
315 #if rhg dirstate-v2-append pre-some-read
316 $ hg log -GT '{node|short} {desc}\n'
317 @ 9a86dcbfb938 more files to have two commit
318 |
319 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
320
321 $ hg status
322 A dir/o
323 R dir/nested/m
324 ! dir/i
325 ! dir/j
326 ! dir/nested/h
327 ! dir2/k
328 ! dir2/l
329 ! g
330 ? dir/n
331 ? p
332 ? q
333 #else
334 $ hg log -GT '{node|short} {desc}\n'
315 $ hg log -GT '{node|short} {desc}\n'
335 o 9a86dcbfb938 more files to have two commit
316 o 9a86dcbfb938 more files to have two commit
336 |
317 |
337 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
318 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
338
319
339 $ hg status
320 $ hg status
340 A dir/o
321 A dir/o
341 ? dir/n
322 ? dir/n
342 ? p
323 ? p
343 ? q
324 ? q
344 #endif
345
325
346 The status process should return a consistent result and not crash.
326 The status process should return a consistent result and not crash.
347
327
348 #if rhg dirstate-v2-append pre-some-read
328 #if rhg dirstate-v2-append pre-some-read
349 $ cat $TESTTMP/status-race-lock.out
329 $ cat $TESTTMP/status-race-lock.out
350 A dir/o
330 A dir/o
351 R dir/nested/m
331 R dir/nested/m
352 ! dir/i
332 ! dir/i
353 ! dir/j
333 ! dir/j
354 ! dir/nested/h
334 ! dir/nested/h
355 ! dir2/k
335 ! dir2/k
356 ! dir2/l
336 ! dir2/l
357 ! g
337 ! g
358 ? dir/n
338 ? dir/n
359 ? p
339 ? p
360 ? q
340 ? q
361 #else
341 #else
362 #if rust no-rhg dirstate-v2-append
342 #if rust no-rhg dirstate-v2-append
363 $ cat $TESTTMP/status-race-lock.out
343 $ cat $TESTTMP/status-race-lock.out
364 A dir/o
344 A dir/o
365 R dir/nested/m
345 R dir/nested/m
366 ! dir/i
346 ! dir/i
367 ! dir/j
347 ! dir/j
368 ! dir/nested/h
348 ! dir/nested/h
369 ! dir2/k
349 ! dir2/k
370 ! dir2/l
350 ! dir2/l
371 ! g
351 ! g
372 ? dir/n
352 ? dir/n
373 ? p
353 ? p
374 ? q
354 ? q
375 #else
355 #else
376 $ cat $TESTTMP/status-race-lock.out
356 $ cat $TESTTMP/status-race-lock.out
377 A dir/o
357 A dir/o
378 ? dir/n
358 ? dir/n
379 ? p
359 ? p
380 ? q
360 ? q
381 #endif
361 #endif
382 #endif
362 #endif
383 $ cat $TESTTMP/status-race-lock.log
363 $ cat $TESTTMP/status-race-lock.log
384
364
385 final cleanup
365 final cleanup
386
366
387 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
367 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
388 $ cd ..
368 $ cd ..
389
369
390 Race with a cache updating `hg status`
370 Race with a cache updating `hg status`
391 --------------------------------------
371 --------------------------------------
392
372
393 It is interesting to race with "read-only" operation (that still update its cache)
373 It is interesting to race with "read-only" operation (that still update its cache)
394
374
395 $ cp -a reference-repo race-with-status
375 $ cp -a reference-repo race-with-status
396 $ cd race-with-status
376 $ cd race-with-status
397
377
398 spin a `hg status` with some caches to update
378 spin a `hg status` with some caches to update
399
379
400 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
380 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
401 > --config rhg.on-unsupported=abort \
381 > --config rhg.on-unsupported=abort \
402 > --config ${cfg}=$TESTTMP/status-race-lock \
382 > --config ${cfg}=$TESTTMP/status-race-lock \
403 > &
383 > &
404 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
384 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
405 do an update
385 do an update
406
386
407 $ touch -t 200001020006 f
387 $ touch -t 200001020006 f
408 $ hg $d2args status
388 $ hg $d2args status
409 A dir/o
389 A dir/o
410 R dir/nested/m
390 R dir/nested/m
411 ? dir/n
391 ? dir/n
412 ? p
392 ? p
413 ? q
393 ? q
414 $ touch $TESTTMP/status-race-lock
394 $ touch $TESTTMP/status-race-lock
415 $ wait
395 $ wait
416
396
417 The status process should return a consistent result and not crash.
397 The status process should return a consistent result and not crash.
418
398
419 $ cat $TESTTMP/status-race-lock.out
399 $ cat $TESTTMP/status-race-lock.out
420 A dir/o
400 A dir/o
421 R dir/nested/m
401 R dir/nested/m
422 ? dir/n
402 ? dir/n
423 ? p
403 ? p
424 ? q
404 ? q
425 $ cat $TESTTMP/status-race-lock.log
405 $ cat $TESTTMP/status-race-lock.log
426
406
427 final cleanup
407 final cleanup
428
408
429 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
409 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
430 $ cd ..
410 $ cd ..
@@ -1,485 +1,482 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 dirstate-v1 !)
246 A dir/n (rhg dirstate-v2-rewrite !)
246 A dir/n (no-dirstate-v1 !)
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 dirstate-v1 !)
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 !)
264
263
265 final cleanup
264 final cleanup
266
265
267 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
266 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
268 $ cd ..
267 $ cd ..
269
268
270 Race with a `hg commit`
269 Race with a `hg commit`
271 ----------------------
270 ----------------------
272
271
273 $ cp -a reference-repo race-with-commit
272 $ cp -a reference-repo race-with-commit
274 $ cd race-with-commit
273 $ cd race-with-commit
275
274
276 spin a `hg status` with some caches to update
275 spin a `hg status` with some caches to update
277
276
278 $ touch -t 200001020001 dir/j
277 $ touch -t 200001020001 dir/j
279 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
278 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
280 > --config rhg.on-unsupported=abort \
279 > --config rhg.on-unsupported=abort \
281 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
280 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
282 > &
281 > &
283 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
282 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
284
283
285 Add a file and force the data file rewrite
284 Add a file and force the data file rewrite
286
285
287 $ hg $d2args commit -m created-during-status dir/o
286 $ hg $d2args commit -m created-during-status dir/o
288 $ touch $TESTTMP/status-race-lock
287 $ touch $TESTTMP/status-race-lock
289 $ wait
288 $ wait
290
289
291 The parent must change and the status should be clean
290 The parent must change and the status should be clean
292
291
293 # XXX rhg misbehaves here
292 # XXX rhg misbehaves here
294 #if no-rhg
293 #if rhg dirstate-v1
295 $ hg summary
296 parent: 2:2e3b442a2fd4 tip
297 created-during-status
298 branch: default
299 commit: 1 removed, 3 unknown
300 update: (current)
301 phases: 3 draft
302 $ hg status
303 R dir/nested/m
304 ? dir/n
305 ? p
306 ? q
307 #else
308 $ hg summary
294 $ hg summary
309 parent: 1:c349430a1631
295 parent: 1:c349430a1631
310 more files to have two commits
296 more files to have two commits
311 branch: default
297 branch: default
312 commit: 1 added, 1 removed, 3 unknown (new branch head)
298 commit: 1 added, 1 removed, 3 unknown (new branch head)
313 update: 1 new changesets (update)
299 update: 1 new changesets (update)
314 phases: 3 draft
300 phases: 3 draft
315 $ hg status
301 $ hg status
316 A dir/o
302 A dir/o
317 R dir/nested/m
303 R dir/nested/m
318 ? dir/n
304 ? dir/n
319 ? p
305 ? p
320 ? q
306 ? q
307 #else
308 $ hg summary
309 parent: 2:2e3b442a2fd4 tip
310 created-during-status
311 branch: default
312 commit: 1 removed, 3 unknown
313 update: (current)
314 phases: 3 draft
315 $ hg status
316 R dir/nested/m
317 ? dir/n
318 ? p
319 ? q
321 #endif
320 #endif
322
321
323 The status process should return a consistent result and not crash.
322 The status process should return a consistent result and not crash.
324
323
325 $ cat $TESTTMP/status-race-lock.out
324 $ cat $TESTTMP/status-race-lock.out
326 A dir/o
325 A dir/o
327 R dir/nested/m
326 R dir/nested/m
328 ? dir/n
327 ? dir/n
329 ? p
328 ? p
330 ? q
329 ? q
331 $ cat $TESTTMP/status-race-lock.log
330 $ 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 !)
333
331
334 final cleanup
332 final cleanup
335
333
336 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
334 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
337 $ cd ..
335 $ cd ..
338
336
339 Race with a `hg update`
337 Race with a `hg update`
340 ----------------------
338 ----------------------
341
339
342 $ cp -a reference-repo race-with-update
340 $ cp -a reference-repo race-with-update
343 $ cd race-with-update
341 $ cd race-with-update
344
342
345 spin a `hg status` with some caches to update
343 spin a `hg status` with some caches to update
346
344
347 $ touch -t 200001020001 dir2/k
345 $ touch -t 200001020001 dir2/k
348 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
346 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
349 > --config rhg.on-unsupported=abort \
347 > --config rhg.on-unsupported=abort \
350 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
348 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
351 > &
349 > &
352 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
350 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
353
351
354 Add a file and force the data file rewrite
352 Add a file and force the data file rewrite
355
353
356 $ hg $d2args update ".~1"
354 $ hg $d2args update ".~1"
357 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
355 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
358 $ touch $TESTTMP/status-race-lock
356 $ touch $TESTTMP/status-race-lock
359 $ wait
357 $ wait
360
358
361 The parent must change and the status should be clean
359 The parent must change and the status should be clean
362
360
363 $ hg summary
361 $ hg summary
364 parent: 0:4f23db756b09
362 parent: 0:4f23db756b09
365 recreate a bunch of files to facilitate dirstate-v2 append
363 recreate a bunch of files to facilitate dirstate-v2 append
366 branch: default
364 branch: default
367 commit: 1 added, 3 unknown (new branch head)
365 commit: 1 added, 3 unknown (new branch head)
368 update: 1 new changesets (update)
366 update: 1 new changesets (update)
369 phases: 2 draft
367 phases: 2 draft
370 $ hg status
368 $ hg status
371 A dir/o
369 A dir/o
372 ? dir/n
370 ? dir/n
373 ? p
371 ? p
374 ? q
372 ? q
375
373
376 The status process should return a consistent result and not crash.
374 The status process should return a consistent result and not crash.
377
375
378 $ cat $TESTTMP/status-race-lock.out
376 $ cat $TESTTMP/status-race-lock.out
379 A dir/o
377 A dir/o
380 R dir/nested/m
378 R dir/nested/m
381 ? dir/n
379 ? dir/n
382 ? p
380 ? p
383 ? q
381 ? q
384 $ cat $TESTTMP/status-race-lock.log
382 $ cat $TESTTMP/status-race-lock.log
385
383
386 final cleanup
384 final cleanup
387
385
388 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
386 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
389 $ cd ..
387 $ cd ..
390
388
391 Race with another status
389 Race with another status
392 ------------------------
390 ------------------------
393
391
394 $ cp -a reference-repo race-with-status
392 $ cp -a reference-repo race-with-status
395 $ cd race-with-status
393 $ cd race-with-status
396
394
397 spin a `hg status` with some caches to update
395 spin a `hg status` with some caches to update
398
396
399 $ touch -t 200001010030 dir/nested/h
397 $ touch -t 200001010030 dir/nested/h
400 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
398 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
401 > --config rhg.on-unsupported=abort \
399 > --config rhg.on-unsupported=abort \
402 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
400 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
403 > &
401 > &
404 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
402 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
405
403
406 touch g
404 touch g
407
405
408 $ touch -t 200001010025 g
406 $ touch -t 200001010025 g
409 $ hg $d2args status
407 $ hg $d2args status
410 A dir/o
408 A dir/o
411 R dir/nested/m
409 R dir/nested/m
412 ? dir/n
410 ? dir/n
413 ? p
411 ? p
414 ? q
412 ? q
415 $ touch $TESTTMP/status-race-lock
413 $ touch $TESTTMP/status-race-lock
416 $ wait
414 $ wait
417
415
418 the first update should be on disk
416 the first update should be on disk
419
417
420 $ hg debugstate --all | grep "g"
418 $ hg debugstate --all | grep "g"
419 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg dirstate-v1 !)
420 n 644 0 2000-01-01 00:25:00 g (rhg no-dirstate-v1 !)
421 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 !)
422 n 644 0 2000-01-01 00:25:00 g (missing-correct-output rhg !)
423 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg !)
424
422
425 The status process should return a consistent result and not crash.
423 The status process should return a consistent result and not crash.
426
424
427 $ cat $TESTTMP/status-race-lock.out
425 $ cat $TESTTMP/status-race-lock.out
428 A dir/o
426 A dir/o
429 R dir/nested/m
427 R dir/nested/m
430 ? dir/n
428 ? dir/n
431 ? p
429 ? p
432 ? q
430 ? q
433 $ cat $TESTTMP/status-race-lock.log
431 $ cat $TESTTMP/status-race-lock.log
434 abort: when removing $TESTTMP/race-with-status/.hg/dirstate.*: $ENOENT$ (glob) (known-bad-output rhg dirstate-v2-rewrite !)
435
432
436 final cleanup
433 final cleanup
437
434
438 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
435 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
439 $ cd ..
436 $ cd ..
440
437
441 Race with the removal of an ambiguous file
438 Race with the removal of an ambiguous file
442 ----------------------è-------------------
439 ----------------------è-------------------
443
440
444 $ cp -a reference-repo race-with-remove
441 $ cp -a reference-repo race-with-remove
445 $ cd race-with-remove
442 $ cd race-with-remove
446
443
447 spin a `hg status` with some caches to update
444 spin a `hg status` with some caches to update
448
445
449 $ touch -t 200001010035 dir2/l
446 $ touch -t 200001010035 dir2/l
450 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
447 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
451 > --config rhg.on-unsupported=abort \
448 > --config rhg.on-unsupported=abort \
452 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
449 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
453 > &
450 > &
454 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
451 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
455
452
456 remove that same file
453 remove that same file
457
454
458 $ hg $d2args remove dir2/l
455 $ hg $d2args remove dir2/l
459 $ touch $TESTTMP/status-race-lock
456 $ touch $TESTTMP/status-race-lock
460 $ wait
457 $ wait
461
458
462 file should be marked as removed
459 file should be marked as removed
463
460
464 $ hg status
461 $ hg status
465 A dir/o
462 A dir/o
466 R dir/nested/m
463 R dir/nested/m
467 R dir2/l
464 R dir2/l
468 ? dir/n
465 ? dir/n
469 ? p
466 ? p
470 ? q
467 ? q
471
468
472 The status process should return a consistent result and not crash.
469 The status process should return a consistent result and not crash.
473
470
474 $ cat $TESTTMP/status-race-lock.out
471 $ cat $TESTTMP/status-race-lock.out
475 A dir/o
472 A dir/o
476 R dir/nested/m
473 R dir/nested/m
477 ? dir/n
474 ? dir/n
478 ? p
475 ? p
479 ? q
476 ? q
480 $ cat $TESTTMP/status-race-lock.log
477 $ cat $TESTTMP/status-race-lock.log
481
478
482 final cleanup
479 final cleanup
483
480
484 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
481 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
485 $ cd ..
482 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now