hg_parser/
types.rs

1use std::{
2    fmt::{self, Display},
3    ops::{Add, Range, Sub},
4    rc::Rc,
5    str::FromStr,
6    sync::Arc,
7};
8
9use super::error::ErrorKind;
10use bitflags::bitflags;
11use chrono::{
12    DateTime as ChronoDateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone,
13};
14use sha1::{Digest, Sha1};
15
16/// Repository requires flags.
17/// Repositories contain a file (``.hg/requires``) containing a list of
18/// features/capabilities that are *required* for clients to interface
19/// with the repository.
20#[derive(Debug, Clone, Eq, PartialEq, Hash)]
21pub enum RepositoryRequire {
22    /// When present, revlogs are version 1 (**RevlogNG**).
23    Revlogv1,
24    /// The **store** repository layout is used.
25    Store,
26    /// The **fncache** layout hash encodes filenames with long paths and encodes reserved filenames.
27    FnCache,
28    /// Denotes that the store for a repository is shared from another location (defined by the ``.hg/sharedpath`` file).
29    Shared,
30    /// Derivative of ``shared``; the location of the store is relative to the store of this repository.
31    RelShared,
32    /// The *dotencode* layout encodes the first period or space in filenames to prevent issues on OS X and Windows.
33    DotEncode,
34    /// Denotes a revlog delta encoding format that was experimental and
35    /// replaced by *generaldelta*. It should not be seen in the wild because
36    /// it was never enabled by default.
37    ParentDelta,
38    /// Revlogs should be created with the *generaldelta* flag enabled. The
39    /// generaldelta flag will cause deltas to be encoded against a parent
40    /// revision instead of the previous revision in the revlog.
41    GeneralDelta,
42    /// Denotes that version 2 of manifests are being used.
43    Manifestv2,
44    /// Denotes that tree manifests are being used. Tree manifests are
45    /// one manifest per directory (as opposed to a single flat manifest).
46    TreeManifest,
47    /// The working directory is sparse (only contains a subset of files).
48    ExpSparse,
49    /// Safe behaviour for all shares that access a repository.
50    ShareSafe,
51    /// Unknown requirement.
52    Unknown(String),
53}
54
55impl FromStr for RepositoryRequire {
56    type Err = ErrorKind;
57
58    fn from_str(value: &str) -> Result<RepositoryRequire, ErrorKind> {
59        use RepositoryRequire::*;
60        match value {
61            "revlogv1" => Ok(Revlogv1),
62            "store" => Ok(Store),
63            "fncache" => Ok(FnCache),
64            "shared" => Ok(Shared),
65            "relshared" => Ok(RelShared),
66            "dotencode" => Ok(DotEncode),
67            "parentdelta" => Ok(ParentDelta),
68            "generaldelta" => Ok(GeneralDelta),
69            "manifestv2" => Ok(Manifestv2),
70            "treemanifest" => Ok(TreeManifest),
71            "exp-sparse" => Ok(ExpSparse),
72            "share-safe" => Ok(ShareSafe),
73            other => Err(ErrorKind::UnknownRequirement(Self::Unknown(other.into()))),
74        }
75    }
76}
77
78impl std::fmt::Display for RepositoryRequire {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        use RepositoryRequire::*;
81        match self {
82            Revlogv1 => "revlogv1".fmt(f),
83            Store => "store".fmt(f),
84            FnCache => "fncache".fmt(f),
85            Shared => "shared".fmt(f),
86            RelShared => "relshared".fmt(f),
87            DotEncode => "dotencode".fmt(f),
88            ParentDelta => "parentdelta".fmt(f),
89            GeneralDelta => "generaldelta".fmt(f),
90            Manifestv2 => "manifestv2".fmt(f),
91            TreeManifest => "treemanifest".fmt(f),
92            ExpSparse => "exp-sparse".fmt(f),
93            ShareSafe => "share-safe".fmt(f),
94            Unknown(s) => s.fmt(f),
95        }
96    }
97}
98
99/// Mercurial revision's index.
100#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
101pub struct Revision(pub u32);
102
103impl Revision {
104    /// Return iterator for a range from index to `lim`.
105    pub fn range_to(self, lim: Self) -> RevisionRange {
106        RevisionRange(self.0, lim.0)
107    }
108
109    /// Return an open ended iterator from index.
110    pub fn range(self) -> RevisionRange {
111        RevisionRange(self.0, u32::MAX)
112    }
113}
114
115impl From<u32> for Revision {
116    fn from(value: u32) -> Self {
117        Self(value)
118    }
119}
120
121impl Add<u32> for Revision {
122    type Output = Self;
123
124    fn add(self, other: u32) -> Self {
125        Self(self.0 + other)
126    }
127}
128
129impl Sub<u32> for Revision {
130    type Output = Self;
131
132    fn sub(self, other: u32) -> Self {
133        assert!(self.0 >= other);
134        Self(self.0 - other)
135    }
136}
137
138impl From<Revision> for usize {
139    fn from(value: Revision) -> usize {
140        value.0 as usize
141    }
142}
143
144/// Convert a `Revision` into an iterator of `Revision` values
145/// starting at `Revision`'s value. ie, `Revision`(2).into_iter() => `Revision`(2), `Revision`(3), ...
146impl<'a> IntoIterator for &'a Revision {
147    type Item = Revision;
148    type IntoIter = RevisionRange;
149
150    fn into_iter(self) -> Self::IntoIter {
151        self.range()
152    }
153}
154
155/// An iterator over a range of `Revision`.
156#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
157pub struct RevisionRange(u32, u32);
158
159impl Iterator for RevisionRange {
160    type Item = Revision;
161
162    fn next(&mut self) -> Option<Self::Item> {
163        if self.0 < self.1 {
164            let ret = Revision(self.0);
165            self.0 += 1;
166            Some(ret)
167        } else {
168            None
169        }
170    }
171}
172
173impl DoubleEndedIterator for RevisionRange {
174    fn next_back(&mut self) -> Option<Revision> {
175        if self.0 < self.1 {
176            self.1 -= 1;
177            let ret = Revision(self.1);
178            Some(ret)
179        } else {
180            None
181        }
182    }
183}
184
185impl From<Range<usize>> for RevisionRange {
186    fn from(value: Range<usize>) -> Self {
187        Self(value.start as u32, value.end as u32)
188    }
189}
190
191/// `Revlog` version number
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193#[repr(u16)]
194pub enum Version {
195    Revlog0 = 0,
196    RevlogNG = 1,
197}
198
199bitflags! {
200    /// `Revlog` features
201    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
202    pub struct Features: u16 {
203        const INLINE        = 1;
204        const GENERAL_DELTA = 1 << 1;
205    }
206}
207
208bitflags! {
209    /// Per-revision flags
210    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
211    pub struct IdxFlags: u16 {
212        const EXTSTORED     = 1 << 13;
213        const CENSORED      = 1 << 15;
214    }
215}
216
217/// `Revlog` header
218#[derive(Copy, Clone, Debug, Eq, PartialEq)]
219pub struct RevisionLogHeader {
220    pub version: Version,
221    pub features: Features,
222}
223
224#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
225pub struct NodeHash([u8; 20]);
226
227const HEX_CHARS: &[u8] = b"0123456789abcdef";
228
229impl NodeHash {
230    pub fn to_hex(self) -> String {
231        let mut v = Vec::with_capacity(40);
232        for &byte in &self.0 {
233            v.push(HEX_CHARS[(byte >> 4) as usize]);
234            v.push(HEX_CHARS[(byte & 0xf) as usize]);
235        }
236
237        unsafe { String::from_utf8_unchecked(v) }
238    }
239
240    pub fn from_slice(data: &[u8]) -> Self {
241        (&Sha1::digest(data)[..]).into()
242    }
243}
244
245impl AsRef<[u8]> for NodeHash {
246    fn as_ref(&self) -> &[u8] {
247        &self.0[..]
248    }
249}
250
251impl std::fmt::Display for NodeHash {
252    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
253        Display::fmt(&self.to_hex(), fmt)
254    }
255}
256
257impl std::fmt::Debug for NodeHash {
258    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
259        write!(fmt, "NodeHash({})", self)
260    }
261}
262
263impl<'a> From<&'a [u8]> for NodeHash {
264    fn from(value: &'a [u8]) -> Self {
265        let mut data: [u8; 20] = Default::default();
266        data.copy_from_slice(value);
267        Self(data)
268    }
269}
270
271impl FromStr for NodeHash {
272    type Err = ErrorKind;
273
274    fn from_str(s: &str) -> Result<Self, ErrorKind> {
275        let mut ret = Self([0; 20]);
276
277        for idx in 0..ret.0.len() {
278            ret.0[idx] = match u8::from_str_radix(&s[(idx * 2)..(idx * 2 + 2)], 16) {
279                Ok(v) => v,
280                Err(_) => return Err(ErrorKind::Parser),
281            }
282        }
283
284        Ok(ret)
285    }
286}
287
288/// Entry entry for a revision
289#[derive(Debug, Copy, Clone)]
290pub struct RevisionLogEntry {
291    pub offset: u64,         // offset of content (delta/literal) in datafile (or inlined)
292    pub flags: IdxFlags,     // unused?
293    pub compressed_len: u32, // compressed content size
294    pub len: Option<u32>,    // size of final file (after applying deltas)
295    pub baserev: Option<Revision>, // base/previous rev for deltas (None if literal)
296    pub linkrev: Revision,   // changeset id
297    pub p1: Option<Revision>, // parent p1
298    pub p2: Option<Revision>, // parent p2
299    pub nodeid: NodeHash,    // nodeid
300}
301
302#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
303pub struct Delta {
304    // Fragments should be in sorted order by start offset and should not overlap.
305    pub(crate) fragments: Vec<Fragment>,
306}
307
308impl Delta {
309    /// Construct a new Delta object. Verify that `frags` is sane, sorted and
310    /// non-overlapping.
311    pub fn new(fragments: Vec<Fragment>) -> Result<Self, ErrorKind> {
312        Ok(Delta { fragments })
313    }
314    pub fn fragments(&self) -> &[Fragment] {
315        self.fragments.as_slice()
316    }
317}
318/// Represents a single contiguous modified region of text.
319#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
320pub struct Fragment {
321    pub start: usize,
322    pub end: usize,
323    pub content: Rc<[u8]>,
324}
325
326impl Fragment {
327    // Return the length of the content
328    pub fn content_length(&self) -> usize {
329        self.content.len()
330    }
331}
332
333impl std::fmt::Debug for Fragment {
334    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
335        write!(
336            fmt,
337            "Fragment(\nstart:{}\nend:{}\ncontent:{:?}\n)",
338            self.start,
339            self.end,
340            std::str::from_utf8(&self.content)
341        )
342    }
343}
344
345#[derive(Debug, Clone)]
346pub enum Chunk {
347    /// Literal text of the revision
348    Literal(Arc<[u8]>),
349    /// Vector of `Delta`s against a previous version
350    Deltas(Delta),
351}
352
353#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
354pub struct DateTime(ChronoDateTime<FixedOffset>);
355
356impl DateTime {
357    #[inline]
358    pub fn new(dt: ChronoDateTime<FixedOffset>) -> Self {
359        DateTime(dt)
360    }
361
362    pub fn now() -> Self {
363        let now = Local::now();
364        DateTime(now.with_timezone(now.offset()))
365    }
366
367    pub fn from_timestamp(secs: i64, tz_offset_secs: i32) -> Result<Self, ErrorKind> {
368        let tz = FixedOffset::west_opt(tz_offset_secs).ok_or_else(|| {
369            ErrorKind::InvalidDateTime(format!("timezone offset out of range: {}", tz_offset_secs))
370        })?;
371        let dt = match tz.timestamp_opt(secs, 0) {
372            LocalResult::Single(dt) => dt,
373            _ => {
374                return Err(ErrorKind::InvalidDateTime(format!(
375                    "seconds out of range: {}",
376                    secs
377                )));
378            }
379        };
380        Ok(Self::new(dt))
381    }
382
383    /// Construct a new `DateTime` from an RFC3339 string.
384    ///
385    /// RFC3339 is a standardized way to represent a specific moment in time. See
386    /// https://tools.ietf.org/html/rfc3339.
387    pub fn from_rfc3339(rfc3339: &str) -> Result<Self, ErrorKind> {
388        let dt = ChronoDateTime::parse_from_rfc3339(rfc3339)?;
389        Ok(Self::new(dt))
390    }
391
392    /// Retrieves the Unix timestamp in UTC.
393    #[inline]
394    pub fn timestamp_secs(&self) -> i64 {
395        self.0.timestamp()
396    }
397
398    /// Retrieves the timezone offset, as represented by the number of seconds to
399    /// add to convert local time to UTC.
400    #[inline]
401    pub fn tz_offset_secs(&self) -> i32 {
402        // This is the same as the way Mercurial stores timezone offsets.
403        self.0.offset().utc_minus_local()
404    }
405
406    #[inline]
407    pub fn as_chrono(&self) -> &ChronoDateTime<FixedOffset> {
408        &self.0
409    }
410
411    #[inline]
412    pub fn into_chrono(self) -> ChronoDateTime<FixedOffset> {
413        self.0
414    }
415}
416
417impl Display for DateTime {
418    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
419        write!(fmt, "{}", self.0)
420    }
421}
422
423/// Number of non-leap-nanoseconds since January 1, 1970 UTC
424#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
425pub struct Timestamp(i64);
426
427impl Timestamp {
428    pub fn now() -> Self {
429        DateTime::now().into()
430    }
431
432    pub fn from_timestamp_nanos(ts: i64) -> Self {
433        Timestamp(ts)
434    }
435
436    pub fn timestamp_nanos(self) -> i64 {
437        self.0
438    }
439}
440
441impl From<DateTime> for Timestamp {
442    fn from(dt: DateTime) -> Self {
443        Timestamp(dt.0.timestamp_nanos())
444    }
445}
446
447impl From<Timestamp> for DateTime {
448    fn from(ts: Timestamp) -> Self {
449        let ts_secs = ts.0 / 1_000_000_000;
450        let ts_nsecs = (ts.0 % 1_000_000_000) as u32;
451        DateTime::new(ChronoDateTime::<FixedOffset>::from_utc(
452            NaiveDateTime::from_timestamp(ts_secs, ts_nsecs),
453            FixedOffset::west(0),
454        ))
455    }
456}
457
458pub struct MercurialTag {
459    pub node: NodeHash,
460    pub name: String,
461}
462
463impl FromStr for MercurialTag {
464    type Err = ErrorKind;
465
466    fn from_str(value: &str) -> Result<Self, Self::Err> {
467        let mut parts = value.split_whitespace();
468        if let (Some(node), Some(name)) = (
469            parts
470                .next()
471                .and_then(|x| if x.len() == 40 { Some(x) } else { None })
472                .and_then(|x| x.parse().ok()),
473            parts.next().map(String::from),
474        ) {
475            Ok(Self { node, name })
476        } else {
477            Err(ErrorKind::Parser)
478        }
479    }
480}