##// END OF EJS Templates
rust-clippy: merge "revlog" module definition and struct implementation...
Raphaël Gomès -
r50832:75040950 default
parent child Browse files
Show More
@@ -1,141 +1,141 b''
1 1 use super::*;
2 2
3 3 /// Unit tests for:
4 4 ///
5 5 /// ```ignore
6 6 /// fn compare_value(
7 7 /// current_merge: Revision,
8 8 /// merge_case_for_dest: impl Fn() -> MergeCase,
9 9 /// src_minor: &CopySource,
10 10 /// src_major: &CopySource,
11 11 /// ) -> (MergePick, /* overwrite: */ bool)
12 12 /// ```
13 13 #[test]
14 14 fn test_compare_value() {
15 15 // The `compare_value!` macro calls the `compare_value` function with
16 16 // arguments given in pseudo-syntax:
17 17 //
18 18 // * For `merge_case_for_dest` it takes a plain `MergeCase` value instead
19 19 // of a closure.
20 20 // * `CopySource` values are represented as `(rev, path, overwritten)`
21 21 // tuples of type `(Revision, Option<PathToken>, OrdSet<Revision>)`.
22 22 // * `PathToken` is an integer not read by `compare_value`. It only checks
23 23 // for `Some(_)` indicating a file copy v.s. `None` for a file deletion.
24 24 // * `OrdSet<Revision>` is represented as a Python-like set literal.
25 25
26 26 use MergeCase::*;
27 27 use MergePick::*;
28 28
29 29 assert_eq!(
30 30 compare_value!(1, Normal, (1, None, { 1 }), (1, None, { 1 })),
31 31 (Any, false)
32 32 );
33 33 }
34 34
35 35 /// Unit tests for:
36 36 ///
37 37 /// ```ignore
38 38 /// fn merge_copies_dict(
39 39 /// path_map: &TwoWayPathMap, // Not visible in test cases
40 40 /// current_merge: Revision,
41 41 /// minor: InternalPathCopies,
42 42 /// major: InternalPathCopies,
43 43 /// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
44 44 /// ) -> InternalPathCopies
45 45 /// ```
46 46 #[test]
47 47 fn test_merge_copies_dict() {
48 48 // The `merge_copies_dict!` macro calls the `merge_copies_dict` function
49 49 // with arguments given in pseudo-syntax:
50 50 //
51 51 // * `TwoWayPathMap` and path tokenization are implicitly taken care of.
52 52 // All paths are given as string literals.
53 53 // * Key-value maps are represented with `{key1 => value1, key2 => value2}`
54 54 // pseudo-syntax.
55 55 // * `InternalPathCopies` is a map of copy destination path keys to
56 56 // `CopySource` values.
57 57 // - `CopySource` is represented as a `(rev, source_path, overwritten)`
58 58 // tuple of type `(Revision, Option<Path>, OrdSet<Revision>)`.
59 59 // - Unlike in `test_compare_value`, source paths are string literals.
60 60 // - `OrdSet<Revision>` is again represented as a Python-like set
61 61 // literal.
62 62 // * `get_merge_case` is represented as a map of copy destination path to
63 63 // `MergeCase`. The default for paths not in the map is
64 64 // `MergeCase::Normal`.
65 65 //
66 66 // `internal_path_copies!` creates an `InternalPathCopies` value with the
67 67 // same pseudo-syntax as in `merge_copies_dict!`.
68 68
69 69 use MergeCase::*;
70 70
71 71 assert_eq!(
72 72 merge_copies_dict!(
73 73 1,
74 74 {"foo" => (1, None, {})},
75 75 {},
76 76 {"foo" => Merged}
77 77 ),
78 78 internal_path_copies!("foo" => (1, None, {}))
79 79 );
80 80 }
81 81
82 82 /// Unit tests for:
83 83 ///
84 84 /// ```ignore
85 85 /// impl CombineChangesetCopies {
86 86 /// fn new(children_count: HashMap<Revision, usize>) -> Self
87 87 ///
88 88 /// // Called repeatedly:
89 89 /// fn add_revision_inner<'a>(
90 90 /// &mut self,
91 91 /// rev: Revision,
92 92 /// p1: Revision,
93 93 /// p2: Revision,
94 94 /// copy_actions: impl Iterator<Item = Action<'a>>,
95 95 /// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
96 96 /// )
97 97 ///
98 98 /// fn finish(mut self, target_rev: Revision) -> PathCopies
99 99 /// }
100 100 /// ```
101 101 #[test]
102 102 fn test_combine_changeset_copies() {
103 103 // `combine_changeset_copies!` creates a `CombineChangesetCopies` with
104 104 // `new`, then calls `add_revision_inner` repeatedly, then calls `finish`
105 105 // for its return value.
106 106 //
107 107 // All paths given as string literals.
108 108 //
109 109 // * Key-value maps are represented with `{key1 => value1, key2 => value2}`
110 110 // pseudo-syntax.
111 111 // * `children_count` is a map of revision numbers to count of children in
112 112 // the DAG. It includes all revisions that should be considered by the
113 113 // algorithm.
114 114 // * Calls to `add_revision_inner` are represented as an array of anonymous
115 115 // structs with named fields, one pseudo-struct per call.
116 116 //
117 117 // `path_copies!` creates a `PathCopies` value, a map of copy destination
118 118 // keys to copy source values. Note: the arrows for map literal syntax
119 119 // point **backwards** compared to the logical direction of copy!
120 120
121 use crate::NULL_REVISION as NULL;
121 use crate::revlog::NULL_REVISION as NULL;
122 122 use Action::*;
123 123 use MergeCase::*;
124 124
125 125 assert_eq!(
126 126 combine_changeset_copies!(
127 127 { 1 => 1, 2 => 1 },
128 128 [
129 129 { rev: 1, p1: NULL, p2: NULL, actions: [], merge_cases: {}, },
130 130 { rev: 2, p1: NULL, p2: NULL, actions: [], merge_cases: {}, },
131 131 {
132 132 rev: 3, p1: 1, p2: 2,
133 133 actions: [CopiedFromP1("destination.txt", "source.txt")],
134 134 merge_cases: {"destination.txt" => Merged},
135 135 },
136 136 ],
137 137 3,
138 138 ),
139 139 path_copies!("destination.txt" => "source.txt")
140 140 );
141 141 }
@@ -1,115 +1,115 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::repo::Repo;
9 use crate::revlog::revlog::RevlogError;
10 9 use crate::revlog::Node;
10 use crate::revlog::RevlogError;
11 11
12 12 use crate::utils::hg_path::HgPath;
13 13
14 14 use crate::errors::HgError;
15 15 use crate::manifest::Manifest;
16 16 use crate::manifest::ManifestEntry;
17 17 use itertools::put_back;
18 18 use itertools::PutBack;
19 19 use std::cmp::Ordering;
20 20
21 21 pub struct CatOutput<'a> {
22 22 /// Whether any file in the manifest matched the paths given as CLI
23 23 /// arguments
24 24 pub found_any: bool,
25 25 /// The contents of matching files, in manifest order
26 26 pub results: Vec<(&'a HgPath, Vec<u8>)>,
27 27 /// Which of the CLI arguments did not match any manifest file
28 28 pub missing: Vec<&'a HgPath>,
29 29 /// The node ID that the given revset was resolved to
30 30 pub node: Node,
31 31 }
32 32
33 33 // Find an item in an iterator over a sorted collection.
34 34 fn find_item<'a>(
35 35 i: &mut PutBack<impl Iterator<Item = Result<ManifestEntry<'a>, HgError>>>,
36 36 needle: &HgPath,
37 37 ) -> Result<Option<Node>, HgError> {
38 38 loop {
39 39 match i.next() {
40 40 None => return Ok(None),
41 41 Some(result) => {
42 42 let entry = result?;
43 43 match needle.as_bytes().cmp(entry.path.as_bytes()) {
44 44 Ordering::Less => {
45 45 i.put_back(Ok(entry));
46 46 return Ok(None);
47 47 }
48 48 Ordering::Greater => continue,
49 49 Ordering::Equal => return Ok(Some(entry.node_id()?)),
50 50 }
51 51 }
52 52 }
53 53 }
54 54 }
55 55
56 56 // Tuple of (missing, found) paths in the manifest
57 57 type ManifestQueryResponse<'a> = (Vec<(&'a HgPath, Node)>, Vec<&'a HgPath>);
58 58
59 59 fn find_files_in_manifest<'query>(
60 60 manifest: &Manifest,
61 61 query: impl Iterator<Item = &'query HgPath>,
62 62 ) -> Result<ManifestQueryResponse<'query>, HgError> {
63 63 let mut manifest = put_back(manifest.iter());
64 64 let mut res = vec![];
65 65 let mut missing = vec![];
66 66
67 67 for file in query {
68 68 match find_item(&mut manifest, file)? {
69 69 None => missing.push(file),
70 70 Some(item) => res.push((file, item)),
71 71 }
72 72 }
73 73 Ok((res, missing))
74 74 }
75 75
76 76 /// Output the given revision of files
77 77 ///
78 78 /// * `root`: Repository root
79 79 /// * `rev`: The revision to cat the files from.
80 80 /// * `files`: The files to output.
81 81 pub fn cat<'a>(
82 82 repo: &Repo,
83 83 revset: &str,
84 84 mut files: Vec<&'a HgPath>,
85 85 ) -> Result<CatOutput<'a>, RevlogError> {
86 86 let rev = crate::revset::resolve_single(revset, repo)?;
87 87 let manifest = repo.manifest_for_rev(rev)?;
88 88 let node = *repo
89 89 .changelog()?
90 90 .node_from_rev(rev)
91 91 .expect("should succeed when repo.manifest did");
92 92 let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![];
93 93 let mut found_any = false;
94 94
95 95 files.sort_unstable();
96 96
97 97 let (found, missing) =
98 98 find_files_in_manifest(&manifest, files.into_iter())?;
99 99
100 100 for (file_path, file_node) in found {
101 101 found_any = true;
102 102 let file_log = repo.filelog(file_path)?;
103 103 results.push((
104 104 file_path,
105 105 file_log.data_for_node(file_node)?.into_file_data()?,
106 106 ));
107 107 }
108 108
109 109 Ok(CatOutput {
110 110 found_any,
111 111 results,
112 112 missing,
113 113 node,
114 114 })
115 115 }
@@ -1,38 +1,38 b''
1 1 // debugdata.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::repo::Repo;
9 9 use crate::requirements;
10 use crate::revlog::revlog::{Revlog, RevlogError};
10 use crate::revlog::{Revlog, RevlogError};
11 11
12 12 /// Kind of data to debug
13 13 #[derive(Debug, Copy, Clone)]
14 14 pub enum DebugDataKind {
15 15 Changelog,
16 16 Manifest,
17 17 }
18 18
19 19 /// Dump the contents data of a revision.
20 20 pub fn debug_data(
21 21 repo: &Repo,
22 22 revset: &str,
23 23 kind: DebugDataKind,
24 24 ) -> Result<Vec<u8>, RevlogError> {
25 25 let index_file = match kind {
26 26 DebugDataKind::Changelog => "00changelog.i",
27 27 DebugDataKind::Manifest => "00manifest.i",
28 28 };
29 29 let use_nodemap = repo
30 30 .requirements()
31 31 .contains(requirements::NODEMAP_REQUIREMENT);
32 32 let revlog =
33 33 Revlog::open(&repo.store_vfs(), index_file, None, use_nodemap)?;
34 34 let rev =
35 35 crate::revset::resolve_rev_number_or_hex_prefix(revset, &revlog)?;
36 36 let data = revlog.get_rev_data(rev)?;
37 37 Ok(data.into_owned())
38 38 }
@@ -1,82 +1,82 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate_entries;
9 9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 10 use crate::errors::HgError;
11 11 use crate::repo::Repo;
12 12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::RevlogError;
14 14 use crate::utils::hg_path::HgPath;
15 15 use crate::DirstateError;
16 16 use rayon::prelude::*;
17 17
18 18 /// List files under Mercurial control in the working directory
19 19 /// by reading the dirstate
20 20 pub struct Dirstate {
21 21 /// The `dirstate` content.
22 22 content: Vec<u8>,
23 23 v2_metadata: Option<Vec<u8>>,
24 24 }
25 25
26 26 impl Dirstate {
27 27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
28 28 let mut content = repo.hg_vfs().read("dirstate")?;
29 29 let v2_metadata = if repo.has_dirstate_v2() {
30 30 let docket = read_docket(&content)?;
31 31 let meta = docket.tree_metadata().to_vec();
32 32 content = repo.hg_vfs().read(docket.data_filename())?;
33 33 Some(meta)
34 34 } else {
35 35 None
36 36 };
37 37 Ok(Self {
38 38 content,
39 39 v2_metadata,
40 40 })
41 41 }
42 42
43 43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
44 44 let mut files = Vec::new();
45 45 if !self.content.is_empty() {
46 46 if let Some(meta) = &self.v2_metadata {
47 47 for_each_tracked_path(&self.content, meta, |path| {
48 48 files.push(path)
49 49 })?
50 50 } else {
51 51 let _parents = parse_dirstate_entries(
52 52 &self.content,
53 53 |path, entry, _copy_source| {
54 54 if entry.tracked() {
55 55 files.push(path)
56 56 }
57 57 Ok(())
58 58 },
59 59 )?;
60 60 }
61 61 }
62 62 files.par_sort_unstable();
63 63 Ok(files)
64 64 }
65 65 }
66 66
67 67 /// List files under Mercurial control at a given revision.
68 68 pub fn list_rev_tracked_files(
69 69 repo: &Repo,
70 70 revset: &str,
71 71 ) -> Result<FilesForRev, RevlogError> {
72 72 let rev = crate::revset::resolve_single(revset, repo)?;
73 73 Ok(FilesForRev(repo.manifest_for_rev(rev)?))
74 74 }
75 75
76 76 pub struct FilesForRev(Manifest);
77 77
78 78 impl FilesForRev {
79 79 pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
80 80 self.0.iter().map(|entry| Ok(entry?.path))
81 81 }
82 82 }
@@ -1,563 +1,563 b''
1 1 use crate::changelog::Changelog;
2 2 use crate::config::{Config, ConfigError, ConfigParseError};
3 3 use crate::dirstate::DirstateParents;
4 4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 6 use crate::errors::HgResultExt;
7 7 use crate::errors::{HgError, IoResultExt};
8 8 use crate::lock::{try_with_lock_no_wait, LockError};
9 9 use crate::manifest::{Manifest, Manifestlog};
10 10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::RevlogError;
12 12 use crate::utils::files::get_path_from_bytes;
13 13 use crate::utils::hg_path::HgPath;
14 14 use crate::utils::SliceExt;
15 15 use crate::vfs::{is_dir, is_file, Vfs};
16 16 use crate::{requirements, NodePrefix};
17 17 use crate::{DirstateError, Revision};
18 18 use std::cell::{Ref, RefCell, RefMut};
19 19 use std::collections::HashSet;
20 20 use std::io::Seek;
21 21 use std::io::SeekFrom;
22 22 use std::io::Write as IoWrite;
23 23 use std::path::{Path, PathBuf};
24 24
25 25 /// A repository on disk
26 26 pub struct Repo {
27 27 working_directory: PathBuf,
28 28 dot_hg: PathBuf,
29 29 store: PathBuf,
30 30 requirements: HashSet<String>,
31 31 config: Config,
32 32 dirstate_parents: LazyCell<DirstateParents>,
33 33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
34 34 dirstate_map: LazyCell<OwningDirstateMap>,
35 35 changelog: LazyCell<Changelog>,
36 36 manifestlog: LazyCell<Manifestlog>,
37 37 }
38 38
39 39 #[derive(Debug, derive_more::From)]
40 40 pub enum RepoError {
41 41 NotFound {
42 42 at: PathBuf,
43 43 },
44 44 #[from]
45 45 ConfigParseError(ConfigParseError),
46 46 #[from]
47 47 Other(HgError),
48 48 }
49 49
50 50 impl From<ConfigError> for RepoError {
51 51 fn from(error: ConfigError) -> Self {
52 52 match error {
53 53 ConfigError::Parse(error) => error.into(),
54 54 ConfigError::Other(error) => error.into(),
55 55 }
56 56 }
57 57 }
58 58
59 59 impl Repo {
60 60 /// tries to find nearest repository root in current working directory or
61 61 /// its ancestors
62 62 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
63 63 let current_directory = crate::utils::current_dir()?;
64 64 // ancestors() is inclusive: it first yields `current_directory`
65 65 // as-is.
66 66 for ancestor in current_directory.ancestors() {
67 67 if is_dir(ancestor.join(".hg"))? {
68 68 return Ok(ancestor.to_path_buf());
69 69 }
70 70 }
71 71 Err(RepoError::NotFound {
72 72 at: current_directory,
73 73 })
74 74 }
75 75
76 76 /// Find a repository, either at the given path (which must contain a `.hg`
77 77 /// sub-directory) or by searching the current directory and its
78 78 /// ancestors.
79 79 ///
80 80 /// A method with two very different "modes" like this usually a code smell
81 81 /// to make two methods instead, but in this case an `Option` is what rhg
82 82 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
83 83 /// Having two methods would just move that `if` to almost all callers.
84 84 pub fn find(
85 85 config: &Config,
86 86 explicit_path: Option<PathBuf>,
87 87 ) -> Result<Self, RepoError> {
88 88 if let Some(root) = explicit_path {
89 89 if is_dir(root.join(".hg"))? {
90 90 Self::new_at_path(root, config)
91 91 } else if is_file(&root)? {
92 92 Err(HgError::unsupported("bundle repository").into())
93 93 } else {
94 94 Err(RepoError::NotFound { at: root })
95 95 }
96 96 } else {
97 97 let root = Self::find_repo_root()?;
98 98 Self::new_at_path(root, config)
99 99 }
100 100 }
101 101
102 102 /// To be called after checking that `.hg` is a sub-directory
103 103 fn new_at_path(
104 104 working_directory: PathBuf,
105 105 config: &Config,
106 106 ) -> Result<Self, RepoError> {
107 107 let dot_hg = working_directory.join(".hg");
108 108
109 109 let mut repo_config_files =
110 110 vec![dot_hg.join("hgrc"), dot_hg.join("hgrc-not-shared")];
111 111
112 112 let hg_vfs = Vfs { base: &dot_hg };
113 113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 114 let relative =
115 115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 116 let shared =
117 117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118 118
119 119 // From `mercurial/localrepo.py`:
120 120 //
121 121 // if .hg/requires contains the sharesafe requirement, it means
122 122 // there exists a `.hg/store/requires` too and we should read it
123 123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 125 // is not present, refer checkrequirementscompat() for that
126 126 //
127 127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 128 // repository was shared the old way. We check the share source
129 129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 130 // current repository needs to be reshared
131 131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132 132
133 133 let store_path;
134 134 if !shared {
135 135 store_path = dot_hg.join("store");
136 136 } else {
137 137 let bytes = hg_vfs.read("sharedpath")?;
138 138 let mut shared_path =
139 139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 140 .to_owned();
141 141 if relative {
142 142 shared_path = dot_hg.join(shared_path)
143 143 }
144 144 if !is_dir(&shared_path)? {
145 145 return Err(HgError::corrupted(format!(
146 146 ".hg/sharedpath points to nonexistent directory {}",
147 147 shared_path.display()
148 148 ))
149 149 .into());
150 150 }
151 151
152 152 store_path = shared_path.join("store");
153 153
154 154 let source_is_share_safe =
155 155 requirements::load(Vfs { base: &shared_path })?
156 156 .contains(requirements::SHARESAFE_REQUIREMENT);
157 157
158 158 if share_safe != source_is_share_safe {
159 159 return Err(HgError::unsupported("share-safe mismatch").into());
160 160 }
161 161
162 162 if share_safe {
163 163 repo_config_files.insert(0, shared_path.join("hgrc"))
164 164 }
165 165 }
166 166 if share_safe {
167 167 reqs.extend(requirements::load(Vfs { base: &store_path })?);
168 168 }
169 169
170 170 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
171 171 config.combine_with_repo(&repo_config_files)?
172 172 } else {
173 173 config.clone()
174 174 };
175 175
176 176 let repo = Self {
177 177 requirements: reqs,
178 178 working_directory,
179 179 store: store_path,
180 180 dot_hg,
181 181 config: repo_config,
182 182 dirstate_parents: LazyCell::new(),
183 183 dirstate_data_file_uuid: LazyCell::new(),
184 184 dirstate_map: LazyCell::new(),
185 185 changelog: LazyCell::new(),
186 186 manifestlog: LazyCell::new(),
187 187 };
188 188
189 189 requirements::check(&repo)?;
190 190
191 191 Ok(repo)
192 192 }
193 193
194 194 pub fn working_directory_path(&self) -> &Path {
195 195 &self.working_directory
196 196 }
197 197
198 198 pub fn requirements(&self) -> &HashSet<String> {
199 199 &self.requirements
200 200 }
201 201
202 202 pub fn config(&self) -> &Config {
203 203 &self.config
204 204 }
205 205
206 206 /// For accessing repository files (in `.hg`), except for the store
207 207 /// (`.hg/store`).
208 208 pub fn hg_vfs(&self) -> Vfs<'_> {
209 209 Vfs { base: &self.dot_hg }
210 210 }
211 211
212 212 /// For accessing repository store files (in `.hg/store`)
213 213 pub fn store_vfs(&self) -> Vfs<'_> {
214 214 Vfs { base: &self.store }
215 215 }
216 216
217 217 /// For accessing the working copy
218 218 pub fn working_directory_vfs(&self) -> Vfs<'_> {
219 219 Vfs {
220 220 base: &self.working_directory,
221 221 }
222 222 }
223 223
224 224 pub fn try_with_wlock_no_wait<R>(
225 225 &self,
226 226 f: impl FnOnce() -> R,
227 227 ) -> Result<R, LockError> {
228 228 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
229 229 }
230 230
231 231 pub fn has_dirstate_v2(&self) -> bool {
232 232 self.requirements
233 233 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
234 234 }
235 235
236 236 pub fn has_sparse(&self) -> bool {
237 237 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
238 238 }
239 239
240 240 pub fn has_narrow(&self) -> bool {
241 241 self.requirements.contains(requirements::NARROW_REQUIREMENT)
242 242 }
243 243
244 244 pub fn has_nodemap(&self) -> bool {
245 245 self.requirements
246 246 .contains(requirements::NODEMAP_REQUIREMENT)
247 247 }
248 248
249 249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
250 250 Ok(self
251 251 .hg_vfs()
252 252 .read("dirstate")
253 253 .io_not_found_as_none()?
254 254 .unwrap_or_default())
255 255 }
256 256
257 257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
258 258 Ok(*self
259 259 .dirstate_parents
260 260 .get_or_init(|| self.read_dirstate_parents())?)
261 261 }
262 262
263 263 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
264 264 let dirstate = self.dirstate_file_contents()?;
265 265 let parents = if dirstate.is_empty() {
266 266 if self.has_dirstate_v2() {
267 267 self.dirstate_data_file_uuid.set(None);
268 268 }
269 269 DirstateParents::NULL
270 270 } else if self.has_dirstate_v2() {
271 271 let docket =
272 272 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
273 273 self.dirstate_data_file_uuid
274 274 .set(Some(docket.uuid.to_owned()));
275 275 docket.parents()
276 276 } else {
277 277 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
278 278 };
279 279 self.dirstate_parents.set(parents);
280 280 Ok(parents)
281 281 }
282 282
283 283 fn read_dirstate_data_file_uuid(
284 284 &self,
285 285 ) -> Result<Option<Vec<u8>>, HgError> {
286 286 assert!(
287 287 self.has_dirstate_v2(),
288 288 "accessing dirstate data file ID without dirstate-v2"
289 289 );
290 290 let dirstate = self.dirstate_file_contents()?;
291 291 if dirstate.is_empty() {
292 292 self.dirstate_parents.set(DirstateParents::NULL);
293 293 Ok(None)
294 294 } else {
295 295 let docket =
296 296 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
297 297 self.dirstate_parents.set(docket.parents());
298 298 Ok(Some(docket.uuid.to_owned()))
299 299 }
300 300 }
301 301
302 302 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
303 303 let dirstate_file_contents = self.dirstate_file_contents()?;
304 304 if dirstate_file_contents.is_empty() {
305 305 self.dirstate_parents.set(DirstateParents::NULL);
306 306 if self.has_dirstate_v2() {
307 307 self.dirstate_data_file_uuid.set(None);
308 308 }
309 309 Ok(OwningDirstateMap::new_empty(Vec::new()))
310 310 } else if self.has_dirstate_v2() {
311 311 let docket = crate::dirstate_tree::on_disk::read_docket(
312 312 &dirstate_file_contents,
313 313 )?;
314 314 self.dirstate_parents.set(docket.parents());
315 315 self.dirstate_data_file_uuid
316 316 .set(Some(docket.uuid.to_owned()));
317 317 let data_size = docket.data_size();
318 318 let metadata = docket.tree_metadata();
319 319 if let Some(data_mmap) = self
320 320 .hg_vfs()
321 321 .mmap_open(docket.data_filename())
322 322 .io_not_found_as_none()?
323 323 {
324 324 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
325 325 } else {
326 326 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
327 327 }
328 328 } else {
329 329 let (map, parents) =
330 330 OwningDirstateMap::new_v1(dirstate_file_contents)?;
331 331 self.dirstate_parents.set(parents);
332 332 Ok(map)
333 333 }
334 334 }
335 335
336 336 pub fn dirstate_map(
337 337 &self,
338 338 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
339 339 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
340 340 }
341 341
342 342 pub fn dirstate_map_mut(
343 343 &self,
344 344 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
345 345 self.dirstate_map
346 346 .get_mut_or_init(|| self.new_dirstate_map())
347 347 }
348 348
349 349 fn new_changelog(&self) -> Result<Changelog, HgError> {
350 350 Changelog::open(&self.store_vfs(), self.has_nodemap())
351 351 }
352 352
353 353 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
354 354 self.changelog.get_or_init(|| self.new_changelog())
355 355 }
356 356
357 357 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
358 358 self.changelog.get_mut_or_init(|| self.new_changelog())
359 359 }
360 360
361 361 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
362 362 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
363 363 }
364 364
365 365 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
366 366 self.manifestlog.get_or_init(|| self.new_manifestlog())
367 367 }
368 368
369 369 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
370 370 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
371 371 }
372 372
373 373 /// Returns the manifest of the *changeset* with the given node ID
374 374 pub fn manifest_for_node(
375 375 &self,
376 376 node: impl Into<NodePrefix>,
377 377 ) -> Result<Manifest, RevlogError> {
378 378 self.manifestlog()?.data_for_node(
379 379 self.changelog()?
380 380 .data_for_node(node.into())?
381 381 .manifest_node()?
382 382 .into(),
383 383 )
384 384 }
385 385
386 386 /// Returns the manifest of the *changeset* with the given revision number
387 387 pub fn manifest_for_rev(
388 388 &self,
389 389 revision: Revision,
390 390 ) -> Result<Manifest, RevlogError> {
391 391 self.manifestlog()?.data_for_node(
392 392 self.changelog()?
393 393 .data_for_rev(revision)?
394 394 .manifest_node()?
395 395 .into(),
396 396 )
397 397 }
398 398
399 399 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
400 400 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
401 401 Ok(entry.tracked())
402 402 } else {
403 403 Ok(false)
404 404 }
405 405 }
406 406
407 407 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
408 408 Filelog::open(self, path)
409 409 }
410 410
411 411 /// Write to disk any updates that were made through `dirstate_map_mut`.
412 412 ///
413 413 /// The "wlock" must be held while calling this.
414 414 /// See for example `try_with_wlock_no_wait`.
415 415 ///
416 416 /// TODO: have a `WritableRepo` type only accessible while holding the
417 417 /// lock?
418 418 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
419 419 let map = self.dirstate_map()?;
420 420 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
421 421 // it’s unset
422 422 let parents = self.dirstate_parents()?;
423 423 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
424 424 let uuid_opt = self
425 425 .dirstate_data_file_uuid
426 426 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
427 427 let uuid_opt = uuid_opt.as_ref();
428 428 let can_append = uuid_opt.is_some();
429 429 let (data, tree_metadata, append, old_data_size) =
430 430 map.pack_v2(can_append)?;
431 431
432 432 // Reuse the uuid, or generate a new one, keeping the old for
433 433 // deletion.
434 434 let (uuid, old_uuid) = match uuid_opt {
435 435 Some(uuid) => {
436 436 let as_str = std::str::from_utf8(uuid)
437 437 .map_err(|_| {
438 438 HgError::corrupted(
439 439 "non-UTF-8 dirstate data file ID",
440 440 )
441 441 })?
442 442 .to_owned();
443 443 if append {
444 444 (as_str, None)
445 445 } else {
446 446 (DirstateDocket::new_uid(), Some(as_str))
447 447 }
448 448 }
449 449 None => (DirstateDocket::new_uid(), None),
450 450 };
451 451
452 452 let data_filename = format!("dirstate.{}", uuid);
453 453 let data_filename = self.hg_vfs().join(data_filename);
454 454 let mut options = std::fs::OpenOptions::new();
455 455 options.write(true);
456 456
457 457 // Why are we not using the O_APPEND flag when appending?
458 458 //
459 459 // - O_APPEND makes it trickier to deal with garbage at the end of
460 460 // the file, left by a previous uncommitted transaction. By
461 461 // starting the write at [old_data_size] we make sure we erase
462 462 // all such garbage.
463 463 //
464 464 // - O_APPEND requires to special-case 0-byte writes, whereas we
465 465 // don't need that.
466 466 //
467 467 // - Some OSes have bugs in implementation O_APPEND:
468 468 // revlog.py talks about a Solaris bug, but we also saw some ZFS
469 469 // bug: https://github.com/openzfs/zfs/pull/3124,
470 470 // https://github.com/openzfs/zfs/issues/13370
471 471 //
472 472 if !append {
473 473 options.create_new(true);
474 474 }
475 475
476 476 let data_size = (|| {
477 477 // TODO: loop and try another random ID if !append and this
478 478 // returns `ErrorKind::AlreadyExists`? Collision chance of two
479 479 // random IDs is one in 2**32
480 480 let mut file = options.open(&data_filename)?;
481 481 if append {
482 482 file.seek(SeekFrom::Start(old_data_size as u64))?;
483 483 }
484 484 file.write_all(&data)?;
485 485 file.flush()?;
486 486 file.seek(SeekFrom::Current(0))
487 487 })()
488 488 .when_writing_file(&data_filename)?;
489 489
490 490 let packed_dirstate = DirstateDocket::serialize(
491 491 parents,
492 492 tree_metadata,
493 493 data_size,
494 494 uuid.as_bytes(),
495 495 )
496 496 .map_err(|_: std::num::TryFromIntError| {
497 497 HgError::corrupted("overflow in dirstate docket serialization")
498 498 })?;
499 499
500 500 (packed_dirstate, old_uuid)
501 501 } else {
502 502 (map.pack_v1(parents)?, None)
503 503 };
504 504
505 505 let vfs = self.hg_vfs();
506 506 vfs.atomic_write("dirstate", &packed_dirstate)?;
507 507 if let Some(uuid) = old_uuid_to_remove {
508 508 // Remove the old data file after the new docket pointing to the
509 509 // new data file was written.
510 510 vfs.remove_file(format!("dirstate.{}", uuid))?;
511 511 }
512 512 Ok(())
513 513 }
514 514 }
515 515
516 516 /// Lazily-initialized component of `Repo` with interior mutability
517 517 ///
518 518 /// This differs from `OnceCell` in that the value can still be "deinitialized"
519 519 /// later by setting its inner `Option` to `None`. It also takes the
520 520 /// initialization function as an argument when the value is requested, not
521 521 /// when the instance is created.
522 522 struct LazyCell<T> {
523 523 value: RefCell<Option<T>>,
524 524 }
525 525
526 526 impl<T> LazyCell<T> {
527 527 fn new() -> Self {
528 528 Self {
529 529 value: RefCell::new(None),
530 530 }
531 531 }
532 532
533 533 fn set(&self, value: T) {
534 534 *self.value.borrow_mut() = Some(value)
535 535 }
536 536
537 537 fn get_or_init<E>(
538 538 &self,
539 539 init: impl Fn() -> Result<T, E>,
540 540 ) -> Result<Ref<T>, E> {
541 541 let mut borrowed = self.value.borrow();
542 542 if borrowed.is_none() {
543 543 drop(borrowed);
544 544 // Only use `borrow_mut` if it is really needed to avoid panic in
545 545 // case there is another outstanding borrow but mutation is not
546 546 // needed.
547 547 *self.value.borrow_mut() = Some(init()?);
548 548 borrowed = self.value.borrow()
549 549 }
550 550 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
551 551 }
552 552
553 553 fn get_mut_or_init<E>(
554 554 &self,
555 555 init: impl Fn() -> Result<T, E>,
556 556 ) -> Result<RefMut<T>, E> {
557 557 let mut borrowed = self.value.borrow_mut();
558 558 if borrowed.is_none() {
559 559 *borrowed = Some(init()?);
560 560 }
561 561 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
562 562 }
563 563 }
@@ -1,271 +1,271 b''
1 1 use crate::errors::HgError;
2 use crate::revlog::revlog::{Revlog, RevlogEntry, RevlogError};
3 2 use crate::revlog::Revision;
4 3 use crate::revlog::{Node, NodePrefix};
4 use crate::revlog::{Revlog, RevlogEntry, RevlogError};
5 5 use crate::utils::hg_path::HgPath;
6 6 use crate::vfs::Vfs;
7 7 use itertools::Itertools;
8 8 use std::ascii::escape_default;
9 9 use std::borrow::Cow;
10 10 use std::fmt::{Debug, Formatter};
11 11
12 12 /// A specialized `Revlog` to work with `changelog` data format.
13 13 pub struct Changelog {
14 14 /// The generic `revlog` format.
15 15 pub(crate) revlog: Revlog,
16 16 }
17 17
18 18 impl Changelog {
19 19 /// Open the `changelog` of a repository given by its root.
20 20 pub fn open(store_vfs: &Vfs, use_nodemap: bool) -> Result<Self, HgError> {
21 21 let revlog =
22 22 Revlog::open(store_vfs, "00changelog.i", None, use_nodemap)?;
23 23 Ok(Self { revlog })
24 24 }
25 25
26 26 /// Return the `ChangelogEntry` for the given node ID.
27 27 pub fn data_for_node(
28 28 &self,
29 29 node: NodePrefix,
30 30 ) -> Result<ChangelogRevisionData, RevlogError> {
31 31 let rev = self.revlog.rev_from_node(node)?;
32 32 self.data_for_rev(rev)
33 33 }
34 34
35 35 /// Return the `RevlogEntry` of the given revision number.
36 36 pub fn entry_for_rev(
37 37 &self,
38 38 rev: Revision,
39 39 ) -> Result<RevlogEntry, RevlogError> {
40 40 self.revlog.get_entry(rev)
41 41 }
42 42
43 43 /// Return the `ChangelogEntry` of the given revision number.
44 44 pub fn data_for_rev(
45 45 &self,
46 46 rev: Revision,
47 47 ) -> Result<ChangelogRevisionData, RevlogError> {
48 48 let bytes = self.revlog.get_rev_data(rev)?;
49 49 if bytes.is_empty() {
50 50 Ok(ChangelogRevisionData::null())
51 51 } else {
52 52 Ok(ChangelogRevisionData::new(bytes).map_err(|err| {
53 53 RevlogError::Other(HgError::CorruptedRepository(format!(
54 54 "Invalid changelog data for revision {}: {:?}",
55 55 rev, err
56 56 )))
57 57 })?)
58 58 }
59 59 }
60 60
61 61 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
62 62 self.revlog.node_from_rev(rev)
63 63 }
64 64
65 65 pub fn rev_from_node(
66 66 &self,
67 67 node: NodePrefix,
68 68 ) -> Result<Revision, RevlogError> {
69 69 self.revlog.rev_from_node(node)
70 70 }
71 71 }
72 72
73 73 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
74 74 #[derive(PartialEq)]
75 75 pub struct ChangelogRevisionData<'changelog> {
76 76 /// The data bytes of the `changelog` entry.
77 77 bytes: Cow<'changelog, [u8]>,
78 78 /// The end offset for the hex manifest (not including the newline)
79 79 manifest_end: usize,
80 80 /// The end offset for the user+email (not including the newline)
81 81 user_end: usize,
82 82 /// The end offset for the timestamp+timezone+extras (not including the
83 83 /// newline)
84 84 timestamp_end: usize,
85 85 /// The end offset for the file list (not including the newline)
86 86 files_end: usize,
87 87 }
88 88
89 89 impl<'changelog> ChangelogRevisionData<'changelog> {
90 90 fn new(bytes: Cow<'changelog, [u8]>) -> Result<Self, HgError> {
91 91 let mut line_iter = bytes.split(|b| b == &b'\n');
92 92 let manifest_end = line_iter
93 93 .next()
94 94 .expect("Empty iterator from split()?")
95 95 .len();
96 96 let user_slice = line_iter.next().ok_or_else(|| {
97 97 HgError::corrupted("Changeset data truncated after manifest line")
98 98 })?;
99 99 let user_end = manifest_end + 1 + user_slice.len();
100 100 let timestamp_slice = line_iter.next().ok_or_else(|| {
101 101 HgError::corrupted("Changeset data truncated after user line")
102 102 })?;
103 103 let timestamp_end = user_end + 1 + timestamp_slice.len();
104 104 let mut files_end = timestamp_end + 1;
105 105 loop {
106 106 let line = line_iter.next().ok_or_else(|| {
107 107 HgError::corrupted("Changeset data truncated in files list")
108 108 })?;
109 109 if line.is_empty() {
110 110 if files_end == bytes.len() {
111 111 // The list of files ended with a single newline (there
112 112 // should be two)
113 113 return Err(HgError::corrupted(
114 114 "Changeset data truncated after files list",
115 115 ));
116 116 }
117 117 files_end -= 1;
118 118 break;
119 119 }
120 120 files_end += line.len() + 1;
121 121 }
122 122
123 123 Ok(Self {
124 124 bytes,
125 125 manifest_end,
126 126 user_end,
127 127 timestamp_end,
128 128 files_end,
129 129 })
130 130 }
131 131
132 132 fn null() -> Self {
133 133 Self::new(Cow::Borrowed(
134 134 b"0000000000000000000000000000000000000000\n\n0 0\n\n",
135 135 ))
136 136 .unwrap()
137 137 }
138 138
139 139 /// Return an iterator over the lines of the entry.
140 140 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
141 141 self.bytes.split(|b| b == &b'\n')
142 142 }
143 143
144 144 /// Return the node id of the `manifest` referenced by this `changelog`
145 145 /// entry.
146 146 pub fn manifest_node(&self) -> Result<Node, HgError> {
147 147 let manifest_node_hex = &self.bytes[..self.manifest_end];
148 148 Node::from_hex_for_repo(manifest_node_hex)
149 149 }
150 150
151 151 /// The full user string (usually a name followed by an email enclosed in
152 152 /// angle brackets)
153 153 pub fn user(&self) -> &[u8] {
154 154 &self.bytes[self.manifest_end + 1..self.user_end]
155 155 }
156 156
157 157 /// The full timestamp line (timestamp in seconds, offset in seconds, and
158 158 /// possibly extras)
159 159 // TODO: We should expose this in a more useful way
160 160 pub fn timestamp_line(&self) -> &[u8] {
161 161 &self.bytes[self.user_end + 1..self.timestamp_end]
162 162 }
163 163
164 164 /// The files changed in this revision.
165 165 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
166 166 self.bytes[self.timestamp_end + 1..self.files_end]
167 167 .split(|b| b == &b'\n')
168 168 .map(HgPath::new)
169 169 }
170 170
171 171 /// The change description.
172 172 pub fn description(&self) -> &[u8] {
173 173 &self.bytes[self.files_end + 2..]
174 174 }
175 175 }
176 176
177 177 impl Debug for ChangelogRevisionData<'_> {
178 178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 179 f.debug_struct("ChangelogRevisionData")
180 180 .field("bytes", &debug_bytes(&self.bytes))
181 181 .field("manifest", &debug_bytes(&self.bytes[..self.manifest_end]))
182 182 .field(
183 183 "user",
184 184 &debug_bytes(
185 185 &self.bytes[self.manifest_end + 1..self.user_end],
186 186 ),
187 187 )
188 188 .field(
189 189 "timestamp",
190 190 &debug_bytes(
191 191 &self.bytes[self.user_end + 1..self.timestamp_end],
192 192 ),
193 193 )
194 194 .field(
195 195 "files",
196 196 &debug_bytes(
197 197 &self.bytes[self.timestamp_end + 1..self.files_end],
198 198 ),
199 199 )
200 200 .field(
201 201 "description",
202 202 &debug_bytes(&self.bytes[self.files_end + 2..]),
203 203 )
204 204 .finish()
205 205 }
206 206 }
207 207
208 208 fn debug_bytes(bytes: &[u8]) -> String {
209 209 String::from_utf8_lossy(
210 210 &bytes.iter().flat_map(|b| escape_default(*b)).collect_vec(),
211 211 )
212 212 .to_string()
213 213 }
214 214
215 215 #[cfg(test)]
216 216 mod tests {
217 217 use super::*;
218 218 use pretty_assertions::assert_eq;
219 219
220 220 #[test]
221 221 fn test_create_changelogrevisiondata_invalid() {
222 222 // Completely empty
223 223 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
224 224 // No newline after manifest
225 225 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
226 226 // No newline after user
227 227 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n")).is_err());
228 228 // No newline after timestamp
229 229 assert!(
230 230 ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n\n0 0")).is_err()
231 231 );
232 232 // Missing newline after files
233 233 assert!(ChangelogRevisionData::new(Cow::Borrowed(
234 234 b"abcd\n\n0 0\nfile1\nfile2"
235 235 ))
236 236 .is_err(),);
237 237 // Only one newline after files
238 238 assert!(ChangelogRevisionData::new(Cow::Borrowed(
239 239 b"abcd\n\n0 0\nfile1\nfile2\n"
240 240 ))
241 241 .is_err(),);
242 242 }
243 243
244 244 #[test]
245 245 fn test_create_changelogrevisiondata() {
246 246 let data = ChangelogRevisionData::new(Cow::Borrowed(
247 247 b"0123456789abcdef0123456789abcdef01234567
248 248 Some One <someone@example.com>
249 249 0 0
250 250 file1
251 251 file2
252 252
253 253 some
254 254 commit
255 255 message",
256 256 ))
257 257 .unwrap();
258 258 assert_eq!(
259 259 data.manifest_node().unwrap(),
260 260 Node::from_hex("0123456789abcdef0123456789abcdef01234567")
261 261 .unwrap()
262 262 );
263 263 assert_eq!(data.user(), b"Some One <someone@example.com>");
264 264 assert_eq!(data.timestamp_line(), b"0 0");
265 265 assert_eq!(
266 266 data.files().collect_vec(),
267 267 vec![HgPath::new("file1"), HgPath::new("file2")]
268 268 );
269 269 assert_eq!(data.description(), b"some\ncommit\nmessage");
270 270 }
271 271 }
@@ -1,208 +1,208 b''
1 1 use crate::errors::HgError;
2 2 use crate::repo::Repo;
3 3 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::revlog::RevlogEntry;
5 use crate::revlog::revlog::{Revlog, RevlogError};
6 4 use crate::revlog::NodePrefix;
7 5 use crate::revlog::Revision;
6 use crate::revlog::RevlogEntry;
7 use crate::revlog::{Revlog, RevlogError};
8 8 use crate::utils::files::get_path_from_bytes;
9 9 use crate::utils::hg_path::HgPath;
10 10 use crate::utils::SliceExt;
11 11 use std::path::PathBuf;
12 12
13 13 /// A specialized `Revlog` to work with file data logs.
14 14 pub struct Filelog {
15 15 /// The generic `revlog` format.
16 16 revlog: Revlog,
17 17 }
18 18
19 19 impl Filelog {
20 20 pub fn open_vfs(
21 21 store_vfs: &crate::vfs::Vfs<'_>,
22 22 file_path: &HgPath,
23 23 ) -> Result<Self, HgError> {
24 24 let index_path = store_path(file_path, b".i");
25 25 let data_path = store_path(file_path, b".d");
26 26 let revlog =
27 27 Revlog::open(store_vfs, index_path, Some(&data_path), false)?;
28 28 Ok(Self { revlog })
29 29 }
30 30
31 31 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> {
32 32 Self::open_vfs(&repo.store_vfs(), file_path)
33 33 }
34 34
35 35 /// The given node ID is that of the file as found in a filelog, not of a
36 36 /// changeset.
37 37 pub fn data_for_node(
38 38 &self,
39 39 file_node: impl Into<NodePrefix>,
40 40 ) -> Result<FilelogRevisionData, RevlogError> {
41 41 let file_rev = self.revlog.rev_from_node(file_node.into())?;
42 42 self.data_for_rev(file_rev)
43 43 }
44 44
45 45 /// The given revision is that of the file as found in a filelog, not of a
46 46 /// changeset.
47 47 pub fn data_for_rev(
48 48 &self,
49 49 file_rev: Revision,
50 50 ) -> Result<FilelogRevisionData, RevlogError> {
51 51 let data: Vec<u8> = self.revlog.get_rev_data(file_rev)?.into_owned();
52 52 Ok(FilelogRevisionData(data))
53 53 }
54 54
55 55 /// The given node ID is that of the file as found in a filelog, not of a
56 56 /// changeset.
57 57 pub fn entry_for_node(
58 58 &self,
59 59 file_node: impl Into<NodePrefix>,
60 60 ) -> Result<FilelogEntry, RevlogError> {
61 61 let file_rev = self.revlog.rev_from_node(file_node.into())?;
62 62 self.entry_for_rev(file_rev)
63 63 }
64 64
65 65 /// The given revision is that of the file as found in a filelog, not of a
66 66 /// changeset.
67 67 pub fn entry_for_rev(
68 68 &self,
69 69 file_rev: Revision,
70 70 ) -> Result<FilelogEntry, RevlogError> {
71 71 Ok(FilelogEntry(self.revlog.get_entry(file_rev)?))
72 72 }
73 73 }
74 74
75 75 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
76 76 let encoded_bytes =
77 77 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
78 78 get_path_from_bytes(&encoded_bytes).into()
79 79 }
80 80
81 81 pub struct FilelogEntry<'a>(RevlogEntry<'a>);
82 82
83 83 impl FilelogEntry<'_> {
84 84 /// `self.data()` can be expensive, with decompression and delta
85 85 /// resolution.
86 86 ///
87 87 /// *Without* paying this cost, based on revlog index information
88 88 /// including `RevlogEntry::uncompressed_len`:
89 89 ///
90 90 /// * Returns `true` if the length that `self.data().file_data().len()`
91 91 /// would return is definitely **not equal** to `other_len`.
92 92 /// * Returns `false` if available information is inconclusive.
93 93 pub fn file_data_len_not_equal_to(&self, other_len: u64) -> bool {
94 94 // Relevant code that implement this behavior in Python code:
95 95 // basefilectx.cmp, filelog.size, storageutil.filerevisioncopied,
96 96 // revlog.size, revlog.rawsize
97 97
98 98 // Let’s call `file_data_len` what would be returned by
99 99 // `self.data().file_data().len()`.
100 100
101 101 if self.0.is_censored() {
102 102 let file_data_len = 0;
103 103 return other_len != file_data_len;
104 104 }
105 105
106 106 if self.0.has_length_affecting_flag_processor() {
107 107 // We can’t conclude anything about `file_data_len`.
108 108 return false;
109 109 }
110 110
111 111 // Revlog revisions (usually) have metadata for the size of
112 112 // their data after decompression and delta resolution
113 113 // as would be returned by `Revlog::get_rev_data`.
114 114 //
115 115 // For filelogs this is the file’s contents preceded by an optional
116 116 // metadata block.
117 117 let uncompressed_len = if let Some(l) = self.0.uncompressed_len() {
118 118 l as u64
119 119 } else {
120 120 // The field was set to -1, the actual uncompressed len is unknown.
121 121 // We need to decompress to say more.
122 122 return false;
123 123 };
124 124 // `uncompressed_len = file_data_len + optional_metadata_len`,
125 125 // so `file_data_len <= uncompressed_len`.
126 126 if uncompressed_len < other_len {
127 127 // Transitively, `file_data_len < other_len`.
128 128 // So `other_len != file_data_len` definitely.
129 129 return true;
130 130 }
131 131
132 132 if uncompressed_len == other_len + 4 {
133 133 // It’s possible that `file_data_len == other_len` with an empty
134 134 // metadata block (2 start marker bytes + 2 end marker bytes).
135 135 // This happens when there wouldn’t otherwise be metadata, but
136 136 // the first 2 bytes of file data happen to match a start marker
137 137 // and would be ambiguous.
138 138 return false;
139 139 }
140 140
141 141 if !self.0.has_p1() {
142 142 // There may or may not be copy metadata, so we can’t deduce more
143 143 // about `file_data_len` without computing file data.
144 144 return false;
145 145 }
146 146
147 147 // Filelog ancestry is not meaningful in the way changelog ancestry is.
148 148 // It only provides hints to delta generation.
149 149 // p1 and p2 are set to null when making a copy or rename since
150 150 // contents are likely unrelatedto what might have previously existed
151 151 // at the destination path.
152 152 //
153 153 // Conversely, since here p1 is non-null, there is no copy metadata.
154 154 // Note that this reasoning may be invalidated in the presence of
155 155 // merges made by some previous versions of Mercurial that
156 156 // swapped p1 and p2. See <https://bz.mercurial-scm.org/show_bug.cgi?id=6528>
157 157 // and `tests/test-issue6528.t`.
158 158 //
159 159 // Since copy metadata is currently the only kind of metadata
160 160 // kept in revlog data of filelogs,
161 161 // this `FilelogEntry` does not have such metadata:
162 162 let file_data_len = uncompressed_len;
163 163
164 164 file_data_len != other_len
165 165 }
166 166
167 167 pub fn data(&self) -> Result<FilelogRevisionData, HgError> {
168 168 Ok(FilelogRevisionData(self.0.data()?.into_owned()))
169 169 }
170 170 }
171 171
172 172 /// The data for one revision in a filelog, uncompressed and delta-resolved.
173 173 pub struct FilelogRevisionData(Vec<u8>);
174 174
175 175 impl FilelogRevisionData {
176 176 /// Split into metadata and data
177 177 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
178 178 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
179 179
180 180 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
181 181 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
182 182 Ok((Some(metadata), data))
183 183 } else {
184 184 Err(HgError::corrupted(
185 185 "Missing metadata end delimiter in filelog entry",
186 186 ))
187 187 }
188 188 } else {
189 189 Ok((None, &self.0))
190 190 }
191 191 }
192 192
193 193 /// Returns the file contents at this revision, stripped of any metadata
194 194 pub fn file_data(&self) -> Result<&[u8], HgError> {
195 195 let (_metadata, data) = self.split()?;
196 196 Ok(data)
197 197 }
198 198
199 199 /// Consume the entry, and convert it into data, discarding any metadata,
200 200 /// if present.
201 201 pub fn into_file_data(self) -> Result<Vec<u8>, HgError> {
202 202 if let (Some(_metadata), data) = self.split()? {
203 203 Ok(data.to_owned())
204 204 } else {
205 205 Ok(self.0)
206 206 }
207 207 }
208 208 }
@@ -1,194 +1,194 b''
1 1 use crate::errors::HgError;
2 use crate::revlog::revlog::{Revlog, RevlogError};
3 2 use crate::revlog::Revision;
4 3 use crate::revlog::{Node, NodePrefix};
4 use crate::revlog::{Revlog, RevlogError};
5 5 use crate::utils::hg_path::HgPath;
6 6 use crate::utils::SliceExt;
7 7 use crate::vfs::Vfs;
8 8
9 9 /// A specialized `Revlog` to work with `manifest` data format.
10 10 pub struct Manifestlog {
11 11 /// The generic `revlog` format.
12 12 revlog: Revlog,
13 13 }
14 14
15 15 impl Manifestlog {
16 16 /// Open the `manifest` of a repository given by its root.
17 17 pub fn open(store_vfs: &Vfs, use_nodemap: bool) -> Result<Self, HgError> {
18 18 let revlog =
19 19 Revlog::open(store_vfs, "00manifest.i", None, use_nodemap)?;
20 20 Ok(Self { revlog })
21 21 }
22 22
23 23 /// Return the `Manifest` for the given node ID.
24 24 ///
25 25 /// Note: this is a node ID in the manifestlog, typically found through
26 26 /// `ChangelogEntry::manifest_node`. It is *not* the node ID of any
27 27 /// changeset.
28 28 ///
29 29 /// See also `Repo::manifest_for_node`
30 30 pub fn data_for_node(
31 31 &self,
32 32 node: NodePrefix,
33 33 ) -> Result<Manifest, RevlogError> {
34 34 let rev = self.revlog.rev_from_node(node)?;
35 35 self.data_for_rev(rev)
36 36 }
37 37
38 38 /// Return the `Manifest` of a given revision number.
39 39 ///
40 40 /// Note: this is a revision number in the manifestlog, *not* of any
41 41 /// changeset.
42 42 ///
43 43 /// See also `Repo::manifest_for_rev`
44 44 pub fn data_for_rev(
45 45 &self,
46 46 rev: Revision,
47 47 ) -> Result<Manifest, RevlogError> {
48 48 let bytes = self.revlog.get_rev_data(rev)?.into_owned();
49 49 Ok(Manifest { bytes })
50 50 }
51 51 }
52 52
53 53 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
54 54 #[derive(Debug)]
55 55 pub struct Manifest {
56 56 /// Format for a manifest: flat sequence of variable-size entries,
57 57 /// sorted by path, each as:
58 58 ///
59 59 /// ```text
60 60 /// <path> \0 <hex_node_id> <flags> \n
61 61 /// ```
62 62 ///
63 63 /// The last entry is also terminated by a newline character.
64 64 /// Flags is one of `b""` (the empty string), `b"x"`, `b"l"`, or `b"t"`.
65 65 bytes: Vec<u8>,
66 66 }
67 67
68 68 impl Manifest {
69 69 pub fn iter(
70 70 &self,
71 71 ) -> impl Iterator<Item = Result<ManifestEntry, HgError>> {
72 72 self.bytes
73 73 .split(|b| b == &b'\n')
74 74 .filter(|line| !line.is_empty())
75 75 .map(ManifestEntry::from_raw)
76 76 }
77 77
78 78 /// If the given path is in this manifest, return its filelog node ID
79 79 pub fn find_by_path(
80 80 &self,
81 81 path: &HgPath,
82 82 ) -> Result<Option<ManifestEntry>, HgError> {
83 83 use std::cmp::Ordering::*;
84 84 let path = path.as_bytes();
85 85 // Both boundaries of this `&[u8]` slice are always at the boundary of
86 86 // an entry
87 87 let mut bytes = &*self.bytes;
88 88
89 89 // Binary search algorithm derived from `[T]::binary_search_by`
90 90 // <https://github.com/rust-lang/rust/blob/1.57.0/library/core/src/slice/mod.rs#L2221>
91 91 // except we don’t have a slice of entries. Instead we jump to the
92 92 // middle of the byte slice and look around for entry delimiters
93 93 // (newlines).
94 94 while let Some(entry_range) = Self::find_entry_near_middle_of(bytes)? {
95 95 let (entry_path, rest) =
96 96 ManifestEntry::split_path(&bytes[entry_range.clone()])?;
97 97 let cmp = entry_path.cmp(path);
98 98 if cmp == Less {
99 99 let after_newline = entry_range.end + 1;
100 100 bytes = &bytes[after_newline..];
101 101 } else if cmp == Greater {
102 102 bytes = &bytes[..entry_range.start];
103 103 } else {
104 104 return Ok(Some(ManifestEntry::from_path_and_rest(
105 105 entry_path, rest,
106 106 )));
107 107 }
108 108 }
109 109 Ok(None)
110 110 }
111 111
112 112 /// If there is at least one, return the byte range of an entry *excluding*
113 113 /// the final newline.
114 114 fn find_entry_near_middle_of(
115 115 bytes: &[u8],
116 116 ) -> Result<Option<std::ops::Range<usize>>, HgError> {
117 117 let len = bytes.len();
118 118 if len > 0 {
119 119 let middle = bytes.len() / 2;
120 120 // Integer division rounds down, so `middle < len`.
121 121 let (before, after) = bytes.split_at(middle);
122 122 let is_newline = |&byte: &u8| byte == b'\n';
123 123 let entry_start = match before.iter().rposition(is_newline) {
124 124 Some(i) => i + 1,
125 125 None => 0, // We choose the first entry in `bytes`
126 126 };
127 127 let entry_end = match after.iter().position(is_newline) {
128 128 Some(i) => {
129 129 // No `+ 1` here to exclude this newline from the range
130 130 middle + i
131 131 }
132 132 None => {
133 133 // In a well-formed manifest:
134 134 //
135 135 // * Since `len > 0`, `bytes` contains at least one entry
136 136 // * Every entry ends with a newline
137 137 // * Since `middle < len`, `after` contains at least the
138 138 // newline at the end of the last entry of `bytes`.
139 139 //
140 140 // We didn’t find a newline, so this manifest is not
141 141 // well-formed.
142 142 return Err(HgError::corrupted(
143 143 "manifest entry without \\n delimiter",
144 144 ));
145 145 }
146 146 };
147 147 Ok(Some(entry_start..entry_end))
148 148 } else {
149 149 // len == 0
150 150 Ok(None)
151 151 }
152 152 }
153 153 }
154 154
155 155 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
156 156 #[derive(Debug)]
157 157 pub struct ManifestEntry<'manifest> {
158 158 pub path: &'manifest HgPath,
159 159 pub hex_node_id: &'manifest [u8],
160 160
161 161 /// `Some` values are b'x', b'l', or 't'
162 162 pub flags: Option<u8>,
163 163 }
164 164
165 165 impl<'a> ManifestEntry<'a> {
166 166 fn split_path(bytes: &[u8]) -> Result<(&[u8], &[u8]), HgError> {
167 167 bytes.split_2(b'\0').ok_or_else(|| {
168 168 HgError::corrupted("manifest entry without \\0 delimiter")
169 169 })
170 170 }
171 171
172 172 fn from_path_and_rest(path: &'a [u8], rest: &'a [u8]) -> Self {
173 173 let (hex_node_id, flags) = match rest.split_last() {
174 174 Some((&b'x', rest)) => (rest, Some(b'x')),
175 175 Some((&b'l', rest)) => (rest, Some(b'l')),
176 176 Some((&b't', rest)) => (rest, Some(b't')),
177 177 _ => (rest, None),
178 178 };
179 179 Self {
180 180 path: HgPath::new(path),
181 181 hex_node_id,
182 182 flags,
183 183 }
184 184 }
185 185
186 186 fn from_raw(bytes: &'a [u8]) -> Result<Self, HgError> {
187 187 let (path, rest) = Self::split_path(bytes)?;
188 188 Ok(Self::from_path_and_rest(path, rest))
189 189 }
190 190
191 191 pub fn node_id(&self) -> Result<Node, HgError> {
192 192 Node::from_hex_for_repo(self.hex_node_id)
193 193 }
194 194 }
This diff has been collapsed as it changes many lines, (642 lines changed) Show them Hide them
@@ -1,72 +1,710 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2023 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6 //! Mercurial concepts for handling revision history
7 7
8 8 pub mod node;
9 9 pub mod nodemap;
10 10 mod nodemap_docket;
11 11 pub mod path_encode;
12 12 pub use node::{FromHexError, Node, NodePrefix};
13 13 pub mod changelog;
14 14 pub mod filelog;
15 15 pub mod index;
16 16 pub mod manifest;
17 17 pub mod patch;
18 pub mod revlog;
18
19 use std::borrow::Cow;
20 use std::io::Read;
21 use std::ops::Deref;
22 use std::path::Path;
23
24 use flate2::read::ZlibDecoder;
25 use sha1::{Digest, Sha1};
26 use zstd;
27
28 use self::node::{NODE_BYTES_LENGTH, NULL_NODE};
29 use self::nodemap_docket::NodeMapDocket;
30 use super::index::Index;
31 use super::nodemap::{NodeMap, NodeMapError};
32 use crate::errors::HgError;
33 use crate::vfs::Vfs;
19 34
20 35 /// Mercurial revision numbers
21 36 ///
22 37 /// As noted in revlog.c, revision numbers are actually encoded in
23 38 /// 4 bytes, and are liberally converted to ints, whence the i32
24 39 pub type Revision = i32;
25 40
26 41 /// Marker expressing the absence of a parent
27 42 ///
28 43 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
29 44 /// to be smaller than all existing revisions.
30 45 pub const NULL_REVISION: Revision = -1;
31 46
32 47 /// Same as `mercurial.node.wdirrev`
33 48 ///
34 49 /// This is also equal to `i32::max_value()`, but it's better to spell
35 50 /// it out explicitely, same as in `mercurial.node`
36 51 #[allow(clippy::unreadable_literal)]
37 52 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
38 53
39 54 pub const WORKING_DIRECTORY_HEX: &str =
40 55 "ffffffffffffffffffffffffffffffffffffffff";
41 56
42 57 /// The simplest expression of what we need of Mercurial DAGs.
43 58 pub trait Graph {
44 59 /// Return the two parents of the given `Revision`.
45 60 ///
46 61 /// Each of the parents can be independently `NULL_REVISION`
47 62 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
48 63 }
49 64
50 65 #[derive(Clone, Debug, PartialEq)]
51 66 pub enum GraphError {
52 67 ParentOutOfRange(Revision),
53 68 WorkingDirectoryUnsupported,
54 69 }
55 70
56 71 /// The Mercurial Revlog Index
57 72 ///
58 73 /// This is currently limited to the minimal interface that is needed for
59 74 /// the [`nodemap`](nodemap/index.html) module
60 75 pub trait RevlogIndex {
61 76 /// Total number of Revisions referenced in this index
62 77 fn len(&self) -> usize;
63 78
64 79 fn is_empty(&self) -> bool {
65 80 self.len() == 0
66 81 }
67 82
68 83 /// Return a reference to the Node or `None` if rev is out of bounds
69 84 ///
70 85 /// `NULL_REVISION` is not considered to be out of bounds.
71 86 fn node(&self, rev: Revision) -> Option<&Node>;
72 87 }
88
89 const REVISION_FLAG_CENSORED: u16 = 1 << 15;
90 const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14;
91 const REVISION_FLAG_EXTSTORED: u16 = 1 << 13;
92 const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12;
93
94 // Keep this in sync with REVIDX_KNOWN_FLAGS in
95 // mercurial/revlogutils/flagutil.py
96 const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED
97 | REVISION_FLAG_ELLIPSIS
98 | REVISION_FLAG_EXTSTORED
99 | REVISION_FLAG_HASCOPIESINFO;
100
101 const NULL_REVLOG_ENTRY_FLAGS: u16 = 0;
102
103 #[derive(Debug, derive_more::From)]
104 pub enum RevlogError {
105 InvalidRevision,
106 /// Working directory is not supported
107 WDirUnsupported,
108 /// Found more than one entry whose ID match the requested prefix
109 AmbiguousPrefix,
110 #[from]
111 Other(HgError),
112 }
113
114 impl From<NodeMapError> for RevlogError {
115 fn from(error: NodeMapError) -> Self {
116 match error {
117 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
118 NodeMapError::RevisionNotInIndex(rev) => RevlogError::corrupted(
119 format!("nodemap point to revision {} not in index", rev),
120 ),
121 }
122 }
123 }
124
125 fn corrupted<S: AsRef<str>>(context: S) -> HgError {
126 HgError::corrupted(format!("corrupted revlog, {}", context.as_ref()))
127 }
128
129 impl RevlogError {
130 fn corrupted<S: AsRef<str>>(context: S) -> Self {
131 RevlogError::Other(corrupted(context))
132 }
133 }
134
135 /// Read only implementation of revlog.
136 pub struct Revlog {
137 /// When index and data are not interleaved: bytes of the revlog index.
138 /// When index and data are interleaved: bytes of the revlog index and
139 /// data.
140 index: Index,
141 /// When index and data are not interleaved: bytes of the revlog data
142 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
143 /// When present on disk: the persistent nodemap for this revlog
144 nodemap: Option<nodemap::NodeTree>,
145 }
146
147 impl Revlog {
148 /// Open a revlog index file.
149 ///
150 /// It will also open the associated data file if index and data are not
151 /// interleaved.
152 pub fn open(
153 store_vfs: &Vfs,
154 index_path: impl AsRef<Path>,
155 data_path: Option<&Path>,
156 use_nodemap: bool,
157 ) -> Result<Self, HgError> {
158 let index_path = index_path.as_ref();
159 let index = {
160 match store_vfs.mmap_open_opt(&index_path)? {
161 None => Index::new(Box::new(vec![])),
162 Some(index_mmap) => {
163 let index = Index::new(Box::new(index_mmap))?;
164 Ok(index)
165 }
166 }
167 }?;
168
169 let default_data_path = index_path.with_extension("d");
170
171 // type annotation required
172 // won't recognize Mmap as Deref<Target = [u8]>
173 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
174 if index.is_inline() {
175 None
176 } else {
177 let data_path = data_path.unwrap_or(&default_data_path);
178 let data_mmap = store_vfs.mmap_open(data_path)?;
179 Some(Box::new(data_mmap))
180 };
181
182 let nodemap = if index.is_inline() || !use_nodemap {
183 None
184 } else {
185 NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
186 |(docket, data)| {
187 nodemap::NodeTree::load_bytes(
188 Box::new(data),
189 docket.data_length,
190 )
191 },
192 )
193 };
194
195 Ok(Revlog {
196 index,
197 data_bytes,
198 nodemap,
199 })
200 }
201
202 /// Return number of entries of the `Revlog`.
203 pub fn len(&self) -> usize {
204 self.index.len()
205 }
206
207 /// Returns `true` if the `Revlog` has zero `entries`.
208 pub fn is_empty(&self) -> bool {
209 self.index.is_empty()
210 }
211
212 /// Returns the node ID for the given revision number, if it exists in this
213 /// revlog
214 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
215 if rev == NULL_REVISION {
216 return Some(&NULL_NODE);
217 }
218 Some(self.index.get_entry(rev)?.hash())
219 }
220
221 /// Return the revision number for the given node ID, if it exists in this
222 /// revlog
223 pub fn rev_from_node(
224 &self,
225 node: NodePrefix,
226 ) -> Result<Revision, RevlogError> {
227 if node.is_prefix_of(&NULL_NODE) {
228 return Ok(NULL_REVISION);
229 }
230
231 if let Some(nodemap) = &self.nodemap {
232 return nodemap
233 .find_bin(&self.index, node)?
234 .ok_or(RevlogError::InvalidRevision);
235 }
236
237 // Fallback to linear scan when a persistent nodemap is not present.
238 // This happens when the persistent-nodemap experimental feature is not
239 // enabled, or for small revlogs.
240 //
241 // TODO: consider building a non-persistent nodemap in memory to
242 // optimize these cases.
243 let mut found_by_prefix = None;
244 for rev in (0..self.len() as Revision).rev() {
245 let index_entry = self.index.get_entry(rev).ok_or_else(|| {
246 HgError::corrupted(
247 "revlog references a revision not in the index",
248 )
249 })?;
250 if node == *index_entry.hash() {
251 return Ok(rev);
252 }
253 if node.is_prefix_of(index_entry.hash()) {
254 if found_by_prefix.is_some() {
255 return Err(RevlogError::AmbiguousPrefix);
256 }
257 found_by_prefix = Some(rev)
258 }
259 }
260 found_by_prefix.ok_or(RevlogError::InvalidRevision)
261 }
262
263 /// Returns whether the given revision exists in this revlog.
264 pub fn has_rev(&self, rev: Revision) -> bool {
265 self.index.get_entry(rev).is_some()
266 }
267
268 /// Return the full data associated to a revision.
269 ///
270 /// All entries required to build the final data out of deltas will be
271 /// retrieved as needed, and the deltas will be applied to the inital
272 /// snapshot to rebuild the final data.
273 pub fn get_rev_data(
274 &self,
275 rev: Revision,
276 ) -> Result<Cow<[u8]>, RevlogError> {
277 if rev == NULL_REVISION {
278 return Ok(Cow::Borrowed(&[]));
279 };
280 Ok(self.get_entry(rev)?.data()?)
281 }
282
283 /// Check the hash of some given data against the recorded hash.
284 pub fn check_hash(
285 &self,
286 p1: Revision,
287 p2: Revision,
288 expected: &[u8],
289 data: &[u8],
290 ) -> bool {
291 let e1 = self.index.get_entry(p1);
292 let h1 = match e1 {
293 Some(ref entry) => entry.hash(),
294 None => &NULL_NODE,
295 };
296 let e2 = self.index.get_entry(p2);
297 let h2 = match e2 {
298 Some(ref entry) => entry.hash(),
299 None => &NULL_NODE,
300 };
301
302 hash(data, h1.as_bytes(), h2.as_bytes()) == expected
303 }
304
305 /// Build the full data of a revision out its snapshot
306 /// and its deltas.
307 fn build_data_from_deltas(
308 snapshot: RevlogEntry,
309 deltas: &[RevlogEntry],
310 ) -> Result<Vec<u8>, HgError> {
311 let snapshot = snapshot.data_chunk()?;
312 let deltas = deltas
313 .iter()
314 .rev()
315 .map(RevlogEntry::data_chunk)
316 .collect::<Result<Vec<_>, _>>()?;
317 let patches: Vec<_> =
318 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
319 let patch = patch::fold_patch_lists(&patches);
320 Ok(patch.apply(&snapshot))
321 }
322
323 /// Return the revlog data.
324 fn data(&self) -> &[u8] {
325 match &self.data_bytes {
326 Some(data_bytes) => data_bytes,
327 None => panic!(
328 "forgot to load the data or trying to access inline data"
329 ),
330 }
331 }
332
333 pub fn make_null_entry(&self) -> RevlogEntry {
334 RevlogEntry {
335 revlog: self,
336 rev: NULL_REVISION,
337 bytes: b"",
338 compressed_len: 0,
339 uncompressed_len: 0,
340 base_rev_or_base_of_delta_chain: None,
341 p1: NULL_REVISION,
342 p2: NULL_REVISION,
343 flags: NULL_REVLOG_ENTRY_FLAGS,
344 hash: NULL_NODE,
345 }
346 }
347
348 /// Get an entry of the revlog.
349 pub fn get_entry(
350 &self,
351 rev: Revision,
352 ) -> Result<RevlogEntry, RevlogError> {
353 if rev == NULL_REVISION {
354 return Ok(self.make_null_entry());
355 }
356 let index_entry = self
357 .index
358 .get_entry(rev)
359 .ok_or(RevlogError::InvalidRevision)?;
360 let start = index_entry.offset();
361 let end = start + index_entry.compressed_len() as usize;
362 let data = if self.index.is_inline() {
363 self.index.data(start, end)
364 } else {
365 &self.data()[start..end]
366 };
367 let entry = RevlogEntry {
368 revlog: self,
369 rev,
370 bytes: data,
371 compressed_len: index_entry.compressed_len(),
372 uncompressed_len: index_entry.uncompressed_len(),
373 base_rev_or_base_of_delta_chain: if index_entry
374 .base_revision_or_base_of_delta_chain()
375 == rev
376 {
377 None
378 } else {
379 Some(index_entry.base_revision_or_base_of_delta_chain())
380 },
381 p1: index_entry.p1(),
382 p2: index_entry.p2(),
383 flags: index_entry.flags(),
384 hash: *index_entry.hash(),
385 };
386 Ok(entry)
387 }
388
389 /// when resolving internal references within revlog, any errors
390 /// should be reported as corruption, instead of e.g. "invalid revision"
391 fn get_entry_internal(
392 &self,
393 rev: Revision,
394 ) -> Result<RevlogEntry, HgError> {
395 self.get_entry(rev)
396 .map_err(|_| corrupted(format!("revision {} out of range", rev)))
397 }
398 }
399
400 /// The revlog entry's bytes and the necessary informations to extract
401 /// the entry's data.
402 #[derive(Clone)]
403 pub struct RevlogEntry<'a> {
404 revlog: &'a Revlog,
405 rev: Revision,
406 bytes: &'a [u8],
407 compressed_len: u32,
408 uncompressed_len: i32,
409 base_rev_or_base_of_delta_chain: Option<Revision>,
410 p1: Revision,
411 p2: Revision,
412 flags: u16,
413 hash: Node,
414 }
415
416 impl<'a> RevlogEntry<'a> {
417 pub fn revision(&self) -> Revision {
418 self.rev
419 }
420
421 pub fn node(&self) -> &Node {
422 &self.hash
423 }
424
425 pub fn uncompressed_len(&self) -> Option<u32> {
426 u32::try_from(self.uncompressed_len).ok()
427 }
428
429 pub fn has_p1(&self) -> bool {
430 self.p1 != NULL_REVISION
431 }
432
433 pub fn p1_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
434 if self.p1 == NULL_REVISION {
435 Ok(None)
436 } else {
437 Ok(Some(self.revlog.get_entry(self.p1)?))
438 }
439 }
440
441 pub fn p2_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
442 if self.p2 == NULL_REVISION {
443 Ok(None)
444 } else {
445 Ok(Some(self.revlog.get_entry(self.p2)?))
446 }
447 }
448
449 pub fn p1(&self) -> Option<Revision> {
450 if self.p1 == NULL_REVISION {
451 None
452 } else {
453 Some(self.p1)
454 }
455 }
456
457 pub fn p2(&self) -> Option<Revision> {
458 if self.p2 == NULL_REVISION {
459 None
460 } else {
461 Some(self.p2)
462 }
463 }
464
465 pub fn is_censored(&self) -> bool {
466 (self.flags & REVISION_FLAG_CENSORED) != 0
467 }
468
469 pub fn has_length_affecting_flag_processor(&self) -> bool {
470 // Relevant Python code: revlog.size()
471 // note: ELLIPSIS is known to not change the content
472 (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0
473 }
474
475 /// The data for this entry, after resolving deltas if any.
476 pub fn rawdata(&self) -> Result<Cow<'a, [u8]>, HgError> {
477 let mut entry = self.clone();
478 let mut delta_chain = vec![];
479
480 // The meaning of `base_rev_or_base_of_delta_chain` depends on
481 // generaldelta. See the doc on `ENTRY_DELTA_BASE` in
482 // `mercurial/revlogutils/constants.py` and the code in
483 // [_chaininfo] and in [index_deltachain].
484 let uses_generaldelta = self.revlog.index.uses_generaldelta();
485 while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain {
486 let base_rev = if uses_generaldelta {
487 base_rev
488 } else {
489 entry.rev - 1
490 };
491 delta_chain.push(entry);
492 entry = self.revlog.get_entry_internal(base_rev)?;
493 }
494
495 let data = if delta_chain.is_empty() {
496 entry.data_chunk()?
497 } else {
498 Revlog::build_data_from_deltas(entry, &delta_chain)?.into()
499 };
500
501 Ok(data)
502 }
503
504 fn check_data(
505 &self,
506 data: Cow<'a, [u8]>,
507 ) -> Result<Cow<'a, [u8]>, HgError> {
508 if self.revlog.check_hash(
509 self.p1,
510 self.p2,
511 self.hash.as_bytes(),
512 &data,
513 ) {
514 Ok(data)
515 } else {
516 if (self.flags & REVISION_FLAG_ELLIPSIS) != 0 {
517 return Err(HgError::unsupported(
518 "ellipsis revisions are not supported by rhg",
519 ));
520 }
521 Err(corrupted(format!(
522 "hash check failed for revision {}",
523 self.rev
524 )))
525 }
526 }
527
528 pub fn data(&self) -> Result<Cow<'a, [u8]>, HgError> {
529 let data = self.rawdata()?;
530 if self.is_censored() {
531 return Err(HgError::CensoredNodeError);
532 }
533 self.check_data(data)
534 }
535
536 /// Extract the data contained in the entry.
537 /// This may be a delta. (See `is_delta`.)
538 fn data_chunk(&self) -> Result<Cow<'a, [u8]>, HgError> {
539 if self.bytes.is_empty() {
540 return Ok(Cow::Borrowed(&[]));
541 }
542 match self.bytes[0] {
543 // Revision data is the entirety of the entry, including this
544 // header.
545 b'\0' => Ok(Cow::Borrowed(self.bytes)),
546 // Raw revision data follows.
547 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
548 // zlib (RFC 1950) data.
549 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
550 // zstd data.
551 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
552 // A proper new format should have had a repo/store requirement.
553 format_type => Err(corrupted(format!(
554 "unknown compression header '{}'",
555 format_type
556 ))),
557 }
558 }
559
560 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, HgError> {
561 let mut decoder = ZlibDecoder::new(self.bytes);
562 if self.is_delta() {
563 let mut buf = Vec::with_capacity(self.compressed_len as usize);
564 decoder
565 .read_to_end(&mut buf)
566 .map_err(|e| corrupted(e.to_string()))?;
567 Ok(buf)
568 } else {
569 let cap = self.uncompressed_len.max(0) as usize;
570 let mut buf = vec![0; cap];
571 decoder
572 .read_exact(&mut buf)
573 .map_err(|e| corrupted(e.to_string()))?;
574 Ok(buf)
575 }
576 }
577
578 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, HgError> {
579 if self.is_delta() {
580 let mut buf = Vec::with_capacity(self.compressed_len as usize);
581 zstd::stream::copy_decode(self.bytes, &mut buf)
582 .map_err(|e| corrupted(e.to_string()))?;
583 Ok(buf)
584 } else {
585 let cap = self.uncompressed_len.max(0) as usize;
586 let mut buf = vec![0; cap];
587 let len = zstd::bulk::decompress_to_buffer(self.bytes, &mut buf)
588 .map_err(|e| corrupted(e.to_string()))?;
589 if len != self.uncompressed_len as usize {
590 Err(corrupted("uncompressed length does not match"))
591 } else {
592 Ok(buf)
593 }
594 }
595 }
596
597 /// Tell if the entry is a snapshot or a delta
598 /// (influences on decompression).
599 fn is_delta(&self) -> bool {
600 self.base_rev_or_base_of_delta_chain.is_some()
601 }
602 }
603
604 /// Calculate the hash of a revision given its data and its parents.
605 fn hash(
606 data: &[u8],
607 p1_hash: &[u8],
608 p2_hash: &[u8],
609 ) -> [u8; NODE_BYTES_LENGTH] {
610 let mut hasher = Sha1::new();
611 let (a, b) = (p1_hash, p2_hash);
612 if a > b {
613 hasher.update(b);
614 hasher.update(a);
615 } else {
616 hasher.update(a);
617 hasher.update(b);
618 }
619 hasher.update(data);
620 *hasher.finalize().as_ref()
621 }
622
623 #[cfg(test)]
624 mod tests {
625 use super::*;
626 use crate::index::{IndexEntryBuilder, INDEX_ENTRY_SIZE};
627 use itertools::Itertools;
628
629 #[test]
630 fn test_empty() {
631 let temp = tempfile::tempdir().unwrap();
632 let vfs = Vfs { base: temp.path() };
633 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
634 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
635 assert!(revlog.is_empty());
636 assert_eq!(revlog.len(), 0);
637 assert!(revlog.get_entry(0).is_err());
638 assert!(!revlog.has_rev(0));
639 }
640
641 #[test]
642 fn test_inline() {
643 let temp = tempfile::tempdir().unwrap();
644 let vfs = Vfs { base: temp.path() };
645 let node0 = Node::from_hex("2ed2a3912a0b24502043eae84ee4b279c18b90dd")
646 .unwrap();
647 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
648 .unwrap();
649 let node2 = Node::from_hex("dd6ad206e907be60927b5a3117b97dffb2590582")
650 .unwrap();
651 let entry0_bytes = IndexEntryBuilder::new()
652 .is_first(true)
653 .with_version(1)
654 .with_inline(true)
655 .with_offset(INDEX_ENTRY_SIZE)
656 .with_node(node0)
657 .build();
658 let entry1_bytes = IndexEntryBuilder::new()
659 .with_offset(INDEX_ENTRY_SIZE)
660 .with_node(node1)
661 .build();
662 let entry2_bytes = IndexEntryBuilder::new()
663 .with_offset(INDEX_ENTRY_SIZE)
664 .with_p1(0)
665 .with_p2(1)
666 .with_node(node2)
667 .build();
668 let contents = vec![entry0_bytes, entry1_bytes, entry2_bytes]
669 .into_iter()
670 .flatten()
671 .collect_vec();
672 std::fs::write(temp.path().join("foo.i"), contents).unwrap();
673 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
674
675 let entry0 = revlog.get_entry(0).ok().unwrap();
676 assert_eq!(entry0.revision(), 0);
677 assert_eq!(*entry0.node(), node0);
678 assert!(!entry0.has_p1());
679 assert_eq!(entry0.p1(), None);
680 assert_eq!(entry0.p2(), None);
681 let p1_entry = entry0.p1_entry().unwrap();
682 assert!(p1_entry.is_none());
683 let p2_entry = entry0.p2_entry().unwrap();
684 assert!(p2_entry.is_none());
685
686 let entry1 = revlog.get_entry(1).ok().unwrap();
687 assert_eq!(entry1.revision(), 1);
688 assert_eq!(*entry1.node(), node1);
689 assert!(!entry1.has_p1());
690 assert_eq!(entry1.p1(), None);
691 assert_eq!(entry1.p2(), None);
692 let p1_entry = entry1.p1_entry().unwrap();
693 assert!(p1_entry.is_none());
694 let p2_entry = entry1.p2_entry().unwrap();
695 assert!(p2_entry.is_none());
696
697 let entry2 = revlog.get_entry(2).ok().unwrap();
698 assert_eq!(entry2.revision(), 2);
699 assert_eq!(*entry2.node(), node2);
700 assert!(entry2.has_p1());
701 assert_eq!(entry2.p1(), Some(0));
702 assert_eq!(entry2.p2(), Some(1));
703 let p1_entry = entry2.p1_entry().unwrap();
704 assert!(p1_entry.is_some());
705 assert_eq!(p1_entry.unwrap().revision(), 0);
706 let p2_entry = entry2.p2_entry().unwrap();
707 assert!(p2_entry.is_some());
708 assert_eq!(p2_entry.unwrap().revision(), 1);
709 }
710 }
@@ -1,69 +1,69 b''
1 1 //! The revset query language
2 2 //!
3 3 //! <https://www.mercurial-scm.org/repo/hg/help/revsets>
4 4
5 5 use crate::errors::HgError;
6 6 use crate::repo::Repo;
7 use crate::revlog::revlog::{Revlog, RevlogError};
8 7 use crate::revlog::NodePrefix;
9 8 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
9 use crate::revlog::{Revlog, RevlogError};
10 10 use crate::Node;
11 11
12 12 /// Resolve a query string into a single revision.
13 13 ///
14 14 /// Only some of the revset language is implemented yet.
15 15 pub fn resolve_single(
16 16 input: &str,
17 17 repo: &Repo,
18 18 ) -> Result<Revision, RevlogError> {
19 19 let changelog = repo.changelog()?;
20 20
21 21 match input {
22 22 "." => {
23 23 let p1 = repo.dirstate_parents()?.p1;
24 24 return changelog.revlog.rev_from_node(p1.into());
25 25 }
26 26 "null" => return Ok(NULL_REVISION),
27 27 _ => {}
28 28 }
29 29
30 30 match resolve_rev_number_or_hex_prefix(input, &changelog.revlog) {
31 31 Err(RevlogError::InvalidRevision) => {
32 32 // TODO: support for the rest of the language here.
33 33 let msg = format!("cannot parse revset '{}'", input);
34 34 Err(HgError::unsupported(msg).into())
35 35 }
36 36 result => result,
37 37 }
38 38 }
39 39
40 40 /// Resolve the small subset of the language suitable for revlogs other than
41 41 /// the changelog, such as in `hg debugdata --manifest` CLI argument.
42 42 ///
43 43 /// * A non-negative decimal integer for a revision number, or
44 44 /// * An hexadecimal string, for the unique node ID that starts with this
45 45 /// prefix
46 46 pub fn resolve_rev_number_or_hex_prefix(
47 47 input: &str,
48 48 revlog: &Revlog,
49 49 ) -> Result<Revision, RevlogError> {
50 50 // The Python equivalent of this is part of `revsymbol` in
51 51 // `mercurial/scmutil.py`
52 52
53 53 if let Ok(integer) = input.parse::<i32>() {
54 54 if integer.to_string() == input
55 55 && integer >= 0
56 56 && revlog.has_rev(integer)
57 57 {
58 58 return Ok(integer);
59 59 }
60 60 }
61 61 if let Ok(prefix) = NodePrefix::from_hex(input) {
62 62 if prefix.is_prefix_of(&Node::from_hex(WORKING_DIRECTORY_HEX).unwrap())
63 63 {
64 64 return Err(RevlogError::WDirUnsupported);
65 65 }
66 66 return revlog.rev_from_node(prefix);
67 67 }
68 68 Err(RevlogError::InvalidRevision)
69 69 }
@@ -1,295 +1,295 b''
1 1 use crate::ui::utf8_to_local;
2 2 use crate::ui::UiError;
3 3 use crate::NoRepoInCwdError;
4 4 use format_bytes::format_bytes;
5 5 use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError};
6 6 use hg::dirstate_tree::on_disk::DirstateV2ParseError;
7 7 use hg::errors::HgError;
8 8 use hg::exit_codes;
9 9 use hg::repo::RepoError;
10 use hg::revlog::revlog::RevlogError;
10 use hg::revlog::RevlogError;
11 11 use hg::sparse::SparseConfigError;
12 12 use hg::utils::files::get_bytes_from_path;
13 13 use hg::{DirstateError, DirstateMapError, StatusError};
14 14 use std::convert::From;
15 15
16 16 /// The kind of command error
17 17 #[derive(Debug)]
18 18 pub enum CommandError {
19 19 /// Exit with an error message and "standard" failure exit code.
20 20 Abort {
21 21 message: Vec<u8>,
22 22 detailed_exit_code: exit_codes::ExitCode,
23 23 hint: Option<Vec<u8>>,
24 24 },
25 25
26 26 /// Exit with a failure exit code but no message.
27 27 Unsuccessful,
28 28
29 29 /// Encountered something (such as a CLI argument, repository layout, …)
30 30 /// not supported by this version of `rhg`. Depending on configuration
31 31 /// `rhg` may attempt to silently fall back to Python-based `hg`, which
32 32 /// may or may not support this feature.
33 33 UnsupportedFeature { message: Vec<u8> },
34 34 /// The fallback executable does not exist (or has some other problem if
35 35 /// we end up being more precise about broken fallbacks).
36 36 InvalidFallback { path: Vec<u8>, err: String },
37 37 }
38 38
39 39 impl CommandError {
40 40 pub fn abort(message: impl AsRef<str>) -> Self {
41 41 CommandError::abort_with_exit_code(message, exit_codes::ABORT)
42 42 }
43 43
44 44 pub fn abort_with_exit_code(
45 45 message: impl AsRef<str>,
46 46 detailed_exit_code: exit_codes::ExitCode,
47 47 ) -> Self {
48 48 CommandError::Abort {
49 49 // TODO: bytes-based (instead of Unicode-based) formatting
50 50 // of error messages to handle non-UTF-8 filenames etc:
51 51 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
52 52 message: utf8_to_local(message.as_ref()).into(),
53 53 detailed_exit_code,
54 54 hint: None,
55 55 }
56 56 }
57 57
58 58 pub fn abort_with_exit_code_and_hint(
59 59 message: impl AsRef<str>,
60 60 detailed_exit_code: exit_codes::ExitCode,
61 61 hint: Option<impl AsRef<str>>,
62 62 ) -> Self {
63 63 CommandError::Abort {
64 64 message: utf8_to_local(message.as_ref()).into(),
65 65 detailed_exit_code,
66 66 hint: hint.map(|h| utf8_to_local(h.as_ref()).into()),
67 67 }
68 68 }
69 69
70 70 pub fn abort_with_exit_code_bytes(
71 71 message: impl AsRef<[u8]>,
72 72 detailed_exit_code: exit_codes::ExitCode,
73 73 ) -> Self {
74 74 // TODO: use this everywhere it makes sense instead of the string
75 75 // version.
76 76 CommandError::Abort {
77 77 message: message.as_ref().into(),
78 78 detailed_exit_code,
79 79 hint: None,
80 80 }
81 81 }
82 82
83 83 pub fn unsupported(message: impl AsRef<str>) -> Self {
84 84 CommandError::UnsupportedFeature {
85 85 message: utf8_to_local(message.as_ref()).into(),
86 86 }
87 87 }
88 88 }
89 89
90 90 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
91 91 /// but not supported yet by `rhg`.
92 92 impl From<clap::Error> for CommandError {
93 93 fn from(error: clap::Error) -> Self {
94 94 CommandError::unsupported(error.to_string())
95 95 }
96 96 }
97 97
98 98 impl From<HgError> for CommandError {
99 99 fn from(error: HgError) -> Self {
100 100 match error {
101 101 HgError::UnsupportedFeature(message) => {
102 102 CommandError::unsupported(message)
103 103 }
104 104 HgError::CensoredNodeError => {
105 105 CommandError::unsupported("Encountered a censored node")
106 106 }
107 107 HgError::Abort {
108 108 message,
109 109 detailed_exit_code,
110 110 hint,
111 111 } => CommandError::abort_with_exit_code_and_hint(
112 112 message,
113 113 detailed_exit_code,
114 114 hint,
115 115 ),
116 116 _ => CommandError::abort(error.to_string()),
117 117 }
118 118 }
119 119 }
120 120
121 121 impl From<ConfigValueParseError> for CommandError {
122 122 fn from(error: ConfigValueParseError) -> Self {
123 123 CommandError::abort_with_exit_code(
124 124 error.to_string(),
125 125 exit_codes::CONFIG_ERROR_ABORT,
126 126 )
127 127 }
128 128 }
129 129
130 130 impl From<UiError> for CommandError {
131 131 fn from(_error: UiError) -> Self {
132 132 // If we already failed writing to stdout or stderr,
133 133 // writing an error message to stderr about it would be likely to fail
134 134 // too.
135 135 CommandError::abort("")
136 136 }
137 137 }
138 138
139 139 impl From<RepoError> for CommandError {
140 140 fn from(error: RepoError) -> Self {
141 141 match error {
142 142 RepoError::NotFound { at } => {
143 143 CommandError::abort_with_exit_code_bytes(
144 144 format_bytes!(
145 145 b"abort: repository {} not found",
146 146 get_bytes_from_path(at)
147 147 ),
148 148 exit_codes::ABORT,
149 149 )
150 150 }
151 151 RepoError::ConfigParseError(error) => error.into(),
152 152 RepoError::Other(error) => error.into(),
153 153 }
154 154 }
155 155 }
156 156
157 157 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
158 158 fn from(error: &'a NoRepoInCwdError) -> Self {
159 159 let NoRepoInCwdError { cwd } = error;
160 160 CommandError::abort_with_exit_code_bytes(
161 161 format_bytes!(
162 162 b"abort: no repository found in '{}' (.hg not found)!",
163 163 get_bytes_from_path(cwd)
164 164 ),
165 165 exit_codes::ABORT,
166 166 )
167 167 }
168 168 }
169 169
170 170 impl From<ConfigError> for CommandError {
171 171 fn from(error: ConfigError) -> Self {
172 172 match error {
173 173 ConfigError::Parse(error) => error.into(),
174 174 ConfigError::Other(error) => error.into(),
175 175 }
176 176 }
177 177 }
178 178
179 179 impl From<ConfigParseError> for CommandError {
180 180 fn from(error: ConfigParseError) -> Self {
181 181 let ConfigParseError {
182 182 origin,
183 183 line,
184 184 message,
185 185 } = error;
186 186 let line_message = if let Some(line_number) = line {
187 187 format_bytes!(b":{}", line_number.to_string().into_bytes())
188 188 } else {
189 189 Vec::new()
190 190 };
191 191 CommandError::abort_with_exit_code_bytes(
192 192 format_bytes!(
193 193 b"config error at {}{}: {}",
194 194 origin,
195 195 line_message,
196 196 message
197 197 ),
198 198 exit_codes::CONFIG_ERROR_ABORT,
199 199 )
200 200 }
201 201 }
202 202
203 203 impl From<(RevlogError, &str)> for CommandError {
204 204 fn from((err, rev): (RevlogError, &str)) -> CommandError {
205 205 match err {
206 206 RevlogError::WDirUnsupported => CommandError::abort(
207 207 "abort: working directory revision cannot be specified",
208 208 ),
209 209 RevlogError::InvalidRevision => CommandError::abort(format!(
210 210 "abort: invalid revision identifier: {}",
211 211 rev
212 212 )),
213 213 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
214 214 "abort: ambiguous revision identifier: {}",
215 215 rev
216 216 )),
217 217 RevlogError::Other(error) => error.into(),
218 218 }
219 219 }
220 220 }
221 221
222 222 impl From<StatusError> for CommandError {
223 223 fn from(error: StatusError) -> Self {
224 224 match error {
225 225 StatusError::Pattern(_) => {
226 226 CommandError::unsupported(format!("{}", error))
227 227 }
228 228 _ => CommandError::abort(format!("{}", error)),
229 229 }
230 230 }
231 231 }
232 232
233 233 impl From<DirstateMapError> for CommandError {
234 234 fn from(error: DirstateMapError) -> Self {
235 235 CommandError::abort(format!("{}", error))
236 236 }
237 237 }
238 238
239 239 impl From<DirstateError> for CommandError {
240 240 fn from(error: DirstateError) -> Self {
241 241 match error {
242 242 DirstateError::Common(error) => error.into(),
243 243 DirstateError::Map(error) => error.into(),
244 244 }
245 245 }
246 246 }
247 247
248 248 impl From<DirstateV2ParseError> for CommandError {
249 249 fn from(error: DirstateV2ParseError) -> Self {
250 250 HgError::from(error).into()
251 251 }
252 252 }
253 253
254 254 impl From<SparseConfigError> for CommandError {
255 255 fn from(e: SparseConfigError) -> Self {
256 256 match e {
257 257 SparseConfigError::IncludesAfterExcludes { context } => {
258 258 Self::abort_with_exit_code_bytes(
259 259 format_bytes!(
260 260 b"{} config cannot have includes after excludes",
261 261 context
262 262 ),
263 263 exit_codes::CONFIG_PARSE_ERROR_ABORT,
264 264 )
265 265 }
266 266 SparseConfigError::EntryOutsideSection { context, line } => {
267 267 Self::abort_with_exit_code_bytes(
268 268 format_bytes!(
269 269 b"{} config entry outside of section: {}",
270 270 context,
271 271 &line,
272 272 ),
273 273 exit_codes::CONFIG_PARSE_ERROR_ABORT,
274 274 )
275 275 }
276 276 SparseConfigError::InvalidNarrowPrefix(prefix) => {
277 277 Self::abort_with_exit_code_bytes(
278 278 format_bytes!(
279 279 b"invalid prefix on narrow pattern: {}",
280 280 &prefix
281 281 ),
282 282 exit_codes::ABORT,
283 283 )
284 284 }
285 285 SparseConfigError::IncludesInNarrow => Self::abort(
286 286 "including other spec files using '%include' \
287 287 is not supported in narrowspec",
288 288 ),
289 289 SparseConfigError::HgError(e) => Self::from(e),
290 290 SparseConfigError::PatternError(e) => {
291 291 Self::unsupported(format!("{}", e))
292 292 }
293 293 }
294 294 }
295 295 }
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (642 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now