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