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