gix_ref/store/file/log/
line.rs1use gix_hash::ObjectId;
2
3use crate::{log::Line, store_impl::file::log::LineRef};
4
5impl LineRef<'_> {
6 pub fn to_owned(&self) -> Line {
8 (*self).into()
9 }
10}
11
12mod write {
13 use std::io;
14
15 use gix_object::bstr::{BStr, ByteSlice};
16
17 use crate::log::Line;
18
19 #[derive(Debug, thiserror::Error)]
21 #[allow(missing_docs)]
22 enum Error {
23 #[error(r"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::other(err)
30 }
31 }
32
33 impl Line {
35 pub fn write_to(&self, out: &mut dyn io::Write) -> io::Result<()> {
37 write!(out, "{} {} ", self.previous_oid, self.new_oid)?;
38 self.signature.write_to(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
51impl LineRef<'_> {
52 pub fn previous_oid(&self) -> ObjectId {
55 ObjectId::from_hex(self.previous_oid).expect("parse validation")
56 }
57 pub fn new_oid(&self) -> ObjectId {
59 ObjectId::from_hex(self.new_oid).expect("parse validation")
60 }
61}
62
63impl<'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
74pub mod decode {
76 use gix_object::bstr::{BStr, ByteSlice};
77
78 use crate::{file::log::LineRef, parse::hex_hash_any};
79
80 mod error {
82 use gix_object::bstr::{BString, ByteSlice};
83
84 #[derive(Debug)]
86 pub struct Error {
87 pub input: BString,
88 }
89
90 impl std::fmt::Display for Error {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(
93 f,
94 r"{:?} did not match '<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\t<message>'",
95 self.input
96 )
97 }
98 }
99
100 impl std::error::Error for Error {}
101
102 impl Error {
103 pub(crate) fn new(input: &[u8]) -> Self {
104 Error {
105 input: input.as_bstr().to_owned(),
106 }
107 }
108 }
109 }
110 pub use error::Error;
111
112 impl<'a> LineRef<'a> {
113 pub fn from_bytes(input: &'a [u8]) -> Result<LineRef<'a>, Error> {
120 decode(input).map_err(|_| Error::new(first_line(input)))
121 }
122 }
123
124 fn first_line(input: &[u8]) -> &[u8] {
128 let line_end = input.iter().position(|b| *b == b'\n').unwrap_or(input.len());
129 &input[..line_end]
130 }
131
132 fn decode(bytes: &[u8]) -> Result<LineRef<'_>, ()> {
140 let line = first_line(bytes);
141 let (mut head, message) = match line.find_byte(b'\t') {
142 Some(tab) => (&line[..tab], line[tab + 1..].as_bstr()),
143 None => (line, BStr::new(b"")),
144 };
145
146 let old = hex_hash_any(&mut head)?;
147 head = head.strip_prefix(b" ").ok_or(())?;
148 let new = hex_hash_any(&mut head)?;
149 head = head.strip_prefix(b" ").ok_or(())?;
150 let signature = gix_actor::signature::decode(&mut head).map_err(|_| ())?;
151 if !head.is_empty() {
152 return Err(());
153 }
154 Ok(LineRef {
155 previous_oid: old,
156 new_oid: new,
157 signature,
158 message,
159 })
160 }
161
162 #[cfg(test)]
163 mod test_decode {
164 use super::*;
165
166 fn hex_to_oid(hex: &str) -> gix_hash::ObjectId {
168 gix_hash::ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex")
169 }
170
171 fn with_newline(mut v: Vec<u8>) -> Vec<u8> {
172 v.push(b'\n');
173 v
174 }
175
176 mod invalid {
177 use super::decode;
178
179 #[test]
180 fn completely_bogus_shows_error_with_context() {
181 let input = b"definitely not a log entry".as_slice();
182 decode(input).expect_err("this should fail");
183 }
184
185 #[test]
186 fn missing_whitespace_between_signature_and_message() {
187 let line = "0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000message";
188 decode(line.as_bytes()).expect_err("this should fail");
189 }
190 }
191
192 const NULL_SHA1: &[u8] = b"0000000000000000000000000000000000000000";
193
194 #[test]
195 fn entry_with_empty_message() {
196 let line_without_nl: Vec<_> = b"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 name <foo@example.com> 1234567890 -0000".to_vec();
197 let line_with_nl = with_newline(line_without_nl.clone());
198 for input in &[line_without_nl, line_with_nl] {
199 assert_eq!(
200 decode(input.as_slice()).expect("successful parsing"),
201 LineRef {
202 previous_oid: NULL_SHA1.as_bstr(),
203 new_oid: NULL_SHA1.as_bstr(),
204 signature: gix_actor::SignatureRef {
205 name: b"name".as_bstr(),
206 email: b"foo@example.com".as_bstr(),
207 time: "1234567890 -0000"
208 },
209 message: b"".as_bstr(),
210 }
211 );
212 }
213 }
214
215 #[test]
216 fn entry_with_message_without_newline_and_with_newline() {
217 let line_without_nl: Vec<_> = b"a5828ae6b52137b913b978e16cd2334482eb4c1f 89b43f80a514aee58b662ad606e6352e03eaeee4 Sebastian Thiel <foo@example.com> 1618030561 +0800\tpull --ff-only: Fast-forward".to_vec();
218 let line_with_nl = with_newline(line_without_nl.clone());
219
220 for input in &[line_without_nl, line_with_nl] {
221 let res = decode(input.as_slice()).expect("successful parsing");
222 let actual = LineRef {
223 previous_oid: b"a5828ae6b52137b913b978e16cd2334482eb4c1f".as_bstr(),
224 new_oid: b"89b43f80a514aee58b662ad606e6352e03eaeee4".as_bstr(),
225 signature: gix_actor::SignatureRef {
226 name: b"Sebastian Thiel".as_bstr(),
227 email: b"foo@example.com".as_bstr(),
228 time: "1618030561 +0800",
229 },
230 message: b"pull --ff-only: Fast-forward".as_bstr(),
231 };
232 assert_eq!(res, actual);
233 assert_eq!(
234 actual.previous_oid(),
235 hex_to_oid("a5828ae6b52137b913b978e16cd2334482eb4c1f")
236 );
237 assert_eq!(actual.new_oid(), hex_to_oid("89b43f80a514aee58b662ad606e6352e03eaeee4"));
238 }
239 }
240
241 #[test]
242 fn two_lines_in_a_row_with_and_without_newline() {
243 let lines = b"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000\t\n0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 two <foo@example.com> 1234567890 -0000\thello";
244 let parsed = decode(lines.as_slice()).expect("parse single line");
245 assert_eq!(parsed.message, b"".as_bstr(), "first message is empty");
246 }
247 }
248}