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