Skip to content

Commit

Permalink
lib: add footer module for commit footers
Browse files Browse the repository at this point in the history
To be used for parsing `Change-Id`s from commits, in service of Gerrit
support.

Signed-off-by: Austin Seipp <[email protected]>
Change-Id: I434d76b1229b36b815622ad7409ced3a405cbe22
  • Loading branch information
thoughtpolice committed Feb 23, 2024
1 parent 2f5ce9d commit b71273f
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ gix = { workspace = true }
glob = { workspace = true }
hex = { workspace = true }
ignore = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
jj-lib-proc-macros = { workspace = true }
maplit = { workspace = true }
Expand Down
144 changes: 144 additions & 0 deletions lib/src/footer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Parsing footer lines from commit messages.
use indexmap::IndexMap;

/// Parse the footer lines from a commit message; these are simple key-value
/// pairs, separated by a colon, describing extra information in a commit
/// message; an example is the following:
///
/// ```text
/// chore: fix bug 1234
///
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
/// tempor incididunt ut labore et dolore magna aliqua.
///
/// Co-authored-by: Alice <[email protected]>
/// Co-authored-by: Bob <[email protected]>
/// Reviewed-by: Charlie <[email protected]>
/// Change-Id: I1234567890abcdef1234567890abcdef12345678
/// ```
///
/// In this case, there are four footer lines: two `Co-authored-by` lines, one
/// `Reviewed-by` line, and one `Change-Id` line.
pub fn get_footer_lines(body: &str) -> Option<IndexMap<String, Vec<String>>> {
// a footer always comes at the end of a message; we can split the message
// by newline, but we need to immediately reverse the order of the lines
// to ensure we parse the footer in an unambiguous manner; this avoids cases
// where a colon in the body of the message is mistaken for a footer line

let lines = body.trim().lines().rev().collect::<Vec<&str>>();

// short-circuit if there is only 1 line; this avoids a case where a commit
// with a single-line description like 'cli: fix bug' does not have a
// footer, but would otherwise be mistaken for a footer line
if lines.len() <= 1 {
return None;
}

let mut footer = IndexMap::new();
for line in lines {
if line.is_empty() {
break;
}
if let Some((key, value)) = line.split_once(": ") {
let key = key.trim();
let value = value.trim();
footer
.entry(key.to_string())
.or_insert_with(Vec::new)
.push(value.to_string());
} else {
break;
}
}

// reverse the insert order, since we parsed the footer in reverse
footer.reverse();

// reverse the order of the values for each key, since we reversed the
// order of the lines via 'push'
for (_, values) in footer.iter_mut() {
values.reverse();
}

if footer.is_empty() {
None
} else {
Some(footer)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_simple_footer_lines() {
let body = r#"chore: fix bug 1234
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Acked-by: Austin Seipp <[email protected]>
Reviewed-by: Yuya Nishihara <[email protected]>
Reviewed-by: Martin von Zweigbergk <[email protected]>
Change-Id: I1234567890abcdef1234567890abcdef12345678"#;

let footer = get_footer_lines(body).unwrap();
assert_eq!(footer.len(), 3);

assert_eq!(
footer.get_index(0).unwrap().1,
&vec!["Austin Seipp <[email protected]>"]
);

assert_eq!(
footer.get_index(1).unwrap().1,
&vec![
"Yuya Nishihara <[email protected]>",
"Martin von Zweigbergk <[email protected]>",
]
);
assert_eq!(
footer.get_index(2).unwrap().1,
&vec!["I1234567890abcdef1234567890abcdef12345678"]
);
}

#[test]
fn test_footer_lines_with_colon_in_body() {
let body = r#"chore: fix bug 1234
Summary: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
Change-Id: I1234567890abcdef1234567890abcdef12345678"#;

let footer = get_footer_lines(body).unwrap();

// should only have Change-Id
assert_eq!(footer.len(), 1);
assert_ne!(footer.get("Change-Id"), None);
}

#[test]
fn test_footer_lines_with_single_line_description() {
let body = r#"chore: fix bug 1234"#;
let footer = get_footer_lines(body);
assert_eq!(footer, None);
}
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub mod diff;
pub mod file_util;
pub mod files;
pub mod fmt_util;
pub mod footer;
pub mod fsmonitor;
pub mod git;
pub mod git_backend;
Expand Down

0 comments on commit b71273f

Please sign in to comment.