##// END OF EJS Templates
rust-repo: move dirstate-v2 opening to a separate method...
Raphaël Gomès -
r51122:cbd4c923 stable
parent child Browse files
Show More
@@ -1,599 +1,609 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::files::get_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::utils::SliceExt;
15 use crate::utils::SliceExt;
16 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::{requirements, NodePrefix};
17 use crate::{requirements, NodePrefix};
18 use crate::{DirstateError, Revision};
18 use crate::{DirstateError, Revision};
19 use std::cell::{Ref, RefCell, RefMut};
19 use std::cell::{Ref, RefCell, RefMut};
20 use std::collections::HashSet;
20 use std::collections::HashSet;
21 use std::io::Seek;
21 use std::io::Seek;
22 use std::io::SeekFrom;
22 use std::io::SeekFrom;
23 use std::io::Write as IoWrite;
23 use std::io::Write as IoWrite;
24 use std::path::{Path, PathBuf};
24 use std::path::{Path, PathBuf};
25
25
26 /// A repository on disk
26 /// A repository on disk
27 pub struct Repo {
27 pub struct Repo {
28 working_directory: PathBuf,
28 working_directory: PathBuf,
29 dot_hg: PathBuf,
29 dot_hg: PathBuf,
30 store: PathBuf,
30 store: PathBuf,
31 requirements: HashSet<String>,
31 requirements: HashSet<String>,
32 config: Config,
32 config: Config,
33 dirstate_parents: LazyCell<DirstateParents>,
33 dirstate_parents: LazyCell<DirstateParents>,
34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
35 dirstate_map: LazyCell<OwningDirstateMap>,
35 dirstate_map: LazyCell<OwningDirstateMap>,
36 changelog: LazyCell<Changelog>,
36 changelog: LazyCell<Changelog>,
37 manifestlog: LazyCell<Manifestlog>,
37 manifestlog: LazyCell<Manifestlog>,
38 }
38 }
39
39
40 #[derive(Debug, derive_more::From)]
40 #[derive(Debug, derive_more::From)]
41 pub enum RepoError {
41 pub enum RepoError {
42 NotFound {
42 NotFound {
43 at: PathBuf,
43 at: PathBuf,
44 },
44 },
45 #[from]
45 #[from]
46 ConfigParseError(ConfigParseError),
46 ConfigParseError(ConfigParseError),
47 #[from]
47 #[from]
48 Other(HgError),
48 Other(HgError),
49 }
49 }
50
50
51 impl From<ConfigError> for RepoError {
51 impl From<ConfigError> for RepoError {
52 fn from(error: ConfigError) -> Self {
52 fn from(error: ConfigError) -> Self {
53 match error {
53 match error {
54 ConfigError::Parse(error) => error.into(),
54 ConfigError::Parse(error) => error.into(),
55 ConfigError::Other(error) => error.into(),
55 ConfigError::Other(error) => error.into(),
56 }
56 }
57 }
57 }
58 }
58 }
59
59
60 impl Repo {
60 impl Repo {
61 /// tries to find nearest repository root in current working directory or
61 /// tries to find nearest repository root in current working directory or
62 /// its ancestors
62 /// its ancestors
63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
64 let current_directory = crate::utils::current_dir()?;
64 let current_directory = crate::utils::current_dir()?;
65 // ancestors() is inclusive: it first yields `current_directory`
65 // ancestors() is inclusive: it first yields `current_directory`
66 // as-is.
66 // as-is.
67 for ancestor in current_directory.ancestors() {
67 for ancestor in current_directory.ancestors() {
68 if is_dir(ancestor.join(".hg"))? {
68 if is_dir(ancestor.join(".hg"))? {
69 return Ok(ancestor.to_path_buf());
69 return Ok(ancestor.to_path_buf());
70 }
70 }
71 }
71 }
72 return Err(RepoError::NotFound {
72 return Err(RepoError::NotFound {
73 at: current_directory,
73 at: current_directory,
74 });
74 });
75 }
75 }
76
76
77 /// Find a repository, either at the given path (which must contain a `.hg`
77 /// Find a repository, either at the given path (which must contain a `.hg`
78 /// sub-directory) or by searching the current directory and its
78 /// sub-directory) or by searching the current directory and its
79 /// ancestors.
79 /// ancestors.
80 ///
80 ///
81 /// A method with two very different "modes" like this usually a code smell
81 /// A method with two very different "modes" like this usually a code smell
82 /// to make two methods instead, but in this case an `Option` is what rhg
82 /// to make two methods instead, but in this case an `Option` is what rhg
83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
84 /// Having two methods would just move that `if` to almost all callers.
84 /// Having two methods would just move that `if` to almost all callers.
85 pub fn find(
85 pub fn find(
86 config: &Config,
86 config: &Config,
87 explicit_path: Option<PathBuf>,
87 explicit_path: Option<PathBuf>,
88 ) -> Result<Self, RepoError> {
88 ) -> Result<Self, RepoError> {
89 if let Some(root) = explicit_path {
89 if let Some(root) = explicit_path {
90 if is_dir(root.join(".hg"))? {
90 if is_dir(root.join(".hg"))? {
91 Self::new_at_path(root.to_owned(), config)
91 Self::new_at_path(root.to_owned(), config)
92 } else if is_file(&root)? {
92 } else if is_file(&root)? {
93 Err(HgError::unsupported("bundle repository").into())
93 Err(HgError::unsupported("bundle repository").into())
94 } else {
94 } else {
95 Err(RepoError::NotFound {
95 Err(RepoError::NotFound {
96 at: root.to_owned(),
96 at: root.to_owned(),
97 })
97 })
98 }
98 }
99 } else {
99 } else {
100 let root = Self::find_repo_root()?;
100 let root = Self::find_repo_root()?;
101 Self::new_at_path(root, config)
101 Self::new_at_path(root, config)
102 }
102 }
103 }
103 }
104
104
105 /// To be called after checking that `.hg` is a sub-directory
105 /// To be called after checking that `.hg` is a sub-directory
106 fn new_at_path(
106 fn new_at_path(
107 working_directory: PathBuf,
107 working_directory: PathBuf,
108 config: &Config,
108 config: &Config,
109 ) -> Result<Self, RepoError> {
109 ) -> Result<Self, RepoError> {
110 let dot_hg = working_directory.join(".hg");
110 let dot_hg = working_directory.join(".hg");
111
111
112 let mut repo_config_files = Vec::new();
112 let mut repo_config_files = Vec::new();
113 repo_config_files.push(dot_hg.join("hgrc"));
113 repo_config_files.push(dot_hg.join("hgrc"));
114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
115
115
116 let hg_vfs = Vfs { base: &dot_hg };
116 let hg_vfs = Vfs { base: &dot_hg };
117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
118 let relative =
118 let relative =
119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
120 let shared =
120 let shared =
121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
122
122
123 // From `mercurial/localrepo.py`:
123 // From `mercurial/localrepo.py`:
124 //
124 //
125 // if .hg/requires contains the sharesafe requirement, it means
125 // if .hg/requires contains the sharesafe requirement, it means
126 // there exists a `.hg/store/requires` too and we should read it
126 // there exists a `.hg/store/requires` too and we should read it
127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
129 // is not present, refer checkrequirementscompat() for that
129 // is not present, refer checkrequirementscompat() for that
130 //
130 //
131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
132 // repository was shared the old way. We check the share source
132 // repository was shared the old way. We check the share source
133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
134 // current repository needs to be reshared
134 // current repository needs to be reshared
135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
136
136
137 let store_path;
137 let store_path;
138 if !shared {
138 if !shared {
139 store_path = dot_hg.join("store");
139 store_path = dot_hg.join("store");
140 } else {
140 } else {
141 let bytes = hg_vfs.read("sharedpath")?;
141 let bytes = hg_vfs.read("sharedpath")?;
142 let mut shared_path =
142 let mut shared_path =
143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
144 .to_owned();
144 .to_owned();
145 if relative {
145 if relative {
146 shared_path = dot_hg.join(shared_path)
146 shared_path = dot_hg.join(shared_path)
147 }
147 }
148 if !is_dir(&shared_path)? {
148 if !is_dir(&shared_path)? {
149 return Err(HgError::corrupted(format!(
149 return Err(HgError::corrupted(format!(
150 ".hg/sharedpath points to nonexistent directory {}",
150 ".hg/sharedpath points to nonexistent directory {}",
151 shared_path.display()
151 shared_path.display()
152 ))
152 ))
153 .into());
153 .into());
154 }
154 }
155
155
156 store_path = shared_path.join("store");
156 store_path = shared_path.join("store");
157
157
158 let source_is_share_safe =
158 let source_is_share_safe =
159 requirements::load(Vfs { base: &shared_path })?
159 requirements::load(Vfs { base: &shared_path })?
160 .contains(requirements::SHARESAFE_REQUIREMENT);
160 .contains(requirements::SHARESAFE_REQUIREMENT);
161
161
162 if share_safe != source_is_share_safe {
162 if share_safe != source_is_share_safe {
163 return Err(HgError::unsupported("share-safe mismatch").into());
163 return Err(HgError::unsupported("share-safe mismatch").into());
164 }
164 }
165
165
166 if share_safe {
166 if share_safe {
167 repo_config_files.insert(0, shared_path.join("hgrc"))
167 repo_config_files.insert(0, shared_path.join("hgrc"))
168 }
168 }
169 }
169 }
170 if share_safe {
170 if share_safe {
171 reqs.extend(requirements::load(Vfs { base: &store_path })?);
171 reqs.extend(requirements::load(Vfs { base: &store_path })?);
172 }
172 }
173
173
174 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
174 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
175 config.combine_with_repo(&repo_config_files)?
175 config.combine_with_repo(&repo_config_files)?
176 } else {
176 } else {
177 config.clone()
177 config.clone()
178 };
178 };
179
179
180 let repo = Self {
180 let repo = Self {
181 requirements: reqs,
181 requirements: reqs,
182 working_directory,
182 working_directory,
183 store: store_path,
183 store: store_path,
184 dot_hg,
184 dot_hg,
185 config: repo_config,
185 config: repo_config,
186 dirstate_parents: LazyCell::new(),
186 dirstate_parents: LazyCell::new(),
187 dirstate_data_file_uuid: LazyCell::new(),
187 dirstate_data_file_uuid: LazyCell::new(),
188 dirstate_map: LazyCell::new(),
188 dirstate_map: LazyCell::new(),
189 changelog: LazyCell::new(),
189 changelog: LazyCell::new(),
190 manifestlog: LazyCell::new(),
190 manifestlog: LazyCell::new(),
191 };
191 };
192
192
193 requirements::check(&repo)?;
193 requirements::check(&repo)?;
194
194
195 Ok(repo)
195 Ok(repo)
196 }
196 }
197
197
198 pub fn working_directory_path(&self) -> &Path {
198 pub fn working_directory_path(&self) -> &Path {
199 &self.working_directory
199 &self.working_directory
200 }
200 }
201
201
202 pub fn requirements(&self) -> &HashSet<String> {
202 pub fn requirements(&self) -> &HashSet<String> {
203 &self.requirements
203 &self.requirements
204 }
204 }
205
205
206 pub fn config(&self) -> &Config {
206 pub fn config(&self) -> &Config {
207 &self.config
207 &self.config
208 }
208 }
209
209
210 /// For accessing repository files (in `.hg`), except for the store
210 /// For accessing repository files (in `.hg`), except for the store
211 /// (`.hg/store`).
211 /// (`.hg/store`).
212 pub fn hg_vfs(&self) -> Vfs<'_> {
212 pub fn hg_vfs(&self) -> Vfs<'_> {
213 Vfs { base: &self.dot_hg }
213 Vfs { base: &self.dot_hg }
214 }
214 }
215
215
216 /// For accessing repository store files (in `.hg/store`)
216 /// For accessing repository store files (in `.hg/store`)
217 pub fn store_vfs(&self) -> Vfs<'_> {
217 pub fn store_vfs(&self) -> Vfs<'_> {
218 Vfs { base: &self.store }
218 Vfs { base: &self.store }
219 }
219 }
220
220
221 /// For accessing the working copy
221 /// For accessing the working copy
222 pub fn working_directory_vfs(&self) -> Vfs<'_> {
222 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 Vfs {
223 Vfs {
224 base: &self.working_directory,
224 base: &self.working_directory,
225 }
225 }
226 }
226 }
227
227
228 pub fn try_with_wlock_no_wait<R>(
228 pub fn try_with_wlock_no_wait<R>(
229 &self,
229 &self,
230 f: impl FnOnce() -> R,
230 f: impl FnOnce() -> R,
231 ) -> Result<R, LockError> {
231 ) -> Result<R, LockError> {
232 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
232 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 }
233 }
234
234
235 pub fn has_dirstate_v2(&self) -> bool {
235 pub fn has_dirstate_v2(&self) -> bool {
236 self.requirements
236 self.requirements
237 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
237 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 }
238 }
239
239
240 pub fn has_sparse(&self) -> bool {
240 pub fn has_sparse(&self) -> bool {
241 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
241 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 }
242 }
243
243
244 pub fn has_narrow(&self) -> bool {
244 pub fn has_narrow(&self) -> bool {
245 self.requirements.contains(requirements::NARROW_REQUIREMENT)
245 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 }
246 }
247
247
248 pub fn has_nodemap(&self) -> bool {
248 pub fn has_nodemap(&self) -> bool {
249 self.requirements
249 self.requirements
250 .contains(requirements::NODEMAP_REQUIREMENT)
250 .contains(requirements::NODEMAP_REQUIREMENT)
251 }
251 }
252
252
253 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
253 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 Ok(self
254 Ok(self
255 .hg_vfs()
255 .hg_vfs()
256 .read("dirstate")
256 .read("dirstate")
257 .io_not_found_as_none()?
257 .io_not_found_as_none()?
258 .unwrap_or(Vec::new()))
258 .unwrap_or(Vec::new()))
259 }
259 }
260
260
261 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
261 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
262 Ok(*self
262 Ok(*self
263 .dirstate_parents
263 .dirstate_parents
264 .get_or_init(|| self.read_dirstate_parents())?)
264 .get_or_init(|| self.read_dirstate_parents())?)
265 }
265 }
266
266
267 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
267 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
268 let dirstate = self.dirstate_file_contents()?;
268 let dirstate = self.dirstate_file_contents()?;
269 let parents = if dirstate.is_empty() {
269 let parents = if dirstate.is_empty() {
270 if self.has_dirstate_v2() {
270 if self.has_dirstate_v2() {
271 self.dirstate_data_file_uuid.set(None);
271 self.dirstate_data_file_uuid.set(None);
272 }
272 }
273 DirstateParents::NULL
273 DirstateParents::NULL
274 } else if self.has_dirstate_v2() {
274 } else if self.has_dirstate_v2() {
275 let docket =
275 let docket =
276 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
276 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
277 self.dirstate_data_file_uuid
277 self.dirstate_data_file_uuid
278 .set(Some(docket.uuid.to_owned()));
278 .set(Some(docket.uuid.to_owned()));
279 docket.parents()
279 docket.parents()
280 } else {
280 } else {
281 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
281 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
282 .clone()
282 .clone()
283 };
283 };
284 self.dirstate_parents.set(parents);
284 self.dirstate_parents.set(parents);
285 Ok(parents)
285 Ok(parents)
286 }
286 }
287
287
288 fn read_dirstate_data_file_uuid(
288 fn read_dirstate_data_file_uuid(
289 &self,
289 &self,
290 ) -> Result<Option<Vec<u8>>, HgError> {
290 ) -> Result<Option<Vec<u8>>, HgError> {
291 assert!(
291 assert!(
292 self.has_dirstate_v2(),
292 self.has_dirstate_v2(),
293 "accessing dirstate data file ID without dirstate-v2"
293 "accessing dirstate data file ID without dirstate-v2"
294 );
294 );
295 let dirstate = self.dirstate_file_contents()?;
295 let dirstate = self.dirstate_file_contents()?;
296 if dirstate.is_empty() {
296 if dirstate.is_empty() {
297 self.dirstate_parents.set(DirstateParents::NULL);
297 self.dirstate_parents.set(DirstateParents::NULL);
298 Ok(None)
298 Ok(None)
299 } else {
299 } else {
300 let docket =
300 let docket =
301 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
301 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
302 self.dirstate_parents.set(docket.parents());
302 self.dirstate_parents.set(docket.parents());
303 Ok(Some(docket.uuid.to_owned()))
303 Ok(Some(docket.uuid.to_owned()))
304 }
304 }
305 }
305 }
306
306
307 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
307 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
308 if self.has_dirstate_v2() {
309 self.read_docket_and_data_file()
310 } else {
311 let dirstate_file_contents = self.dirstate_file_contents()?;
312 if dirstate_file_contents.is_empty() {
313 self.dirstate_parents.set(DirstateParents::NULL);
314 Ok(OwningDirstateMap::new_empty(Vec::new()))
315 } else {
316 let (map, parents) =
317 OwningDirstateMap::new_v1(dirstate_file_contents)?;
318 self.dirstate_parents.set(parents);
319 Ok(map)
320 }
321 }
322 }
323
324 fn read_docket_and_data_file(
325 &self,
326 ) -> Result<OwningDirstateMap, DirstateError> {
308 let dirstate_file_contents = self.dirstate_file_contents()?;
327 let dirstate_file_contents = self.dirstate_file_contents()?;
309 if dirstate_file_contents.is_empty() {
328 if dirstate_file_contents.is_empty() {
310 self.dirstate_parents.set(DirstateParents::NULL);
329 self.dirstate_parents.set(DirstateParents::NULL);
311 if self.has_dirstate_v2() {
330 self.dirstate_data_file_uuid.set(None);
312 self.dirstate_data_file_uuid.set(None);
331 return Ok(OwningDirstateMap::new_empty(Vec::new()));
313 }
332 }
314 Ok(OwningDirstateMap::new_empty(Vec::new()))
333 let docket = crate::dirstate_tree::on_disk::read_docket(
315 } else if self.has_dirstate_v2() {
334 &dirstate_file_contents,
316 let docket = crate::dirstate_tree::on_disk::read_docket(
335 )?;
317 &dirstate_file_contents,
336 self.dirstate_parents.set(docket.parents());
318 )?;
337 self.dirstate_data_file_uuid
319 self.dirstate_parents.set(docket.parents());
338 .set(Some(docket.uuid.to_owned()));
320 self.dirstate_data_file_uuid
339 let data_size = docket.data_size();
321 .set(Some(docket.uuid.to_owned()));
340 let metadata = docket.tree_metadata();
322 let data_size = docket.data_size();
341 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
323 let metadata = docket.tree_metadata();
342 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
324 let mut map =
343 OwningDirstateMap::new_v2(
325 if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
344 self.hg_vfs().read(docket.data_filename())?,
326 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
345 data_size,
327 OwningDirstateMap::new_v2(
346 metadata,
328 self.hg_vfs().read(docket.data_filename())?,
347 )
329 data_size,
348 } else if let Some(data_mmap) = self
330 metadata,
349 .hg_vfs()
331 )
350 .mmap_open(docket.data_filename())
332 } else if let Some(data_mmap) = self
351 .io_not_found_as_none()?
333 .hg_vfs()
352 {
334 .mmap_open(docket.data_filename())
353 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
335 .io_not_found_as_none()?
354 } else {
336 {
355 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
337 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
356 }?;
338 } else {
339 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
340 }?;
341
357
342 let write_mode_config = self
358 let write_mode_config = self
343 .config()
359 .config()
344 .get_str(b"devel", b"dirstate.v2.data_update_mode")
360 .get_str(b"devel", b"dirstate.v2.data_update_mode")
345 .unwrap_or(Some("auto"))
361 .unwrap_or(Some("auto"))
346 .unwrap_or("auto"); // don't bother for devel options
362 .unwrap_or("auto"); // don't bother for devel options
347 let write_mode = match write_mode_config {
363 let write_mode = match write_mode_config {
348 "auto" => DirstateMapWriteMode::Auto,
364 "auto" => DirstateMapWriteMode::Auto,
349 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
365 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
350 "force-append" => DirstateMapWriteMode::ForceAppend,
366 "force-append" => DirstateMapWriteMode::ForceAppend,
351 _ => DirstateMapWriteMode::Auto,
367 _ => DirstateMapWriteMode::Auto,
352 };
368 };
353
369
354 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
370 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
355
371
356 Ok(map)
372 Ok(map)
357 } else {
358 let (map, parents) =
359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
360 self.dirstate_parents.set(parents);
361 Ok(map)
362 }
363 }
373 }
364
374
365 pub fn dirstate_map(
375 pub fn dirstate_map(
366 &self,
376 &self,
367 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
377 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
368 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
378 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
369 }
379 }
370
380
371 pub fn dirstate_map_mut(
381 pub fn dirstate_map_mut(
372 &self,
382 &self,
373 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
383 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
374 self.dirstate_map
384 self.dirstate_map
375 .get_mut_or_init(|| self.new_dirstate_map())
385 .get_mut_or_init(|| self.new_dirstate_map())
376 }
386 }
377
387
378 fn new_changelog(&self) -> Result<Changelog, HgError> {
388 fn new_changelog(&self) -> Result<Changelog, HgError> {
379 Changelog::open(&self.store_vfs(), self.has_nodemap())
389 Changelog::open(&self.store_vfs(), self.has_nodemap())
380 }
390 }
381
391
382 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
392 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
383 self.changelog.get_or_init(|| self.new_changelog())
393 self.changelog.get_or_init(|| self.new_changelog())
384 }
394 }
385
395
386 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
396 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
387 self.changelog.get_mut_or_init(|| self.new_changelog())
397 self.changelog.get_mut_or_init(|| self.new_changelog())
388 }
398 }
389
399
390 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
400 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
391 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
401 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
392 }
402 }
393
403
394 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
404 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
395 self.manifestlog.get_or_init(|| self.new_manifestlog())
405 self.manifestlog.get_or_init(|| self.new_manifestlog())
396 }
406 }
397
407
398 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
408 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
399 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
409 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
400 }
410 }
401
411
402 /// Returns the manifest of the *changeset* with the given node ID
412 /// Returns the manifest of the *changeset* with the given node ID
403 pub fn manifest_for_node(
413 pub fn manifest_for_node(
404 &self,
414 &self,
405 node: impl Into<NodePrefix>,
415 node: impl Into<NodePrefix>,
406 ) -> Result<Manifest, RevlogError> {
416 ) -> Result<Manifest, RevlogError> {
407 self.manifestlog()?.data_for_node(
417 self.manifestlog()?.data_for_node(
408 self.changelog()?
418 self.changelog()?
409 .data_for_node(node.into())?
419 .data_for_node(node.into())?
410 .manifest_node()?
420 .manifest_node()?
411 .into(),
421 .into(),
412 )
422 )
413 }
423 }
414
424
415 /// Returns the manifest of the *changeset* with the given revision number
425 /// Returns the manifest of the *changeset* with the given revision number
416 pub fn manifest_for_rev(
426 pub fn manifest_for_rev(
417 &self,
427 &self,
418 revision: Revision,
428 revision: Revision,
419 ) -> Result<Manifest, RevlogError> {
429 ) -> Result<Manifest, RevlogError> {
420 self.manifestlog()?.data_for_node(
430 self.manifestlog()?.data_for_node(
421 self.changelog()?
431 self.changelog()?
422 .data_for_rev(revision)?
432 .data_for_rev(revision)?
423 .manifest_node()?
433 .manifest_node()?
424 .into(),
434 .into(),
425 )
435 )
426 }
436 }
427
437
428 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
438 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
429 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
439 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
430 Ok(entry.tracked())
440 Ok(entry.tracked())
431 } else {
441 } else {
432 Ok(false)
442 Ok(false)
433 }
443 }
434 }
444 }
435
445
436 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
446 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
437 Filelog::open(self, path)
447 Filelog::open(self, path)
438 }
448 }
439
449
440 /// Write to disk any updates that were made through `dirstate_map_mut`.
450 /// Write to disk any updates that were made through `dirstate_map_mut`.
441 ///
451 ///
442 /// The "wlock" must be held while calling this.
452 /// The "wlock" must be held while calling this.
443 /// See for example `try_with_wlock_no_wait`.
453 /// See for example `try_with_wlock_no_wait`.
444 ///
454 ///
445 /// TODO: have a `WritableRepo` type only accessible while holding the
455 /// TODO: have a `WritableRepo` type only accessible while holding the
446 /// lock?
456 /// lock?
447 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
457 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
448 let map = self.dirstate_map()?;
458 let map = self.dirstate_map()?;
449 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
459 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
450 // it’s unset
460 // it’s unset
451 let parents = self.dirstate_parents()?;
461 let parents = self.dirstate_parents()?;
452 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
462 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
453 let uuid_opt = self
463 let uuid_opt = self
454 .dirstate_data_file_uuid
464 .dirstate_data_file_uuid
455 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
465 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
456 let uuid_opt = uuid_opt.as_ref();
466 let uuid_opt = uuid_opt.as_ref();
457 let write_mode = if uuid_opt.is_some() {
467 let write_mode = if uuid_opt.is_some() {
458 DirstateMapWriteMode::Auto
468 DirstateMapWriteMode::Auto
459 } else {
469 } else {
460 DirstateMapWriteMode::ForceNewDataFile
470 DirstateMapWriteMode::ForceNewDataFile
461 };
471 };
462 let (data, tree_metadata, append, old_data_size) =
472 let (data, tree_metadata, append, old_data_size) =
463 map.pack_v2(write_mode)?;
473 map.pack_v2(write_mode)?;
464
474
465 // Reuse the uuid, or generate a new one, keeping the old for
475 // Reuse the uuid, or generate a new one, keeping the old for
466 // deletion.
476 // deletion.
467 let (uuid, old_uuid) = match uuid_opt {
477 let (uuid, old_uuid) = match uuid_opt {
468 Some(uuid) => {
478 Some(uuid) => {
469 let as_str = std::str::from_utf8(uuid)
479 let as_str = std::str::from_utf8(uuid)
470 .map_err(|_| {
480 .map_err(|_| {
471 HgError::corrupted(
481 HgError::corrupted(
472 "non-UTF-8 dirstate data file ID",
482 "non-UTF-8 dirstate data file ID",
473 )
483 )
474 })?
484 })?
475 .to_owned();
485 .to_owned();
476 if append {
486 if append {
477 (as_str, None)
487 (as_str, None)
478 } else {
488 } else {
479 (DirstateDocket::new_uid(), Some(as_str))
489 (DirstateDocket::new_uid(), Some(as_str))
480 }
490 }
481 }
491 }
482 None => (DirstateDocket::new_uid(), None),
492 None => (DirstateDocket::new_uid(), None),
483 };
493 };
484
494
485 let data_filename = format!("dirstate.{}", uuid);
495 let data_filename = format!("dirstate.{}", uuid);
486 let data_filename = self.hg_vfs().join(data_filename);
496 let data_filename = self.hg_vfs().join(data_filename);
487 let mut options = std::fs::OpenOptions::new();
497 let mut options = std::fs::OpenOptions::new();
488 options.write(true);
498 options.write(true);
489
499
490 // Why are we not using the O_APPEND flag when appending?
500 // Why are we not using the O_APPEND flag when appending?
491 //
501 //
492 // - O_APPEND makes it trickier to deal with garbage at the end of
502 // - O_APPEND makes it trickier to deal with garbage at the end of
493 // the file, left by a previous uncommitted transaction. By
503 // the file, left by a previous uncommitted transaction. By
494 // starting the write at [old_data_size] we make sure we erase
504 // starting the write at [old_data_size] we make sure we erase
495 // all such garbage.
505 // all such garbage.
496 //
506 //
497 // - O_APPEND requires to special-case 0-byte writes, whereas we
507 // - O_APPEND requires to special-case 0-byte writes, whereas we
498 // don't need that.
508 // don't need that.
499 //
509 //
500 // - Some OSes have bugs in implementation O_APPEND:
510 // - Some OSes have bugs in implementation O_APPEND:
501 // revlog.py talks about a Solaris bug, but we also saw some ZFS
511 // revlog.py talks about a Solaris bug, but we also saw some ZFS
502 // bug: https://github.com/openzfs/zfs/pull/3124,
512 // bug: https://github.com/openzfs/zfs/pull/3124,
503 // https://github.com/openzfs/zfs/issues/13370
513 // https://github.com/openzfs/zfs/issues/13370
504 //
514 //
505 if !append {
515 if !append {
506 log::trace!("creating a new dirstate data file");
516 log::trace!("creating a new dirstate data file");
507 options.create_new(true);
517 options.create_new(true);
508 } else {
518 } else {
509 log::trace!("appending to the dirstate data file");
519 log::trace!("appending to the dirstate data file");
510 }
520 }
511
521
512 let data_size = (|| {
522 let data_size = (|| {
513 // TODO: loop and try another random ID if !append and this
523 // TODO: loop and try another random ID if !append and this
514 // returns `ErrorKind::AlreadyExists`? Collision chance of two
524 // returns `ErrorKind::AlreadyExists`? Collision chance of two
515 // random IDs is one in 2**32
525 // random IDs is one in 2**32
516 let mut file = options.open(&data_filename)?;
526 let mut file = options.open(&data_filename)?;
517 if append {
527 if append {
518 file.seek(SeekFrom::Start(old_data_size as u64))?;
528 file.seek(SeekFrom::Start(old_data_size as u64))?;
519 }
529 }
520 file.write_all(&data)?;
530 file.write_all(&data)?;
521 file.flush()?;
531 file.flush()?;
522 file.seek(SeekFrom::Current(0))
532 file.seek(SeekFrom::Current(0))
523 })()
533 })()
524 .when_writing_file(&data_filename)?;
534 .when_writing_file(&data_filename)?;
525
535
526 let packed_dirstate = DirstateDocket::serialize(
536 let packed_dirstate = DirstateDocket::serialize(
527 parents,
537 parents,
528 tree_metadata,
538 tree_metadata,
529 data_size,
539 data_size,
530 uuid.as_bytes(),
540 uuid.as_bytes(),
531 )
541 )
532 .map_err(|_: std::num::TryFromIntError| {
542 .map_err(|_: std::num::TryFromIntError| {
533 HgError::corrupted("overflow in dirstate docket serialization")
543 HgError::corrupted("overflow in dirstate docket serialization")
534 })?;
544 })?;
535
545
536 (packed_dirstate, old_uuid)
546 (packed_dirstate, old_uuid)
537 } else {
547 } else {
538 (map.pack_v1(parents)?, None)
548 (map.pack_v1(parents)?, None)
539 };
549 };
540
550
541 let vfs = self.hg_vfs();
551 let vfs = self.hg_vfs();
542 vfs.atomic_write("dirstate", &packed_dirstate)?;
552 vfs.atomic_write("dirstate", &packed_dirstate)?;
543 if let Some(uuid) = old_uuid_to_remove {
553 if let Some(uuid) = old_uuid_to_remove {
544 // Remove the old data file after the new docket pointing to the
554 // Remove the old data file after the new docket pointing to the
545 // new data file was written.
555 // new data file was written.
546 vfs.remove_file(format!("dirstate.{}", uuid))?;
556 vfs.remove_file(format!("dirstate.{}", uuid))?;
547 }
557 }
548 Ok(())
558 Ok(())
549 }
559 }
550 }
560 }
551
561
552 /// Lazily-initialized component of `Repo` with interior mutability
562 /// Lazily-initialized component of `Repo` with interior mutability
553 ///
563 ///
554 /// This differs from `OnceCell` in that the value can still be "deinitialized"
564 /// This differs from `OnceCell` in that the value can still be "deinitialized"
555 /// later by setting its inner `Option` to `None`. It also takes the
565 /// later by setting its inner `Option` to `None`. It also takes the
556 /// initialization function as an argument when the value is requested, not
566 /// initialization function as an argument when the value is requested, not
557 /// when the instance is created.
567 /// when the instance is created.
558 struct LazyCell<T> {
568 struct LazyCell<T> {
559 value: RefCell<Option<T>>,
569 value: RefCell<Option<T>>,
560 }
570 }
561
571
562 impl<T> LazyCell<T> {
572 impl<T> LazyCell<T> {
563 fn new() -> Self {
573 fn new() -> Self {
564 Self {
574 Self {
565 value: RefCell::new(None),
575 value: RefCell::new(None),
566 }
576 }
567 }
577 }
568
578
569 fn set(&self, value: T) {
579 fn set(&self, value: T) {
570 *self.value.borrow_mut() = Some(value)
580 *self.value.borrow_mut() = Some(value)
571 }
581 }
572
582
573 fn get_or_init<E>(
583 fn get_or_init<E>(
574 &self,
584 &self,
575 init: impl Fn() -> Result<T, E>,
585 init: impl Fn() -> Result<T, E>,
576 ) -> Result<Ref<T>, E> {
586 ) -> Result<Ref<T>, E> {
577 let mut borrowed = self.value.borrow();
587 let mut borrowed = self.value.borrow();
578 if borrowed.is_none() {
588 if borrowed.is_none() {
579 drop(borrowed);
589 drop(borrowed);
580 // Only use `borrow_mut` if it is really needed to avoid panic in
590 // Only use `borrow_mut` if it is really needed to avoid panic in
581 // case there is another outstanding borrow but mutation is not
591 // case there is another outstanding borrow but mutation is not
582 // needed.
592 // needed.
583 *self.value.borrow_mut() = Some(init()?);
593 *self.value.borrow_mut() = Some(init()?);
584 borrowed = self.value.borrow()
594 borrowed = self.value.borrow()
585 }
595 }
586 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
596 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
587 }
597 }
588
598
589 fn get_mut_or_init<E>(
599 fn get_mut_or_init<E>(
590 &self,
600 &self,
591 init: impl Fn() -> Result<T, E>,
601 init: impl Fn() -> Result<T, E>,
592 ) -> Result<RefMut<T>, E> {
602 ) -> Result<RefMut<T>, E> {
593 let mut borrowed = self.value.borrow_mut();
603 let mut borrowed = self.value.borrow_mut();
594 if borrowed.is_none() {
604 if borrowed.is_none() {
595 *borrowed = Some(init()?);
605 *borrowed = Some(init()?);
596 }
606 }
597 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
607 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
598 }
608 }
599 }
609 }
General Comments 0
You need to be logged in to leave comments. Login now