##// END OF EJS Templates
rust-repo: add a method to get a `Node` from a `Revision` to the `Repo`...
rust-repo: add a method to get a `Node` from a `Revision` to the `Repo` This is going to be more and more useful as we start writing higher-level code

File last commit:

r52928:babfa9dd default
r52928:babfa9dd default
Show More
repo.rs
855 lines | 30.5 KiB | application/rls-services+xml | RustLexer
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 use crate::changelog::Changelog;
Simon Sapin
rhg: Parse per-repository configuration...
r47215 use crate::config::{Config, ConfigError, ConfigParseError};
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 use crate::dirstate::DirstateParents;
dirstate: use more than a bool to control append behavior...
r51116 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 use crate::dirstate_tree::owning::OwningDirstateMap;
use crate::errors::HgResultExt;
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 use crate::errors::{HgError, IoResultExt};
Simon Sapin
rhg: Initial repository locking...
r49245 use crate::lock::{try_with_lock_no_wait, LockError};
Simon Sapin
rust: Add Repo::manifest(revision)...
r48774 use crate::manifest::{Manifest, Manifestlog};
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 use crate::requirements::{
CHANGELOGV2_REQUIREMENT, GENERALDELTA_REQUIREMENT, NODEMAP_REQUIREMENT,
REVLOGV1_REQUIREMENT, REVLOGV2_REQUIREMENT,
};
Simon Sapin
rust: Add a Filelog struct that wraps Revlog...
r48775 use crate::revlog::filelog::Filelog;
Raphaël Gomès
rust-clippy: merge "revlog" module definition and struct implementation...
r50832 use crate::revlog::RevlogError;
dirstate: add a synchronisation point before doing a full dirstate read...
r51123 use crate::utils::debug::debug_wait_for_file_or_print;
Simon Sapin
rhg: initial support for shared repositories...
r47190 use crate::utils::files::get_path_from_bytes;
Simon Sapin
rust: Add a Filelog struct that wraps Revlog...
r48775 use crate::utils::hg_path::HgPath;
Simon Sapin
rhg: Don’t make repository path absolute too early...
r47474 use crate::utils::SliceExt;
Raphaël Gomès
rust: add Vfs trait...
r52761 use crate::vfs::{is_dir, is_file, VfsImpl};
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 use crate::{
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 requirements, NodePrefix, RevlogDataConfig, RevlogDeltaConfig,
RevlogFeatureConfig, RevlogType, RevlogVersionOptions, UncheckedRevision,
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 };
use crate::{DirstateError, RevlogOpenOptions};
Simon Sapin
rhg: Make Repo::dirstate_parents a LazyCell...
r49247 use std::cell::{Ref, RefCell, RefMut};
Simon Sapin
rhg: initial support for shared repositories...
r47190 use std::collections::HashSet;
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write as IoWrite;
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 use std::path::{Path, PathBuf};
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 const V2_MAX_READ_ATTEMPTS: usize = 5;
branching: merge stable into default...
r51147 type DirstateMapIdentity = (Option<u64>, Option<Vec<u8>>, usize);
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 /// A repository on disk
pub struct Repo {
working_directory: PathBuf,
dot_hg: PathBuf,
store: PathBuf,
Simon Sapin
rhg: initial support for shared repositories...
r47190 requirements: HashSet<String>,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 config: Config,
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 dirstate_parents: LazyCell<DirstateParents>,
dirstate_map: LazyCell<OwningDirstateMap>,
changelog: LazyCell<Changelog>,
manifestlog: LazyCell<Manifestlog>,
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }
Simon Sapin
rust: Fold find_root and check_requirements into Repo::find...
r47175 #[derive(Debug, derive_more::From)]
Simon Sapin
rhg: Parse per-repository configuration...
r47215 pub enum RepoError {
NotFound {
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 at: PathBuf,
Simon Sapin
rust: Fold find_root and check_requirements into Repo::find...
r47175 },
#[from]
Simon Sapin
rhg: Parse per-repository configuration...
r47215 ConfigParseError(ConfigParseError),
#[from]
Simon Sapin
rust: Fold find_root and check_requirements into Repo::find...
r47175 Other(HgError),
}
Simon Sapin
rhg: Parse per-repository configuration...
r47215 impl From<ConfigError> for RepoError {
fn from(error: ConfigError) -> Self {
match error {
ConfigError::Parse(error) => error.into(),
ConfigError::Other(error) => error.into(),
}
}
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 impl Repo {
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 /// tries to find nearest repository root in current working directory or
/// its ancestors
pub fn find_repo_root() -> Result<PathBuf, RepoError> {
let current_directory = crate::utils::current_dir()?;
// ancestors() is inclusive: it first yields `current_directory`
// as-is.
for ancestor in current_directory.ancestors() {
Simon Sapin
rhg: Propagate permission errors when finding a repository...
r48584 if is_dir(ancestor.join(".hg"))? {
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 return Ok(ancestor.to_path_buf());
}
}
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Err(RepoError::NotFound {
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 at: current_directory,
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 })
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 }
Simon Sapin
rhg: add limited support for the `config` sub-command...
r47255 /// Find a repository, either at the given path (which must contain a `.hg`
/// sub-directory) or by searching the current directory and its
/// ancestors.
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 ///
Simon Sapin
rhg: add limited support for the `config` sub-command...
r47255 /// A method with two very different "modes" like this usually a code smell
/// to make two methods instead, but in this case an `Option` is what rhg
/// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
/// Having two methods would just move that `if` to almost all callers.
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 pub fn find(
config: &Config,
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 explicit_path: Option<PathBuf>,
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 ) -> Result<Self, RepoError> {
if let Some(root) = explicit_path {
Simon Sapin
rhg: Propagate permission errors when finding a repository...
r48584 if is_dir(root.join(".hg"))? {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Self::new_at_path(root, config)
Simon Sapin
rhg: Propagate permission errors when finding a repository...
r48584 } else if is_file(&root)? {
Simon Sapin
rhg: Fall back to Python for bundle repositories...
r47464 Err(HgError::unsupported("bundle repository").into())
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Err(RepoError::NotFound { at: root })
Simon Sapin
rust: Fold find_root and check_requirements into Repo::find...
r47175 }
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253 } else {
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 let root = Self::find_repo_root()?;
Self::new_at_path(root, config)
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }
}
Simon Sapin
rhg: initial support for shared repositories...
r47190 /// To be called after checking that `.hg` is a sub-directory
Simon Sapin
rhg: Abort based on config on share-safe mismatch...
r47214 fn new_at_path(
working_directory: PathBuf,
config: &Config,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 ) -> Result<Self, RepoError> {
Simon Sapin
rhg: initial support for shared repositories...
r47190 let dot_hg = working_directory.join(".hg");
Simon Sapin
rhg: add support for share-safe...
r47191
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 let mut repo_config_files =
vec![dot_hg.join("hgrc"), dot_hg.join("hgrc-not-shared")];
Simon Sapin
rhg: Parse per-repository configuration...
r47215
Raphaël Gomès
rust: add Vfs trait...
r52761 let hg_vfs = VfsImpl {
base: dot_hg.to_owned(),
};
let mut reqs = requirements::load_if_exists(&hg_vfs)?;
Simon Sapin
rhg: initial support for shared repositories...
r47190 let relative =
reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
let shared =
reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
Simon Sapin
rhg: add support for share-safe...
r47191
// From `mercurial/localrepo.py`:
//
// if .hg/requires contains the sharesafe requirement, it means
// there exists a `.hg/store/requires` too and we should read it
// NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
// is present. We never write SHARESAFE_REQUIREMENT for a repo if store
// is not present, refer checkrequirementscompat() for that
//
// However, if SHARESAFE_REQUIREMENT is not present, it means that the
// repository was shared the old way. We check the share source
// .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
// current repository needs to be reshared
let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
Simon Sapin
rhg: initial support for shared repositories...
r47190 let store_path;
if !shared {
store_path = dot_hg.join("store");
} else {
let bytes = hg_vfs.read("sharedpath")?;
Simon Sapin
rhg: Ignore trailing newlines in .hg/sharedpath...
r47427 let mut shared_path =
Simon Sapin
rust: Generalize the `trim_end_newlines` utility of byte strings...
r48761 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
.to_owned();
Simon Sapin
rhg: initial support for shared repositories...
r47190 if relative {
shared_path = dot_hg.join(shared_path)
}
Simon Sapin
rhg: Propagate permission errors when finding a repository...
r48584 if !is_dir(&shared_path)? {
Simon Sapin
rhg: initial support for shared repositories...
r47190 return Err(HgError::corrupted(format!(
".hg/sharedpath points to nonexistent directory {}",
shared_path.display()
Simon Sapin
rhg: Parse per-repository configuration...
r47215 ))
.into());
Simon Sapin
rhg: initial support for shared repositories...
r47190 }
store_path = shared_path.join("store");
Simon Sapin
rhg: add support for share-safe...
r47191
Raphaël Gomès
rust: add Vfs trait...
r52761 let source_is_share_safe = requirements::load(VfsImpl {
base: shared_path.to_owned(),
})?
.contains(requirements::SHARESAFE_REQUIREMENT);
Simon Sapin
rhg: add support for share-safe...
r47191
Arseniy Alekseyev
rhg: simplify the handling of share-safe config mismatch...
r49664 if share_safe != source_is_share_safe {
return Err(HgError::unsupported("share-safe mismatch").into());
Simon Sapin
rhg: add support for share-safe...
r47191 }
Simon Sapin
rhg: Parse per-repository configuration...
r47215
if share_safe {
repo_config_files.insert(0, shared_path.join("hgrc"))
}
Simon Sapin
rhg: initial support for shared repositories...
r47190 }
Simon Sapin
rhg: Bug fix: with share-safe, always read store requirements...
r47357 if share_safe {
Raphaël Gomès
rust: add Vfs trait...
r52761 reqs.extend(requirements::load(VfsImpl {
base: store_path.to_owned(),
})?);
Simon Sapin
rhg: Bug fix: with share-safe, always read store requirements...
r47357 }
Simon Sapin
rhg: initial support for shared repositories...
r47190
Simon Sapin
rhg: Add support for the HGRCSKIPREPO environment variable...
r47475 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
config.combine_with_repo(&repo_config_files)?
} else {
config.clone()
};
Simon Sapin
rhg: Parse per-repository configuration...
r47215
Simon Sapin
rhg: initial support for shared repositories...
r47190 let repo = Self {
requirements: reqs,
working_directory,
store: store_path,
dot_hg,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 config: repo_config,
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 dirstate_parents: LazyCell::new(),
dirstate_map: LazyCell::new(),
changelog: LazyCell::new(),
manifestlog: LazyCell::new(),
Simon Sapin
rhg: initial support for shared repositories...
r47190 };
requirements::check(&repo)?;
Ok(repo)
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 pub fn working_directory_path(&self) -> &Path {
&self.working_directory
}
Simon Sapin
rhg: initial support for shared repositories...
r47190 pub fn requirements(&self) -> &HashSet<String> {
&self.requirements
}
Simon Sapin
rhg: Parse per-repository configuration...
r47215 pub fn config(&self) -> &Config {
&self.config
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 /// For accessing repository files (in `.hg`), except for the store
/// (`.hg/store`).
Raphaël Gomès
rust: add Vfs trait...
r52761 pub fn hg_vfs(&self) -> VfsImpl {
VfsImpl {
base: self.dot_hg.to_owned(),
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }
/// For accessing repository store files (in `.hg/store`)
Raphaël Gomès
rust: add Vfs trait...
r52761 pub fn store_vfs(&self) -> VfsImpl {
VfsImpl {
base: self.store.to_owned(),
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }
/// For accessing the working copy
Raphaël Gomès
rust: add Vfs trait...
r52761 pub fn working_directory_vfs(&self) -> VfsImpl {
VfsImpl {
base: self.working_directory.to_owned(),
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }
}
Simon Sapin
rhg: Add support for the blackbox extension...
r47343
Simon Sapin
rhg: Initial repository locking...
r49245 pub fn try_with_wlock_no_wait<R>(
&self,
f: impl FnOnce() -> R,
) -> Result<R, LockError> {
Raphaël Gomès
rust: add Vfs trait...
r52761 try_with_lock_no_wait(&self.hg_vfs(), "wlock", f)
Simon Sapin
rhg: Initial repository locking...
r49245 }
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 /// Whether this repo should use dirstate-v2.
/// The presence of `dirstate-v2` in the requirements does not mean that
/// the on-disk dirstate is necessarily in version 2. In most cases,
/// a dirstate-v2 file will indeed be found, but in rare cases (like the
/// upgrade mechanism being cut short), the on-disk version will be a
/// v1 file.
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 /// Semantically, having a requirement only means that a client cannot
/// properly understand or properly update the repo if it lacks the support
/// for the required feature, but not that that feature is actually used
/// in all occasions.
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 pub fn use_dirstate_v2(&self) -> bool {
Simon Sapin
rhg: Add support for dirstate-v2...
r48165 self.requirements
.contains(requirements::DIRSTATE_V2_REQUIREMENT)
}
Arseniy Alekseyev
rhg: add support for narrow clones and sparse checkouts...
r49238 pub fn has_sparse(&self) -> bool {
self.requirements.contains(requirements::SPARSE_REQUIREMENT)
}
pub fn has_narrow(&self) -> bool {
self.requirements.contains(requirements::NARROW_REQUIREMENT)
}
Martin von Zweigbergk
rust-repo: extract a function for checking nodemap requirement...
r49982 pub fn has_nodemap(&self) -> bool {
self.requirements
.contains(requirements::NODEMAP_REQUIREMENT)
}
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
Ok(self
.hg_vfs()
.read("dirstate")
.io_not_found_as_none()?
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 .unwrap_or_default())
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 }
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
use std::os::unix::fs::MetadataExt;
Ok(self
.hg_vfs()
.symlink_metadata("dirstate")
.io_not_found_as_none()?
.map(|meta| meta.ino()))
}
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 Ok(*self
.dirstate_parents
.get_or_init(|| self.read_dirstate_parents())?)
Simon Sapin
rhg: Make Repo::dirstate_parents a LazyCell...
r49247 }
fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 let dirstate = self.dirstate_file_contents()?;
let parents = if dirstate.is_empty() {
DirstateParents::NULL
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 } else if self.use_dirstate_v2() {
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 let docket_res =
crate::dirstate_tree::on_disk::read_docket(&dirstate);
match docket_res {
Ok(docket) => docket.parents(),
Err(_) => {
log::info!(
"Parsing dirstate docket failed, \
falling back to dirstate-v1"
);
*crate::dirstate::parsers::parse_dirstate_parents(
&dirstate,
)?
}
}
Simon Sapin
rhg: Add support for dirstate-v2...
r48165 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
Simon Sapin
rhg: Add support for dirstate-v2...
r48165 };
Simon Sapin
rhg: Make Repo::dirstate_parents a LazyCell...
r49247 self.dirstate_parents.set(parents);
Simon Sapin
dirstate-v2: Introduce a docket file...
r48474 Ok(parents)
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 }
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768
Raphaël Gomès
rust-dirstate-v2: don't write dirstate if data file has changed...
r51139 /// Returns the information read from the dirstate docket necessary to
/// check if the data file has been updated/deleted by another process
/// since we last read the dirstate.
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 /// Namely, the inode, data file uuid and the data size.
Raphaël Gomès
rust-dirstate-v2: don't write dirstate if data file has changed...
r51139 fn get_dirstate_data_file_integrity(
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 &self,
branching: merge stable into default...
r51147 ) -> Result<DirstateMapIdentity, HgError> {
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 assert!(
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 self.use_dirstate_v2(),
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 "accessing dirstate data file ID without dirstate-v2"
);
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 // Get the identity before the contents since we could have a race
// between the two. Having an identity that is too old is fine, but
// one that is younger than the content change is bad.
let identity = self.dirstate_identity()?;
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 let dirstate = self.dirstate_file_contents()?;
if dirstate.is_empty() {
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 Ok((identity, None, 0))
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 } else {
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 let docket_res =
crate::dirstate_tree::on_disk::read_docket(&dirstate);
match docket_res {
Ok(docket) => {
self.dirstate_parents.set(docket.parents());
Ok((
identity,
Some(docket.uuid.to_owned()),
docket.data_size(),
))
}
Err(_) => {
log::info!(
"Parsing dirstate docket failed, \
falling back to dirstate-v1"
);
let parents =
*crate::dirstate::parsers::parse_dirstate_parents(
&dirstate,
)?;
self.dirstate_parents.set(parents);
Ok((identity, None, 0))
}
}
Simon Sapin
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
r49248 }
}
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 if self.use_dirstate_v2() {
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 // The v2 dirstate is split into a docket and a data file.
// Since we don't always take the `wlock` to read it
// (like in `hg status`), it is susceptible to races.
// A simple retry method should be enough since full rewrites
// only happen when too much garbage data is present and
// this race is unlikely.
let mut tries = 0;
while tries < V2_MAX_READ_ATTEMPTS {
tries += 1;
match self.read_docket_and_data_file() {
Ok(m) => {
return Ok(m);
}
Err(e) => match e {
DirstateError::Common(HgError::RaceDetected(
context,
)) => {
log::info!(
"dirstate read race detected {} (retry {}/{})",
context,
tries,
V2_MAX_READ_ATTEMPTS,
);
continue;
}
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 _ => {
log::info!(
"Reading dirstate v2 failed, \
falling back to v1"
);
return self.new_dirstate_map_v1();
}
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 },
}
}
let error = HgError::abort(
format!("dirstate read race happened {tries} times in a row"),
255,
None,
);
branching: merge stable into default...
r51147 Err(DirstateError::Common(error))
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 } else {
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 self.new_dirstate_map_v1()
}
}
fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> {
debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
let identity = self.dirstate_identity()?;
let dirstate_file_contents = self.dirstate_file_contents()?;
if dirstate_file_contents.is_empty() {
self.dirstate_parents.set(DirstateParents::NULL);
Raphaël Gomès
rust-dirstate: actually remember the identity...
r52926 Ok(OwningDirstateMap::new_empty(Vec::new(), identity))
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 } else {
let (map, parents) =
OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
self.dirstate_parents.set(parents);
Ok(map)
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 }
}
fn read_docket_and_data_file(
&self,
) -> Result<OwningDirstateMap, DirstateError> {
dirstate: add a synchronisation point before doing a full dirstate read...
r51123 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 let dirstate_file_contents = self.dirstate_file_contents()?;
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 let identity = self.dirstate_identity()?;
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 if dirstate_file_contents.is_empty() {
Raphaël Gomès
rust-dirstate: actually remember the identity...
r52926 return Ok(OwningDirstateMap::new_empty(Vec::new(), identity));
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 }
let docket = crate::dirstate_tree::on_disk::read_docket(
&dirstate_file_contents,
)?;
Raphaël Gomès
dirstate: add a synchronisation point in the middle of the read...
r51129 debug_wait_for_file_or_print(
self.config(),
"dirstate.post-docket-read-file",
);
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 self.dirstate_parents.set(docket.parents());
Raphaël Gomès
rust-dirstate: remember the data file uuid dirstate was loaded with...
r51138 let uuid = docket.uuid.to_owned();
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 let data_size = docket.data_size();
dirstate: deal with read-race for pure rust code path (rhg)...
r51134
let context = "between reading dirstate docket and data file";
let race_error = HgError::RaceDetected(context.into());
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 let metadata = docket.tree_metadata();
dirstate: deal with read-race for pure rust code path (rhg)...
r51134
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
// Don't mmap on NFS to prevent `SIGBUS` error on deletion
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 let contents = self.hg_vfs().read(docket.data_filename());
let contents = match contents {
Ok(c) => c,
Err(HgError::IoError { error, context }) => {
match error.raw_os_error().expect("real os error") {
// 2 = ENOENT, No such file or directory
// 116 = ESTALE, Stale NFS file handle
//
// TODO match on `error.kind()` when
// `ErrorKind::StaleNetworkFileHandle` is stable.
2 | 116 => {
// Race where the data file was deleted right after
// we read the docket, try again
return Err(race_error.into());
}
_ => {
return Err(
HgError::IoError { error, context }.into()
)
}
}
}
Err(e) => return Err(e.into()),
};
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 OwningDirstateMap::new_v2(
contents, data_size, metadata, uuid, identity,
)
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 } else {
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 match self
.hg_vfs()
.mmap_open(docket.data_filename())
.io_not_found_as_none()
{
Raphaël Gomès
rust-dirstate: remember the data file uuid dirstate was loaded with...
r51138 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 data_mmap, data_size, metadata, uuid, identity,
Raphaël Gomès
rust-dirstate: remember the data file uuid dirstate was loaded with...
r51138 ),
dirstate: deal with read-race for pure rust code path (rhg)...
r51134 Ok(None) => {
// Race where the data file was deleted right after we
// read the docket, try again
return Err(race_error.into());
}
Err(e) => return Err(e.into()),
}
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 }?;
Raphaël Gomès
dirstate-v2: add devel config option to control write behavior...
r51117
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 let write_mode_config = self
.config()
.get_str(b"devel", b"dirstate.v2.data_update_mode")
.unwrap_or(Some("auto"))
.unwrap_or("auto"); // don't bother for devel options
let write_mode = match write_mode_config {
"auto" => DirstateMapWriteMode::Auto,
"force-new" => DirstateMapWriteMode::ForceNewDataFile,
"force-append" => DirstateMapWriteMode::ForceAppend,
_ => DirstateMapWriteMode::Auto,
};
Raphaël Gomès
dirstate-v2: add devel config option to control write behavior...
r51117
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
Raphaël Gomès
dirstate-v2: add devel config option to control write behavior...
r51117
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 Ok(map)
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 }
pub fn dirstate_map(
&self,
) -> Result<Ref<OwningDirstateMap>, DirstateError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 }
pub fn dirstate_map_mut(
&self,
) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.dirstate_map
.get_mut_or_init(|| self.new_dirstate_map())
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 }
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773
Martin von Zweigbergk
rust-revlog: make `Changelog` and `ManifestLog` unaware of `Repo`...
r49981 fn new_changelog(&self) -> Result<Changelog, HgError> {
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 Changelog::open(
&self.store_vfs(),
self.default_revlog_options(RevlogType::Changelog)?,
)
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 }
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773
Simon Sapin
rust: Return HgError instead of RevlogError in revlog constructors...
r48777 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.changelog.get_or_init(|| self.new_changelog())
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 }
Simon Sapin
rust: Return HgError instead of RevlogError in revlog constructors...
r48777 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.changelog.get_mut_or_init(|| self.new_changelog())
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 }
Martin von Zweigbergk
rust-revlog: make `Changelog` and `ManifestLog` unaware of `Repo`...
r49981 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 Manifestlog::open(
&self.store_vfs(),
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 self.default_revlog_options(RevlogType::Manifestlog)?,
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 )
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 }
Simon Sapin
rust: Return HgError instead of RevlogError in revlog constructors...
r48777 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.manifestlog.get_or_init(|| self.new_manifestlog())
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 }
Simon Sapin
rust: Return HgError instead of RevlogError in revlog constructors...
r48777 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
Simon Sapin
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
r48773 }
Simon Sapin
rust: Add Repo::manifest(revision)...
r48774
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 /// Returns the manifest of the *changeset* with the given node ID
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 pub fn manifest_for_node(
&self,
node: impl Into<NodePrefix>,
) -> Result<Manifest, RevlogError> {
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 self.manifestlog()?.data_for_node(
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 self.changelog()?
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 .data_for_node(node.into())?
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 .manifest_node()?
.into(),
)
}
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 /// Returns the manifest of the *changeset* with the given revision number
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 pub fn manifest_for_rev(
Simon Sapin
rust: Add Repo::manifest(revision)...
r48774 &self,
Raphaël Gomès
rust: use the new `UncheckedRevision` everywhere applicable...
r51870 revision: UncheckedRevision,
Simon Sapin
rust: Add Repo::manifest(revision)...
r48774 ) -> Result<Manifest, RevlogError> {
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 self.manifestlog()?.data_for_node(
self.changelog()?
.data_for_rev(revision)?
.manifest_node()?
.into(),
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 )
Simon Sapin
rust: Add Repo::manifest(revision)...
r48774 }
Simon Sapin
rust: Add a Filelog struct that wraps Revlog...
r48775
Simon Sapin
rhg: Sub-repositories are not supported...
r49341 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
Raphaël Gomès
rust: use `entry.tracked()` directly...
r50027 Ok(entry.tracked())
Simon Sapin
rhg: Sub-repositories are not supported...
r49341 } else {
Ok(false)
}
}
Simon Sapin
rust: Return HgError instead of RevlogError in revlog constructors...
r48777 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 Filelog::open(
self,
path,
self.default_revlog_options(RevlogType::Filelog)?,
)
Simon Sapin
rust: Add a Filelog struct that wraps Revlog...
r48775 }
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 /// Write to disk any updates that were made through `dirstate_map_mut`.
///
/// The "wlock" must be held while calling this.
/// See for example `try_with_wlock_no_wait`.
///
/// TODO: have a `WritableRepo` type only accessible while holding the
/// lock?
pub fn write_dirstate(&self) -> Result<(), DirstateError> {
let map = self.dirstate_map()?;
// TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
// it’s unset
let parents = self.dirstate_parents()?;
Raphaël Gomès
rust-dirstate: rename `has_dirstate_v2` to `use_dirstate_v2`...
r51551 let (packed_dirstate, old_uuid_to_remove) = if self.use_dirstate_v2() {
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 let (identity, uuid, data_size) =
self.get_dirstate_data_file_integrity()?;
let identity_changed = identity != map.old_identity();
Raphaël Gomès
rust-dirstate-v2: don't write dirstate if data file has changed...
r51139 let uuid_changed = uuid.as_deref() != map.old_uuid();
let data_length_changed = data_size != map.old_data_size();
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 if identity_changed || uuid_changed || data_length_changed {
// If any of identity, uuid or length have changed since
// last disk read, don't write.
Raphaël Gomès
rust-dirstate-v2: don't write dirstate if data file has changed...
r51139 // This is fine because either we're in a command that doesn't
// write anything too important (like `hg status`), or we're in
// `hg add` and we're supposed to have taken the lock before
// reading anyway.
//
// TODO complain loudly if we've changed anything important
// without taking the lock.
// (see `hg help config.format.use-dirstate-tracked-hint`)
log::debug!(
"dirstate has changed since last read, not updating."
);
return Ok(());
}
let uuid_opt = map.old_uuid();
dirstate: use more than a bool to control append behavior...
r51116 let write_mode = if uuid_opt.is_some() {
DirstateMapWriteMode::Auto
} else {
DirstateMapWriteMode::ForceNewDataFile
};
Raphaël Gomès
rust-dirstate-v2: save proper data size if no new data on append...
r50037 let (data, tree_metadata, append, old_data_size) =
dirstate: use more than a bool to control append behavior...
r51116 map.pack_v2(write_mode)?;
Raphaël Gomès
rhg: fix dirstate-v2 data file removal system...
r50044
// Reuse the uuid, or generate a new one, keeping the old for
// deletion.
let (uuid, old_uuid) = match uuid_opt {
Some(uuid) => {
let as_str = std::str::from_utf8(uuid)
.map_err(|_| {
HgError::corrupted(
"non-UTF-8 dirstate data file ID",
)
})?
.to_owned();
if append {
(as_str, None)
} else {
(DirstateDocket::new_uid(), Some(as_str))
}
}
None => (DirstateDocket::new_uid(), None),
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 };
Raphaël Gomès
rhg: fix dirstate-v2 data file removal system...
r50044
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 let data_filename = format!("dirstate.{}", uuid);
let data_filename = self.hg_vfs().join(data_filename);
let mut options = std::fs::OpenOptions::new();
Arseniy Alekseyev
rhg: align the dirstate v2 writing algorithm with python...
r50097 options.write(true);
// Why are we not using the O_APPEND flag when appending?
//
// - O_APPEND makes it trickier to deal with garbage at the end of
// the file, left by a previous uncommitted transaction. By
// starting the write at [old_data_size] we make sure we erase
// all such garbage.
//
// - O_APPEND requires to special-case 0-byte writes, whereas we
// don't need that.
//
// - Some OSes have bugs in implementation O_APPEND:
// revlog.py talks about a Solaris bug, but we also saw some ZFS
// bug: https://github.com/openzfs/zfs/pull/3124,
// https://github.com/openzfs/zfs/issues/13370
//
if !append {
Raphaël Gomès
dirstate: add some debug output when writing the dirstate...
r51108 log::trace!("creating a new dirstate data file");
Arseniy Alekseyev
rhg: align the dirstate v2 writing algorithm with python...
r50097 options.create_new(true);
Raphaël Gomès
dirstate: add some debug output when writing the dirstate...
r51108 } else {
log::trace!("appending to the dirstate data file");
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 }
Arseniy Alekseyev
rhg: align the dirstate v2 writing algorithm with python...
r50097
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 let data_size = (|| {
// TODO: loop and try another random ID if !append and this
// returns `ErrorKind::AlreadyExists`? Collision chance of two
// random IDs is one in 2**32
let mut file = options.open(&data_filename)?;
Arseniy Alekseyev
rhg: align the dirstate v2 writing algorithm with python...
r50097 if append {
file.seek(SeekFrom::Start(old_data_size as u64))?;
Raphaël Gomès
rust-dirstate-v2: save proper data size if no new data on append...
r50037 }
Arseniy Alekseyev
rhg: align the dirstate v2 writing algorithm with python...
r50097 file.write_all(&data)?;
file.flush()?;
Raphaël Gomès
rust: run a clippy pass with the latest stable version...
r52013 file.stream_position()
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 })()
.when_writing_file(&data_filename)?;
Raphaël Gomès
rust-dirstate-v2: clean up previous data file after the docket is written...
r50038
let packed_dirstate = DirstateDocket::serialize(
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 parents,
tree_metadata,
data_size,
uuid.as_bytes(),
)
.map_err(|_: std::num::TryFromIntError| {
HgError::corrupted("overflow in dirstate docket serialization")
Raphaël Gomès
rust-dirstate-v2: clean up previous data file after the docket is written...
r50038 })?;
(packed_dirstate, old_uuid)
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 } else {
Raphaël Gomès
rhg: remember the inode of .hg/dirstate...
r51140 let identity = self.dirstate_identity()?;
if identity != map.old_identity() {
// If identity changed since last disk read, don't write.
// This is fine because either we're in a command that doesn't
// write anything too important (like `hg status`), or we're in
// `hg add` and we're supposed to have taken the lock before
// reading anyway.
//
// TODO complain loudly if we've changed anything important
// without taking the lock.
// (see `hg help config.format.use-dirstate-tracked-hint`)
log::debug!(
"dirstate has changed since last read, not updating."
);
return Ok(());
}
Raphaël Gomès
rust-dirstate-v2: clean up previous data file after the docket is written...
r50038 (map.pack_v1(parents)?, None)
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 };
Raphaël Gomès
rust-dirstate-v2: clean up previous data file after the docket is written...
r50038
let vfs = self.hg_vfs();
vfs.atomic_write("dirstate", &packed_dirstate)?;
if let Some(uuid) = old_uuid_to_remove {
// Remove the old data file after the new docket pointing to the
// new data file was written.
vfs.remove_file(format!("dirstate.{}", uuid))?;
}
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 Ok(())
}
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084
pub fn default_revlog_options(
&self,
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 revlog_type: RevlogType,
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 ) -> Result<RevlogOpenOptions, HgError> {
let requirements = self.requirements();
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 let is_changelog = revlog_type == RevlogType::Changelog;
let version = if is_changelog
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 && requirements.contains(CHANGELOGV2_REQUIREMENT)
{
let compute_rank = self
.config()
.get_bool(b"experimental", b"changelog-v2.compute-rank")?;
RevlogVersionOptions::ChangelogV2 { compute_rank }
} else if requirements.contains(REVLOGV2_REQUIREMENT) {
RevlogVersionOptions::V2
} else if requirements.contains(REVLOGV1_REQUIREMENT) {
RevlogVersionOptions::V1 {
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 general_delta: requirements.contains(GENERALDELTA_REQUIREMENT),
inline: !is_changelog,
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 }
} else {
RevlogVersionOptions::V0
};
Ok(RevlogOpenOptions {
version,
// We don't need to dance around the slow path like in the Python
// implementation since we know we have access to the fast code.
use_nodemap: requirements.contains(NODEMAP_REQUIREMENT),
Raphaël Gomès
rust: use new revlog configs in all revlog opening code...
r52760 delta_config: RevlogDeltaConfig::new(
self.config(),
self.requirements(),
revlog_type,
)?,
data_config: RevlogDataConfig::new(
self.config(),
self.requirements(),
)?,
feature_config: RevlogFeatureConfig::new(
self.config(),
requirements,
)?,
Raphaël Gomès
rust-revlog: teach the revlog opening code to read the repo options...
r52084 })
}
Raphaël Gomès
rust-repo: add a method to get a `Node` from a `Revision` to the `Repo`...
r52928
pub fn node(&self, rev: UncheckedRevision) -> Option<crate::Node> {
self.changelog()
.ok()
.and_then(|c| c.node_from_rev(rev).copied())
}
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 }
/// Lazily-initialized component of `Repo` with interior mutability
///
/// This differs from `OnceCell` in that the value can still be "deinitialized"
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 /// later by setting its inner `Option` to `None`. It also takes the
/// initialization function as an argument when the value is requested, not
/// when the instance is created.
struct LazyCell<T> {
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 value: RefCell<Option<T>>,
}
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 impl<T> LazyCell<T> {
fn new() -> Self {
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 Self {
value: RefCell::new(None),
}
}
Simon Sapin
rhg: Make Repo::dirstate_parents a LazyCell...
r49247 fn set(&self, value: T) {
*self.value.borrow_mut() = Some(value)
}
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 fn get_or_init<E>(
&self,
init: impl Fn() -> Result<T, E>,
) -> Result<Ref<T>, E> {
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 let mut borrowed = self.value.borrow();
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 if borrowed.is_none() {
drop(borrowed);
// Only use `borrow_mut` if it is really needed to avoid panic in
// case there is another outstanding borrow but mutation is not
// needed.
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 *self.value.borrow_mut() = Some(init()?);
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 borrowed = self.value.borrow()
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 }
Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
}
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 fn get_mut_or_init<E>(
&self,
init: impl Fn() -> Result<T, E>,
) -> Result<RefMut<T>, E> {
Simon Sapin
rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct...
r48772 let mut borrowed = self.value.borrow_mut();
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 if borrowed.is_none() {
Martin von Zweigbergk
rust-repo: make `Send` by not storing functions in `LazyCell`...
r50072 *borrowed = Some(init()?);
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 }
Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
}
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 }