##// END OF EJS Templates
rust-repo: extract a function for checking nodemap requirement...
Martin von Zweigbergk -
r49982:9b5334c1 default
parent child Browse files
Show More
@@ -1,523 +1,522 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::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, HgError>,
32 dirstate_parents: LazyCell<DirstateParents, HgError>,
33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
34 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
34 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
35 changelog: LazyCell<Changelog, HgError>,
35 changelog: LazyCell<Changelog, HgError>,
36 manifestlog: LazyCell<Manifestlog, HgError>,
36 manifestlog: LazyCell<Manifestlog, HgError>,
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(Self::read_dirstate_parents),
185 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
186 dirstate_data_file_uuid: LazyCell::new(
186 dirstate_data_file_uuid: LazyCell::new(
187 Self::read_dirstate_data_file_uuid,
187 Self::read_dirstate_data_file_uuid,
188 ),
188 ),
189 dirstate_map: LazyCell::new(Self::new_dirstate_map),
189 dirstate_map: LazyCell::new(Self::new_dirstate_map),
190 changelog: LazyCell::new(Self::new_changelog),
190 changelog: LazyCell::new(Self::new_changelog),
191 manifestlog: LazyCell::new(Self::new_manifestlog),
191 manifestlog: LazyCell::new(Self::new_manifestlog),
192 };
192 };
193
193
194 requirements::check(&repo)?;
194 requirements::check(&repo)?;
195
195
196 Ok(repo)
196 Ok(repo)
197 }
197 }
198
198
199 pub fn working_directory_path(&self) -> &Path {
199 pub fn working_directory_path(&self) -> &Path {
200 &self.working_directory
200 &self.working_directory
201 }
201 }
202
202
203 pub fn requirements(&self) -> &HashSet<String> {
203 pub fn requirements(&self) -> &HashSet<String> {
204 &self.requirements
204 &self.requirements
205 }
205 }
206
206
207 pub fn config(&self) -> &Config {
207 pub fn config(&self) -> &Config {
208 &self.config
208 &self.config
209 }
209 }
210
210
211 /// For accessing repository files (in `.hg`), except for the store
211 /// For accessing repository files (in `.hg`), except for the store
212 /// (`.hg/store`).
212 /// (`.hg/store`).
213 pub fn hg_vfs(&self) -> Vfs<'_> {
213 pub fn hg_vfs(&self) -> Vfs<'_> {
214 Vfs { base: &self.dot_hg }
214 Vfs { base: &self.dot_hg }
215 }
215 }
216
216
217 /// For accessing repository store files (in `.hg/store`)
217 /// For accessing repository store files (in `.hg/store`)
218 pub fn store_vfs(&self) -> Vfs<'_> {
218 pub fn store_vfs(&self) -> Vfs<'_> {
219 Vfs { base: &self.store }
219 Vfs { base: &self.store }
220 }
220 }
221
221
222 /// For accessing the working copy
222 /// For accessing the working copy
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
224 Vfs {
224 Vfs {
225 base: &self.working_directory,
225 base: &self.working_directory,
226 }
226 }
227 }
227 }
228
228
229 pub fn try_with_wlock_no_wait<R>(
229 pub fn try_with_wlock_no_wait<R>(
230 &self,
230 &self,
231 f: impl FnOnce() -> R,
231 f: impl FnOnce() -> R,
232 ) -> Result<R, LockError> {
232 ) -> Result<R, LockError> {
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
234 }
234 }
235
235
236 pub fn has_dirstate_v2(&self) -> bool {
236 pub fn has_dirstate_v2(&self) -> bool {
237 self.requirements
237 self.requirements
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
239 }
239 }
240
240
241 pub fn has_sparse(&self) -> bool {
241 pub fn has_sparse(&self) -> bool {
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
243 }
243 }
244
244
245 pub fn has_narrow(&self) -> bool {
245 pub fn has_narrow(&self) -> bool {
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
247 }
247 }
248
248
249 pub fn has_nodemap(&self) -> bool {
250 self.requirements
251 .contains(requirements::NODEMAP_REQUIREMENT)
252 }
253
249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
250 Ok(self
255 Ok(self
251 .hg_vfs()
256 .hg_vfs()
252 .read("dirstate")
257 .read("dirstate")
253 .io_not_found_as_none()?
258 .io_not_found_as_none()?
254 .unwrap_or(Vec::new()))
259 .unwrap_or(Vec::new()))
255 }
260 }
256
261
257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
258 Ok(*self.dirstate_parents.get_or_init(self)?)
263 Ok(*self.dirstate_parents.get_or_init(self)?)
259 }
264 }
260
265
261 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
266 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
262 let dirstate = self.dirstate_file_contents()?;
267 let dirstate = self.dirstate_file_contents()?;
263 let parents = if dirstate.is_empty() {
268 let parents = if dirstate.is_empty() {
264 if self.has_dirstate_v2() {
269 if self.has_dirstate_v2() {
265 self.dirstate_data_file_uuid.set(None);
270 self.dirstate_data_file_uuid.set(None);
266 }
271 }
267 DirstateParents::NULL
272 DirstateParents::NULL
268 } else if self.has_dirstate_v2() {
273 } else if self.has_dirstate_v2() {
269 let docket =
274 let docket =
270 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
275 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
271 self.dirstate_data_file_uuid
276 self.dirstate_data_file_uuid
272 .set(Some(docket.uuid.to_owned()));
277 .set(Some(docket.uuid.to_owned()));
273 docket.parents()
278 docket.parents()
274 } else {
279 } else {
275 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
280 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
276 .clone()
281 .clone()
277 };
282 };
278 self.dirstate_parents.set(parents);
283 self.dirstate_parents.set(parents);
279 Ok(parents)
284 Ok(parents)
280 }
285 }
281
286
282 fn read_dirstate_data_file_uuid(
287 fn read_dirstate_data_file_uuid(
283 &self,
288 &self,
284 ) -> Result<Option<Vec<u8>>, HgError> {
289 ) -> Result<Option<Vec<u8>>, HgError> {
285 assert!(
290 assert!(
286 self.has_dirstate_v2(),
291 self.has_dirstate_v2(),
287 "accessing dirstate data file ID without dirstate-v2"
292 "accessing dirstate data file ID without dirstate-v2"
288 );
293 );
289 let dirstate = self.dirstate_file_contents()?;
294 let dirstate = self.dirstate_file_contents()?;
290 if dirstate.is_empty() {
295 if dirstate.is_empty() {
291 self.dirstate_parents.set(DirstateParents::NULL);
296 self.dirstate_parents.set(DirstateParents::NULL);
292 Ok(None)
297 Ok(None)
293 } else {
298 } else {
294 let docket =
299 let docket =
295 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
300 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
296 self.dirstate_parents.set(docket.parents());
301 self.dirstate_parents.set(docket.parents());
297 Ok(Some(docket.uuid.to_owned()))
302 Ok(Some(docket.uuid.to_owned()))
298 }
303 }
299 }
304 }
300
305
301 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
306 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
302 let dirstate_file_contents = self.dirstate_file_contents()?;
307 let dirstate_file_contents = self.dirstate_file_contents()?;
303 if dirstate_file_contents.is_empty() {
308 if dirstate_file_contents.is_empty() {
304 self.dirstate_parents.set(DirstateParents::NULL);
309 self.dirstate_parents.set(DirstateParents::NULL);
305 if self.has_dirstate_v2() {
310 if self.has_dirstate_v2() {
306 self.dirstate_data_file_uuid.set(None);
311 self.dirstate_data_file_uuid.set(None);
307 }
312 }
308 Ok(OwningDirstateMap::new_empty(Vec::new()))
313 Ok(OwningDirstateMap::new_empty(Vec::new()))
309 } else if self.has_dirstate_v2() {
314 } else if self.has_dirstate_v2() {
310 let docket = crate::dirstate_tree::on_disk::read_docket(
315 let docket = crate::dirstate_tree::on_disk::read_docket(
311 &dirstate_file_contents,
316 &dirstate_file_contents,
312 )?;
317 )?;
313 self.dirstate_parents.set(docket.parents());
318 self.dirstate_parents.set(docket.parents());
314 self.dirstate_data_file_uuid
319 self.dirstate_data_file_uuid
315 .set(Some(docket.uuid.to_owned()));
320 .set(Some(docket.uuid.to_owned()));
316 let data_size = docket.data_size();
321 let data_size = docket.data_size();
317 let metadata = docket.tree_metadata();
322 let metadata = docket.tree_metadata();
318 if let Some(data_mmap) = self
323 if let Some(data_mmap) = self
319 .hg_vfs()
324 .hg_vfs()
320 .mmap_open(docket.data_filename())
325 .mmap_open(docket.data_filename())
321 .io_not_found_as_none()?
326 .io_not_found_as_none()?
322 {
327 {
323 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
328 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
324 } else {
329 } else {
325 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
330 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
326 }
331 }
327 } else {
332 } else {
328 let (map, parents) =
333 let (map, parents) =
329 OwningDirstateMap::new_v1(dirstate_file_contents)?;
334 OwningDirstateMap::new_v1(dirstate_file_contents)?;
330 self.dirstate_parents.set(parents);
335 self.dirstate_parents.set(parents);
331 Ok(map)
336 Ok(map)
332 }
337 }
333 }
338 }
334
339
335 pub fn dirstate_map(
340 pub fn dirstate_map(
336 &self,
341 &self,
337 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
342 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
338 self.dirstate_map.get_or_init(self)
343 self.dirstate_map.get_or_init(self)
339 }
344 }
340
345
341 pub fn dirstate_map_mut(
346 pub fn dirstate_map_mut(
342 &self,
347 &self,
343 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
348 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
344 self.dirstate_map.get_mut_or_init(self)
349 self.dirstate_map.get_mut_or_init(self)
345 }
350 }
346
351
347 fn new_changelog(&self) -> Result<Changelog, HgError> {
352 fn new_changelog(&self) -> Result<Changelog, HgError> {
348 let use_nodemap = self
353 Changelog::open(&self.store_vfs(), self.has_nodemap())
349 .requirements
350 .contains(requirements::NODEMAP_REQUIREMENT);
351 Changelog::open(&self.store_vfs(), use_nodemap)
352 }
354 }
353
355
354 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
356 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
355 self.changelog.get_or_init(self)
357 self.changelog.get_or_init(self)
356 }
358 }
357
359
358 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
360 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
359 self.changelog.get_mut_or_init(self)
361 self.changelog.get_mut_or_init(self)
360 }
362 }
361
363
362 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
364 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
363 let use_nodemap = self
365 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
364 .requirements
365 .contains(requirements::NODEMAP_REQUIREMENT);
366 Manifestlog::open(&self.store_vfs(), use_nodemap)
367 }
366 }
368
367
369 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
368 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
370 self.manifestlog.get_or_init(self)
369 self.manifestlog.get_or_init(self)
371 }
370 }
372
371
373 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
372 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
374 self.manifestlog.get_mut_or_init(self)
373 self.manifestlog.get_mut_or_init(self)
375 }
374 }
376
375
377 /// Returns the manifest of the *changeset* with the given node ID
376 /// Returns the manifest of the *changeset* with the given node ID
378 pub fn manifest_for_node(
377 pub fn manifest_for_node(
379 &self,
378 &self,
380 node: impl Into<NodePrefix>,
379 node: impl Into<NodePrefix>,
381 ) -> Result<Manifest, RevlogError> {
380 ) -> Result<Manifest, RevlogError> {
382 self.manifestlog()?.data_for_node(
381 self.manifestlog()?.data_for_node(
383 self.changelog()?
382 self.changelog()?
384 .data_for_node(node.into())?
383 .data_for_node(node.into())?
385 .manifest_node()?
384 .manifest_node()?
386 .into(),
385 .into(),
387 )
386 )
388 }
387 }
389
388
390 /// Returns the manifest of the *changeset* with the given revision number
389 /// Returns the manifest of the *changeset* with the given revision number
391 pub fn manifest_for_rev(
390 pub fn manifest_for_rev(
392 &self,
391 &self,
393 revision: Revision,
392 revision: Revision,
394 ) -> Result<Manifest, RevlogError> {
393 ) -> Result<Manifest, RevlogError> {
395 self.manifestlog()?.data_for_node(
394 self.manifestlog()?.data_for_node(
396 self.changelog()?
395 self.changelog()?
397 .data_for_rev(revision)?
396 .data_for_rev(revision)?
398 .manifest_node()?
397 .manifest_node()?
399 .into(),
398 .into(),
400 )
399 )
401 }
400 }
402
401
403 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
402 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
404 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
403 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
405 Ok(entry.state().is_tracked())
404 Ok(entry.state().is_tracked())
406 } else {
405 } else {
407 Ok(false)
406 Ok(false)
408 }
407 }
409 }
408 }
410
409
411 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
410 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
412 Filelog::open(self, path)
411 Filelog::open(self, path)
413 }
412 }
414
413
415 /// Write to disk any updates that were made through `dirstate_map_mut`.
414 /// Write to disk any updates that were made through `dirstate_map_mut`.
416 ///
415 ///
417 /// The "wlock" must be held while calling this.
416 /// The "wlock" must be held while calling this.
418 /// See for example `try_with_wlock_no_wait`.
417 /// See for example `try_with_wlock_no_wait`.
419 ///
418 ///
420 /// TODO: have a `WritableRepo` type only accessible while holding the
419 /// TODO: have a `WritableRepo` type only accessible while holding the
421 /// lock?
420 /// lock?
422 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
421 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
423 let map = self.dirstate_map()?;
422 let map = self.dirstate_map()?;
424 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
423 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
425 // it’s unset
424 // it’s unset
426 let parents = self.dirstate_parents()?;
425 let parents = self.dirstate_parents()?;
427 let packed_dirstate = if self.has_dirstate_v2() {
426 let packed_dirstate = if self.has_dirstate_v2() {
428 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
427 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
429 let mut uuid = uuid.as_ref();
428 let mut uuid = uuid.as_ref();
430 let can_append = uuid.is_some();
429 let can_append = uuid.is_some();
431 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
430 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
432 if !append {
431 if !append {
433 uuid = None
432 uuid = None
434 }
433 }
435 let uuid = if let Some(uuid) = uuid {
434 let uuid = if let Some(uuid) = uuid {
436 std::str::from_utf8(uuid)
435 std::str::from_utf8(uuid)
437 .map_err(|_| {
436 .map_err(|_| {
438 HgError::corrupted("non-UTF-8 dirstate data file ID")
437 HgError::corrupted("non-UTF-8 dirstate data file ID")
439 })?
438 })?
440 .to_owned()
439 .to_owned()
441 } else {
440 } else {
442 DirstateDocket::new_uid()
441 DirstateDocket::new_uid()
443 };
442 };
444 let data_filename = format!("dirstate.{}", uuid);
443 let data_filename = format!("dirstate.{}", uuid);
445 let data_filename = self.hg_vfs().join(data_filename);
444 let data_filename = self.hg_vfs().join(data_filename);
446 let mut options = std::fs::OpenOptions::new();
445 let mut options = std::fs::OpenOptions::new();
447 if append {
446 if append {
448 options.append(true);
447 options.append(true);
449 } else {
448 } else {
450 options.write(true).create_new(true);
449 options.write(true).create_new(true);
451 }
450 }
452 let data_size = (|| {
451 let data_size = (|| {
453 // TODO: loop and try another random ID if !append and this
452 // TODO: loop and try another random ID if !append and this
454 // returns `ErrorKind::AlreadyExists`? Collision chance of two
453 // returns `ErrorKind::AlreadyExists`? Collision chance of two
455 // random IDs is one in 2**32
454 // random IDs is one in 2**32
456 let mut file = options.open(&data_filename)?;
455 let mut file = options.open(&data_filename)?;
457 file.write_all(&data)?;
456 file.write_all(&data)?;
458 file.flush()?;
457 file.flush()?;
459 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
458 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
460 file.seek(SeekFrom::Current(0))
459 file.seek(SeekFrom::Current(0))
461 })()
460 })()
462 .when_writing_file(&data_filename)?;
461 .when_writing_file(&data_filename)?;
463 DirstateDocket::serialize(
462 DirstateDocket::serialize(
464 parents,
463 parents,
465 tree_metadata,
464 tree_metadata,
466 data_size,
465 data_size,
467 uuid.as_bytes(),
466 uuid.as_bytes(),
468 )
467 )
469 .map_err(|_: std::num::TryFromIntError| {
468 .map_err(|_: std::num::TryFromIntError| {
470 HgError::corrupted("overflow in dirstate docket serialization")
469 HgError::corrupted("overflow in dirstate docket serialization")
471 })?
470 })?
472 } else {
471 } else {
473 map.pack_v1(parents)?
472 map.pack_v1(parents)?
474 };
473 };
475 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
474 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
476 Ok(())
475 Ok(())
477 }
476 }
478 }
477 }
479
478
480 /// Lazily-initialized component of `Repo` with interior mutability
479 /// Lazily-initialized component of `Repo` with interior mutability
481 ///
480 ///
482 /// This differs from `OnceCell` in that the value can still be "deinitialized"
481 /// This differs from `OnceCell` in that the value can still be "deinitialized"
483 /// later by setting its inner `Option` to `None`.
482 /// later by setting its inner `Option` to `None`.
484 struct LazyCell<T, E> {
483 struct LazyCell<T, E> {
485 value: RefCell<Option<T>>,
484 value: RefCell<Option<T>>,
486 // `Fn`s that don’t capture environment are zero-size, so this box does
485 // `Fn`s that don’t capture environment are zero-size, so this box does
487 // not allocate:
486 // not allocate:
488 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
487 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
489 }
488 }
490
489
491 impl<T, E> LazyCell<T, E> {
490 impl<T, E> LazyCell<T, E> {
492 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
491 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
493 Self {
492 Self {
494 value: RefCell::new(None),
493 value: RefCell::new(None),
495 init: Box::new(init),
494 init: Box::new(init),
496 }
495 }
497 }
496 }
498
497
499 fn set(&self, value: T) {
498 fn set(&self, value: T) {
500 *self.value.borrow_mut() = Some(value)
499 *self.value.borrow_mut() = Some(value)
501 }
500 }
502
501
503 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
502 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
504 let mut borrowed = self.value.borrow();
503 let mut borrowed = self.value.borrow();
505 if borrowed.is_none() {
504 if borrowed.is_none() {
506 drop(borrowed);
505 drop(borrowed);
507 // Only use `borrow_mut` if it is really needed to avoid panic in
506 // Only use `borrow_mut` if it is really needed to avoid panic in
508 // case there is another outstanding borrow but mutation is not
507 // case there is another outstanding borrow but mutation is not
509 // needed.
508 // needed.
510 *self.value.borrow_mut() = Some((self.init)(repo)?);
509 *self.value.borrow_mut() = Some((self.init)(repo)?);
511 borrowed = self.value.borrow()
510 borrowed = self.value.borrow()
512 }
511 }
513 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
512 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
514 }
513 }
515
514
516 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
515 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
517 let mut borrowed = self.value.borrow_mut();
516 let mut borrowed = self.value.borrow_mut();
518 if borrowed.is_none() {
517 if borrowed.is_none() {
519 *borrowed = Some((self.init)(repo)?);
518 *borrowed = Some((self.init)(repo)?);
520 }
519 }
521 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
520 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
522 }
521 }
523 }
522 }
General Comments 0
You need to be logged in to leave comments. Login now