##// END OF EJS Templates
rust-repo: don't use on-disk dirstate parents in v1...
Raphaël Gomès -
r52940:e1fe336c default
parent child Browse files
Show More
@@ -1,899 +1,902
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::requirements::{
11 use crate::requirements::{
12 CHANGELOGV2_REQUIREMENT, GENERALDELTA_REQUIREMENT, NODEMAP_REQUIREMENT,
12 CHANGELOGV2_REQUIREMENT, GENERALDELTA_REQUIREMENT, NODEMAP_REQUIREMENT,
13 REVLOGV1_REQUIREMENT, REVLOGV2_REQUIREMENT,
13 REVLOGV1_REQUIREMENT, REVLOGV2_REQUIREMENT,
14 };
14 };
15 use crate::revlog::filelog::Filelog;
15 use crate::revlog::filelog::Filelog;
16 use crate::revlog::RevlogError;
16 use crate::revlog::RevlogError;
17 use crate::utils::debug::debug_wait_for_file_or_print;
17 use crate::utils::debug::debug_wait_for_file_or_print;
18 use crate::utils::files::get_path_from_bytes;
18 use crate::utils::files::get_path_from_bytes;
19 use crate::utils::hg_path::HgPath;
19 use crate::utils::hg_path::HgPath;
20 use crate::utils::SliceExt;
20 use crate::utils::SliceExt;
21 use crate::vfs::{is_dir, is_file, VfsImpl};
21 use crate::vfs::{is_dir, is_file, VfsImpl};
22 use crate::{
22 use crate::{
23 exit_codes, requirements, NodePrefix, RevlogDataConfig, RevlogDeltaConfig,
23 exit_codes, requirements, NodePrefix, RevlogDataConfig, RevlogDeltaConfig,
24 RevlogFeatureConfig, RevlogType, RevlogVersionOptions, UncheckedRevision,
24 RevlogFeatureConfig, RevlogType, RevlogVersionOptions, UncheckedRevision,
25 };
25 };
26 use crate::{DirstateError, RevlogOpenOptions};
26 use crate::{DirstateError, RevlogOpenOptions};
27 use std::cell::{Ref, RefCell, RefMut};
27 use std::cell::{Ref, RefCell, RefMut};
28 use std::collections::HashSet;
28 use std::collections::HashSet;
29 use std::io::Seek;
29 use std::io::Seek;
30 use std::io::SeekFrom;
30 use std::io::SeekFrom;
31 use std::io::Write as IoWrite;
31 use std::io::Write as IoWrite;
32 use std::path::{Path, PathBuf};
32 use std::path::{Path, PathBuf};
33
33
34 const V2_MAX_READ_ATTEMPTS: usize = 5;
34 const V2_MAX_READ_ATTEMPTS: usize = 5;
35
35
36 type DirstateMapIdentity = (Option<u64>, Option<Vec<u8>>, usize);
36 type DirstateMapIdentity = (Option<u64>, Option<Vec<u8>>, usize);
37
37
38 /// A repository on disk
38 /// A repository on disk
39 pub struct Repo {
39 pub struct Repo {
40 working_directory: PathBuf,
40 working_directory: PathBuf,
41 dot_hg: PathBuf,
41 dot_hg: PathBuf,
42 store: PathBuf,
42 store: PathBuf,
43 requirements: HashSet<String>,
43 requirements: HashSet<String>,
44 config: Config,
44 config: Config,
45 dirstate_parents: LazyCell<DirstateParents>,
45 dirstate_parents: LazyCell<DirstateParents>,
46 dirstate_map: LazyCell<OwningDirstateMap>,
46 dirstate_map: LazyCell<OwningDirstateMap>,
47 changelog: LazyCell<Changelog>,
47 changelog: LazyCell<Changelog>,
48 manifestlog: LazyCell<Manifestlog>,
48 manifestlog: LazyCell<Manifestlog>,
49 }
49 }
50
50
51 #[derive(Debug, derive_more::From)]
51 #[derive(Debug, derive_more::From)]
52 pub enum RepoError {
52 pub enum RepoError {
53 NotFound {
53 NotFound {
54 at: PathBuf,
54 at: PathBuf,
55 },
55 },
56 #[from]
56 #[from]
57 ConfigParseError(ConfigParseError),
57 ConfigParseError(ConfigParseError),
58 #[from]
58 #[from]
59 Other(HgError),
59 Other(HgError),
60 }
60 }
61
61
62 impl From<ConfigError> for RepoError {
62 impl From<ConfigError> for RepoError {
63 fn from(error: ConfigError) -> Self {
63 fn from(error: ConfigError) -> Self {
64 match error {
64 match error {
65 ConfigError::Parse(error) => error.into(),
65 ConfigError::Parse(error) => error.into(),
66 ConfigError::Other(error) => error.into(),
66 ConfigError::Other(error) => error.into(),
67 }
67 }
68 }
68 }
69 }
69 }
70
70
71 impl From<RepoError> for HgError {
71 impl From<RepoError> for HgError {
72 fn from(value: RepoError) -> Self {
72 fn from(value: RepoError) -> Self {
73 match value {
73 match value {
74 RepoError::NotFound { at } => HgError::abort(
74 RepoError::NotFound { at } => HgError::abort(
75 format!(
75 format!(
76 "abort: no repository found in '{}' (.hg not found)!",
76 "abort: no repository found in '{}' (.hg not found)!",
77 at.display()
77 at.display()
78 ),
78 ),
79 exit_codes::ABORT,
79 exit_codes::ABORT,
80 None,
80 None,
81 ),
81 ),
82 RepoError::ConfigParseError(config_parse_error) => {
82 RepoError::ConfigParseError(config_parse_error) => {
83 HgError::Abort {
83 HgError::Abort {
84 message: String::from_utf8_lossy(
84 message: String::from_utf8_lossy(
85 &config_parse_error.message,
85 &config_parse_error.message,
86 )
86 )
87 .to_string(),
87 .to_string(),
88 detailed_exit_code: exit_codes::CONFIG_PARSE_ERROR_ABORT,
88 detailed_exit_code: exit_codes::CONFIG_PARSE_ERROR_ABORT,
89 hint: None,
89 hint: None,
90 }
90 }
91 }
91 }
92 RepoError::Other(hg_error) => hg_error,
92 RepoError::Other(hg_error) => hg_error,
93 }
93 }
94 }
94 }
95 }
95 }
96
96
97 impl Repo {
97 impl Repo {
98 /// tries to find nearest repository root in current working directory or
98 /// tries to find nearest repository root in current working directory or
99 /// its ancestors
99 /// its ancestors
100 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
100 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
101 let current_directory = crate::utils::current_dir()?;
101 let current_directory = crate::utils::current_dir()?;
102 // ancestors() is inclusive: it first yields `current_directory`
102 // ancestors() is inclusive: it first yields `current_directory`
103 // as-is.
103 // as-is.
104 for ancestor in current_directory.ancestors() {
104 for ancestor in current_directory.ancestors() {
105 if is_dir(ancestor.join(".hg"))? {
105 if is_dir(ancestor.join(".hg"))? {
106 return Ok(ancestor.to_path_buf());
106 return Ok(ancestor.to_path_buf());
107 }
107 }
108 }
108 }
109 Err(RepoError::NotFound {
109 Err(RepoError::NotFound {
110 at: current_directory,
110 at: current_directory,
111 })
111 })
112 }
112 }
113
113
114 /// Find a repository, either at the given path (which must contain a `.hg`
114 /// Find a repository, either at the given path (which must contain a `.hg`
115 /// sub-directory) or by searching the current directory and its
115 /// sub-directory) or by searching the current directory and its
116 /// ancestors.
116 /// ancestors.
117 ///
117 ///
118 /// A method with two very different "modes" like this usually a code smell
118 /// A method with two very different "modes" like this usually a code smell
119 /// to make two methods instead, but in this case an `Option` is what rhg
119 /// to make two methods instead, but in this case an `Option` is what rhg
120 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
120 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
121 /// Having two methods would just move that `if` to almost all callers.
121 /// Having two methods would just move that `if` to almost all callers.
122 pub fn find(
122 pub fn find(
123 config: &Config,
123 config: &Config,
124 explicit_path: Option<PathBuf>,
124 explicit_path: Option<PathBuf>,
125 ) -> Result<Self, RepoError> {
125 ) -> Result<Self, RepoError> {
126 if let Some(root) = explicit_path {
126 if let Some(root) = explicit_path {
127 if is_dir(root.join(".hg"))? {
127 if is_dir(root.join(".hg"))? {
128 Self::new_at_path(root, config)
128 Self::new_at_path(root, config)
129 } else if is_file(&root)? {
129 } else if is_file(&root)? {
130 Err(HgError::unsupported("bundle repository").into())
130 Err(HgError::unsupported("bundle repository").into())
131 } else {
131 } else {
132 Err(RepoError::NotFound { at: root })
132 Err(RepoError::NotFound { at: root })
133 }
133 }
134 } else {
134 } else {
135 let root = Self::find_repo_root()?;
135 let root = Self::find_repo_root()?;
136 Self::new_at_path(root, config)
136 Self::new_at_path(root, config)
137 }
137 }
138 }
138 }
139
139
140 /// To be called after checking that `.hg` is a sub-directory
140 /// To be called after checking that `.hg` is a sub-directory
141 fn new_at_path(
141 fn new_at_path(
142 working_directory: PathBuf,
142 working_directory: PathBuf,
143 config: &Config,
143 config: &Config,
144 ) -> Result<Self, RepoError> {
144 ) -> Result<Self, RepoError> {
145 let dot_hg = working_directory.join(".hg");
145 let dot_hg = working_directory.join(".hg");
146
146
147 let mut repo_config_files =
147 let mut repo_config_files =
148 vec![dot_hg.join("hgrc"), dot_hg.join("hgrc-not-shared")];
148 vec![dot_hg.join("hgrc"), dot_hg.join("hgrc-not-shared")];
149
149
150 let hg_vfs = VfsImpl {
150 let hg_vfs = VfsImpl {
151 base: dot_hg.to_owned(),
151 base: dot_hg.to_owned(),
152 };
152 };
153 let mut reqs = requirements::load_if_exists(&hg_vfs)?;
153 let mut reqs = requirements::load_if_exists(&hg_vfs)?;
154 let relative =
154 let relative =
155 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
155 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
156 let shared =
156 let shared =
157 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
157 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
158
158
159 // From `mercurial/localrepo.py`:
159 // From `mercurial/localrepo.py`:
160 //
160 //
161 // if .hg/requires contains the sharesafe requirement, it means
161 // if .hg/requires contains the sharesafe requirement, it means
162 // there exists a `.hg/store/requires` too and we should read it
162 // there exists a `.hg/store/requires` too and we should read it
163 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
163 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
164 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
164 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
165 // is not present, refer checkrequirementscompat() for that
165 // is not present, refer checkrequirementscompat() for that
166 //
166 //
167 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
167 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
168 // repository was shared the old way. We check the share source
168 // repository was shared the old way. We check the share source
169 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
169 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
170 // current repository needs to be reshared
170 // current repository needs to be reshared
171 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
171 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
172
172
173 let store_path;
173 let store_path;
174 if !shared {
174 if !shared {
175 store_path = dot_hg.join("store");
175 store_path = dot_hg.join("store");
176 } else {
176 } else {
177 let bytes = hg_vfs.read("sharedpath")?;
177 let bytes = hg_vfs.read("sharedpath")?;
178 let mut shared_path =
178 let mut shared_path =
179 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
179 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
180 .to_owned();
180 .to_owned();
181 if relative {
181 if relative {
182 shared_path = dot_hg.join(shared_path)
182 shared_path = dot_hg.join(shared_path)
183 }
183 }
184 if !is_dir(&shared_path)? {
184 if !is_dir(&shared_path)? {
185 return Err(HgError::corrupted(format!(
185 return Err(HgError::corrupted(format!(
186 ".hg/sharedpath points to nonexistent directory {}",
186 ".hg/sharedpath points to nonexistent directory {}",
187 shared_path.display()
187 shared_path.display()
188 ))
188 ))
189 .into());
189 .into());
190 }
190 }
191
191
192 store_path = shared_path.join("store");
192 store_path = shared_path.join("store");
193
193
194 let source_is_share_safe = requirements::load(VfsImpl {
194 let source_is_share_safe = requirements::load(VfsImpl {
195 base: shared_path.to_owned(),
195 base: shared_path.to_owned(),
196 })?
196 })?
197 .contains(requirements::SHARESAFE_REQUIREMENT);
197 .contains(requirements::SHARESAFE_REQUIREMENT);
198
198
199 if share_safe != source_is_share_safe {
199 if share_safe != source_is_share_safe {
200 return Err(HgError::unsupported("share-safe mismatch").into());
200 return Err(HgError::unsupported("share-safe mismatch").into());
201 }
201 }
202
202
203 if share_safe {
203 if share_safe {
204 repo_config_files.insert(0, shared_path.join("hgrc"))
204 repo_config_files.insert(0, shared_path.join("hgrc"))
205 }
205 }
206 }
206 }
207 if share_safe {
207 if share_safe {
208 reqs.extend(requirements::load(VfsImpl {
208 reqs.extend(requirements::load(VfsImpl {
209 base: store_path.to_owned(),
209 base: store_path.to_owned(),
210 })?);
210 })?);
211 }
211 }
212
212
213 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
213 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
214 config.combine_with_repo(&repo_config_files)?
214 config.combine_with_repo(&repo_config_files)?
215 } else {
215 } else {
216 config.clone()
216 config.clone()
217 };
217 };
218
218
219 let repo = Self {
219 let repo = Self {
220 requirements: reqs,
220 requirements: reqs,
221 working_directory,
221 working_directory,
222 store: store_path,
222 store: store_path,
223 dot_hg,
223 dot_hg,
224 config: repo_config,
224 config: repo_config,
225 dirstate_parents: LazyCell::new(),
225 dirstate_parents: LazyCell::new(),
226 dirstate_map: LazyCell::new(),
226 dirstate_map: LazyCell::new(),
227 changelog: LazyCell::new(),
227 changelog: LazyCell::new(),
228 manifestlog: LazyCell::new(),
228 manifestlog: LazyCell::new(),
229 };
229 };
230
230
231 requirements::check(&repo)?;
231 requirements::check(&repo)?;
232
232
233 Ok(repo)
233 Ok(repo)
234 }
234 }
235
235
236 pub fn working_directory_path(&self) -> &Path {
236 pub fn working_directory_path(&self) -> &Path {
237 &self.working_directory
237 &self.working_directory
238 }
238 }
239
239
240 pub fn requirements(&self) -> &HashSet<String> {
240 pub fn requirements(&self) -> &HashSet<String> {
241 &self.requirements
241 &self.requirements
242 }
242 }
243
243
244 pub fn config(&self) -> &Config {
244 pub fn config(&self) -> &Config {
245 &self.config
245 &self.config
246 }
246 }
247
247
248 /// For accessing repository files (in `.hg`), except for the store
248 /// For accessing repository files (in `.hg`), except for the store
249 /// (`.hg/store`).
249 /// (`.hg/store`).
250 pub fn hg_vfs(&self) -> VfsImpl {
250 pub fn hg_vfs(&self) -> VfsImpl {
251 VfsImpl {
251 VfsImpl {
252 base: self.dot_hg.to_owned(),
252 base: self.dot_hg.to_owned(),
253 }
253 }
254 }
254 }
255
255
256 /// For accessing repository store files (in `.hg/store`)
256 /// For accessing repository store files (in `.hg/store`)
257 pub fn store_vfs(&self) -> VfsImpl {
257 pub fn store_vfs(&self) -> VfsImpl {
258 VfsImpl {
258 VfsImpl {
259 base: self.store.to_owned(),
259 base: self.store.to_owned(),
260 }
260 }
261 }
261 }
262
262
263 /// For accessing the working copy
263 /// For accessing the working copy
264 pub fn working_directory_vfs(&self) -> VfsImpl {
264 pub fn working_directory_vfs(&self) -> VfsImpl {
265 VfsImpl {
265 VfsImpl {
266 base: self.working_directory.to_owned(),
266 base: self.working_directory.to_owned(),
267 }
267 }
268 }
268 }
269
269
270 pub fn try_with_wlock_no_wait<R>(
270 pub fn try_with_wlock_no_wait<R>(
271 &self,
271 &self,
272 f: impl FnOnce() -> R,
272 f: impl FnOnce() -> R,
273 ) -> Result<R, LockError> {
273 ) -> Result<R, LockError> {
274 try_with_lock_no_wait(&self.hg_vfs(), "wlock", f)
274 try_with_lock_no_wait(&self.hg_vfs(), "wlock", f)
275 }
275 }
276
276
277 /// Whether this repo should use dirstate-v2.
277 /// Whether this repo should use dirstate-v2.
278 /// The presence of `dirstate-v2` in the requirements does not mean that
278 /// The presence of `dirstate-v2` in the requirements does not mean that
279 /// the on-disk dirstate is necessarily in version 2. In most cases,
279 /// the on-disk dirstate is necessarily in version 2. In most cases,
280 /// a dirstate-v2 file will indeed be found, but in rare cases (like the
280 /// a dirstate-v2 file will indeed be found, but in rare cases (like the
281 /// upgrade mechanism being cut short), the on-disk version will be a
281 /// upgrade mechanism being cut short), the on-disk version will be a
282 /// v1 file.
282 /// v1 file.
283 /// Semantically, having a requirement only means that a client cannot
283 /// Semantically, having a requirement only means that a client cannot
284 /// properly understand or properly update the repo if it lacks the support
284 /// properly understand or properly update the repo if it lacks the support
285 /// for the required feature, but not that that feature is actually used
285 /// for the required feature, but not that that feature is actually used
286 /// in all occasions.
286 /// in all occasions.
287 pub fn use_dirstate_v2(&self) -> bool {
287 pub fn use_dirstate_v2(&self) -> bool {
288 self.requirements
288 self.requirements
289 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
289 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
290 }
290 }
291
291
292 pub fn has_sparse(&self) -> bool {
292 pub fn has_sparse(&self) -> bool {
293 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
293 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
294 }
294 }
295
295
296 pub fn has_narrow(&self) -> bool {
296 pub fn has_narrow(&self) -> bool {
297 self.requirements.contains(requirements::NARROW_REQUIREMENT)
297 self.requirements.contains(requirements::NARROW_REQUIREMENT)
298 }
298 }
299
299
300 pub fn has_nodemap(&self) -> bool {
300 pub fn has_nodemap(&self) -> bool {
301 self.requirements
301 self.requirements
302 .contains(requirements::NODEMAP_REQUIREMENT)
302 .contains(requirements::NODEMAP_REQUIREMENT)
303 }
303 }
304
304
305 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
305 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
306 Ok(self
306 Ok(self
307 .hg_vfs()
307 .hg_vfs()
308 .read("dirstate")
308 .read("dirstate")
309 .io_not_found_as_none()?
309 .io_not_found_as_none()?
310 .unwrap_or_default())
310 .unwrap_or_default())
311 }
311 }
312
312
313 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
313 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
314 use std::os::unix::fs::MetadataExt;
314 use std::os::unix::fs::MetadataExt;
315 Ok(self
315 Ok(self
316 .hg_vfs()
316 .hg_vfs()
317 .symlink_metadata("dirstate")
317 .symlink_metadata("dirstate")
318 .io_not_found_as_none()?
318 .io_not_found_as_none()?
319 .map(|meta| meta.ino()))
319 .map(|meta| meta.ino()))
320 }
320 }
321
321
322 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
322 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
323 Ok(*self
323 Ok(*self
324 .dirstate_parents
324 .dirstate_parents
325 .get_or_init(|| self.read_dirstate_parents())?)
325 .get_or_init(|| self.read_dirstate_parents())?)
326 }
326 }
327
327
328 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
328 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
329 let dirstate = self.dirstate_file_contents()?;
329 let dirstate = self.dirstate_file_contents()?;
330 let parents = if dirstate.is_empty() {
330 let parents = if dirstate.is_empty() {
331 DirstateParents::NULL
331 DirstateParents::NULL
332 } else if self.use_dirstate_v2() {
332 } else if self.use_dirstate_v2() {
333 let docket_res =
333 let docket_res =
334 crate::dirstate_tree::on_disk::read_docket(&dirstate);
334 crate::dirstate_tree::on_disk::read_docket(&dirstate);
335 match docket_res {
335 match docket_res {
336 Ok(docket) => docket.parents(),
336 Ok(docket) => docket.parents(),
337 Err(_) => {
337 Err(_) => {
338 log::info!(
338 log::info!(
339 "Parsing dirstate docket failed, \
339 "Parsing dirstate docket failed, \
340 falling back to dirstate-v1"
340 falling back to dirstate-v1"
341 );
341 );
342 *crate::dirstate::parsers::parse_dirstate_parents(
342 *crate::dirstate::parsers::parse_dirstate_parents(
343 &dirstate,
343 &dirstate,
344 )?
344 )?
345 }
345 }
346 }
346 }
347 } else {
347 } else {
348 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
348 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
349 };
349 };
350 self.dirstate_parents.set(parents);
350 self.dirstate_parents.set(parents);
351 Ok(parents)
351 Ok(parents)
352 }
352 }
353
353
354 /// Returns the information read from the dirstate docket necessary to
354 /// Returns the information read from the dirstate docket necessary to
355 /// check if the data file has been updated/deleted by another process
355 /// check if the data file has been updated/deleted by another process
356 /// since we last read the dirstate.
356 /// since we last read the dirstate.
357 /// Namely, the inode, data file uuid and the data size.
357 /// Namely, the inode, data file uuid and the data size.
358 fn get_dirstate_data_file_integrity(
358 fn get_dirstate_data_file_integrity(
359 &self,
359 &self,
360 ) -> Result<DirstateMapIdentity, HgError> {
360 ) -> Result<DirstateMapIdentity, HgError> {
361 assert!(
361 assert!(
362 self.use_dirstate_v2(),
362 self.use_dirstate_v2(),
363 "accessing dirstate data file ID without dirstate-v2"
363 "accessing dirstate data file ID without dirstate-v2"
364 );
364 );
365 // Get the identity before the contents since we could have a race
365 // Get the identity before the contents since we could have a race
366 // between the two. Having an identity that is too old is fine, but
366 // between the two. Having an identity that is too old is fine, but
367 // one that is younger than the content change is bad.
367 // one that is younger than the content change is bad.
368 let identity = self.dirstate_identity()?;
368 let identity = self.dirstate_identity()?;
369 let dirstate = self.dirstate_file_contents()?;
369 let dirstate = self.dirstate_file_contents()?;
370 if dirstate.is_empty() {
370 if dirstate.is_empty() {
371 Ok((identity, None, 0))
371 Ok((identity, None, 0))
372 } else {
372 } else {
373 let docket_res =
373 let docket_res =
374 crate::dirstate_tree::on_disk::read_docket(&dirstate);
374 crate::dirstate_tree::on_disk::read_docket(&dirstate);
375 match docket_res {
375 match docket_res {
376 Ok(docket) => {
376 Ok(docket) => {
377 self.dirstate_parents.set(docket.parents());
377 self.dirstate_parents.set(docket.parents());
378 Ok((
378 Ok((
379 identity,
379 identity,
380 Some(docket.uuid.to_owned()),
380 Some(docket.uuid.to_owned()),
381 docket.data_size(),
381 docket.data_size(),
382 ))
382 ))
383 }
383 }
384 Err(_) => {
384 Err(_) => {
385 log::info!(
385 log::info!(
386 "Parsing dirstate docket failed, \
386 "Parsing dirstate docket failed, \
387 falling back to dirstate-v1"
387 falling back to dirstate-v1"
388 );
388 );
389 let parents =
389 let parents =
390 *crate::dirstate::parsers::parse_dirstate_parents(
390 *crate::dirstate::parsers::parse_dirstate_parents(
391 &dirstate,
391 &dirstate,
392 )?;
392 )?;
393 self.dirstate_parents.set(parents);
393 self.dirstate_parents.set(parents);
394 Ok((identity, None, 0))
394 Ok((identity, None, 0))
395 }
395 }
396 }
396 }
397 }
397 }
398 }
398 }
399
399
400 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
400 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
401 if self.use_dirstate_v2() {
401 if self.use_dirstate_v2() {
402 // The v2 dirstate is split into a docket and a data file.
402 // The v2 dirstate is split into a docket and a data file.
403 // Since we don't always take the `wlock` to read it
403 // Since we don't always take the `wlock` to read it
404 // (like in `hg status`), it is susceptible to races.
404 // (like in `hg status`), it is susceptible to races.
405 // A simple retry method should be enough since full rewrites
405 // A simple retry method should be enough since full rewrites
406 // only happen when too much garbage data is present and
406 // only happen when too much garbage data is present and
407 // this race is unlikely.
407 // this race is unlikely.
408 let mut tries = 0;
408 let mut tries = 0;
409
409
410 while tries < V2_MAX_READ_ATTEMPTS {
410 while tries < V2_MAX_READ_ATTEMPTS {
411 tries += 1;
411 tries += 1;
412 match self.read_docket_and_data_file() {
412 match self.read_docket_and_data_file() {
413 Ok(m) => {
413 Ok(m) => {
414 return Ok(m);
414 return Ok(m);
415 }
415 }
416 Err(e) => match e {
416 Err(e) => match e {
417 DirstateError::Common(HgError::RaceDetected(
417 DirstateError::Common(HgError::RaceDetected(
418 context,
418 context,
419 )) => {
419 )) => {
420 log::info!(
420 log::info!(
421 "dirstate read race detected {} (retry {}/{})",
421 "dirstate read race detected {} (retry {}/{})",
422 context,
422 context,
423 tries,
423 tries,
424 V2_MAX_READ_ATTEMPTS,
424 V2_MAX_READ_ATTEMPTS,
425 );
425 );
426 continue;
426 continue;
427 }
427 }
428 _ => {
428 _ => {
429 log::info!(
429 log::info!(
430 "Reading dirstate v2 failed, \
430 "Reading dirstate v2 failed, \
431 falling back to v1"
431 falling back to v1"
432 );
432 );
433 return self.new_dirstate_map_v1();
433 return self.new_dirstate_map_v1();
434 }
434 }
435 },
435 },
436 }
436 }
437 }
437 }
438 let error = HgError::abort(
438 let error = HgError::abort(
439 format!("dirstate read race happened {tries} times in a row"),
439 format!("dirstate read race happened {tries} times in a row"),
440 255,
440 255,
441 None,
441 None,
442 );
442 );
443 Err(DirstateError::Common(error))
443 Err(DirstateError::Common(error))
444 } else {
444 } else {
445 self.new_dirstate_map_v1()
445 self.new_dirstate_map_v1()
446 }
446 }
447 }
447 }
448
448
449 fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> {
449 fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> {
450 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
450 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
451 let identity = self.dirstate_identity()?;
451 let identity = self.dirstate_identity()?;
452 let dirstate_file_contents = self.dirstate_file_contents()?;
452 let dirstate_file_contents = self.dirstate_file_contents()?;
453 let parents = self.dirstate_parents()?;
453 if dirstate_file_contents.is_empty() {
454 if dirstate_file_contents.is_empty() {
454 self.dirstate_parents.set(DirstateParents::NULL);
455 self.dirstate_parents.set(parents);
455 Ok(OwningDirstateMap::new_empty(Vec::new(), identity))
456 Ok(OwningDirstateMap::new_empty(Vec::new(), identity))
456 } else {
457 } else {
457 let (map, parents) =
458 // Ignore the dirstate on-disk parents, they may have been set in
459 // the repo before
460 let (map, _) =
458 OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
461 OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
459 self.dirstate_parents.set(parents);
462 self.dirstate_parents.set(parents);
460 Ok(map)
463 Ok(map)
461 }
464 }
462 }
465 }
463
466
464 fn read_docket_and_data_file(
467 fn read_docket_and_data_file(
465 &self,
468 &self,
466 ) -> Result<OwningDirstateMap, DirstateError> {
469 ) -> Result<OwningDirstateMap, DirstateError> {
467 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
470 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
468 let dirstate_file_contents = self.dirstate_file_contents()?;
471 let dirstate_file_contents = self.dirstate_file_contents()?;
469 let identity = self.dirstate_identity()?;
472 let identity = self.dirstate_identity()?;
470 if dirstate_file_contents.is_empty() {
473 if dirstate_file_contents.is_empty() {
471 return Ok(OwningDirstateMap::new_empty(Vec::new(), identity));
474 return Ok(OwningDirstateMap::new_empty(Vec::new(), identity));
472 }
475 }
473 let docket = crate::dirstate_tree::on_disk::read_docket(
476 let docket = crate::dirstate_tree::on_disk::read_docket(
474 &dirstate_file_contents,
477 &dirstate_file_contents,
475 )?;
478 )?;
476 debug_wait_for_file_or_print(
479 debug_wait_for_file_or_print(
477 self.config(),
480 self.config(),
478 "dirstate.post-docket-read-file",
481 "dirstate.post-docket-read-file",
479 );
482 );
480 self.dirstate_parents.set(docket.parents());
483 self.dirstate_parents.set(docket.parents());
481 let uuid = docket.uuid.to_owned();
484 let uuid = docket.uuid.to_owned();
482 let data_size = docket.data_size();
485 let data_size = docket.data_size();
483
486
484 let context = "between reading dirstate docket and data file";
487 let context = "between reading dirstate docket and data file";
485 let race_error = HgError::RaceDetected(context.into());
488 let race_error = HgError::RaceDetected(context.into());
486 let metadata = docket.tree_metadata();
489 let metadata = docket.tree_metadata();
487
490
488 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
491 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
489 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
492 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
490 let contents = self.hg_vfs().read(docket.data_filename());
493 let contents = self.hg_vfs().read(docket.data_filename());
491 let contents = match contents {
494 let contents = match contents {
492 Ok(c) => c,
495 Ok(c) => c,
493 Err(HgError::IoError { error, context }) => {
496 Err(HgError::IoError { error, context }) => {
494 match error.raw_os_error().expect("real os error") {
497 match error.raw_os_error().expect("real os error") {
495 // 2 = ENOENT, No such file or directory
498 // 2 = ENOENT, No such file or directory
496 // 116 = ESTALE, Stale NFS file handle
499 // 116 = ESTALE, Stale NFS file handle
497 //
500 //
498 // TODO match on `error.kind()` when
501 // TODO match on `error.kind()` when
499 // `ErrorKind::StaleNetworkFileHandle` is stable.
502 // `ErrorKind::StaleNetworkFileHandle` is stable.
500 2 | 116 => {
503 2 | 116 => {
501 // Race where the data file was deleted right after
504 // Race where the data file was deleted right after
502 // we read the docket, try again
505 // we read the docket, try again
503 return Err(race_error.into());
506 return Err(race_error.into());
504 }
507 }
505 _ => {
508 _ => {
506 return Err(
509 return Err(
507 HgError::IoError { error, context }.into()
510 HgError::IoError { error, context }.into()
508 )
511 )
509 }
512 }
510 }
513 }
511 }
514 }
512 Err(e) => return Err(e.into()),
515 Err(e) => return Err(e.into()),
513 };
516 };
514 OwningDirstateMap::new_v2(
517 OwningDirstateMap::new_v2(
515 contents, data_size, metadata, uuid, identity,
518 contents, data_size, metadata, uuid, identity,
516 )
519 )
517 } else {
520 } else {
518 match self
521 match self
519 .hg_vfs()
522 .hg_vfs()
520 .mmap_open(docket.data_filename())
523 .mmap_open(docket.data_filename())
521 .io_not_found_as_none()
524 .io_not_found_as_none()
522 {
525 {
523 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
526 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
524 data_mmap, data_size, metadata, uuid, identity,
527 data_mmap, data_size, metadata, uuid, identity,
525 ),
528 ),
526 Ok(None) => {
529 Ok(None) => {
527 // Race where the data file was deleted right after we
530 // Race where the data file was deleted right after we
528 // read the docket, try again
531 // read the docket, try again
529 return Err(race_error.into());
532 return Err(race_error.into());
530 }
533 }
531 Err(e) => return Err(e.into()),
534 Err(e) => return Err(e.into()),
532 }
535 }
533 }?;
536 }?;
534
537
535 let write_mode_config = self
538 let write_mode_config = self
536 .config()
539 .config()
537 .get_str(b"devel", b"dirstate.v2.data_update_mode")
540 .get_str(b"devel", b"dirstate.v2.data_update_mode")
538 .unwrap_or(Some("auto"))
541 .unwrap_or(Some("auto"))
539 .unwrap_or("auto"); // don't bother for devel options
542 .unwrap_or("auto"); // don't bother for devel options
540 let write_mode = match write_mode_config {
543 let write_mode = match write_mode_config {
541 "auto" => DirstateMapWriteMode::Auto,
544 "auto" => DirstateMapWriteMode::Auto,
542 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
545 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
543 "force-append" => DirstateMapWriteMode::ForceAppend,
546 "force-append" => DirstateMapWriteMode::ForceAppend,
544 _ => DirstateMapWriteMode::Auto,
547 _ => DirstateMapWriteMode::Auto,
545 };
548 };
546
549
547 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
550 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
548
551
549 Ok(map)
552 Ok(map)
550 }
553 }
551
554
552 pub fn dirstate_map(
555 pub fn dirstate_map(
553 &self,
556 &self,
554 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
557 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
555 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
558 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
556 }
559 }
557
560
558 pub fn dirstate_map_mut(
561 pub fn dirstate_map_mut(
559 &self,
562 &self,
560 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
563 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
561 self.dirstate_map
564 self.dirstate_map
562 .get_mut_or_init(|| self.new_dirstate_map())
565 .get_mut_or_init(|| self.new_dirstate_map())
563 }
566 }
564
567
565 fn new_changelog(&self) -> Result<Changelog, HgError> {
568 fn new_changelog(&self) -> Result<Changelog, HgError> {
566 Changelog::open(
569 Changelog::open(
567 &self.store_vfs(),
570 &self.store_vfs(),
568 self.default_revlog_options(RevlogType::Changelog)?,
571 self.default_revlog_options(RevlogType::Changelog)?,
569 )
572 )
570 }
573 }
571
574
572 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
575 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
573 self.changelog.get_or_init(|| self.new_changelog())
576 self.changelog.get_or_init(|| self.new_changelog())
574 }
577 }
575
578
576 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
579 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
577 self.changelog.get_mut_or_init(|| self.new_changelog())
580 self.changelog.get_mut_or_init(|| self.new_changelog())
578 }
581 }
579
582
580 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
583 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
581 Manifestlog::open(
584 Manifestlog::open(
582 &self.store_vfs(),
585 &self.store_vfs(),
583 self.default_revlog_options(RevlogType::Manifestlog)?,
586 self.default_revlog_options(RevlogType::Manifestlog)?,
584 )
587 )
585 }
588 }
586
589
587 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
590 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
588 self.manifestlog.get_or_init(|| self.new_manifestlog())
591 self.manifestlog.get_or_init(|| self.new_manifestlog())
589 }
592 }
590
593
591 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
594 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
592 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
595 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
593 }
596 }
594
597
595 /// Returns the manifest of the *changeset* with the given node ID
598 /// Returns the manifest of the *changeset* with the given node ID
596 pub fn manifest_for_node(
599 pub fn manifest_for_node(
597 &self,
600 &self,
598 node: impl Into<NodePrefix>,
601 node: impl Into<NodePrefix>,
599 ) -> Result<Manifest, RevlogError> {
602 ) -> Result<Manifest, RevlogError> {
600 self.manifestlog()?.data_for_node(
603 self.manifestlog()?.data_for_node(
601 self.changelog()?
604 self.changelog()?
602 .data_for_node(node.into())?
605 .data_for_node(node.into())?
603 .manifest_node()?
606 .manifest_node()?
604 .into(),
607 .into(),
605 )
608 )
606 }
609 }
607
610
608 /// Returns the manifest of the *changeset* with the given revision number
611 /// Returns the manifest of the *changeset* with the given revision number
609 pub fn manifest_for_rev(
612 pub fn manifest_for_rev(
610 &self,
613 &self,
611 revision: UncheckedRevision,
614 revision: UncheckedRevision,
612 ) -> Result<Manifest, RevlogError> {
615 ) -> Result<Manifest, RevlogError> {
613 self.manifestlog()?.data_for_node(
616 self.manifestlog()?.data_for_node(
614 self.changelog()?
617 self.changelog()?
615 .data_for_rev(revision)?
618 .data_for_rev(revision)?
616 .manifest_node()?
619 .manifest_node()?
617 .into(),
620 .into(),
618 )
621 )
619 }
622 }
620
623
621 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
624 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
622 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
625 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
623 Ok(entry.tracked())
626 Ok(entry.tracked())
624 } else {
627 } else {
625 Ok(false)
628 Ok(false)
626 }
629 }
627 }
630 }
628
631
629 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
632 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
630 Filelog::open(
633 Filelog::open(
631 self,
634 self,
632 path,
635 path,
633 self.default_revlog_options(RevlogType::Filelog)?,
636 self.default_revlog_options(RevlogType::Filelog)?,
634 )
637 )
635 }
638 }
636 /// Write to disk any updates that were made through `dirstate_map_mut`.
639 /// Write to disk any updates that were made through `dirstate_map_mut`.
637 ///
640 ///
638 /// The "wlock" must be held while calling this.
641 /// The "wlock" must be held while calling this.
639 /// See for example `try_with_wlock_no_wait`.
642 /// See for example `try_with_wlock_no_wait`.
640 ///
643 ///
641 /// TODO: have a `WritableRepo` type only accessible while holding the
644 /// TODO: have a `WritableRepo` type only accessible while holding the
642 /// lock?
645 /// lock?
643 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
646 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
644 let map = self.dirstate_map()?;
647 let map = self.dirstate_map()?;
645 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
648 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
646 // it’s unset
649 // it’s unset
647 let parents = self.dirstate_parents()?;
650 let parents = self.dirstate_parents()?;
648 let (packed_dirstate, old_uuid_to_remove) = if self.use_dirstate_v2() {
651 let (packed_dirstate, old_uuid_to_remove) = if self.use_dirstate_v2() {
649 let (identity, uuid, data_size) =
652 let (identity, uuid, data_size) =
650 self.get_dirstate_data_file_integrity()?;
653 self.get_dirstate_data_file_integrity()?;
651 let identity_changed = identity != map.old_identity();
654 let identity_changed = identity != map.old_identity();
652 let uuid_changed = uuid.as_deref() != map.old_uuid();
655 let uuid_changed = uuid.as_deref() != map.old_uuid();
653 let data_length_changed = data_size != map.old_data_size();
656 let data_length_changed = data_size != map.old_data_size();
654
657
655 if identity_changed || uuid_changed || data_length_changed {
658 if identity_changed || uuid_changed || data_length_changed {
656 // If any of identity, uuid or length have changed since
659 // If any of identity, uuid or length have changed since
657 // last disk read, don't write.
660 // last disk read, don't write.
658 // This is fine because either we're in a command that doesn't
661 // This is fine because either we're in a command that doesn't
659 // write anything too important (like `hg status`), or we're in
662 // write anything too important (like `hg status`), or we're in
660 // `hg add` and we're supposed to have taken the lock before
663 // `hg add` and we're supposed to have taken the lock before
661 // reading anyway.
664 // reading anyway.
662 //
665 //
663 // TODO complain loudly if we've changed anything important
666 // TODO complain loudly if we've changed anything important
664 // without taking the lock.
667 // without taking the lock.
665 // (see `hg help config.format.use-dirstate-tracked-hint`)
668 // (see `hg help config.format.use-dirstate-tracked-hint`)
666 log::debug!(
669 log::debug!(
667 "dirstate has changed since last read, not updating."
670 "dirstate has changed since last read, not updating."
668 );
671 );
669 return Ok(());
672 return Ok(());
670 }
673 }
671
674
672 let uuid_opt = map.old_uuid();
675 let uuid_opt = map.old_uuid();
673 let write_mode = if uuid_opt.is_some() {
676 let write_mode = if uuid_opt.is_some() {
674 DirstateMapWriteMode::Auto
677 DirstateMapWriteMode::Auto
675 } else {
678 } else {
676 DirstateMapWriteMode::ForceNewDataFile
679 DirstateMapWriteMode::ForceNewDataFile
677 };
680 };
678 let (data, tree_metadata, append, old_data_size) =
681 let (data, tree_metadata, append, old_data_size) =
679 map.pack_v2(write_mode)?;
682 map.pack_v2(write_mode)?;
680
683
681 // Reuse the uuid, or generate a new one, keeping the old for
684 // Reuse the uuid, or generate a new one, keeping the old for
682 // deletion.
685 // deletion.
683 let (uuid, old_uuid) = match uuid_opt {
686 let (uuid, old_uuid) = match uuid_opt {
684 Some(uuid) => {
687 Some(uuid) => {
685 let as_str = std::str::from_utf8(uuid)
688 let as_str = std::str::from_utf8(uuid)
686 .map_err(|_| {
689 .map_err(|_| {
687 HgError::corrupted(
690 HgError::corrupted(
688 "non-UTF-8 dirstate data file ID",
691 "non-UTF-8 dirstate data file ID",
689 )
692 )
690 })?
693 })?
691 .to_owned();
694 .to_owned();
692 if append {
695 if append {
693 (as_str, None)
696 (as_str, None)
694 } else {
697 } else {
695 (DirstateDocket::new_uid(), Some(as_str))
698 (DirstateDocket::new_uid(), Some(as_str))
696 }
699 }
697 }
700 }
698 None => (DirstateDocket::new_uid(), None),
701 None => (DirstateDocket::new_uid(), None),
699 };
702 };
700
703
701 let data_filename = format!("dirstate.{}", uuid);
704 let data_filename = format!("dirstate.{}", uuid);
702 let data_filename = self.hg_vfs().join(data_filename);
705 let data_filename = self.hg_vfs().join(data_filename);
703 let mut options = std::fs::OpenOptions::new();
706 let mut options = std::fs::OpenOptions::new();
704 options.write(true);
707 options.write(true);
705
708
706 // Why are we not using the O_APPEND flag when appending?
709 // Why are we not using the O_APPEND flag when appending?
707 //
710 //
708 // - O_APPEND makes it trickier to deal with garbage at the end of
711 // - O_APPEND makes it trickier to deal with garbage at the end of
709 // the file, left by a previous uncommitted transaction. By
712 // the file, left by a previous uncommitted transaction. By
710 // starting the write at [old_data_size] we make sure we erase
713 // starting the write at [old_data_size] we make sure we erase
711 // all such garbage.
714 // all such garbage.
712 //
715 //
713 // - O_APPEND requires to special-case 0-byte writes, whereas we
716 // - O_APPEND requires to special-case 0-byte writes, whereas we
714 // don't need that.
717 // don't need that.
715 //
718 //
716 // - Some OSes have bugs in implementation O_APPEND:
719 // - Some OSes have bugs in implementation O_APPEND:
717 // revlog.py talks about a Solaris bug, but we also saw some ZFS
720 // revlog.py talks about a Solaris bug, but we also saw some ZFS
718 // bug: https://github.com/openzfs/zfs/pull/3124,
721 // bug: https://github.com/openzfs/zfs/pull/3124,
719 // https://github.com/openzfs/zfs/issues/13370
722 // https://github.com/openzfs/zfs/issues/13370
720 //
723 //
721 if !append {
724 if !append {
722 log::trace!("creating a new dirstate data file");
725 log::trace!("creating a new dirstate data file");
723 options.create_new(true);
726 options.create_new(true);
724 } else {
727 } else {
725 log::trace!("appending to the dirstate data file");
728 log::trace!("appending to the dirstate data file");
726 }
729 }
727
730
728 let data_size = (|| {
731 let data_size = (|| {
729 // TODO: loop and try another random ID if !append and this
732 // TODO: loop and try another random ID if !append and this
730 // returns `ErrorKind::AlreadyExists`? Collision chance of two
733 // returns `ErrorKind::AlreadyExists`? Collision chance of two
731 // random IDs is one in 2**32
734 // random IDs is one in 2**32
732 let mut file = options.open(&data_filename)?;
735 let mut file = options.open(&data_filename)?;
733 if append {
736 if append {
734 file.seek(SeekFrom::Start(old_data_size as u64))?;
737 file.seek(SeekFrom::Start(old_data_size as u64))?;
735 }
738 }
736 file.write_all(&data)?;
739 file.write_all(&data)?;
737 file.flush()?;
740 file.flush()?;
738 file.stream_position()
741 file.stream_position()
739 })()
742 })()
740 .when_writing_file(&data_filename)?;
743 .when_writing_file(&data_filename)?;
741
744
742 let packed_dirstate = DirstateDocket::serialize(
745 let packed_dirstate = DirstateDocket::serialize(
743 parents,
746 parents,
744 tree_metadata,
747 tree_metadata,
745 data_size,
748 data_size,
746 uuid.as_bytes(),
749 uuid.as_bytes(),
747 )
750 )
748 .map_err(|_: std::num::TryFromIntError| {
751 .map_err(|_: std::num::TryFromIntError| {
749 HgError::corrupted("overflow in dirstate docket serialization")
752 HgError::corrupted("overflow in dirstate docket serialization")
750 })?;
753 })?;
751
754
752 (packed_dirstate, old_uuid)
755 (packed_dirstate, old_uuid)
753 } else {
756 } else {
754 let identity = self.dirstate_identity()?;
757 let identity = self.dirstate_identity()?;
755 if identity != map.old_identity() {
758 if identity != map.old_identity() {
756 // If identity changed since last disk read, don't write.
759 // If identity changed since last disk read, don't write.
757 // This is fine because either we're in a command that doesn't
760 // This is fine because either we're in a command that doesn't
758 // write anything too important (like `hg status`), or we're in
761 // write anything too important (like `hg status`), or we're in
759 // `hg add` and we're supposed to have taken the lock before
762 // `hg add` and we're supposed to have taken the lock before
760 // reading anyway.
763 // reading anyway.
761 //
764 //
762 // TODO complain loudly if we've changed anything important
765 // TODO complain loudly if we've changed anything important
763 // without taking the lock.
766 // without taking the lock.
764 // (see `hg help config.format.use-dirstate-tracked-hint`)
767 // (see `hg help config.format.use-dirstate-tracked-hint`)
765 log::debug!(
768 log::debug!(
766 "dirstate has changed since last read, not updating."
769 "dirstate has changed since last read, not updating."
767 );
770 );
768 return Ok(());
771 return Ok(());
769 }
772 }
770 (map.pack_v1(parents)?, None)
773 (map.pack_v1(parents)?, None)
771 };
774 };
772
775
773 let vfs = self.hg_vfs();
776 let vfs = self.hg_vfs();
774 vfs.atomic_write("dirstate", &packed_dirstate)?;
777 vfs.atomic_write("dirstate", &packed_dirstate)?;
775 if let Some(uuid) = old_uuid_to_remove {
778 if let Some(uuid) = old_uuid_to_remove {
776 // Remove the old data file after the new docket pointing to the
779 // Remove the old data file after the new docket pointing to the
777 // new data file was written.
780 // new data file was written.
778 vfs.remove_file(format!("dirstate.{}", uuid))?;
781 vfs.remove_file(format!("dirstate.{}", uuid))?;
779 }
782 }
780 Ok(())
783 Ok(())
781 }
784 }
782
785
783 pub fn default_revlog_options(
786 pub fn default_revlog_options(
784 &self,
787 &self,
785 revlog_type: RevlogType,
788 revlog_type: RevlogType,
786 ) -> Result<RevlogOpenOptions, HgError> {
789 ) -> Result<RevlogOpenOptions, HgError> {
787 let requirements = self.requirements();
790 let requirements = self.requirements();
788 let is_changelog = revlog_type == RevlogType::Changelog;
791 let is_changelog = revlog_type == RevlogType::Changelog;
789 let version = if is_changelog
792 let version = if is_changelog
790 && requirements.contains(CHANGELOGV2_REQUIREMENT)
793 && requirements.contains(CHANGELOGV2_REQUIREMENT)
791 {
794 {
792 let compute_rank = self
795 let compute_rank = self
793 .config()
796 .config()
794 .get_bool(b"experimental", b"changelog-v2.compute-rank")?;
797 .get_bool(b"experimental", b"changelog-v2.compute-rank")?;
795 RevlogVersionOptions::ChangelogV2 { compute_rank }
798 RevlogVersionOptions::ChangelogV2 { compute_rank }
796 } else if requirements.contains(REVLOGV2_REQUIREMENT) {
799 } else if requirements.contains(REVLOGV2_REQUIREMENT) {
797 RevlogVersionOptions::V2
800 RevlogVersionOptions::V2
798 } else if requirements.contains(REVLOGV1_REQUIREMENT) {
801 } else if requirements.contains(REVLOGV1_REQUIREMENT) {
799 RevlogVersionOptions::V1 {
802 RevlogVersionOptions::V1 {
800 general_delta: requirements.contains(GENERALDELTA_REQUIREMENT),
803 general_delta: requirements.contains(GENERALDELTA_REQUIREMENT),
801 inline: !is_changelog,
804 inline: !is_changelog,
802 }
805 }
803 } else {
806 } else {
804 RevlogVersionOptions::V0
807 RevlogVersionOptions::V0
805 };
808 };
806 Ok(RevlogOpenOptions {
809 Ok(RevlogOpenOptions {
807 version,
810 version,
808 // We don't need to dance around the slow path like in the Python
811 // We don't need to dance around the slow path like in the Python
809 // implementation since we know we have access to the fast code.
812 // implementation since we know we have access to the fast code.
810 use_nodemap: requirements.contains(NODEMAP_REQUIREMENT),
813 use_nodemap: requirements.contains(NODEMAP_REQUIREMENT),
811 delta_config: RevlogDeltaConfig::new(
814 delta_config: RevlogDeltaConfig::new(
812 self.config(),
815 self.config(),
813 self.requirements(),
816 self.requirements(),
814 revlog_type,
817 revlog_type,
815 )?,
818 )?,
816 data_config: RevlogDataConfig::new(
819 data_config: RevlogDataConfig::new(
817 self.config(),
820 self.config(),
818 self.requirements(),
821 self.requirements(),
819 )?,
822 )?,
820 feature_config: RevlogFeatureConfig::new(
823 feature_config: RevlogFeatureConfig::new(
821 self.config(),
824 self.config(),
822 requirements,
825 requirements,
823 )?,
826 )?,
824 })
827 })
825 }
828 }
826
829
827 pub fn node(&self, rev: UncheckedRevision) -> Option<crate::Node> {
830 pub fn node(&self, rev: UncheckedRevision) -> Option<crate::Node> {
828 self.changelog()
831 self.changelog()
829 .ok()
832 .ok()
830 .and_then(|c| c.node_from_rev(rev).copied())
833 .and_then(|c| c.node_from_rev(rev).copied())
831 }
834 }
832
835
833 /// Change the current working directory parents cached in the repo.
836 /// Change the current working directory parents cached in the repo.
834 ///
837 ///
835 /// TODO
838 /// TODO
836 /// This does *not* do a lot of what it expected from a full `set_parents`:
839 /// This does *not* do a lot of what it expected from a full `set_parents`:
837 /// - parents should probably be stored in the dirstate
840 /// - parents should probably be stored in the dirstate
838 /// - dirstate should have a "changing parents" context
841 /// - dirstate should have a "changing parents" context
839 /// - dirstate should return copies if out of a merge context to be
842 /// - dirstate should return copies if out of a merge context to be
840 /// discarded within the repo context
843 /// discarded within the repo context
841 /// See `setparents` in `context.py`.
844 /// See `setparents` in `context.py`.
842 pub fn manually_set_parents(
845 pub fn manually_set_parents(
843 &self,
846 &self,
844 new_parents: DirstateParents,
847 new_parents: DirstateParents,
845 ) -> Result<(), HgError> {
848 ) -> Result<(), HgError> {
846 let mut parents = self.dirstate_parents.value.borrow_mut();
849 let mut parents = self.dirstate_parents.value.borrow_mut();
847 *parents = Some(new_parents);
850 *parents = Some(new_parents);
848 Ok(())
851 Ok(())
849 }
852 }
850 }
853 }
851
854
852 /// Lazily-initialized component of `Repo` with interior mutability
855 /// Lazily-initialized component of `Repo` with interior mutability
853 ///
856 ///
854 /// This differs from `OnceCell` in that the value can still be "deinitialized"
857 /// This differs from `OnceCell` in that the value can still be "deinitialized"
855 /// later by setting its inner `Option` to `None`. It also takes the
858 /// later by setting its inner `Option` to `None`. It also takes the
856 /// initialization function as an argument when the value is requested, not
859 /// initialization function as an argument when the value is requested, not
857 /// when the instance is created.
860 /// when the instance is created.
858 struct LazyCell<T> {
861 struct LazyCell<T> {
859 value: RefCell<Option<T>>,
862 value: RefCell<Option<T>>,
860 }
863 }
861
864
862 impl<T> LazyCell<T> {
865 impl<T> LazyCell<T> {
863 fn new() -> Self {
866 fn new() -> Self {
864 Self {
867 Self {
865 value: RefCell::new(None),
868 value: RefCell::new(None),
866 }
869 }
867 }
870 }
868
871
869 fn set(&self, value: T) {
872 fn set(&self, value: T) {
870 *self.value.borrow_mut() = Some(value)
873 *self.value.borrow_mut() = Some(value)
871 }
874 }
872
875
873 fn get_or_init<E>(
876 fn get_or_init<E>(
874 &self,
877 &self,
875 init: impl Fn() -> Result<T, E>,
878 init: impl Fn() -> Result<T, E>,
876 ) -> Result<Ref<T>, E> {
879 ) -> Result<Ref<T>, E> {
877 let mut borrowed = self.value.borrow();
880 let mut borrowed = self.value.borrow();
878 if borrowed.is_none() {
881 if borrowed.is_none() {
879 drop(borrowed);
882 drop(borrowed);
880 // Only use `borrow_mut` if it is really needed to avoid panic in
883 // Only use `borrow_mut` if it is really needed to avoid panic in
881 // case there is another outstanding borrow but mutation is not
884 // case there is another outstanding borrow but mutation is not
882 // needed.
885 // needed.
883 *self.value.borrow_mut() = Some(init()?);
886 *self.value.borrow_mut() = Some(init()?);
884 borrowed = self.value.borrow()
887 borrowed = self.value.borrow()
885 }
888 }
886 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
889 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
887 }
890 }
888
891
889 fn get_mut_or_init<E>(
892 fn get_mut_or_init<E>(
890 &self,
893 &self,
891 init: impl Fn() -> Result<T, E>,
894 init: impl Fn() -> Result<T, E>,
892 ) -> Result<RefMut<T>, E> {
895 ) -> Result<RefMut<T>, E> {
893 let mut borrowed = self.value.borrow_mut();
896 let mut borrowed = self.value.borrow_mut();
894 if borrowed.is_none() {
897 if borrowed.is_none() {
895 *borrowed = Some(init()?);
898 *borrowed = Some(init()?);
896 }
899 }
897 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
900 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
898 }
901 }
899 }
902 }
General Comments 0
You need to be logged in to leave comments. Login now