#[cfg(feature = "pyo3")]
use pyo3::{prelude::*, types::PyBytes};
use std::fmt::{Debug, Error, Formatter};
pub const DEFAULT_CHUNK_SIZE: usize = 4096;
pub mod bencode_serializer;
pub mod bisect_multi;
pub mod branch;
pub mod btree_builder;
pub mod btree_graph_index;
pub mod btree_index;
pub mod btree_serializer;
pub mod bzrdir;
pub mod chk_inventory;
pub mod chk_map;
pub mod chunk_writer;
pub mod config;
pub mod dirstate;
pub mod filters;
pub mod gen_ids;
pub mod globbing;
#[cfg(feature = "gpg")]
pub mod gpg;
pub mod groupcompress;
pub mod hashcache;
pub mod ignores;
pub mod index;
pub mod inventory;
pub mod inventory_delta;
pub mod key_mapper;
pub mod knit;
pub mod lock;
pub mod lockdir;
pub mod lru_cache;
pub mod multiparent;
pub mod osutils;
pub mod pack;
pub mod pack_repo;
pub mod plan_merge;
pub mod recordcounter;
pub mod repository;
pub mod revision;
pub mod rio;
pub mod serializer;
pub mod smart;
pub mod testament;
pub mod textinv;
pub mod textmerge;
pub mod transport;
pub mod tuned_gzip;
pub mod versionedfile;
pub mod weave;
pub mod workingtree;
pub mod xml_serializer;
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FileId(Vec<u8>);
impl Debug for FileId {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", String::from_utf8(self.0.clone()).unwrap())
}
}
impl From<Vec<u8>> for FileId {
fn from(v: Vec<u8>) -> Self {
check_valid(&v);
FileId(v)
}
}
impl From<FileId> for Vec<u8> {
fn from(v: FileId) -> Self {
v.0
}
}
impl From<&[u8]> for FileId {
fn from(v: &[u8]) -> Self {
check_valid(v);
FileId(v.to_vec())
}
}
impl From<&Vec<u8>> for FileId {
fn from(v: &Vec<u8>) -> Self {
FileId::from(v.as_slice())
}
}
impl FileId {
pub fn generate(name: &str) -> Self {
Self::from(gen_ids::gen_file_id(name))
}
pub fn generate_root_id() -> Self {
Self::from(gen_ids::gen_root_id())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[cfg(feature = "pyo3")]
impl<'a, 'py> FromPyObject<'a, 'py> for FileId {
type Error = PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
let s: Vec<u8> = ob.extract()?;
if !is_valid(&s) {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Invalid file id: {:?}",
s
)));
}
Ok(FileId::from(s))
}
}
#[cfg(feature = "pyo3")]
impl<'py> IntoPyObject<'py> for &FileId {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyBytes::new(py, &self.0))
}
}
#[cfg(feature = "pyo3")]
impl<'py> IntoPyObject<'py> for FileId {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
impl std::fmt::Display for FileId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", String::from_utf8(self.0.clone()).unwrap())
}
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RevisionId(Vec<u8>);
impl Debug for RevisionId {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", String::from_utf8(self.0.clone()).unwrap())
}
}
impl std::fmt::Display for RevisionId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", String::from_utf8(self.0.clone()).unwrap())
}
}
impl From<Vec<u8>> for RevisionId {
fn from(v: Vec<u8>) -> Self {
check_valid(&v);
RevisionId(v)
}
}
impl From<&[u8]> for RevisionId {
fn from(v: &[u8]) -> Self {
check_valid(v);
RevisionId(v.to_vec())
}
}
impl From<RevisionId> for Vec<u8> {
fn from(v: RevisionId) -> Self {
v.0
}
}
#[cfg(feature = "pyo3")]
impl<'a, 'py> FromPyObject<'a, 'py> for RevisionId {
type Error = PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
let s: Vec<u8> = ob.extract()?;
if !is_valid(&s) {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Invalid revision id: {:?}",
s
)));
}
Ok(RevisionId::from(s))
}
}
#[cfg(feature = "pyo3")]
impl<'py> IntoPyObject<'py> for &RevisionId {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let obj = PyBytes::new(py, &self.0);
Ok(obj)
}
}
#[cfg(feature = "pyo3")]
impl<'py> IntoPyObject<'py> for RevisionId {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
pub const NULL_REVISION: &[u8] = b"null:";
pub const CURRENT_REVISION: &[u8] = b"current:";
pub fn is_valid(id: &[u8]) -> bool {
if id.contains(&b' ') || id.contains(&b'\t') || id.contains(&b'\n') || id.contains(&b'\r') {
return false;
}
if id.is_empty() {
return false;
}
true
}
pub fn check_valid(id: &[u8]) {
if !is_valid(id) {
if let Ok(id) = String::from_utf8(id.to_vec()) {
panic!("Invalid id: {:?}", id);
} else {
panic!("Invalid id: {:?}", id);
}
}
}
impl RevisionId {
pub fn is_null(&self) -> bool {
self.0 == NULL_REVISION
}
pub fn generate(username: &str, timestamp: Option<u64>) -> Self {
Self::from(gen_ids::gen_revision_id(username, timestamp))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn is_reserved(&self) -> bool {
self.0.ends_with(b":")
}
pub fn expect_not_reserved(&self) {
if self.is_reserved() {
panic!("Expected non-reserved revision id, got {:?}", self);
}
}
}
#[cfg(test)]
mod id_validation_tests {
use super::*;
#[test]
fn is_valid_accepts_normal_ids() {
assert!(is_valid(b"simple-id"));
assert!(is_valid(b"with.dots"));
assert!(is_valid(b"\xc3\xa9clair")); }
#[test]
fn is_valid_rejects_whitespace_and_empty() {
assert!(!is_valid(b""));
assert!(!is_valid(b"a dir id"));
assert!(!is_valid(b"tabbed\tid"));
assert!(!is_valid(b"newline\nid"));
assert!(!is_valid(b"carriage\rid"));
}
}