History

feat(commit): Add `message()` method and `MessageRef` type…(#198)

…allowing a better understanding of typical commit messages.

Sebastian Thiel committed 5 years ago

Commit 6150b2d1 parent 32dd2806 files · +625 −521Browse files

Showing the changes to git-object/src/commit (2 of 6 files). Show all

Unified · Split

2 files
  1. git-object/src/commit/message.rs +71 −0
  2. git-object/src/commit/mod.rs +20 −0
git-object/src/commit/message.rs+71−0
@@ -0,0 +1,71 @@
1use crate::bstr::{BStr, BString, ByteSlice, ByteVec};
2use crate::commit::MessageRef;
3use std::borrow::Cow;
4
5mod decode {
6 use crate::bstr::BStr;
7 use nom::error::ParseError;
8 use nom::IResult;
9
10 /// Returns title and body, without separator
11 pub fn bytes<'a, E: ParseError<&'a [u8]>>(_i: &'a [u8]) -> IResult<&[u8], (&'a BStr, &'a BStr), E> {
12 todo!("actual decoding")
13 }
14}
15
16impl<'a> MessageRef<'a> {
17 /// Parse the given `input` as message.
18 ///
19 /// Note that this cannot fail as everything will be interpreted as title if there is no body separator.
20 pub fn from_bytes(input: &'a [u8]) -> Self {
21 let (rest, (title, body)) = decode::bytes::<()>(input)
22 .map(|(i, (title, body))| (i, (title, Some(body))))
23 .unwrap_or_else(|_| (&[], (input.as_bstr(), None)));
24 debug_assert!(rest.is_empty(), "all consuming message parsing");
25 MessageRef { title, body }
26 }
27 /// Produce a short commit summary for the message title.
28 ///
29 /// This means the following
30 ///
31 /// * Take the subject line which is delimited by two newlines (\n\n)
32 /// * transform intermediate consecutive whitespace including \r into one space
33 ///
34 /// The resulting summary will have folded whitespace before a newline into spaces and stopped that process
35 /// once two consecutive newlines are encountered.
36 pub fn summary(&self) -> Cow<'a, BStr> {
37 let message = self.title.trim();
38 match message.find_byte(b'\n') {
39 Some(mut pos) => {
40 let mut out = BString::default();
41 let mut previous_pos = None;
42 loop {
43 if let Some(previous_pos) = previous_pos {
44 if previous_pos + 1 == pos {
45 let len_after_trim = out.trim_end().len();
46 out.resize(len_after_trim, 0);
47 break out.into();
48 }
49 }
50 let message_to_newline = &message[previous_pos.map(|p| p + 1).unwrap_or(0)..pos];
51
52 if let Some(pos_before_whitespace) = message_to_newline.rfind_not_byteset(b"\t\n\x0C\r ") {
53 out.extend_from_slice(&message_to_newline[..pos_before_whitespace + 1]);
54 }
55 out.push_byte(b' ');
56 previous_pos = Some(pos);
57 match message.get(pos + 1..).and_then(|i| i.find_byte(b'\n')) {
58 Some(next_nl_pos) => pos += next_nl_pos + 1,
59 None => {
60 if let Some(slice) = message.get((pos + 1)..) {
61 out.extend_from_slice(slice);
62 }
63 break out.into();
64 }
65 }
66 }
67 }
68 None => message.as_bstr().into(),
69 }
70 }
71}
git-object/src/commit/mod.rs+20−0
@@ -3,6 +3,21 @@ use bstr::{BStr, ByteSlice}; · 2 unmodified lines
3use crate::{Commit, CommitRef, TagRef};3use crate::{Commit, CommitRef, TagRef};
4 4
5mod decode;5mod decode;
6mod message;
7
8/// A parsed commit message that assumes a title separated from the body by two consecutive newlines.
9///
10/// Titles can have any amount of whitespace
11#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
12#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
13pub struct MessageRef<'a> {
14 /// The title of the commit, as separated from the body with two consecutive newlines. The newlines are not included.
15 pub title: &'a BStr,
16 /// All bytes not consumed by the title, excluding the separating newlines.
17 ///
18 /// The body is `None` if there was now title separation or the body was empty after the separator.
19 pub body: Option<&'a BStr>,
20}
6 21
7///22///
8pub mod ref_iter;23pub mod ref_iter;
@@ -30,6 +45,11 @@ impl<'a> CommitRef<'a> { · 21 unmodified lines
30 pub fn extra_headers(&self) -> crate::commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {45 pub fn extra_headers(&self) -> crate::commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
31 crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))46 crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))
32 }47 }
48
49 /// Returns a partially parsed message from which more information can be derived.
50 pub fn message(&self) -> MessageRef<'a> {
51 MessageRef::from_bytes(&self.message)
52 }
33}53}
34 54
35impl Commit {55impl Commit {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t