##// END OF EJS Templates
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
Simon Sapin -
r48783:87e3f878 default
parent child Browse files
Show More
@@ -1,71 +1,71 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::repo::Repo;
8 use crate::repo::Repo;
9 use crate::revlog::revlog::RevlogError;
9 use crate::revlog::revlog::RevlogError;
10 use crate::revlog::Node;
10 use crate::revlog::Node;
11
11
12 use crate::utils::hg_path::HgPathBuf;
12 use crate::utils::hg_path::HgPathBuf;
13
13
14 pub struct CatOutput {
14 pub struct CatOutput {
15 /// Whether any file in the manifest matched the paths given as CLI
15 /// Whether any file in the manifest matched the paths given as CLI
16 /// arguments
16 /// arguments
17 pub found_any: bool,
17 pub found_any: bool,
18 /// The contents of matching files, in manifest order
18 /// The contents of matching files, in manifest order
19 pub concatenated: Vec<u8>,
19 pub concatenated: Vec<u8>,
20 /// Which of the CLI arguments did not match any manifest file
20 /// Which of the CLI arguments did not match any manifest file
21 pub missing: Vec<HgPathBuf>,
21 pub missing: Vec<HgPathBuf>,
22 /// The node ID that the given revset was resolved to
22 /// The node ID that the given revset was resolved to
23 pub node: Node,
23 pub node: Node,
24 }
24 }
25
25
26 /// Output the given revision of files
26 /// Output the given revision of files
27 ///
27 ///
28 /// * `root`: Repository root
28 /// * `root`: Repository root
29 /// * `rev`: The revision to cat the files from.
29 /// * `rev`: The revision to cat the files from.
30 /// * `files`: The files to output.
30 /// * `files`: The files to output.
31 pub fn cat<'a>(
31 pub fn cat<'a>(
32 repo: &Repo,
32 repo: &Repo,
33 revset: &str,
33 revset: &str,
34 files: &'a [HgPathBuf],
34 files: &'a [HgPathBuf],
35 ) -> Result<CatOutput, RevlogError> {
35 ) -> Result<CatOutput, RevlogError> {
36 let rev = crate::revset::resolve_single(revset, repo)?;
36 let rev = crate::revset::resolve_single(revset, repo)?;
37 let manifest = repo.manifest_for_rev(rev)?;
37 let manifest = repo.manifest_for_rev(rev)?;
38 let node = *repo
38 let node = *repo
39 .changelog()?
39 .changelog()?
40 .node_from_rev(rev)
40 .node_from_rev(rev)
41 .expect("should succeed when repo.manifest did");
41 .expect("should succeed when repo.manifest did");
42 let mut bytes = vec![];
42 let mut bytes = vec![];
43 let mut matched = vec![false; files.len()];
43 let mut matched = vec![false; files.len()];
44 let mut found_any = false;
44 let mut found_any = false;
45
45
46 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
46 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
47 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
47 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
48 if cat_file.as_bytes() == manifest_file.as_bytes() {
48 if cat_file.as_bytes() == manifest_file.as_bytes() {
49 *is_matched = true;
49 *is_matched = true;
50 found_any = true;
50 found_any = true;
51 let file_log = repo.filelog(manifest_file)?;
51 let file_log = repo.filelog(manifest_file)?;
52 let file_node = Node::from_hex_for_repo(node_bytes)?;
52 let file_node = Node::from_hex_for_repo(node_bytes)?;
53 let entry = file_log.get_node(file_node)?;
53 let entry = file_log.data_for_node(file_node)?;
54 bytes.extend(entry.data()?)
54 bytes.extend(entry.data()?)
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 let missing: Vec<_> = files
59 let missing: Vec<_> = files
60 .iter()
60 .iter()
61 .zip(&matched)
61 .zip(&matched)
62 .filter(|pair| !*pair.1)
62 .filter(|pair| !*pair.1)
63 .map(|pair| pair.0.clone())
63 .map(|pair| pair.0.clone())
64 .collect();
64 .collect();
65 Ok(CatOutput {
65 Ok(CatOutput {
66 found_any,
66 found_any,
67 concatenated: bytes,
67 concatenated: bytes,
68 missing,
68 missing,
69 node,
69 node,
70 })
70 })
71 }
71 }
@@ -1,419 +1,422 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::exit_codes;
9 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
15 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::{requirements, NodePrefix};
16 use crate::{requirements, NodePrefix};
17 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
19 use std::collections::HashSet;
19 use std::collections::HashSet;
20 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
21
21
22 /// A repository on disk
22 /// A repository on disk
23 pub struct Repo {
23 pub struct Repo {
24 working_directory: PathBuf,
24 working_directory: PathBuf,
25 dot_hg: PathBuf,
25 dot_hg: PathBuf,
26 store: PathBuf,
26 store: PathBuf,
27 requirements: HashSet<String>,
27 requirements: HashSet<String>,
28 config: Config,
28 config: Config,
29 // None means not known/initialized yet
29 // None means not known/initialized yet
30 dirstate_parents: Cell<Option<DirstateParents>>,
30 dirstate_parents: Cell<Option<DirstateParents>>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 changelog: LazyCell<Changelog, HgError>,
32 changelog: LazyCell<Changelog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
34 }
34 }
35
35
36 #[derive(Debug, derive_more::From)]
36 #[derive(Debug, derive_more::From)]
37 pub enum RepoError {
37 pub enum RepoError {
38 NotFound {
38 NotFound {
39 at: PathBuf,
39 at: PathBuf,
40 },
40 },
41 #[from]
41 #[from]
42 ConfigParseError(ConfigParseError),
42 ConfigParseError(ConfigParseError),
43 #[from]
43 #[from]
44 Other(HgError),
44 Other(HgError),
45 }
45 }
46
46
47 impl From<ConfigError> for RepoError {
47 impl From<ConfigError> for RepoError {
48 fn from(error: ConfigError) -> Self {
48 fn from(error: ConfigError) -> Self {
49 match error {
49 match error {
50 ConfigError::Parse(error) => error.into(),
50 ConfigError::Parse(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
52 }
52 }
53 }
53 }
54 }
54 }
55
55
56 impl Repo {
56 impl Repo {
57 /// tries to find nearest repository root in current working directory or
57 /// tries to find nearest repository root in current working directory or
58 /// its ancestors
58 /// its ancestors
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 let current_directory = crate::utils::current_dir()?;
60 let current_directory = crate::utils::current_dir()?;
61 // ancestors() is inclusive: it first yields `current_directory`
61 // ancestors() is inclusive: it first yields `current_directory`
62 // as-is.
62 // as-is.
63 for ancestor in current_directory.ancestors() {
63 for ancestor in current_directory.ancestors() {
64 if is_dir(ancestor.join(".hg"))? {
64 if is_dir(ancestor.join(".hg"))? {
65 return Ok(ancestor.to_path_buf());
65 return Ok(ancestor.to_path_buf());
66 }
66 }
67 }
67 }
68 return Err(RepoError::NotFound {
68 return Err(RepoError::NotFound {
69 at: current_directory,
69 at: current_directory,
70 });
70 });
71 }
71 }
72
72
73 /// Find a repository, either at the given path (which must contain a `.hg`
73 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// sub-directory) or by searching the current directory and its
74 /// sub-directory) or by searching the current directory and its
75 /// ancestors.
75 /// ancestors.
76 ///
76 ///
77 /// A method with two very different "modes" like this usually a code smell
77 /// A method with two very different "modes" like this usually a code smell
78 /// to make two methods instead, but in this case an `Option` is what rhg
78 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// Having two methods would just move that `if` to almost all callers.
80 /// Having two methods would just move that `if` to almost all callers.
81 pub fn find(
81 pub fn find(
82 config: &Config,
82 config: &Config,
83 explicit_path: Option<PathBuf>,
83 explicit_path: Option<PathBuf>,
84 ) -> Result<Self, RepoError> {
84 ) -> Result<Self, RepoError> {
85 if let Some(root) = explicit_path {
85 if let Some(root) = explicit_path {
86 if is_dir(root.join(".hg"))? {
86 if is_dir(root.join(".hg"))? {
87 Self::new_at_path(root.to_owned(), config)
87 Self::new_at_path(root.to_owned(), config)
88 } else if is_file(&root)? {
88 } else if is_file(&root)? {
89 Err(HgError::unsupported("bundle repository").into())
89 Err(HgError::unsupported("bundle repository").into())
90 } else {
90 } else {
91 Err(RepoError::NotFound {
91 Err(RepoError::NotFound {
92 at: root.to_owned(),
92 at: root.to_owned(),
93 })
93 })
94 }
94 }
95 } else {
95 } else {
96 let root = Self::find_repo_root()?;
96 let root = Self::find_repo_root()?;
97 Self::new_at_path(root, config)
97 Self::new_at_path(root, config)
98 }
98 }
99 }
99 }
100
100
101 /// To be called after checking that `.hg` is a sub-directory
101 /// To be called after checking that `.hg` is a sub-directory
102 fn new_at_path(
102 fn new_at_path(
103 working_directory: PathBuf,
103 working_directory: PathBuf,
104 config: &Config,
104 config: &Config,
105 ) -> Result<Self, RepoError> {
105 ) -> Result<Self, RepoError> {
106 let dot_hg = working_directory.join(".hg");
106 let dot_hg = working_directory.join(".hg");
107
107
108 let mut repo_config_files = Vec::new();
108 let mut repo_config_files = Vec::new();
109 repo_config_files.push(dot_hg.join("hgrc"));
109 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111
111
112 let hg_vfs = Vfs { base: &dot_hg };
112 let hg_vfs = Vfs { base: &dot_hg };
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let relative =
114 let relative =
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 let shared =
116 let shared =
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118
118
119 // From `mercurial/localrepo.py`:
119 // From `mercurial/localrepo.py`:
120 //
120 //
121 // if .hg/requires contains the sharesafe requirement, it means
121 // if .hg/requires contains the sharesafe requirement, it means
122 // there exists a `.hg/store/requires` too and we should read it
122 // there exists a `.hg/store/requires` too and we should read it
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is not present, refer checkrequirementscompat() for that
125 // is not present, refer checkrequirementscompat() for that
126 //
126 //
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // repository was shared the old way. We check the share source
128 // repository was shared the old way. We check the share source
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // current repository needs to be reshared
130 // current repository needs to be reshared
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132
132
133 let store_path;
133 let store_path;
134 if !shared {
134 if !shared {
135 store_path = dot_hg.join("store");
135 store_path = dot_hg.join("store");
136 } else {
136 } else {
137 let bytes = hg_vfs.read("sharedpath")?;
137 let bytes = hg_vfs.read("sharedpath")?;
138 let mut shared_path =
138 let mut shared_path =
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 .to_owned();
140 .to_owned();
141 if relative {
141 if relative {
142 shared_path = dot_hg.join(shared_path)
142 shared_path = dot_hg.join(shared_path)
143 }
143 }
144 if !is_dir(&shared_path)? {
144 if !is_dir(&shared_path)? {
145 return Err(HgError::corrupted(format!(
145 return Err(HgError::corrupted(format!(
146 ".hg/sharedpath points to nonexistent directory {}",
146 ".hg/sharedpath points to nonexistent directory {}",
147 shared_path.display()
147 shared_path.display()
148 ))
148 ))
149 .into());
149 .into());
150 }
150 }
151
151
152 store_path = shared_path.join("store");
152 store_path = shared_path.join("store");
153
153
154 let source_is_share_safe =
154 let source_is_share_safe =
155 requirements::load(Vfs { base: &shared_path })?
155 requirements::load(Vfs { base: &shared_path })?
156 .contains(requirements::SHARESAFE_REQUIREMENT);
156 .contains(requirements::SHARESAFE_REQUIREMENT);
157
157
158 if share_safe && !source_is_share_safe {
158 if share_safe && !source_is_share_safe {
159 return Err(match config
159 return Err(match config
160 .get(b"share", b"safe-mismatch.source-not-safe")
160 .get(b"share", b"safe-mismatch.source-not-safe")
161 {
161 {
162 Some(b"abort") | None => HgError::abort(
162 Some(b"abort") | None => HgError::abort(
163 "abort: share source does not support share-safe requirement\n\
163 "abort: share source does not support share-safe requirement\n\
164 (see `hg help config.format.use-share-safe` for more information)",
164 (see `hg help config.format.use-share-safe` for more information)",
165 exit_codes::ABORT,
165 exit_codes::ABORT,
166 ),
166 ),
167 _ => HgError::unsupported("share-safe downgrade"),
167 _ => HgError::unsupported("share-safe downgrade"),
168 }
168 }
169 .into());
169 .into());
170 } else if source_is_share_safe && !share_safe {
170 } else if source_is_share_safe && !share_safe {
171 return Err(
171 return Err(
172 match config.get(b"share", b"safe-mismatch.source-safe") {
172 match config.get(b"share", b"safe-mismatch.source-safe") {
173 Some(b"abort") | None => HgError::abort(
173 Some(b"abort") | None => HgError::abort(
174 "abort: version mismatch: source uses share-safe \
174 "abort: version mismatch: source uses share-safe \
175 functionality while the current share does not\n\
175 functionality while the current share does not\n\
176 (see `hg help config.format.use-share-safe` for more information)",
176 (see `hg help config.format.use-share-safe` for more information)",
177 exit_codes::ABORT,
177 exit_codes::ABORT,
178 ),
178 ),
179 _ => HgError::unsupported("share-safe upgrade"),
179 _ => HgError::unsupported("share-safe upgrade"),
180 }
180 }
181 .into(),
181 .into(),
182 );
182 );
183 }
183 }
184
184
185 if share_safe {
185 if share_safe {
186 repo_config_files.insert(0, shared_path.join("hgrc"))
186 repo_config_files.insert(0, shared_path.join("hgrc"))
187 }
187 }
188 }
188 }
189 if share_safe {
189 if share_safe {
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 }
191 }
192
192
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 config.combine_with_repo(&repo_config_files)?
194 config.combine_with_repo(&repo_config_files)?
195 } else {
195 } else {
196 config.clone()
196 config.clone()
197 };
197 };
198
198
199 let repo = Self {
199 let repo = Self {
200 requirements: reqs,
200 requirements: reqs,
201 working_directory,
201 working_directory,
202 store: store_path,
202 store: store_path,
203 dot_hg,
203 dot_hg,
204 config: repo_config,
204 config: repo_config,
205 dirstate_parents: Cell::new(None),
205 dirstate_parents: Cell::new(None),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 changelog: LazyCell::new(Changelog::open),
207 changelog: LazyCell::new(Changelog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
209 };
209 };
210
210
211 requirements::check(&repo)?;
211 requirements::check(&repo)?;
212
212
213 Ok(repo)
213 Ok(repo)
214 }
214 }
215
215
216 pub fn working_directory_path(&self) -> &Path {
216 pub fn working_directory_path(&self) -> &Path {
217 &self.working_directory
217 &self.working_directory
218 }
218 }
219
219
220 pub fn requirements(&self) -> &HashSet<String> {
220 pub fn requirements(&self) -> &HashSet<String> {
221 &self.requirements
221 &self.requirements
222 }
222 }
223
223
224 pub fn config(&self) -> &Config {
224 pub fn config(&self) -> &Config {
225 &self.config
225 &self.config
226 }
226 }
227
227
228 /// For accessing repository files (in `.hg`), except for the store
228 /// For accessing repository files (in `.hg`), except for the store
229 /// (`.hg/store`).
229 /// (`.hg/store`).
230 pub fn hg_vfs(&self) -> Vfs<'_> {
230 pub fn hg_vfs(&self) -> Vfs<'_> {
231 Vfs { base: &self.dot_hg }
231 Vfs { base: &self.dot_hg }
232 }
232 }
233
233
234 /// For accessing repository store files (in `.hg/store`)
234 /// For accessing repository store files (in `.hg/store`)
235 pub fn store_vfs(&self) -> Vfs<'_> {
235 pub fn store_vfs(&self) -> Vfs<'_> {
236 Vfs { base: &self.store }
236 Vfs { base: &self.store }
237 }
237 }
238
238
239 /// For accessing the working copy
239 /// For accessing the working copy
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 Vfs {
241 Vfs {
242 base: &self.working_directory,
242 base: &self.working_directory,
243 }
243 }
244 }
244 }
245
245
246 pub fn has_dirstate_v2(&self) -> bool {
246 pub fn has_dirstate_v2(&self) -> bool {
247 self.requirements
247 self.requirements
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
249 }
249 }
250
250
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
252 Ok(self
252 Ok(self
253 .hg_vfs()
253 .hg_vfs()
254 .read("dirstate")
254 .read("dirstate")
255 .io_not_found_as_none()?
255 .io_not_found_as_none()?
256 .unwrap_or(Vec::new()))
256 .unwrap_or(Vec::new()))
257 }
257 }
258
258
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
260 if let Some(parents) = self.dirstate_parents.get() {
260 if let Some(parents) = self.dirstate_parents.get() {
261 return Ok(parents);
261 return Ok(parents);
262 }
262 }
263 let dirstate = self.dirstate_file_contents()?;
263 let dirstate = self.dirstate_file_contents()?;
264 let parents = if dirstate.is_empty() {
264 let parents = if dirstate.is_empty() {
265 DirstateParents::NULL
265 DirstateParents::NULL
266 } else if self.has_dirstate_v2() {
266 } else if self.has_dirstate_v2() {
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
268 } else {
268 } else {
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
270 .clone()
270 .clone()
271 };
271 };
272 self.dirstate_parents.set(Some(parents));
272 self.dirstate_parents.set(Some(parents));
273 Ok(parents)
273 Ok(parents)
274 }
274 }
275
275
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
277 let dirstate_file_contents = self.dirstate_file_contents()?;
277 let dirstate_file_contents = self.dirstate_file_contents()?;
278 if dirstate_file_contents.is_empty() {
278 if dirstate_file_contents.is_empty() {
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
281 } else if self.has_dirstate_v2() {
281 } else if self.has_dirstate_v2() {
282 let docket = crate::dirstate_tree::on_disk::read_docket(
282 let docket = crate::dirstate_tree::on_disk::read_docket(
283 &dirstate_file_contents,
283 &dirstate_file_contents,
284 )?;
284 )?;
285 self.dirstate_parents.set(Some(docket.parents()));
285 self.dirstate_parents.set(Some(docket.parents()));
286 let data_size = docket.data_size();
286 let data_size = docket.data_size();
287 let metadata = docket.tree_metadata();
287 let metadata = docket.tree_metadata();
288 let mut map = if let Some(data_mmap) = self
288 let mut map = if let Some(data_mmap) = self
289 .hg_vfs()
289 .hg_vfs()
290 .mmap_open(docket.data_filename())
290 .mmap_open(docket.data_filename())
291 .io_not_found_as_none()?
291 .io_not_found_as_none()?
292 {
292 {
293 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
293 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
294 } else {
294 } else {
295 OwningDirstateMap::new_empty(Vec::new())
295 OwningDirstateMap::new_empty(Vec::new())
296 };
296 };
297 let (on_disk, placeholder) = map.get_mut_pair();
297 let (on_disk, placeholder) = map.get_mut_pair();
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
299 Ok(map)
299 Ok(map)
300 } else {
300 } else {
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
302 let (on_disk, placeholder) = map.get_mut_pair();
302 let (on_disk, placeholder) = map.get_mut_pair();
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
304 self.dirstate_parents
304 self.dirstate_parents
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
306 *placeholder = inner;
306 *placeholder = inner;
307 Ok(map)
307 Ok(map)
308 }
308 }
309 }
309 }
310
310
311 pub fn dirstate_map(
311 pub fn dirstate_map(
312 &self,
312 &self,
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
314 self.dirstate_map.get_or_init(self)
314 self.dirstate_map.get_or_init(self)
315 }
315 }
316
316
317 pub fn dirstate_map_mut(
317 pub fn dirstate_map_mut(
318 &self,
318 &self,
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
320 self.dirstate_map.get_mut_or_init(self)
320 self.dirstate_map.get_mut_or_init(self)
321 }
321 }
322
322
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
324 self.changelog.get_or_init(self)
324 self.changelog.get_or_init(self)
325 }
325 }
326
326
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
328 self.changelog.get_mut_or_init(self)
328 self.changelog.get_mut_or_init(self)
329 }
329 }
330
330
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
332 self.manifestlog.get_or_init(self)
332 self.manifestlog.get_or_init(self)
333 }
333 }
334
334
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
336 self.manifestlog.get_mut_or_init(self)
336 self.manifestlog.get_mut_or_init(self)
337 }
337 }
338
338
339 /// Returns the manifest of the given node ID
339 /// Returns the manifest of the *changeset* with the given node ID
340 pub fn manifest_for_node(
340 pub fn manifest_for_node(
341 &self,
341 &self,
342 node: impl Into<NodePrefix>,
342 node: impl Into<NodePrefix>,
343 ) -> Result<Manifest, RevlogError> {
343 ) -> Result<Manifest, RevlogError> {
344 self.manifestlog()?.get_node(
344 self.manifestlog()?.data_for_node(
345 self.changelog()?
345 self.changelog()?
346 .get_node(node.into())?
346 .data_for_node(node.into())?
347 .manifest_node()?
347 .manifest_node()?
348 .into(),
348 .into(),
349 )
349 )
350 }
350 }
351
351
352 /// Returns the manifest of the given revision
352 /// Returns the manifest of the *changeset* with the given revision number
353 pub fn manifest_for_rev(
353 pub fn manifest_for_rev(
354 &self,
354 &self,
355 revision: Revision,
355 revision: Revision,
356 ) -> Result<Manifest, RevlogError> {
356 ) -> Result<Manifest, RevlogError> {
357 self.manifestlog()?.get_node(
357 self.manifestlog()?.data_for_node(
358 self.changelog()?.get_rev(revision)?.manifest_node()?.into(),
358 self.changelog()?
359 .data_for_rev(revision)?
360 .manifest_node()?
361 .into(),
359 )
362 )
360 }
363 }
361
364
362 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
365 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
363 Filelog::open(self, path)
366 Filelog::open(self, path)
364 }
367 }
365 }
368 }
366
369
367 /// Lazily-initialized component of `Repo` with interior mutability
370 /// Lazily-initialized component of `Repo` with interior mutability
368 ///
371 ///
369 /// This differs from `OnceCell` in that the value can still be "deinitialized"
372 /// This differs from `OnceCell` in that the value can still be "deinitialized"
370 /// later by setting its inner `Option` to `None`.
373 /// later by setting its inner `Option` to `None`.
371 struct LazyCell<T, E> {
374 struct LazyCell<T, E> {
372 value: RefCell<Option<T>>,
375 value: RefCell<Option<T>>,
373 // `Fn`s that don’t capture environment are zero-size, so this box does
376 // `Fn`s that don’t capture environment are zero-size, so this box does
374 // not allocate:
377 // not allocate:
375 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
378 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
376 }
379 }
377
380
378 impl<T, E> LazyCell<T, E> {
381 impl<T, E> LazyCell<T, E> {
379 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
382 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
380 Self {
383 Self {
381 value: RefCell::new(None),
384 value: RefCell::new(None),
382 init: Box::new(init),
385 init: Box::new(init),
383 }
386 }
384 }
387 }
385
388
386 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
389 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
387 let mut borrowed = self.value.borrow();
390 let mut borrowed = self.value.borrow();
388 if borrowed.is_none() {
391 if borrowed.is_none() {
389 drop(borrowed);
392 drop(borrowed);
390 // Only use `borrow_mut` if it is really needed to avoid panic in
393 // Only use `borrow_mut` if it is really needed to avoid panic in
391 // case there is another outstanding borrow but mutation is not
394 // case there is another outstanding borrow but mutation is not
392 // needed.
395 // needed.
393 *self.value.borrow_mut() = Some((self.init)(repo)?);
396 *self.value.borrow_mut() = Some((self.init)(repo)?);
394 borrowed = self.value.borrow()
397 borrowed = self.value.borrow()
395 }
398 }
396 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
399 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
397 }
400 }
398
401
399 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
402 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
400 let mut borrowed = self.value.borrow_mut();
403 let mut borrowed = self.value.borrow_mut();
401 if borrowed.is_none() {
404 if borrowed.is_none() {
402 *borrowed = Some((self.init)(repo)?);
405 *borrowed = Some((self.init)(repo)?);
403 }
406 }
404 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
407 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
405 }
408 }
406 }
409 }
407
410
408 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
411 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
409 struct MmapWrapper(memmap2::Mmap);
412 struct MmapWrapper(memmap2::Mmap);
410
413
411 impl std::ops::Deref for MmapWrapper {
414 impl std::ops::Deref for MmapWrapper {
412 type Target = [u8];
415 type Target = [u8];
413
416
414 fn deref(&self) -> &[u8] {
417 fn deref(&self) -> &[u8] {
415 self.0.deref()
418 self.0.deref()
416 }
419 }
417 }
420 }
418
421
419 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
422 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
@@ -1,67 +1,67 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::revlog::{Revlog, RevlogError};
3 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::Revision;
4 use crate::revlog::Revision;
5 use crate::revlog::{Node, NodePrefix};
5 use crate::revlog::{Node, NodePrefix};
6
6
7 /// A specialized `Revlog` to work with `changelog` data format.
7 /// A specialized `Revlog` to work with `changelog` data format.
8 pub struct Changelog {
8 pub struct Changelog {
9 /// The generic `revlog` format.
9 /// The generic `revlog` format.
10 pub(crate) revlog: Revlog,
10 pub(crate) revlog: Revlog,
11 }
11 }
12
12
13 impl Changelog {
13 impl Changelog {
14 /// Open the `changelog` of a repository given by its root.
14 /// Open the `changelog` of a repository given by its root.
15 pub fn open(repo: &Repo) -> Result<Self, HgError> {
15 pub fn open(repo: &Repo) -> Result<Self, HgError> {
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
17 Ok(Self { revlog })
17 Ok(Self { revlog })
18 }
18 }
19
19
20 /// Return the `ChangelogEntry` a given node id.
20 /// Return the `ChangelogEntry` for the given node ID.
21 pub fn get_node(
21 pub fn data_for_node(
22 &self,
22 &self,
23 node: NodePrefix,
23 node: NodePrefix,
24 ) -> Result<ChangelogEntry, RevlogError> {
24 ) -> Result<ChangelogEntry, RevlogError> {
25 let rev = self.revlog.rev_from_node(node)?;
25 let rev = self.revlog.rev_from_node(node)?;
26 self.get_rev(rev)
26 self.data_for_rev(rev)
27 }
27 }
28
28
29 /// Return the `ChangelogEntry` of a given node revision.
29 /// Return the `ChangelogEntry` of the given revision number.
30 pub fn get_rev(
30 pub fn data_for_rev(
31 &self,
31 &self,
32 rev: Revision,
32 rev: Revision,
33 ) -> Result<ChangelogEntry, RevlogError> {
33 ) -> Result<ChangelogEntry, RevlogError> {
34 let bytes = self.revlog.get_rev_data(rev)?;
34 let bytes = self.revlog.get_rev_data(rev)?;
35 Ok(ChangelogEntry { bytes })
35 Ok(ChangelogEntry { bytes })
36 }
36 }
37
37
38 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
38 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
39 self.revlog.node_from_rev(rev)
39 self.revlog.node_from_rev(rev)
40 }
40 }
41 }
41 }
42
42
43 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
43 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
44 #[derive(Debug)]
44 #[derive(Debug)]
45 pub struct ChangelogEntry {
45 pub struct ChangelogEntry {
46 /// The data bytes of the `changelog` entry.
46 /// The data bytes of the `changelog` entry.
47 bytes: Vec<u8>,
47 bytes: Vec<u8>,
48 }
48 }
49
49
50 impl ChangelogEntry {
50 impl ChangelogEntry {
51 /// Return an iterator over the lines of the entry.
51 /// Return an iterator over the lines of the entry.
52 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
52 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
53 self.bytes
53 self.bytes
54 .split(|b| b == &b'\n')
54 .split(|b| b == &b'\n')
55 .filter(|line| !line.is_empty())
55 .filter(|line| !line.is_empty())
56 }
56 }
57
57
58 /// Return the node id of the `manifest` referenced by this `changelog`
58 /// Return the node id of the `manifest` referenced by this `changelog`
59 /// entry.
59 /// entry.
60 pub fn manifest_node(&self) -> Result<Node, HgError> {
60 pub fn manifest_node(&self) -> Result<Node, HgError> {
61 Node::from_hex_for_repo(
61 Node::from_hex_for_repo(
62 self.lines()
62 self.lines()
63 .next()
63 .next()
64 .ok_or_else(|| HgError::corrupted("empty changelog entry"))?,
64 .ok_or_else(|| HgError::corrupted("empty changelog entry"))?,
65 )
65 )
66 }
66 }
67 }
67 }
@@ -1,79 +1,79 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::path_encode::path_encode;
3 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::revlog::{Revlog, RevlogError};
5 use crate::revlog::NodePrefix;
5 use crate::revlog::NodePrefix;
6 use crate::revlog::Revision;
6 use crate::revlog::Revision;
7 use crate::utils::files::get_path_from_bytes;
7 use crate::utils::files::get_path_from_bytes;
8 use crate::utils::hg_path::HgPath;
8 use crate::utils::hg_path::HgPath;
9 use crate::utils::SliceExt;
9 use crate::utils::SliceExt;
10 use std::borrow::Cow;
10 use std::borrow::Cow;
11 use std::path::PathBuf;
11 use std::path::PathBuf;
12
12
13 /// A specialized `Revlog` to work with file data logs.
13 /// A specialized `Revlog` to work with file data logs.
14 pub struct Filelog {
14 pub struct Filelog {
15 /// The generic `revlog` format.
15 /// The generic `revlog` format.
16 revlog: Revlog,
16 revlog: Revlog,
17 }
17 }
18
18
19 impl Filelog {
19 impl Filelog {
20 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> {
20 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> {
21 let index_path = store_path(file_path, b".i");
21 let index_path = store_path(file_path, b".i");
22 let data_path = store_path(file_path, b".d");
22 let data_path = store_path(file_path, b".d");
23 let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
23 let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
24 Ok(Self { revlog })
24 Ok(Self { revlog })
25 }
25 }
26
26
27 /// The given node ID is that of the file as found in a manifest, not of a
27 /// The given node ID is that of the file as found in a manifest, not of a
28 /// changeset.
28 /// changeset.
29 pub fn get_node(
29 pub fn data_for_node(
30 &self,
30 &self,
31 file_node: impl Into<NodePrefix>,
31 file_node: impl Into<NodePrefix>,
32 ) -> Result<FilelogEntry, RevlogError> {
32 ) -> Result<FilelogEntry, RevlogError> {
33 let file_rev = self.revlog.rev_from_node(file_node.into())?;
33 let file_rev = self.revlog.rev_from_node(file_node.into())?;
34 self.get_rev(file_rev)
34 self.data_for_rev(file_rev)
35 }
35 }
36
36
37 /// The given revision is that of the file as found in a manifest, not of a
37 /// The given revision is that of the file as found in a manifest, not of a
38 /// changeset.
38 /// changeset.
39 pub fn get_rev(
39 pub fn data_for_rev(
40 &self,
40 &self,
41 file_rev: Revision,
41 file_rev: Revision,
42 ) -> Result<FilelogEntry, RevlogError> {
42 ) -> Result<FilelogEntry, RevlogError> {
43 let data = self.revlog.get_rev_data(file_rev)?;
43 let data = self.revlog.get_rev_data(file_rev)?;
44 Ok(FilelogEntry(data.into()))
44 Ok(FilelogEntry(data.into()))
45 }
45 }
46 }
46 }
47
47
48 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
48 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
49 let encoded_bytes =
49 let encoded_bytes =
50 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
50 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
51 get_path_from_bytes(&encoded_bytes).into()
51 get_path_from_bytes(&encoded_bytes).into()
52 }
52 }
53
53
54 pub struct FilelogEntry<'filelog>(Cow<'filelog, [u8]>);
54 pub struct FilelogEntry<'filelog>(Cow<'filelog, [u8]>);
55
55
56 impl<'filelog> FilelogEntry<'filelog> {
56 impl<'filelog> FilelogEntry<'filelog> {
57 /// Split into metadata and data
57 /// Split into metadata and data
58 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
58 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
59 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
59 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
60
60
61 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
61 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
62 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
62 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
63 Ok((Some(metadata), data))
63 Ok((Some(metadata), data))
64 } else {
64 } else {
65 Err(HgError::corrupted(
65 Err(HgError::corrupted(
66 "Missing metadata end delimiter in filelog entry",
66 "Missing metadata end delimiter in filelog entry",
67 ))
67 ))
68 }
68 }
69 } else {
69 } else {
70 Ok((None, &self.0))
70 Ok((None, &self.0))
71 }
71 }
72 }
72 }
73
73
74 /// Returns the file contents at this revision, stripped of any metadata
74 /// Returns the file contents at this revision, stripped of any metadata
75 pub fn data(&self) -> Result<&[u8], HgError> {
75 pub fn data(&self) -> Result<&[u8], HgError> {
76 let (_metadata, data) = self.split()?;
76 let (_metadata, data) = self.split()?;
77 Ok(data)
77 Ok(data)
78 }
78 }
79 }
79 }
@@ -1,84 +1,101 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::revlog::{Revlog, RevlogError};
3 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::Revision;
4 use crate::revlog::Revision;
5 use crate::revlog::{Node, NodePrefix};
5 use crate::revlog::{Node, NodePrefix};
6 use crate::utils::hg_path::HgPath;
6 use crate::utils::hg_path::HgPath;
7
7
8 /// A specialized `Revlog` to work with `manifest` data format.
8 /// A specialized `Revlog` to work with `manifest` data format.
9 pub struct Manifestlog {
9 pub struct Manifestlog {
10 /// The generic `revlog` format.
10 /// The generic `revlog` format.
11 revlog: Revlog,
11 revlog: Revlog,
12 }
12 }
13
13
14 impl Manifestlog {
14 impl Manifestlog {
15 /// Open the `manifest` of a repository given by its root.
15 /// Open the `manifest` of a repository given by its root.
16 pub fn open(repo: &Repo) -> Result<Self, HgError> {
16 pub fn open(repo: &Repo) -> Result<Self, HgError> {
17 let revlog = Revlog::open(repo, "00manifest.i", None)?;
17 let revlog = Revlog::open(repo, "00manifest.i", None)?;
18 Ok(Self { revlog })
18 Ok(Self { revlog })
19 }
19 }
20
20
21 /// Return the `ManifestEntry` of a given node id.
21 /// Return the `Manifest` for the given node ID.
22 pub fn get_node(&self, node: NodePrefix) -> Result<Manifest, RevlogError> {
22 ///
23 /// Note: this is a node ID in the manifestlog, typically found through
24 /// `ChangelogEntry::manifest_node`. It is *not* the node ID of any
25 /// changeset.
26 ///
27 /// See also `Repo::manifest_for_node`
28 pub fn data_for_node(
29 &self,
30 node: NodePrefix,
31 ) -> Result<Manifest, RevlogError> {
23 let rev = self.revlog.rev_from_node(node)?;
32 let rev = self.revlog.rev_from_node(node)?;
24 self.get_rev(rev)
33 self.data_for_rev(rev)
25 }
34 }
26
35
27 /// Return the `ManifestEntry` of a given node revision.
36 /// Return the `Manifest` of a given revision number.
28 pub fn get_rev(&self, rev: Revision) -> Result<Manifest, RevlogError> {
37 ///
38 /// Note: this is a revision number in the manifestlog, *not* of any
39 /// changeset.
40 ///
41 /// See also `Repo::manifest_for_rev`
42 pub fn data_for_rev(
43 &self,
44 rev: Revision,
45 ) -> Result<Manifest, RevlogError> {
29 let bytes = self.revlog.get_rev_data(rev)?;
46 let bytes = self.revlog.get_rev_data(rev)?;
30 Ok(Manifest { bytes })
47 Ok(Manifest { bytes })
31 }
48 }
32 }
49 }
33
50
34 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
51 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
35 #[derive(Debug)]
52 #[derive(Debug)]
36 pub struct Manifest {
53 pub struct Manifest {
37 bytes: Vec<u8>,
54 bytes: Vec<u8>,
38 }
55 }
39
56
40 impl Manifest {
57 impl Manifest {
41 /// Return an iterator over the lines of the entry.
58 /// Return an iterator over the lines of the entry.
42 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
59 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
43 self.bytes
60 self.bytes
44 .split(|b| b == &b'\n')
61 .split(|b| b == &b'\n')
45 .filter(|line| !line.is_empty())
62 .filter(|line| !line.is_empty())
46 }
63 }
47
64
48 /// Return an iterator over the files of the entry.
65 /// Return an iterator over the files of the entry.
49 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
66 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
50 self.lines().filter(|line| !line.is_empty()).map(|line| {
67 self.lines().filter(|line| !line.is_empty()).map(|line| {
51 let pos = line
68 let pos = line
52 .iter()
69 .iter()
53 .position(|x| x == &b'\0')
70 .position(|x| x == &b'\0')
54 .expect("manifest line should contain \\0");
71 .expect("manifest line should contain \\0");
55 HgPath::new(&line[..pos])
72 HgPath::new(&line[..pos])
56 })
73 })
57 }
74 }
58
75
59 /// Return an iterator over the files of the entry.
76 /// Return an iterator over the files of the entry.
60 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
77 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
61 self.lines().filter(|line| !line.is_empty()).map(|line| {
78 self.lines().filter(|line| !line.is_empty()).map(|line| {
62 let pos = line
79 let pos = line
63 .iter()
80 .iter()
64 .position(|x| x == &b'\0')
81 .position(|x| x == &b'\0')
65 .expect("manifest line should contain \\0");
82 .expect("manifest line should contain \\0");
66 let hash_start = pos + 1;
83 let hash_start = pos + 1;
67 let hash_end = hash_start + 40;
84 let hash_end = hash_start + 40;
68 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
85 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
69 })
86 })
70 }
87 }
71
88
72 /// If the given path is in this manifest, return its filelog node ID
89 /// If the given path is in this manifest, return its filelog node ID
73 pub fn find_file(&self, path: &HgPath) -> Result<Option<Node>, HgError> {
90 pub fn find_file(&self, path: &HgPath) -> Result<Option<Node>, HgError> {
74 // TODO: use binary search instead of linear scan. This may involve
91 // TODO: use binary search instead of linear scan. This may involve
75 // building (and caching) an index of the byte indicex of each manifest
92 // building (and caching) an index of the byte indicex of each manifest
76 // line.
93 // line.
77 for (manifest_path, node) in self.files_with_nodes() {
94 for (manifest_path, node) in self.files_with_nodes() {
78 if manifest_path == path {
95 if manifest_path == path {
79 return Ok(Some(Node::from_hex_for_repo(node)?));
96 return Ok(Some(Node::from_hex_for_repo(node)?));
80 }
97 }
81 }
98 }
82 Ok(None)
99 Ok(None)
83 }
100 }
84 }
101 }
@@ -1,406 +1,408 b''
1 use std::borrow::Cow;
1 use std::borrow::Cow;
2 use std::io::Read;
2 use std::io::Read;
3 use std::ops::Deref;
3 use std::ops::Deref;
4 use std::path::Path;
4 use std::path::Path;
5
5
6 use byteorder::{BigEndian, ByteOrder};
6 use byteorder::{BigEndian, ByteOrder};
7 use flate2::read::ZlibDecoder;
7 use flate2::read::ZlibDecoder;
8 use micro_timer::timed;
8 use micro_timer::timed;
9 use sha1::{Digest, Sha1};
9 use sha1::{Digest, Sha1};
10 use zstd;
10 use zstd;
11
11
12 use super::index::Index;
12 use super::index::Index;
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
14 use super::nodemap;
14 use super::nodemap;
15 use super::nodemap::{NodeMap, NodeMapError};
15 use super::nodemap::{NodeMap, NodeMapError};
16 use super::nodemap_docket::NodeMapDocket;
16 use super::nodemap_docket::NodeMapDocket;
17 use super::patch;
17 use super::patch;
18 use crate::errors::HgError;
18 use crate::errors::HgError;
19 use crate::repo::Repo;
19 use crate::repo::Repo;
20 use crate::revlog::Revision;
20 use crate::revlog::Revision;
21 use crate::{Node, NULL_REVISION};
21 use crate::{Node, NULL_REVISION};
22
22
23 #[derive(derive_more::From)]
23 #[derive(derive_more::From)]
24 pub enum RevlogError {
24 pub enum RevlogError {
25 InvalidRevision,
25 InvalidRevision,
26 /// Working directory is not supported
26 /// Working directory is not supported
27 WDirUnsupported,
27 WDirUnsupported,
28 /// Found more than one entry whose ID match the requested prefix
28 /// Found more than one entry whose ID match the requested prefix
29 AmbiguousPrefix,
29 AmbiguousPrefix,
30 #[from]
30 #[from]
31 Other(HgError),
31 Other(HgError),
32 }
32 }
33
33
34 impl From<NodeMapError> for RevlogError {
34 impl From<NodeMapError> for RevlogError {
35 fn from(error: NodeMapError) -> Self {
35 fn from(error: NodeMapError) -> Self {
36 match error {
36 match error {
37 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
37 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
38 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
38 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
39 }
39 }
40 }
40 }
41 }
41 }
42
42
43 impl RevlogError {
43 impl RevlogError {
44 fn corrupted() -> Self {
44 fn corrupted() -> Self {
45 RevlogError::Other(HgError::corrupted("corrupted revlog"))
45 RevlogError::Other(HgError::corrupted("corrupted revlog"))
46 }
46 }
47 }
47 }
48
48
49 /// Read only implementation of revlog.
49 /// Read only implementation of revlog.
50 pub struct Revlog {
50 pub struct Revlog {
51 /// When index and data are not interleaved: bytes of the revlog index.
51 /// When index and data are not interleaved: bytes of the revlog index.
52 /// When index and data are interleaved: bytes of the revlog index and
52 /// When index and data are interleaved: bytes of the revlog index and
53 /// data.
53 /// data.
54 index: Index,
54 index: Index,
55 /// When index and data are not interleaved: bytes of the revlog data
55 /// When index and data are not interleaved: bytes of the revlog data
56 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
56 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
57 /// When present on disk: the persistent nodemap for this revlog
57 /// When present on disk: the persistent nodemap for this revlog
58 nodemap: Option<nodemap::NodeTree>,
58 nodemap: Option<nodemap::NodeTree>,
59 }
59 }
60
60
61 impl Revlog {
61 impl Revlog {
62 /// Open a revlog index file.
62 /// Open a revlog index file.
63 ///
63 ///
64 /// It will also open the associated data file if index and data are not
64 /// It will also open the associated data file if index and data are not
65 /// interleaved.
65 /// interleaved.
66 #[timed]
66 #[timed]
67 pub fn open(
67 pub fn open(
68 repo: &Repo,
68 repo: &Repo,
69 index_path: impl AsRef<Path>,
69 index_path: impl AsRef<Path>,
70 data_path: Option<&Path>,
70 data_path: Option<&Path>,
71 ) -> Result<Self, HgError> {
71 ) -> Result<Self, HgError> {
72 let index_path = index_path.as_ref();
72 let index_path = index_path.as_ref();
73 let index_mmap = repo.store_vfs().mmap_open(&index_path)?;
73 let index_mmap = repo.store_vfs().mmap_open(&index_path)?;
74
74
75 let version = get_version(&index_mmap);
75 let version = get_version(&index_mmap);
76 if version != 1 {
76 if version != 1 {
77 // A proper new version should have had a repo/store requirement.
77 // A proper new version should have had a repo/store requirement.
78 return Err(HgError::corrupted("corrupted revlog"));
78 return Err(HgError::corrupted("corrupted revlog"));
79 }
79 }
80
80
81 let index = Index::new(Box::new(index_mmap))?;
81 let index = Index::new(Box::new(index_mmap))?;
82
82
83 let default_data_path = index_path.with_extension("d");
83 let default_data_path = index_path.with_extension("d");
84
84
85 // type annotation required
85 // type annotation required
86 // won't recognize Mmap as Deref<Target = [u8]>
86 // won't recognize Mmap as Deref<Target = [u8]>
87 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
87 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
88 if index.is_inline() {
88 if index.is_inline() {
89 None
89 None
90 } else {
90 } else {
91 let data_path = data_path.unwrap_or(&default_data_path);
91 let data_path = data_path.unwrap_or(&default_data_path);
92 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
92 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
93 Some(Box::new(data_mmap))
93 Some(Box::new(data_mmap))
94 };
94 };
95
95
96 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
96 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
97 |(docket, data)| {
97 |(docket, data)| {
98 nodemap::NodeTree::load_bytes(
98 nodemap::NodeTree::load_bytes(
99 Box::new(data),
99 Box::new(data),
100 docket.data_length,
100 docket.data_length,
101 )
101 )
102 },
102 },
103 );
103 );
104
104
105 Ok(Revlog {
105 Ok(Revlog {
106 index,
106 index,
107 data_bytes,
107 data_bytes,
108 nodemap,
108 nodemap,
109 })
109 })
110 }
110 }
111
111
112 /// Return number of entries of the `Revlog`.
112 /// Return number of entries of the `Revlog`.
113 pub fn len(&self) -> usize {
113 pub fn len(&self) -> usize {
114 self.index.len()
114 self.index.len()
115 }
115 }
116
116
117 /// Returns `true` if the `Revlog` has zero `entries`.
117 /// Returns `true` if the `Revlog` has zero `entries`.
118 pub fn is_empty(&self) -> bool {
118 pub fn is_empty(&self) -> bool {
119 self.index.is_empty()
119 self.index.is_empty()
120 }
120 }
121
121
122 /// Returns the node ID for the given revision number, if it exists in this revlog
122 /// Returns the node ID for the given revision number, if it exists in this
123 /// revlog
123 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
124 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
124 Some(self.index.get_entry(rev)?.hash())
125 Some(self.index.get_entry(rev)?.hash())
125 }
126 }
126
127
127 /// Return the revision number for the given node ID, if it exists in this revlog
128 /// Return the revision number for the given node ID, if it exists in this
129 /// revlog
128 #[timed]
130 #[timed]
129 pub fn rev_from_node(
131 pub fn rev_from_node(
130 &self,
132 &self,
131 node: NodePrefix,
133 node: NodePrefix,
132 ) -> Result<Revision, RevlogError> {
134 ) -> Result<Revision, RevlogError> {
133 if node.is_prefix_of(&NULL_NODE) {
135 if node.is_prefix_of(&NULL_NODE) {
134 return Ok(NULL_REVISION);
136 return Ok(NULL_REVISION);
135 }
137 }
136
138
137 if let Some(nodemap) = &self.nodemap {
139 if let Some(nodemap) = &self.nodemap {
138 return nodemap
140 return nodemap
139 .find_bin(&self.index, node)?
141 .find_bin(&self.index, node)?
140 .ok_or(RevlogError::InvalidRevision);
142 .ok_or(RevlogError::InvalidRevision);
141 }
143 }
142
144
143 // Fallback to linear scan when a persistent nodemap is not present.
145 // Fallback to linear scan when a persistent nodemap is not present.
144 // This happens when the persistent-nodemap experimental feature is not
146 // This happens when the persistent-nodemap experimental feature is not
145 // enabled, or for small revlogs.
147 // enabled, or for small revlogs.
146 //
148 //
147 // TODO: consider building a non-persistent nodemap in memory to
149 // TODO: consider building a non-persistent nodemap in memory to
148 // optimize these cases.
150 // optimize these cases.
149 let mut found_by_prefix = None;
151 let mut found_by_prefix = None;
150 for rev in (0..self.len() as Revision).rev() {
152 for rev in (0..self.len() as Revision).rev() {
151 let index_entry =
153 let index_entry =
152 self.index.get_entry(rev).ok_or(HgError::corrupted(
154 self.index.get_entry(rev).ok_or(HgError::corrupted(
153 "revlog references a revision not in the index",
155 "revlog references a revision not in the index",
154 ))?;
156 ))?;
155 if node == *index_entry.hash() {
157 if node == *index_entry.hash() {
156 return Ok(rev);
158 return Ok(rev);
157 }
159 }
158 if node.is_prefix_of(index_entry.hash()) {
160 if node.is_prefix_of(index_entry.hash()) {
159 if found_by_prefix.is_some() {
161 if found_by_prefix.is_some() {
160 return Err(RevlogError::AmbiguousPrefix);
162 return Err(RevlogError::AmbiguousPrefix);
161 }
163 }
162 found_by_prefix = Some(rev)
164 found_by_prefix = Some(rev)
163 }
165 }
164 }
166 }
165 found_by_prefix.ok_or(RevlogError::InvalidRevision)
167 found_by_prefix.ok_or(RevlogError::InvalidRevision)
166 }
168 }
167
169
168 /// Returns whether the given revision exists in this revlog.
170 /// Returns whether the given revision exists in this revlog.
169 pub fn has_rev(&self, rev: Revision) -> bool {
171 pub fn has_rev(&self, rev: Revision) -> bool {
170 self.index.get_entry(rev).is_some()
172 self.index.get_entry(rev).is_some()
171 }
173 }
172
174
173 /// Return the full data associated to a revision.
175 /// Return the full data associated to a revision.
174 ///
176 ///
175 /// All entries required to build the final data out of deltas will be
177 /// All entries required to build the final data out of deltas will be
176 /// retrieved as needed, and the deltas will be applied to the inital
178 /// retrieved as needed, and the deltas will be applied to the inital
177 /// snapshot to rebuild the final data.
179 /// snapshot to rebuild the final data.
178 #[timed]
180 #[timed]
179 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
181 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
180 // Todo return -> Cow
182 // Todo return -> Cow
181 let mut entry = self.get_entry(rev)?;
183 let mut entry = self.get_entry(rev)?;
182 let mut delta_chain = vec![];
184 let mut delta_chain = vec![];
183 while let Some(base_rev) = entry.base_rev {
185 while let Some(base_rev) = entry.base_rev {
184 delta_chain.push(entry);
186 delta_chain.push(entry);
185 entry = self
187 entry = self
186 .get_entry(base_rev)
188 .get_entry(base_rev)
187 .map_err(|_| RevlogError::corrupted())?;
189 .map_err(|_| RevlogError::corrupted())?;
188 }
190 }
189
191
190 // TODO do not look twice in the index
192 // TODO do not look twice in the index
191 let index_entry = self
193 let index_entry = self
192 .index
194 .index
193 .get_entry(rev)
195 .get_entry(rev)
194 .ok_or(RevlogError::InvalidRevision)?;
196 .ok_or(RevlogError::InvalidRevision)?;
195
197
196 let data: Vec<u8> = if delta_chain.is_empty() {
198 let data: Vec<u8> = if delta_chain.is_empty() {
197 entry.data()?.into()
199 entry.data()?.into()
198 } else {
200 } else {
199 Revlog::build_data_from_deltas(entry, &delta_chain)?
201 Revlog::build_data_from_deltas(entry, &delta_chain)?
200 };
202 };
201
203
202 if self.check_hash(
204 if self.check_hash(
203 index_entry.p1(),
205 index_entry.p1(),
204 index_entry.p2(),
206 index_entry.p2(),
205 index_entry.hash().as_bytes(),
207 index_entry.hash().as_bytes(),
206 &data,
208 &data,
207 ) {
209 ) {
208 Ok(data)
210 Ok(data)
209 } else {
211 } else {
210 Err(RevlogError::corrupted())
212 Err(RevlogError::corrupted())
211 }
213 }
212 }
214 }
213
215
214 /// Check the hash of some given data against the recorded hash.
216 /// Check the hash of some given data against the recorded hash.
215 pub fn check_hash(
217 pub fn check_hash(
216 &self,
218 &self,
217 p1: Revision,
219 p1: Revision,
218 p2: Revision,
220 p2: Revision,
219 expected: &[u8],
221 expected: &[u8],
220 data: &[u8],
222 data: &[u8],
221 ) -> bool {
223 ) -> bool {
222 let e1 = self.index.get_entry(p1);
224 let e1 = self.index.get_entry(p1);
223 let h1 = match e1 {
225 let h1 = match e1 {
224 Some(ref entry) => entry.hash(),
226 Some(ref entry) => entry.hash(),
225 None => &NULL_NODE,
227 None => &NULL_NODE,
226 };
228 };
227 let e2 = self.index.get_entry(p2);
229 let e2 = self.index.get_entry(p2);
228 let h2 = match e2 {
230 let h2 = match e2 {
229 Some(ref entry) => entry.hash(),
231 Some(ref entry) => entry.hash(),
230 None => &NULL_NODE,
232 None => &NULL_NODE,
231 };
233 };
232
234
233 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
235 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
234 }
236 }
235
237
236 /// Build the full data of a revision out its snapshot
238 /// Build the full data of a revision out its snapshot
237 /// and its deltas.
239 /// and its deltas.
238 #[timed]
240 #[timed]
239 fn build_data_from_deltas(
241 fn build_data_from_deltas(
240 snapshot: RevlogEntry,
242 snapshot: RevlogEntry,
241 deltas: &[RevlogEntry],
243 deltas: &[RevlogEntry],
242 ) -> Result<Vec<u8>, RevlogError> {
244 ) -> Result<Vec<u8>, RevlogError> {
243 let snapshot = snapshot.data()?;
245 let snapshot = snapshot.data()?;
244 let deltas = deltas
246 let deltas = deltas
245 .iter()
247 .iter()
246 .rev()
248 .rev()
247 .map(RevlogEntry::data)
249 .map(RevlogEntry::data)
248 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
250 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
249 let patches: Vec<_> =
251 let patches: Vec<_> =
250 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
252 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
251 let patch = patch::fold_patch_lists(&patches);
253 let patch = patch::fold_patch_lists(&patches);
252 Ok(patch.apply(&snapshot))
254 Ok(patch.apply(&snapshot))
253 }
255 }
254
256
255 /// Return the revlog data.
257 /// Return the revlog data.
256 fn data(&self) -> &[u8] {
258 fn data(&self) -> &[u8] {
257 match self.data_bytes {
259 match self.data_bytes {
258 Some(ref data_bytes) => &data_bytes,
260 Some(ref data_bytes) => &data_bytes,
259 None => panic!(
261 None => panic!(
260 "forgot to load the data or trying to access inline data"
262 "forgot to load the data or trying to access inline data"
261 ),
263 ),
262 }
264 }
263 }
265 }
264
266
265 /// Get an entry of the revlog.
267 /// Get an entry of the revlog.
266 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
268 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
267 let index_entry = self
269 let index_entry = self
268 .index
270 .index
269 .get_entry(rev)
271 .get_entry(rev)
270 .ok_or(RevlogError::InvalidRevision)?;
272 .ok_or(RevlogError::InvalidRevision)?;
271 let start = index_entry.offset();
273 let start = index_entry.offset();
272 let end = start + index_entry.compressed_len();
274 let end = start + index_entry.compressed_len();
273 let data = if self.index.is_inline() {
275 let data = if self.index.is_inline() {
274 self.index.data(start, end)
276 self.index.data(start, end)
275 } else {
277 } else {
276 &self.data()[start..end]
278 &self.data()[start..end]
277 };
279 };
278 let entry = RevlogEntry {
280 let entry = RevlogEntry {
279 rev,
281 rev,
280 bytes: data,
282 bytes: data,
281 compressed_len: index_entry.compressed_len(),
283 compressed_len: index_entry.compressed_len(),
282 uncompressed_len: index_entry.uncompressed_len(),
284 uncompressed_len: index_entry.uncompressed_len(),
283 base_rev: if index_entry.base_revision() == rev {
285 base_rev: if index_entry.base_revision() == rev {
284 None
286 None
285 } else {
287 } else {
286 Some(index_entry.base_revision())
288 Some(index_entry.base_revision())
287 },
289 },
288 };
290 };
289 Ok(entry)
291 Ok(entry)
290 }
292 }
291 }
293 }
292
294
293 /// The revlog entry's bytes and the necessary informations to extract
295 /// The revlog entry's bytes and the necessary informations to extract
294 /// the entry's data.
296 /// the entry's data.
295 #[derive(Debug)]
297 #[derive(Debug)]
296 pub struct RevlogEntry<'a> {
298 pub struct RevlogEntry<'a> {
297 rev: Revision,
299 rev: Revision,
298 bytes: &'a [u8],
300 bytes: &'a [u8],
299 compressed_len: usize,
301 compressed_len: usize,
300 uncompressed_len: usize,
302 uncompressed_len: usize,
301 base_rev: Option<Revision>,
303 base_rev: Option<Revision>,
302 }
304 }
303
305
304 impl<'a> RevlogEntry<'a> {
306 impl<'a> RevlogEntry<'a> {
305 /// Extract the data contained in the entry.
307 /// Extract the data contained in the entry.
306 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
308 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
307 if self.bytes.is_empty() {
309 if self.bytes.is_empty() {
308 return Ok(Cow::Borrowed(&[]));
310 return Ok(Cow::Borrowed(&[]));
309 }
311 }
310 match self.bytes[0] {
312 match self.bytes[0] {
311 // Revision data is the entirety of the entry, including this
313 // Revision data is the entirety of the entry, including this
312 // header.
314 // header.
313 b'\0' => Ok(Cow::Borrowed(self.bytes)),
315 b'\0' => Ok(Cow::Borrowed(self.bytes)),
314 // Raw revision data follows.
316 // Raw revision data follows.
315 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
317 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
316 // zlib (RFC 1950) data.
318 // zlib (RFC 1950) data.
317 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
319 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
318 // zstd data.
320 // zstd data.
319 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
321 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
320 // A proper new format should have had a repo/store requirement.
322 // A proper new format should have had a repo/store requirement.
321 _format_type => Err(RevlogError::corrupted()),
323 _format_type => Err(RevlogError::corrupted()),
322 }
324 }
323 }
325 }
324
326
325 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
327 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
326 let mut decoder = ZlibDecoder::new(self.bytes);
328 let mut decoder = ZlibDecoder::new(self.bytes);
327 if self.is_delta() {
329 if self.is_delta() {
328 let mut buf = Vec::with_capacity(self.compressed_len);
330 let mut buf = Vec::with_capacity(self.compressed_len);
329 decoder
331 decoder
330 .read_to_end(&mut buf)
332 .read_to_end(&mut buf)
331 .map_err(|_| RevlogError::corrupted())?;
333 .map_err(|_| RevlogError::corrupted())?;
332 Ok(buf)
334 Ok(buf)
333 } else {
335 } else {
334 let mut buf = vec![0; self.uncompressed_len];
336 let mut buf = vec![0; self.uncompressed_len];
335 decoder
337 decoder
336 .read_exact(&mut buf)
338 .read_exact(&mut buf)
337 .map_err(|_| RevlogError::corrupted())?;
339 .map_err(|_| RevlogError::corrupted())?;
338 Ok(buf)
340 Ok(buf)
339 }
341 }
340 }
342 }
341
343
342 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
344 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
343 if self.is_delta() {
345 if self.is_delta() {
344 let mut buf = Vec::with_capacity(self.compressed_len);
346 let mut buf = Vec::with_capacity(self.compressed_len);
345 zstd::stream::copy_decode(self.bytes, &mut buf)
347 zstd::stream::copy_decode(self.bytes, &mut buf)
346 .map_err(|_| RevlogError::corrupted())?;
348 .map_err(|_| RevlogError::corrupted())?;
347 Ok(buf)
349 Ok(buf)
348 } else {
350 } else {
349 let mut buf = vec![0; self.uncompressed_len];
351 let mut buf = vec![0; self.uncompressed_len];
350 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
352 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
351 .map_err(|_| RevlogError::corrupted())?;
353 .map_err(|_| RevlogError::corrupted())?;
352 if len != self.uncompressed_len {
354 if len != self.uncompressed_len {
353 Err(RevlogError::corrupted())
355 Err(RevlogError::corrupted())
354 } else {
356 } else {
355 Ok(buf)
357 Ok(buf)
356 }
358 }
357 }
359 }
358 }
360 }
359
361
360 /// Tell if the entry is a snapshot or a delta
362 /// Tell if the entry is a snapshot or a delta
361 /// (influences on decompression).
363 /// (influences on decompression).
362 fn is_delta(&self) -> bool {
364 fn is_delta(&self) -> bool {
363 self.base_rev.is_some()
365 self.base_rev.is_some()
364 }
366 }
365 }
367 }
366
368
367 /// Format version of the revlog.
369 /// Format version of the revlog.
368 pub fn get_version(index_bytes: &[u8]) -> u16 {
370 pub fn get_version(index_bytes: &[u8]) -> u16 {
369 BigEndian::read_u16(&index_bytes[2..=3])
371 BigEndian::read_u16(&index_bytes[2..=3])
370 }
372 }
371
373
372 /// Calculate the hash of a revision given its data and its parents.
374 /// Calculate the hash of a revision given its data and its parents.
373 fn hash(
375 fn hash(
374 data: &[u8],
376 data: &[u8],
375 p1_hash: &[u8],
377 p1_hash: &[u8],
376 p2_hash: &[u8],
378 p2_hash: &[u8],
377 ) -> [u8; NODE_BYTES_LENGTH] {
379 ) -> [u8; NODE_BYTES_LENGTH] {
378 let mut hasher = Sha1::new();
380 let mut hasher = Sha1::new();
379 let (a, b) = (p1_hash, p2_hash);
381 let (a, b) = (p1_hash, p2_hash);
380 if a > b {
382 if a > b {
381 hasher.update(b);
383 hasher.update(b);
382 hasher.update(a);
384 hasher.update(a);
383 } else {
385 } else {
384 hasher.update(a);
386 hasher.update(a);
385 hasher.update(b);
387 hasher.update(b);
386 }
388 }
387 hasher.update(data);
389 hasher.update(data);
388 *hasher.finalize().as_ref()
390 *hasher.finalize().as_ref()
389 }
391 }
390
392
391 #[cfg(test)]
393 #[cfg(test)]
392 mod tests {
394 mod tests {
393 use super::*;
395 use super::*;
394
396
395 use super::super::index::IndexEntryBuilder;
397 use super::super::index::IndexEntryBuilder;
396
398
397 #[test]
399 #[test]
398 fn version_test() {
400 fn version_test() {
399 let bytes = IndexEntryBuilder::new()
401 let bytes = IndexEntryBuilder::new()
400 .is_first(true)
402 .is_first(true)
401 .with_version(1)
403 .with_version(1)
402 .build();
404 .build();
403
405
404 assert_eq!(get_version(&bytes), 1)
406 assert_eq!(get_version(&bytes), 1)
405 }
407 }
406 }
408 }
@@ -1,281 +1,281 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@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::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::dirstate_tree::dispatch::DirstateMapMethods;
12 use hg::dirstate_tree::dispatch::DirstateMapMethods;
13 use hg::errors::HgError;
13 use hg::errors::HgError;
14 use hg::manifest::Manifest;
14 use hg::manifest::Manifest;
15 use hg::matchers::AlwaysMatcher;
15 use hg::matchers::AlwaysMatcher;
16 use hg::repo::Repo;
16 use hg::repo::Repo;
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
18 use hg::{HgPathCow, StatusOptions};
18 use hg::{HgPathCow, StatusOptions};
19 use log::{info, warn};
19 use log::{info, warn};
20
20
21 pub const HELP_TEXT: &str = "
21 pub const HELP_TEXT: &str = "
22 Show changed files in the working directory
22 Show changed files in the working directory
23
23
24 This is a pure Rust version of `hg status`.
24 This is a pure Rust version of `hg status`.
25
25
26 Some options might be missing, check the list below.
26 Some options might be missing, check the list below.
27 ";
27 ";
28
28
29 pub fn args() -> clap::App<'static, 'static> {
29 pub fn args() -> clap::App<'static, 'static> {
30 SubCommand::with_name("status")
30 SubCommand::with_name("status")
31 .alias("st")
31 .alias("st")
32 .about(HELP_TEXT)
32 .about(HELP_TEXT)
33 .arg(
33 .arg(
34 Arg::with_name("all")
34 Arg::with_name("all")
35 .help("show status of all files")
35 .help("show status of all files")
36 .short("-A")
36 .short("-A")
37 .long("--all"),
37 .long("--all"),
38 )
38 )
39 .arg(
39 .arg(
40 Arg::with_name("modified")
40 Arg::with_name("modified")
41 .help("show only modified files")
41 .help("show only modified files")
42 .short("-m")
42 .short("-m")
43 .long("--modified"),
43 .long("--modified"),
44 )
44 )
45 .arg(
45 .arg(
46 Arg::with_name("added")
46 Arg::with_name("added")
47 .help("show only added files")
47 .help("show only added files")
48 .short("-a")
48 .short("-a")
49 .long("--added"),
49 .long("--added"),
50 )
50 )
51 .arg(
51 .arg(
52 Arg::with_name("removed")
52 Arg::with_name("removed")
53 .help("show only removed files")
53 .help("show only removed files")
54 .short("-r")
54 .short("-r")
55 .long("--removed"),
55 .long("--removed"),
56 )
56 )
57 .arg(
57 .arg(
58 Arg::with_name("clean")
58 Arg::with_name("clean")
59 .help("show only clean files")
59 .help("show only clean files")
60 .short("-c")
60 .short("-c")
61 .long("--clean"),
61 .long("--clean"),
62 )
62 )
63 .arg(
63 .arg(
64 Arg::with_name("deleted")
64 Arg::with_name("deleted")
65 .help("show only deleted files")
65 .help("show only deleted files")
66 .short("-d")
66 .short("-d")
67 .long("--deleted"),
67 .long("--deleted"),
68 )
68 )
69 .arg(
69 .arg(
70 Arg::with_name("unknown")
70 Arg::with_name("unknown")
71 .help("show only unknown (not tracked) files")
71 .help("show only unknown (not tracked) files")
72 .short("-u")
72 .short("-u")
73 .long("--unknown"),
73 .long("--unknown"),
74 )
74 )
75 .arg(
75 .arg(
76 Arg::with_name("ignored")
76 Arg::with_name("ignored")
77 .help("show only ignored files")
77 .help("show only ignored files")
78 .short("-i")
78 .short("-i")
79 .long("--ignored"),
79 .long("--ignored"),
80 )
80 )
81 }
81 }
82
82
83 /// Pure data type allowing the caller to specify file states to display
83 /// Pure data type allowing the caller to specify file states to display
84 #[derive(Copy, Clone, Debug)]
84 #[derive(Copy, Clone, Debug)]
85 pub struct DisplayStates {
85 pub struct DisplayStates {
86 pub modified: bool,
86 pub modified: bool,
87 pub added: bool,
87 pub added: bool,
88 pub removed: bool,
88 pub removed: bool,
89 pub clean: bool,
89 pub clean: bool,
90 pub deleted: bool,
90 pub deleted: bool,
91 pub unknown: bool,
91 pub unknown: bool,
92 pub ignored: bool,
92 pub ignored: bool,
93 }
93 }
94
94
95 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
95 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
96 modified: true,
96 modified: true,
97 added: true,
97 added: true,
98 removed: true,
98 removed: true,
99 clean: false,
99 clean: false,
100 deleted: true,
100 deleted: true,
101 unknown: true,
101 unknown: true,
102 ignored: false,
102 ignored: false,
103 };
103 };
104
104
105 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
105 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
106 modified: true,
106 modified: true,
107 added: true,
107 added: true,
108 removed: true,
108 removed: true,
109 clean: true,
109 clean: true,
110 deleted: true,
110 deleted: true,
111 unknown: true,
111 unknown: true,
112 ignored: true,
112 ignored: true,
113 };
113 };
114
114
115 impl DisplayStates {
115 impl DisplayStates {
116 pub fn is_empty(&self) -> bool {
116 pub fn is_empty(&self) -> bool {
117 !(self.modified
117 !(self.modified
118 || self.added
118 || self.added
119 || self.removed
119 || self.removed
120 || self.clean
120 || self.clean
121 || self.deleted
121 || self.deleted
122 || self.unknown
122 || self.unknown
123 || self.ignored)
123 || self.ignored)
124 }
124 }
125 }
125 }
126
126
127 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
127 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
128 let status_enabled_default = false;
128 let status_enabled_default = false;
129 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
129 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
130 if !status_enabled.unwrap_or(status_enabled_default) {
130 if !status_enabled.unwrap_or(status_enabled_default) {
131 return Err(CommandError::unsupported(
131 return Err(CommandError::unsupported(
132 "status is experimental in rhg (enable it with 'rhg.status = true' \
132 "status is experimental in rhg (enable it with 'rhg.status = true' \
133 or enable fallback with 'rhg.on-unsupported = fallback')"
133 or enable fallback with 'rhg.on-unsupported = fallback')"
134 ));
134 ));
135 }
135 }
136
136
137 let ui = invocation.ui;
137 let ui = invocation.ui;
138 let args = invocation.subcommand_args;
138 let args = invocation.subcommand_args;
139 let display_states = if args.is_present("all") {
139 let display_states = if args.is_present("all") {
140 // TODO when implementing `--quiet`: it excludes clean files
140 // TODO when implementing `--quiet`: it excludes clean files
141 // from `--all`
141 // from `--all`
142 ALL_DISPLAY_STATES
142 ALL_DISPLAY_STATES
143 } else {
143 } else {
144 let requested = DisplayStates {
144 let requested = DisplayStates {
145 modified: args.is_present("modified"),
145 modified: args.is_present("modified"),
146 added: args.is_present("added"),
146 added: args.is_present("added"),
147 removed: args.is_present("removed"),
147 removed: args.is_present("removed"),
148 clean: args.is_present("clean"),
148 clean: args.is_present("clean"),
149 deleted: args.is_present("deleted"),
149 deleted: args.is_present("deleted"),
150 unknown: args.is_present("unknown"),
150 unknown: args.is_present("unknown"),
151 ignored: args.is_present("ignored"),
151 ignored: args.is_present("ignored"),
152 };
152 };
153 if requested.is_empty() {
153 if requested.is_empty() {
154 DEFAULT_DISPLAY_STATES
154 DEFAULT_DISPLAY_STATES
155 } else {
155 } else {
156 requested
156 requested
157 }
157 }
158 };
158 };
159
159
160 let repo = invocation.repo?;
160 let repo = invocation.repo?;
161 let mut dmap = repo.dirstate_map_mut()?;
161 let mut dmap = repo.dirstate_map_mut()?;
162
162
163 let options = StatusOptions {
163 let options = StatusOptions {
164 // TODO should be provided by the dirstate parsing and
164 // TODO should be provided by the dirstate parsing and
165 // hence be stored on dmap. Using a value that assumes we aren't
165 // hence be stored on dmap. Using a value that assumes we aren't
166 // below the time resolution granularity of the FS and the
166 // below the time resolution granularity of the FS and the
167 // dirstate.
167 // dirstate.
168 last_normal_time: 0,
168 last_normal_time: 0,
169 // we're currently supporting file systems with exec flags only
169 // we're currently supporting file systems with exec flags only
170 // anyway
170 // anyway
171 check_exec: true,
171 check_exec: true,
172 list_clean: display_states.clean,
172 list_clean: display_states.clean,
173 list_unknown: display_states.unknown,
173 list_unknown: display_states.unknown,
174 list_ignored: display_states.ignored,
174 list_ignored: display_states.ignored,
175 collect_traversed_dirs: false,
175 collect_traversed_dirs: false,
176 };
176 };
177 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
177 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
178 let (mut ds_status, pattern_warnings) = dmap.status(
178 let (mut ds_status, pattern_warnings) = dmap.status(
179 &AlwaysMatcher,
179 &AlwaysMatcher,
180 repo.working_directory_path().to_owned(),
180 repo.working_directory_path().to_owned(),
181 vec![ignore_file],
181 vec![ignore_file],
182 options,
182 options,
183 )?;
183 )?;
184 if !pattern_warnings.is_empty() {
184 if !pattern_warnings.is_empty() {
185 warn!("Pattern warnings: {:?}", &pattern_warnings);
185 warn!("Pattern warnings: {:?}", &pattern_warnings);
186 }
186 }
187
187
188 if !ds_status.bad.is_empty() {
188 if !ds_status.bad.is_empty() {
189 warn!("Bad matches {:?}", &(ds_status.bad))
189 warn!("Bad matches {:?}", &(ds_status.bad))
190 }
190 }
191 if !ds_status.unsure.is_empty() {
191 if !ds_status.unsure.is_empty() {
192 info!(
192 info!(
193 "Files to be rechecked by retrieval from filelog: {:?}",
193 "Files to be rechecked by retrieval from filelog: {:?}",
194 &ds_status.unsure
194 &ds_status.unsure
195 );
195 );
196 }
196 }
197 if !ds_status.unsure.is_empty()
197 if !ds_status.unsure.is_empty()
198 && (display_states.modified || display_states.clean)
198 && (display_states.modified || display_states.clean)
199 {
199 {
200 let p1 = repo.dirstate_parents()?.p1;
200 let p1 = repo.dirstate_parents()?.p1;
201 let manifest = repo.manifest_for_node(p1).map_err(|e| {
201 let manifest = repo.manifest_for_node(p1).map_err(|e| {
202 CommandError::from((e, &*format!("{:x}", p1.short())))
202 CommandError::from((e, &*format!("{:x}", p1.short())))
203 })?;
203 })?;
204 for to_check in ds_status.unsure {
204 for to_check in ds_status.unsure {
205 if cat_file_is_modified(repo, &manifest, &to_check)? {
205 if cat_file_is_modified(repo, &manifest, &to_check)? {
206 if display_states.modified {
206 if display_states.modified {
207 ds_status.modified.push(to_check);
207 ds_status.modified.push(to_check);
208 }
208 }
209 } else {
209 } else {
210 if display_states.clean {
210 if display_states.clean {
211 ds_status.clean.push(to_check);
211 ds_status.clean.push(to_check);
212 }
212 }
213 }
213 }
214 }
214 }
215 }
215 }
216 if display_states.modified {
216 if display_states.modified {
217 display_status_paths(ui, &mut ds_status.modified, b"M")?;
217 display_status_paths(ui, &mut ds_status.modified, b"M")?;
218 }
218 }
219 if display_states.added {
219 if display_states.added {
220 display_status_paths(ui, &mut ds_status.added, b"A")?;
220 display_status_paths(ui, &mut ds_status.added, b"A")?;
221 }
221 }
222 if display_states.removed {
222 if display_states.removed {
223 display_status_paths(ui, &mut ds_status.removed, b"R")?;
223 display_status_paths(ui, &mut ds_status.removed, b"R")?;
224 }
224 }
225 if display_states.deleted {
225 if display_states.deleted {
226 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
226 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
227 }
227 }
228 if display_states.unknown {
228 if display_states.unknown {
229 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
229 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
230 }
230 }
231 if display_states.ignored {
231 if display_states.ignored {
232 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
232 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
233 }
233 }
234 if display_states.clean {
234 if display_states.clean {
235 display_status_paths(ui, &mut ds_status.clean, b"C")?;
235 display_status_paths(ui, &mut ds_status.clean, b"C")?;
236 }
236 }
237 Ok(())
237 Ok(())
238 }
238 }
239
239
240 // Probably more elegant to use a Deref or Borrow trait rather than
240 // Probably more elegant to use a Deref or Borrow trait rather than
241 // harcode HgPathBuf, but probably not really useful at this point
241 // harcode HgPathBuf, but probably not really useful at this point
242 fn display_status_paths(
242 fn display_status_paths(
243 ui: &Ui,
243 ui: &Ui,
244 paths: &mut [HgPathCow],
244 paths: &mut [HgPathCow],
245 status_prefix: &[u8],
245 status_prefix: &[u8],
246 ) -> Result<(), CommandError> {
246 ) -> Result<(), CommandError> {
247 paths.sort_unstable();
247 paths.sort_unstable();
248 for path in paths {
248 for path in paths {
249 // Same TODO as in commands::root
249 // Same TODO as in commands::root
250 let bytes: &[u8] = path.as_bytes();
250 let bytes: &[u8] = path.as_bytes();
251 // TODO optim, probably lots of unneeded copies here, especially
251 // TODO optim, probably lots of unneeded copies here, especially
252 // if out stream is buffered
252 // if out stream is buffered
253 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
253 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
254 }
254 }
255 Ok(())
255 Ok(())
256 }
256 }
257
257
258 /// Check if a file is modified by comparing actual repo store and file system.
258 /// Check if a file is modified by comparing actual repo store and file system.
259 ///
259 ///
260 /// This meant to be used for those that the dirstate cannot resolve, due
260 /// This meant to be used for those that the dirstate cannot resolve, due
261 /// to time resolution limits.
261 /// to time resolution limits.
262 ///
262 ///
263 /// TODO: detect permission bits and similar metadata modifications
263 /// TODO: detect permission bits and similar metadata modifications
264 fn cat_file_is_modified(
264 fn cat_file_is_modified(
265 repo: &Repo,
265 repo: &Repo,
266 manifest: &Manifest,
266 manifest: &Manifest,
267 hg_path: &HgPath,
267 hg_path: &HgPath,
268 ) -> Result<bool, HgError> {
268 ) -> Result<bool, HgError> {
269 let file_node = manifest
269 let file_node = manifest
270 .find_file(hg_path)?
270 .find_file(hg_path)?
271 .expect("ambgious file not in p1");
271 .expect("ambgious file not in p1");
272 let filelog = repo.filelog(hg_path)?;
272 let filelog = repo.filelog(hg_path)?;
273 let filelog_entry = filelog.get_node(file_node).map_err(|_| {
273 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
274 HgError::corrupted("filelog missing node from manifest")
274 HgError::corrupted("filelog missing node from manifest")
275 })?;
275 })?;
276 let contents_in_p1 = filelog_entry.data()?;
276 let contents_in_p1 = filelog_entry.data()?;
277
277
278 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
278 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
279 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
279 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
280 return Ok(contents_in_p1 == &*fs_contents);
280 return Ok(contents_in_p1 == &*fs_contents);
281 }
281 }
General Comments 0
You need to be logged in to leave comments. Login now