]> git.proxmox.com Git - rustc.git/blob - vendor/gix-ref/src/store/file/log/line.rs
New upstream version 1.70.0+dfsg2
[rustc.git] / vendor / gix-ref / src / store / file / log / line.rs
1 use gix_hash::ObjectId;
2
3 use crate::{log::Line, store_impl::file::log::LineRef};
4
5 impl<'a> LineRef<'a> {
6 /// Convert this instance into its mutable counterpart
7 pub fn to_owned(&self) -> Line {
8 self.clone().into()
9 }
10 }
11
12 mod write {
13 use std::io;
14
15 use gix_object::bstr::{BStr, ByteSlice};
16
17 use crate::log::Line;
18
19 /// The Error produced by [`Line::write_to()`] (but wrapped in an io error).
20 #[derive(Debug, thiserror::Error)]
21 #[allow(missing_docs)]
22 enum Error {
23 #[error("Messages must not contain newlines\\n")]
24 IllegalCharacter,
25 }
26
27 impl From<Error> for io::Error {
28 fn from(err: Error) -> Self {
29 io::Error::new(io::ErrorKind::Other, err)
30 }
31 }
32
33 /// Output
34 impl Line {
35 /// Serialize this instance to `out` in the git serialization format for ref log lines.
36 pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
37 write!(out, "{} {} ", self.previous_oid, self.new_oid)?;
38 self.signature.write_to(&mut out)?;
39 writeln!(out, "\t{}", check_newlines(self.message.as_ref())?)
40 }
41 }
42
43 fn check_newlines(input: &BStr) -> Result<&BStr, Error> {
44 if input.find_byte(b'\n').is_some() {
45 return Err(Error::IllegalCharacter);
46 }
47 Ok(input)
48 }
49 }
50
51 impl<'a> LineRef<'a> {
52 /// The previous object id of the ref. It will be a null hash if there was no previous id as
53 /// this ref is being created.
54 pub fn previous_oid(&self) -> ObjectId {
55 ObjectId::from_hex(self.previous_oid).expect("parse validation")
56 }
57 /// The new object id of the ref, or a null hash if it is removed.
58 pub fn new_oid(&self) -> ObjectId {
59 ObjectId::from_hex(self.new_oid).expect("parse validation")
60 }
61 }
62
63 impl<'a> From<LineRef<'a>> for Line {
64 fn from(v: LineRef<'a>) -> Self {
65 Line {
66 previous_oid: v.previous_oid(),
67 new_oid: v.new_oid(),
68 signature: v.signature.into(),
69 message: v.message.into(),
70 }
71 }
72 }
73
74 ///
75 pub mod decode {
76 use gix_object::bstr::{BStr, ByteSlice};
77 use nom::{
78 bytes::complete::{tag, take_while},
79 combinator::opt,
80 error::{context, ContextError, ParseError},
81 sequence::{terminated, tuple},
82 IResult,
83 };
84
85 use crate::{file::log::LineRef, parse::hex_hash};
86
87 ///
88 mod error {
89 use gix_object::bstr::{BString, ByteSlice};
90
91 /// The error returned by [from_bytes(…)][super::Line::from_bytes()]
92 #[derive(Debug)]
93 pub struct Error {
94 pub input: BString,
95 }
96
97 impl std::fmt::Display for Error {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 write!(
100 f,
101 "{:?} did not match '<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\\t<message>'",
102 self.input
103 )
104 }
105 }
106
107 impl std::error::Error for Error {}
108
109 impl Error {
110 pub(crate) fn new(input: &[u8]) -> Self {
111 Error {
112 input: input.as_bstr().to_owned(),
113 }
114 }
115 }
116 }
117 pub use error::Error;
118
119 impl<'a> LineRef<'a> {
120 /// Decode a line from the given bytes which are expected to start at a hex sha.
121 pub fn from_bytes(input: &'a [u8]) -> Result<LineRef<'a>, Error> {
122 one::<()>(input).map(|(_, l)| l).map_err(|_| Error::new(input))
123 }
124 }
125
126 fn message<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a BStr, E> {
127 if i.is_empty() {
128 Ok((&[], i.as_bstr()))
129 } else {
130 terminated(take_while(|c| c != b'\n'), opt(tag(b"\n")))(i).map(|(i, o)| (i, o.as_bstr()))
131 }
132 }
133
134 fn one<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(bytes: &'a [u8]) -> IResult<&[u8], LineRef<'a>, E> {
135 let (i, (old, new, signature, message_sep, message)) = context(
136 "<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\\t<message>",
137 tuple((
138 context("<old-hexsha>", terminated(hex_hash, tag(b" "))),
139 context("<new-hexsha>", terminated(hex_hash, tag(b" "))),
140 context("<name> <<email>> <timestamp>", gix_actor::signature::decode),
141 opt(tag(b"\t")),
142 context("<optional message>", message),
143 )),
144 )(bytes)?;
145
146 if message_sep.is_none() {
147 if let Some(first) = message.first() {
148 if !first.is_ascii_whitespace() {
149 return Err(nom::Err::Error(E::add_context(
150 i,
151 "log message must be separated from signature with whitespace",
152 E::from_error_kind(i, nom::error::ErrorKind::MapRes),
153 )));
154 }
155 }
156 }
157
158 Ok((
159 i,
160 LineRef {
161 previous_oid: old,
162 new_oid: new,
163 signature,
164 message,
165 },
166 ))
167 }
168
169 #[cfg(test)]
170 mod test {
171 use gix_actor::{Sign, Time};
172 use gix_object::bstr::ByteSlice;
173
174 use super::*;
175
176 /// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_.
177 fn hex_to_oid(hex: &str) -> gix_hash::ObjectId {
178 gix_hash::ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex")
179 }
180
181 fn with_newline(mut v: Vec<u8>) -> Vec<u8> {
182 v.push(b'\n');
183 v
184 }
185
186 mod invalid {
187 use gix_testtools::to_bstr_err;
188 use nom::error::VerboseError;
189
190 use super::one;
191
192 #[test]
193 fn completely_bogus_shows_error_with_context() {
194 let err = one::<VerboseError<&[u8]>>(b"definitely not a log entry")
195 .map_err(to_bstr_err)
196 .expect_err("this should fail");
197 assert!(err.to_string().contains("<old-hexsha> <new-hexsha>"));
198 }
199
200 #[test]
201 fn missing_whitespace_between_signature_and_message() {
202 let line = "0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000message";
203 let err = one::<VerboseError<&[u8]>>(line.as_bytes())
204 .map_err(to_bstr_err)
205 .expect_err("this should fail");
206 assert!(err
207 .to_string()
208 .contains("log message must be separated from signature with whitespace"));
209 }
210 }
211
212 const NULL_SHA1: &[u8] = b"0000000000000000000000000000000000000000";
213
214 #[test]
215 fn entry_with_empty_message() {
216 let line_without_nl: Vec<_> = b"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 name <foo@example.com> 1234567890 -0000".to_vec();
217 let line_with_nl = with_newline(line_without_nl.clone());
218 for input in &[line_without_nl, line_with_nl] {
219 assert_eq!(
220 one::<nom::error::Error<_>>(input).expect("successful parsing").1,
221 LineRef {
222 previous_oid: NULL_SHA1.as_bstr(),
223 new_oid: NULL_SHA1.as_bstr(),
224 signature: gix_actor::SignatureRef {
225 name: b"name".as_bstr(),
226 email: b"foo@example.com".as_bstr(),
227 time: Time {
228 seconds_since_unix_epoch: 1234567890,
229 offset_in_seconds: 0,
230 sign: Sign::Minus
231 }
232 },
233 message: b"".as_bstr(),
234 }
235 );
236 }
237 }
238
239 #[test]
240 fn entry_with_message_without_newline_and_with_newline() {
241 let line_without_nl: Vec<_> = b"a5828ae6b52137b913b978e16cd2334482eb4c1f 89b43f80a514aee58b662ad606e6352e03eaeee4 Sebastian Thiel <foo@example.com> 1618030561 +0800\tpull --ff-only: Fast-forward".to_vec();
242 let line_with_nl = with_newline(line_without_nl.clone());
243
244 for input in &[line_without_nl, line_with_nl] {
245 let (remaining, res) = one::<nom::error::Error<_>>(input).expect("successful parsing");
246 assert!(remaining.is_empty(), "all consuming even without trailing newline");
247 let actual = LineRef {
248 previous_oid: b"a5828ae6b52137b913b978e16cd2334482eb4c1f".as_bstr(),
249 new_oid: b"89b43f80a514aee58b662ad606e6352e03eaeee4".as_bstr(),
250 signature: gix_actor::SignatureRef {
251 name: b"Sebastian Thiel".as_bstr(),
252 email: b"foo@example.com".as_bstr(),
253 time: Time {
254 seconds_since_unix_epoch: 1618030561,
255 offset_in_seconds: 28800,
256 sign: Sign::Plus,
257 },
258 },
259 message: b"pull --ff-only: Fast-forward".as_bstr(),
260 };
261 assert_eq!(res, actual);
262 assert_eq!(
263 actual.previous_oid(),
264 hex_to_oid("a5828ae6b52137b913b978e16cd2334482eb4c1f")
265 );
266 assert_eq!(actual.new_oid(), hex_to_oid("89b43f80a514aee58b662ad606e6352e03eaeee4"));
267 }
268 }
269
270 #[test]
271 fn two_lines_in_a_row_with_and_without_newline() {
272 let lines = b"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000\t\n0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 two <foo@example.com> 1234567890 -0000\thello";
273 let (remainder, parsed) = one::<nom::error::Error<_>>(lines).expect("parse single line");
274 assert_eq!(parsed.message, b"".as_bstr(), "first message is empty");
275
276 let (remainder, parsed) = one::<nom::error::Error<_>>(remainder).expect("parse single line");
277 assert_eq!(
278 parsed.message,
279 b"hello".as_bstr(),
280 "second message is not and contains no newline"
281 );
282 assert!(remainder.is_empty());
283 }
284 }
285 }