From 71f7290a177cca37ab20f3c1570f7d65ebec380a Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Sun, 24 Mar 2024 11:01:07 +0100 Subject: [PATCH] feature(config): improve support for backwards compatability --- .github/actions/run-fixtures-test/action.yml | 128 ++++++----- .../fixtures/test-bump-version/cliff.v1.toml | 27 +++ Cargo.lock | 2 + git-cliff-core/src/changelog.rs | 25 +- git-cliff-core/src/commit.rs | 6 +- git-cliff-core/src/config/embed.rs | 103 +++++++++ git-cliff-core/src/config/mod.rs | 9 + git-cliff-core/src/config/models_base.rs | 11 + git-cliff-core/src/config/test.rs | 7 +- git-cliff-core/src/embed.rs | 63 ------ git-cliff-core/src/lib.rs | 4 - git-cliff/Cargo.toml | 214 +++++++++--------- git-cliff/src/args.rs | 11 +- git-cliff/src/config.rs | 116 ++++++++++ git-cliff/src/lib.rs | 83 +------ 15 files changed, 495 insertions(+), 314 deletions(-) create mode 100644 .github/fixtures/test-bump-version/cliff.v1.toml create mode 100644 git-cliff-core/src/config/embed.rs create mode 100644 git-cliff-core/src/config/models_base.rs delete mode 100644 git-cliff-core/src/embed.rs create mode 100644 git-cliff/src/config.rs diff --git a/.github/actions/run-fixtures-test/action.yml b/.github/actions/run-fixtures-test/action.yml index d733fe7041..7cf9dc693d 100644 --- a/.github/actions/run-fixtures-test/action.yml +++ b/.github/actions/run-fixtures-test/action.yml @@ -1,54 +1,74 @@ -name: Run a fixtures test -description: Run a fixtures test - -inputs: - fixtures-dir: - description: Path to the fixtures directory - required: true - command: - description: The git-cliff command to run - required: false - default: "" - date-format: - description: The date format to use - required: false - default: "%Y-%m-%d" - -runs: - using: composite - steps: - - name: Install toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Install git-cliff - run: cargo install --path git-cliff/ - shell: bash - - - name: Set git config - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - shell: bash - - - name: Create commits and tags - working-directory: ${{ inputs.fixtures-dir }} - run: | - git init - ./commit.sh - shell: bash - - - name: Generate a changelog - working-directory: ${{ inputs.fixtures-dir }} - run: git cliff --config cliff.toml ${{ inputs.command }} > output.md - shell: bash - - - name: Compare the output with the expected output - working-directory: ${{ inputs.fixtures-dir }} - env: - IN_DATE_FORMAT: ${{ inputs.date-format }} - run: | - cat output.md - current_date=$(date +"$IN_DATE_FORMAT") - sed -i "s/<>/$current_date/g" expected.md - diff --strip-trailing-cr output.md expected.md - shell: bash +name: Run a fixtures test +description: Run a fixtures test + +inputs: + fixtures-dir: + description: Path to the fixtures directory + required: true + command: + description: The git-cliff command to run + required: false + default: "" + date-format: + description: The date format to use + required: false + default: "%Y-%m-%d" + +runs: + using: composite + steps: + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Install git-cliff + run: cargo install --path git-cliff/ + shell: bash + + - name: Set git config + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + shell: bash + + - name: Create commits and tags + working-directory: ${{ inputs.fixtures-dir }} + run: | + git init + ./commit.sh + shell: bash + + - name: Generate a changelog + working-directory: ${{ inputs.fixtures-dir }} + run: git cliff --config cliff.toml ${{ inputs.command }} > output.md + shell: bash + + - name: Compare the output with the expected output + working-directory: ${{ inputs.fixtures-dir }} + env: + IN_DATE_FORMAT: ${{ inputs.date-format }} + run: | + cat output.md + current_date=$(date +"$IN_DATE_FORMAT") + sed -i "s/<>/$current_date/g" expected.md + diff --strip-trailing-cr output.md expected.md + shell: bash + + # test backwards compatibility using v1 confiuration + + - name: Generate a chanlog using v1 configuration + if: ${{ hashFiles(format('{0}/cliff.v1.toml', inputs.fixtures-dir)) }} + working-directory: ${{ inputs.fixtures-dir }} + run: git-cliff --config cliff.v1.toml --config-version 1 ${{ inputs.command }} > output.md + shell: bash + + - name: Compare the v1 configuration output with the expected output + if: ${{ hashFiles(format('{0}/cliff.v1.toml', inputs.fixtures-dir)) }} + working-directory: ${{ inputs.fixtures-dir }} + env: + IN_DATE_FORMAT: ${{ inputs.date-format }} + run: | + cat output.md + current_date=$(date +"$IN_DATE_FORMAT") + sed -i "s/<>/$current_date/g" expected.md + diff --strip-trailing-cr output.md expected.md + shell: bash diff --git a/.github/fixtures/test-bump-version/cliff.v1.toml b/.github/fixtures/test-bump-version/cliff.v1.toml new file mode 100644 index 0000000000..ec2fc39132 --- /dev/null +++ b/.github/fixtures/test-bump-version/cliff.v1.toml @@ -0,0 +1,27 @@ +[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") }}] +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true diff --git a/Cargo.lock b/Cargo.lock index e4afb0a1f8..112b83f3a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,6 +726,7 @@ dependencies = [ "clap", "clap_complete", "clap_mangen", + "config", "dirs", "env_logger", "git-cliff-core", @@ -737,6 +738,7 @@ dependencies = [ "regex", "secrecy", "shellexpand", + "toml", "update-informer", ] diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index ba429e8c49..d93faa557d 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -1,5 +1,9 @@ use crate::commit::Commit; -use crate::config::models_v2::Config; +use crate::config::models_v2::{ + ChangelogConfig, + CommitConfig, + Config, +}; use crate::error::Result; #[cfg(feature = "github")] use crate::github::{ @@ -60,9 +64,10 @@ impl<'a> Changelog<'a> { /// Processes a single commit and returns/logs the result. fn process_commit( commit: Commit<'a>, - git_config: &GitConfig, + changelog_config: &ChangelogConfig, + commit_config: &CommitConfig, ) -> Option> { - match commit.process(git_config) { + match commit.process(changelog_config, commit_config) { Ok(commit) => Some(commit), Err(e) => { trace!( @@ -85,7 +90,13 @@ impl<'a> Changelog<'a> { .commits .iter() .cloned() - .filter_map(|commit| Self::process_commit(commit, &self.config.git)) + .filter_map(|commit| { + Self::process_commit( + commit, + &self.config.changelog, + &self.config.commit, + ) + }) .flat_map(|commit| { if self.config.commit.split_by_newline.unwrap_or(false) { commit @@ -94,7 +105,11 @@ impl<'a> Changelog<'a> { .flat_map(|line| { let mut c = commit.clone(); c.message = line.to_string(); - Self::process_commit(c, &self.config.git) + Self::process_commit( + c, + &self.config.changelog, + &self.config.commit, + ) }) .collect() } else { diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index ec70bb90d9..188a51ea8c 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -190,9 +190,9 @@ impl Commit<'_> { if let Some(preprocessors) = &commit_config.message_preprocessors { commit = commit.preprocess(preprocessors)?; } - if config.parse_conventional_commits.unwrap_or(true) { - if config.exclude_unconventional_commits.unwrap_or(true) && - !config.split_commits.unwrap_or(false) + if commit_config.parse_conventional_commits.unwrap_or(true) { + if commit_config.exclude_unconventional_commits.unwrap_or(true) && + !commit_config.split_by_newline.unwrap_or(false) { commit = commit.into_conventional()?; } else if let Ok(conv_commit) = commit.clone().into_conventional() { diff --git a/git-cliff-core/src/config/embed.rs b/git-cliff-core/src/config/embed.rs new file mode 100644 index 0000000000..2c5ee88a7e --- /dev/null +++ b/git-cliff-core/src/config/embed.rs @@ -0,0 +1,103 @@ +use super::DEFAULT_CONFIG_FILENAME; +use crate::error::{ + Error, + Result, +}; +use regex::{ + Regex, + RegexBuilder, +}; +use rust_embed::RustEmbed; +use std::fs; +use std::{ + path::PathBuf, + str, +}; + +/// 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, +} + +lazy_static::lazy_static! { + /// Array containing manifest information for Rust and Python projects. + static ref MANIFEST_INFO: Vec = 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"), + }, + ]; + +} + +/// Reads the config file contents from project manifest (e.g. Cargo.toml, +/// pyproject.toml) +pub fn read_from_manifest() -> Result> { + 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) +} + +/// Default configuration file embedder/extractor. +/// +/// Embeds `config/`[`DEFAULT_CONFIG`] into the binary. +/// +/// [`DEFAULT_CONFIG`]: crate::DEFAULT_CONFIG +#[derive(Debug, RustEmbed)] +#[folder = "../config/"] +pub struct EmbeddedConfig; + +impl EmbeddedConfig { + /// Extracts the embedded content. + pub fn get_config_str() -> Result { + match Self::get(DEFAULT_CONFIG_FILENAME) { + Some(v) => Ok(str::from_utf8(&v.data)?.to_string()), + None => Err(Error::EmbeddedError(String::from( + "Embedded config not found", + ))), + } + } +} + +/// Built-in configuration file embedder/extractor. +/// +/// Embeds the files under `/examples/` into the binary. +#[derive(RustEmbed)] +#[folder = "../examples/"] +pub struct BuiltinConfig; + +impl BuiltinConfig { + /// Extracts the embedded content. + pub fn get_config_str(mut name: String) -> Result<(String, String)> { + if !name.ends_with(".toml") { + name = format!("{name}.toml"); + } + let contents = match Self::get(&name) { + Some(v) => Ok(str::from_utf8(&v.data)?.to_string()), + None => Err(Error::EmbeddedError(format!("config {} not found", name,))), + }?; + Ok((contents, name)) + } +} diff --git a/git-cliff-core/src/config/mod.rs b/git-cliff-core/src/config/mod.rs index c3bdbaf973..55a489d0c8 100644 --- a/git-cliff-core/src/config/mod.rs +++ b/git-cliff-core/src/config/mod.rs @@ -1,5 +1,9 @@ +/// Embedded file handler. +pub mod embed; /// Provide a command to migrate from old to new configuration. pub mod migrate; +/// Base confih models for git-cliff. +pub mod models_base; /// Deprecated Config models for git-cliff. pub mod models_v1; /// Current Config models for git-cliff. @@ -9,3 +13,8 @@ pub mod parsing; /// Tests for git-cliff Config. #[cfg(test)] pub mod test; + +/// Default configuration file. +pub const DEFAULT_CONFIG_FILENAME: &str = "cliff.toml"; +/// Default configuration version. +pub const DEFAULT_CONFIG_VERSION: i64 = 2; diff --git a/git-cliff-core/src/config/models_base.rs b/git-cliff-core/src/config/models_base.rs new file mode 100644 index 0000000000..f952ce7fbe --- /dev/null +++ b/git-cliff-core/src/config/models_base.rs @@ -0,0 +1,11 @@ +use serde::{ + Deserialize, + Serialize, +}; + +/// Meta section of the configuration file. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MetaConfig { + /// The version of the config schema. + pub version: Option, +} diff --git a/git-cliff-core/src/config/test.rs b/git-cliff-core/src/config/test.rs index 29e5390fbc..66fab9b5ee 100644 --- a/git-cliff-core/src/config/test.rs +++ b/git-cliff-core/src/config/test.rs @@ -2,7 +2,10 @@ use super::models_v2::{ Config, Remote, }; -use crate::config::parsing; +use crate::config::{ + parsing, + DEFAULT_CONFIG_FILENAME, +}; use crate::error::Result; use std::env; use std::path::PathBuf; @@ -14,7 +17,7 @@ fn parse_config() -> Result<()> { .expect("parent directory not found") .to_path_buf() .join("config") - .join(crate::DEFAULT_CONFIG_FILENAME); + .join(DEFAULT_CONFIG_FILENAME); const FOOTER_VALUE: &str = "test"; const RELEASE_TAGS_PATTERN_VALUE: &str = ".*[0-9].*"; diff --git a/git-cliff-core/src/embed.rs b/git-cliff-core/src/embed.rs deleted file mode 100644 index 28643c5f70..0000000000 --- a/git-cliff-core/src/embed.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::config::models_v2::Config; -use crate::error::{ - Error, - Result, -}; -use rust_embed::RustEmbed; -use std::str; - -/// Default configuration file embedder/extractor. -/// -/// Embeds `config/`[`DEFAULT_CONFIG`] into the binary. -/// -/// [`DEFAULT_CONFIG`]: crate::DEFAULT_CONFIG -#[derive(Debug, RustEmbed)] -#[folder = "../config/"] -pub struct EmbeddedConfig; - -impl EmbeddedConfig { - /// Extracts the embedded content. - pub fn get_config() -> Result { - match Self::get(crate::DEFAULT_CONFIG_FILENAME) { - Some(v) => Ok(str::from_utf8(&v.data)?.to_string()), - None => Err(Error::EmbeddedError(String::from( - "Embedded config not found", - ))), - } - } - - /// Parses the extracted content into [`Config`]. - /// - /// [`Config`]: Config - pub fn parse() -> Result { - Ok(toml::from_str(&Self::get_config()?)?) - } -} - -/// Built-in configuration file embedder/extractor. -/// -/// Embeds the files under `/examples/` into the binary. -#[derive(RustEmbed)] -#[folder = "../examples/"] -pub struct BuiltinConfig; - -impl BuiltinConfig { - /// Extracts the embedded content. - pub fn get_config(mut name: String) -> Result { - if !name.ends_with(".toml") { - name = format!("{name}.toml"); - } - let contents = match Self::get(&name) { - Some(v) => Ok(str::from_utf8(&v.data)?.to_string()), - None => Err(Error::EmbeddedError(format!("config {} not found", name,))), - }?; - Ok(contents) - } - - /// Parses the extracted content into [`Config`] along with the name. - /// - /// [`Config`]: Config - pub fn parse(name: String) -> Result<(Config, String)> { - Ok((toml::from_str(&Self::get_config(name.to_string())?)?, name)) - } -} diff --git a/git-cliff-core/src/lib.rs b/git-cliff-core/src/lib.rs index d445051640..04609b2a16 100644 --- a/git-cliff-core/src/lib.rs +++ b/git-cliff-core/src/lib.rs @@ -20,8 +20,6 @@ pub mod command; pub mod commit; /// Config file parser. pub mod config; -/// Embedded file handler. -pub mod embed; /// Error handling. pub mod error; /// GitHub client. @@ -38,8 +36,6 @@ pub mod template; #[macro_use] extern crate log; -/// Default configuration file. -pub const DEFAULT_CONFIG_FILENAME: &str = "cliff.toml"; /// Default output file. pub const DEFAULT_OUTPUT: &str = "CHANGELOG.md"; /// Default ignore file. diff --git a/git-cliff/Cargo.toml b/git-cliff/Cargo.toml index 05552b7e7c..7794ae43aa 100644 --- a/git-cliff/Cargo.toml +++ b/git-cliff/Cargo.toml @@ -1,108 +1,114 @@ -[package] -name = "git-cliff" +[package] +name = "git-cliff" version = "2.2.1" # managed by release.sh -description = "A highly customizable changelog generator ⛰️" -authors = ["git-cliff contributors "] -license = "MIT OR Apache-2.0" -readme = "../README.md" -homepage = "https://github.com/orhun/git-cliff" -repository = "https://github.com/orhun/git-cliff" -keywords = ["changelog", "generator", "conventional", "commit"] -categories = ["command-line-utilities"] -default-run = "git-cliff" -edition = "2021" -rust-version = "1.74.1" - -[[bin]] -name = "git-cliff-completions" -path = "src/bin/completions.rs" - -[[bin]] -name = "git-cliff-mangen" -path = "src/bin/mangen.rs" - -[features] -# check for new versions -default = ["update-informer", "github"] -# inform about new releases -update-informer = ["dep:update-informer"] -# enable GitHub integration -github = ["git-cliff-core/github", "dep:indicatif"] - -[dependencies] -glob.workspace = true -regex.workspace = true -log.workspace = true -secrecy.workspace = true -lazy_static.workspace = true -dirs.workspace = true +description = "A highly customizable changelog generator ⛰️" +authors = ["git-cliff contributors "] +license = "MIT OR Apache-2.0" +readme = "../README.md" +homepage = "https://github.com/orhun/git-cliff" +repository = "https://github.com/orhun/git-cliff" +keywords = ["changelog", "generator", "conventional", "commit"] +categories = ["command-line-utilities"] +default-run = "git-cliff" +edition = "2021" +rust-version = "1.74.1" + +[[bin]] +name = "git-cliff-completions" +path = "src/bin/completions.rs" + +[[bin]] +name = "git-cliff-mangen" +path = "src/bin/mangen.rs" + +[features] +# check for new versions +default = ["update-informer", "github"] +# inform about new releases +update-informer = ["dep:update-informer"] +# enable GitHub integration +github = ["git-cliff-core/github", "dep:indicatif"] + +[dependencies] +glob.workspace = true +regex.workspace = true +log.workspace = true +secrecy.workspace = true +lazy_static.workspace = true +dirs.workspace = true clap = { version = "4.5.4", features = ["derive", "env", "wrap_help", "cargo"] } clap_complete = "4.5.2" -clap_mangen = "0.2.20" -shellexpand = "3.1.0" -update-informer = { version = "1.1.0", optional = true } -indicatif = { version = "0.17.8", optional = true } -env_logger = "0.10.2" - -[dependencies.git-cliff-core] +clap_mangen = "0.2.20" +shellexpand = "3.1.0" +update-informer = { version = "1.1.0", optional = true } +indicatif = { version = "0.17.8", optional = true } +env_logger = "0.10.2" +toml = "0.8.12" + +[dependencies.config] +version = "0.14.0" +default-features = false +features = ["toml", "yaml"] + +[dependencies.git-cliff-core] version = "2.2.1" # managed by release.sh -path = "../git-cliff-core" - -[dev-dependencies] -pretty_assertions = "1.4.0" - -# metadata for cargo-binstall to get the right artifacts -[package.metadata.binstall] -pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" -bin-dir = "{ name }-{ version }/{ bin }{ binary-ext }" -pkg-fmt = "tgz" - -[package.metadata.generate-rpm] -assets = [ - { source = "target/release/git-cliff", dest = "/usr/bin/git-cliff", mode = "755" }, - { source = "LICENSE-MIT", dest = "/usr/share/doc/git-cliff/LICENSE-MIT", mode = "644" }, - { source = "README.md", dest = "/usr/share/doc/git-cliff/README.md", mode = "644" }, - { source = "man/git-cliff.1", dest = "/usr/share/man/man1/git-cliff.1", mode = "644", doc = true }, - { source = "completions/git-cliff.bash", dest = "/usr/share/bash-completion/completions/git-cliff", mode = "644" }, - { source = "completions/git-cliff.fish", dest = "/usr/share/fish/vendor_completions.d/git-cliff.fish", mode = "644" }, - { source = "completions/_git-cliff", dest = "/usr/share/zsh/vendor-completions/", mode = "644" }, -] - -[package.metadata.deb] -assets = [ - [ - "target/release/git-cliff", - "usr/bin/", - "755", - ], - [ - "../LICENSE-MIT", - "/usr/share/doc/git-cliff/LICENSE-MIT", - "644", - ], - [ - "../README.md", - "usr/share/doc/git-cliff/README", - "644", - ], - [ - "../man/git-cliff.1", - "/usr/share/man/man1/git-cliff.1", - "644", - ], - [ - "../completions/git-cliff.bash", - "/usr/share/bash-completion/completions/git-cliff", - "644", - ], - [ - "../completions/git-cliff.fish", - "/usr/share/fish/vendor_completions.d/git-cliff.fish", - "644", - ], - [ - "../completions/_git-cliff", - "/usr/share/zsh/vendor-completions/", - "644", - ], -] +path = "../git-cliff-core" + +[dev-dependencies] +pretty_assertions = "1.4.0" + +# metadata for cargo-binstall to get the right artifacts +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" +bin-dir = "{ name }-{ version }/{ bin }{ binary-ext }" +pkg-fmt = "tgz" + +[package.metadata.generate-rpm] +assets = [ + { source = "target/release/git-cliff", dest = "/usr/bin/git-cliff", mode = "755" }, + { source = "LICENSE-MIT", dest = "/usr/share/doc/git-cliff/LICENSE-MIT", mode = "644" }, + { source = "README.md", dest = "/usr/share/doc/git-cliff/README.md", mode = "644" }, + { source = "man/git-cliff.1", dest = "/usr/share/man/man1/git-cliff.1", mode = "644", doc = true }, + { source = "completions/git-cliff.bash", dest = "/usr/share/bash-completion/completions/git-cliff", mode = "644" }, + { source = "completions/git-cliff.fish", dest = "/usr/share/fish/vendor_completions.d/git-cliff.fish", mode = "644" }, + { source = "completions/_git-cliff", dest = "/usr/share/zsh/vendor-completions/", mode = "644" }, +] + +[package.metadata.deb] +assets = [ + [ + "target/release/git-cliff", + "usr/bin/", + "755", + ], + [ + "../LICENSE-MIT", + "/usr/share/doc/git-cliff/LICENSE-MIT", + "644", + ], + [ + "../README.md", + "usr/share/doc/git-cliff/README", + "644", + ], + [ + "../man/git-cliff.1", + "/usr/share/man/man1/git-cliff.1", + "644", + ], + [ + "../completions/git-cliff.bash", + "/usr/share/bash-completion/completions/git-cliff", + "644", + ], + [ + "../completions/git-cliff.fish", + "/usr/share/fish/vendor_completions.d/git-cliff.fish", + "644", + ], + [ + "../completions/_git-cliff", + "/usr/share/zsh/vendor-completions/", + "644", + ], +] diff --git a/git-cliff/src/args.rs b/git-cliff/src/args.rs index 6140a534f1..cf5f27974a 100644 --- a/git-cliff/src/args.rs +++ b/git-cliff/src/args.rs @@ -21,8 +21,8 @@ use git_cliff_core::{ Remote, TagsOrderBy, }, + DEFAULT_CONFIG_FILENAME, }, - DEFAULT_CONFIG_FILENAME, DEFAULT_OUTPUT, }; use glob::Pattern; @@ -113,13 +113,8 @@ pub struct Opt { pub config: PathBuf, /// Sets the version of the configuration file given in `--config`. - #[arg( - long, - env = "GIT_CLIFF_CONFIG_VERSION", - value_name = "VERSION", - default_value_t = 2 - )] - pub config_version: u8, + #[arg(long, env = "GIT_CLIFF_CONFIG_VERSION", value_name = "VERSION")] + pub config_version: Option, /// Sets the working directory. #[arg( diff --git a/git-cliff/src/config.rs b/git-cliff/src/config.rs new file mode 100644 index 0000000000..372267dfa8 --- /dev/null +++ b/git-cliff/src/config.rs @@ -0,0 +1,116 @@ +use crate::args::Opt; +#[allow(deprecated)] +use git_cliff_core::config::models_v1::Config as Config_v1; +use git_cliff_core::config::{ + embed::BuiltinConfig, + embed::EmbeddedConfig, + models_v2::Config as Config_v2, + DEFAULT_CONFIG_FILENAME, + DEFAULT_CONFIG_VERSION, +}; +use git_cliff_core::error::{ + Error, + Result, +}; +use std::fs; +use std::path::PathBuf; + +/// Gets the effective config argument. +fn get_effective_config_arg(path: PathBuf) -> PathBuf { + if !path.exists() { + if let Some(config_path) = dirs::config_dir().map(|dir| { + dir.join(env!("CARGO_PKG_NAME")) + .join(DEFAULT_CONFIG_FILENAME) + }) { + return config_path; + } + } + path +} + +/// Determines the version of the config schema. +fn determine_config_version(args: &Opt, raw_config: toml::Table) -> i64 { + // determine if `meta.version` is set in the config file. + let meta_version_option = raw_config + .get("meta") + .and_then(|val| val.as_table()) + .and_then(|table| table.get("version")) + .and_then(|version| version.as_integer()); + + // `--config-version` takes precedence over `meta.version`. + if let Some(args_version) = args.config_version { + if let Some(meta_version) = meta_version_option { + warn!( + "Argument `--config-version = {args_version}` takes precedence \ + over option `meta.version = {meta_version}`." + ); + } + return args_version; + } else { + return meta_version_option.unwrap_or(DEFAULT_CONFIG_VERSION); + }; +} + +/// Loads the configuration based on the given command line arguments. +pub fn load_config(args: &Opt) -> Result { + let config_arg = get_effective_config_arg(args.config.clone()); + + // If `--config` matches the name of a built-in config, load it. + let config_str = if let Ok((builtin_config, builtin_config_name)) = + BuiltinConfig::get_config_str(config_arg.to_string_lossy().to_string()) + { + info!("Using built-in configuration {builtin_config_name}."); + builtin_config + } + // If `--config` denotes an existing file, load it from there. + else if config_arg.is_file() { + info!( + "Loading configuration from {}", + config_arg.to_string_lossy() + ); + fs::read_to_string(config_arg)? + } + // If the manifest contains a config, load it. + else if let Some(contents) = + git_cliff_core::config::embed::read_from_manifest()? + { + contents + } + // Otherwise fall back to using the embedded configuration from + // ./config/cliff.toml. + else { + warn!( + "{:?} could not be found. Using the default configuration.", + args.config + ); + EmbeddedConfig::get_config_str()? + }; + + // Determine the version of the config based on the cli argument + // `--config-version` and the option `meta.version`. + let raw_config = config_str.parse::()?; + let config_version = determine_config_version(args, raw_config); + info!("Loading config version {config_version}."); + + // load the file using https://docs.rs/config + let raw_config = config::Config::builder() + .add_source(config::File::from_str( + &config_str, + config::FileFormat::Toml, + )) + .add_source(config::Environment::with_prefix("GIT_CLIFF").separator("__")) + .build()?; + + // Turn the toml::Table into the proper config struct. + if config_version == 1 { + let config_v1 = raw_config.try_deserialize::()?; + return Ok(Config_v2::from(config_v1)); + } else if config_version == 2 { + return Ok(raw_config.try_deserialize::()?); + } else { + return Err(Error::ArgumentError(format!( + "Config version {} is not supported.", + config_version + ))); + } +} diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs index 4df3f006ef..dce3a86329 100644 --- a/git-cliff/src/lib.rs +++ b/git-cliff/src/lib.rs @@ -7,6 +7,9 @@ /// Command-line argument parser. pub mod args; +/// Configuration parser. +pub mod config; + /// Custom logger implementation. pub mod logger; @@ -19,29 +22,24 @@ use args::{ }; use git_cliff_core::changelog::Changelog; use git_cliff_core::commit::Commit; -#[allow(deprecated)] -use git_cliff_core::config::models_v1::Config as Config_v1; +use git_cliff_core::config::embed::{ + BuiltinConfig, + EmbeddedConfig, +}; use git_cliff_core::config::models_v2::{ CommitParser, CommitSortOrder, Config, TagsOrderBy, }; -use git_cliff_core::config::parsing; -use git_cliff_core::embed::{ - BuiltinConfig, - EmbeddedConfig, -}; +use git_cliff_core::config::DEFAULT_CONFIG_FILENAME; use git_cliff_core::error::{ Error, Result, }; use git_cliff_core::release::Release; use git_cliff_core::repo::Repository; -use git_cliff_core::{ - DEFAULT_CONFIG_FILENAME, - IGNORE_FILE, -}; +use git_cliff_core::IGNORE_FILE; use std::env; use std::fs::{ self, @@ -51,7 +49,6 @@ use std::io::{ self, Write, }; -use std::path::PathBuf; use std::time::{ SystemTime, UNIX_EPOCH, @@ -297,62 +294,6 @@ fn process_repository<'a>( Ok(releases) } -pub fn get_config_path(path: PathBuf) -> PathBuf { - if !path.exists() { - if let Some(config_path) = dirs::config_dir().map(|dir| { - dir.join(env!("CARGO_PKG_NAME")) - .join(DEFAULT_CONFIG_FILENAME) - }) { - return config_path; - } - } - path -} - -/// Loads the configuration based on the given command line arguments. -pub fn load_config(args: &Opt) -> Result { - let config_path = get_config_path(args.config.clone()); - // If the argument `--config` matches the name of a config in - // ./examples, use it. - if let Ok((builtin_config, name)) = - BuiltinConfig::parse(args.config.to_string_lossy().to_string()) - { - info!("Using built-in configuration file {name}."); - return Ok(builtin_config); - } - // If `--config` denotes an existing file, try loading it as configuration. - else if config_path.is_file() { - info!( - "Loading configuration from {}.", - args.config.to_string_lossy() - ); - - // Default to loading a v2 config. - if args.config_version == 2 { - Ok(parsing::parse::(&config_path)?) - } - // Load a v1 config and immediately convert it to v2. - else { - warn!( - "Configuration format v1 is deprecated. Consider migrating to v2. \ - Refer to https://git-cliff.org/docs/configuration/migration for more information." - ); - #[allow(deprecated)] - let config_v1 = parsing::parse::(&config_path)?; - Ok(Config::from(config_v1)) - } - } - // Otherwise fall back to using the embedded configuration from - // ./config/cliff.toml. - else { - warn!( - "{:?} could not be found. Using the default configuration.", - args.config - ); - EmbeddedConfig::parse() - } -} - /// Runs `git-cliff`. pub fn run(mut args: Opt) -> Result<()> { // Check if there is a new version available. @@ -362,8 +303,8 @@ pub fn run(mut args: Opt) -> Result<()> { // Create the configuration file if init flag is given. if let Some(init_config) = args.init { let contents = match init_config { - Some(ref name) => BuiltinConfig::get_config(name.to_string())?, - None => EmbeddedConfig::get_config()?, + Some(ref name) => BuiltinConfig::get_config_str(name.to_string())?.0, + None => EmbeddedConfig::get_config_str()?, }; info!( "Saving the configuration file{} to {:?}", @@ -391,7 +332,7 @@ pub fn run(mut args: Opt) -> Result<()> { } // Load the configuration. - let mut config = load_config(&args)?; + let mut config = config::load_config(&args)?; // Update the configuration based on command line arguments and vice versa. match args.strip {