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, 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())?
)
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 bytes
::complete
::{tag, take_while}
,
80 error
::{context, ContextError, ParseError}
,
81 sequence
::{terminated, tuple}
,
85 use crate::{file::log::LineRef, parse::hex_hash}
;
89 use gix_object
::bstr
::{BString, ByteSlice}
;
91 /// The error returned by [from_bytes(…)][super::Line::from_bytes()]
97 impl std
::fmt
::Display
for Error
{
98 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
101 "{:?} did not match '<old-hexsha> <new-hexsha> <name> <<email>> <timestamp> <tz>\\t<message>'",
107 impl std
::error
::Error
for Error {}
110 pub(crate) fn new(input
: &[u8]) -> Self {
112 input
: input
.as_bstr().to_owned(),
117 pub use error
::Error
;
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
))
126 fn message
<'a
, E
: ParseError
<&'a
[u8]>>(i
: &'a
[u8]) -> IResult
<&'a
[u8], &'a BStr
, E
> {
128 Ok((&[], i
.as_bstr()))
130 terminated(take_while(|c
| c
!= b'
\n'
), opt(tag(b
"\n")))(i
).map(|(i
, o
)| (i
, o
.as_bstr()))
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>",
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
),
142 context("<optional message>", message
),
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(
151 "log message must be separated from signature with whitespace",
152 E
::from_error_kind(i
, nom
::error
::ErrorKind
::MapRes
),
171 use gix_actor
::{Sign, Time}
;
172 use gix_object
::bstr
::ByteSlice
;
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")
181 fn with_newline(mut v
: Vec
<u8>) -> Vec
<u8> {
187 use gix_testtools
::to_bstr_err
;
188 use nom
::error
::VerboseError
;
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>"));
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");
208 .contains("log message must be separated from signature with whitespace"));
212 const NULL_SHA1
: &[u8] = b
"0000000000000000000000000000000000000000";
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
] {
220 one
::<nom
::error
::Error
<_
>>(input
).expect("successful parsing").1,
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(),
228 seconds_since_unix_epoch
: 1234567890,
229 offset_in_seconds
: 0,
233 message
: b
"".as_bstr(),
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());
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(),
254 seconds_since_unix_epoch
: 1618030561,
255 offset_in_seconds
: 28800,
259 message
: b
"pull --ff-only: Fast-forward".as_bstr(),
261 assert_eq
!(res
, actual
);
263 actual
.previous_oid(),
264 hex_to_oid("a5828ae6b52137b913b978e16cd2334482eb4c1f")
266 assert_eq
!(actual
.new_oid(), hex_to_oid("89b43f80a514aee58b662ad606e6352e03eaeee4"));
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");
276 let (remainder
, parsed
) = one
::<nom
::error
::Error
<_
>>(remainder
).expect("parse single line");
280 "second message is not and contains no newline"
282 assert
!(remainder
.is_empty());