##// END OF EJS Templates
rust-changelog: removed now useless early conditional for NULL_REVISION...
Georges Racinet -
r51640:9865af71 stable
parent child Browse files
Show More
@@ -1,346 +1,343 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::revlog::Revision;
2 use crate::revlog::{Node, NodePrefix};
3 use crate::revlog::{Node, NodePrefix};
3 use crate::revlog::{Revision, NULL_REVISION};
4 use crate::revlog::{Revlog, RevlogEntry, RevlogError};
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 `ChangelogRevisionData` for the given node ID.
26 /// Return the `ChangelogRevisionData` 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 [`ChangelogEntry`] for the given revision number.
35 /// Return the [`ChangelogEntry`] for 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<ChangelogEntry, RevlogError> {
39 ) -> Result<ChangelogEntry, RevlogError> {
40 let revlog_entry = self.revlog.get_entry(rev)?;
40 let revlog_entry = self.revlog.get_entry(rev)?;
41 Ok(ChangelogEntry { revlog_entry })
41 Ok(ChangelogEntry { revlog_entry })
42 }
42 }
43
43
44 /// Return the [`ChangelogRevisionData`] for the given revision number.
44 /// Return the [`ChangelogRevisionData`] for the given revision number.
45 ///
45 ///
46 /// This is a useful shortcut in case the caller does not need the
46 /// This is a useful shortcut in case the caller does not need the
47 /// generic revlog information (parents, hashes etc). Otherwise
47 /// generic revlog information (parents, hashes etc). Otherwise
48 /// consider taking a [`ChangelogEntry`] with
48 /// consider taking a [`ChangelogEntry`] with
49 /// [entry_for_rev](`Self::entry_for_rev`) and doing everything from there.
49 /// [entry_for_rev](`Self::entry_for_rev`) and doing everything from there.
50 pub fn data_for_rev(
50 pub fn data_for_rev(
51 &self,
51 &self,
52 rev: Revision,
52 rev: Revision,
53 ) -> Result<ChangelogRevisionData, RevlogError> {
53 ) -> Result<ChangelogRevisionData, RevlogError> {
54 if rev == NULL_REVISION {
55 return Ok(ChangelogRevisionData::null());
56 }
57 self.entry_for_rev(rev)?.data()
54 self.entry_for_rev(rev)?.data()
58 }
55 }
59
56
60 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
57 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
61 self.revlog.node_from_rev(rev)
58 self.revlog.node_from_rev(rev)
62 }
59 }
63
60
64 pub fn rev_from_node(
61 pub fn rev_from_node(
65 &self,
62 &self,
66 node: NodePrefix,
63 node: NodePrefix,
67 ) -> Result<Revision, RevlogError> {
64 ) -> Result<Revision, RevlogError> {
68 self.revlog.rev_from_node(node)
65 self.revlog.rev_from_node(node)
69 }
66 }
70 }
67 }
71
68
72 /// A specialized `RevlogEntry` for `changelog` data format
69 /// A specialized `RevlogEntry` for `changelog` data format
73 ///
70 ///
74 /// This is a `RevlogEntry` with the added semantics that the associated
71 /// This is a `RevlogEntry` with the added semantics that the associated
75 /// data should meet the requirements for `changelog`, materialized by
72 /// data should meet the requirements for `changelog`, materialized by
76 /// the fact that `data()` constructs a `ChangelogRevisionData`.
73 /// the fact that `data()` constructs a `ChangelogRevisionData`.
77 /// In case that promise would be broken, the `data` method returns an error.
74 /// In case that promise would be broken, the `data` method returns an error.
78 #[derive(Clone)]
75 #[derive(Clone)]
79 pub struct ChangelogEntry<'changelog> {
76 pub struct ChangelogEntry<'changelog> {
80 /// Same data, as a generic `RevlogEntry`.
77 /// Same data, as a generic `RevlogEntry`.
81 pub(crate) revlog_entry: RevlogEntry<'changelog>,
78 pub(crate) revlog_entry: RevlogEntry<'changelog>,
82 }
79 }
83
80
84 impl<'changelog> ChangelogEntry<'changelog> {
81 impl<'changelog> ChangelogEntry<'changelog> {
85 pub fn data<'a>(
82 pub fn data<'a>(
86 &'a self,
83 &'a self,
87 ) -> Result<ChangelogRevisionData<'changelog>, RevlogError> {
84 ) -> Result<ChangelogRevisionData<'changelog>, RevlogError> {
88 let bytes = self.revlog_entry.data()?;
85 let bytes = self.revlog_entry.data()?;
89 if bytes.is_empty() {
86 if bytes.is_empty() {
90 Ok(ChangelogRevisionData::null())
87 Ok(ChangelogRevisionData::null())
91 } else {
88 } else {
92 Ok(ChangelogRevisionData::new(bytes).map_err(|err| {
89 Ok(ChangelogRevisionData::new(bytes).map_err(|err| {
93 RevlogError::Other(HgError::CorruptedRepository(format!(
90 RevlogError::Other(HgError::CorruptedRepository(format!(
94 "Invalid changelog data for revision {}: {:?}",
91 "Invalid changelog data for revision {}: {:?}",
95 self.revlog_entry.revision(),
92 self.revlog_entry.revision(),
96 err
93 err
97 )))
94 )))
98 })?)
95 })?)
99 }
96 }
100 }
97 }
101
98
102 /// Obtain a reference to the underlying `RevlogEntry`.
99 /// Obtain a reference to the underlying `RevlogEntry`.
103 ///
100 ///
104 /// This allows the caller to access the information that is common
101 /// This allows the caller to access the information that is common
105 /// to all revlog entries: revision number, node id, parent revisions etc.
102 /// to all revlog entries: revision number, node id, parent revisions etc.
106 pub fn as_revlog_entry(&self) -> &RevlogEntry {
103 pub fn as_revlog_entry(&self) -> &RevlogEntry {
107 &self.revlog_entry
104 &self.revlog_entry
108 }
105 }
109
106
110 pub fn p1_entry(&self) -> Result<Option<ChangelogEntry>, RevlogError> {
107 pub fn p1_entry(&self) -> Result<Option<ChangelogEntry>, RevlogError> {
111 Ok(self
108 Ok(self
112 .revlog_entry
109 .revlog_entry
113 .p1_entry()?
110 .p1_entry()?
114 .map(|revlog_entry| Self { revlog_entry }))
111 .map(|revlog_entry| Self { revlog_entry }))
115 }
112 }
116
113
117 pub fn p2_entry(&self) -> Result<Option<ChangelogEntry>, RevlogError> {
114 pub fn p2_entry(&self) -> Result<Option<ChangelogEntry>, RevlogError> {
118 Ok(self
115 Ok(self
119 .revlog_entry
116 .revlog_entry
120 .p2_entry()?
117 .p2_entry()?
121 .map(|revlog_entry| Self { revlog_entry }))
118 .map(|revlog_entry| Self { revlog_entry }))
122 }
119 }
123 }
120 }
124
121
125 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
122 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
126 #[derive(PartialEq)]
123 #[derive(PartialEq)]
127 pub struct ChangelogRevisionData<'changelog> {
124 pub struct ChangelogRevisionData<'changelog> {
128 /// The data bytes of the `changelog` entry.
125 /// The data bytes of the `changelog` entry.
129 bytes: Cow<'changelog, [u8]>,
126 bytes: Cow<'changelog, [u8]>,
130 /// The end offset for the hex manifest (not including the newline)
127 /// The end offset for the hex manifest (not including the newline)
131 manifest_end: usize,
128 manifest_end: usize,
132 /// The end offset for the user+email (not including the newline)
129 /// The end offset for the user+email (not including the newline)
133 user_end: usize,
130 user_end: usize,
134 /// The end offset for the timestamp+timezone+extras (not including the
131 /// The end offset for the timestamp+timezone+extras (not including the
135 /// newline)
132 /// newline)
136 timestamp_end: usize,
133 timestamp_end: usize,
137 /// The end offset for the file list (not including the newline)
134 /// The end offset for the file list (not including the newline)
138 files_end: usize,
135 files_end: usize,
139 }
136 }
140
137
141 impl<'changelog> ChangelogRevisionData<'changelog> {
138 impl<'changelog> ChangelogRevisionData<'changelog> {
142 fn new(bytes: Cow<'changelog, [u8]>) -> Result<Self, HgError> {
139 fn new(bytes: Cow<'changelog, [u8]>) -> Result<Self, HgError> {
143 let mut line_iter = bytes.split(|b| b == &b'\n');
140 let mut line_iter = bytes.split(|b| b == &b'\n');
144 let manifest_end = line_iter
141 let manifest_end = line_iter
145 .next()
142 .next()
146 .expect("Empty iterator from split()?")
143 .expect("Empty iterator from split()?")
147 .len();
144 .len();
148 let user_slice = line_iter.next().ok_or_else(|| {
145 let user_slice = line_iter.next().ok_or_else(|| {
149 HgError::corrupted("Changeset data truncated after manifest line")
146 HgError::corrupted("Changeset data truncated after manifest line")
150 })?;
147 })?;
151 let user_end = manifest_end + 1 + user_slice.len();
148 let user_end = manifest_end + 1 + user_slice.len();
152 let timestamp_slice = line_iter.next().ok_or_else(|| {
149 let timestamp_slice = line_iter.next().ok_or_else(|| {
153 HgError::corrupted("Changeset data truncated after user line")
150 HgError::corrupted("Changeset data truncated after user line")
154 })?;
151 })?;
155 let timestamp_end = user_end + 1 + timestamp_slice.len();
152 let timestamp_end = user_end + 1 + timestamp_slice.len();
156 let mut files_end = timestamp_end + 1;
153 let mut files_end = timestamp_end + 1;
157 loop {
154 loop {
158 let line = line_iter.next().ok_or_else(|| {
155 let line = line_iter.next().ok_or_else(|| {
159 HgError::corrupted("Changeset data truncated in files list")
156 HgError::corrupted("Changeset data truncated in files list")
160 })?;
157 })?;
161 if line.is_empty() {
158 if line.is_empty() {
162 if files_end == bytes.len() {
159 if files_end == bytes.len() {
163 // The list of files ended with a single newline (there
160 // The list of files ended with a single newline (there
164 // should be two)
161 // should be two)
165 return Err(HgError::corrupted(
162 return Err(HgError::corrupted(
166 "Changeset data truncated after files list",
163 "Changeset data truncated after files list",
167 ));
164 ));
168 }
165 }
169 files_end -= 1;
166 files_end -= 1;
170 break;
167 break;
171 }
168 }
172 files_end += line.len() + 1;
169 files_end += line.len() + 1;
173 }
170 }
174
171
175 Ok(Self {
172 Ok(Self {
176 bytes,
173 bytes,
177 manifest_end,
174 manifest_end,
178 user_end,
175 user_end,
179 timestamp_end,
176 timestamp_end,
180 files_end,
177 files_end,
181 })
178 })
182 }
179 }
183
180
184 fn null() -> Self {
181 fn null() -> Self {
185 Self::new(Cow::Borrowed(
182 Self::new(Cow::Borrowed(
186 b"0000000000000000000000000000000000000000\n\n0 0\n\n",
183 b"0000000000000000000000000000000000000000\n\n0 0\n\n",
187 ))
184 ))
188 .unwrap()
185 .unwrap()
189 }
186 }
190
187
191 /// Return an iterator over the lines of the entry.
188 /// Return an iterator over the lines of the entry.
192 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
189 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
193 self.bytes.split(|b| b == &b'\n')
190 self.bytes.split(|b| b == &b'\n')
194 }
191 }
195
192
196 /// Return the node id of the `manifest` referenced by this `changelog`
193 /// Return the node id of the `manifest` referenced by this `changelog`
197 /// entry.
194 /// entry.
198 pub fn manifest_node(&self) -> Result<Node, HgError> {
195 pub fn manifest_node(&self) -> Result<Node, HgError> {
199 let manifest_node_hex = &self.bytes[..self.manifest_end];
196 let manifest_node_hex = &self.bytes[..self.manifest_end];
200 Node::from_hex_for_repo(manifest_node_hex)
197 Node::from_hex_for_repo(manifest_node_hex)
201 }
198 }
202
199
203 /// The full user string (usually a name followed by an email enclosed in
200 /// The full user string (usually a name followed by an email enclosed in
204 /// angle brackets)
201 /// angle brackets)
205 pub fn user(&self) -> &[u8] {
202 pub fn user(&self) -> &[u8] {
206 &self.bytes[self.manifest_end + 1..self.user_end]
203 &self.bytes[self.manifest_end + 1..self.user_end]
207 }
204 }
208
205
209 /// The full timestamp line (timestamp in seconds, offset in seconds, and
206 /// The full timestamp line (timestamp in seconds, offset in seconds, and
210 /// possibly extras)
207 /// possibly extras)
211 // TODO: We should expose this in a more useful way
208 // TODO: We should expose this in a more useful way
212 pub fn timestamp_line(&self) -> &[u8] {
209 pub fn timestamp_line(&self) -> &[u8] {
213 &self.bytes[self.user_end + 1..self.timestamp_end]
210 &self.bytes[self.user_end + 1..self.timestamp_end]
214 }
211 }
215
212
216 /// The files changed in this revision.
213 /// The files changed in this revision.
217 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
214 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
218 self.bytes[self.timestamp_end + 1..self.files_end]
215 self.bytes[self.timestamp_end + 1..self.files_end]
219 .split(|b| b == &b'\n')
216 .split(|b| b == &b'\n')
220 .map(HgPath::new)
217 .map(HgPath::new)
221 }
218 }
222
219
223 /// The change description.
220 /// The change description.
224 pub fn description(&self) -> &[u8] {
221 pub fn description(&self) -> &[u8] {
225 &self.bytes[self.files_end + 2..]
222 &self.bytes[self.files_end + 2..]
226 }
223 }
227 }
224 }
228
225
229 impl Debug for ChangelogRevisionData<'_> {
226 impl Debug for ChangelogRevisionData<'_> {
230 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
227 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231 f.debug_struct("ChangelogRevisionData")
228 f.debug_struct("ChangelogRevisionData")
232 .field("bytes", &debug_bytes(&self.bytes))
229 .field("bytes", &debug_bytes(&self.bytes))
233 .field("manifest", &debug_bytes(&self.bytes[..self.manifest_end]))
230 .field("manifest", &debug_bytes(&self.bytes[..self.manifest_end]))
234 .field(
231 .field(
235 "user",
232 "user",
236 &debug_bytes(
233 &debug_bytes(
237 &self.bytes[self.manifest_end + 1..self.user_end],
234 &self.bytes[self.manifest_end + 1..self.user_end],
238 ),
235 ),
239 )
236 )
240 .field(
237 .field(
241 "timestamp",
238 "timestamp",
242 &debug_bytes(
239 &debug_bytes(
243 &self.bytes[self.user_end + 1..self.timestamp_end],
240 &self.bytes[self.user_end + 1..self.timestamp_end],
244 ),
241 ),
245 )
242 )
246 .field(
243 .field(
247 "files",
244 "files",
248 &debug_bytes(
245 &debug_bytes(
249 &self.bytes[self.timestamp_end + 1..self.files_end],
246 &self.bytes[self.timestamp_end + 1..self.files_end],
250 ),
247 ),
251 )
248 )
252 .field(
249 .field(
253 "description",
250 "description",
254 &debug_bytes(&self.bytes[self.files_end + 2..]),
251 &debug_bytes(&self.bytes[self.files_end + 2..]),
255 )
252 )
256 .finish()
253 .finish()
257 }
254 }
258 }
255 }
259
256
260 fn debug_bytes(bytes: &[u8]) -> String {
257 fn debug_bytes(bytes: &[u8]) -> String {
261 String::from_utf8_lossy(
258 String::from_utf8_lossy(
262 &bytes.iter().flat_map(|b| escape_default(*b)).collect_vec(),
259 &bytes.iter().flat_map(|b| escape_default(*b)).collect_vec(),
263 )
260 )
264 .to_string()
261 .to_string()
265 }
262 }
266
263
267 #[cfg(test)]
264 #[cfg(test)]
268 mod tests {
265 mod tests {
269 use super::*;
266 use super::*;
270 use crate::vfs::Vfs;
267 use crate::vfs::Vfs;
271 use crate::NULL_REVISION;
268 use crate::NULL_REVISION;
272 use pretty_assertions::assert_eq;
269 use pretty_assertions::assert_eq;
273
270
274 #[test]
271 #[test]
275 fn test_create_changelogrevisiondata_invalid() {
272 fn test_create_changelogrevisiondata_invalid() {
276 // Completely empty
273 // Completely empty
277 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
274 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
278 // No newline after manifest
275 // No newline after manifest
279 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
276 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
280 // No newline after user
277 // No newline after user
281 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n")).is_err());
278 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n")).is_err());
282 // No newline after timestamp
279 // No newline after timestamp
283 assert!(
280 assert!(
284 ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n\n0 0")).is_err()
281 ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n\n0 0")).is_err()
285 );
282 );
286 // Missing newline after files
283 // Missing newline after files
287 assert!(ChangelogRevisionData::new(Cow::Borrowed(
284 assert!(ChangelogRevisionData::new(Cow::Borrowed(
288 b"abcd\n\n0 0\nfile1\nfile2"
285 b"abcd\n\n0 0\nfile1\nfile2"
289 ))
286 ))
290 .is_err(),);
287 .is_err(),);
291 // Only one newline after files
288 // Only one newline after files
292 assert!(ChangelogRevisionData::new(Cow::Borrowed(
289 assert!(ChangelogRevisionData::new(Cow::Borrowed(
293 b"abcd\n\n0 0\nfile1\nfile2\n"
290 b"abcd\n\n0 0\nfile1\nfile2\n"
294 ))
291 ))
295 .is_err(),);
292 .is_err(),);
296 }
293 }
297
294
298 #[test]
295 #[test]
299 fn test_create_changelogrevisiondata() {
296 fn test_create_changelogrevisiondata() {
300 let data = ChangelogRevisionData::new(Cow::Borrowed(
297 let data = ChangelogRevisionData::new(Cow::Borrowed(
301 b"0123456789abcdef0123456789abcdef01234567
298 b"0123456789abcdef0123456789abcdef01234567
302 Some One <someone@example.com>
299 Some One <someone@example.com>
303 0 0
300 0 0
304 file1
301 file1
305 file2
302 file2
306
303
307 some
304 some
308 commit
305 commit
309 message",
306 message",
310 ))
307 ))
311 .unwrap();
308 .unwrap();
312 assert_eq!(
309 assert_eq!(
313 data.manifest_node().unwrap(),
310 data.manifest_node().unwrap(),
314 Node::from_hex("0123456789abcdef0123456789abcdef01234567")
311 Node::from_hex("0123456789abcdef0123456789abcdef01234567")
315 .unwrap()
312 .unwrap()
316 );
313 );
317 assert_eq!(data.user(), b"Some One <someone@example.com>");
314 assert_eq!(data.user(), b"Some One <someone@example.com>");
318 assert_eq!(data.timestamp_line(), b"0 0");
315 assert_eq!(data.timestamp_line(), b"0 0");
319 assert_eq!(
316 assert_eq!(
320 data.files().collect_vec(),
317 data.files().collect_vec(),
321 vec![HgPath::new("file1"), HgPath::new("file2")]
318 vec![HgPath::new("file1"), HgPath::new("file2")]
322 );
319 );
323 assert_eq!(data.description(), b"some\ncommit\nmessage");
320 assert_eq!(data.description(), b"some\ncommit\nmessage");
324 }
321 }
325
322
326 #[test]
323 #[test]
327 fn test_data_from_rev_null() -> Result<(), RevlogError> {
324 fn test_data_from_rev_null() -> Result<(), RevlogError> {
328 // an empty revlog will be enough for this case
325 // an empty revlog will be enough for this case
329 let temp = tempfile::tempdir().unwrap();
326 let temp = tempfile::tempdir().unwrap();
330 let vfs = Vfs { base: temp.path() };
327 let vfs = Vfs { base: temp.path() };
331 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
328 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
332 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
329 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
333
330
334 let changelog = Changelog { revlog };
331 let changelog = Changelog { revlog };
335 assert_eq!(
332 assert_eq!(
336 changelog.data_for_rev(NULL_REVISION)?,
333 changelog.data_for_rev(NULL_REVISION)?,
337 ChangelogRevisionData::null()
334 ChangelogRevisionData::null()
338 );
335 );
339 // same with the intermediate entry object
336 // same with the intermediate entry object
340 assert_eq!(
337 assert_eq!(
341 changelog.entry_for_rev(NULL_REVISION)?.data()?,
338 changelog.entry_for_rev(NULL_REVISION)?.data()?,
342 ChangelogRevisionData::null()
339 ChangelogRevisionData::null()
343 );
340 );
344 Ok(())
341 Ok(())
345 }
342 }
346 }
343 }
General Comments 0
You need to be logged in to leave comments. Login now