Skip to content

Commit

Permalink
feat(changelog): support external commands for commit preprocessors (#86
Browse files Browse the repository at this point in the history
)
  • Loading branch information
orhun committed Jun 7, 2022
1 parent 8d8981c commit 7d0786c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 16 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,19 @@ Examples:
- `{ pattern = "([ \\n])(([a-f0-9]{7})[a-f0-9]*)", replace = "${1}commit # [${3}](https://github.com/orhun/git-cliff/commit/${2})"}`
- Hyperlink bare commit hashes like "abcd1234" in commit logs, with short commit hash as description.

Custom OS commands can also be used for modifying the commit messages:

- `{ pattern = "foo", replace_command = "pandoc -t commonmark"}`

This is useful when you want to filter/encode messages using external commands. In the example above, [pandoc](https://pandoc.org/) is used to convert each commit message that matches the given `pattern` to the [CommonMark](https://commonmark.org/) format.

A more fun example would be reversing the each commit message:

- `{ pattern = '.*', replace_command = 'rev | xargs echo "reversed: $@"' }`

`$COMMIT_SHA` environment variable is set during execution of the command so you can do fancier things like reading the commit itself:

- `{ pattern = '.*', replace_command = 'git show -s --format=%B $COMMIT_SHA' }`

#### commit_parsers

Expand Down
81 changes: 81 additions & 0 deletions git-cliff-core/src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::error::Result;
use std::io::{
Error as IoError,
ErrorKind as IoErrorKind,
Write,
};
use std::process::{
Command,
Stdio,
};
use std::str;
use std::thread;

/// Runs the given OS command and returns the output as string.
///
/// Use `input` parameter to specify a text to write to stdin.
/// Environment variables are set accordingly to `envs`.
pub fn run(
command: &str,
input: Option<String>,
envs: Vec<(&str, &str)>,
) -> Result<String> {
let mut child = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(&["/C", command])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
} else {
Command::new("sh")
.envs(envs)
.args(&["-c", command])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
}?;
if let Some(input) = input {
let mut stdin = child.stdin.take().ok_or_else(|| {
IoError::new(IoErrorKind::Other, "stdin is not captured")
})?;
thread::spawn(move || {
stdin
.write_all(input.as_bytes())
.expect("Failed to write to stdin");
});
}
let output = child.wait_with_output()?;
if output.status.success() {
Ok(str::from_utf8(&output.stdout)?.to_string())
} else {
Err(IoError::new(
IoErrorKind::Other,
format!("command exited with {:?}", output.status),
)
.into())
}
}

#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(target_family = "unix")]
fn run_os_command() -> Result<()> {
assert_eq!(
"eroc-ffilc-tig",
run("echo $APP_NAME | rev", None, vec![(
"APP_NAME",
env!("CARGO_PKG_NAME")
)])?
.trim()
);
assert_eq!(
"eroc-ffilc-tig",
run("rev", Some(env!("CARGO_PKG_NAME").to_string()), vec![])?.trim()
);
assert_eq!("testing", run("echo 'testing'", None, vec![])?.trim());
assert!(run("some_command", None, vec![]).is_err());
Ok(())
}
}
35 changes: 25 additions & 10 deletions git-cliff-core/src/commit.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::command;
use crate::config::{
CommitParser,
CommitPreprocessor,
Expand Down Expand Up @@ -75,7 +76,7 @@ impl Commit<'_> {
pub fn process(&self, config: &GitConfig) -> Result<Self> {
let mut commit = self.clone();
if let Some(preprocessors) = &config.commit_preprocessors {
commit = commit.preprocess(preprocessors);
commit = commit.preprocess(preprocessors)?;
}
if config.conventional_commits.unwrap_or(true) {
if config.filter_unconventional.unwrap_or(true) {
Expand Down Expand Up @@ -109,17 +110,31 @@ impl Commit<'_> {

/// Preprocesses the commit using [`CommitPreprocessor`]s.
///
/// Modifies the commit [`message`] using regex.
/// Modifies the commit [`message`] using regex or custom OS command.
///
/// [`message`]: Commit::message
pub fn preprocess(mut self, preprocessors: &[CommitPreprocessor]) -> Self {
preprocessors.iter().for_each(|preprocessor| {
self.message = preprocessor
.pattern
.replace_all(&self.message, &preprocessor.replace)
.to_string();
});
self
pub fn preprocess(
mut self,
preprocessors: &[CommitPreprocessor],
) -> Result<Self> {
preprocessors.iter().try_for_each(|preprocessor| {
if let Some(text) = &preprocessor.replace {
self.message = preprocessor
.pattern
.replace_all(&self.message, text)
.to_string();
} else if let Some(command) = &preprocessor.replace_command {
if preprocessor.pattern.is_match(&self.message) {
self.message = command::run(
command,
Some(self.message.to_string()),
vec![("COMMIT_SHA", &self.id)],
)?;
}
}
Ok::<(), AppError>(())
})?;
Ok(self)
}

/// Parses the commit using [`CommitParser`]s.
Expand Down
6 changes: 4 additions & 2 deletions git-cliff-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ pub struct CommitParser {
pub struct CommitPreprocessor {
/// Regex for matching a text to replace.
#[serde(with = "serde_regex")]
pub pattern: Regex,
pub pattern: Regex,
/// Replacement text.
pub replace: String,
pub replace: Option<String>,
/// Command that will be run for replacing the commit message.
pub replace_command: Option<String>,
}

/// Parser for extracting links in commits.
Expand Down
2 changes: 2 additions & 0 deletions git-cliff-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub use glob;
/// Export `regex` crate.
pub use regex;

/// Command runner.
pub mod command;
/// Git commit.
pub mod commit;
/// Config file parser.
Expand Down
5 changes: 3 additions & 2 deletions git-cliff-core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ fn generate_changelog() -> Result<()> {
conventional_commits: Some(true),
filter_unconventional: Some(true),
commit_preprocessors: Some(vec![CommitPreprocessor {
pattern: Regex::new(r#"\(fixes (#[1-9]+)\)"#).unwrap(),
replace: String::from("[closes Issue${1}]"),
pattern: Regex::new(r#"\(fixes (#[1-9]+)\)"#).unwrap(),
replace: Some(String::from("[closes Issue${1}]")),
replace_command: None,
}]),
commit_parsers: Some(vec![
CommitParser {
Expand Down
7 changes: 5 additions & 2 deletions git-cliff/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,11 @@ mod test {
conventional_commits: Some(true),
filter_unconventional: Some(false),
commit_preprocessors: Some(vec![CommitPreprocessor {
pattern: Regex::new("<preprocess>").unwrap(),
replace: String::from("this commit is preprocessed"),
pattern: Regex::new("<preprocess>").unwrap(),
replace: Some(String::from(
"this commit is preprocessed",
)),
replace_command: None,
}]),
commit_parsers: Some(vec![
CommitParser {
Expand Down

0 comments on commit 7d0786c

Please sign in to comment.