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 ( |
|
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 |
|
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