mirror of
https://github.com/orhun/git-cliff.git
synced 2024-09-11 06:55:38 +03:00
feat(config): support detecting config from project manifest (#571)
* feat(config): support detecting config from project manifest * refactor: apply clippy suggestions * test(fixture): add fixture for configuring from Cargo.toml
This commit is contained in:
parent
f26fbe09ff
commit
9eb3d65945
39
.github/fixtures/test-configure-from-cargo-toml/Cargo.toml
vendored
Normal file
39
.github/fixtures/test-configure-from-cargo-toml/Cargo.toml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.git-cliff.changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# template for the changelog footer
|
||||
footer = """
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the templates
|
||||
trim = true
|
||||
|
||||
[package.metadata.git-cliff.git]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features", default_scope = "app" },
|
||||
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
|
||||
]
|
11
.github/fixtures/test-configure-from-cargo-toml/commit.sh
vendored
Executable file
11
.github/fixtures/test-configure-from-cargo-toml/commit.sh
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
|
||||
git tag v0.1.0
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
|
||||
git tag v0.2.0
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
|
31
.github/fixtures/test-configure-from-cargo-toml/expected.md
vendored
Normal file
31
.github/fixtures/test-configure-from-cargo-toml/expected.md
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [unreleased]
|
||||
|
||||
### Test
|
||||
|
||||
- Add tests
|
||||
|
||||
## [0.2.0] - 2022-04-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix feature 2
|
||||
|
||||
### Features
|
||||
|
||||
- Add feature 2
|
||||
|
||||
## [0.1.0] - 2022-04-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix feature 1
|
||||
|
||||
### Features
|
||||
|
||||
- Add feature 1
|
||||
|
||||
<!-- generated by git-cliff -->
|
1
.github/workflows/test-fixtures.yml
vendored
1
.github/workflows/test-fixtures.yml
vendored
@ -67,6 +67,7 @@ jobs:
|
||||
command: --no-exec
|
||||
- fixtures-name: test-custom-tag-pattern
|
||||
command: --tag-pattern "alpha.*"
|
||||
- fixtures-name: test-configure-from-cargo-toml
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -748,6 +748,7 @@ dependencies = [
|
||||
"http-cache-reqwest",
|
||||
"indexmap",
|
||||
"lazy-regex",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"next_version",
|
||||
"pretty_assertions",
|
||||
|
@ -34,6 +34,7 @@ regex.workspace = true
|
||||
log.workspace = true
|
||||
secrecy.workspace = true
|
||||
dirs.workspace = true
|
||||
lazy_static.workspace = true
|
||||
thiserror = "1.0.58"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
|
@ -9,17 +9,42 @@ use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Regex for matching the metadata in Cargo.toml
|
||||
const CARGO_METADATA_REGEX: &str =
|
||||
r"^\[(?:workspace|package)\.metadata\.git\-cliff\.";
|
||||
/// Manifest file information and regex for matching contents.
|
||||
#[derive(Debug)]
|
||||
struct ManifestInfo {
|
||||
/// Path of the manifest.
|
||||
path: PathBuf,
|
||||
/// Regular expression for matching metadata in the manifest.
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
/// Regex for matching the metadata in pyproject.toml
|
||||
const PYPROJECT_METADATA_REGEX: &str = r"^\[(?:tool)\.git\-cliff\.";
|
||||
lazy_static::lazy_static! {
|
||||
/// Array containing manifest information for Rust and Python projects.
|
||||
static ref MANIFEST_INFO: Vec<ManifestInfo> = vec![
|
||||
ManifestInfo {
|
||||
path: PathBuf::from("Cargo.toml"),
|
||||
regex: RegexBuilder::new(
|
||||
r"^\[(?:workspace|package)\.metadata\.git\-cliff\.",
|
||||
)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.expect("failed to build regex"),
|
||||
},
|
||||
ManifestInfo {
|
||||
path: PathBuf::from("pyproject.toml"),
|
||||
regex: RegexBuilder::new(r"^\[(?:tool)\.git\-cliff\.")
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.expect("failed to build regex"),
|
||||
},
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/// Configuration values.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -232,30 +257,46 @@ pub struct LinkParser {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Reads the config file contents from project manifest (e.g. Cargo.toml,
|
||||
/// pyproject.toml)
|
||||
pub fn read_from_manifest() -> Result<Option<String>> {
|
||||
for info in (*MANIFEST_INFO).iter() {
|
||||
if info.path.exists() {
|
||||
let contents = fs::read_to_string(&info.path)?;
|
||||
if info.regex.is_match(&contents) {
|
||||
return Ok(Some(
|
||||
info.regex.replace_all(&contents, "[").to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Parses the config file from string and returns the values.
|
||||
pub fn parse_from_str(contents: &str) -> Result<Config> {
|
||||
Ok(config::Config::builder()
|
||||
.add_source(config::File::from_str(contents, config::FileFormat::Toml))
|
||||
.add_source(
|
||||
config::Environment::with_prefix("GIT_CLIFF").separator("__"),
|
||||
)
|
||||
.build()?
|
||||
.try_deserialize()?)
|
||||
}
|
||||
|
||||
/// Parses the config file and returns the values.
|
||||
pub fn parse(path: &Path) -> Result<Config> {
|
||||
let config_builder = if path.file_name() == Some(OsStr::new("Cargo.toml")) ||
|
||||
path.file_name() == Some(OsStr::new("pyproject.toml"))
|
||||
if MANIFEST_INFO
|
||||
.iter()
|
||||
.any(|v| path.file_name() == v.path.file_name())
|
||||
{
|
||||
let contents = fs::read_to_string(path)?;
|
||||
let metadata_regex = RegexBuilder::new(
|
||||
if path.file_name() == Some(OsStr::new("Cargo.toml")) {
|
||||
CARGO_METADATA_REGEX
|
||||
} else {
|
||||
PYPROJECT_METADATA_REGEX
|
||||
},
|
||||
)
|
||||
.multi_line(true)
|
||||
.build()?;
|
||||
let contents = metadata_regex.replace_all(&contents, "[");
|
||||
config::Config::builder().add_source(config::File::from_str(
|
||||
&contents,
|
||||
config::FileFormat::Toml,
|
||||
))
|
||||
} else {
|
||||
config::Config::builder().add_source(config::File::from(path))
|
||||
};
|
||||
Ok(config_builder
|
||||
if let Some(contents) = Self::read_from_manifest()? {
|
||||
return Self::parse_from_str(&contents);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config::Config::builder()
|
||||
.add_source(config::File::from(path))
|
||||
.add_source(
|
||||
config::Environment::with_prefix("GIT_CLIFF").separator("__"),
|
||||
)
|
||||
@ -269,7 +310,6 @@ mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
#[test]
|
||||
fn parse_config() -> Result<()> {
|
||||
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
|
@ -347,6 +347,8 @@ pub fn run(mut args: Opt) -> Result<()> {
|
||||
config
|
||||
} else if path.exists() {
|
||||
Config::parse(&path)?
|
||||
} else if let Some(contents) = Config::read_from_manifest()? {
|
||||
Config::parse_from_str(&contents)?
|
||||
} else {
|
||||
if !args.context {
|
||||
warn!(
|
||||
|
Loading…
Reference in New Issue
Block a user