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