##// END OF EJS Templates
extras: re-use Projection from jaraco.collections
extras: re-use Projection from jaraco.collections

File last commit:

r50832:75040950 default
r51261:82e5a9b1 default
Show More
changelog.rs
271 lines | 8.5 KiB | application/rls-services+xml | RustLexer
Simon Sapin
rust: use HgError in RevlogError and Vfs...
r47172 use crate::errors::HgError;
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 use crate::revlog::Revision;
Simon Sapin
rhg: `cat` command: print error messages for missing files...
r47478 use crate::revlog::{Node, NodePrefix};
Raphaël Gomès
rust-clippy: merge "revlog" module definition and struct implementation...
r50832 use crate::revlog::{Revlog, RevlogEntry, RevlogError};
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 use crate::utils::hg_path::HgPath;
Martin von Zweigbergk
rust-revlog: make `Changelog` and `ManifestLog` unaware of `Repo`...
r49981 use crate::vfs::Vfs;
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 use itertools::Itertools;
use std::ascii::escape_default;
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 use std::borrow::Cow;
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 use std::fmt::{Debug, Formatter};
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103
/// A specialized `Revlog` to work with `changelog` data format.
pub struct Changelog {
/// The generic `revlog` format.
Simon Sapin
rhg: centralize parsing of `--rev` CLI arguments...
r47162 pub(crate) revlog: Revlog,
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
impl Changelog {
/// Open the `changelog` of a repository given by its root.
Martin von Zweigbergk
rust-revlog: make `Changelog` and `ManifestLog` unaware of `Repo`...
r49981 pub fn open(store_vfs: &Vfs, use_nodemap: bool) -> Result<Self, HgError> {
let revlog =
Revlog::open(store_vfs, "00changelog.i", None, use_nodemap)?;
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 Ok(Self { revlog })
}
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 /// Return the `ChangelogEntry` for the given node ID.
pub fn data_for_node(
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 &self,
Simon Sapin
rust: Make NodePrefix allocation-free and Copy, remove NodePrefixRef...
r47160 node: NodePrefix,
Simon Sapin
rhg: Rename some revlog-related types and methods...
r49372 ) -> Result<ChangelogRevisionData, RevlogError> {
Simon Sapin
rust: Rename the `Revlog::get_node_rev` method to `rev_from_node`...
r48782 let rev = self.revlog.rev_from_node(node)?;
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 self.data_for_rev(rev)
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
Martin von Zweigbergk
rust-revlog: add methods for getting parent revs and entries...
r49939 /// Return the `RevlogEntry` of the given revision number.
pub fn entry_for_rev(
&self,
rev: Revision,
) -> Result<RevlogEntry, RevlogError> {
self.revlog.get_entry(rev)
}
Simon Sapin
rust: Rename get_node methods to data_for_node, get_rev to data_for_rev...
r48783 /// Return the `ChangelogEntry` of the given revision number.
pub fn data_for_rev(
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 &self,
rev: Revision,
Simon Sapin
rhg: Rename some revlog-related types and methods...
r49372 ) -> Result<ChangelogRevisionData, RevlogError> {
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 let bytes = self.revlog.get_rev_data(rev)?;
Martin von Zweigbergk
rust-changelog: remove special parsing of empty changelog data for null rev...
r49937 if bytes.is_empty() {
Ok(ChangelogRevisionData::null())
} else {
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 Ok(ChangelogRevisionData::new(bytes).map_err(|err| {
RevlogError::Other(HgError::CorruptedRepository(format!(
"Invalid changelog data for revision {}: {:?}",
rev, err
)))
})?)
Martin von Zweigbergk
rust-changelog: remove special parsing of empty changelog data for null rev...
r49937 }
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
Simon Sapin
rhg: `cat` command: print error messages for missing files...
r47478
pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
Simon Sapin
rust: Make private the `index` field of the `Revlog` struct...
r48781 self.revlog.node_from_rev(rev)
Simon Sapin
rhg: `cat` command: print error messages for missing files...
r47478 }
Martin von Zweigbergk
rust-revlog: add methods for getting parent revs and entries...
r49939
pub fn rev_from_node(
&self,
node: NodePrefix,
) -> Result<Revision, RevlogError> {
self.revlog.rev_from_node(node)
}
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
/// `Changelog` entry which knows how to interpret the `changelog` data bytes.
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 #[derive(PartialEq)]
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 pub struct ChangelogRevisionData<'changelog> {
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 /// The data bytes of the `changelog` entry.
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 bytes: Cow<'changelog, [u8]>,
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 /// The end offset for the hex manifest (not including the newline)
manifest_end: usize,
/// The end offset for the user+email (not including the newline)
user_end: usize,
/// The end offset for the timestamp+timezone+extras (not including the
/// newline)
timestamp_end: usize,
/// The end offset for the file list (not including the newline)
files_end: usize,
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 impl<'changelog> ChangelogRevisionData<'changelog> {
fn new(bytes: Cow<'changelog, [u8]>) -> Result<Self, HgError> {
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 let mut line_iter = bytes.split(|b| b == &b'\n');
let manifest_end = line_iter
.next()
.expect("Empty iterator from split()?")
.len();
let user_slice = line_iter.next().ok_or_else(|| {
HgError::corrupted("Changeset data truncated after manifest line")
})?;
let user_end = manifest_end + 1 + user_slice.len();
let timestamp_slice = line_iter.next().ok_or_else(|| {
HgError::corrupted("Changeset data truncated after user line")
})?;
let timestamp_end = user_end + 1 + timestamp_slice.len();
let mut files_end = timestamp_end + 1;
loop {
let line = line_iter.next().ok_or_else(|| {
HgError::corrupted("Changeset data truncated in files list")
})?;
if line.is_empty() {
if files_end == bytes.len() {
// The list of files ended with a single newline (there
// should be two)
return Err(HgError::corrupted(
"Changeset data truncated after files list",
));
}
files_end -= 1;
break;
}
files_end += line.len() + 1;
}
Ok(Self {
bytes,
manifest_end,
user_end,
timestamp_end,
files_end,
})
Martin von Zweigbergk
rust-changelog: remove special parsing of empty changelog data for null rev...
r49937 }
fn null() -> Self {
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 Self::new(Cow::Borrowed(
b"0000000000000000000000000000000000000000\n\n0 0\n\n",
))
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 .unwrap()
Martin von Zweigbergk
rust-changelog: remove special parsing of empty changelog data for null rev...
r49937 }
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 /// Return an iterator over the lines of the entry.
pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
Martin von Zweigbergk
rust-changelog: don't skip empty lines when iterating over changeset lines...
r49936 self.bytes.split(|b| b == &b'\n')
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
/// Return the node id of the `manifest` referenced by this `changelog`
/// entry.
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 pub fn manifest_node(&self) -> Result<Node, HgError> {
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 let manifest_node_hex = &self.bytes[..self.manifest_end];
Martin von Zweigbergk
rust-changelog: remove special parsing of empty changelog data for null rev...
r49937 Node::from_hex_for_repo(manifest_node_hex)
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938
/// The full user string (usually a name followed by an email enclosed in
/// angle brackets)
pub fn user(&self) -> &[u8] {
&self.bytes[self.manifest_end + 1..self.user_end]
}
/// The full timestamp line (timestamp in seconds, offset in seconds, and
/// possibly extras)
// TODO: We should expose this in a more useful way
pub fn timestamp_line(&self) -> &[u8] {
&self.bytes[self.user_end + 1..self.timestamp_end]
}
/// The files changed in this revision.
pub fn files(&self) -> impl Iterator<Item = &HgPath> {
self.bytes[self.timestamp_end + 1..self.files_end]
.split(|b| b == &b'\n')
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 .map(HgPath::new)
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 }
/// The change description.
pub fn description(&self) -> &[u8] {
&self.bytes[self.files_end + 2..]
}
Antoine Cezar
hg-core: add `Changlog` a specialized `Revlog`...
r46103 }
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 impl Debug for ChangelogRevisionData<'_> {
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChangelogRevisionData")
.field("bytes", &debug_bytes(&self.bytes))
.field("manifest", &debug_bytes(&self.bytes[..self.manifest_end]))
.field(
"user",
&debug_bytes(
&self.bytes[self.manifest_end + 1..self.user_end],
),
)
.field(
"timestamp",
&debug_bytes(
&self.bytes[self.user_end + 1..self.timestamp_end],
),
)
.field(
"files",
&debug_bytes(
&self.bytes[self.timestamp_end + 1..self.files_end],
),
)
.field(
"description",
&debug_bytes(&self.bytes[self.files_end + 2..]),
)
.finish()
}
}
fn debug_bytes(bytes: &[u8]) -> String {
String::from_utf8_lossy(
&bytes.iter().flat_map(|b| escape_default(*b)).collect_vec(),
)
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_create_changelogrevisiondata_invalid() {
// Completely empty
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 // No newline after manifest
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd")).is_err());
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 // No newline after user
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n")).is_err());
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 // No newline after timestamp
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(
ChangelogRevisionData::new(Cow::Borrowed(b"abcd\n\n0 0")).is_err()
);
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 // Missing newline after files
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(ChangelogRevisionData::new(Cow::Borrowed(
b"abcd\n\n0 0\nfile1\nfile2"
))
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 .is_err(),);
// Only one newline after files
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 assert!(ChangelogRevisionData::new(Cow::Borrowed(
b"abcd\n\n0 0\nfile1\nfile2\n"
))
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 .is_err(),);
}
#[test]
fn test_create_changelogrevisiondata() {
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 let data = ChangelogRevisionData::new(Cow::Borrowed(
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 b"0123456789abcdef0123456789abcdef01234567
Some One <someone@example.com>
0 0
file1
file2
some
commit
Martin von Zweigbergk
changelog: avoid copying changeset data into `ChangesetRevisionData`...
r49987 message",
))
Martin von Zweigbergk
rust-changelog: start parsing changeset data...
r49938 .unwrap();
assert_eq!(
data.manifest_node().unwrap(),
Node::from_hex("0123456789abcdef0123456789abcdef01234567")
.unwrap()
);
assert_eq!(data.user(), b"Some One <someone@example.com>");
assert_eq!(data.timestamp_line(), b"0 0");
assert_eq!(
data.files().collect_vec(),
vec![HgPath::new("file1"), HgPath::new("file2")]
);
assert_eq!(data.description(), b"some\ncommit\nmessage");
}
}