##// END OF EJS Templates
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
Simon Sapin -
r48773:21d25e9e default
parent child Browse files
Show More
@@ -1,105 +1,103 b''
1 // list_tracked_files.rs
1 // list_tracked_files.rs
2 //
2 //
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use std::path::PathBuf;
8 use std::path::PathBuf;
9
9
10 use crate::repo::Repo;
10 use crate::repo::Repo;
11 use crate::revlog::changelog::Changelog;
12 use crate::revlog::manifest::Manifestlog;
13 use crate::revlog::path_encode::path_encode;
11 use crate::revlog::path_encode::path_encode;
14 use crate::revlog::revlog::Revlog;
12 use crate::revlog::revlog::Revlog;
15 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::revlog::RevlogError;
16 use crate::revlog::Node;
14 use crate::revlog::Node;
17 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::files::get_path_from_bytes;
18 use crate::utils::hg_path::{HgPath, HgPathBuf};
16 use crate::utils::hg_path::{HgPath, HgPathBuf};
19
17
20 pub struct CatOutput {
18 pub struct CatOutput {
21 /// Whether any file in the manifest matched the paths given as CLI
19 /// Whether any file in the manifest matched the paths given as CLI
22 /// arguments
20 /// arguments
23 pub found_any: bool,
21 pub found_any: bool,
24 /// The contents of matching files, in manifest order
22 /// The contents of matching files, in manifest order
25 pub concatenated: Vec<u8>,
23 pub concatenated: Vec<u8>,
26 /// Which of the CLI arguments did not match any manifest file
24 /// Which of the CLI arguments did not match any manifest file
27 pub missing: Vec<HgPathBuf>,
25 pub missing: Vec<HgPathBuf>,
28 /// The node ID that the given revset was resolved to
26 /// The node ID that the given revset was resolved to
29 pub node: Node,
27 pub node: Node,
30 }
28 }
31
29
32 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
30 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
33
31
34 /// Output the given revision of files
32 /// Output the given revision of files
35 ///
33 ///
36 /// * `root`: Repository root
34 /// * `root`: Repository root
37 /// * `rev`: The revision to cat the files from.
35 /// * `rev`: The revision to cat the files from.
38 /// * `files`: The files to output.
36 /// * `files`: The files to output.
39 pub fn cat<'a>(
37 pub fn cat<'a>(
40 repo: &Repo,
38 repo: &Repo,
41 revset: &str,
39 revset: &str,
42 files: &'a [HgPathBuf],
40 files: &'a [HgPathBuf],
43 ) -> Result<CatOutput, RevlogError> {
41 ) -> Result<CatOutput, RevlogError> {
44 let rev = crate::revset::resolve_single(revset, repo)?;
42 let rev = crate::revset::resolve_single(revset, repo)?;
45 let changelog = Changelog::open(repo)?;
43 let changelog = repo.changelog()?;
46 let manifest = Manifestlog::open(repo)?;
44 let manifest = repo.manifestlog()?;
47 let changelog_entry = changelog.get_rev(rev)?;
45 let changelog_entry = changelog.get_rev(rev)?;
48 let node = *changelog
46 let node = *changelog
49 .node_from_rev(rev)
47 .node_from_rev(rev)
50 .expect("should succeed when changelog.get_rev did");
48 .expect("should succeed when changelog.get_rev did");
51 let manifest_node =
49 let manifest_node =
52 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
50 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
53 let manifest_entry = manifest.get_node(manifest_node.into())?;
51 let manifest_entry = manifest.get_node(manifest_node.into())?;
54 let mut bytes = vec![];
52 let mut bytes = vec![];
55 let mut matched = vec![false; files.len()];
53 let mut matched = vec![false; files.len()];
56 let mut found_any = false;
54 let mut found_any = false;
57
55
58 for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
56 for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
59 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
57 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
60 if cat_file.as_bytes() == manifest_file.as_bytes() {
58 if cat_file.as_bytes() == manifest_file.as_bytes() {
61 *is_matched = true;
59 *is_matched = true;
62 found_any = true;
60 found_any = true;
63 let index_path = store_path(manifest_file, b".i");
61 let index_path = store_path(manifest_file, b".i");
64 let data_path = store_path(manifest_file, b".d");
62 let data_path = store_path(manifest_file, b".d");
65
63
66 let file_log =
64 let file_log =
67 Revlog::open(repo, &index_path, Some(&data_path))?;
65 Revlog::open(repo, &index_path, Some(&data_path))?;
68 let file_node = Node::from_hex_for_repo(node_bytes)?;
66 let file_node = Node::from_hex_for_repo(node_bytes)?;
69 let file_rev = file_log.get_node_rev(file_node.into())?;
67 let file_rev = file_log.get_node_rev(file_node.into())?;
70 let data = file_log.get_rev_data(file_rev)?;
68 let data = file_log.get_rev_data(file_rev)?;
71 if data.starts_with(&METADATA_DELIMITER) {
69 if data.starts_with(&METADATA_DELIMITER) {
72 let end_delimiter_position = data
70 let end_delimiter_position = data
73 [METADATA_DELIMITER.len()..]
71 [METADATA_DELIMITER.len()..]
74 .windows(METADATA_DELIMITER.len())
72 .windows(METADATA_DELIMITER.len())
75 .position(|bytes| bytes == METADATA_DELIMITER);
73 .position(|bytes| bytes == METADATA_DELIMITER);
76 if let Some(position) = end_delimiter_position {
74 if let Some(position) = end_delimiter_position {
77 let offset = METADATA_DELIMITER.len() * 2;
75 let offset = METADATA_DELIMITER.len() * 2;
78 bytes.extend(data[position + offset..].iter());
76 bytes.extend(data[position + offset..].iter());
79 }
77 }
80 } else {
78 } else {
81 bytes.extend(data);
79 bytes.extend(data);
82 }
80 }
83 }
81 }
84 }
82 }
85 }
83 }
86
84
87 let missing: Vec<_> = files
85 let missing: Vec<_> = files
88 .iter()
86 .iter()
89 .zip(&matched)
87 .zip(&matched)
90 .filter(|pair| !*pair.1)
88 .filter(|pair| !*pair.1)
91 .map(|pair| pair.0.clone())
89 .map(|pair| pair.0.clone())
92 .collect();
90 .collect();
93 Ok(CatOutput {
91 Ok(CatOutput {
94 found_any,
92 found_any,
95 concatenated: bytes,
93 concatenated: bytes,
96 missing,
94 missing,
97 node,
95 node,
98 })
96 })
99 }
97 }
100
98
101 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
99 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
102 let encoded_bytes =
100 let encoded_bytes =
103 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
101 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
104 get_path_from_bytes(&encoded_bytes).into()
102 get_path_from_bytes(&encoded_bytes).into()
105 }
103 }
@@ -1,90 +1,89 b''
1 // list_tracked_files.rs
1 // list_tracked_files.rs
2 //
2 //
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::dirstate::parsers::parse_dirstate_entries;
8 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 use crate::errors::HgError;
10 use crate::errors::HgError;
11 use crate::repo::Repo;
11 use crate::repo::Repo;
12 use crate::revlog::changelog::Changelog;
12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::manifest::{Manifest, Manifestlog};
14 use crate::revlog::node::Node;
13 use crate::revlog::node::Node;
15 use crate::revlog::revlog::RevlogError;
14 use crate::revlog::revlog::RevlogError;
16 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
17 use crate::DirstateError;
16 use crate::DirstateError;
18 use rayon::prelude::*;
17 use rayon::prelude::*;
19
18
20 /// List files under Mercurial control in the working directory
19 /// List files under Mercurial control in the working directory
21 /// by reading the dirstate
20 /// by reading the dirstate
22 pub struct Dirstate {
21 pub struct Dirstate {
23 /// The `dirstate` content.
22 /// The `dirstate` content.
24 content: Vec<u8>,
23 content: Vec<u8>,
25 v2_metadata: Option<Vec<u8>>,
24 v2_metadata: Option<Vec<u8>>,
26 }
25 }
27
26
28 impl Dirstate {
27 impl Dirstate {
29 pub fn new(repo: &Repo) -> Result<Self, HgError> {
28 pub fn new(repo: &Repo) -> Result<Self, HgError> {
30 let mut content = repo.hg_vfs().read("dirstate")?;
29 let mut content = repo.hg_vfs().read("dirstate")?;
31 let v2_metadata = if repo.has_dirstate_v2() {
30 let v2_metadata = if repo.has_dirstate_v2() {
32 let docket = read_docket(&content)?;
31 let docket = read_docket(&content)?;
33 let meta = docket.tree_metadata().to_vec();
32 let meta = docket.tree_metadata().to_vec();
34 content = repo.hg_vfs().read(docket.data_filename())?;
33 content = repo.hg_vfs().read(docket.data_filename())?;
35 Some(meta)
34 Some(meta)
36 } else {
35 } else {
37 None
36 None
38 };
37 };
39 Ok(Self {
38 Ok(Self {
40 content,
39 content,
41 v2_metadata,
40 v2_metadata,
42 })
41 })
43 }
42 }
44
43
45 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
44 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
46 let mut files = Vec::new();
45 let mut files = Vec::new();
47 if !self.content.is_empty() {
46 if !self.content.is_empty() {
48 if let Some(meta) = &self.v2_metadata {
47 if let Some(meta) = &self.v2_metadata {
49 for_each_tracked_path(&self.content, meta, |path| {
48 for_each_tracked_path(&self.content, meta, |path| {
50 files.push(path)
49 files.push(path)
51 })?
50 })?
52 } else {
51 } else {
53 let _parents = parse_dirstate_entries(
52 let _parents = parse_dirstate_entries(
54 &self.content,
53 &self.content,
55 |path, entry, _copy_source| {
54 |path, entry, _copy_source| {
56 if entry.state.is_tracked() {
55 if entry.state.is_tracked() {
57 files.push(path)
56 files.push(path)
58 }
57 }
59 Ok(())
58 Ok(())
60 },
59 },
61 )?;
60 )?;
62 }
61 }
63 }
62 }
64 files.par_sort_unstable();
63 files.par_sort_unstable();
65 Ok(files)
64 Ok(files)
66 }
65 }
67 }
66 }
68
67
69 /// List files under Mercurial control at a given revision.
68 /// List files under Mercurial control at a given revision.
70 pub fn list_rev_tracked_files(
69 pub fn list_rev_tracked_files(
71 repo: &Repo,
70 repo: &Repo,
72 revset: &str,
71 revset: &str,
73 ) -> Result<FilesForRev, RevlogError> {
72 ) -> Result<FilesForRev, RevlogError> {
74 let rev = crate::revset::resolve_single(revset, repo)?;
73 let rev = crate::revset::resolve_single(revset, repo)?;
75 let changelog = Changelog::open(repo)?;
74 let changelog = repo.changelog()?;
76 let manifest = Manifestlog::open(repo)?;
75 let manifest = repo.manifestlog()?;
77 let changelog_entry = changelog.get_rev(rev)?;
76 let changelog_entry = changelog.get_rev(rev)?;
78 let manifest_node =
77 let manifest_node =
79 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
78 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
80 let manifest_entry = manifest.get_node(manifest_node.into())?;
79 let manifest_entry = manifest.get_node(manifest_node.into())?;
81 Ok(FilesForRev(manifest_entry))
80 Ok(FilesForRev(manifest_entry))
82 }
81 }
83
82
84 pub struct FilesForRev(Manifest);
83 pub struct FilesForRev(Manifest);
85
84
86 impl FilesForRev {
85 impl FilesForRev {
87 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
86 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
88 self.0.files()
87 self.0.files()
89 }
88 }
90 }
89 }
@@ -1,367 +1,390 b''
1 use crate::changelog::Changelog;
1 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::errors::HgError;
6 use crate::errors::HgError;
6 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
7 use crate::exit_codes;
8 use crate::exit_codes;
9 use crate::manifest::Manifestlog;
8 use crate::requirements;
10 use crate::requirements;
11 use crate::revlog::revlog::RevlogError;
9 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
10 use crate::utils::SliceExt;
13 use crate::utils::SliceExt;
11 use crate::vfs::{is_dir, is_file, Vfs};
14 use crate::vfs::{is_dir, is_file, Vfs};
12 use crate::DirstateError;
15 use crate::DirstateError;
13 use std::cell::{Cell, Ref, RefCell, RefMut};
16 use std::cell::{Cell, Ref, RefCell, RefMut};
14 use std::collections::HashSet;
17 use std::collections::HashSet;
15 use std::path::{Path, PathBuf};
18 use std::path::{Path, PathBuf};
16
19
17 /// A repository on disk
20 /// A repository on disk
18 pub struct Repo {
21 pub struct Repo {
19 working_directory: PathBuf,
22 working_directory: PathBuf,
20 dot_hg: PathBuf,
23 dot_hg: PathBuf,
21 store: PathBuf,
24 store: PathBuf,
22 requirements: HashSet<String>,
25 requirements: HashSet<String>,
23 config: Config,
26 config: Config,
24 // None means not known/initialized yet
27 // None means not known/initialized yet
25 dirstate_parents: Cell<Option<DirstateParents>>,
28 dirstate_parents: Cell<Option<DirstateParents>>,
26 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
29 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
30 changelog: LazyCell<Changelog, RevlogError>,
31 manifestlog: LazyCell<Manifestlog, RevlogError>,
27 }
32 }
28
33
29 #[derive(Debug, derive_more::From)]
34 #[derive(Debug, derive_more::From)]
30 pub enum RepoError {
35 pub enum RepoError {
31 NotFound {
36 NotFound {
32 at: PathBuf,
37 at: PathBuf,
33 },
38 },
34 #[from]
39 #[from]
35 ConfigParseError(ConfigParseError),
40 ConfigParseError(ConfigParseError),
36 #[from]
41 #[from]
37 Other(HgError),
42 Other(HgError),
38 }
43 }
39
44
40 impl From<ConfigError> for RepoError {
45 impl From<ConfigError> for RepoError {
41 fn from(error: ConfigError) -> Self {
46 fn from(error: ConfigError) -> Self {
42 match error {
47 match error {
43 ConfigError::Parse(error) => error.into(),
48 ConfigError::Parse(error) => error.into(),
44 ConfigError::Other(error) => error.into(),
49 ConfigError::Other(error) => error.into(),
45 }
50 }
46 }
51 }
47 }
52 }
48
53
49 impl Repo {
54 impl Repo {
50 /// tries to find nearest repository root in current working directory or
55 /// tries to find nearest repository root in current working directory or
51 /// its ancestors
56 /// its ancestors
52 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
57 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
53 let current_directory = crate::utils::current_dir()?;
58 let current_directory = crate::utils::current_dir()?;
54 // ancestors() is inclusive: it first yields `current_directory`
59 // ancestors() is inclusive: it first yields `current_directory`
55 // as-is.
60 // as-is.
56 for ancestor in current_directory.ancestors() {
61 for ancestor in current_directory.ancestors() {
57 if is_dir(ancestor.join(".hg"))? {
62 if is_dir(ancestor.join(".hg"))? {
58 return Ok(ancestor.to_path_buf());
63 return Ok(ancestor.to_path_buf());
59 }
64 }
60 }
65 }
61 return Err(RepoError::NotFound {
66 return Err(RepoError::NotFound {
62 at: current_directory,
67 at: current_directory,
63 });
68 });
64 }
69 }
65
70
66 /// Find a repository, either at the given path (which must contain a `.hg`
71 /// Find a repository, either at the given path (which must contain a `.hg`
67 /// sub-directory) or by searching the current directory and its
72 /// sub-directory) or by searching the current directory and its
68 /// ancestors.
73 /// ancestors.
69 ///
74 ///
70 /// A method with two very different "modes" like this usually a code smell
75 /// A method with two very different "modes" like this usually a code smell
71 /// to make two methods instead, but in this case an `Option` is what rhg
76 /// to make two methods instead, but in this case an `Option` is what rhg
72 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
77 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
73 /// Having two methods would just move that `if` to almost all callers.
78 /// Having two methods would just move that `if` to almost all callers.
74 pub fn find(
79 pub fn find(
75 config: &Config,
80 config: &Config,
76 explicit_path: Option<PathBuf>,
81 explicit_path: Option<PathBuf>,
77 ) -> Result<Self, RepoError> {
82 ) -> Result<Self, RepoError> {
78 if let Some(root) = explicit_path {
83 if let Some(root) = explicit_path {
79 if is_dir(root.join(".hg"))? {
84 if is_dir(root.join(".hg"))? {
80 Self::new_at_path(root.to_owned(), config)
85 Self::new_at_path(root.to_owned(), config)
81 } else if is_file(&root)? {
86 } else if is_file(&root)? {
82 Err(HgError::unsupported("bundle repository").into())
87 Err(HgError::unsupported("bundle repository").into())
83 } else {
88 } else {
84 Err(RepoError::NotFound {
89 Err(RepoError::NotFound {
85 at: root.to_owned(),
90 at: root.to_owned(),
86 })
91 })
87 }
92 }
88 } else {
93 } else {
89 let root = Self::find_repo_root()?;
94 let root = Self::find_repo_root()?;
90 Self::new_at_path(root, config)
95 Self::new_at_path(root, config)
91 }
96 }
92 }
97 }
93
98
94 /// To be called after checking that `.hg` is a sub-directory
99 /// To be called after checking that `.hg` is a sub-directory
95 fn new_at_path(
100 fn new_at_path(
96 working_directory: PathBuf,
101 working_directory: PathBuf,
97 config: &Config,
102 config: &Config,
98 ) -> Result<Self, RepoError> {
103 ) -> Result<Self, RepoError> {
99 let dot_hg = working_directory.join(".hg");
104 let dot_hg = working_directory.join(".hg");
100
105
101 let mut repo_config_files = Vec::new();
106 let mut repo_config_files = Vec::new();
102 repo_config_files.push(dot_hg.join("hgrc"));
107 repo_config_files.push(dot_hg.join("hgrc"));
103 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
108 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
104
109
105 let hg_vfs = Vfs { base: &dot_hg };
110 let hg_vfs = Vfs { base: &dot_hg };
106 let mut reqs = requirements::load_if_exists(hg_vfs)?;
111 let mut reqs = requirements::load_if_exists(hg_vfs)?;
107 let relative =
112 let relative =
108 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
113 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
109 let shared =
114 let shared =
110 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
115 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
111
116
112 // From `mercurial/localrepo.py`:
117 // From `mercurial/localrepo.py`:
113 //
118 //
114 // if .hg/requires contains the sharesafe requirement, it means
119 // if .hg/requires contains the sharesafe requirement, it means
115 // there exists a `.hg/store/requires` too and we should read it
120 // there exists a `.hg/store/requires` too and we should read it
116 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
121 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
117 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
122 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
118 // is not present, refer checkrequirementscompat() for that
123 // is not present, refer checkrequirementscompat() for that
119 //
124 //
120 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
125 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
121 // repository was shared the old way. We check the share source
126 // repository was shared the old way. We check the share source
122 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
127 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
123 // current repository needs to be reshared
128 // current repository needs to be reshared
124 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
129 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
125
130
126 let store_path;
131 let store_path;
127 if !shared {
132 if !shared {
128 store_path = dot_hg.join("store");
133 store_path = dot_hg.join("store");
129 } else {
134 } else {
130 let bytes = hg_vfs.read("sharedpath")?;
135 let bytes = hg_vfs.read("sharedpath")?;
131 let mut shared_path =
136 let mut shared_path =
132 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
137 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
133 .to_owned();
138 .to_owned();
134 if relative {
139 if relative {
135 shared_path = dot_hg.join(shared_path)
140 shared_path = dot_hg.join(shared_path)
136 }
141 }
137 if !is_dir(&shared_path)? {
142 if !is_dir(&shared_path)? {
138 return Err(HgError::corrupted(format!(
143 return Err(HgError::corrupted(format!(
139 ".hg/sharedpath points to nonexistent directory {}",
144 ".hg/sharedpath points to nonexistent directory {}",
140 shared_path.display()
145 shared_path.display()
141 ))
146 ))
142 .into());
147 .into());
143 }
148 }
144
149
145 store_path = shared_path.join("store");
150 store_path = shared_path.join("store");
146
151
147 let source_is_share_safe =
152 let source_is_share_safe =
148 requirements::load(Vfs { base: &shared_path })?
153 requirements::load(Vfs { base: &shared_path })?
149 .contains(requirements::SHARESAFE_REQUIREMENT);
154 .contains(requirements::SHARESAFE_REQUIREMENT);
150
155
151 if share_safe && !source_is_share_safe {
156 if share_safe && !source_is_share_safe {
152 return Err(match config
157 return Err(match config
153 .get(b"share", b"safe-mismatch.source-not-safe")
158 .get(b"share", b"safe-mismatch.source-not-safe")
154 {
159 {
155 Some(b"abort") | None => HgError::abort(
160 Some(b"abort") | None => HgError::abort(
156 "abort: share source does not support share-safe requirement\n\
161 "abort: share source does not support share-safe requirement\n\
157 (see `hg help config.format.use-share-safe` for more information)",
162 (see `hg help config.format.use-share-safe` for more information)",
158 exit_codes::ABORT,
163 exit_codes::ABORT,
159 ),
164 ),
160 _ => HgError::unsupported("share-safe downgrade"),
165 _ => HgError::unsupported("share-safe downgrade"),
161 }
166 }
162 .into());
167 .into());
163 } else if source_is_share_safe && !share_safe {
168 } else if source_is_share_safe && !share_safe {
164 return Err(
169 return Err(
165 match config.get(b"share", b"safe-mismatch.source-safe") {
170 match config.get(b"share", b"safe-mismatch.source-safe") {
166 Some(b"abort") | None => HgError::abort(
171 Some(b"abort") | None => HgError::abort(
167 "abort: version mismatch: source uses share-safe \
172 "abort: version mismatch: source uses share-safe \
168 functionality while the current share does not\n\
173 functionality while the current share does not\n\
169 (see `hg help config.format.use-share-safe` for more information)",
174 (see `hg help config.format.use-share-safe` for more information)",
170 exit_codes::ABORT,
175 exit_codes::ABORT,
171 ),
176 ),
172 _ => HgError::unsupported("share-safe upgrade"),
177 _ => HgError::unsupported("share-safe upgrade"),
173 }
178 }
174 .into(),
179 .into(),
175 );
180 );
176 }
181 }
177
182
178 if share_safe {
183 if share_safe {
179 repo_config_files.insert(0, shared_path.join("hgrc"))
184 repo_config_files.insert(0, shared_path.join("hgrc"))
180 }
185 }
181 }
186 }
182 if share_safe {
187 if share_safe {
183 reqs.extend(requirements::load(Vfs { base: &store_path })?);
188 reqs.extend(requirements::load(Vfs { base: &store_path })?);
184 }
189 }
185
190
186 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
191 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
187 config.combine_with_repo(&repo_config_files)?
192 config.combine_with_repo(&repo_config_files)?
188 } else {
193 } else {
189 config.clone()
194 config.clone()
190 };
195 };
191
196
192 let repo = Self {
197 let repo = Self {
193 requirements: reqs,
198 requirements: reqs,
194 working_directory,
199 working_directory,
195 store: store_path,
200 store: store_path,
196 dot_hg,
201 dot_hg,
197 config: repo_config,
202 config: repo_config,
198 dirstate_parents: Cell::new(None),
203 dirstate_parents: Cell::new(None),
199 dirstate_map: LazyCell::new(Self::new_dirstate_map),
204 dirstate_map: LazyCell::new(Self::new_dirstate_map),
205 changelog: LazyCell::new(Changelog::open),
206 manifestlog: LazyCell::new(Manifestlog::open),
200 };
207 };
201
208
202 requirements::check(&repo)?;
209 requirements::check(&repo)?;
203
210
204 Ok(repo)
211 Ok(repo)
205 }
212 }
206
213
207 pub fn working_directory_path(&self) -> &Path {
214 pub fn working_directory_path(&self) -> &Path {
208 &self.working_directory
215 &self.working_directory
209 }
216 }
210
217
211 pub fn requirements(&self) -> &HashSet<String> {
218 pub fn requirements(&self) -> &HashSet<String> {
212 &self.requirements
219 &self.requirements
213 }
220 }
214
221
215 pub fn config(&self) -> &Config {
222 pub fn config(&self) -> &Config {
216 &self.config
223 &self.config
217 }
224 }
218
225
219 /// For accessing repository files (in `.hg`), except for the store
226 /// For accessing repository files (in `.hg`), except for the store
220 /// (`.hg/store`).
227 /// (`.hg/store`).
221 pub fn hg_vfs(&self) -> Vfs<'_> {
228 pub fn hg_vfs(&self) -> Vfs<'_> {
222 Vfs { base: &self.dot_hg }
229 Vfs { base: &self.dot_hg }
223 }
230 }
224
231
225 /// For accessing repository store files (in `.hg/store`)
232 /// For accessing repository store files (in `.hg/store`)
226 pub fn store_vfs(&self) -> Vfs<'_> {
233 pub fn store_vfs(&self) -> Vfs<'_> {
227 Vfs { base: &self.store }
234 Vfs { base: &self.store }
228 }
235 }
229
236
230 /// For accessing the working copy
237 /// For accessing the working copy
231 pub fn working_directory_vfs(&self) -> Vfs<'_> {
238 pub fn working_directory_vfs(&self) -> Vfs<'_> {
232 Vfs {
239 Vfs {
233 base: &self.working_directory,
240 base: &self.working_directory,
234 }
241 }
235 }
242 }
236
243
237 pub fn has_dirstate_v2(&self) -> bool {
244 pub fn has_dirstate_v2(&self) -> bool {
238 self.requirements
245 self.requirements
239 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
240 }
247 }
241
248
242 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
243 Ok(self
250 Ok(self
244 .hg_vfs()
251 .hg_vfs()
245 .read("dirstate")
252 .read("dirstate")
246 .io_not_found_as_none()?
253 .io_not_found_as_none()?
247 .unwrap_or(Vec::new()))
254 .unwrap_or(Vec::new()))
248 }
255 }
249
256
250 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
251 if let Some(parents) = self.dirstate_parents.get() {
258 if let Some(parents) = self.dirstate_parents.get() {
252 return Ok(parents);
259 return Ok(parents);
253 }
260 }
254 let dirstate = self.dirstate_file_contents()?;
261 let dirstate = self.dirstate_file_contents()?;
255 let parents = if dirstate.is_empty() {
262 let parents = if dirstate.is_empty() {
256 DirstateParents::NULL
263 DirstateParents::NULL
257 } else if self.has_dirstate_v2() {
264 } else if self.has_dirstate_v2() {
258 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
265 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
259 } else {
266 } else {
260 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
267 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
261 .clone()
268 .clone()
262 };
269 };
263 self.dirstate_parents.set(Some(parents));
270 self.dirstate_parents.set(Some(parents));
264 Ok(parents)
271 Ok(parents)
265 }
272 }
266
273
267 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
274 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
268 let dirstate_file_contents = self.dirstate_file_contents()?;
275 let dirstate_file_contents = self.dirstate_file_contents()?;
269 if dirstate_file_contents.is_empty() {
276 if dirstate_file_contents.is_empty() {
270 self.dirstate_parents.set(Some(DirstateParents::NULL));
277 self.dirstate_parents.set(Some(DirstateParents::NULL));
271 Ok(OwningDirstateMap::new_empty(Vec::new()))
278 Ok(OwningDirstateMap::new_empty(Vec::new()))
272 } else if self.has_dirstate_v2() {
279 } else if self.has_dirstate_v2() {
273 let docket = crate::dirstate_tree::on_disk::read_docket(
280 let docket = crate::dirstate_tree::on_disk::read_docket(
274 &dirstate_file_contents,
281 &dirstate_file_contents,
275 )?;
282 )?;
276 self.dirstate_parents.set(Some(docket.parents()));
283 self.dirstate_parents.set(Some(docket.parents()));
277 let data_size = docket.data_size();
284 let data_size = docket.data_size();
278 let metadata = docket.tree_metadata();
285 let metadata = docket.tree_metadata();
279 let mut map = if let Some(data_mmap) = self
286 let mut map = if let Some(data_mmap) = self
280 .hg_vfs()
287 .hg_vfs()
281 .mmap_open(docket.data_filename())
288 .mmap_open(docket.data_filename())
282 .io_not_found_as_none()?
289 .io_not_found_as_none()?
283 {
290 {
284 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
291 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
285 } else {
292 } else {
286 OwningDirstateMap::new_empty(Vec::new())
293 OwningDirstateMap::new_empty(Vec::new())
287 };
294 };
288 let (on_disk, placeholder) = map.get_mut_pair();
295 let (on_disk, placeholder) = map.get_mut_pair();
289 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
296 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
290 Ok(map)
297 Ok(map)
291 } else {
298 } else {
292 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
299 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
293 let (on_disk, placeholder) = map.get_mut_pair();
300 let (on_disk, placeholder) = map.get_mut_pair();
294 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
301 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
295 self.dirstate_parents
302 self.dirstate_parents
296 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
303 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
297 *placeholder = inner;
304 *placeholder = inner;
298 Ok(map)
305 Ok(map)
299 }
306 }
300 }
307 }
301
308
302 pub fn dirstate_map(
309 pub fn dirstate_map(
303 &self,
310 &self,
304 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
311 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
305 self.dirstate_map.get_or_init(self)
312 self.dirstate_map.get_or_init(self)
306 }
313 }
307
314
308 pub fn dirstate_map_mut(
315 pub fn dirstate_map_mut(
309 &self,
316 &self,
310 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
317 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
311 self.dirstate_map.get_mut_or_init(self)
318 self.dirstate_map.get_mut_or_init(self)
312 }
319 }
320
321 pub fn changelog(&self) -> Result<Ref<Changelog>, RevlogError> {
322 self.changelog.get_or_init(self)
323 }
324
325 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, RevlogError> {
326 self.changelog.get_mut_or_init(self)
327 }
328
329 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, RevlogError> {
330 self.manifestlog.get_or_init(self)
331 }
332
333 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, RevlogError> {
334 self.manifestlog.get_mut_or_init(self)
335 }
313 }
336 }
314
337
315 /// Lazily-initialized component of `Repo` with interior mutability
338 /// Lazily-initialized component of `Repo` with interior mutability
316 ///
339 ///
317 /// This differs from `OnceCell` in that the value can still be "deinitialized"
340 /// This differs from `OnceCell` in that the value can still be "deinitialized"
318 /// later by setting its inner `Option` to `None`.
341 /// later by setting its inner `Option` to `None`.
319 struct LazyCell<T, E> {
342 struct LazyCell<T, E> {
320 value: RefCell<Option<T>>,
343 value: RefCell<Option<T>>,
321 // `Fn`s that don’t capture environment are zero-size, so this box does
344 // `Fn`s that don’t capture environment are zero-size, so this box does
322 // not allocate:
345 // not allocate:
323 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
346 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
324 }
347 }
325
348
326 impl<T, E> LazyCell<T, E> {
349 impl<T, E> LazyCell<T, E> {
327 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
350 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
328 Self {
351 Self {
329 value: RefCell::new(None),
352 value: RefCell::new(None),
330 init: Box::new(init),
353 init: Box::new(init),
331 }
354 }
332 }
355 }
333
356
334 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
357 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
335 let mut borrowed = self.value.borrow();
358 let mut borrowed = self.value.borrow();
336 if borrowed.is_none() {
359 if borrowed.is_none() {
337 drop(borrowed);
360 drop(borrowed);
338 // Only use `borrow_mut` if it is really needed to avoid panic in
361 // Only use `borrow_mut` if it is really needed to avoid panic in
339 // case there is another outstanding borrow but mutation is not
362 // case there is another outstanding borrow but mutation is not
340 // needed.
363 // needed.
341 *self.value.borrow_mut() = Some((self.init)(repo)?);
364 *self.value.borrow_mut() = Some((self.init)(repo)?);
342 borrowed = self.value.borrow()
365 borrowed = self.value.borrow()
343 }
366 }
344 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
367 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
345 }
368 }
346
369
347 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
370 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
348 let mut borrowed = self.value.borrow_mut();
371 let mut borrowed = self.value.borrow_mut();
349 if borrowed.is_none() {
372 if borrowed.is_none() {
350 *borrowed = Some((self.init)(repo)?);
373 *borrowed = Some((self.init)(repo)?);
351 }
374 }
352 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
375 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
353 }
376 }
354 }
377 }
355
378
356 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
379 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
357 struct MmapWrapper(memmap2::Mmap);
380 struct MmapWrapper(memmap2::Mmap);
358
381
359 impl std::ops::Deref for MmapWrapper {
382 impl std::ops::Deref for MmapWrapper {
360 type Target = [u8];
383 type Target = [u8];
361
384
362 fn deref(&self) -> &[u8] {
385 fn deref(&self) -> &[u8] {
363 self.0.deref()
386 self.0.deref()
364 }
387 }
365 }
388 }
366
389
367 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
390 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
@@ -1,62 +1,61 b''
1 //! The revset query language
1 //! The revset query language
2 //!
2 //!
3 //! <https://www.mercurial-scm.org/repo/hg/help/revsets>
3 //! <https://www.mercurial-scm.org/repo/hg/help/revsets>
4
4
5 use crate::errors::HgError;
5 use crate::errors::HgError;
6 use crate::repo::Repo;
6 use crate::repo::Repo;
7 use crate::revlog::changelog::Changelog;
8 use crate::revlog::revlog::{Revlog, RevlogError};
7 use crate::revlog::revlog::{Revlog, RevlogError};
9 use crate::revlog::NodePrefix;
8 use crate::revlog::NodePrefix;
10 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
9 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
11 use crate::Node;
10 use crate::Node;
12
11
13 /// Resolve a query string into a single revision.
12 /// Resolve a query string into a single revision.
14 ///
13 ///
15 /// Only some of the revset language is implemented yet.
14 /// Only some of the revset language is implemented yet.
16 pub fn resolve_single(
15 pub fn resolve_single(
17 input: &str,
16 input: &str,
18 repo: &Repo,
17 repo: &Repo,
19 ) -> Result<Revision, RevlogError> {
18 ) -> Result<Revision, RevlogError> {
20 let changelog = Changelog::open(repo)?;
19 let changelog = repo.changelog()?;
21
20
22 match resolve_rev_number_or_hex_prefix(input, &changelog.revlog) {
21 match resolve_rev_number_or_hex_prefix(input, &changelog.revlog) {
23 Err(RevlogError::InvalidRevision) => {} // Try other syntax
22 Err(RevlogError::InvalidRevision) => {} // Try other syntax
24 result => return result,
23 result => return result,
25 }
24 }
26
25
27 if input == "null" {
26 if input == "null" {
28 return Ok(NULL_REVISION);
27 return Ok(NULL_REVISION);
29 }
28 }
30
29
31 // TODO: support for the rest of the language here.
30 // TODO: support for the rest of the language here.
32
31
33 Err(
32 Err(
34 HgError::unsupported(format!("cannot parse revset '{}'", input))
33 HgError::unsupported(format!("cannot parse revset '{}'", input))
35 .into(),
34 .into(),
36 )
35 )
37 }
36 }
38
37
39 /// Resolve the small subset of the language suitable for revlogs other than
38 /// Resolve the small subset of the language suitable for revlogs other than
40 /// the changelog, such as in `hg debugdata --manifest` CLI argument.
39 /// the changelog, such as in `hg debugdata --manifest` CLI argument.
41 ///
40 ///
42 /// * A non-negative decimal integer for a revision number, or
41 /// * A non-negative decimal integer for a revision number, or
43 /// * An hexadecimal string, for the unique node ID that starts with this
42 /// * An hexadecimal string, for the unique node ID that starts with this
44 /// prefix
43 /// prefix
45 pub fn resolve_rev_number_or_hex_prefix(
44 pub fn resolve_rev_number_or_hex_prefix(
46 input: &str,
45 input: &str,
47 revlog: &Revlog,
46 revlog: &Revlog,
48 ) -> Result<Revision, RevlogError> {
47 ) -> Result<Revision, RevlogError> {
49 if let Ok(integer) = input.parse::<i32>() {
48 if let Ok(integer) = input.parse::<i32>() {
50 if integer >= 0 && revlog.has_rev(integer) {
49 if integer >= 0 && revlog.has_rev(integer) {
51 return Ok(integer);
50 return Ok(integer);
52 }
51 }
53 }
52 }
54 if let Ok(prefix) = NodePrefix::from_hex(input) {
53 if let Ok(prefix) = NodePrefix::from_hex(input) {
55 if prefix.is_prefix_of(&Node::from_hex(WORKING_DIRECTORY_HEX).unwrap())
54 if prefix.is_prefix_of(&Node::from_hex(WORKING_DIRECTORY_HEX).unwrap())
56 {
55 {
57 return Err(RevlogError::WDirUnsupported);
56 return Err(RevlogError::WDirUnsupported);
58 }
57 }
59 return revlog.get_node_rev(prefix);
58 return revlog.get_node_rev(prefix);
60 }
59 }
61 Err(RevlogError::InvalidRevision)
60 Err(RevlogError::InvalidRevision)
62 }
61 }
General Comments 0
You need to be logged in to leave comments. Login now