1 use gix_hash
::ObjectId
;
3 use crate::{log::Line, store_impl::file::log::LineRef}
;
6 /// Convert this instance into its mutable counterpart
7 pub fn to_owned(&self) -> Line
{
15 use gix_object
::bstr
::{BStr, ByteSlice}
;
19 /// The Error produced by [`Line::write_to()`] (but wrapped in an io error).
20 #[derive(Debug, thiserror::Error)]
21 #[allow(missing_docs)]
23 #[error("Messages must not contain newlines\\n")]
27 impl From
<Error
> for io
::Error
{
28 fn from(err
: Error
) -> Self {
29 io
::Error
::new(io
::ErrorKind
::Other
, err
)
35 /// Serialize this instance to `out` in the git serialization format for ref log lines.
36 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())?
)
43 fn check_newlines(input
: &BStr
) -> Result
<&BStr
, Error
> {
44 if input
.find_byte(b'
\n'
).is_some() {
45 return Err(Error
::IllegalCharacter
);
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")
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")
63 impl<'a
> From
<LineRef
<'a
>> for Line
{
64 fn from(v
: LineRef
<'a
>) -> Self {
66 previous_oid
: v
.previous_oid(),
68 signature
: v
.signature
.into(),
69 message
: v
.message
.into(),
76 use gix_object
::bstr
::{BStr, ByteSlice}
;
78 combinator
::{alt, eof, fail, opt, preceded, rest, terminated}
,
79 error
::{AddContext, ParserError, StrContext}
,
84 use crate::{file::log::LineRef, parse::hex_hash}
;
88 use gix_object
::bstr
::{BString, ByteSlice}
;
90 /// The error returned by [`from_bytes(…)`][super::Line::from_bytes()]
96 impl std
::fmt
::Display
for Error
{
97 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
100 "{:?} did not match '<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\\t<message>'",
106 impl std
::error
::Error
for Error {}
109 pub(crate) fn new(input
: &[u8]) -> Self {
111 input
: input
.as_bstr().to_owned(),
116 pub use error
::Error
;
118 impl<'a
> LineRef
<'a
> {
119 /// Decode a line from the given bytes which are expected to start at a hex sha.
120 pub fn from_bytes(mut input
: &'a
[u8]) -> Result
<LineRef
<'a
>, Error
> {
121 one
::<()>(&mut input
).map_err(|_
| Error
::new(input
))
125 fn message
<'a
, E
: ParserError
<&'a
[u8]>>(i
: &mut &'a
[u8]) -> PResult
<&'a BStr
, E
> {
127 rest
.map(ByteSlice
::as_bstr
).parse_next(i
)
129 terminated(take_while(0.., |c
| c
!= b'
\n'
), opt(b'
\n'
))
130 .map(ByteSlice
::as_bstr
)
135 fn one
<'a
, E
: ParserError
<&'a
[u8]> + AddContext
<&'a
[u8], StrContext
>>(
136 bytes
: &mut &'a
[u8],
137 ) -> PResult
<LineRef
<'a
>, E
> {
140 terminated(hex_hash
, b
" ").context(StrContext
::Expected("<old-hexsha>".into())),
141 terminated(hex_hash
, b
" ").context(StrContext
::Expected("<new-hexsha>".into())),
142 gix_actor
::signature
::decode
.context(StrContext
::Expected("<name> <<email>> <timestamp>".into())),
144 .context(StrContext
::Expected(
145 "<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\\t<message>".into(),
150 message
.context(StrContext
::Expected("<optional message>".into())),
152 b'
\n'
.value(Default
::default()),
153 eof
.value(Default
::default()),
154 fail
.context(StrContext
::Expected(
155 "log message must be separated from signature with whitespace".into(),
159 .map(|((old
, new
, signature
), message
)| LineRef
{
170 use gix_date
::{time::Sign, Time}
;
171 use gix_object
::bstr
::ByteSlice
;
175 /// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_.
176 fn hex_to_oid(hex
: &str) -> gix_hash
::ObjectId
{
177 gix_hash
::ObjectId
::from_hex(hex
.as_bytes()).expect("40 bytes hex")
180 fn with_newline(mut v
: Vec
<u8>) -> Vec
<u8> {
186 use gix_testtools
::to_bstr_err
;
187 use winnow
::{error::TreeError, prelude::*}
;
192 fn completely_bogus_shows_error_with_context() {
193 let err
= one
::<TreeError
<&[u8], _
>>
194 .parse_peek(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>"));
201 fn missing_whitespace_between_signature_and_message() {
202 let line
= "0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000message";
203 let err
= one
::<TreeError
<&[u8], _
>>
204 .parse_peek(line
.as_bytes())
205 .map_err(to_bstr_err
)
206 .expect_err("this should fail");
209 .contains("log message must be separated from signature with whitespace"),
210 "expected\n `log message must be separated from signature with whitespace`\nin\n```\n{err}\n```"
215 const NULL_SHA1
: &[u8] = b
"0000000000000000000000000000000000000000";
218 fn entry_with_empty_message() {
219 let line_without_nl
: Vec
<_
> = b
"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 name <foo@example.com> 1234567890 -0000".to_vec();
220 let line_with_nl
= with_newline(line_without_nl
.clone());
221 for input
in &[line_without_nl
, line_with_nl
] {
223 one
::<winnow
::error
::InputError
<_
>>
225 .expect("successful parsing")
228 previous_oid
: NULL_SHA1
.as_bstr(),
229 new_oid
: NULL_SHA1
.as_bstr(),
230 signature
: gix_actor
::SignatureRef
{
231 name
: b
"name".as_bstr(),
232 email
: b
"foo@example.com".as_bstr(),
239 message
: b
"".as_bstr(),
246 fn entry_with_message_without_newline_and_with_newline() {
247 let line_without_nl
: Vec
<_
> = b
"a5828ae6b52137b913b978e16cd2334482eb4c1f 89b43f80a514aee58b662ad606e6352e03eaeee4 Sebastian Thiel <foo@example.com> 1618030561 +0800\tpull --ff-only: Fast-forward".to_vec();
248 let line_with_nl
= with_newline(line_without_nl
.clone());
250 for input
in &[line_without_nl
, line_with_nl
] {
251 let (remaining
, res
) = one
::<winnow
::error
::InputError
<_
>>
253 .expect("successful parsing");
254 assert
!(remaining
.is_empty(), "all consuming even without trailing newline");
255 let actual
= LineRef
{
256 previous_oid
: b
"a5828ae6b52137b913b978e16cd2334482eb4c1f".as_bstr(),
257 new_oid
: b
"89b43f80a514aee58b662ad606e6352e03eaeee4".as_bstr(),
258 signature
: gix_actor
::SignatureRef
{
259 name
: b
"Sebastian Thiel".as_bstr(),
260 email
: b
"foo@example.com".as_bstr(),
267 message
: b
"pull --ff-only: Fast-forward".as_bstr(),
269 assert_eq
!(res
, actual
);
271 actual
.previous_oid(),
272 hex_to_oid("a5828ae6b52137b913b978e16cd2334482eb4c1f")
274 assert_eq
!(actual
.new_oid(), hex_to_oid("89b43f80a514aee58b662ad606e6352e03eaeee4"));
279 fn two_lines_in_a_row_with_and_without_newline() {
280 let lines
= b
"0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 one <foo@example.com> 1234567890 -0000\t\n0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 two <foo@example.com> 1234567890 -0000\thello";
281 let (remainder
, parsed
) = one
::<winnow
::error
::InputError
<_
>>
283 .expect("parse single line");
284 assert_eq
!(parsed
.message
, b
"".as_bstr(), "first message is empty");
286 let (remainder
, parsed
) = one
::<winnow
::error
::InputError
<_
>>
287 .parse_peek(remainder
)
288 .expect("parse single line");
292 "second message is not and contains no newline"
294 assert
!(remainder
.is_empty());