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"); | ||||
} | } | ||||
} | } |