hg_parser/
changeset.rs

1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::sync::Arc;
4use std::{fmt, str};
5
6use crate::{
7    error::ErrorKind,
8    manifest::ManifestEntry,
9    types::{DateTime, NodeHash, Revision, RevisionLogEntry},
10};
11
12/// Changeset's header.
13pub struct ChangesetHeader {
14    /// Parent 1.
15    pub p1: Option<Revision>,
16    /// Parent 2.
17    pub p2: Option<Revision>,
18    /// Manifest id hash.
19    pub manifestid: NodeHash,
20    /// User.
21    pub user: Vec<u8>,
22    /// Time.
23    pub time: DateTime,
24    /// Extra properties (like b"branch" shows current branch name, b"closed" equal to b"1" means branch is closed).
25    pub extra: HashMap<Vec<u8>, Vec<u8>>,
26    /// Files paths.
27    pub files: Vec<Vec<u8>>,
28    /// Comment to revision.
29    pub comment: Vec<u8>,
30}
31
32impl Debug for ChangesetHeader {
33    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
34        writeln!(
35            fmt,
36            "ChangesetHeader(\np1:{:?}\np2:{:?}\nmanifestid:{}\nuser:{}\ntime:{:?}\nextra:{}\nfiles:\n{}\ncomment:\n{}\n)",
37            self.p1,
38            self.p2,
39            self.manifestid,
40            str::from_utf8(&self.user).expect("bad user utf8"),
41            self.time,
42            self.extra
43                .iter()
44                .map(|(key, value)| format!(
45                    "{}:{}",
46                    str::from_utf8(key).expect("bad extra key utf8"),
47                    str::from_utf8(value).unwrap_or("bad extra value utf8")
48                ))
49                .collect::<Vec<_>>()
50                .join(","),
51            self.files.iter()
52                .map(|key| str::from_utf8(key).unwrap().to_string())
53                .collect::<Vec<_>>()
54                .join("\n"),
55            str::from_utf8(&self.comment).expect("bad comment utf8"),
56        )
57    }
58}
59
60impl ChangesetHeader {
61    pub(crate) fn from_entry_bytes(
62        entry: &RevisionLogEntry,
63        data: &[u8],
64    ) -> Result<Self, ErrorKind> {
65        let mut lines = data.split(|&x| x == b'\n');
66        if let (Some(manifestid), Some(user), Some(timeextra)) = (
67            lines
68                .next()
69                .and_then(|x| str::from_utf8(x).ok())
70                .and_then(|x| x.parse().ok()),
71            lines.next(),
72            lines.next(),
73        ) {
74            let mut parts = timeextra.splitn(3, |&x| x == b' ');
75
76            if let (Some(time), Some(tz), extra) = (
77                parts
78                    .next()
79                    .and_then(|x| str::from_utf8(x).ok())
80                    .and_then(|x| x.parse().ok()),
81                parts
82                    .next()
83                    .and_then(|x| str::from_utf8(x).ok())
84                    .and_then(|x| x.parse().ok()),
85                parts.next(),
86            ) {
87                let mut files = Vec::new();
88                for file in lines.by_ref() {
89                    if file.is_empty() {
90                        break;
91                    }
92                    files.push(file.into());
93                }
94                Ok(ChangesetHeader {
95                    p1: entry.p1,
96                    p2: entry.p2,
97                    manifestid,
98                    user: user.into(),
99                    time: DateTime::from_timestamp(time, tz)?,
100                    extra: extra
101                        .map(|x| {
102                            x.split(|&y| y == 0)
103                                .filter_map(|y| {
104                                    let mut z = y.splitn(2, |&k| k == b':');
105                                    if let (Some(key), Some(value)) =
106                                        (z.next().map(unescape), z.next().map(unescape))
107                                    {
108                                        Some((key, value))
109                                    } else {
110                                        None
111                                    }
112                                })
113                                .collect()
114                        })
115                        .unwrap_or_else(HashMap::new),
116                    files,
117                    comment: lines.collect::<Vec<_>>().join(&b'\n'),
118                })
119            } else {
120                Err(ErrorKind::Parser)
121            }
122        } else {
123            Err(ErrorKind::Parser)
124        }
125    }
126}
127
128fn unescape<'a, S: IntoIterator<Item = &'a u8>>(s: S) -> Vec<u8> {
129    let mut ret = Vec::new();
130    let mut quote = false;
131
132    for c in s.into_iter() {
133        match *c {
134            b'0' if quote => {
135                quote = false;
136                ret.push(b'\0');
137            }
138            b'n' if quote => {
139                quote = false;
140                ret.push(b'\n');
141            }
142            b'r' if quote => {
143                quote = false;
144                ret.push(b'\r');
145            }
146            b'\\' if quote => {
147                quote = false;
148                ret.push(b'\\');
149            }
150            c if quote => {
151                quote = false;
152                ret.push(b'\\');
153                ret.push(c)
154            }
155            b'\\' => {
156                assert!(!quote);
157                quote = true;
158            }
159            c => {
160                quote = false;
161                ret.push(c);
162            }
163        }
164    }
165
166    ret
167}
168
169/// `Changeset` info about revision, contains revision number, header and files
170/// with bodies and metadata.
171#[derive(Debug)]
172pub struct Changeset {
173    /// Revision.
174    pub revision: Revision,
175    /// Header with parents, manifest, user, date, extra, files, and comment attributes.
176    pub header: ChangesetHeader,
177    /// File list for revision.
178    pub files: Vec<ChangesetFile>,
179}
180
181/// Changeset's file. Contains path info, internal blob from Mercurial
182/// (use [file_content](fn.file_content.html) for actual file data) and meta information.
183#[derive(Debug)]
184pub struct ChangesetFile {
185    /// Path.
186    pub path: Vec<u8>,
187    /// Data of file, `None` - file was deleted, otherwise it was added or modified.
188    pub data: Option<Arc<[u8]>>,
189    /// Meta information, `None` - file was deleted, otherwise it was added or modified.
190    pub manifest_entry: Option<ManifestEntry>,
191}