repo.rs
615 lines
| 20.9 KiB
| application/rls-services+xml
|
RustLexer
Simon Sapin
|
r48773 | use crate::changelog::Changelog; | ||
Simon Sapin
|
r47215 | use crate::config::{Config, ConfigError, ConfigParseError}; | ||
Simon Sapin
|
r48768 | use crate::dirstate::DirstateParents; | ||
r51116 | use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode; | |||
Simon Sapin
|
r49249 | use crate::dirstate_tree::on_disk::Docket as DirstateDocket; | ||
Simon Sapin
|
r48768 | use crate::dirstate_tree::owning::OwningDirstateMap; | ||
use crate::errors::HgResultExt; | ||||
Simon Sapin
|
r49249 | use crate::errors::{HgError, IoResultExt}; | ||
Simon Sapin
|
r49245 | use crate::lock::{try_with_lock_no_wait, LockError}; | ||
Simon Sapin
|
r48774 | use crate::manifest::{Manifest, Manifestlog}; | ||
Simon Sapin
|
r48775 | use crate::revlog::filelog::Filelog; | ||
Simon Sapin
|
r48773 | use crate::revlog::revlog::RevlogError; | ||
r51123 | use crate::utils::debug::debug_wait_for_file_or_print; | |||
Simon Sapin
|
r47190 | use crate::utils::files::get_path_from_bytes; | ||
Simon Sapin
|
r48775 | use crate::utils::hg_path::HgPath; | ||
Simon Sapin
|
r47474 | use crate::utils::SliceExt; | ||
Simon Sapin
|
r48764 | use crate::vfs::{is_dir, is_file, Vfs}; | ||
Simon Sapin
|
r48778 | use crate::{requirements, NodePrefix}; | ||
Simon Sapin
|
r48774 | use crate::{DirstateError, Revision}; | ||
Simon Sapin
|
r49247 | use std::cell::{Ref, RefCell, RefMut}; | ||
Simon Sapin
|
r47190 | use std::collections::HashSet; | ||
Simon Sapin
|
r49249 | use std::io::Seek; | ||
use std::io::SeekFrom; | ||||
use std::io::Write as IoWrite; | ||||
Simon Sapin
|
r46782 | use std::path::{Path, PathBuf}; | ||
/// A repository on disk | ||||
pub struct Repo { | ||||
working_directory: PathBuf, | ||||
dot_hg: PathBuf, | ||||
store: PathBuf, | ||||
Simon Sapin
|
r47190 | requirements: HashSet<String>, | ||
Simon Sapin
|
r47215 | config: Config, | ||
Martin von Zweigbergk
|
r50072 | dirstate_parents: LazyCell<DirstateParents>, | ||
dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>, | ||||
dirstate_map: LazyCell<OwningDirstateMap>, | ||||
changelog: LazyCell<Changelog>, | ||||
manifestlog: LazyCell<Manifestlog>, | ||||
Simon Sapin
|
r46782 | } | ||
Simon Sapin
|
r47175 | #[derive(Debug, derive_more::From)] | ||
Simon Sapin
|
r47215 | pub enum RepoError { | ||
NotFound { | ||||
Simon Sapin
|
r47253 | at: PathBuf, | ||
Simon Sapin
|
r47175 | }, | ||
#[from] | ||||
Simon Sapin
|
r47215 | ConfigParseError(ConfigParseError), | ||
#[from] | ||||
Simon Sapin
|
r47175 | Other(HgError), | ||
} | ||||
Simon Sapin
|
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
|
r46782 | impl Repo { | ||
Pulkit Goyal
|
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
|
r48584 | if is_dir(ancestor.join(".hg"))? { | ||
Pulkit Goyal
|
r48197 | return Ok(ancestor.to_path_buf()); | ||
} | ||||
} | ||||
return Err(RepoError::NotFound { | ||||
at: current_directory, | ||||
}); | ||||
} | ||||
Simon Sapin
|
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
|
r47253 | /// | ||
Simon Sapin
|
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
|
r47253 | pub fn find( | ||
config: &Config, | ||||
Pulkit Goyal
|
r48196 | explicit_path: Option<PathBuf>, | ||
Simon Sapin
|
r47253 | ) -> Result<Self, RepoError> { | ||
if let Some(root) = explicit_path { | ||||
Simon Sapin
|
r48584 | if is_dir(root.join(".hg"))? { | ||
Simon Sapin
|
r47474 | Self::new_at_path(root.to_owned(), config) | ||
Simon Sapin
|
r48584 | } else if is_file(&root)? { | ||
Simon Sapin
|
r47464 | Err(HgError::unsupported("bundle repository").into()) | ||
Simon Sapin
|
r47253 | } else { | ||
Err(RepoError::NotFound { | ||||
at: root.to_owned(), | ||||
}) | ||||
Simon Sapin
|
r47175 | } | ||
Simon Sapin
|
r47253 | } else { | ||
Pulkit Goyal
|
r48197 | let root = Self::find_repo_root()?; | ||
Self::new_at_path(root, config) | ||||
Simon Sapin
|
r46782 | } | ||
} | ||||
Simon Sapin
|
r47190 | /// To be called after checking that `.hg` is a sub-directory | ||
Simon Sapin
|
r47214 | fn new_at_path( | ||
working_directory: PathBuf, | ||||
config: &Config, | ||||
Simon Sapin
|
r47215 | ) -> Result<Self, RepoError> { | ||
Simon Sapin
|
r47190 | let dot_hg = working_directory.join(".hg"); | ||
Simon Sapin
|
r47191 | |||
Simon Sapin
|
r47215 | let mut repo_config_files = Vec::new(); | ||
repo_config_files.push(dot_hg.join("hgrc")); | ||||
repo_config_files.push(dot_hg.join("hgrc-not-shared")); | ||||
Simon Sapin
|
r47190 | let hg_vfs = Vfs { base: &dot_hg }; | ||
Simon Sapin
|
r47191 | let mut reqs = requirements::load_if_exists(hg_vfs)?; | ||
Simon Sapin
|
r47190 | let relative = | ||
reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); | ||||
let shared = | ||||
reqs.contains(requirements::SHARED_REQUIREMENT) || relative; | ||||
Simon Sapin
|
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
|
r47190 | let store_path; | ||
if !shared { | ||||
store_path = dot_hg.join("store"); | ||||
} else { | ||||
let bytes = hg_vfs.read("sharedpath")?; | ||||
Simon Sapin
|
r47427 | let mut shared_path = | ||
Simon Sapin
|
r48761 | get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n')) | ||
.to_owned(); | ||||
Simon Sapin
|
r47190 | if relative { | ||
shared_path = dot_hg.join(shared_path) | ||||
} | ||||
Simon Sapin
|
r48584 | if !is_dir(&shared_path)? { | ||
Simon Sapin
|
r47190 | return Err(HgError::corrupted(format!( | ||
".hg/sharedpath points to nonexistent directory {}", | ||||
shared_path.display() | ||||
Simon Sapin
|
r47215 | )) | ||
.into()); | ||||
Simon Sapin
|
r47190 | } | ||
store_path = shared_path.join("store"); | ||||
Simon Sapin
|
r47191 | |||
let source_is_share_safe = | ||||
requirements::load(Vfs { base: &shared_path })? | ||||
.contains(requirements::SHARESAFE_REQUIREMENT); | ||||
Arseniy Alekseyev
|
r49664 | if share_safe != source_is_share_safe { | ||
return Err(HgError::unsupported("share-safe mismatch").into()); | ||||
Simon Sapin
|
r47191 | } | ||
Simon Sapin
|
r47215 | |||
if share_safe { | ||||
repo_config_files.insert(0, shared_path.join("hgrc")) | ||||
} | ||||
Simon Sapin
|
r47190 | } | ||
Simon Sapin
|
r47357 | if share_safe { | ||
reqs.extend(requirements::load(Vfs { base: &store_path })?); | ||||
} | ||||
Simon Sapin
|
r47190 | |||
Simon Sapin
|
r47475 | let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() { | ||
config.combine_with_repo(&repo_config_files)? | ||||
} else { | ||||
config.clone() | ||||
}; | ||||
Simon Sapin
|
r47215 | |||
Simon Sapin
|
r47190 | let repo = Self { | ||
requirements: reqs, | ||||
working_directory, | ||||
store: store_path, | ||||
dot_hg, | ||||
Simon Sapin
|
r47215 | config: repo_config, | ||
Martin von Zweigbergk
|
r50072 | dirstate_parents: LazyCell::new(), | ||
dirstate_data_file_uuid: LazyCell::new(), | ||||
dirstate_map: LazyCell::new(), | ||||
changelog: LazyCell::new(), | ||||
manifestlog: LazyCell::new(), | ||||
Simon Sapin
|
r47190 | }; | ||
requirements::check(&repo)?; | ||||
Ok(repo) | ||||
} | ||||
Simon Sapin
|
r46782 | pub fn working_directory_path(&self) -> &Path { | ||
&self.working_directory | ||||
} | ||||
Simon Sapin
|
r47190 | pub fn requirements(&self) -> &HashSet<String> { | ||
&self.requirements | ||||
} | ||||
Simon Sapin
|
r47215 | pub fn config(&self) -> &Config { | ||
&self.config | ||||
} | ||||
Simon Sapin
|
r46782 | /// For accessing repository files (in `.hg`), except for the store | ||
/// (`.hg/store`). | ||||
Simon Sapin
|
r47341 | pub fn hg_vfs(&self) -> Vfs<'_> { | ||
Simon Sapin
|
r46782 | Vfs { base: &self.dot_hg } | ||
} | ||||
/// For accessing repository store files (in `.hg/store`) | ||||
Simon Sapin
|
r47341 | pub fn store_vfs(&self) -> Vfs<'_> { | ||
Simon Sapin
|
r46782 | Vfs { base: &self.store } | ||
} | ||||
/// For accessing the working copy | ||||
Georges Racinet
|
r47578 | pub fn working_directory_vfs(&self) -> Vfs<'_> { | ||
Simon Sapin
|
r46782 | Vfs { | ||
base: &self.working_directory, | ||||
} | ||||
} | ||||
Simon Sapin
|
r47343 | |||
Simon Sapin
|
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) | ||||
} | ||||
Simon Sapin
|
r48165 | pub fn has_dirstate_v2(&self) -> bool { | ||
self.requirements | ||||
.contains(requirements::DIRSTATE_V2_REQUIREMENT) | ||||
} | ||||
Arseniy Alekseyev
|
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
|
r49982 | pub fn has_nodemap(&self) -> bool { | ||
self.requirements | ||||
.contains(requirements::NODEMAP_REQUIREMENT) | ||||
} | ||||
Simon Sapin
|
r48768 | fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> { | ||
Ok(self | ||||
.hg_vfs() | ||||
.read("dirstate") | ||||
.io_not_found_as_none()? | ||||
.unwrap_or(Vec::new())) | ||||
} | ||||
pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> { | ||||
Martin von Zweigbergk
|
r50072 | Ok(*self | ||
.dirstate_parents | ||||
.get_or_init(|| self.read_dirstate_parents())?) | ||||
Simon Sapin
|
r49247 | } | ||
fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> { | ||||
Simon Sapin
|
r48768 | let dirstate = self.dirstate_file_contents()?; | ||
let parents = if dirstate.is_empty() { | ||||
Simon Sapin
|
r49248 | if self.has_dirstate_v2() { | ||
self.dirstate_data_file_uuid.set(None); | ||||
} | ||||
Simon Sapin
|
r48768 | DirstateParents::NULL | ||
} else if self.has_dirstate_v2() { | ||||
Simon Sapin
|
r49248 | let docket = | ||
crate::dirstate_tree::on_disk::read_docket(&dirstate)?; | ||||
self.dirstate_data_file_uuid | ||||
.set(Some(docket.uuid.to_owned())); | ||||
docket.parents() | ||||
Simon Sapin
|
r48165 | } else { | ||
crate::dirstate::parsers::parse_dirstate_parents(&dirstate)? | ||||
Simon Sapin
|
r48474 | .clone() | ||
Simon Sapin
|
r48165 | }; | ||
Simon Sapin
|
r49247 | self.dirstate_parents.set(parents); | ||
Simon Sapin
|
r48474 | Ok(parents) | ||
Simon Sapin
|
r47343 | } | ||
Simon Sapin
|
r48768 | |||
Simon Sapin
|
r49248 | fn read_dirstate_data_file_uuid( | ||
&self, | ||||
) -> Result<Option<Vec<u8>>, HgError> { | ||||
assert!( | ||||
self.has_dirstate_v2(), | ||||
"accessing dirstate data file ID without dirstate-v2" | ||||
); | ||||
let dirstate = self.dirstate_file_contents()?; | ||||
if dirstate.is_empty() { | ||||
self.dirstate_parents.set(DirstateParents::NULL); | ||||
Ok(None) | ||||
} else { | ||||
let docket = | ||||
crate::dirstate_tree::on_disk::read_docket(&dirstate)?; | ||||
self.dirstate_parents.set(docket.parents()); | ||||
Ok(Some(docket.uuid.to_owned())) | ||||
} | ||||
} | ||||
Simon Sapin
|
r48768 | fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> { | ||
Raphaël Gomès
|
r51122 | if self.has_dirstate_v2() { | ||
self.read_docket_and_data_file() | ||||
} else { | ||||
r51123 | debug_wait_for_file_or_print( | |||
self.config(), | ||||
"dirstate.pre-read-file", | ||||
); | ||||
Raphaël Gomès
|
r51122 | 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)?; | ||||
self.dirstate_parents.set(parents); | ||||
Ok(map) | ||||
} | ||||
} | ||||
} | ||||
fn read_docket_and_data_file( | ||||
&self, | ||||
) -> Result<OwningDirstateMap, DirstateError> { | ||||
r51123 | debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file"); | |||
Simon Sapin
|
r48768 | let dirstate_file_contents = self.dirstate_file_contents()?; | ||
if dirstate_file_contents.is_empty() { | ||||
Simon Sapin
|
r49247 | self.dirstate_parents.set(DirstateParents::NULL); | ||
Raphaël Gomès
|
r51122 | self.dirstate_data_file_uuid.set(None); | ||
return Ok(OwningDirstateMap::new_empty(Vec::new())); | ||||
} | ||||
let docket = crate::dirstate_tree::on_disk::read_docket( | ||||
&dirstate_file_contents, | ||||
)?; | ||||
self.dirstate_parents.set(docket.parents()); | ||||
self.dirstate_data_file_uuid | ||||
.set(Some(docket.uuid.to_owned())); | ||||
let data_size = docket.data_size(); | ||||
let metadata = docket.tree_metadata(); | ||||
let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) { | ||||
// Don't mmap on NFS to prevent `SIGBUS` error on deletion | ||||
OwningDirstateMap::new_v2( | ||||
self.hg_vfs().read(docket.data_filename())?, | ||||
data_size, | ||||
metadata, | ||||
) | ||||
} else if let Some(data_mmap) = self | ||||
.hg_vfs() | ||||
.mmap_open(docket.data_filename()) | ||||
.io_not_found_as_none()? | ||||
{ | ||||
OwningDirstateMap::new_v2(data_mmap, data_size, metadata) | ||||
} else { | ||||
OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) | ||||
}?; | ||||
Raphaël Gomès
|
r51117 | |||
Raphaël Gomès
|
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
|
r51117 | |||
Raphaël Gomès
|
r51122 | map.with_dmap_mut(|m| m.set_write_mode(write_mode)); | ||
Raphaël Gomès
|
r51117 | |||
Raphaël Gomès
|
r51122 | Ok(map) | ||
Simon Sapin
|
r48768 | } | ||
pub fn dirstate_map( | ||||
&self, | ||||
) -> Result<Ref<OwningDirstateMap>, DirstateError> { | ||||
Martin von Zweigbergk
|
r50072 | self.dirstate_map.get_or_init(|| self.new_dirstate_map()) | ||
Simon Sapin
|
r48772 | } | ||
pub fn dirstate_map_mut( | ||||
&self, | ||||
) -> Result<RefMut<OwningDirstateMap>, DirstateError> { | ||||
Martin von Zweigbergk
|
r50072 | self.dirstate_map | ||
.get_mut_or_init(|| self.new_dirstate_map()) | ||||
Simon Sapin
|
r48772 | } | ||
Simon Sapin
|
r48773 | |||
Martin von Zweigbergk
|
r49981 | fn new_changelog(&self) -> Result<Changelog, HgError> { | ||
Martin von Zweigbergk
|
r49982 | Changelog::open(&self.store_vfs(), self.has_nodemap()) | ||
Simon Sapin
|
r48772 | } | ||
Simon Sapin
|
r48773 | |||
Simon Sapin
|
r48777 | pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> { | ||
Martin von Zweigbergk
|
r50072 | self.changelog.get_or_init(|| self.new_changelog()) | ||
Simon Sapin
|
r48773 | } | ||
Simon Sapin
|
r48777 | pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> { | ||
Martin von Zweigbergk
|
r50072 | self.changelog.get_mut_or_init(|| self.new_changelog()) | ||
Simon Sapin
|
r48773 | } | ||
Martin von Zweigbergk
|
r49981 | fn new_manifestlog(&self) -> Result<Manifestlog, HgError> { | ||
Martin von Zweigbergk
|
r49982 | Manifestlog::open(&self.store_vfs(), self.has_nodemap()) | ||
Simon Sapin
|
r48773 | } | ||
Simon Sapin
|
r48777 | pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> { | ||
Martin von Zweigbergk
|
r50072 | self.manifestlog.get_or_init(|| self.new_manifestlog()) | ||
Simon Sapin
|
r48773 | } | ||
Simon Sapin
|
r48777 | pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> { | ||
Martin von Zweigbergk
|
r50072 | self.manifestlog.get_mut_or_init(|| self.new_manifestlog()) | ||
Simon Sapin
|
r48773 | } | ||
Simon Sapin
|
r48774 | |||
Simon Sapin
|
r48783 | /// Returns the manifest of the *changeset* with the given node ID | ||
Simon Sapin
|
r48778 | pub fn manifest_for_node( | ||
&self, | ||||
node: impl Into<NodePrefix>, | ||||
) -> Result<Manifest, RevlogError> { | ||||
Simon Sapin
|
r48783 | self.manifestlog()?.data_for_node( | ||
Simon Sapin
|
r48778 | self.changelog()? | ||
Simon Sapin
|
r48783 | .data_for_node(node.into())? | ||
Simon Sapin
|
r48778 | .manifest_node()? | ||
.into(), | ||||
) | ||||
} | ||||
Simon Sapin
|
r48783 | /// Returns the manifest of the *changeset* with the given revision number | ||
Simon Sapin
|
r48778 | pub fn manifest_for_rev( | ||
Simon Sapin
|
r48774 | &self, | ||
revision: Revision, | ||||
) -> Result<Manifest, RevlogError> { | ||||
Simon Sapin
|
r48783 | self.manifestlog()?.data_for_node( | ||
self.changelog()? | ||||
.data_for_rev(revision)? | ||||
.manifest_node()? | ||||
.into(), | ||||
Simon Sapin
|
r48778 | ) | ||
Simon Sapin
|
r48774 | } | ||
Simon Sapin
|
r48775 | |||
Simon Sapin
|
r49341 | pub fn has_subrepos(&self) -> Result<bool, DirstateError> { | ||
if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? { | ||||
Raphaël Gomès
|
r50027 | Ok(entry.tracked()) | ||
Simon Sapin
|
r49341 | } else { | ||
Ok(false) | ||||
} | ||||
} | ||||
Simon Sapin
|
r48777 | pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> { | ||
Simon Sapin
|
r48775 | Filelog::open(self, path) | ||
} | ||||
Simon Sapin
|
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
|
r50038 | let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() { | ||
Martin von Zweigbergk
|
r50072 | let uuid_opt = self | ||
.dirstate_data_file_uuid | ||||
.get_or_init(|| self.read_dirstate_data_file_uuid())?; | ||||
Raphaël Gomès
|
r50044 | let uuid_opt = uuid_opt.as_ref(); | ||
r51116 | let write_mode = if uuid_opt.is_some() { | |||
DirstateMapWriteMode::Auto | ||||
} else { | ||||
DirstateMapWriteMode::ForceNewDataFile | ||||
}; | ||||
Raphaël Gomès
|
r50037 | let (data, tree_metadata, append, old_data_size) = | ||
r51116 | map.pack_v2(write_mode)?; | |||
Raphaël Gomès
|
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
|
r49249 | }; | ||
Raphaël Gomès
|
r50044 | |||
Simon Sapin
|
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
|
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
|
r51108 | log::trace!("creating a new dirstate data file"); | ||
Arseniy Alekseyev
|
r50097 | options.create_new(true); | ||
Raphaël Gomès
|
r51108 | } else { | ||
log::trace!("appending to the dirstate data file"); | ||||
Simon Sapin
|
r49249 | } | ||
Arseniy Alekseyev
|
r50097 | |||
Simon Sapin
|
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
|
r50097 | if append { | ||
file.seek(SeekFrom::Start(old_data_size as u64))?; | ||||
Raphaël Gomès
|
r50037 | } | ||
Arseniy Alekseyev
|
r50097 | file.write_all(&data)?; | ||
file.flush()?; | ||||
file.seek(SeekFrom::Current(0)) | ||||
Simon Sapin
|
r49249 | })() | ||
.when_writing_file(&data_filename)?; | ||||
Raphaël Gomès
|
r50038 | |||
let packed_dirstate = DirstateDocket::serialize( | ||||
Simon Sapin
|
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
|
r50038 | })?; | ||
(packed_dirstate, old_uuid) | ||||
Simon Sapin
|
r49249 | } else { | ||
Raphaël Gomès
|
r50038 | (map.pack_v1(parents)?, None) | ||
Simon Sapin
|
r49249 | }; | ||
Raphaël Gomès
|
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
|
r49249 | Ok(()) | ||
} | ||||
Simon Sapin
|
r48772 | } | ||
/// Lazily-initialized component of `Repo` with interior mutability | ||||
/// | ||||
/// This differs from `OnceCell` in that the value can still be "deinitialized" | ||||
Martin von Zweigbergk
|
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
|
r48772 | value: RefCell<Option<T>>, | ||
} | ||||
Martin von Zweigbergk
|
r50072 | impl<T> LazyCell<T> { | ||
fn new() -> Self { | ||||
Simon Sapin
|
r48772 | Self { | ||
value: RefCell::new(None), | ||||
} | ||||
} | ||||
Simon Sapin
|
r49247 | fn set(&self, value: T) { | ||
*self.value.borrow_mut() = Some(value) | ||||
} | ||||
Martin von Zweigbergk
|
r50072 | fn get_or_init<E>( | ||
&self, | ||||
init: impl Fn() -> Result<T, E>, | ||||
) -> Result<Ref<T>, E> { | ||||
Simon Sapin
|
r48772 | let mut borrowed = self.value.borrow(); | ||
Simon Sapin
|
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
|
r50072 | *self.value.borrow_mut() = Some(init()?); | ||
Simon Sapin
|
r48772 | borrowed = self.value.borrow() | ||
Simon Sapin
|
r48768 | } | ||
Ok(Ref::map(borrowed, |option| option.as_ref().unwrap())) | ||||
} | ||||
Martin von Zweigbergk
|
r50072 | fn get_mut_or_init<E>( | ||
&self, | ||||
init: impl Fn() -> Result<T, E>, | ||||
) -> Result<RefMut<T>, E> { | ||||
Simon Sapin
|
r48772 | let mut borrowed = self.value.borrow_mut(); | ||
Simon Sapin
|
r48768 | if borrowed.is_none() { | ||
Martin von Zweigbergk
|
r50072 | *borrowed = Some(init()?); | ||
Simon Sapin
|
r48768 | } | ||
Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap())) | ||||
} | ||||
Simon Sapin
|
r46782 | } | ||