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