##// END OF EJS Templates
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
Raphaël Gomès -
r51551:1e2c6cda stable
parent child Browse files
Show More
@@ -1,736 +1,745 b''
1 1 use crate::changelog::Changelog;
2 2 use crate::config::{Config, ConfigError, ConfigParseError};
3 3 use crate::dirstate::DirstateParents;
4 4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
5 5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 6 use crate::dirstate_tree::owning::OwningDirstateMap;
7 7 use crate::errors::HgResultExt;
8 8 use crate::errors::{HgError, IoResultExt};
9 9 use crate::lock::{try_with_lock_no_wait, LockError};
10 10 use crate::manifest::{Manifest, Manifestlog};
11 11 use crate::revlog::filelog::Filelog;
12 12 use crate::revlog::RevlogError;
13 13 use crate::utils::debug::debug_wait_for_file_or_print;
14 14 use crate::utils::files::get_path_from_bytes;
15 15 use crate::utils::hg_path::HgPath;
16 16 use crate::utils::SliceExt;
17 17 use crate::vfs::{is_dir, is_file, Vfs};
18 18 use crate::{requirements, NodePrefix};
19 19 use crate::{DirstateError, Revision};
20 20 use std::cell::{Ref, RefCell, RefMut};
21 21 use std::collections::HashSet;
22 22 use std::io::Seek;
23 23 use std::io::SeekFrom;
24 24 use std::io::Write as IoWrite;
25 25 use std::path::{Path, PathBuf};
26 26
27 27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28 28
29 29 type DirstateMapIdentity = (Option<u64>, Option<Vec<u8>>, usize);
30 30
31 31 /// A repository on disk
32 32 pub struct Repo {
33 33 working_directory: PathBuf,
34 34 dot_hg: PathBuf,
35 35 store: PathBuf,
36 36 requirements: HashSet<String>,
37 37 config: Config,
38 38 dirstate_parents: LazyCell<DirstateParents>,
39 39 dirstate_map: LazyCell<OwningDirstateMap>,
40 40 changelog: LazyCell<Changelog>,
41 41 manifestlog: LazyCell<Manifestlog>,
42 42 }
43 43
44 44 #[derive(Debug, derive_more::From)]
45 45 pub enum RepoError {
46 46 NotFound {
47 47 at: PathBuf,
48 48 },
49 49 #[from]
50 50 ConfigParseError(ConfigParseError),
51 51 #[from]
52 52 Other(HgError),
53 53 }
54 54
55 55 impl From<ConfigError> for RepoError {
56 56 fn from(error: ConfigError) -> Self {
57 57 match error {
58 58 ConfigError::Parse(error) => error.into(),
59 59 ConfigError::Other(error) => error.into(),
60 60 }
61 61 }
62 62 }
63 63
64 64 impl Repo {
65 65 /// tries to find nearest repository root in current working directory or
66 66 /// its ancestors
67 67 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
68 68 let current_directory = crate::utils::current_dir()?;
69 69 // ancestors() is inclusive: it first yields `current_directory`
70 70 // as-is.
71 71 for ancestor in current_directory.ancestors() {
72 72 if is_dir(ancestor.join(".hg"))? {
73 73 return Ok(ancestor.to_path_buf());
74 74 }
75 75 }
76 76 Err(RepoError::NotFound {
77 77 at: current_directory,
78 78 })
79 79 }
80 80
81 81 /// Find a repository, either at the given path (which must contain a `.hg`
82 82 /// sub-directory) or by searching the current directory and its
83 83 /// ancestors.
84 84 ///
85 85 /// A method with two very different "modes" like this usually a code smell
86 86 /// to make two methods instead, but in this case an `Option` is what rhg
87 87 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
88 88 /// Having two methods would just move that `if` to almost all callers.
89 89 pub fn find(
90 90 config: &Config,
91 91 explicit_path: Option<PathBuf>,
92 92 ) -> Result<Self, RepoError> {
93 93 if let Some(root) = explicit_path {
94 94 if is_dir(root.join(".hg"))? {
95 95 Self::new_at_path(root, config)
96 96 } else if is_file(&root)? {
97 97 Err(HgError::unsupported("bundle repository").into())
98 98 } else {
99 99 Err(RepoError::NotFound { at: root })
100 100 }
101 101 } else {
102 102 let root = Self::find_repo_root()?;
103 103 Self::new_at_path(root, config)
104 104 }
105 105 }
106 106
107 107 /// To be called after checking that `.hg` is a sub-directory
108 108 fn new_at_path(
109 109 working_directory: PathBuf,
110 110 config: &Config,
111 111 ) -> Result<Self, RepoError> {
112 112 let dot_hg = working_directory.join(".hg");
113 113
114 114 let mut repo_config_files =
115 115 vec![dot_hg.join("hgrc"), dot_hg.join("hgrc-not-shared")];
116 116
117 117 let hg_vfs = Vfs { base: &dot_hg };
118 118 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 119 let relative =
120 120 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 121 let shared =
122 122 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123 123
124 124 // From `mercurial/localrepo.py`:
125 125 //
126 126 // if .hg/requires contains the sharesafe requirement, it means
127 127 // there exists a `.hg/store/requires` too and we should read it
128 128 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 129 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 130 // is not present, refer checkrequirementscompat() for that
131 131 //
132 132 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 133 // repository was shared the old way. We check the share source
134 134 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 135 // current repository needs to be reshared
136 136 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137 137
138 138 let store_path;
139 139 if !shared {
140 140 store_path = dot_hg.join("store");
141 141 } else {
142 142 let bytes = hg_vfs.read("sharedpath")?;
143 143 let mut shared_path =
144 144 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 145 .to_owned();
146 146 if relative {
147 147 shared_path = dot_hg.join(shared_path)
148 148 }
149 149 if !is_dir(&shared_path)? {
150 150 return Err(HgError::corrupted(format!(
151 151 ".hg/sharedpath points to nonexistent directory {}",
152 152 shared_path.display()
153 153 ))
154 154 .into());
155 155 }
156 156
157 157 store_path = shared_path.join("store");
158 158
159 159 let source_is_share_safe =
160 160 requirements::load(Vfs { base: &shared_path })?
161 161 .contains(requirements::SHARESAFE_REQUIREMENT);
162 162
163 163 if share_safe != source_is_share_safe {
164 164 return Err(HgError::unsupported("share-safe mismatch").into());
165 165 }
166 166
167 167 if share_safe {
168 168 repo_config_files.insert(0, shared_path.join("hgrc"))
169 169 }
170 170 }
171 171 if share_safe {
172 172 reqs.extend(requirements::load(Vfs { base: &store_path })?);
173 173 }
174 174
175 175 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
176 176 config.combine_with_repo(&repo_config_files)?
177 177 } else {
178 178 config.clone()
179 179 };
180 180
181 181 let repo = Self {
182 182 requirements: reqs,
183 183 working_directory,
184 184 store: store_path,
185 185 dot_hg,
186 186 config: repo_config,
187 187 dirstate_parents: LazyCell::new(),
188 188 dirstate_map: LazyCell::new(),
189 189 changelog: LazyCell::new(),
190 190 manifestlog: LazyCell::new(),
191 191 };
192 192
193 193 requirements::check(&repo)?;
194 194
195 195 Ok(repo)
196 196 }
197 197
198 198 pub fn working_directory_path(&self) -> &Path {
199 199 &self.working_directory
200 200 }
201 201
202 202 pub fn requirements(&self) -> &HashSet<String> {
203 203 &self.requirements
204 204 }
205 205
206 206 pub fn config(&self) -> &Config {
207 207 &self.config
208 208 }
209 209
210 210 /// For accessing repository files (in `.hg`), except for the store
211 211 /// (`.hg/store`).
212 212 pub fn hg_vfs(&self) -> Vfs<'_> {
213 213 Vfs { base: &self.dot_hg }
214 214 }
215 215
216 216 /// For accessing repository store files (in `.hg/store`)
217 217 pub fn store_vfs(&self) -> Vfs<'_> {
218 218 Vfs { base: &self.store }
219 219 }
220 220
221 221 /// For accessing the working copy
222 222 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 223 Vfs {
224 224 base: &self.working_directory,
225 225 }
226 226 }
227 227
228 228 pub fn try_with_wlock_no_wait<R>(
229 229 &self,
230 230 f: impl FnOnce() -> R,
231 231 ) -> Result<R, LockError> {
232 232 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 233 }
234 234
235 pub fn has_dirstate_v2(&self) -> bool {
235 /// Whether this repo should use dirstate-v2.
236 /// The presence of `dirstate-v2` in the requirements does not mean that
237 /// the on-disk dirstate is necessarily in version 2. In most cases,
238 /// a dirstate-v2 file will indeed be found, but in rare cases (like the
239 /// upgrade mechanism being cut short), the on-disk version will be a
240 /// v1 file.
241 /// Semantically, having a requirement only means that a client should be
242 /// able to understand the repo *if* it uses the requirement, but not that
243 /// the requirement is actually used.
244 pub fn use_dirstate_v2(&self) -> bool {
236 245 self.requirements
237 246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 247 }
239 248
240 249 pub fn has_sparse(&self) -> bool {
241 250 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 251 }
243 252
244 253 pub fn has_narrow(&self) -> bool {
245 254 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 255 }
247 256
248 257 pub fn has_nodemap(&self) -> bool {
249 258 self.requirements
250 259 .contains(requirements::NODEMAP_REQUIREMENT)
251 260 }
252 261
253 262 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 263 Ok(self
255 264 .hg_vfs()
256 265 .read("dirstate")
257 266 .io_not_found_as_none()?
258 267 .unwrap_or_default())
259 268 }
260 269
261 270 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
262 271 use std::os::unix::fs::MetadataExt;
263 272 Ok(self
264 273 .hg_vfs()
265 274 .symlink_metadata("dirstate")
266 275 .io_not_found_as_none()?
267 276 .map(|meta| meta.ino()))
268 277 }
269 278
270 279 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
271 280 Ok(*self
272 281 .dirstate_parents
273 282 .get_or_init(|| self.read_dirstate_parents())?)
274 283 }
275 284
276 285 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
277 286 let dirstate = self.dirstate_file_contents()?;
278 287 let parents = if dirstate.is_empty() {
279 288 DirstateParents::NULL
280 } else if self.has_dirstate_v2() {
289 } else if self.use_dirstate_v2() {
281 290 let docket =
282 291 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
283 292 docket.parents()
284 293 } else {
285 294 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
286 295 };
287 296 self.dirstate_parents.set(parents);
288 297 Ok(parents)
289 298 }
290 299
291 300 /// Returns the information read from the dirstate docket necessary to
292 301 /// check if the data file has been updated/deleted by another process
293 302 /// since we last read the dirstate.
294 303 /// Namely, the inode, data file uuid and the data size.
295 304 fn get_dirstate_data_file_integrity(
296 305 &self,
297 306 ) -> Result<DirstateMapIdentity, HgError> {
298 307 assert!(
299 self.has_dirstate_v2(),
308 self.use_dirstate_v2(),
300 309 "accessing dirstate data file ID without dirstate-v2"
301 310 );
302 311 // Get the identity before the contents since we could have a race
303 312 // between the two. Having an identity that is too old is fine, but
304 313 // one that is younger than the content change is bad.
305 314 let identity = self.dirstate_identity()?;
306 315 let dirstate = self.dirstate_file_contents()?;
307 316 if dirstate.is_empty() {
308 317 self.dirstate_parents.set(DirstateParents::NULL);
309 318 Ok((identity, None, 0))
310 319 } else {
311 320 let docket =
312 321 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
313 322 self.dirstate_parents.set(docket.parents());
314 323 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
315 324 }
316 325 }
317 326
318 327 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
319 if self.has_dirstate_v2() {
328 if self.use_dirstate_v2() {
320 329 // The v2 dirstate is split into a docket and a data file.
321 330 // Since we don't always take the `wlock` to read it
322 331 // (like in `hg status`), it is susceptible to races.
323 332 // A simple retry method should be enough since full rewrites
324 333 // only happen when too much garbage data is present and
325 334 // this race is unlikely.
326 335 let mut tries = 0;
327 336
328 337 while tries < V2_MAX_READ_ATTEMPTS {
329 338 tries += 1;
330 339 match self.read_docket_and_data_file() {
331 340 Ok(m) => {
332 341 return Ok(m);
333 342 }
334 343 Err(e) => match e {
335 344 DirstateError::Common(HgError::RaceDetected(
336 345 context,
337 346 )) => {
338 347 log::info!(
339 348 "dirstate read race detected {} (retry {}/{})",
340 349 context,
341 350 tries,
342 351 V2_MAX_READ_ATTEMPTS,
343 352 );
344 353 continue;
345 354 }
346 355 _ => return Err(e),
347 356 },
348 357 }
349 358 }
350 359 let error = HgError::abort(
351 360 format!("dirstate read race happened {tries} times in a row"),
352 361 255,
353 362 None,
354 363 );
355 364 Err(DirstateError::Common(error))
356 365 } else {
357 366 debug_wait_for_file_or_print(
358 367 self.config(),
359 368 "dirstate.pre-read-file",
360 369 );
361 370 let identity = self.dirstate_identity()?;
362 371 let dirstate_file_contents = self.dirstate_file_contents()?;
363 372 if dirstate_file_contents.is_empty() {
364 373 self.dirstate_parents.set(DirstateParents::NULL);
365 374 Ok(OwningDirstateMap::new_empty(Vec::new()))
366 375 } else {
367 376 let (map, parents) = OwningDirstateMap::new_v1(
368 377 dirstate_file_contents,
369 378 identity,
370 379 )?;
371 380 self.dirstate_parents.set(parents);
372 381 Ok(map)
373 382 }
374 383 }
375 384 }
376 385
377 386 fn read_docket_and_data_file(
378 387 &self,
379 388 ) -> Result<OwningDirstateMap, DirstateError> {
380 389 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
381 390 let dirstate_file_contents = self.dirstate_file_contents()?;
382 391 let identity = self.dirstate_identity()?;
383 392 if dirstate_file_contents.is_empty() {
384 393 self.dirstate_parents.set(DirstateParents::NULL);
385 394 return Ok(OwningDirstateMap::new_empty(Vec::new()));
386 395 }
387 396 let docket = crate::dirstate_tree::on_disk::read_docket(
388 397 &dirstate_file_contents,
389 398 )?;
390 399 debug_wait_for_file_or_print(
391 400 self.config(),
392 401 "dirstate.post-docket-read-file",
393 402 );
394 403 self.dirstate_parents.set(docket.parents());
395 404 let uuid = docket.uuid.to_owned();
396 405 let data_size = docket.data_size();
397 406
398 407 let context = "between reading dirstate docket and data file";
399 408 let race_error = HgError::RaceDetected(context.into());
400 409 let metadata = docket.tree_metadata();
401 410
402 411 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
403 412 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
404 413 let contents = self.hg_vfs().read(docket.data_filename());
405 414 let contents = match contents {
406 415 Ok(c) => c,
407 416 Err(HgError::IoError { error, context }) => {
408 417 match error.raw_os_error().expect("real os error") {
409 418 // 2 = ENOENT, No such file or directory
410 419 // 116 = ESTALE, Stale NFS file handle
411 420 //
412 421 // TODO match on `error.kind()` when
413 422 // `ErrorKind::StaleNetworkFileHandle` is stable.
414 423 2 | 116 => {
415 424 // Race where the data file was deleted right after
416 425 // we read the docket, try again
417 426 return Err(race_error.into());
418 427 }
419 428 _ => {
420 429 return Err(
421 430 HgError::IoError { error, context }.into()
422 431 )
423 432 }
424 433 }
425 434 }
426 435 Err(e) => return Err(e.into()),
427 436 };
428 437 OwningDirstateMap::new_v2(
429 438 contents, data_size, metadata, uuid, identity,
430 439 )
431 440 } else {
432 441 match self
433 442 .hg_vfs()
434 443 .mmap_open(docket.data_filename())
435 444 .io_not_found_as_none()
436 445 {
437 446 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
438 447 data_mmap, data_size, metadata, uuid, identity,
439 448 ),
440 449 Ok(None) => {
441 450 // Race where the data file was deleted right after we
442 451 // read the docket, try again
443 452 return Err(race_error.into());
444 453 }
445 454 Err(e) => return Err(e.into()),
446 455 }
447 456 }?;
448 457
449 458 let write_mode_config = self
450 459 .config()
451 460 .get_str(b"devel", b"dirstate.v2.data_update_mode")
452 461 .unwrap_or(Some("auto"))
453 462 .unwrap_or("auto"); // don't bother for devel options
454 463 let write_mode = match write_mode_config {
455 464 "auto" => DirstateMapWriteMode::Auto,
456 465 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
457 466 "force-append" => DirstateMapWriteMode::ForceAppend,
458 467 _ => DirstateMapWriteMode::Auto,
459 468 };
460 469
461 470 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
462 471
463 472 Ok(map)
464 473 }
465 474
466 475 pub fn dirstate_map(
467 476 &self,
468 477 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
469 478 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
470 479 }
471 480
472 481 pub fn dirstate_map_mut(
473 482 &self,
474 483 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
475 484 self.dirstate_map
476 485 .get_mut_or_init(|| self.new_dirstate_map())
477 486 }
478 487
479 488 fn new_changelog(&self) -> Result<Changelog, HgError> {
480 489 Changelog::open(&self.store_vfs(), self.has_nodemap())
481 490 }
482 491
483 492 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
484 493 self.changelog.get_or_init(|| self.new_changelog())
485 494 }
486 495
487 496 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
488 497 self.changelog.get_mut_or_init(|| self.new_changelog())
489 498 }
490 499
491 500 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
492 501 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
493 502 }
494 503
495 504 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
496 505 self.manifestlog.get_or_init(|| self.new_manifestlog())
497 506 }
498 507
499 508 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
500 509 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
501 510 }
502 511
503 512 /// Returns the manifest of the *changeset* with the given node ID
504 513 pub fn manifest_for_node(
505 514 &self,
506 515 node: impl Into<NodePrefix>,
507 516 ) -> Result<Manifest, RevlogError> {
508 517 self.manifestlog()?.data_for_node(
509 518 self.changelog()?
510 519 .data_for_node(node.into())?
511 520 .manifest_node()?
512 521 .into(),
513 522 )
514 523 }
515 524
516 525 /// Returns the manifest of the *changeset* with the given revision number
517 526 pub fn manifest_for_rev(
518 527 &self,
519 528 revision: Revision,
520 529 ) -> Result<Manifest, RevlogError> {
521 530 self.manifestlog()?.data_for_node(
522 531 self.changelog()?
523 532 .data_for_rev(revision)?
524 533 .manifest_node()?
525 534 .into(),
526 535 )
527 536 }
528 537
529 538 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
530 539 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
531 540 Ok(entry.tracked())
532 541 } else {
533 542 Ok(false)
534 543 }
535 544 }
536 545
537 546 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
538 547 Filelog::open(self, path)
539 548 }
540 549
541 550 /// Write to disk any updates that were made through `dirstate_map_mut`.
542 551 ///
543 552 /// The "wlock" must be held while calling this.
544 553 /// See for example `try_with_wlock_no_wait`.
545 554 ///
546 555 /// TODO: have a `WritableRepo` type only accessible while holding the
547 556 /// lock?
548 557 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
549 558 let map = self.dirstate_map()?;
550 559 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
551 560 // it’s unset
552 561 let parents = self.dirstate_parents()?;
553 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
562 let (packed_dirstate, old_uuid_to_remove) = if self.use_dirstate_v2() {
554 563 let (identity, uuid, data_size) =
555 564 self.get_dirstate_data_file_integrity()?;
556 565 let identity_changed = identity != map.old_identity();
557 566 let uuid_changed = uuid.as_deref() != map.old_uuid();
558 567 let data_length_changed = data_size != map.old_data_size();
559 568
560 569 if identity_changed || uuid_changed || data_length_changed {
561 570 // If any of identity, uuid or length have changed since
562 571 // last disk read, don't write.
563 572 // This is fine because either we're in a command that doesn't
564 573 // write anything too important (like `hg status`), or we're in
565 574 // `hg add` and we're supposed to have taken the lock before
566 575 // reading anyway.
567 576 //
568 577 // TODO complain loudly if we've changed anything important
569 578 // without taking the lock.
570 579 // (see `hg help config.format.use-dirstate-tracked-hint`)
571 580 log::debug!(
572 581 "dirstate has changed since last read, not updating."
573 582 );
574 583 return Ok(());
575 584 }
576 585
577 586 let uuid_opt = map.old_uuid();
578 587 let write_mode = if uuid_opt.is_some() {
579 588 DirstateMapWriteMode::Auto
580 589 } else {
581 590 DirstateMapWriteMode::ForceNewDataFile
582 591 };
583 592 let (data, tree_metadata, append, old_data_size) =
584 593 map.pack_v2(write_mode)?;
585 594
586 595 // Reuse the uuid, or generate a new one, keeping the old for
587 596 // deletion.
588 597 let (uuid, old_uuid) = match uuid_opt {
589 598 Some(uuid) => {
590 599 let as_str = std::str::from_utf8(uuid)
591 600 .map_err(|_| {
592 601 HgError::corrupted(
593 602 "non-UTF-8 dirstate data file ID",
594 603 )
595 604 })?
596 605 .to_owned();
597 606 if append {
598 607 (as_str, None)
599 608 } else {
600 609 (DirstateDocket::new_uid(), Some(as_str))
601 610 }
602 611 }
603 612 None => (DirstateDocket::new_uid(), None),
604 613 };
605 614
606 615 let data_filename = format!("dirstate.{}", uuid);
607 616 let data_filename = self.hg_vfs().join(data_filename);
608 617 let mut options = std::fs::OpenOptions::new();
609 618 options.write(true);
610 619
611 620 // Why are we not using the O_APPEND flag when appending?
612 621 //
613 622 // - O_APPEND makes it trickier to deal with garbage at the end of
614 623 // the file, left by a previous uncommitted transaction. By
615 624 // starting the write at [old_data_size] we make sure we erase
616 625 // all such garbage.
617 626 //
618 627 // - O_APPEND requires to special-case 0-byte writes, whereas we
619 628 // don't need that.
620 629 //
621 630 // - Some OSes have bugs in implementation O_APPEND:
622 631 // revlog.py talks about a Solaris bug, but we also saw some ZFS
623 632 // bug: https://github.com/openzfs/zfs/pull/3124,
624 633 // https://github.com/openzfs/zfs/issues/13370
625 634 //
626 635 if !append {
627 636 log::trace!("creating a new dirstate data file");
628 637 options.create_new(true);
629 638 } else {
630 639 log::trace!("appending to the dirstate data file");
631 640 }
632 641
633 642 let data_size = (|| {
634 643 // TODO: loop and try another random ID if !append and this
635 644 // returns `ErrorKind::AlreadyExists`? Collision chance of two
636 645 // random IDs is one in 2**32
637 646 let mut file = options.open(&data_filename)?;
638 647 if append {
639 648 file.seek(SeekFrom::Start(old_data_size as u64))?;
640 649 }
641 650 file.write_all(&data)?;
642 651 file.flush()?;
643 652 file.seek(SeekFrom::Current(0))
644 653 })()
645 654 .when_writing_file(&data_filename)?;
646 655
647 656 let packed_dirstate = DirstateDocket::serialize(
648 657 parents,
649 658 tree_metadata,
650 659 data_size,
651 660 uuid.as_bytes(),
652 661 )
653 662 .map_err(|_: std::num::TryFromIntError| {
654 663 HgError::corrupted("overflow in dirstate docket serialization")
655 664 })?;
656 665
657 666 (packed_dirstate, old_uuid)
658 667 } else {
659 668 let identity = self.dirstate_identity()?;
660 669 if identity != map.old_identity() {
661 670 // If identity changed since last disk read, don't write.
662 671 // This is fine because either we're in a command that doesn't
663 672 // write anything too important (like `hg status`), or we're in
664 673 // `hg add` and we're supposed to have taken the lock before
665 674 // reading anyway.
666 675 //
667 676 // TODO complain loudly if we've changed anything important
668 677 // without taking the lock.
669 678 // (see `hg help config.format.use-dirstate-tracked-hint`)
670 679 log::debug!(
671 680 "dirstate has changed since last read, not updating."
672 681 );
673 682 return Ok(());
674 683 }
675 684 (map.pack_v1(parents)?, None)
676 685 };
677 686
678 687 let vfs = self.hg_vfs();
679 688 vfs.atomic_write("dirstate", &packed_dirstate)?;
680 689 if let Some(uuid) = old_uuid_to_remove {
681 690 // Remove the old data file after the new docket pointing to the
682 691 // new data file was written.
683 692 vfs.remove_file(format!("dirstate.{}", uuid))?;
684 693 }
685 694 Ok(())
686 695 }
687 696 }
688 697
689 698 /// Lazily-initialized component of `Repo` with interior mutability
690 699 ///
691 700 /// This differs from `OnceCell` in that the value can still be "deinitialized"
692 701 /// later by setting its inner `Option` to `None`. It also takes the
693 702 /// initialization function as an argument when the value is requested, not
694 703 /// when the instance is created.
695 704 struct LazyCell<T> {
696 705 value: RefCell<Option<T>>,
697 706 }
698 707
699 708 impl<T> LazyCell<T> {
700 709 fn new() -> Self {
701 710 Self {
702 711 value: RefCell::new(None),
703 712 }
704 713 }
705 714
706 715 fn set(&self, value: T) {
707 716 *self.value.borrow_mut() = Some(value)
708 717 }
709 718
710 719 fn get_or_init<E>(
711 720 &self,
712 721 init: impl Fn() -> Result<T, E>,
713 722 ) -> Result<Ref<T>, E> {
714 723 let mut borrowed = self.value.borrow();
715 724 if borrowed.is_none() {
716 725 drop(borrowed);
717 726 // Only use `borrow_mut` if it is really needed to avoid panic in
718 727 // case there is another outstanding borrow but mutation is not
719 728 // needed.
720 729 *self.value.borrow_mut() = Some(init()?);
721 730 borrowed = self.value.borrow()
722 731 }
723 732 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
724 733 }
725 734
726 735 fn get_mut_or_init<E>(
727 736 &self,
728 737 init: impl Fn() -> Result<T, E>,
729 738 ) -> Result<RefMut<T>, E> {
730 739 let mut borrowed = self.value.borrow_mut();
731 740 if borrowed.is_none() {
732 741 *borrowed = Some(init()?);
733 742 }
734 743 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
735 744 }
736 745 }
General Comments 0
You need to be logged in to leave comments. Login now