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