##// END OF EJS Templates
rust: use NodePrefix::from_hex instead of hex::decode directly...
Simon Sapin -
r46647:88e741bf default
parent child Browse files
Show More
@@ -1,172 +1,176 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use std::convert::From;
9 9 use std::path::{Path, PathBuf};
10 10
11 11 use crate::revlog::changelog::Changelog;
12 12 use crate::revlog::manifest::{Manifest, ManifestEntry};
13 13 use crate::revlog::path_encode::path_encode;
14 14 use crate::revlog::revlog::Revlog;
15 15 use crate::revlog::revlog::RevlogError;
16 use crate::revlog::Node;
17 use crate::revlog::NodePrefix;
16 18 use crate::revlog::Revision;
17 19 use crate::utils::files::get_path_from_bytes;
18 20 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 21
20 22 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
21 23
22 24 /// Kind of error encountered by `CatRev`
23 25 #[derive(Debug)]
24 26 pub enum CatRevErrorKind {
25 27 /// Error when reading a `revlog` file.
26 28 IoError(std::io::Error),
27 29 /// The revision has not been found.
28 30 InvalidRevision,
29 31 /// Found more than one revision whose ID match the requested prefix
30 32 AmbiguousPrefix,
31 33 /// A `revlog` file is corrupted.
32 34 CorruptedRevlog,
33 35 /// The `revlog` format version is not supported.
34 36 UnsuportedRevlogVersion(u16),
35 37 /// The `revlog` data format is not supported.
36 38 UnknowRevlogDataFormat(u8),
37 39 }
38 40
39 41 /// A `CatRev` error
40 42 #[derive(Debug)]
41 43 pub struct CatRevError {
42 44 /// Kind of error encountered by `CatRev`
43 45 pub kind: CatRevErrorKind,
44 46 }
45 47
46 48 impl From<CatRevErrorKind> for CatRevError {
47 49 fn from(kind: CatRevErrorKind) -> Self {
48 50 CatRevError { kind }
49 51 }
50 52 }
51 53
52 54 impl From<RevlogError> for CatRevError {
53 55 fn from(err: RevlogError) -> Self {
54 56 match err {
55 57 RevlogError::IoError(err) => CatRevErrorKind::IoError(err),
56 58 RevlogError::UnsuportedVersion(version) => {
57 59 CatRevErrorKind::UnsuportedRevlogVersion(version)
58 60 }
59 61 RevlogError::InvalidRevision => CatRevErrorKind::InvalidRevision,
60 62 RevlogError::AmbiguousPrefix => CatRevErrorKind::AmbiguousPrefix,
61 63 RevlogError::Corrupted => CatRevErrorKind::CorruptedRevlog,
62 64 RevlogError::UnknowDataFormat(format) => {
63 65 CatRevErrorKind::UnknowRevlogDataFormat(format)
64 66 }
65 67 }
66 68 .into()
67 69 }
68 70 }
69 71
70 72 /// List files under Mercurial control at a given revision.
71 73 pub struct CatRev<'a> {
72 74 root: &'a PathBuf,
73 75 /// The revision to cat the files from.
74 76 rev: &'a str,
75 77 /// The files to output.
76 78 files: &'a [HgPathBuf],
77 79 /// The changelog file
78 80 changelog: Changelog,
79 81 /// The manifest file
80 82 manifest: Manifest,
81 83 /// The manifest entry corresponding to the revision.
82 84 ///
83 85 /// Used to hold the owner of the returned references.
84 86 manifest_entry: Option<ManifestEntry>,
85 87 }
86 88
87 89 impl<'a> CatRev<'a> {
88 90 pub fn new(
89 91 root: &'a PathBuf,
90 92 rev: &'a str,
91 93 files: &'a [HgPathBuf],
92 94 ) -> Result<Self, CatRevError> {
93 95 let changelog = Changelog::open(&root)?;
94 96 let manifest = Manifest::open(&root)?;
95 97 let manifest_entry = None;
96 98
97 99 Ok(Self {
98 100 root,
99 101 rev,
100 102 files,
101 103 changelog,
102 104 manifest,
103 105 manifest_entry,
104 106 })
105 107 }
106 108
107 109 pub fn run(&mut self) -> Result<Vec<u8>, CatRevError> {
108 110 let changelog_entry = match self.rev.parse::<Revision>() {
109 111 Ok(rev) => self.changelog.get_rev(rev)?,
110 112 _ => {
111 let changelog_node = hex::decode(&self.rev)
113 let changelog_node = NodePrefix::from_hex(&self.rev)
112 114 .map_err(|_| CatRevErrorKind::InvalidRevision)?;
113 self.changelog.get_node(&changelog_node)?
115 self.changelog.get_node(changelog_node.borrow())?
114 116 }
115 117 };
116 let manifest_node = hex::decode(&changelog_entry.manifest_node()?)
118 let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?)
117 119 .map_err(|_| CatRevErrorKind::CorruptedRevlog)?;
118 120
119 self.manifest_entry = Some(self.manifest.get_node(&manifest_node)?);
121 self.manifest_entry =
122 Some(self.manifest.get_node((&manifest_node).into())?);
120 123 if let Some(ref manifest_entry) = self.manifest_entry {
121 124 let mut bytes = vec![];
122 125
123 126 for (manifest_file, node_bytes) in
124 127 manifest_entry.files_with_nodes()
125 128 {
126 129 for cat_file in self.files.iter() {
127 130 if cat_file.as_bytes() == manifest_file.as_bytes() {
128 131 let index_path =
129 132 store_path(self.root, manifest_file, b".i");
130 133 let data_path =
131 134 store_path(self.root, manifest_file, b".d");
132 135
133 136 let file_log =
134 137 Revlog::open(&index_path, Some(&data_path))?;
135 let file_node = hex::decode(&node_bytes)
138 let file_node = Node::from_hex(node_bytes)
136 139 .map_err(|_| CatRevErrorKind::CorruptedRevlog)?;
137 let file_rev = file_log.get_node_rev(&file_node)?;
140 let file_rev =
141 file_log.get_node_rev((&file_node).into())?;
138 142 let data = file_log.get_rev_data(file_rev)?;
139 143 if data.starts_with(&METADATA_DELIMITER) {
140 144 let end_delimiter_position = data
141 145 [METADATA_DELIMITER.len()..]
142 146 .windows(METADATA_DELIMITER.len())
143 147 .position(|bytes| bytes == METADATA_DELIMITER);
144 148 if let Some(position) = end_delimiter_position {
145 149 let offset = METADATA_DELIMITER.len() * 2;
146 150 bytes.extend(data[position + offset..].iter());
147 151 }
148 152 } else {
149 153 bytes.extend(data);
150 154 }
151 155 }
152 156 }
153 157 }
154 158
155 159 Ok(bytes)
156 160 } else {
157 161 unreachable!("manifest_entry should have been stored");
158 162 }
159 163 }
160 164 }
161 165
162 166 fn store_path(root: &Path, hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
163 167 let encoded_bytes =
164 168 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
165 169 [
166 170 root,
167 171 &Path::new(".hg/store/"),
168 172 get_path_from_bytes(&encoded_bytes),
169 173 ]
170 174 .iter()
171 175 .collect()
172 176 }
@@ -1,119 +1,120 b''
1 1 // debugdata.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use super::find_root;
9 9 use crate::revlog::revlog::{Revlog, RevlogError};
10 use crate::revlog::NodePrefix;
10 11 use crate::revlog::Revision;
11 12
12 13 /// Kind of data to debug
13 14 #[derive(Debug, Copy, Clone)]
14 15 pub enum DebugDataKind {
15 16 Changelog,
16 17 Manifest,
17 18 }
18 19
19 20 /// Kind of error encountered by DebugData
20 21 #[derive(Debug)]
21 22 pub enum DebugDataErrorKind {
22 23 FindRootError(find_root::FindRootError),
23 24 /// Error when reading a `revlog` file.
24 25 IoError(std::io::Error),
25 26 /// The revision has not been found.
26 27 InvalidRevision,
27 28 /// Found more than one revision whose ID match the requested prefix
28 29 AmbiguousPrefix,
29 30 /// A `revlog` file is corrupted.
30 31 CorruptedRevlog,
31 32 /// The `revlog` format version is not supported.
32 33 UnsuportedRevlogVersion(u16),
33 34 /// The `revlog` data format is not supported.
34 35 UnknowRevlogDataFormat(u8),
35 36 }
36 37
37 38 /// A DebugData error
38 39 #[derive(Debug)]
39 40 pub struct DebugDataError {
40 41 /// Kind of error encountered by DebugData
41 42 pub kind: DebugDataErrorKind,
42 43 }
43 44
44 45 impl From<DebugDataErrorKind> for DebugDataError {
45 46 fn from(kind: DebugDataErrorKind) -> Self {
46 47 DebugDataError { kind }
47 48 }
48 49 }
49 50
50 51 impl From<find_root::FindRootError> for DebugDataError {
51 52 fn from(err: find_root::FindRootError) -> Self {
52 53 let kind = DebugDataErrorKind::FindRootError(err);
53 54 DebugDataError { kind }
54 55 }
55 56 }
56 57
57 58 impl From<std::io::Error> for DebugDataError {
58 59 fn from(err: std::io::Error) -> Self {
59 60 let kind = DebugDataErrorKind::IoError(err);
60 61 DebugDataError { kind }
61 62 }
62 63 }
63 64
64 65 impl From<RevlogError> for DebugDataError {
65 66 fn from(err: RevlogError) -> Self {
66 67 match err {
67 68 RevlogError::IoError(err) => DebugDataErrorKind::IoError(err),
68 69 RevlogError::UnsuportedVersion(version) => {
69 70 DebugDataErrorKind::UnsuportedRevlogVersion(version)
70 71 }
71 72 RevlogError::InvalidRevision => {
72 73 DebugDataErrorKind::InvalidRevision
73 74 }
74 75 RevlogError::AmbiguousPrefix => {
75 76 DebugDataErrorKind::AmbiguousPrefix
76 77 }
77 78 RevlogError::Corrupted => DebugDataErrorKind::CorruptedRevlog,
78 79 RevlogError::UnknowDataFormat(format) => {
79 80 DebugDataErrorKind::UnknowRevlogDataFormat(format)
80 81 }
81 82 }
82 83 .into()
83 84 }
84 85 }
85 86
86 87 /// Dump the contents data of a revision.
87 88 pub struct DebugData<'a> {
88 89 /// Revision or hash of the revision.
89 90 rev: &'a str,
90 91 /// Kind of data to debug.
91 92 kind: DebugDataKind,
92 93 }
93 94
94 95 impl<'a> DebugData<'a> {
95 96 pub fn new(rev: &'a str, kind: DebugDataKind) -> Self {
96 97 DebugData { rev, kind }
97 98 }
98 99
99 100 pub fn run(&mut self) -> Result<Vec<u8>, DebugDataError> {
100 101 let root = find_root::FindRoot::new().run()?;
101 102 let index_file = match self.kind {
102 103 DebugDataKind::Changelog => root.join(".hg/store/00changelog.i"),
103 104 DebugDataKind::Manifest => root.join(".hg/store/00manifest.i"),
104 105 };
105 106 let revlog = Revlog::open(&index_file, None)?;
106 107
107 108 let data = match self.rev.parse::<Revision>() {
108 109 Ok(rev) => revlog.get_rev_data(rev)?,
109 110 _ => {
110 let node = hex::decode(&self.rev)
111 let node = NodePrefix::from_hex(&self.rev)
111 112 .map_err(|_| DebugDataErrorKind::InvalidRevision)?;
112 let rev = revlog.get_node_rev(&node)?;
113 let rev = revlog.get_node_rev(node.borrow())?;
113 114 revlog.get_rev_data(rev)?
114 115 }
115 116 };
116 117
117 118 Ok(data)
118 119 }
119 120 }
@@ -1,192 +1,194 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate;
9 9 use crate::revlog::changelog::Changelog;
10 10 use crate::revlog::manifest::{Manifest, ManifestEntry};
11 use crate::revlog::node::{Node, NodePrefix};
11 12 use crate::revlog::revlog::RevlogError;
12 13 use crate::revlog::Revision;
13 14 use crate::utils::hg_path::HgPath;
14 15 use crate::{DirstateParseError, EntryState};
15 16 use rayon::prelude::*;
16 17 use std::convert::From;
17 18 use std::fs;
18 19 use std::path::PathBuf;
19 20
20 21 /// Kind of error encountered by `ListDirstateTrackedFiles`
21 22 #[derive(Debug)]
22 23 pub enum ListDirstateTrackedFilesErrorKind {
23 24 /// Error when reading the `dirstate` file
24 25 IoError(std::io::Error),
25 26 /// Error when parsing the `dirstate` file
26 27 ParseError(DirstateParseError),
27 28 }
28 29
29 30 /// A `ListDirstateTrackedFiles` error
30 31 #[derive(Debug)]
31 32 pub struct ListDirstateTrackedFilesError {
32 33 /// Kind of error encountered by `ListDirstateTrackedFiles`
33 34 pub kind: ListDirstateTrackedFilesErrorKind,
34 35 }
35 36
36 37 impl From<ListDirstateTrackedFilesErrorKind>
37 38 for ListDirstateTrackedFilesError
38 39 {
39 40 fn from(kind: ListDirstateTrackedFilesErrorKind) -> Self {
40 41 ListDirstateTrackedFilesError { kind }
41 42 }
42 43 }
43 44
44 45 impl From<std::io::Error> for ListDirstateTrackedFilesError {
45 46 fn from(err: std::io::Error) -> Self {
46 47 let kind = ListDirstateTrackedFilesErrorKind::IoError(err);
47 48 ListDirstateTrackedFilesError { kind }
48 49 }
49 50 }
50 51
51 52 /// List files under Mercurial control in the working directory
52 53 /// by reading the dirstate
53 54 pub struct ListDirstateTrackedFiles {
54 55 /// The `dirstate` content.
55 56 content: Vec<u8>,
56 57 }
57 58
58 59 impl ListDirstateTrackedFiles {
59 60 pub fn new(root: &PathBuf) -> Result<Self, ListDirstateTrackedFilesError> {
60 61 let dirstate = root.join(".hg/dirstate");
61 62 let content = fs::read(&dirstate)?;
62 63 Ok(Self { content })
63 64 }
64 65
65 66 pub fn run(
66 67 &mut self,
67 68 ) -> Result<Vec<&HgPath>, ListDirstateTrackedFilesError> {
68 69 let (_, entries, _) = parse_dirstate(&self.content)
69 70 .map_err(ListDirstateTrackedFilesErrorKind::ParseError)?;
70 71 let mut files: Vec<&HgPath> = entries
71 72 .into_iter()
72 73 .filter_map(|(path, entry)| match entry.state {
73 74 EntryState::Removed => None,
74 75 _ => Some(path),
75 76 })
76 77 .collect();
77 78 files.par_sort_unstable();
78 79 Ok(files)
79 80 }
80 81 }
81 82
82 83 /// Kind of error encountered by `ListRevTrackedFiles`
83 84 #[derive(Debug)]
84 85 pub enum ListRevTrackedFilesErrorKind {
85 86 /// Error when reading a `revlog` file.
86 87 IoError(std::io::Error),
87 88 /// The revision has not been found.
88 89 InvalidRevision,
89 90 /// Found more than one revision whose ID match the requested prefix
90 91 AmbiguousPrefix,
91 92 /// A `revlog` file is corrupted.
92 93 CorruptedRevlog,
93 94 /// The `revlog` format version is not supported.
94 95 UnsuportedRevlogVersion(u16),
95 96 /// The `revlog` data format is not supported.
96 97 UnknowRevlogDataFormat(u8),
97 98 }
98 99
99 100 /// A `ListRevTrackedFiles` error
100 101 #[derive(Debug)]
101 102 pub struct ListRevTrackedFilesError {
102 103 /// Kind of error encountered by `ListRevTrackedFiles`
103 104 pub kind: ListRevTrackedFilesErrorKind,
104 105 }
105 106
106 107 impl From<ListRevTrackedFilesErrorKind> for ListRevTrackedFilesError {
107 108 fn from(kind: ListRevTrackedFilesErrorKind) -> Self {
108 109 ListRevTrackedFilesError { kind }
109 110 }
110 111 }
111 112
112 113 impl From<RevlogError> for ListRevTrackedFilesError {
113 114 fn from(err: RevlogError) -> Self {
114 115 match err {
115 116 RevlogError::IoError(err) => {
116 117 ListRevTrackedFilesErrorKind::IoError(err)
117 118 }
118 119 RevlogError::UnsuportedVersion(version) => {
119 120 ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version)
120 121 }
121 122 RevlogError::InvalidRevision => {
122 123 ListRevTrackedFilesErrorKind::InvalidRevision
123 124 }
124 125 RevlogError::AmbiguousPrefix => {
125 126 ListRevTrackedFilesErrorKind::AmbiguousPrefix
126 127 }
127 128 RevlogError::Corrupted => {
128 129 ListRevTrackedFilesErrorKind::CorruptedRevlog
129 130 }
130 131 RevlogError::UnknowDataFormat(format) => {
131 132 ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format)
132 133 }
133 134 }
134 135 .into()
135 136 }
136 137 }
137 138
138 139 /// List files under Mercurial control at a given revision.
139 140 pub struct ListRevTrackedFiles<'a> {
140 141 /// The revision to list the files from.
141 142 rev: &'a str,
142 143 /// The changelog file
143 144 changelog: Changelog,
144 145 /// The manifest file
145 146 manifest: Manifest,
146 147 /// The manifest entry corresponding to the revision.
147 148 ///
148 149 /// Used to hold the owner of the returned references.
149 150 manifest_entry: Option<ManifestEntry>,
150 151 }
151 152
152 153 impl<'a> ListRevTrackedFiles<'a> {
153 154 pub fn new(
154 155 root: &PathBuf,
155 156 rev: &'a str,
156 157 ) -> Result<Self, ListRevTrackedFilesError> {
157 158 let changelog = Changelog::open(&root)?;
158 159 let manifest = Manifest::open(&root)?;
159 160
160 161 Ok(Self {
161 162 rev,
162 163 changelog,
163 164 manifest,
164 165 manifest_entry: None,
165 166 })
166 167 }
167 168
168 169 pub fn run(
169 170 &mut self,
170 171 ) -> Result<impl Iterator<Item = &HgPath>, ListRevTrackedFilesError> {
171 172 let changelog_entry = match self.rev.parse::<Revision>() {
172 173 Ok(rev) => self.changelog.get_rev(rev)?,
173 174 _ => {
174 let changelog_node = hex::decode(&self.rev)
175 let changelog_node = NodePrefix::from_hex(&self.rev)
175 176 .or(Err(ListRevTrackedFilesErrorKind::InvalidRevision))?;
176 self.changelog.get_node(&changelog_node)?
177 self.changelog.get_node(changelog_node.borrow())?
177 178 }
178 179 };
179 let manifest_node = hex::decode(&changelog_entry.manifest_node()?)
180 let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?)
180 181 .or(Err(ListRevTrackedFilesErrorKind::CorruptedRevlog))?;
181 182
182 self.manifest_entry = Some(self.manifest.get_node(&manifest_node)?);
183 self.manifest_entry =
184 Some(self.manifest.get_node((&manifest_node).into())?);
183 185
184 186 if let Some(ref manifest_entry) = self.manifest_entry {
185 187 Ok(manifest_entry.files())
186 188 } else {
187 189 panic!(
188 190 "manifest entry should have been stored in self.manifest_node to ensure its lifetime since references are returned from it"
189 191 )
190 192 }
191 193 }
192 194 }
@@ -1,58 +1,59 b''
1 1 use crate::revlog::revlog::{Revlog, RevlogError};
2 use crate::revlog::NodePrefixRef;
2 3 use crate::revlog::Revision;
3 4 use std::path::PathBuf;
4 5
5 6 /// A specialized `Revlog` to work with `changelog` data format.
6 7 pub struct Changelog {
7 8 /// The generic `revlog` format.
8 9 revlog: Revlog,
9 10 }
10 11
11 12 impl Changelog {
12 13 /// Open the `changelog` of a repository given by its root.
13 14 pub fn open(root: &PathBuf) -> Result<Self, RevlogError> {
14 15 let index_file = root.join(".hg/store/00changelog.i");
15 16 let revlog = Revlog::open(&index_file, None)?;
16 17 Ok(Self { revlog })
17 18 }
18 19
19 20 /// Return the `ChangelogEntry` a given node id.
20 21 pub fn get_node(
21 22 &self,
22 node: &[u8],
23 node: NodePrefixRef,
23 24 ) -> Result<ChangelogEntry, RevlogError> {
24 25 let rev = self.revlog.get_node_rev(node)?;
25 26 self.get_rev(rev)
26 27 }
27 28
28 29 /// Return the `ChangelogEntry` of a given node revision.
29 30 pub fn get_rev(
30 31 &self,
31 32 rev: Revision,
32 33 ) -> Result<ChangelogEntry, RevlogError> {
33 34 let bytes = self.revlog.get_rev_data(rev)?;
34 35 Ok(ChangelogEntry { bytes })
35 36 }
36 37 }
37 38
38 39 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
39 40 #[derive(Debug)]
40 41 pub struct ChangelogEntry {
41 42 /// The data bytes of the `changelog` entry.
42 43 bytes: Vec<u8>,
43 44 }
44 45
45 46 impl ChangelogEntry {
46 47 /// Return an iterator over the lines of the entry.
47 48 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
48 49 self.bytes
49 50 .split(|b| b == &b'\n')
50 51 .filter(|line| !line.is_empty())
51 52 }
52 53
53 54 /// Return the node id of the `manifest` referenced by this `changelog`
54 55 /// entry.
55 56 pub fn manifest_node(&self) -> Result<&[u8], RevlogError> {
56 57 self.lines().next().ok_or(RevlogError::Corrupted)
57 58 }
58 59 }
@@ -1,390 +1,392 b''
1 use std::convert::TryInto;
1 2 use std::ops::Deref;
2 3
3 4 use byteorder::{BigEndian, ByteOrder};
4 5
6 use crate::revlog::node::Node;
5 7 use crate::revlog::revlog::RevlogError;
6 8 use crate::revlog::{Revision, NULL_REVISION};
7 9
8 10 pub const INDEX_ENTRY_SIZE: usize = 64;
9 11
10 12 /// A Revlog index
11 13 pub struct Index {
12 14 bytes: Box<dyn Deref<Target = [u8]> + Send>,
13 15 /// Offsets of starts of index blocks.
14 16 /// Only needed when the index is interleaved with data.
15 17 offsets: Option<Vec<usize>>,
16 18 }
17 19
18 20 impl Index {
19 21 /// Create an index from bytes.
20 22 /// Calculate the start of each entry when is_inline is true.
21 23 pub fn new(
22 24 bytes: Box<dyn Deref<Target = [u8]> + Send>,
23 25 ) -> Result<Self, RevlogError> {
24 26 if is_inline(&bytes) {
25 27 let mut offset: usize = 0;
26 28 let mut offsets = Vec::new();
27 29
28 30 while offset + INDEX_ENTRY_SIZE <= bytes.len() {
29 31 offsets.push(offset);
30 32 let end = offset + INDEX_ENTRY_SIZE;
31 33 let entry = IndexEntry {
32 34 bytes: &bytes[offset..end],
33 35 offset_override: None,
34 36 };
35 37
36 38 offset += INDEX_ENTRY_SIZE + entry.compressed_len();
37 39 }
38 40
39 41 if offset == bytes.len() {
40 42 Ok(Self {
41 43 bytes,
42 44 offsets: Some(offsets),
43 45 })
44 46 } else {
45 47 Err(RevlogError::Corrupted)
46 48 }
47 49 } else {
48 50 Ok(Self {
49 51 bytes,
50 52 offsets: None,
51 53 })
52 54 }
53 55 }
54 56
55 57 /// Value of the inline flag.
56 58 pub fn is_inline(&self) -> bool {
57 59 is_inline(&self.bytes)
58 60 }
59 61
60 62 /// Return a slice of bytes if `revlog` is inline. Panic if not.
61 63 pub fn data(&self, start: usize, end: usize) -> &[u8] {
62 64 if !self.is_inline() {
63 65 panic!("tried to access data in the index of a revlog that is not inline");
64 66 }
65 67 &self.bytes[start..end]
66 68 }
67 69
68 70 /// Return number of entries of the revlog index.
69 71 pub fn len(&self) -> usize {
70 72 if let Some(offsets) = &self.offsets {
71 73 offsets.len()
72 74 } else {
73 75 self.bytes.len() / INDEX_ENTRY_SIZE
74 76 }
75 77 }
76 78
77 79 /// Returns `true` if the `Index` has zero `entries`.
78 80 pub fn is_empty(&self) -> bool {
79 81 self.len() == 0
80 82 }
81 83
82 84 /// Return the index entry corresponding to the given revision if it
83 85 /// exists.
84 86 pub fn get_entry(&self, rev: Revision) -> Option<IndexEntry> {
85 87 if rev == NULL_REVISION {
86 88 return None;
87 89 }
88 90 if let Some(offsets) = &self.offsets {
89 91 self.get_entry_inline(rev, offsets)
90 92 } else {
91 93 self.get_entry_separated(rev)
92 94 }
93 95 }
94 96
95 97 fn get_entry_inline(
96 98 &self,
97 99 rev: Revision,
98 100 offsets: &[usize],
99 101 ) -> Option<IndexEntry> {
100 102 let start = *offsets.get(rev as usize)?;
101 103 let end = start.checked_add(INDEX_ENTRY_SIZE)?;
102 104 let bytes = &self.bytes[start..end];
103 105
104 106 // See IndexEntry for an explanation of this override.
105 107 let offset_override = Some(end);
106 108
107 109 Some(IndexEntry {
108 110 bytes,
109 111 offset_override,
110 112 })
111 113 }
112 114
113 115 fn get_entry_separated(&self, rev: Revision) -> Option<IndexEntry> {
114 116 let max_rev = self.bytes.len() / INDEX_ENTRY_SIZE;
115 117 if rev as usize >= max_rev {
116 118 return None;
117 119 }
118 120 let start = rev as usize * INDEX_ENTRY_SIZE;
119 121 let end = start + INDEX_ENTRY_SIZE;
120 122 let bytes = &self.bytes[start..end];
121 123
122 124 // Override the offset of the first revision as its bytes are used
123 125 // for the index's metadata (saving space because it is always 0)
124 126 let offset_override = if rev == 0 { Some(0) } else { None };
125 127
126 128 Some(IndexEntry {
127 129 bytes,
128 130 offset_override,
129 131 })
130 132 }
131 133 }
132 134
133 135 #[derive(Debug)]
134 136 pub struct IndexEntry<'a> {
135 137 bytes: &'a [u8],
136 138 /// Allows to override the offset value of the entry.
137 139 ///
138 140 /// For interleaved index and data, the offset stored in the index
139 141 /// corresponds to the separated data offset.
140 142 /// It has to be overridden with the actual offset in the interleaved
141 143 /// index which is just after the index block.
142 144 ///
143 145 /// For separated index and data, the offset stored in the first index
144 146 /// entry is mixed with the index headers.
145 147 /// It has to be overridden with 0.
146 148 offset_override: Option<usize>,
147 149 }
148 150
149 151 impl<'a> IndexEntry<'a> {
150 152 /// Return the offset of the data.
151 153 pub fn offset(&self) -> usize {
152 154 if let Some(offset_override) = self.offset_override {
153 155 offset_override
154 156 } else {
155 157 let mut bytes = [0; 8];
156 158 bytes[2..8].copy_from_slice(&self.bytes[0..=5]);
157 159 BigEndian::read_u64(&bytes[..]) as usize
158 160 }
159 161 }
160 162
161 163 /// Return the compressed length of the data.
162 164 pub fn compressed_len(&self) -> usize {
163 165 BigEndian::read_u32(&self.bytes[8..=11]) as usize
164 166 }
165 167
166 168 /// Return the uncompressed length of the data.
167 169 pub fn uncompressed_len(&self) -> usize {
168 170 BigEndian::read_u32(&self.bytes[12..=15]) as usize
169 171 }
170 172
171 173 /// Return the revision upon which the data has been derived.
172 174 pub fn base_revision(&self) -> Revision {
173 175 // TODO Maybe return an Option when base_revision == rev?
174 176 // Requires to add rev to IndexEntry
175 177
176 178 BigEndian::read_i32(&self.bytes[16..])
177 179 }
178 180
179 181 pub fn p1(&self) -> Revision {
180 182 BigEndian::read_i32(&self.bytes[24..])
181 183 }
182 184
183 185 pub fn p2(&self) -> Revision {
184 186 BigEndian::read_i32(&self.bytes[28..])
185 187 }
186 188
187 189 /// Return the hash of revision's full text.
188 190 ///
189 191 /// Currently, SHA-1 is used and only the first 20 bytes of this field
190 192 /// are used.
191 pub fn hash(&self) -> &[u8] {
192 &self.bytes[32..52]
193 pub fn hash(&self) -> &Node {
194 (&self.bytes[32..52]).try_into().unwrap()
193 195 }
194 196 }
195 197
196 198 /// Value of the inline flag.
197 199 pub fn is_inline(index_bytes: &[u8]) -> bool {
198 200 match &index_bytes[0..=1] {
199 201 [0, 0] | [0, 2] => false,
200 202 _ => true,
201 203 }
202 204 }
203 205
204 206 #[cfg(test)]
205 207 mod tests {
206 208 use super::*;
207 209
208 210 #[cfg(test)]
209 211 #[derive(Debug, Copy, Clone)]
210 212 pub struct IndexEntryBuilder {
211 213 is_first: bool,
212 214 is_inline: bool,
213 215 is_general_delta: bool,
214 216 version: u16,
215 217 offset: usize,
216 218 compressed_len: usize,
217 219 uncompressed_len: usize,
218 220 base_revision: Revision,
219 221 }
220 222
221 223 #[cfg(test)]
222 224 impl IndexEntryBuilder {
223 225 pub fn new() -> Self {
224 226 Self {
225 227 is_first: false,
226 228 is_inline: false,
227 229 is_general_delta: true,
228 230 version: 2,
229 231 offset: 0,
230 232 compressed_len: 0,
231 233 uncompressed_len: 0,
232 234 base_revision: 0,
233 235 }
234 236 }
235 237
236 238 pub fn is_first(&mut self, value: bool) -> &mut Self {
237 239 self.is_first = value;
238 240 self
239 241 }
240 242
241 243 pub fn with_inline(&mut self, value: bool) -> &mut Self {
242 244 self.is_inline = value;
243 245 self
244 246 }
245 247
246 248 pub fn with_general_delta(&mut self, value: bool) -> &mut Self {
247 249 self.is_general_delta = value;
248 250 self
249 251 }
250 252
251 253 pub fn with_version(&mut self, value: u16) -> &mut Self {
252 254 self.version = value;
253 255 self
254 256 }
255 257
256 258 pub fn with_offset(&mut self, value: usize) -> &mut Self {
257 259 self.offset = value;
258 260 self
259 261 }
260 262
261 263 pub fn with_compressed_len(&mut self, value: usize) -> &mut Self {
262 264 self.compressed_len = value;
263 265 self
264 266 }
265 267
266 268 pub fn with_uncompressed_len(&mut self, value: usize) -> &mut Self {
267 269 self.uncompressed_len = value;
268 270 self
269 271 }
270 272
271 273 pub fn with_base_revision(&mut self, value: Revision) -> &mut Self {
272 274 self.base_revision = value;
273 275 self
274 276 }
275 277
276 278 pub fn build(&self) -> Vec<u8> {
277 279 let mut bytes = Vec::with_capacity(INDEX_ENTRY_SIZE);
278 280 if self.is_first {
279 281 bytes.extend(&match (self.is_general_delta, self.is_inline) {
280 282 (false, false) => [0u8, 0],
281 283 (false, true) => [0u8, 1],
282 284 (true, false) => [0u8, 2],
283 285 (true, true) => [0u8, 3],
284 286 });
285 287 bytes.extend(&self.version.to_be_bytes());
286 288 // Remaining offset bytes.
287 289 bytes.extend(&[0u8; 2]);
288 290 } else {
289 291 // Offset is only 6 bytes will usize is 8.
290 292 bytes.extend(&self.offset.to_be_bytes()[2..]);
291 293 }
292 294 bytes.extend(&[0u8; 2]); // Revision flags.
293 295 bytes.extend(&self.compressed_len.to_be_bytes()[4..]);
294 296 bytes.extend(&self.uncompressed_len.to_be_bytes()[4..]);
295 297 bytes.extend(&self.base_revision.to_be_bytes());
296 298 bytes
297 299 }
298 300 }
299 301
300 302 #[test]
301 303 fn is_not_inline_when_no_inline_flag_test() {
302 304 let bytes = IndexEntryBuilder::new()
303 305 .is_first(true)
304 306 .with_general_delta(false)
305 307 .with_inline(false)
306 308 .build();
307 309
308 310 assert_eq!(is_inline(&bytes), false)
309 311 }
310 312
311 313 #[test]
312 314 fn is_inline_when_inline_flag_test() {
313 315 let bytes = IndexEntryBuilder::new()
314 316 .is_first(true)
315 317 .with_general_delta(false)
316 318 .with_inline(true)
317 319 .build();
318 320
319 321 assert_eq!(is_inline(&bytes), true)
320 322 }
321 323
322 324 #[test]
323 325 fn is_inline_when_inline_and_generaldelta_flags_test() {
324 326 let bytes = IndexEntryBuilder::new()
325 327 .is_first(true)
326 328 .with_general_delta(true)
327 329 .with_inline(true)
328 330 .build();
329 331
330 332 assert_eq!(is_inline(&bytes), true)
331 333 }
332 334
333 335 #[test]
334 336 fn test_offset() {
335 337 let bytes = IndexEntryBuilder::new().with_offset(1).build();
336 338 let entry = IndexEntry {
337 339 bytes: &bytes,
338 340 offset_override: None,
339 341 };
340 342
341 343 assert_eq!(entry.offset(), 1)
342 344 }
343 345
344 346 #[test]
345 347 fn test_with_overridden_offset() {
346 348 let bytes = IndexEntryBuilder::new().with_offset(1).build();
347 349 let entry = IndexEntry {
348 350 bytes: &bytes,
349 351 offset_override: Some(2),
350 352 };
351 353
352 354 assert_eq!(entry.offset(), 2)
353 355 }
354 356
355 357 #[test]
356 358 fn test_compressed_len() {
357 359 let bytes = IndexEntryBuilder::new().with_compressed_len(1).build();
358 360 let entry = IndexEntry {
359 361 bytes: &bytes,
360 362 offset_override: None,
361 363 };
362 364
363 365 assert_eq!(entry.compressed_len(), 1)
364 366 }
365 367
366 368 #[test]
367 369 fn test_uncompressed_len() {
368 370 let bytes = IndexEntryBuilder::new().with_uncompressed_len(1).build();
369 371 let entry = IndexEntry {
370 372 bytes: &bytes,
371 373 offset_override: None,
372 374 };
373 375
374 376 assert_eq!(entry.uncompressed_len(), 1)
375 377 }
376 378
377 379 #[test]
378 380 fn test_base_revision() {
379 381 let bytes = IndexEntryBuilder::new().with_base_revision(1).build();
380 382 let entry = IndexEntry {
381 383 bytes: &bytes,
382 384 offset_override: None,
383 385 };
384 386
385 387 assert_eq!(entry.base_revision(), 1)
386 388 }
387 389 }
388 390
389 391 #[cfg(test)]
390 392 pub use tests::IndexEntryBuilder;
@@ -1,73 +1,77 b''
1 1 use crate::revlog::revlog::{Revlog, RevlogError};
2 use crate::revlog::NodePrefixRef;
2 3 use crate::revlog::Revision;
3 4 use crate::utils::hg_path::HgPath;
4 5 use std::path::PathBuf;
5 6
6 7 /// A specialized `Revlog` to work with `manifest` data format.
7 8 pub struct Manifest {
8 9 /// The generic `revlog` format.
9 10 revlog: Revlog,
10 11 }
11 12
12 13 impl Manifest {
13 14 /// Open the `manifest` of a repository given by its root.
14 15 pub fn open(root: &PathBuf) -> Result<Self, RevlogError> {
15 16 let index_file = root.join(".hg/store/00manifest.i");
16 17 let revlog = Revlog::open(&index_file, None)?;
17 18 Ok(Self { revlog })
18 19 }
19 20
20 21 /// Return the `ManifestEntry` of a given node id.
21 pub fn get_node(&self, node: &[u8]) -> Result<ManifestEntry, RevlogError> {
22 pub fn get_node(
23 &self,
24 node: NodePrefixRef,
25 ) -> Result<ManifestEntry, RevlogError> {
22 26 let rev = self.revlog.get_node_rev(node)?;
23 27 self.get_rev(rev)
24 28 }
25 29
26 30 /// Return the `ManifestEntry` of a given node revision.
27 31 pub fn get_rev(
28 32 &self,
29 33 rev: Revision,
30 34 ) -> Result<ManifestEntry, RevlogError> {
31 35 let bytes = self.revlog.get_rev_data(rev)?;
32 36 Ok(ManifestEntry { bytes })
33 37 }
34 38 }
35 39
36 40 /// `Manifest` entry which knows how to interpret the `manifest` data bytes.
37 41 #[derive(Debug)]
38 42 pub struct ManifestEntry {
39 43 bytes: Vec<u8>,
40 44 }
41 45
42 46 impl ManifestEntry {
43 47 /// Return an iterator over the lines of the entry.
44 48 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
45 49 self.bytes
46 50 .split(|b| b == &b'\n')
47 51 .filter(|line| !line.is_empty())
48 52 }
49 53
50 54 /// Return an iterator over the files of the entry.
51 55 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
52 56 self.lines().filter(|line| !line.is_empty()).map(|line| {
53 57 let pos = line
54 58 .iter()
55 59 .position(|x| x == &b'\0')
56 60 .expect("manifest line should contain \\0");
57 61 HgPath::new(&line[..pos])
58 62 })
59 63 }
60 64
61 65 /// Return an iterator over the files of the entry.
62 66 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
63 67 self.lines().filter(|line| !line.is_empty()).map(|line| {
64 68 let pos = line
65 69 .iter()
66 70 .position(|x| x == &b'\0')
67 71 .expect("manifest line should contain \\0");
68 72 let hash_start = pos + 1;
69 73 let hash_end = hash_start + 40;
70 74 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
71 75 })
72 76 }
73 77 }
@@ -1,438 +1,456 b''
1 1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5
6 6 //! Definitions and utilities for Revision nodes
7 7 //!
8 8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
9 9 //! of a revision.
10 10
11 11 use hex::{self, FromHex, FromHexError};
12 use std::convert::{TryFrom, TryInto};
12 13
13 14 /// The length in bytes of a `Node`
14 15 ///
15 16 /// This constant is meant to ease refactors of this module, and
16 17 /// are private so that calling code does not expect all nodes have
17 18 /// the same size, should we support several formats concurrently in
18 19 /// the future.
19 20 pub const NODE_BYTES_LENGTH: usize = 20;
20 21
21 22 /// Id of the null node.
22 23 ///
23 24 /// Used to indicate the absence of node.
24 25 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
25 26
26 27 /// The length in bytes of a `Node`
27 28 ///
28 29 /// see also `NODES_BYTES_LENGTH` about it being private.
29 30 const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH;
30 31
31 32 /// Private alias for readability and to ease future change
32 33 type NodeData = [u8; NODE_BYTES_LENGTH];
33 34
34 35 /// Binary revision SHA
35 36 ///
36 37 /// ## Future changes of hash size
37 38 ///
38 39 /// To accomodate future changes of hash size, Rust callers
39 40 /// should use the conversion methods at the boundaries (FFI, actual
40 41 /// computation of hashes and I/O) only, and only if required.
41 42 ///
42 43 /// All other callers outside of unit tests should just handle `Node` values
43 44 /// and never make any assumption on the actual length, using [`nybbles_len`]
44 45 /// if they need a loop boundary.
45 46 ///
46 47 /// All methods that create a `Node` either take a type that enforces
47 48 /// the size or fail immediately at runtime with [`ExactLengthRequired`].
48 49 ///
49 50 /// [`nybbles_len`]: #method.nybbles_len
50 51 /// [`ExactLengthRequired`]: struct.NodeError#variant.ExactLengthRequired
51 52 #[derive(Clone, Debug, PartialEq)]
52 53 #[repr(transparent)]
53 54 pub struct Node {
54 55 data: NodeData,
55 56 }
56 57
57 58 /// The node value for NULL_REVISION
58 59 pub const NULL_NODE: Node = Node {
59 60 data: [0; NODE_BYTES_LENGTH],
60 61 };
61 62
62 63 impl From<NodeData> for Node {
63 64 fn from(data: NodeData) -> Node {
64 65 Node { data }
65 66 }
66 67 }
67 68
69 /// Return an error if the slice has an unexpected length
70 impl<'a> TryFrom<&'a [u8]> for &'a Node {
71 type Error = std::array::TryFromSliceError;
72
73 #[inline]
74 fn try_from(bytes: &'a [u8]) -> Result<&'a Node, Self::Error> {
75 let data = bytes.try_into()?;
76 // Safety: `#[repr(transparent)]` makes it ok to "wrap" the target
77 // of a reference to the type of the single field.
78 Ok(unsafe { std::mem::transmute::<&NodeData, &Node>(data) })
79 }
80 }
81
68 82 #[derive(Debug, PartialEq)]
69 83 pub enum NodeError {
70 84 ExactLengthRequired(usize, String),
71 85 PrefixTooLong(String),
72 86 HexError(FromHexError, String),
73 87 }
74 88
75 89 /// Low level utility function, also for prefixes
76 90 fn get_nybble(s: &[u8], i: usize) -> u8 {
77 91 if i % 2 == 0 {
78 92 s[i / 2] >> 4
79 93 } else {
80 94 s[i / 2] & 0x0f
81 95 }
82 96 }
83 97
84 98 impl Node {
85 99 /// Retrieve the `i`th half-byte of the binary data.
86 100 ///
87 101 /// This is also the `i`th hexadecimal digit in numeric form,
88 102 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
89 103 pub fn get_nybble(&self, i: usize) -> u8 {
90 104 get_nybble(&self.data, i)
91 105 }
92 106
93 107 /// Length of the data, in nybbles
94 108 pub fn nybbles_len(&self) -> usize {
95 109 // public exposure as an instance method only, so that we can
96 110 // easily support several sizes of hashes if needed in the future.
97 111 NODE_NYBBLES_LENGTH
98 112 }
99 113
100 114 /// Convert from hexadecimal string representation
101 115 ///
102 116 /// Exact length is required.
103 117 ///
104 118 /// To be used in FFI and I/O only, in order to facilitate future
105 119 /// changes of hash format.
106 pub fn from_hex(hex: &str) -> Result<Node, NodeError> {
107 Ok(NodeData::from_hex(hex)
120 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, NodeError> {
121 Ok(NodeData::from_hex(hex.as_ref())
108 122 .map_err(|e| NodeError::from((e, hex)))?
109 123 .into())
110 124 }
111 125
112 126 /// Convert to hexadecimal string representation
113 127 ///
114 128 /// To be used in FFI and I/O only, in order to facilitate future
115 129 /// changes of hash format.
116 130 pub fn encode_hex(&self) -> String {
117 131 hex::encode(self.data)
118 132 }
119 133
120 134 /// Provide access to binary data
121 135 ///
122 136 /// This is needed by FFI layers, for instance to return expected
123 137 /// binary values to Python.
124 138 pub fn as_bytes(&self) -> &[u8] {
125 139 &self.data
126 140 }
127 141 }
128 142
129 impl<T: AsRef<str>> From<(FromHexError, T)> for NodeError {
143 impl<T: AsRef<[u8]>> From<(FromHexError, T)> for NodeError {
130 144 fn from(err_offender: (FromHexError, T)) -> Self {
131 145 let (err, offender) = err_offender;
146 let offender = String::from_utf8_lossy(offender.as_ref()).into_owned();
132 147 match err {
133 148 FromHexError::InvalidStringLength => {
134 NodeError::ExactLengthRequired(
135 NODE_NYBBLES_LENGTH,
136 offender.as_ref().to_owned(),
137 )
149 NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, offender)
138 150 }
139 _ => NodeError::HexError(err, offender.as_ref().to_owned()),
151 _ => NodeError::HexError(err, offender),
140 152 }
141 153 }
142 154 }
143 155
144 156 /// The beginning of a binary revision SHA.
145 157 ///
146 158 /// Since it can potentially come from an hexadecimal representation with
147 159 /// odd length, it needs to carry around whether the last 4 bits are relevant
148 160 /// or not.
149 161 #[derive(Debug, PartialEq)]
150 162 pub struct NodePrefix {
151 163 buf: Vec<u8>,
152 164 is_odd: bool,
153 165 }
154 166
155 167 impl NodePrefix {
156 168 /// Convert from hexadecimal string representation
157 169 ///
158 170 /// Similarly to `hex::decode`, can be used with Unicode string types
159 171 /// (`String`, `&str`) as well as bytes.
160 172 ///
161 173 /// To be used in FFI and I/O only, in order to facilitate future
162 174 /// changes of hash format.
163 175 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, NodeError> {
164 176 let hex = hex.as_ref();
165 177 let len = hex.len();
166 178 if len > NODE_NYBBLES_LENGTH {
167 179 return Err(NodeError::PrefixTooLong(
168 180 String::from_utf8_lossy(hex).to_owned().to_string(),
169 181 ));
170 182 }
171 183
172 184 let is_odd = len % 2 == 1;
173 185 let even_part = if is_odd { &hex[..len - 1] } else { hex };
174 let mut buf: Vec<u8> = Vec::from_hex(&even_part)
175 .map_err(|e| (e, String::from_utf8_lossy(hex)))?;
186 let mut buf: Vec<u8> =
187 Vec::from_hex(&even_part).map_err(|e| (e, hex))?;
176 188
177 189 if is_odd {
178 190 let latest_char = char::from(hex[len - 1]);
179 191 let latest_nybble = latest_char.to_digit(16).ok_or_else(|| {
180 192 (
181 193 FromHexError::InvalidHexCharacter {
182 194 c: latest_char,
183 195 index: len - 1,
184 196 },
185 String::from_utf8_lossy(hex),
197 hex,
186 198 )
187 199 })? as u8;
188 200 buf.push(latest_nybble << 4);
189 201 }
190 202 Ok(NodePrefix { buf, is_odd })
191 203 }
192 204
193 205 pub fn borrow(&self) -> NodePrefixRef {
194 206 NodePrefixRef {
195 207 buf: &self.buf,
196 208 is_odd: self.is_odd,
197 209 }
198 210 }
199 211 }
200 212
201 213 #[derive(Clone, Debug, PartialEq)]
202 214 pub struct NodePrefixRef<'a> {
203 215 buf: &'a [u8],
204 216 is_odd: bool,
205 217 }
206 218
207 219 impl<'a> NodePrefixRef<'a> {
208 220 pub fn len(&self) -> usize {
209 221 if self.is_odd {
210 222 self.buf.len() * 2 - 1
211 223 } else {
212 224 self.buf.len() * 2
213 225 }
214 226 }
215 227
216 228 pub fn is_empty(&self) -> bool {
217 229 self.len() == 0
218 230 }
219 231
220 232 pub fn is_prefix_of(&self, node: &Node) -> bool {
221 233 if self.is_odd {
222 234 let buf = self.buf;
223 235 let last_pos = buf.len() - 1;
224 236 node.data.starts_with(buf.split_at(last_pos).0)
225 237 && node.data[last_pos] >> 4 == buf[last_pos] >> 4
226 238 } else {
227 239 node.data.starts_with(self.buf)
228 240 }
229 241 }
230 242
231 243 /// Retrieve the `i`th half-byte from the prefix.
232 244 ///
233 245 /// This is also the `i`th hexadecimal digit in numeric form,
234 246 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
235 247 pub fn get_nybble(&self, i: usize) -> u8 {
236 248 assert!(i < self.len());
237 249 get_nybble(self.buf, i)
238 250 }
239 251
240 252 /// Return the index first nybble that's different from `node`
241 253 ///
242 254 /// If the return value is `None` that means that `self` is
243 255 /// a prefix of `node`, but the current method is a bit slower
244 256 /// than `is_prefix_of`.
245 257 ///
246 258 /// Returned index is as in `get_nybble`, i.e., starting at 0.
247 259 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
248 260 let buf = self.buf;
249 261 let until = if self.is_odd {
250 262 buf.len() - 1
251 263 } else {
252 264 buf.len()
253 265 };
254 266 for (i, item) in buf.iter().enumerate().take(until) {
255 267 if *item != node.data[i] {
256 268 return if *item & 0xf0 == node.data[i] & 0xf0 {
257 269 Some(2 * i + 1)
258 270 } else {
259 271 Some(2 * i)
260 272 };
261 273 }
262 274 }
263 275 if self.is_odd && buf[until] & 0xf0 != node.data[until] & 0xf0 {
264 276 Some(until * 2)
265 277 } else {
266 278 None
267 279 }
268 280 }
269 281 }
270 282
271 283 /// A shortcut for full `Node` references
272 284 impl<'a> From<&'a Node> for NodePrefixRef<'a> {
273 285 fn from(node: &'a Node) -> Self {
274 286 NodePrefixRef {
275 287 buf: &node.data,
276 288 is_odd: false,
277 289 }
278 290 }
279 291 }
280 292
293 impl PartialEq<Node> for NodePrefixRef<'_> {
294 fn eq(&self, other: &Node) -> bool {
295 !self.is_odd && self.buf == other.data
296 }
297 }
298
281 299 #[cfg(test)]
282 300 mod tests {
283 301 use super::*;
284 302
285 303 fn sample_node() -> Node {
286 304 let mut data = [0; NODE_BYTES_LENGTH];
287 305 data.copy_from_slice(&[
288 306 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
289 307 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
290 308 ]);
291 309 data.into()
292 310 }
293 311
294 312 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
295 ///
313 ///check_hash
296 314 /// The padding is made with zeros
297 315 pub fn hex_pad_right(hex: &str) -> String {
298 316 let mut res = hex.to_string();
299 317 while res.len() < NODE_NYBBLES_LENGTH {
300 318 res.push('0');
301 319 }
302 320 res
303 321 }
304 322
305 323 fn sample_node_hex() -> String {
306 324 hex_pad_right("0123456789abcdeffedcba9876543210deadbeef")
307 325 }
308 326
309 327 #[test]
310 328 fn test_node_from_hex() {
311 329 assert_eq!(Node::from_hex(&sample_node_hex()), Ok(sample_node()));
312 330
313 331 let mut short = hex_pad_right("0123");
314 332 short.pop();
315 333 short.pop();
316 334 assert_eq!(
317 335 Node::from_hex(&short),
318 336 Err(NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, short)),
319 337 );
320 338
321 339 let not_hex = hex_pad_right("012... oops");
322 340 assert_eq!(
323 341 Node::from_hex(&not_hex),
324 342 Err(NodeError::HexError(
325 343 FromHexError::InvalidHexCharacter { c: '.', index: 3 },
326 344 not_hex,
327 345 )),
328 346 );
329 347 }
330 348
331 349 #[test]
332 350 fn test_node_encode_hex() {
333 351 assert_eq!(sample_node().encode_hex(), sample_node_hex());
334 352 }
335 353
336 354 #[test]
337 355 fn test_prefix_from_hex() -> Result<(), NodeError> {
338 356 assert_eq!(
339 357 NodePrefix::from_hex("0e1")?,
340 358 NodePrefix {
341 359 buf: vec![14, 16],
342 360 is_odd: true
343 361 }
344 362 );
345 363 assert_eq!(
346 364 NodePrefix::from_hex("0e1a")?,
347 365 NodePrefix {
348 366 buf: vec![14, 26],
349 367 is_odd: false
350 368 }
351 369 );
352 370
353 371 // checking limit case
354 372 let node_as_vec = sample_node().data.iter().cloned().collect();
355 373 assert_eq!(
356 374 NodePrefix::from_hex(sample_node_hex())?,
357 375 NodePrefix {
358 376 buf: node_as_vec,
359 377 is_odd: false
360 378 }
361 379 );
362 380
363 381 Ok(())
364 382 }
365 383
366 384 #[test]
367 385 fn test_prefix_from_hex_errors() {
368 386 assert_eq!(
369 387 NodePrefix::from_hex("testgr"),
370 388 Err(NodeError::HexError(
371 389 FromHexError::InvalidHexCharacter { c: 't', index: 0 },
372 390 "testgr".to_string()
373 391 ))
374 392 );
375 393 let mut long = NULL_NODE.encode_hex();
376 394 long.push('c');
377 395 match NodePrefix::from_hex(&long)
378 396 .expect_err("should be refused as too long")
379 397 {
380 398 NodeError::PrefixTooLong(s) => assert_eq!(s, long),
381 399 err => panic!(format!("Should have been TooLong, got {:?}", err)),
382 400 }
383 401 }
384 402
385 403 #[test]
386 404 fn test_is_prefix_of() -> Result<(), NodeError> {
387 405 let mut node_data = [0; NODE_BYTES_LENGTH];
388 406 node_data[0] = 0x12;
389 407 node_data[1] = 0xca;
390 408 let node = Node::from(node_data);
391 409 assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node));
392 410 assert!(!NodePrefix::from_hex("1a")?.borrow().is_prefix_of(&node));
393 411 assert!(NodePrefix::from_hex("12c")?.borrow().is_prefix_of(&node));
394 412 assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node));
395 413 Ok(())
396 414 }
397 415
398 416 #[test]
399 417 fn test_get_nybble() -> Result<(), NodeError> {
400 418 let prefix = NodePrefix::from_hex("dead6789cafe")?;
401 419 assert_eq!(prefix.borrow().get_nybble(0), 13);
402 420 assert_eq!(prefix.borrow().get_nybble(7), 9);
403 421 Ok(())
404 422 }
405 423
406 424 #[test]
407 425 fn test_first_different_nybble_even_prefix() {
408 426 let prefix = NodePrefix::from_hex("12ca").unwrap();
409 427 let prefref = prefix.borrow();
410 428 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
411 429 assert_eq!(prefref.first_different_nybble(&node), Some(0));
412 430 node.data[0] = 0x13;
413 431 assert_eq!(prefref.first_different_nybble(&node), Some(1));
414 432 node.data[0] = 0x12;
415 433 assert_eq!(prefref.first_different_nybble(&node), Some(2));
416 434 node.data[1] = 0xca;
417 435 // now it is a prefix
418 436 assert_eq!(prefref.first_different_nybble(&node), None);
419 437 }
420 438
421 439 #[test]
422 440 fn test_first_different_nybble_odd_prefix() {
423 441 let prefix = NodePrefix::from_hex("12c").unwrap();
424 442 let prefref = prefix.borrow();
425 443 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
426 444 assert_eq!(prefref.first_different_nybble(&node), Some(0));
427 445 node.data[0] = 0x13;
428 446 assert_eq!(prefref.first_different_nybble(&node), Some(1));
429 447 node.data[0] = 0x12;
430 448 assert_eq!(prefref.first_different_nybble(&node), Some(2));
431 449 node.data[1] = 0xca;
432 450 // now it is a prefix
433 451 assert_eq!(prefref.first_different_nybble(&node), None);
434 452 }
435 453 }
436 454
437 455 #[cfg(test)]
438 456 pub use tests::hex_pad_right;
@@ -1,344 +1,347 b''
1 1 use std::borrow::Cow;
2 2 use std::fs::File;
3 3 use std::io::Read;
4 4 use std::ops::Deref;
5 5 use std::path::Path;
6 6
7 7 use byteorder::{BigEndian, ByteOrder};
8 8 use crypto::digest::Digest;
9 9 use crypto::sha1::Sha1;
10 10 use flate2::read::ZlibDecoder;
11 11 use memmap::{Mmap, MmapOptions};
12 12 use micro_timer::timed;
13 13 use zstd;
14 14
15 15 use super::index::Index;
16 use super::node::{NODE_BYTES_LENGTH, NULL_NODE_ID};
16 use super::node::{NodePrefixRef, NODE_BYTES_LENGTH, NULL_NODE};
17 17 use super::patch;
18 18 use crate::revlog::Revision;
19 19
20 20 pub enum RevlogError {
21 21 IoError(std::io::Error),
22 22 UnsuportedVersion(u16),
23 23 InvalidRevision,
24 24 /// Found more than one entry whose ID match the requested prefix
25 25 AmbiguousPrefix,
26 26 Corrupted,
27 27 UnknowDataFormat(u8),
28 28 }
29 29
30 30 fn mmap_open(path: &Path) -> Result<Mmap, std::io::Error> {
31 31 let file = File::open(path)?;
32 32 let mmap = unsafe { MmapOptions::new().map(&file) }?;
33 33 Ok(mmap)
34 34 }
35 35
36 36 /// Read only implementation of revlog.
37 37 pub struct Revlog {
38 38 /// When index and data are not interleaved: bytes of the revlog index.
39 39 /// When index and data are interleaved: bytes of the revlog index and
40 40 /// data.
41 41 index: Index,
42 42 /// When index and data are not interleaved: bytes of the revlog data
43 43 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
44 44 }
45 45
46 46 impl Revlog {
47 47 /// Open a revlog index file.
48 48 ///
49 49 /// It will also open the associated data file if index and data are not
50 50 /// interleaved.
51 51 #[timed]
52 52 pub fn open(
53 53 index_path: &Path,
54 54 data_path: Option<&Path>,
55 55 ) -> Result<Self, RevlogError> {
56 56 let index_mmap =
57 57 mmap_open(&index_path).map_err(RevlogError::IoError)?;
58 58
59 59 let version = get_version(&index_mmap);
60 60 if version != 1 {
61 61 return Err(RevlogError::UnsuportedVersion(version));
62 62 }
63 63
64 64 let index = Index::new(Box::new(index_mmap))?;
65 65
66 66 let default_data_path = index_path.with_extension("d");
67 67
68 68 // type annotation required
69 69 // won't recognize Mmap as Deref<Target = [u8]>
70 70 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
71 71 if index.is_inline() {
72 72 None
73 73 } else {
74 74 let data_path = data_path.unwrap_or(&default_data_path);
75 75 let data_mmap =
76 76 mmap_open(data_path).map_err(RevlogError::IoError)?;
77 77 Some(Box::new(data_mmap))
78 78 };
79 79
80 80 Ok(Revlog { index, data_bytes })
81 81 }
82 82
83 83 /// Return number of entries of the `Revlog`.
84 84 pub fn len(&self) -> usize {
85 85 self.index.len()
86 86 }
87 87
88 88 /// Returns `true` if the `Revlog` has zero `entries`.
89 89 pub fn is_empty(&self) -> bool {
90 90 self.index.is_empty()
91 91 }
92 92
93 93 /// Return the full data associated to a node.
94 94 #[timed]
95 pub fn get_node_rev(&self, node: &[u8]) -> Result<Revision, RevlogError> {
95 pub fn get_node_rev(
96 &self,
97 node: NodePrefixRef,
98 ) -> Result<Revision, RevlogError> {
96 99 // This is brute force. But it is fast enough for now.
97 100 // Optimization will come later.
98 101 let mut found_by_prefix = None;
99 102 for rev in (0..self.len() as Revision).rev() {
100 103 let index_entry =
101 104 self.index.get_entry(rev).ok_or(RevlogError::Corrupted)?;
102 if index_entry.hash() == node {
105 if node == *index_entry.hash() {
103 106 return Ok(rev);
104 107 }
105 if index_entry.hash().starts_with(node) {
108 if node.is_prefix_of(index_entry.hash()) {
106 109 if found_by_prefix.is_some() {
107 110 return Err(RevlogError::AmbiguousPrefix);
108 111 }
109 112 found_by_prefix = Some(rev)
110 113 }
111 114 }
112 115 found_by_prefix.ok_or(RevlogError::InvalidRevision)
113 116 }
114 117
115 118 /// Return the full data associated to a revision.
116 119 ///
117 120 /// All entries required to build the final data out of deltas will be
118 121 /// retrieved as needed, and the deltas will be applied to the inital
119 122 /// snapshot to rebuild the final data.
120 123 #[timed]
121 124 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
122 125 // Todo return -> Cow
123 126 let mut entry = self.get_entry(rev)?;
124 127 let mut delta_chain = vec![];
125 128 while let Some(base_rev) = entry.base_rev {
126 129 delta_chain.push(entry);
127 130 entry =
128 131 self.get_entry(base_rev).or(Err(RevlogError::Corrupted))?;
129 132 }
130 133
131 134 // TODO do not look twice in the index
132 135 let index_entry = self
133 136 .index
134 137 .get_entry(rev)
135 138 .ok_or(RevlogError::InvalidRevision)?;
136 139
137 140 let data: Vec<u8> = if delta_chain.is_empty() {
138 141 entry.data()?.into()
139 142 } else {
140 143 Revlog::build_data_from_deltas(entry, &delta_chain)?
141 144 };
142 145
143 146 if self.check_hash(
144 147 index_entry.p1(),
145 148 index_entry.p2(),
146 index_entry.hash(),
149 index_entry.hash().as_bytes(),
147 150 &data,
148 151 ) {
149 152 Ok(data)
150 153 } else {
151 154 Err(RevlogError::Corrupted)
152 155 }
153 156 }
154 157
155 158 /// Check the hash of some given data against the recorded hash.
156 159 pub fn check_hash(
157 160 &self,
158 161 p1: Revision,
159 162 p2: Revision,
160 163 expected: &[u8],
161 164 data: &[u8],
162 165 ) -> bool {
163 166 let e1 = self.index.get_entry(p1);
164 167 let h1 = match e1 {
165 168 Some(ref entry) => entry.hash(),
166 None => &NULL_NODE_ID,
169 None => &NULL_NODE,
167 170 };
168 171 let e2 = self.index.get_entry(p2);
169 172 let h2 = match e2 {
170 173 Some(ref entry) => entry.hash(),
171 None => &NULL_NODE_ID,
174 None => &NULL_NODE,
172 175 };
173 176
174 hash(data, &h1, &h2).as_slice() == expected
177 hash(data, h1.as_bytes(), h2.as_bytes()).as_slice() == expected
175 178 }
176 179
177 180 /// Build the full data of a revision out its snapshot
178 181 /// and its deltas.
179 182 #[timed]
180 183 fn build_data_from_deltas(
181 184 snapshot: RevlogEntry,
182 185 deltas: &[RevlogEntry],
183 186 ) -> Result<Vec<u8>, RevlogError> {
184 187 let snapshot = snapshot.data()?;
185 188 let deltas = deltas
186 189 .iter()
187 190 .rev()
188 191 .map(RevlogEntry::data)
189 192 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
190 193 let patches: Vec<_> =
191 194 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
192 195 let patch = patch::fold_patch_lists(&patches);
193 196 Ok(patch.apply(&snapshot))
194 197 }
195 198
196 199 /// Return the revlog data.
197 200 fn data(&self) -> &[u8] {
198 201 match self.data_bytes {
199 202 Some(ref data_bytes) => &data_bytes,
200 203 None => panic!(
201 204 "forgot to load the data or trying to access inline data"
202 205 ),
203 206 }
204 207 }
205 208
206 209 /// Get an entry of the revlog.
207 210 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
208 211 let index_entry = self
209 212 .index
210 213 .get_entry(rev)
211 214 .ok_or(RevlogError::InvalidRevision)?;
212 215 let start = index_entry.offset();
213 216 let end = start + index_entry.compressed_len();
214 217 let data = if self.index.is_inline() {
215 218 self.index.data(start, end)
216 219 } else {
217 220 &self.data()[start..end]
218 221 };
219 222 let entry = RevlogEntry {
220 223 rev,
221 224 bytes: data,
222 225 compressed_len: index_entry.compressed_len(),
223 226 uncompressed_len: index_entry.uncompressed_len(),
224 227 base_rev: if index_entry.base_revision() == rev {
225 228 None
226 229 } else {
227 230 Some(index_entry.base_revision())
228 231 },
229 232 };
230 233 Ok(entry)
231 234 }
232 235 }
233 236
234 237 /// The revlog entry's bytes and the necessary informations to extract
235 238 /// the entry's data.
236 239 #[derive(Debug)]
237 240 pub struct RevlogEntry<'a> {
238 241 rev: Revision,
239 242 bytes: &'a [u8],
240 243 compressed_len: usize,
241 244 uncompressed_len: usize,
242 245 base_rev: Option<Revision>,
243 246 }
244 247
245 248 impl<'a> RevlogEntry<'a> {
246 249 /// Extract the data contained in the entry.
247 250 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
248 251 if self.bytes.is_empty() {
249 252 return Ok(Cow::Borrowed(&[]));
250 253 }
251 254 match self.bytes[0] {
252 255 // Revision data is the entirety of the entry, including this
253 256 // header.
254 257 b'\0' => Ok(Cow::Borrowed(self.bytes)),
255 258 // Raw revision data follows.
256 259 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
257 260 // zlib (RFC 1950) data.
258 261 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
259 262 // zstd data.
260 263 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
261 264 format_type => Err(RevlogError::UnknowDataFormat(format_type)),
262 265 }
263 266 }
264 267
265 268 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
266 269 let mut decoder = ZlibDecoder::new(self.bytes);
267 270 if self.is_delta() {
268 271 let mut buf = Vec::with_capacity(self.compressed_len);
269 272 decoder
270 273 .read_to_end(&mut buf)
271 274 .or(Err(RevlogError::Corrupted))?;
272 275 Ok(buf)
273 276 } else {
274 277 let mut buf = vec![0; self.uncompressed_len];
275 278 decoder
276 279 .read_exact(&mut buf)
277 280 .or(Err(RevlogError::Corrupted))?;
278 281 Ok(buf)
279 282 }
280 283 }
281 284
282 285 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
283 286 if self.is_delta() {
284 287 let mut buf = Vec::with_capacity(self.compressed_len);
285 288 zstd::stream::copy_decode(self.bytes, &mut buf)
286 289 .or(Err(RevlogError::Corrupted))?;
287 290 Ok(buf)
288 291 } else {
289 292 let mut buf = vec![0; self.uncompressed_len];
290 293 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
291 294 .or(Err(RevlogError::Corrupted))?;
292 295 if len != self.uncompressed_len {
293 296 Err(RevlogError::Corrupted)
294 297 } else {
295 298 Ok(buf)
296 299 }
297 300 }
298 301 }
299 302
300 303 /// Tell if the entry is a snapshot or a delta
301 304 /// (influences on decompression).
302 305 fn is_delta(&self) -> bool {
303 306 self.base_rev.is_some()
304 307 }
305 308 }
306 309
307 310 /// Format version of the revlog.
308 311 pub fn get_version(index_bytes: &[u8]) -> u16 {
309 312 BigEndian::read_u16(&index_bytes[2..=3])
310 313 }
311 314
312 315 /// Calculate the hash of a revision given its data and its parents.
313 316 fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> {
314 317 let mut hasher = Sha1::new();
315 318 let (a, b) = (p1_hash, p2_hash);
316 319 if a > b {
317 320 hasher.input(b);
318 321 hasher.input(a);
319 322 } else {
320 323 hasher.input(a);
321 324 hasher.input(b);
322 325 }
323 326 hasher.input(data);
324 327 let mut hash = vec![0; NODE_BYTES_LENGTH];
325 328 hasher.result(&mut hash);
326 329 hash
327 330 }
328 331
329 332 #[cfg(test)]
330 333 mod tests {
331 334 use super::*;
332 335
333 336 use super::super::index::IndexEntryBuilder;
334 337
335 338 #[test]
336 339 fn version_test() {
337 340 let bytes = IndexEntryBuilder::new()
338 341 .is_first(true)
339 342 .with_version(1)
340 343 .build();
341 344
342 345 assert_eq!(get_version(&bytes), 1)
343 346 }
344 347 }
@@ -1,165 +1,167 b''
1 1 #require rust
2 2
3 3 Define an rhg function that will only run if rhg exists
4 4 $ rhg() {
5 5 > if [ -f "$RUNTESTDIR/../rust/target/debug/rhg" ]; then
6 6 > "$RUNTESTDIR/../rust/target/debug/rhg" "$@"
7 7 > else
8 8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 9 > exit 80
10 10 > fi
11 11 > }
12 12
13 13 Unimplemented command
14 14 $ rhg unimplemented-command
15 15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16 16
17 17 USAGE:
18 18 rhg <SUBCOMMAND>
19 19
20 20 For more information try --help
21 21 [252]
22 22
23 23 Finding root
24 24 $ rhg root
25 25 abort: no repository found in '$TESTTMP' (.hg not found)!
26 26 [255]
27 27
28 28 $ hg init repository
29 29 $ cd repository
30 30 $ rhg root
31 31 $TESTTMP/repository
32 32
33 33 Unwritable file descriptor
34 34 $ rhg root > /dev/full
35 35 abort: No space left on device (os error 28)
36 36 [255]
37 37
38 38 Deleted repository
39 39 $ rm -rf `pwd`
40 40 $ rhg root
41 41 abort: error getting current working directory: $ENOENT$
42 42 [255]
43 43
44 44 Listing tracked files
45 45 $ cd $TESTTMP
46 46 $ hg init repository
47 47 $ cd repository
48 48 $ for i in 1 2 3; do
49 49 > echo $i >> file$i
50 50 > hg add file$i
51 51 > done
52 52 > hg commit -m "commit $i" -q
53 53
54 54 Listing tracked files from root
55 55 $ rhg files
56 56 file1
57 57 file2
58 58 file3
59 59
60 60 Listing tracked files from subdirectory
61 61 $ mkdir -p path/to/directory
62 62 $ cd path/to/directory
63 63 $ rhg files
64 64 ../../../file1
65 65 ../../../file2
66 66 ../../../file3
67 67
68 68 Listing tracked files through broken pipe
69 69 $ rhg files | head -n 1
70 70 ../../../file1
71 71
72 72 Debuging data in inline index
73 73 $ cd $TESTTMP
74 74 $ rm -rf repository
75 75 $ hg init repository
76 76 $ cd repository
77 77 $ for i in 1 2 3 4 5 6; do
78 78 > echo $i >> file-$i
79 79 > hg add file-$i
80 80 > hg commit -m "Commit $i" -q
81 81 > done
82 82 $ rhg debugdata -c 2
83 83 8d0267cb034247ebfa5ee58ce59e22e57a492297
84 84 test
85 85 0 0
86 86 file-3
87 87
88 88 Commit 3 (no-eol)
89 89 $ rhg debugdata -m 2
90 90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93 93
94 94 Debuging with full node id
95 95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 96 d1d1c679d3053e8926061b6f45ca52009f011e3f
97 97 test
98 98 0 0
99 99 file-1
100 100
101 101 Commit 1 (no-eol)
102 102
103 103 Specifying revisions by changeset ID
104 104 $ hg log -T '{node}\n'
105 105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
106 106 d654274993d0149eecc3cc03214f598320211900
107 107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
108 108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
109 109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
110 110 6ae9681c6d30389694d8701faf24b583cf3ccafe
111 111 $ rhg files -r cf8b83
112 112 file-1
113 113 file-2
114 114 file-3
115 115 $ rhg cat -r cf8b83 file-2
116 116 2
117 117 $ rhg cat -r c file-2
118 abort: invalid revision identifier c
118 abort: ambiguous revision identifier c
119 119 [255]
120 $ rhg cat -r d file-2
121 2
120 122
121 123 Cat files
122 124 $ cd $TESTTMP
123 125 $ rm -rf repository
124 126 $ hg init repository
125 127 $ cd repository
126 128 $ echo "original content" > original
127 129 $ hg add original
128 130 $ hg commit -m "add original" original
129 131 $ rhg cat -r 0 original
130 132 original content
131 133 Cat copied file should not display copy metadata
132 134 $ hg copy original copy_of_original
133 135 $ hg commit -m "add copy of original"
134 136 $ rhg cat -r 1 copy_of_original
135 137 original content
136 138
137 139 Requirements
138 140 $ rhg debugrequirements
139 141 dotencode
140 142 fncache
141 143 generaldelta
142 144 revlogv1
143 145 sparserevlog
144 146 store
145 147
146 148 $ echo indoor-pool >> .hg/requires
147 149 $ rhg files
148 150 [252]
149 151
150 152 $ rhg cat -r 1 copy_of_original
151 153 [252]
152 154
153 155 $ rhg debugrequirements
154 156 dotencode
155 157 fncache
156 158 generaldelta
157 159 revlogv1
158 160 sparserevlog
159 161 store
160 162 indoor-pool
161 163
162 164 $ echo -e '\xFF' >> .hg/requires
163 165 $ rhg debugrequirements
164 166 abort: .hg/requires is corrupted
165 167 [255]
General Comments 0
You need to be logged in to leave comments. Login now