Details
Details
- Reviewers
Alphare - Group Reviewers
hg-reviewers
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
( )
| Alphare |
| hg-reviewers |
| No Linters Available |
| No Unit Test Coverage |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-core/src/revlog/changelog.rs (48 lines) |
| Commit | Parents | Author | Summary | Date |
|---|---|---|---|---|
| 74a45ec51eb2 | 34fe8c82ae05 | Martin von Zweigbergk | Wed, Apr 13, 2:15 AM |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | martinvonz | ||
| Needs Review | martinvonz | ||
| Needs Review | martinvonz | ||
| Accepted | martinvonz | ||
| Accepted | martinvonz | ||
| Needs Review | martinvonz | ||
| Needs Review | martinvonz | ||
| Needs Review | martinvonz | ||
| Needs Review | martinvonz | ||
| Needs Review | martinvonz | ||
| Accepted | martinvonz | ||
| Accepted | martinvonz | ||
| Needs Review | martinvonz | ||
| Accepted | martinvonz | ||
| Accepted | martinvonz | ||
| Closed | martinvonz | ||
| Closed | martinvonz | ||
| Closed | martinvonz | ||
| Closed | martinvonz |
| use crate::errors::HgError; | use crate::errors::HgError; | ||||
| use crate::revlog::revlog::{Revlog, RevlogEntry, RevlogError}; | use crate::revlog::revlog::{Revlog, RevlogEntry, RevlogError}; | ||||
| use crate::revlog::Revision; | use crate::revlog::Revision; | ||||
| use crate::revlog::{Node, NodePrefix}; | use crate::revlog::{Node, NodePrefix}; | ||||
| use crate::utils::hg_path::HgPath; | use crate::utils::hg_path::HgPath; | ||||
| use crate::vfs::Vfs; | use crate::vfs::Vfs; | ||||
| use itertools::Itertools; | use itertools::Itertools; | ||||
| use std::ascii::escape_default; | use std::ascii::escape_default; | ||||
| use std::borrow::Cow; | |||||
| use std::fmt::{Debug, Formatter}; | use std::fmt::{Debug, Formatter}; | ||||
| /// A specialized `Revlog` to work with `changelog` data format. | /// A specialized `Revlog` to work with `changelog` data format. | ||||
| pub struct Changelog { | pub struct Changelog { | ||||
| /// The generic `revlog` format. | /// The generic `revlog` format. | ||||
| pub(crate) revlog: Revlog, | pub(crate) revlog: Revlog, | ||||
| } | } | ||||
| self.revlog.get_entry(rev) | self.revlog.get_entry(rev) | ||||
| } | } | ||||
| /// Return the `ChangelogEntry` of the given revision number. | /// Return the `ChangelogEntry` of the given revision number. | ||||
| pub fn data_for_rev( | pub fn data_for_rev( | ||||
| &self, | &self, | ||||
| rev: Revision, | rev: Revision, | ||||
| ) -> Result<ChangelogRevisionData, RevlogError> { | ) -> Result<ChangelogRevisionData, RevlogError> { | ||||
| let bytes = self.revlog.get_rev_data(rev)?.into_owned(); | let bytes = self.revlog.get_rev_data(rev)?; | ||||
| if bytes.is_empty() { | if bytes.is_empty() { | ||||
| Ok(ChangelogRevisionData::null()) | Ok(ChangelogRevisionData::null()) | ||||
| } else { | } else { | ||||
| Ok(ChangelogRevisionData::new(bytes).map_err(|err| { | Ok(ChangelogRevisionData::new(bytes).map_err(|err| { | ||||
| RevlogError::Other(HgError::CorruptedRepository(format!( | RevlogError::Other(HgError::CorruptedRepository(format!( | ||||
| "Invalid changelog data for revision {}: {:?}", | "Invalid changelog data for revision {}: {:?}", | ||||
| rev, err | rev, err | ||||
| ))) | ))) | ||||
| node: NodePrefix, | node: NodePrefix, | ||||
| ) -> Result<Revision, RevlogError> { | ) -> Result<Revision, RevlogError> { | ||||
| self.revlog.rev_from_node(node) | self.revlog.rev_from_node(node) | ||||
| } | } | ||||
| } | } | ||||
| /// `Changelog` entry which knows how to interpret the `changelog` data bytes. | /// `Changelog` entry which knows how to interpret the `changelog` data bytes. | ||||
| #[derive(PartialEq)] | #[derive(PartialEq)] | ||||
| pub struct ChangelogRevisionData { | pub struct ChangelogRevisionData<'changelog> { | ||||
| /// The data bytes of the `changelog` entry. | /// The data bytes of the `changelog` entry. | ||||
| bytes: Vec<u8>, | bytes: Cow<'changelog, [u8]>, | ||||
| /// The end offset for the hex manifest (not including the newline) | /// The end offset for the hex manifest (not including the newline) | ||||
| manifest_end: usize, | manifest_end: usize, | ||||
| /// The end offset for the user+email (not including the newline) | /// The end offset for the user+email (not including the newline) | ||||
| user_end: usize, | user_end: usize, | ||||
| /// The end offset for the timestamp+timezone+extras (not including the | /// The end offset for the timestamp+timezone+extras (not including the | ||||
| /// newline) | /// newline) | ||||
| timestamp_end: usize, | timestamp_end: usize, | ||||
| /// The end offset for the file list (not including the newline) | /// The end offset for the file list (not including the newline) | ||||
| files_end: usize, | files_end: usize, | ||||
| } | } | ||||
| impl ChangelogRevisionData { | impl<'changelog> ChangelogRevisionData<'changelog> { | ||||
| fn new(bytes: Vec<u8>) -> Result<Self, HgError> { | fn new(bytes: Cow<'changelog, [u8]>) -> Result<Self, HgError> { | ||||
| let mut line_iter = bytes.split(|b| b == &b'\n'); | let mut line_iter = bytes.split(|b| b == &b'\n'); | ||||
| let manifest_end = line_iter | let manifest_end = line_iter | ||||
| .next() | .next() | ||||
| .expect("Empty iterator from split()?") | .expect("Empty iterator from split()?") | ||||
| .len(); | .len(); | ||||
| let user_slice = line_iter.next().ok_or_else(|| { | let user_slice = line_iter.next().ok_or_else(|| { | ||||
| HgError::corrupted("Changeset data truncated after manifest line") | HgError::corrupted("Changeset data truncated after manifest line") | ||||
| })?; | })?; | ||||
| manifest_end, | manifest_end, | ||||
| user_end, | user_end, | ||||
| timestamp_end, | timestamp_end, | ||||
| files_end, | files_end, | ||||
| }) | }) | ||||
| } | } | ||||
| fn null() -> Self { | fn null() -> Self { | ||||
| Self::new( | Self::new(Cow::Borrowed( | ||||
| b"0000000000000000000000000000000000000000\n\n0 0\n\n".to_vec(), | b"0000000000000000000000000000000000000000\n\n0 0\n\n", | ||||
| ) | )) | ||||
| .unwrap() | .unwrap() | ||||
| } | } | ||||
| /// Return an iterator over the lines of the entry. | /// Return an iterator over the lines of the entry. | ||||
| pub fn lines(&self) -> impl Iterator<Item = &[u8]> { | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { | ||||
| self.bytes.split(|b| b == &b'\n') | self.bytes.split(|b| b == &b'\n') | ||||
| } | } | ||||
| } | } | ||||
| /// The change description. | /// The change description. | ||||
| pub fn description(&self) -> &[u8] { | pub fn description(&self) -> &[u8] { | ||||
| &self.bytes[self.files_end + 2..] | &self.bytes[self.files_end + 2..] | ||||
| } | } | ||||
| } | } | ||||
| impl Debug for ChangelogRevisionData { | impl Debug for ChangelogRevisionData<'_> { | ||||
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||||
| f.debug_struct("ChangelogRevisionData") | f.debug_struct("ChangelogRevisionData") | ||||
| .field("bytes", &debug_bytes(&self.bytes)) | .field("bytes", &debug_bytes(&self.bytes)) | ||||
| .field("manifest", &debug_bytes(&self.bytes[..self.manifest_end])) | .field("manifest", &debug_bytes(&self.bytes[..self.manifest_end])) | ||||
| .field( | .field( | ||||
| "user", | "user", | ||||
| &debug_bytes( | &debug_bytes( | ||||
| &self.bytes[self.manifest_end + 1..self.user_end], | &self.bytes[self.manifest_end + 1..self.user_end], | ||||
| #[cfg(test)] | #[cfg(test)] | ||||
| mod tests { | mod tests { | ||||
| use super::*; | use super::*; | ||||
| use pretty_assertions::assert_eq; | use pretty_assertions::assert_eq; | ||||
| #[test] | #[test] | ||||
| fn test_create_changelogrevisiondata_invalid() { | fn test_create_changelogrevisiondata_invalid() { | ||||
| // Completely empty | // Completely empty | ||||
| assert!(ChangelogRevisionData::new(b"abcd".to_vec()).is_err()); | assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err()); | ||||
| // No newline after manifest | // No newline after manifest | ||||
| assert!(ChangelogRevisionData::new(b"abcd".to_vec()).is_err()); | assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err()); | ||||
| // No newline after user | // No newline after user | ||||
| assert!(ChangelogRevisionData::new(b"abcd\n".to_vec()).is_err()); | assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n")).is_err()); | ||||
| // No newline after timestamp | // No newline after timestamp | ||||
| assert!(ChangelogRevisionData::new(b"abcd\n\n0 0".to_vec()).is_err()); | assert!( | ||||
| ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n\n0 0")).is_err() | |||||
| ); | |||||
| // Missing newline after files | // Missing newline after files | ||||
| assert!(ChangelogRevisionData::new( | assert!(ChangelogRevisionData::new(Cow::Borrowed( | ||||
| b"abcd\n\n0 0\nfile1\nfile2".to_vec() | b"abcd\n\n0 0\nfile1\nfile2" | ||||
| ) | )) | ||||
| .is_err(),); | .is_err(),); | ||||
| // Only one newline after files | // Only one newline after files | ||||
| assert!(ChangelogRevisionData::new( | assert!(ChangelogRevisionData::new(Cow::Borrowed( | ||||
| b"abcd\n\n0 0\nfile1\nfile2\n".to_vec() | b"abcd\n\n0 0\nfile1\nfile2\n" | ||||
| ) | )) | ||||
| .is_err(),); | .is_err(),); | ||||
| } | } | ||||
| #[test] | #[test] | ||||
| fn test_create_changelogrevisiondata() { | fn test_create_changelogrevisiondata() { | ||||
| let data = ChangelogRevisionData::new( | let data = ChangelogRevisionData::new(Cow::Borrowed( | ||||
| b"0123456789abcdef0123456789abcdef01234567 | b"0123456789abcdef0123456789abcdef01234567 | ||||
| Some One <someone@example.com> | Some One <someone@example.com> | ||||
| 0 0 | 0 0 | ||||
| file1 | file1 | ||||
| file2 | file2 | ||||
| some | some | ||||
| commit | commit | ||||
| message" | message", | ||||
| .to_vec(), | )) | ||||
| ) | |||||
| .unwrap(); | .unwrap(); | ||||
| assert_eq!( | assert_eq!( | ||||
| data.manifest_node().unwrap(), | data.manifest_node().unwrap(), | ||||
| Node::from_hex("0123456789abcdef0123456789abcdef01234567") | Node::from_hex("0123456789abcdef0123456789abcdef01234567") | ||||
| .unwrap() | .unwrap() | ||||
| ); | ); | ||||
| assert_eq!(data.user(), b"Some One <someone@example.com>"); | assert_eq!(data.user(), b"Some One <someone@example.com>"); | ||||
| assert_eq!(data.timestamp_line(), b"0 0"); | assert_eq!(data.timestamp_line(), b"0 0"); | ||||
| assert_eq!( | assert_eq!( | ||||
| data.files().collect_vec(), | data.files().collect_vec(), | ||||
| vec![HgPath::new("file1"), HgPath::new("file2")] | vec![HgPath::new("file1"), HgPath::new("file2")] | ||||
| ); | ); | ||||
| assert_eq!(data.description(), b"some\ncommit\nmessage"); | assert_eq!(data.description(), b"some\ncommit\nmessage"); | ||||
| } | } | ||||
| } | } | ||||