use crate::errors::{HgError, HgResultExt}; use crate::repo::{Repo, Vfs}; use crate::utils::join_display; use std::collections::HashSet; fn parse(bytes: &[u8]) -> Result, HgError> { // The Python code reading this file uses `str.splitlines` // which looks for a number of line separators (even including a couple of // non-ASCII ones), but Python code writing it always uses `\n`. let lines = bytes.split(|&byte| byte == b'\n'); lines .filter(|line| !line.is_empty()) .map(|line| { // Python uses Unicode `str.isalnum` but feature names are all // ASCII if line[0].is_ascii_alphanumeric() && line.is_ascii() { Ok(String::from_utf8(line.into()).unwrap()) } else { Err(HgError::corrupted("parse error in 'requires' file")) } }) .collect() } pub(crate) fn load(hg_vfs: Vfs) -> Result, HgError> { parse(&hg_vfs.read("requires")?) } pub(crate) fn load_if_exists(hg_vfs: Vfs) -> Result, HgError> { if let Some(bytes) = hg_vfs.read("requires").io_not_found_as_none()? { parse(&bytes) } else { // Treat a missing file the same as an empty file. // From `mercurial/localrepo.py`: // > requires file contains a newline-delimited list of // > features/capabilities the opener (us) must have in order to use // > the repository. This file was introduced in Mercurial 0.9.2, // > which means very old repositories may not have one. We assume // > a missing file translates to no requirements. Ok(HashSet::new()) } } pub(crate) fn check(repo: &Repo) -> Result<(), HgError> { let unknown: Vec<_> = repo .requirements() .iter() .map(String::as_str) // .filter(|feature| !ALL_SUPPORTED.contains(feature.as_str())) .filter(|feature| { !REQUIRED.contains(feature) && !SUPPORTED.contains(feature) }) .collect(); if !unknown.is_empty() { return Err(HgError::unsupported(format!( "repository requires feature unknown to this Mercurial: {}", join_display(&unknown, ", ") ))); } let missing: Vec<_> = REQUIRED .iter() .filter(|&&feature| !repo.requirements().contains(feature)) .collect(); if !missing.is_empty() { return Err(HgError::unsupported(format!( "repository is missing feature required by this Mercurial: {}", join_display(&missing, ", ") ))); } Ok(()) } /// rhg does not support repositories that are *missing* any of these features const REQUIRED: &[&str] = &["revlogv1", "store", "fncache", "dotencode"]; /// rhg supports repository with or without these const SUPPORTED: &[&str] = &[ "generaldelta", SHARED_REQUIREMENT, SHARESAFE_REQUIREMENT, SPARSEREVLOG_REQUIREMENT, RELATIVE_SHARED_REQUIREMENT, REVLOG_COMPRESSION_ZSTD, DIRSTATE_V2_REQUIREMENT, // As of this writing everything rhg does is read-only. // When it starts writing to the repository, it’ll need to either keep the // persistent nodemap up to date or remove this entry: NODEMAP_REQUIREMENT, ]; // Copied from mercurial/requirements.py: pub(crate) const DIRSTATE_V2_REQUIREMENT: &str = "exp-dirstate-v2"; /// When narrowing is finalized and no longer subject to format changes, /// we should move this to just "narrow" or similar. #[allow(unused)] pub(crate) const NARROW_REQUIREMENT: &str = "narrowhg-experimental"; /// Enables sparse working directory usage #[allow(unused)] pub(crate) const SPARSE_REQUIREMENT: &str = "exp-sparse"; /// Enables the internal phase which is used to hide changesets instead /// of stripping them #[allow(unused)] pub(crate) const INTERNAL_PHASE_REQUIREMENT: &str = "internal-phase"; /// Stores manifest in Tree structure #[allow(unused)] pub(crate) const TREEMANIFEST_REQUIREMENT: &str = "treemanifest"; /// Increment the sub-version when the revlog v2 format changes to lock out old /// clients. #[allow(unused)] pub(crate) const REVLOGV2_REQUIREMENT: &str = "exp-revlogv2.1"; /// A repository with the sparserevlog feature will have delta chains that /// can spread over a larger span. Sparse reading cuts these large spans into /// pieces, so that each piece isn't too big. /// Without the sparserevlog capability, reading from the repository could use /// huge amounts of memory, because the whole span would be read at once, /// including all the intermediate revisions that aren't pertinent for the /// chain. This is why once a repository has enabled sparse-read, it becomes /// required. #[allow(unused)] pub(crate) const SPARSEREVLOG_REQUIREMENT: &str = "sparserevlog"; /// A repository with the the copies-sidedata-changeset requirement will store /// copies related information in changeset's sidedata. #[allow(unused)] pub(crate) const COPIESSDC_REQUIREMENT: &str = "exp-copies-sidedata-changeset"; /// The repository use persistent nodemap for the changelog and the manifest. #[allow(unused)] pub(crate) const NODEMAP_REQUIREMENT: &str = "persistent-nodemap"; /// Denotes that the current repository is a share #[allow(unused)] pub(crate) const SHARED_REQUIREMENT: &str = "shared"; /// Denotes that current repository is a share and the shared source path is /// relative to the current repository root path #[allow(unused)] pub(crate) const RELATIVE_SHARED_REQUIREMENT: &str = "relshared"; /// A repository with share implemented safely. The repository has different /// store and working copy requirements i.e. both `.hg/requires` and /// `.hg/store/requires` are present. #[allow(unused)] pub(crate) const SHARESAFE_REQUIREMENT: &str = "share-safe"; /// A repository that use zstd compression inside its revlog #[allow(unused)] pub(crate) const REVLOG_COMPRESSION_ZSTD: &str = "revlog-compression-zstd";