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