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
12pub struct ChangesetHeader {
14 pub p1: Option<Revision>,
16 pub p2: Option<Revision>,
18 pub manifestid: NodeHash,
20 pub user: Vec<u8>,
22 pub time: DateTime,
24 pub extra: HashMap<Vec<u8>, Vec<u8>>,
26 pub files: Vec<Vec<u8>>,
28 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#[derive(Debug)]
172pub struct Changeset {
173 pub revision: Revision,
175 pub header: ChangesetHeader,
177 pub files: Vec<ChangesetFile>,
179}
180
181#[derive(Debug)]
184pub struct ChangesetFile {
185 pub path: Vec<u8>,
187 pub data: Option<Arc<[u8]>>,
189 pub manifest_entry: Option<ManifestEntry>,
191}