##// END OF EJS Templates
rust: make `Revision` a newtype...
rust: make `Revision` a newtype This change is the one we've been building towards during this series. The aim is to make `Revision` mean more than a simple integer, holding the information that it is valid for a given revlog index. While this still allows for programmer error, since creating a revision directly and querying a different index with a "checked" revision are still possible, the friction created by the newtype will hopefully make us think twice about which type to use. Enough of the Rust ecosystem relies on the newtype pattern to be efficiently optimized away (even compiler in codegen tests¹), so I'm not worried about this being a fundamental problem. [1] https://github.com/rust-lang/rust/blob/7a70647f195f6b0a0f1ebd72b1542ba91a32f43a/tests/codegen/vec-in-place.rs#L47

File last commit:

r51870:1928b770 default
r51872:4c5f6e95 default
Show More
repo.rs
782 lines | 28.2 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};
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;
Simon Sapin
rust: Move VFS code to its own module...
r48764 use crate::vfs::{is_dir, is_file, Vfs};
Raphaël Gomès
rust: use the new `UncheckedRevision` everywhere applicable...
r51870 use crate::DirstateError;
use crate::{requirements, NodePrefix, UncheckedRevision};
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
Simon Sapin
rhg: initial support for shared repositories...
r47190 let hg_vfs = Vfs { base: &dot_hg };
Simon Sapin
rhg: add support for share-safe...
r47191 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
let source_is_share_safe =
requirements::load(Vfs { base: &shared_path })?
.contains(requirements::SHARESAFE_REQUIREMENT);
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 {
reqs.extend(requirements::load(Vfs { base: &store_path })?);
}
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`).
Simon Sapin
rust: Add a log file rotation utility...
r47341 pub fn hg_vfs(&self) -> Vfs<'_> {
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 Vfs { base: &self.dot_hg }
}
/// For accessing repository store files (in `.hg/store`)
Simon Sapin
rust: Add a log file rotation utility...
r47341 pub fn store_vfs(&self) -> Vfs<'_> {
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 Vfs { base: &self.store }
}
/// For accessing the working copy
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 pub fn working_directory_vfs(&self) -> Vfs<'_> {
Simon Sapin
rust: introduce Repo and Vfs types for filesystem abstraction...
r46782 Vfs {
base: &self.working_directory,
}
}
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> {
try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
}
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() {
self.dirstate_parents.set(DirstateParents::NULL);
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);
Ok(OwningDirstateMap::new_empty(Vec::new()))
} 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() {
Simon Sapin
rhg: Make Repo::dirstate_parents a LazyCell...
r49247 self.dirstate_parents.set(DirstateParents::NULL);
Raphaël Gomès
rust-repo: move dirstate-v2 opening to a separate method...
r51122 return Ok(OwningDirstateMap::new_empty(Vec::new()));
}
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> {
Martin von Zweigbergk
rust-repo: extract a function for checking nodemap requirement...
r49982 Changelog::open(&self.store_vfs(), self.has_nodemap())
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> {
Martin von Zweigbergk
rust-repo: extract a function for checking nodemap requirement...
r49982 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
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> {
Simon Sapin
rust: Add a Filelog struct that wraps Revlog...
r48775 Filelog::open(self, path)
}
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()?;
file.seek(SeekFrom::Current(0))
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(())
}
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 }