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 @@
1+use crate::bstr::{BStr, BString, ByteSlice, ByteVec};
2+use crate::commit::MessageRef;
3+use std::borrow::Cow;
4+
5+mod 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+
16+impl<'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
33 use crate::{Commit, CommitRef, TagRef};
44
55 mod decode;
6+mod 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))]
13+pub 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+}
621
722 ///
823 pub mod ref_iter;
@@ -30,6 +45,11 @@ impl<'a> CommitRef<'a> { · 21 unmodified lines
3045 pub fn extra_headers(&self) -> crate::commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
3146 crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))
3247 }
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+ }
3353 }
3454
3555 impl 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