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/new-fixture-template/cliff.toml b/.github/fixtures/new-fixture-template/cliff.toml index 9a9d380abc..47b901979d 100644 --- a/.github/fixtures/new-fixture-template/cliff.toml +++ b/.github/fixtures/new-fixture-template/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,15 +18,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, diff --git a/.github/fixtures/test-bump-version-keep-zerover/cliff.toml b/.github/fixtures/test-bump-version-keep-zerover/cliff.toml index 15414304ae..bbb34220dd 100644 --- a/.github/fixtures/test-bump-version-keep-zerover/cliff.toml +++ b/.github/fixtures/test-bump-version-keep-zerover/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] {% else %}\ @@ -19,12 +18,12 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true [bump] features_always_bump_minor = false diff --git a/.github/fixtures/test-bump-version/cliff.toml b/.github/fixtures/test-bump-version/cliff.toml index ec2fc39132..24e42bb4ff 100644 --- a/.github/fixtures/test-bump-version/cliff.toml +++ b/.github/fixtures/test-bump-version/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] {% else %}\ @@ -19,9 +18,9 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-topo-order-arg/cliff.toml b/.github/fixtures/test-bump-version/cliff.v1.toml similarity index 88% rename from .github/fixtures/test-topo-order-arg/cliff.toml rename to .github/fixtures/test-bump-version/cliff.v1.toml index 5504810e90..ec2fc39132 100644 --- a/.github/fixtures/test-topo-order-arg/cliff.toml +++ b/.github/fixtures/test-bump-version/cliff.v1.toml @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file.\n # https://keats.github.io/tera/docs/#introduction body = """ {% if version %}\ - ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} + ## [{{ version | trim_start_matches(pat="v") }}] {% else %}\ ## [unreleased] {% endif %}\ diff --git a/.github/fixtures/test-bumped-version/cliff.toml b/.github/fixtures/test-bumped-version/cliff.toml index ec2fc39132..24e42bb4ff 100644 --- a/.github/fixtures/test-bumped-version/cliff.toml +++ b/.github/fixtures/test-bumped-version/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] {% else %}\ @@ -19,9 +18,9 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-commit-footers/cliff.toml b/.github/fixtures/test-commit-footers/cliff.toml index e143f3b6e9..5ee7d8ebab 100644 --- a/.github/fixtures/test-commit-footers/cliff.toml +++ b/.github/fixtures/test-commit-footers/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -22,9 +21,9 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-commit-preprocessors/cliff.toml b/.github/fixtures/test-commit-preprocessors/cliff.toml index 2d86f4cd02..d8c038c309 100644 --- a/.github/fixtures/test-commit-preprocessors/cliff.toml +++ b/.github/fixtures/test-commit-preprocessors/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,16 +18,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for preprocessing the commit messages -commit_preprocessors = [ +[commit] +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ { pattern = '\(#([0-9]+)\)', replace = "([issue#${1}](https://github.com/orhun/git-cliff/issues/${1}))" }, { pattern = " +", replace = " " }, ] diff --git a/.github/fixtures/test-configure-from-cargo-toml/Cargo.toml b/.github/fixtures/test-configure-from-cargo-toml/Cargo.toml index d44e73bd9d..c89b891d2a 100644 --- a/.github/fixtures/test-configure-from-cargo-toml/Cargo.toml +++ b/.github/fixtures/test-configure-from-cargo-toml/Cargo.toml @@ -4,14 +4,13 @@ version = "0.1.0" edition = "2021" [package.metadata.git-cliff.changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -24,15 +23,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[package.metadata.git-cliff.git] -# regex for parsing and grouping commits +[package.metadata.git-cliff.commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, diff --git a/.github/fixtures/test-custom-scope/cliff.toml b/.github/fixtures/test-custom-scope/cliff.toml index fe30b9070c..f7d494478d 100644 --- a/.github/fixtures/test-custom-scope/cliff.toml +++ b/.github/fixtures/test-custom-scope/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -22,15 +21,16 @@ body = """ {% endfor %}\ {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, diff --git a/.github/fixtures/test-custom-tag-pattern/cliff.toml b/.github/fixtures/test-custom-tag-pattern/cliff.toml index fc3d5db2a2..0a25fc6c84 100644 --- a/.github/fixtures/test-custom-tag-pattern/cliff.toml +++ b/.github/fixtures/test-custom-tag-pattern/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="alpha-") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,15 +18,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, diff --git a/.github/fixtures/test-date-order/cliff.toml b/.github/fixtures/test-date-order/cliff.toml index 5504810e90..de3df0399f 100644 --- a/.github/fixtures/test-date-order/cliff.toml +++ b/.github/fixtures/test-date-order/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,9 +18,9 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-footer-template/cliff.toml b/.github/fixtures/test-footer-template/cliff.toml index ebfa371d5a..d088363ecb 100644 --- a/.github/fixtures/test-footer-template/cliff.toml +++ b/.github/fixtures/test-footer-template/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] {% else %}\ @@ -19,8 +18,8 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ {% for release in releases %}\ {% if release.version %}\ {% if release.previous.version %}\ @@ -31,5 +30,5 @@ footer = """ {% endif %}\ {% endfor %}\ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-github-integration/cliff.toml b/.github/fixtures/test-github-integration/cliff.toml index fcddf67338..65942c4eac 100644 --- a/.github/fixtures/test-github-integration/cliff.toml +++ b/.github/fixtures/test-github-integration/cliff.toml @@ -3,9 +3,8 @@ owner = "orhun" repo = "git-cliff-readme-example" [changelog] -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ ## What's Changed {%- if version %} in {{ version }}{%- endif -%} @@ -31,21 +30,22 @@ body = """ {% raw %}\n{% endraw %} {% endif %} """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = false -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false +[commit] +# Whether to parse commits according to the conventional commits specification. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = false +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }] +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false diff --git a/.github/fixtures/test-ignore-tags/cliff.toml b/.github/fixtures/test-ignore-tags/cliff.toml index 62a656124d..8010109c23 100644 --- a/.github/fixtures/test-ignore-tags/cliff.toml +++ b/.github/fixtures/test-ignore-tags/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,15 +18,19 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "v.*-beta.*" +[release] +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "v.*-beta.*" + +[commit] +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" diff --git a/.github/fixtures/test-keep-a-changelog-links-current-arg/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-current-arg/cliff.toml index c96c37331f..d602a2d133 100644 --- a/.github/fixtures/test-keep-a-changelog-links-current-arg/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-current-arg/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,35 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +74,12 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-latest-arg/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-latest-arg/cliff.toml index c96c37331f..346c1fa284 100644 --- a/.github/fixtures/test-keep-a-changelog-links-latest-arg/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-latest-arg/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-no-tags/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-no-tags/cliff.toml index c96c37331f..346c1fa284 100644 --- a/.github/fixtures/test-keep-a-changelog-links-no-tags/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-no-tags/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-one-tag-bump-arg/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-one-tag-bump-arg/cliff.toml index c96c37331f..708fa79a09 100644 --- a/.github/fixtures/test-keep-a-changelog-links-one-tag-bump-arg/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-one-tag-bump-arg/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-one-tag/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-one-tag/cliff.toml index c96c37331f..329f02e47c 100644 --- a/.github/fixtures/test-keep-a-changelog-links-one-tag/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-one-tag/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-tag-arg/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-tag-arg/cliff.toml index c96c37331f..708fa79a09 100644 --- a/.github/fixtures/test-keep-a-changelog-links-tag-arg/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-tag-arg/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links-unreleased-arg/cliff.toml b/.github/fixtures/test-keep-a-changelog-links-unreleased-arg/cliff.toml index c96c37331f..708fa79a09 100644 --- a/.github/fixtures/test-keep-a-changelog-links-unreleased-arg/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links-unreleased-arg/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-keep-a-changelog-links/cliff.toml b/.github/fixtures/test-keep-a-changelog-links/cliff.toml index 59ef355cf0..3c64c83be1 100644 --- a/.github/fixtures/test-keep-a-changelog-links/cliff.toml +++ b/.github/fixtures/test-keep-a-changelog-links/cliff.toml @@ -1,5 +1,5 @@ [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -7,9 +7,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ {% if previous %}\ {% if previous.version %}\ @@ -36,21 +35,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# glob pattern for matching git tags -tag_pattern = "v[0-9]*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/.github/fixtures/test-latest-with-one-tag/cliff.toml b/.github/fixtures/test-latest-with-one-tag/cliff.toml index 5504810e90..de3df0399f 100644 --- a/.github/fixtures/test-latest-with-one-tag/cliff.toml +++ b/.github/fixtures/test-latest-with-one-tag/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,9 +18,9 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-limit-commits/cliff.toml b/.github/fixtures/test-limit-commits/cliff.toml index f053d363b3..1fb3fca66a 100644 --- a/.github/fixtures/test-limit-commits/cliff.toml +++ b/.github/fixtures/test-limit-commits/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,12 +18,13 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -limit_commits = 2 +[commit] +# Whether to limit the total number of commits to be included in the changelog. +max_commit_count = 2 diff --git a/.github/fixtures/test-no-exec/cliff.toml b/.github/fixtures/test-no-exec/cliff.toml index 7a8024d7a1..de18bd2559 100644 --- a/.github/fixtures/test-no-exec/cliff.toml +++ b/.github/fixtures/test-no-exec/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,24 +18,25 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true -# postprocessors +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A list of postprocessors using regex to modify the changelog. postprocessors = [ { pattern = '.*', replace_command = 'this_command_does_not_exist' }, ] -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, ] -# regex for preprocessing the commit messages -commit_preprocessors = [ +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ { pattern = '.*', replace_command = "this_command_does_not_exist" }, ] diff --git a/.github/fixtures/test-order-by-topology-arg/cliff.toml b/.github/fixtures/test-order-by-topology-arg/cliff.toml new file mode 100644 index 0000000000..de3df0399f --- /dev/null +++ b/.github/fixtures/test-order-by-topology-arg/cliff.toml @@ -0,0 +1,26 @@ +[changelog] +# A static header for the changelog. +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ +{% 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 %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ + +""" +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true diff --git a/.github/fixtures/test-topo-order-arg/commit.sh b/.github/fixtures/test-order-by-topology-arg/commit.sh similarity index 100% rename from .github/fixtures/test-topo-order-arg/commit.sh rename to .github/fixtures/test-order-by-topology-arg/commit.sh diff --git a/.github/fixtures/test-topo-order-arg/expected.md b/.github/fixtures/test-order-by-topology-arg/expected.md similarity index 100% rename from .github/fixtures/test-topo-order-arg/expected.md rename to .github/fixtures/test-order-by-topology-arg/expected.md diff --git a/.github/fixtures/test-order-by-topology/cliff.toml b/.github/fixtures/test-order-by-topology/cliff.toml new file mode 100644 index 0000000000..3558d46067 --- /dev/null +++ b/.github/fixtures/test-order-by-topology/cliff.toml @@ -0,0 +1,31 @@ +[changelog] +# A static header for the changelog. +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ +{% 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 %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ + +""" +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true + +[release] +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "topology" diff --git a/.github/fixtures/test-topo-order/commit.sh b/.github/fixtures/test-order-by-topology/commit.sh similarity index 100% rename from .github/fixtures/test-topo-order/commit.sh rename to .github/fixtures/test-order-by-topology/commit.sh diff --git a/.github/fixtures/test-topo-order/expected.md b/.github/fixtures/test-order-by-topology/expected.md similarity index 100% rename from .github/fixtures/test-topo-order/expected.md rename to .github/fixtures/test-order-by-topology/expected.md diff --git a/.github/fixtures/test-regex-replace-parser/cliff.toml b/.github/fixtures/test-regex-replace-parser/cliff.toml index b464addd34..9c495280ba 100644 --- a/.github/fixtures/test-regex-replace-parser/cliff.toml +++ b/.github/fixtures/test-regex-replace-parser/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,15 +18,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = '^fix\((.*)\)', group = 'Fix (${1}) 🧰' }, { message = '^feat\((.*)\)', group = 'Feature (${1}) 🚀' }, diff --git a/.github/fixtures/test-skip-breaking-changes/cliff.toml b/.github/fixtures/test-skip-breaking-changes/cliff.toml index d0574838ca..8f23dc57a4 100644 --- a/.github/fixtures/test-skip-breaking-changes/cliff.toml +++ b/.github/fixtures/test-skip-breaking-changes/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -22,18 +21,19 @@ body = """ {% endfor %}\ {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app", skip = true }, { message = "^fix", group = "Bug Fixes", scope = "cli", skip = true }, ] -# protect breaking changes from being skipped -protect_breaking_commits = true +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = true diff --git a/.github/fixtures/test-skip-commits/cliff.toml b/.github/fixtures/test-skip-commits/cliff.toml index 9a9d380abc..47b901979d 100644 --- a/.github/fixtures/test-skip-commits/cliff.toml +++ b/.github/fixtures/test-skip-commits/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,15 +18,16 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# regex for parsing and grouping commits +[commit] +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features", default_scope = "app" }, { message = "^fix", group = "Bug Fixes", scope = "cli" }, diff --git a/.github/fixtures/test-split-commits/cliff.toml b/.github/fixtures/test-split-commits/cliff.toml index ec20df871c..d7884012ab 100644 --- a/.github/fixtures/test-split-commits/cliff.toml +++ b/.github/fixtures/test-split-commits/cliff.toml @@ -1,12 +1,11 @@ [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -19,13 +18,13 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true -[git] -# process each line of a commit as an individual commit -split_commits = true +[commit] +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = true diff --git a/.github/fixtures/test-topo-order/cliff.toml b/.github/fixtures/test-v1-config-arg/cliff.toml similarity index 75% rename from .github/fixtures/test-topo-order/cliff.toml rename to .github/fixtures/test-v1-config-arg/cliff.toml index f4bbb0a47b..9a9d380abc 100644 --- a/.github/fixtures/test-topo-order/cliff.toml +++ b/.github/fixtures/test-v1-config-arg/cliff.toml @@ -15,7 +15,7 @@ body = """ {% 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 }}\ + - {{ commit.message | upper_first }}\ {% endfor %} {% endfor %}\n """ @@ -27,4 +27,8 @@ footer = """ trim = true [git] -topo_order = true +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features", default_scope = "app" }, + { message = "^fix", group = "Bug Fixes", scope = "cli" }, +] diff --git a/.github/fixtures/test-v1-config-arg/commit.sh b/.github/fixtures/test-v1-config-arg/commit.sh new file mode 100755 index 0000000000..7c6fe32718 --- /dev/null +++ b/.github/fixtures/test-v1-config-arg/commit.sh @@ -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" diff --git a/.github/fixtures/test-v1-config-arg/expected.md b/.github/fixtures/test-v1-config-arg/expected.md new file mode 100644 index 0000000000..72e96a6f09 --- /dev/null +++ b/.github/fixtures/test-v1-config-arg/expected.md @@ -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 + + diff --git a/.github/fixtures/test-v1-config-meta/cliff.toml b/.github/fixtures/test-v1-config-meta/cliff.toml new file mode 100644 index 0000000000..f92639e05b --- /dev/null +++ b/.github/fixtures/test-v1-config-meta/cliff.toml @@ -0,0 +1,38 @@ +[meta] +# The version of the config schema. +version = 1 + +[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 = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[git] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features", default_scope = "app" }, + { message = "^fix", group = "Bug Fixes", scope = "cli" }, +] diff --git a/.github/fixtures/test-v1-config-meta/commit.sh b/.github/fixtures/test-v1-config-meta/commit.sh new file mode 100755 index 0000000000..7c6fe32718 --- /dev/null +++ b/.github/fixtures/test-v1-config-meta/commit.sh @@ -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" diff --git a/.github/fixtures/test-v1-config-meta/expected.md b/.github/fixtures/test-v1-config-meta/expected.md new file mode 100644 index 0000000000..72e96a6f09 --- /dev/null +++ b/.github/fixtures/test-v1-config-meta/expected.md @@ -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 + + diff --git a/.github/workflows/test-fixtures.yml b/.github/workflows/test-fixtures.yml index 2e35cf7010..6a5413b81d 100644 --- a/.github/workflows/test-fixtures.yml +++ b/.github/workflows/test-fixtures.yml @@ -19,12 +19,12 @@ jobs: - fixtures-name: new-fixture-template - fixtures-name: test-github-integration - fixtures-name: test-ignore-tags - - fixtures-name: test-topo-order + - fixtures-name: test-order-by-topology command: --latest - fixtures-name: test-date-order command: --latest - - fixtures-name: test-topo-order-arg - command: --latest --topo-order + - fixtures-name: test-order-by-topology-arg + command: --latest --release-order-by topology - fixtures-name: test-latest-with-one-tag command: --latest - fixtures-name: test-commit-footers @@ -66,8 +66,11 @@ jobs: - fixtures-name: test-no-exec command: --no-exec - fixtures-name: test-custom-tag-pattern - command: --tag-pattern "alpha.*" + command: --release-tags-pattern "alpha.*" - fixtures-name: test-configure-from-cargo-toml + - fixtures-name: test-v1-config-meta + - fixtures-name: test-v1-config-arg + command: --config-version 1 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index 0df7bb0ebd..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", ] @@ -744,6 +746,7 @@ dependencies = [ name = "git-cliff-core" version = "2.2.1" dependencies = [ + "clap", "config", "dirs", "document-features", diff --git a/README.md b/README.md index 2ffa7fd942..853e6ba48f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Website -**git-cliff** can generate [changelog](https://en.wikipedia.org/wiki/Changelog) files from the [Git](https://git-scm.com/) history by utilizing [conventional commits](https://git-cliff.org/docs/configuration/git#conventional_commits) as well as regex-powered [custom parsers](https://git-cliff.org/docs/configuration/git#commit_parsers). The [changelog template](https://git-cliff.org/docs/category/templating) can be customized with a [configuration file](https://git-cliff.org/docs/configuration) to match the desired format. +**git-cliff** can generate [changelog](https://en.wikipedia.org/wiki/Changelog) files from the [Git](https://git-scm.com/) history by utilizing [conventional commits](https://git-cliff.org/docs/configuration/commit#parse_conventional_commits) as well as regex-powered [custom parsers](https://git-cliff.org/docs/configuration/commit#commit_parsers). The [changelog template](https://git-cliff.org/docs/category/templating) can be customized with a [configuration file](https://git-cliff.org/docs/configuration) to match the desired format. ![animation](https://raw.githubusercontent.com/orhun/git-cliff/main/website/static/img/git-cliff-anim.gif) diff --git a/cliff.toml b/cliff.toml index 8c677a67fd..be9eecaf57 100644 --- a/cliff.toml +++ b/cliff.toml @@ -6,13 +6,12 @@ # See documentation for more information on available options. [changelog] -# changelog header +# A static header for the changelog. header = """ [![animation](https://raw.githubusercontent.com/orhun/git-cliff/main/website/static/img/git-cliff-anim.gif)](https://git-cliff.org)\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {%- macro remote_url() -%} https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} {%- endmacro -%} @@ -50,32 +49,48 @@ body = """ {% endfor -%} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true -# postprocessors +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A list of postprocessors using regex to modify the changelog. postprocessors = [ { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL ] +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "rc|v2.1.0|v2.1.1" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification from https://www.conventionalcommits.org. +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, # Check spelling of the commit with https://github.com/crate-ci/typos # If the spelling is incorrect, it will be automatically fixed. { pattern = '.*', replace_command = 'typos --write-changes -' }, ] -# regex for parsing and grouping commits +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "⛰️ Features" }, { message = "^fix", group = "🐛 Bug Fixes" }, @@ -93,17 +108,10 @@ commit_parsers = [ { body = ".*security", group = "🛡️ Security" }, { message = "^revert", group = "◀️ Revert" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "beta|alpha" -# regex for ignoring tags -ignore_tags = "rc|v2.1.0|v2.1.1" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "newest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "beta|alpha" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "newest" diff --git a/config/cliff.toml b/config/cliff.toml index b0f88b2362..5a3a26293f 100644 --- a/config/cliff.toml +++ b/config/cliff.toml @@ -6,14 +6,13 @@ # See documentation for more information on available options. [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -28,33 +27,49 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing s -trim = true -# postprocessors +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A list of postprocessors using regex to modify the changelog. postprocessors = [ # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL ] +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ +[release] +# # Regex to select git tags that represent releases. +# # Example: "v[0-9].*" +# tags_pattern = "v[0-9].*" +# # Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# # Changes belonging to these releases will be included in the next non-skipped release. +# # Example: "rc" +# skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ # Replace issue numbers #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, # Check spelling of the commit with https://github.com/crate-ci/typos # If the spelling is incorrect, it will be automatically fixed. #{ pattern = '.*', replace_command = 'typos --write-changes -' }, ] -# regex for parsing and grouping commits +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "🚀 Features" }, { message = "^fix", group = "🐛 Bug Fixes" }, @@ -71,19 +86,12 @@ commit_parsers = [ { body = ".*security", group = "🛡️ Security" }, { message = "^revert", group = "◀️ Revert" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -# tag_pattern = "v[0-9].*" -# regex for skipping tags -# skip_tags = "" -# regex for ignoring tags -# ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" -# limit the number of commits included in the changelog. -# limit_commits = 42 +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# # Regex to select git tags that should be excluded from the changelog. +# exclude_tags_pattern = "" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" +# # Whether to limit the total number of commits to be included in the changelog. +# max_commit_count = 42 diff --git a/examples/cocogitto.toml b/examples/cocogitto.toml index d6f47d6f36..0632180b33 100644 --- a/examples/cocogitto.toml +++ b/examples/cocogitto.toml @@ -2,14 +2,13 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ --- {% if version %}\ {% if previous.version %}\ @@ -38,29 +37,45 @@ body = """ {% endfor -%} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true -# postprocessors +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A list of postprocessors using regex to modify the changelog. postprocessors = [ { pattern = '\$REPO', replace = "https://github.com/cocogitto/cocogitto" }, # replace repository URL ] +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers ] -# regex for parsing and grouping commits +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, @@ -74,19 +89,12 @@ commit_parsers = [ { message = "^chore", group = "Miscellaneous Chores" }, { body = ".*security", group = "Security" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" -# limit the number of commits included in the changelog. -# limit_commits = 42 +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" +# # Whether to limit the total number of commits to be included in the changelog. +# max_commit_count = 42 diff --git a/examples/detailed.toml b/examples/detailed.toml index 7648691440..33ee9a1de4 100644 --- a/examples/detailed.toml +++ b/examples/detailed.toml @@ -2,14 +2,13 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -31,21 +30,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, @@ -61,17 +76,10 @@ commit_parsers = [ { message = "^chore|^ci", group = "Miscellaneous Tasks" }, { body = ".*security", group = "Security" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/examples/github-keepachangelog.toml b/examples/github-keepachangelog.toml index c50807f57b..1ca15c9212 100644 --- a/examples/github-keepachangelog.toml +++ b/examples/github-keepachangelog.toml @@ -2,7 +2,7 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -10,9 +10,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {%- macro remote_url() -%} https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} {%- endmacro -%} @@ -47,8 +46,8 @@ body = """ {%- endif %} {%- endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ {%- macro remote_url() -%} https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} {%- endmacro -%} @@ -65,22 +64,38 @@ footer = """ {% endfor %} """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ # remove issue numbers from commits { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, ] -# regex for parsing and grouping commits +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -91,17 +106,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/examples/github.toml b/examples/github.toml index e71507fd7a..5f953ef5f7 100644 --- a/examples/github.toml +++ b/examples/github.toml @@ -7,9 +7,8 @@ # token = "" [changelog] -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ ## What's Changed {%- if version %} in {{ version }}{%- endif -%} @@ -49,38 +48,46 @@ body = """ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} {%- endmacro -%} """ -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = """ +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# postprocessors +# A list of postprocessors using regex to modify the changelog. postprocessors = [] +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = false -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "rc" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = false +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of preprocessors to modify commit messages using regex prior to further processing. +message_preprocessors = [ # remove issue numbers from commits { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "beta|alpha" -# regex for ignoring tags -ignore_tags = "rc" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "newest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "beta|alpha" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "newest" diff --git a/examples/keepachangelog.toml b/examples/keepachangelog.toml index fdcbea2f99..d8d7fcdfc2 100644 --- a/examples/keepachangelog.toml +++ b/examples/keepachangelog.toml @@ -2,7 +2,7 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. header = """ # Changelog\n All notable changes to this project will be documented in this file. @@ -10,9 +10,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n """ -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version -%} ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else -%} @@ -25,8 +24,8 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ {% for release in releases -%} {% if release.version -%} {% if release.previous.version -%} @@ -41,17 +40,33 @@ footer = """ {% endfor %} """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = true -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^.*: add", group = "Added" }, { message = "^.*: support", group = "Added" }, @@ -62,17 +77,10 @@ commit_parsers = [ { message = "^.*: fix", group = "Fixed" }, { message = "^.*", group = "Changed" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = true -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/examples/minimal.toml b/examples/minimal.toml index 05c62b9f01..5f17fc404c 100644 --- a/examples/minimal.toml +++ b/examples/minimal.toml @@ -2,9 +2,8 @@ # https://git-cliff.org/docs/configuration [changelog] -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}\ {% else %}\ diff --git a/examples/scoped.toml b/examples/scoped.toml index 46c06e37d7..8c7e92e36f 100644 --- a/examples/scoped.toml +++ b/examples/scoped.toml @@ -2,14 +2,13 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -25,21 +24,37 @@ body = """ {% endfor %}\ {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" + +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, @@ -52,17 +67,10 @@ commit_parsers = [ { message = "^chore", group = "Miscellaneous Tasks" }, { body = ".*security", group = "Security" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/examples/scopesorted.toml b/examples/scopesorted.toml index ce13531c08..176d85de36 100644 --- a/examples/scopesorted.toml +++ b/examples/scopesorted.toml @@ -2,14 +2,13 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -38,22 +37,37 @@ body = """ {% raw %}\n{% endraw %}\ {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = true +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, @@ -66,17 +80,10 @@ commit_parsers = [ { message = "^chore", group = "Miscellaneous Tasks" }, { body = ".*security", group = "Security" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/examples/unconventional.toml b/examples/unconventional.toml index 0a255cf488..b212499167 100644 --- a/examples/unconventional.toml +++ b/examples/unconventional.toml @@ -2,14 +2,13 @@ # https://git-cliff.org/docs/configuration [changelog] -# changelog header +# A static header for the changelog. 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 = """ +# A Tera template to be rendered for each release in the changelog (see https://keats.github.io/tera/docs/#introduction). +body_template = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ @@ -22,22 +21,37 @@ body = """ {% endfor %} {% endfor %}\n """ -# template for the changelog footer -footer = """ +# A Tera template to be rendered as the changelog's footer (see https://keats.github.io/tera/docs/#introduction). +footer_template = """ """ -# remove the leading and trailing whitespace from the templates -trim = true +# Whether to remove leading and trailing whitespaces from all lines of the changelog's body. +trim_body_whitespace = true +# Whether to exclude changes that do not belong to any group from the changelog. +exclude_ungrouped_changes = false +[release] +# Regex to select git tags that represent releases. +# Example: "v[0-9].*" +tags_pattern = "v[0-9].*" +# Regex to select git tags that do not represent proper releases. Takes precedence over `release.tags_pattern`. +# Changes belonging to these releases will be included in the next non-skipped release. +# Example: "rc" +skip_tags_pattern = "" +# Whether to order releases chronologically or topologically. +# Must be either `time` or `topology`. +order_by = "time" -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = false -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits +[commit] +# Whether to parse commits according to the conventional commits specification (see https://www.conventionalcommits.org). +# Sets the commits' `group` (= `type`), `scope`, `message` (= `description`), `body`, `breaking`, `breaking_description` and `footers`. +parse_conventional_commits = true +# Whether to exclude commits that do not match the conventional commits specification from the changelog. +exclude_unconventional_commits = false +# Whether to split commits on newlines, treating each line as an individual commit. +split_by_newline = false +# A list of parsers using regex for extracting data from the commit message. +# Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, @@ -51,17 +65,10 @@ commit_parsers = [ { body = ".*security", group = "Security" }, { body = ".*", group = "Other (unconventional)" }, ] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# regex for matching git tags -tag_pattern = "v[0-9].*" -# regex for skipping tags -skip_tags = "v0.1.0-beta.1" -# regex for ignoring tags -ignore_tags = "" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" +# Whether to prevent breaking changes from being excluded by commit parsers. +retain_breaking_changes = false +# Regex to select git tags that should be excluded from the changelog. +exclude_tags_pattern = "v0.1.0-beta.1" +# Whether to order commits newest to oldest or oldest to newest in their group. +# Must be either `newest` or `oldest`. +sort_order = "oldest" diff --git a/git-cliff-core/Cargo.toml b/git-cliff-core/Cargo.toml index 9bd5adc672..13dca97a00 100644 --- a/git-cliff-core/Cargo.toml +++ b/git-cliff-core/Cargo.toml @@ -29,6 +29,7 @@ github = [ ] [dependencies] +clap = { version = "4.5.3", features = ["derive", "env", "wrap_help", "cargo"] } glob = { workspace = true, optional = true } regex.workspace = true log.workspace = true diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index 662c306683..ca1e26e943 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -1,7 +1,8 @@ use crate::commit::Commit; -use crate::config::{ +use crate::config::models_v2::{ + ChangelogConfig, + CommitConfig, Config, - GitConfig, }; use crate::error::Result; #[cfg(feature = "github")] @@ -37,19 +38,19 @@ pub struct Changelog<'a> { impl<'a> Changelog<'a> { /// Constructs a new instance. pub fn new(releases: Vec>, config: &'a Config) -> Result { - let trim = config.changelog.trim.unwrap_or(true); + let trim = config.changelog.trim_body_whitespace.unwrap_or(true); let mut changelog = Self { releases, body_template: Template::new( config .changelog - .body + .body_template .as_deref() .unwrap_or_default() .to_string(), trim, )?, - footer_template: match &config.changelog.footer { + footer_template: match &config.changelog.footer_template { Some(footer) => Some(Template::new(footer.to_string(), trim)?), None => None, }, @@ -63,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!( @@ -88,9 +90,15 @@ 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.git.split_commits.unwrap_or(false) { + if self.config.commit.split_by_newline.unwrap_or(false) { commit .message .lines() @@ -98,7 +106,11 @@ impl<'a> Changelog<'a> { let mut c = commit.clone(); c.message = line.to_string(); if !c.message.is_empty() { - Self::process_commit(c, &self.config.git) + Self::process_commit( + c, + &self.config.changelog, + &self.config.commit, + ) } else { None } @@ -108,6 +120,26 @@ impl<'a> Changelog<'a> { vec![commit] } }) + .filter_map(|commit| { + match commit.process(&self.config.changelog, &self.config.commit) + { + Ok(commit) => Some(commit), + Err(e) => { + trace!( + "{} - {} ({})", + commit.id[..7].to_string(), + e, + commit + .message + .lines() + .next() + .unwrap_or_default() + .trim() + ); + None + } + } + }) .collect::>(); }); } @@ -115,7 +147,7 @@ impl<'a> Changelog<'a> { /// Processes the releases and filters them out based on the configuration. fn process_releases(&mut self) { debug!("Processing the releases..."); - let skip_regex = self.config.git.skip_tags.as_ref(); + let skip_regex = self.config.commit.exclude_tags_pattern.as_ref(); let mut skipped_tags = Vec::new(); self.releases = self .releases @@ -322,12 +354,14 @@ impl<'a> Changelog<'a> { #[cfg(test)] mod test { use super::*; - use crate::config::{ + use crate::config::models_v2::{ Bump, - ChangelogConfig, CommitParser, + CommitSortOrder, + ReleaseConfig, Remote, RemoteConfig, + TagsOrderBy, TextProcessor, }; use pretty_assertions::assert_eq; @@ -337,8 +371,8 @@ mod test { fn get_test_data() -> (Config, Vec>) { let config = Config { changelog: ChangelogConfig { - header: Some(String::from("# Changelog")), - body: Some(String::from( + header: Some(String::from("# Changelog")), + body_template: Some(String::from( r#"{% if version %} ## Release [{{ version }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% if commit_id %}({{ commit_id }}){% endif %}{% else %} @@ -349,22 +383,28 @@ mod test { - {{ commit.message }}{% endfor %} {% endfor %}{% endfor %}"#, )), - footer: Some(String::from( + footer_template: Some(String::from( r#"-- total releases: {{ releases | length }} --"#, )), - trim: Some(true), - postprocessors: Some(vec![TextProcessor { + trim_body_whitespace: Some(true), + postprocessors: Some(vec![TextProcessor { pattern: Regex::new("boring") .expect("failed to compile regex"), replace: Some(String::from("exciting")), replace_command: None, }]), + exclude_ungrouped_changes: Some(false), + }, + release: ReleaseConfig { + tags_pattern: None, + skip_tags_pattern: None, + order_by: Some(TagsOrderBy::Time), }, - git: GitConfig { - conventional_commits: Some(true), - filter_unconventional: Some(false), - split_commits: Some(false), - commit_preprocessors: Some(vec![TextProcessor { + commit: CommitConfig { + parse_conventional_commits: Some(true), + exclude_unconventional_commits: Some(false), + split_by_newline: Some(false), + message_preprocessors: Some(vec![TextProcessor { pattern: Regex::new("") .expect("failed to compile regex"), replace: Some(String::from( @@ -372,7 +412,7 @@ mod test { )), replace_command: None, }]), - commit_parsers: Some(vec![ + commit_parsers: Some(vec![ CommitParser { sha: Some(String::from("tea")), message: None, @@ -484,15 +524,11 @@ mod test { pattern: None, }, ]), - protect_breaking_commits: None, - filter_commits: Some(false), - tag_pattern: None, - skip_tags: Regex::new("v3.*").ok(), - ignore_tags: None, - topo_order: Some(false), - sort_commits: Some(String::from("oldest")), - link_parsers: None, - limit_commits: None, + retain_breaking_changes: None, + exclude_tags_pattern: Regex::new("v3.*").ok(), + sort_order: Some(CommitSortOrder::Oldest), + link_parsers: None, + max_commit_count: None, }, remote: RemoteConfig { github: Remote { @@ -701,9 +737,9 @@ mod test { #[test] fn changelog_generator_split_commits() -> Result<()> { let (mut config, mut releases) = get_test_data(); - config.git.split_commits = Some(true); - config.git.filter_unconventional = Some(false); - config.git.protect_breaking_commits = Some(true); + config.commit.split_by_newline = Some(true); + config.commit.exclude_unconventional_commits = Some(false); + config.commit.retain_breaking_changes = Some(true); releases[0].commits.push(Commit::new( String::from("0bc123"), String::from( diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index c9c535b6a4..188a51ea8c 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -1,6 +1,7 @@ -use crate::config::{ +use crate::config::models_v2::{ + ChangelogConfig, + CommitConfig, CommitParser, - GitConfig, LinkParser, TextProcessor, }; @@ -180,28 +181,32 @@ impl Commit<'_> { /// * converts commit to a conventional commit /// * sets the group for the commit /// * extacts links and generates URLs - pub fn process(&self, config: &GitConfig) -> Result { + pub fn process( + &self, + changelog_config: &ChangelogConfig, + commit_config: &CommitConfig, + ) -> Result { let mut commit = self.clone(); - if let Some(preprocessors) = &config.commit_preprocessors { + if let Some(preprocessors) = &commit_config.message_preprocessors { commit = commit.preprocess(preprocessors)?; } - if config.conventional_commits.unwrap_or(true) { - if config.filter_unconventional.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() { commit = conv_commit; } } - if let Some(parsers) = &config.commit_parsers { + if let Some(parsers) = &commit_config.commit_parsers { commit = commit.parse( parsers, - config.protect_breaking_commits.unwrap_or(false), - config.filter_commits.unwrap_or(false), + commit_config.retain_breaking_changes.unwrap_or(false), + changelog_config.exclude_ungrouped_changes.unwrap_or(false), )?; } - if let Some(parsers) = &config.link_parsers { + if let Some(parsers) = &commit_config.link_parsers { commit = commit.parse_links(parsers)?; } Ok(commit) @@ -236,13 +241,13 @@ impl Commit<'_> { /// States if the commit is skipped in the provided `CommitParser`. /// - /// Returns `false` if `protect_breaking_commits` is enabled in the config - /// and the commit is breaking, or the parser's `skip` field is None or - /// `false`. Returns `true` otherwise. - fn skip_commit(&self, parser: &CommitParser, protect_breaking: bool) -> bool { + /// Returns `false` if `commit.retain_breaking_changes` is enabled in the + /// config and the commit is breaking, or the parser's `skip` field is None + /// or `false`. Returns `true` otherwise. + fn skip_commit(&self, parser: &CommitParser, retain_breaking: bool) -> bool { parser.skip.unwrap_or(false) && !(self.conv.as_ref().map(|c| c.breaking()).unwrap_or(false) && - protect_breaking) + retain_breaking) } /// Parses the commit using [`CommitParser`]s. @@ -254,8 +259,8 @@ impl Commit<'_> { pub fn parse( mut self, parsers: &[CommitParser], - protect_breaking: bool, - filter: bool, + retain_breaking: bool, + exclude_ungrouped: bool, ) -> Result { for parser in parsers { let mut regex_checks = Vec::new(); @@ -296,7 +301,7 @@ impl Commit<'_> { if parser.sha.clone().map(|v| v.to_lowercase()).as_deref() == Some(&self.id) { - if self.skip_commit(parser, protect_breaking) { + if self.skip_commit(parser, retain_breaking) { return Err(AppError::GroupError(String::from( "Skipping commit", ))); @@ -310,7 +315,7 @@ impl Commit<'_> { } for (regex, text) in regex_checks { if regex.is_match(text.trim()) { - if self.skip_commit(parser, protect_breaking) { + if self.skip_commit(parser, retain_breaking) { return Err(AppError::GroupError(String::from( "Skipping commit", ))); @@ -332,7 +337,7 @@ impl Commit<'_> { } } } - if !filter { + if !exclude_ungrouped { Ok(self) } else { Err(AppError::GroupError(String::from( @@ -487,8 +492,11 @@ mod test { #[test] fn conventional_footers() { - let cfg = crate::config::GitConfig { - conventional_commits: Some(true), + let changelog_config = crate::config::models_v2::ChangelogConfig { + ..Default::default() + }; + let commit_config = crate::config::models_v2::CommitConfig { + parse_conventional_commits: Some(true), ..Default::default() }; let test_cases = vec![ @@ -532,7 +540,9 @@ mod test { ), ]; for (commit, footers) in &test_cases { - let commit = commit.process(&cfg).expect("commit should process"); + let commit = commit + .process(&changelog_config, &commit_config) + .expect("commit should process"); assert_eq!(&commit.footers().collect::>(), footers); } } diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs deleted file mode 100644 index ad778637d2..0000000000 --- a/git-cliff-core/src/config.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::command; -use crate::error::Result; -use regex::{ - Regex, - RegexBuilder, -}; -use secrecy::SecretString; -use serde::{ - Deserialize, - Serialize, -}; -use std::fmt; -use std::fs; -use std::path::Path; -use std::path::PathBuf; - -/// 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"), - }, - ]; - -} - -/// Configuration values. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - /// Configuration values about changelog generation. - #[serde(default)] - pub changelog: ChangelogConfig, - /// Configuration values about git. - #[serde(default)] - pub git: GitConfig, - /// Configuration values about remote. - #[serde(default)] - pub remote: RemoteConfig, - /// Configuration values about bump version. - #[serde(default)] - pub bump: Bump, -} - -/// Changelog configuration. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct ChangelogConfig { - /// Changelog header. - pub header: Option, - /// Changelog body, template. - pub body: Option, - /// Changelog footer. - pub footer: Option, - /// Trim the template. - pub trim: Option, - /// Changelog postprocessors. - pub postprocessors: Option>, -} - -/// Git configuration -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct GitConfig { - /// Whether to enable parsing conventional commits. - pub conventional_commits: Option, - /// Whether to filter out unconventional commits. - pub filter_unconventional: Option, - /// Whether to split commits by line, processing each line as an individual - /// commit. - pub split_commits: Option, - - /// Git commit preprocessors. - pub commit_preprocessors: Option>, - /// Git commit parsers. - pub commit_parsers: Option>, - /// Whether to protect all breaking changes from being skipped by a commit - /// parser. - pub protect_breaking_commits: Option, - /// Link parsers. - pub link_parsers: Option>, - /// Whether to filter out commits. - pub filter_commits: Option, - /// Blob pattern for git tags. - #[serde(with = "serde_regex", default)] - pub tag_pattern: Option, - /// Regex to skip matched tags. - #[serde(with = "serde_regex", default)] - pub skip_tags: Option, - /// Regex to ignore matched tags. - #[serde(with = "serde_regex", default)] - pub ignore_tags: Option, - /// Whether to sort tags topologically. - pub topo_order: Option, - /// Sorting of the commits inside sections. - pub sort_commits: Option, - /// Limit the number of commits included in the changelog. - pub limit_commits: Option, -} - -/// Remote configuration. -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RemoteConfig { - /// GitHub remote. - pub github: Remote, -} - -/// A single remote. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Remote { - /// Owner of the remote. - pub owner: String, - /// Repository name. - pub repo: String, - /// Access token. - #[serde(skip_serializing)] - pub token: Option, -} - -impl fmt::Display for Remote { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.owner, self.repo) - } -} - -impl PartialEq for Remote { - fn eq(&self, other: &Self) -> bool { - self.to_string() == other.to_string() - } -} - -impl Remote { - /// Constructs a new instance. - pub fn new>(owner: S, repo: S) -> Self { - Self { - owner: owner.into(), - repo: repo.into(), - token: None, - } - } - - /// Returns `true` if the remote has an owner and repo. - pub fn is_set(&self) -> bool { - !self.owner.is_empty() && !self.repo.is_empty() - } -} - -/// Bump version configuration. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Bump { - /// Configures automatic minor version increments for feature changes. - /// - /// When `true`, a feature will always trigger a minor version update. - /// When `false`, a feature will trigger: - /// - /// - A patch version update if the major version is 0. - /// - A minor version update otherwise. - pub features_always_bump_minor: Option, - - /// Configures 0 -> 1 major version increments for breaking changes. - /// - /// When `true`, a breaking change commit will always trigger a major - /// version update (including the transition from version 0 to 1) - /// When `false`, a breaking change commit will trigger: - /// - /// - A minor version update if the major version is 0. - /// - A major version update otherwise. - pub breaking_always_bump_major: Option, -} - -/// Parser for grouping commits. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct CommitParser { - /// SHA1 of the commit. - pub sha: Option, - /// Regex for matching the commit message. - #[serde(with = "serde_regex", default)] - pub message: Option, - /// Regex for matching the commit body. - #[serde(with = "serde_regex", default)] - pub body: Option, - /// Group of the commit. - pub group: Option, - /// Default scope of the commit. - pub default_scope: Option, - /// Commit scope for overriding the default scope. - pub scope: Option, - /// Whether to skip this commit group. - pub skip: Option, - /// Field name of the commit to match the regex against. - pub field: Option, - /// Regex for matching the field value. - #[serde(with = "serde_regex", default)] - pub pattern: Option, -} - -/// TextProcessor, e.g. for modifying commit messages. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TextProcessor { - /// Regex for matching a text to replace. - #[serde(with = "serde_regex")] - pub pattern: Regex, - /// Replacement text. - pub replace: Option, - /// Command that will be run for replacing the commit message. - pub replace_command: Option, -} - -impl TextProcessor { - /// Replaces the text with using the given pattern or the command output. - pub fn replace( - &self, - rendered: &mut String, - command_envs: Vec<(&str, &str)>, - ) -> Result<()> { - if let Some(text) = &self.replace { - *rendered = self.pattern.replace_all(rendered, text).to_string(); - } else if let Some(command) = &self.replace_command { - if self.pattern.is_match(rendered) { - *rendered = - command::run(command, Some(rendered.to_string()), command_envs)?; - } - } - Ok(()) - } -} - -/// Parser for extracting links in commits. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LinkParser { - /// Regex for finding links in the commit message. - #[serde(with = "serde_regex")] - pub pattern: Regex, - /// The string used to generate the link URL. - pub href: String, - /// The string used to generate the link text. - pub text: Option, -} - -impl Config { - /// 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) - } - - /// Parses the config file from string and returns the values. - pub fn parse_from_str(contents: &str) -> Result { - 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 { - if MANIFEST_INFO - .iter() - .any(|v| path.file_name() == v.path.file_name()) - { - 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("__"), - ) - .build()? - .try_deserialize()?) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - use std::env; - #[test] - fn parse_config() -> Result<()> { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .expect("parent directory not found") - .to_path_buf() - .join("config") - .join(crate::DEFAULT_CONFIG); - - const FOOTER_VALUE: &str = "test"; - const TAG_PATTERN_VALUE: &str = ".*[0-9].*"; - const IGNORE_TAGS_VALUE: &str = "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"; - - env::set_var("GIT_CLIFF__CHANGELOG__FOOTER", FOOTER_VALUE); - env::set_var("GIT_CLIFF__GIT__TAG_PATTERN", TAG_PATTERN_VALUE); - env::set_var("GIT_CLIFF__GIT__IGNORE_TAGS", IGNORE_TAGS_VALUE); - - let config = Config::parse(&path)?; - - assert_eq!(Some(String::from(FOOTER_VALUE)), config.changelog.footer); - assert_eq!( - Some(String::from(TAG_PATTERN_VALUE)), - config - .git - .tag_pattern - .map(|tag_pattern| tag_pattern.to_string()) - ); - assert_eq!( - Some(String::from(IGNORE_TAGS_VALUE)), - config - .git - .ignore_tags - .map(|ignore_tags| ignore_tags.to_string()) - ); - Ok(()) - } - - #[test] - fn remote_config() { - let remote1 = Remote::new("abc", "xyz1"); - let remote2 = Remote::new("abc", "xyz2"); - assert!(!remote1.eq(&remote2)); - assert_eq!("abc/xyz1", remote1.to_string()); - assert!(remote1.is_set()); - assert!(!Remote::new("", "test").is_set()); - assert!(!Remote::new("test", "").is_set()); - assert!(!Remote::new("", "").is_set()); - } -} diff --git a/git-cliff-core/src/config/embed.rs b/git-cliff-core/src/config/embed.rs new file mode 100644 index 0000000000..90f3cf4960 --- /dev/null +++ b/git-cliff-core/src/config/embed.rs @@ -0,0 +1,106 @@ +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) { + info!("Using configuration from manifest in {:?}.", info.path); + return Ok(Some(info.regex.replace_all(&contents, "[").to_string())); + } + } + } + Err(Error::EmbeddedError(String::from( + "Could not read config from manifest.", + ))) +} + +/// 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/migrate.rs b/git-cliff-core/src/config/migrate.rs new file mode 100644 index 0000000000..90fdc1ebfb --- /dev/null +++ b/git-cliff-core/src/config/migrate.rs @@ -0,0 +1,56 @@ +use crate::error::{ + Error, + Result, +}; +use clap::Args; +use std::{ + fs, + io::Write, + path::PathBuf, +}; + +/// Migrates configuration files from the old to the new schema. +#[derive(Args, Debug)] +pub struct MigrateArgs { + /// The file to read the original configuration from. + #[arg(long = "in")] + pub in_path: PathBuf, + + /// The file to write the migrated configuration to. + #[arg(long = "out")] + pub out_path: PathBuf, +} + +/// Migrates configuration files from the old to the new schema. +pub fn run(args: &MigrateArgs) -> Result<()> { + // load the old configuration + if !args.in_path.exists() { + return Err(Error::ArgumentError(format!( + "File {0} does not exist.", + &args + .in_path + .to_str() + .expect("could not unwrap argument 'in_path'") + ))); + } + + let old_config = + super::parsing::parse::(&args.in_path)?; + + // convert to the new config format + let new_config = super::models_v2::Config::from(old_config); + let new_toml = toml::to_string(&new_config) + .expect("could not serialize migrated config into toml"); + + // write the new config file + let mut new_config_file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&args.out_path)?; + + new_config_file.write_all(new_toml.as_bytes())?; + new_config_file.flush()?; + + Ok(()) +} diff --git a/git-cliff-core/src/config/mod.rs b/git-cliff-core/src/config/mod.rs new file mode 100644 index 0000000000..55a489d0c8 --- /dev/null +++ b/git-cliff-core/src/config/mod.rs @@ -0,0 +1,20 @@ +/// 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. +pub mod models_v2; +/// Parsing for git-cliff Config. +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/models_v1.rs b/git-cliff-core/src/config/models_v1.rs new file mode 100644 index 0000000000..19713c1452 --- /dev/null +++ b/git-cliff-core/src/config/models_v1.rs @@ -0,0 +1,89 @@ +use super::models_v2::{ + Bump, + CommitParser, + LinkParser, + RemoteConfig, + TextProcessor, +}; +use regex::Regex; +use serde::{ + Deserialize, + Serialize, +}; + +/// Configuration values. +#[deprecated(since = "3.0.0", note = "deprecated in favor of models_v2")] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + /// Configuration values about changelog generation. + #[allow(deprecated)] + #[serde(default)] + pub changelog: ChangelogConfig, + /// Configuration values about git. + #[allow(deprecated)] + #[serde(default)] + pub git: GitConfig, + /// Configuration values about remote. + #[serde(default)] + pub remote: RemoteConfig, + /// Configuration values about bump version. + #[serde(default)] + pub bump: Bump, +} + +/// Changelog configuration. +#[allow(deprecated)] +#[deprecated(since = "3.0.0", note = "deprecated in favor of models_v2")] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ChangelogConfig { + /// Changelog header. + pub header: Option, + /// Changelog body, template. + pub body: Option, + /// Changelog footer. + pub footer: Option, + /// Trim the template. + pub trim: Option, + /// Changelog postprocessors. + pub postprocessors: Option>, +} + +/// Git configuration +#[deprecated(since = "3.0.0", note = "deprecated in favor of models_v2")] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct GitConfig { + /// Whether to enable parsing conventional commits. + pub conventional_commits: Option, + /// Whether to filter out unconventional commits. + pub filter_unconventional: Option, + /// Whether to split commits by line, processing each line as an individual + /// commit. + pub split_commits: Option, + + /// Git commit preprocessors. + pub commit_preprocessors: Option>, + /// Git commit parsers. + pub commit_parsers: Option>, + /// Whether to protect all breaking changes from being skipped by a commit + /// parser. + pub protect_breaking_commits: Option, + /// Link parsers. + pub link_parsers: Option>, + /// Whether to filter out commits. + pub filter_commits: Option, + /// Blob pattern for git tags. + #[serde(with = "serde_regex", default)] + pub tag_pattern: Option, + /// Regex to skip matched tags. + #[serde(with = "serde_regex", default)] + pub skip_tags: Option, + /// Regex to ignore matched tags. + #[serde(with = "serde_regex", default)] + pub ignore_tags: Option, + /// Whether to sort tags topologically. + pub topo_order: Option, + /// Sorting of the commits inside sections. + pub sort_commits: Option, + /// Limit the number of commits included in the changelog. + pub limit_commits: Option, +} diff --git a/git-cliff-core/src/config/models_v2.rs b/git-cliff-core/src/config/models_v2.rs new file mode 100644 index 0000000000..53e8da6a85 --- /dev/null +++ b/git-cliff-core/src/config/models_v2.rs @@ -0,0 +1,314 @@ +use crate::command; +use crate::error::Result; +use clap::ValueEnum; +use regex::Regex; +use secrecy::SecretString; +use serde::{ + Deserialize, + Serialize, +}; +use std::fmt; + +/// Options for ordering git tags. +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TagsOrderBy { + /// Whether to sort git tags according to their creation date. + Time, + /// Whether to sort git tags according to the git topology. + Topology, +} + +/// Options for ordering commits chronologically. +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CommitSortOrder { + /// Whether to sort starting with the oldest element. + Oldest, + /// Whether to sort starting with the newest element. + Newest, +} + +/// Configuration values. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + /// Configuration values about changelog generation. + #[serde(default)] + pub changelog: ChangelogConfig, + /// Configuration values about releases. + #[serde(default)] + pub release: ReleaseConfig, + /// Configuration values about git commits. + #[serde(default)] + pub commit: CommitConfig, + /// Configuration values about remote. + #[serde(default)] + pub remote: RemoteConfig, + /// Configuration values about bump version. + #[serde(default)] + pub bump: Bump, +} + +/// Changelog configuration. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ChangelogConfig { + /// A static header for the changelog. + pub header: Option, + /// A Tera template to be rendered for each release in the changelog. + pub body_template: Option, + /// A Tera template to be rendered as the changelog's footer. + pub footer_template: Option, + /// Whether to remove leading and trailing whitespaces from all lines of the + /// changelog's body. + pub trim_body_whitespace: Option, + /// A list of postprocessors using regex to modify the changelog. + pub postprocessors: Option>, + /// Whether to exclude changes that do not belong to any group from the + /// changelog. + pub exclude_ungrouped_changes: Option, +} + +/// Release configuration. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ReleaseConfig { + /// Regex to select git tags that represent releases. + /// Example: "v[0-9].*" + #[serde(with = "serde_regex", default)] + pub tags_pattern: Option, + /// Regex to select git tags that do not represent proper releases. Takes + /// precedence over `release.tags_pattern`. Changes belonging to these + /// releases will be included in the next non-skipped release. Example: "rc" + #[serde(with = "serde_regex", default)] + pub skip_tags_pattern: Option, + /// Whether to order releases chronologically or topologically. + pub order_by: Option, +} + +/// Git commit configuration +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CommitConfig { + /// Whether to parse commits according to the conventional commits + /// specification. + pub parse_conventional_commits: Option, + /// Whether to exclude commits that do not match the conventional commits + /// specification from the changelog. + pub exclude_unconventional_commits: Option, + /// Whether to split commits on newlines, treating each line as an + /// individual commit. + pub split_by_newline: Option, + + /// A list of preprocessors to modify commit messages using regex prior to + /// further processing. + pub message_preprocessors: Option>, + /// A list of parsers using regex for extracting data from the commit + /// message. + pub commit_parsers: Option>, + /// Whether to prevent breaking changes from being excluded by commit + /// parsers. + pub retain_breaking_changes: Option, + /// A list of parsers using regex for extracting external references found + /// in commit messages, and turning them into links. The gemerated links can + /// be used in the body template as `commit.links`. + pub link_parsers: Option>, + /// Regex to select git tags that should be excluded from the changelog. + #[serde(with = "serde_regex", default)] + pub exclude_tags_pattern: Option, + /// Whether to order commits newest to oldest or oldest to newest in their + /// group. + pub sort_order: Option, + /// Whether to limit the total number of commits to be included in the + /// changelog. + pub max_commit_count: Option, +} + +/// Remote configuration. +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RemoteConfig { + /// GitHub remote. + pub github: Remote, +} + +/// A single remote. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Remote { + /// Owner of the remote. + pub owner: String, + /// Repository name. + pub repo: String, + /// Access token. + #[serde(skip_serializing)] + pub token: Option, +} + +impl fmt::Display for Remote { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.owner, self.repo) + } +} + +impl PartialEq for Remote { + fn eq(&self, other: &Self) -> bool { + self.to_string() == other.to_string() + } +} + +impl Remote { + /// Constructs a new instance. + pub fn new>(owner: S, repo: S) -> Self { + Self { + owner: owner.into(), + repo: repo.into(), + token: None, + } + } + + /// Returns `true` if the remote has an owner and repo. + pub fn is_set(&self) -> bool { + !self.owner.is_empty() && !self.repo.is_empty() + } +} + +/// Bump version configuration. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Bump { + /// Configures automatic minor version increments for feature changes. + /// + /// When `true`, a feature will always trigger a minor version update. + /// When `false`, a feature will trigger: + /// + /// - A patch version update if the major version is 0. + /// - A minor version update otherwise. + pub features_always_bump_minor: Option, + + /// Configures 0 -> 1 major version increments for breaking changes. + /// + /// When `true`, a breaking change commit will always trigger a major + /// version update (including the transition from version 0 to 1) + /// When `false`, a breaking change commit will trigger: + /// + /// - A minor version update if the major version is 0. + /// - A major version update otherwise. + pub breaking_always_bump_major: Option, +} + +/// Parser for grouping commits. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CommitParser { + /// SHA1 of the commit. + pub sha: Option, + /// Regex for matching the commit message. + #[serde(with = "serde_regex", default)] + pub message: Option, + /// Regex for matching the commit body. + #[serde(with = "serde_regex", default)] + pub body: Option, + /// Group of the commit. + pub group: Option, + /// Default scope of the commit. + pub default_scope: Option, + /// Commit scope for overriding the default scope. + pub scope: Option, + /// Whether to skip this commit group. + pub skip: Option, + /// Field name of the commit to match the regex against. + pub field: Option, + /// Regex for matching the field value. + #[serde(with = "serde_regex", default)] + pub pattern: Option, +} + +/// TextProcessor, e.g. for modifying commit messages. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextProcessor { + /// Regex for matching a text to replace. + #[serde(with = "serde_regex")] + pub pattern: Regex, + /// Replacement text. + pub replace: Option, + /// Command that will be run for replacing the commit message. + pub replace_command: Option, +} + +impl TextProcessor { + /// Replaces the text with using the given pattern or the command output. + pub fn replace( + &self, + rendered: &mut String, + command_envs: Vec<(&str, &str)>, + ) -> Result<()> { + if let Some(text) = &self.replace { + *rendered = self.pattern.replace_all(rendered, text).to_string(); + } else if let Some(command) = &self.replace_command { + if self.pattern.is_match(rendered) { + *rendered = + command::run(command, Some(rendered.to_string()), command_envs)?; + } + } + Ok(()) + } +} + +/// Parser for extracting links in commits. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LinkParser { + /// Regex for finding links in the commit message. + #[serde(with = "serde_regex")] + pub pattern: Regex, + /// The string used to generate the link URL. + pub href: String, + /// The string used to generate the link text. + pub text: Option, +} + +impl Config { + /// Creates a v2 Config from a deprecated v1 Config. + #[allow(deprecated)] + pub fn from(config_v1: super::models_v1::Config) -> Config { + Config { + changelog: ChangelogConfig { + header: config_v1.changelog.header, + body_template: config_v1.changelog.body, + footer_template: config_v1.changelog.footer, + trim_body_whitespace: config_v1.changelog.trim, + postprocessors: config_v1.changelog.postprocessors, + exclude_ungrouped_changes: config_v1.git.filter_commits, + }, + release: ReleaseConfig { + tags_pattern: config_v1.git.tag_pattern, + skip_tags_pattern: config_v1.git.ignore_tags, + order_by: Some( + if config_v1 + .git + .topo_order + .is_some_and(|topo_order| topo_order) + { + TagsOrderBy::Topology + } else { + TagsOrderBy::Time + }, + ), + }, + commit: CommitConfig { + sort_order: config_v1.git.sort_commits.map( + |s| { + CommitSortOrder::from_str(&s, true) + .expect("Incorrect config value for 'sort_commits'") + }, + ), + max_commit_count: config_v1.git.limit_commits, + split_by_newline: config_v1.git.split_commits, + exclude_tags_pattern: config_v1.git.skip_tags, + message_preprocessors: config_v1.git.commit_preprocessors, + link_parsers: config_v1.git.link_parsers, + parse_conventional_commits: config_v1.git.conventional_commits, + exclude_unconventional_commits: config_v1.git.filter_unconventional, + commit_parsers: config_v1.git.commit_parsers, + retain_breaking_changes: config_v1 + .git + .protect_breaking_commits, + }, + remote: config_v1.remote.clone(), + bump: config_v1.bump.clone(), + } + } +} diff --git a/git-cliff-core/src/config/parsing.rs b/git-cliff-core/src/config/parsing.rs new file mode 100644 index 0000000000..e2ae190721 --- /dev/null +++ b/git-cliff-core/src/config/parsing.rs @@ -0,0 +1,44 @@ +use crate::error::Result; +use regex::RegexBuilder; +use serde::Deserialize; +use std::{ + ffi::OsStr, + fs, + path::Path, +}; + +/// Regex for matching the metadata in Cargo.toml +const CARGO_METADATA_REGEX: &str = + r"^\[(?:workspace|package)\.metadata\.git\-cliff\."; + +/// Regex for matching the metadata in pyproject.toml +const PYPROJECT_METADATA_REGEX: &str = r"^\[(?:tool)\.git\-cliff\."; + +/// Loads configuration from a file and GIT_CLIFF_* environment variables. +pub fn parse<'de, T: Deserialize<'de>>(path: &Path) -> Result { + let file_name = path.file_name(); + let config_builder = if file_name == Some(OsStr::new("Cargo.toml")) || + file_name == Some(OsStr::new("pyproject.toml")) + { + 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 + .add_source(config::Environment::with_prefix("GIT_CLIFF").separator("__")) + .build()? + .try_deserialize()?) +} diff --git a/git-cliff-core/src/config/test.rs b/git-cliff-core/src/config/test.rs new file mode 100644 index 0000000000..66fab9b5ee --- /dev/null +++ b/git-cliff-core/src/config/test.rs @@ -0,0 +1,69 @@ +use super::models_v2::{ + Config, + Remote, +}; +use crate::config::{ + parsing, + DEFAULT_CONFIG_FILENAME, +}; +use crate::error::Result; +use std::env; +use std::path::PathBuf; + +#[test] +fn parse_config() -> Result<()> { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("parent directory not found") + .to_path_buf() + .join("config") + .join(DEFAULT_CONFIG_FILENAME); + + const FOOTER_VALUE: &str = "test"; + const RELEASE_TAGS_PATTERN_VALUE: &str = ".*[0-9].*"; + const RELEASE_SKIP_TAGS_PATTERN_VALUE: &str = "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"; + + env::set_var("GIT_CLIFF__CHANGELOG__FOOTER_TEMPLATE", FOOTER_VALUE); + env::set_var( + "GIT_CLIFF__RELEASE__TAGS_PATTERN", + RELEASE_TAGS_PATTERN_VALUE, + ); + env::set_var( + "GIT_CLIFF__RELEASE__SKIP_TAGS_PATTERN", + RELEASE_SKIP_TAGS_PATTERN_VALUE, + ); + + let config = parsing::parse::(&path)?; + + assert_eq!( + Some(String::from(FOOTER_VALUE)), + config.changelog.footer_template + ); + assert_eq!( + Some(String::from(RELEASE_TAGS_PATTERN_VALUE)), + config + .release + .tags_pattern + .map(|tags_pattern| tags_pattern.to_string()) + ); + assert_eq!( + Some(String::from(RELEASE_SKIP_TAGS_PATTERN_VALUE)), + config + .release + .skip_tags_pattern + .map(|skip_tags_pattern| skip_tags_pattern.to_string()) + ); + Ok(()) +} + +#[test] +fn remote_config() { + let remote1 = Remote::new("abc", "xyz1"); + let remote2 = Remote::new("abc", "xyz2"); + assert!(!remote1.eq(&remote2)); + assert_eq!("abc/xyz1", remote1.to_string()); + assert!(remote1.is_set()); + assert!(!Remote::new("", "test").is_set()); + assert!(!Remote::new("test", "").is_set()); + assert!(!Remote::new("", "").is_set()); +} diff --git a/git-cliff-core/src/embed.rs b/git-cliff-core/src/embed.rs deleted file mode 100644 index c4b18275cb..0000000000 --- a/git-cliff-core/src/embed.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::config::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) { - 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/github.rs b/git-cliff-core/src/github.rs index 4548814604..73e9ad162e 100644 --- a/git-cliff-core/src/github.rs +++ b/git-cliff-core/src/github.rs @@ -1,4 +1,4 @@ -use crate::config::Remote; +use crate::config::models_v2::Remote; use crate::error::*; use futures::{ future, diff --git a/git-cliff-core/src/lib.rs b/git-cliff-core/src/lib.rs index a2202fe1b6..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: &str = "cliff.toml"; /// Default output file. pub const DEFAULT_OUTPUT: &str = "CHANGELOG.md"; /// Default ignore file. diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs index 34a5c40557..d9b25606dc 100644 --- a/git-cliff-core/src/release.rs +++ b/git-cliff-core/src/release.rs @@ -1,5 +1,5 @@ use crate::commit::Commit; -use crate::config::Bump; +use crate::config::models_v2::Bump; use crate::error::Result; #[cfg(feature = "github")] use crate::github::{ diff --git a/git-cliff-core/src/repo.rs b/git-cliff-core/src/repo.rs index ed7e10f0ee..d52b106677 100644 --- a/git-cliff-core/src/repo.rs +++ b/git-cliff-core/src/repo.rs @@ -1,4 +1,7 @@ -use crate::config::Remote; +use crate::config::models_v2::{ + Remote, + TagsOrderBy, +}; use crate::error::{ Error, Result, @@ -118,7 +121,7 @@ impl Repository { pub fn tags( &self, pattern: &Option, - topo_order: bool, + order_by: &TagsOrderBy, ) -> Result> { let mut tags: Vec<(Commit, String)> = Vec::new(); let tag_names = self.inner.tag_names(None)?; @@ -143,7 +146,7 @@ impl Repository { } } } - if !topo_order { + if order_by == &TagsOrderBy::Time { tags.sort_by(|a, b| a.0.time().seconds().cmp(&b.0.time().seconds())); } Ok(tags @@ -262,7 +265,7 @@ mod test { } } } - let tags = repository.tags(&None, false)?; + let tags = repository.tags(&None, &TagsOrderBy::Time)?; assert_eq!(&get_last_tag()?, tags.last().expect("no tags found").1); Ok(()) } @@ -275,7 +278,7 @@ mod test { .expect("parent directory not found") .to_path_buf(), )?; - let tags = repository.tags(&None, true)?; + let tags = repository.tags(&None, &TagsOrderBy::Topology)?; assert_eq!( tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6").expect( "the commit hash does not exist in the repository (tag v0.1.0)" @@ -294,7 +297,7 @@ mod test { Regex::new("^v[0-9]+\\.[0-9]+\\.[0-9]$") .expect("the regex is not valid"), ), - true, + &TagsOrderBy::Topology, )?; assert_eq!( tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6").expect( diff --git a/git-cliff-core/src/template.rs b/git-cliff-core/src/template.rs index d4542c8fa4..6db4a67fd0 100644 --- a/git-cliff-core/src/template.rs +++ b/git-cliff-core/src/template.rs @@ -1,5 +1,5 @@ use crate::{ - config::TextProcessor, + config::models_v2::TextProcessor, error::{ Error, Result, diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 27b2142668..db34bc54ca 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -2,10 +2,10 @@ use git_cliff_core::commit::{ Commit, Signature, }; -use git_cliff_core::config::{ +use git_cliff_core::config::models_v2::{ ChangelogConfig, + CommitConfig, CommitParser, - GitConfig, LinkParser, TextProcessor, }; @@ -20,8 +20,8 @@ use std::fmt::Write; #[test] fn generate_changelog() -> Result<()> { let changelog_config = ChangelogConfig { - header: Some(String::from("this is a changelog")), - body: Some(String::from( + header: Some(String::from("this is a changelog")), + body_template: Some(String::from( r#" ## Release {{ version }} - {% for group, commits in commits | group_by(attribute="group") %} @@ -38,20 +38,21 @@ fn generate_changelog() -> Result<()> { {% endfor -%} {% endfor %}"#, )), - footer: Some(String::from("eoc - end of changelog")), - trim: None, - postprocessors: None, + footer_template: Some(String::from("eoc - end of changelog")), + trim_body_whitespace: None, + postprocessors: None, + exclude_ungrouped_changes: Some(true), }; - let git_config = GitConfig { - conventional_commits: Some(true), - filter_unconventional: Some(true), - split_commits: Some(false), - commit_preprocessors: Some(vec![TextProcessor { + let commit_config = CommitConfig { + parse_conventional_commits: Some(true), + exclude_unconventional_commits: Some(true), + split_by_newline: Some(false), + message_preprocessors: Some(vec![TextProcessor { pattern: Regex::new(r"\(fixes (#[1-9]+)\)").unwrap(), replace: Some(String::from("[closes Issue${1}]")), replace_command: None, }]), - commit_parsers: Some(vec![ + commit_parsers: Some(vec![ CommitParser { sha: Some(String::from("coffee")), message: None, @@ -108,14 +109,10 @@ fn generate_changelog() -> Result<()> { pattern: Regex::new("John Doe").ok(), }, ]), - protect_breaking_commits: None, - filter_commits: Some(true), - tag_pattern: None, - skip_tags: None, - ignore_tags: None, - topo_order: None, - sort_commits: None, - link_parsers: Some(vec![ + retain_breaking_changes: None, + exclude_tags_pattern: None, + sort_order: None, + link_parsers: Some(vec![ LinkParser { pattern: Regex::new("#(\\d+)").unwrap(), href: String::from("https://github.com/$1"), @@ -127,7 +124,7 @@ fn generate_changelog() -> Result<()> { text: Some(String::from("$1")), }, ]), - limit_commits: None, + max_commit_count: None, }; let mut commit_with_author = Commit::new( @@ -183,7 +180,7 @@ fn generate_changelog() -> Result<()> { commit_with_author ] .iter() - .filter_map(|c| c.process(&git_config).ok()) + .filter_map(|c| c.process(&changelog_config, &commit_config).ok()) .collect::>(), commit_id: None, timestamp: 0, @@ -224,14 +221,15 @@ fn generate_changelog() -> Result<()> { ]; let out = &mut String::new(); - let template = Template::new(changelog_config.body.unwrap(), false)?; + let body_template = + Template::new(changelog_config.body_template.unwrap(), false)?; writeln!(out, "{}", changelog_config.header.unwrap()).unwrap(); for release in releases { write!( out, "{}", - template.render( + body_template.render( &release, Option::>::None.as_ref(), &[TextProcessor { @@ -243,7 +241,7 @@ fn generate_changelog() -> Result<()> { ) .unwrap(); } - writeln!(out, "{}", changelog_config.footer.unwrap()).unwrap(); + writeln!(out, "{}", changelog_config.footer_template.unwrap()).unwrap(); assert_eq!( r#"this is a changelog 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 3f8d6766a2..cf5f27974a 100644 --- a/git-cliff/src/args.rs +++ b/git-cliff/src/args.rs @@ -10,11 +10,19 @@ use clap::{ }, ArgAction, Parser, + Subcommand, ValueEnum, }; use git_cliff_core::{ - config::Remote, - DEFAULT_CONFIG, + config::{ + migrate::MigrateArgs, + models_v2::{ + CommitSortOrder, + Remote, + TagsOrderBy, + }, + DEFAULT_CONFIG_FILENAME, + }, DEFAULT_OUTPUT, }; use glob::Pattern; @@ -29,10 +37,9 @@ pub enum Strip { All, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] -pub enum Sort { - Oldest, - Newest, +#[derive(Debug, Subcommand)] +pub enum SubCommands { + MigrateConfig(MigrateArgs), } /// Command-line arguments to parse. @@ -42,7 +49,7 @@ pub enum Sort { author = clap::crate_authors!("\n"), about, rename_all_env = "screaming-snake", - help_template = "\ + help_template = "\ {before-help}{name} {version} {author-with-newline}{about-with-newline} {usage-heading} @@ -52,10 +59,14 @@ pub enum Sort { ", override_usage = "git-cliff [FLAGS] [OPTIONS] [--] [RANGE]", next_help_heading = Some("OPTIONS"), - disable_help_flag = true, - disable_version_flag = true, + disable_help_flag = true, + disable_version_flag = true, + propagate_version = true, )] pub struct Opt { + #[command(subcommand)] + pub subcommand: Option, + #[arg( short, long, @@ -64,7 +75,8 @@ pub struct Opt { help = "Prints help information", help_heading = "FLAGS" )] - pub help: Option, + pub help: Option, + #[arg( short = 'V', long, @@ -73,10 +85,12 @@ pub struct Opt { help = "Prints version information", help_heading = "FLAGS" )] - pub version: Option, + pub version: Option, + /// Increases the logging verbosity. #[arg(short, long, action = ArgAction::Count, alias = "debug", help_heading = Some("FLAGS"))] - pub verbose: u8, + pub verbose: u8, + /// Writes the default configuration file to cliff.toml #[arg( short, @@ -85,17 +99,23 @@ pub struct Opt { num_args = 0..=1, required = false )] - pub init: Option>, + pub init: Option>, + /// Sets the configuration file. #[arg( short, long, env = "GIT_CLIFF_CONFIG", value_name = "PATH", - default_value = DEFAULT_CONFIG, + default_value = DEFAULT_CONFIG_FILENAME, value_parser = Opt::parse_dir )] - pub config: PathBuf, + pub config: PathBuf, + + /// Sets the version of the configuration file given in `--config`. + #[arg(long, env = "GIT_CLIFF_CONFIG_VERSION", value_name = "VERSION")] + pub config_version: Option, + /// Sets the working directory. #[arg( short, @@ -104,7 +124,8 @@ pub struct Opt { value_name = "PATH", value_parser = Opt::parse_dir )] - pub workdir: Option, + pub workdir: Option, + /// Sets the git repository. #[arg( short, @@ -114,7 +135,8 @@ pub struct Opt { num_args(1..), value_parser = Opt::parse_dir )] - pub repository: Option>, + pub repository: Option>, + /// Sets the path to include related commits. #[arg( long, @@ -122,7 +144,8 @@ pub struct Opt { value_name = "PATTERN", num_args(1..) )] - pub include_path: Option>, + pub include_path: Option>, + /// Sets the path to exclude related commits. #[arg( long, @@ -130,10 +153,12 @@ pub struct Opt { value_name = "PATTERN", num_args(1..) )] - pub exclude_path: Option>, - /// Sets the regex for matching git tags. - #[arg(long, env = "GIT_CLIFF_TAG_PATTERN", value_name = "PATTERN")] - pub tag_pattern: Option, + pub exclude_path: Option>, + + /// Sets the regex to select git tags that represent releases. + #[arg(long, env = "GIT_CLIFF_RELEASE_TAGS_PATTERN", value_name = "PATTERN")] + pub release_tags_pattern: Option, + /// Sets custom commit messages to include in the changelog. #[arg( long, @@ -141,7 +166,8 @@ pub struct Opt { value_name = "MSG", num_args(1..) )] - pub with_commit: Option>, + pub with_commit: Option>, + /// Sets commits that will be skipped in the changelog. #[arg( long, @@ -149,7 +175,8 @@ pub struct Opt { value_name = "SHA1", num_args(1..) )] - pub skip_commit: Option>, + pub skip_commit: Option>, + /// Prepends entries to the given changelog file. #[arg( short, @@ -158,7 +185,8 @@ pub struct Opt { value_name = "PATH", value_parser = Opt::parse_dir )] - pub prepend: Option, + pub prepend: Option, + /// Writes output to the given file. #[arg( short, @@ -169,7 +197,8 @@ pub struct Opt { num_args = 0..=1, default_missing_value = DEFAULT_OUTPUT )] - pub output: Option, + pub output: Option, + /// Sets the tag for the latest version. #[arg( short, @@ -178,53 +207,70 @@ pub struct Opt { value_name = "TAG", allow_hyphen_values = true )] - pub tag: Option, + pub tag: Option, + /// Bumps the version for unreleased changes. #[arg(long, help_heading = Some("FLAGS"))] - pub bump: bool, + pub bump: bool, + /// Prints bumped version for unreleased changes. #[arg(long, help_heading = Some("FLAGS"))] pub bumped_version: bool, - /// Sets the template for the changelog body. + + /// Sets the Tera template to be rendered for each release in the changelog. #[arg( short, long, - env = "GIT_CLIFF_TEMPLATE", + env = "GIT_CLIFF_BODY_TEMPLATE", value_name = "TEMPLATE", allow_hyphen_values = true )] - pub body: Option, + pub body_template: Option, + /// Processes the commits starting from the latest tag. #[arg(short, long, help_heading = Some("FLAGS"))] - pub latest: bool, + pub latest: bool, + /// Processes the commits that belong to the current tag. #[arg(long, help_heading = Some("FLAGS"))] - pub current: bool, + pub current: bool, + /// Processes the commits that do not belong to a tag. #[arg(short, long, help_heading = Some("FLAGS"))] - pub unreleased: bool, - /// Sorts the tags topologically. - #[arg(long, help_heading = Some("FLAGS"))] - pub topo_order: bool, + pub unreleased: bool, + + /// Sets sorting of releases. + #[arg( + long, + value_enum, + default_value_t = TagsOrderBy::Time + )] + pub release_order_by: TagsOrderBy, + /// Disables the external command execution. #[arg(long, help_heading = Some("FLAGS"))] - pub no_exec: bool, + pub no_exec: bool, + /// Prints changelog context as JSON. #[arg(short = 'x', long, help_heading = Some("FLAGS"))] - pub context: bool, + pub context: bool, + /// Strips the given parts from the changelog. #[arg(short, long, value_name = "PART", value_enum)] - pub strip: Option, - /// Sets sorting of the commits inside sections. + pub strip: Option, + + /// Sets ordering of the commits inside sections. #[arg( long, value_enum, - default_value_t = Sort::Oldest + default_value_t = CommitSortOrder::Oldest )] - pub sort: Sort, + pub commit_sort_order: CommitSortOrder, + /// Sets the commit range to process. #[arg(value_name = "RANGE", help_heading = Some("ARGS"))] - pub range: Option, + pub range: Option, + /// Sets the GitHub API token. #[arg( long, @@ -232,7 +278,8 @@ pub struct Opt { value_name = "TOKEN", hide_env_values = true )] - pub github_token: Option, + pub github_token: Option, + /// Sets the GitHub repository. #[arg( long, @@ -240,7 +287,7 @@ pub struct Opt { value_parser = clap::value_parser!(RemoteValue), value_name = "OWNER/REPO" )] - pub github_repo: Option, + pub github_repo: Option, } /// Custom type for the remote value. diff --git a/git-cliff/src/config.rs b/git-cliff/src/config.rs new file mode 100644 index 0000000000..c0b009e38a --- /dev/null +++ b/git-cliff/src/config.rs @@ -0,0 +1,117 @@ +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()? + { + info!("Loading configuration 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 configuration 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!( + "Configuration version {} is not supported.", + config_version + ))); + } +} diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs index 51c63a7564..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; @@ -15,30 +18,28 @@ extern crate log; use args::{ Opt, - Sort, Strip, }; -use clap::ValueEnum; use git_cliff_core::changelog::Changelog; use git_cliff_core::commit::Commit; -use git_cliff_core::config::{ - CommitParser, - Config, -}; -use git_cliff_core::embed::{ +use git_cliff_core::config::embed::{ BuiltinConfig, EmbeddedConfig, }; +use git_cliff_core::config::models_v2::{ + CommitParser, + CommitSortOrder, + Config, + TagsOrderBy, +}; +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, - IGNORE_FILE, -}; +use git_cliff_core::IGNORE_FILE; use std::env; use std::fs::{ self, @@ -84,30 +85,33 @@ fn process_repository<'a>( config: &mut Config, args: &Opt, ) -> Result>> { - let mut tags = repository.tags(&config.git.tag_pattern, args.topo_order)?; - let skip_regex = config.git.skip_tags.as_ref(); - let ignore_regex = config.git.ignore_tags.as_ref(); + let mut tags = + repository.tags(&config.release.tags_pattern, &args.release_order_by)?; + let exclude_tags_pattern = config.commit.exclude_tags_pattern.as_ref(); + let skip_release_pattern = config.release.skip_tags_pattern.as_ref(); tags = tags .into_iter() .filter(|(_, name)| { // Keep skip tags to drop commits in the later stage. - let skip = skip_regex.map(|r| r.is_match(name)).unwrap_or_default(); + let skip = exclude_tags_pattern + .map(|r| r.is_match(name)) + .unwrap_or_default(); - let ignore = ignore_regex + let skip_release = skip_release_pattern .map(|r| { if r.as_str().trim().is_empty() { return false; } - let ignore_tag = r.is_match(name); - if ignore_tag { + let skip_release_tag = r.is_match(name); + if skip_release_tag { trace!("Ignoring release: {}", name) } - ignore_tag + skip_release_tag }) .unwrap_or_default(); - skip || !ignore + skip || !skip_release }) .collect(); @@ -181,7 +185,7 @@ fn process_repository<'a>( args.include_path.clone(), args.exclude_path.clone(), )?; - if let Some(commit_limit_value) = config.git.limit_commits { + if let Some(commit_limit_value) = config.commit.max_commit_count { commits = commits .drain(..commits.len().min(commit_limit_value)) .collect(); @@ -209,7 +213,7 @@ fn process_repository<'a>( for git_commit in commits.iter().rev() { let commit = Commit::from(git_commit); let commit_id = commit.id.to_string(); - if args.sort == Sort::Newest { + if args.commit_sort_order == CommitSortOrder::Newest { releases[release_index].commits.insert(0, commit); } else { releases[release_index].commits.push(commit); @@ -299,22 +303,18 @@ 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 {:?}", init_config.map(|v| format!(" ({v})")).unwrap_or_default(), - DEFAULT_CONFIG + DEFAULT_CONFIG_FILENAME ); - fs::write(DEFAULT_CONFIG, contents)?; + fs::write(DEFAULT_CONFIG_FILENAME, contents)?; return Ok(()); } - // Retrieve the built-in configuration. - let builtin_config = - BuiltinConfig::parse(args.config.to_string_lossy().to_string()); - // Set the working directory. if let Some(ref workdir) = args.workdir { args.config = workdir.join(args.config); @@ -331,37 +331,8 @@ pub fn run(mut args: Opt) -> Result<()> { } } - // Parse the configuration file. - let mut path = args.config.clone(); - if !path.exists() { - if let Some(config_path) = dirs::config_dir() - .map(|dir| dir.join(env!("CARGO_PKG_NAME")).join(DEFAULT_CONFIG)) - { - path = config_path; - } - } - - // Load the default configuration if necessary. - let mut config = if let Ok((config, name)) = builtin_config { - info!("Using built-in configuration file: {name}"); - 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!( - "{:?} is not found, using the default configuration.", - args.config - ); - } - EmbeddedConfig::parse()? - }; - if config.changelog.body.is_none() && !args.context { - warn!("Changelog body is not specified, using the default template."); - config.changelog.body = EmbeddedConfig::parse()?.changelog.body; - } + // Load the configuration. + let mut config = config::load_config(&args)?; // Update the configuration based on command line arguments and vice versa. match args.strip { @@ -369,34 +340,36 @@ pub fn run(mut args: Opt) -> Result<()> { config.changelog.header = None; } Some(Strip::Footer) => { - config.changelog.footer = None; + config.changelog.footer_template = None; } Some(Strip::All) => { config.changelog.header = None; - config.changelog.footer = None; + config.changelog.footer_template = None; } None => {} } if args.prepend.is_some() { - config.changelog.footer = None; + config.changelog.footer_template = None; if !(args.unreleased || args.latest || args.range.is_some()) { return Err(Error::ArgumentError(String::from( "'-u' or '-l' is not specified", ))); } } - if args.body.is_some() { - config.changelog.body.clone_from(&args.body); + if args.body_template.is_some() { + config + .changelog + .body_template + .clone_from(&args.body_template); } - if args.sort == Sort::Oldest { - if let Some(ref sort_commits) = config.git.sort_commits { - args.sort = Sort::from_str(sort_commits, true) - .expect("Incorrect config value for 'sort_commits'"); + if args.commit_sort_order == CommitSortOrder::Oldest { + if let Some(commit_sort_order) = config.commit.sort_order { + args.commit_sort_order = commit_sort_order; } } - if !args.topo_order { - if let Some(topo_order) = config.git.topo_order { - args.topo_order = topo_order; + if args.release_order_by == TagsOrderBy::Time { + if let Some(release_order_by) = config.release.order_by { + args.release_order_by = release_order_by; } } if args.github_token.is_some() { @@ -407,7 +380,7 @@ pub fn run(mut args: Opt) -> Result<()> { config.remote.github.repo = remote.0.repo.to_string(); } if args.no_exec { - if let Some(ref mut preprocessors) = config.git.commit_preprocessors { + if let Some(ref mut preprocessors) = config.commit.message_preprocessors { preprocessors .iter_mut() .for_each(|v| v.replace_command = None); @@ -418,9 +391,15 @@ pub fn run(mut args: Opt) -> Result<()> { .for_each(|v| v.replace_command = None); } } - config.git.skip_tags = config.git.skip_tags.filter(|r| !r.as_str().is_empty()); - if args.tag_pattern.is_some() { - config.git.tag_pattern.clone_from(&args.tag_pattern); + config.commit.exclude_tags_pattern = config + .commit + .exclude_tags_pattern + .filter(|r| !r.as_str().is_empty()); + if args.release_tags_pattern.is_some() { + config + .release + .tags_pattern + .clone_from(&args.release_tags_pattern); } // Process the repositories. @@ -442,7 +421,7 @@ pub fn run(mut args: Opt) -> Result<()> { if let Some(ref skip_commit) = args.skip_commit { skip_list.extend(skip_commit.clone()); } - if let Some(commit_parsers) = config.git.commit_parsers.as_mut() { + if let Some(commit_parsers) = config.commit.commit_parsers.as_mut() { for sha1 in skip_list { commit_parsers.insert(0, CommitParser { sha: Some(sha1.to_string()), diff --git a/git-cliff/src/main.rs b/git-cliff/src/main.rs index 2dd58ba3ab..b25ee4eaa3 100644 --- a/git-cliff/src/main.rs +++ b/git-cliff/src/main.rs @@ -1,12 +1,16 @@ use clap::Parser; use git_cliff::args::Opt; +use git_cliff::args::SubCommands; use git_cliff::logger; use git_cliff_core::error::Result; use std::env; use std::process; fn main() -> Result<()> { - let args = Opt::parse(); + // parse command line arguments + let args: Opt = Opt::parse(); + + // set log level if args.verbose == 1 { env::set_var("RUST_LOG", "debug"); } else if args.verbose > 1 { @@ -15,7 +19,18 @@ fn main() -> Result<()> { env::set_var("RUST_LOG", "info"); } logger::init()?; - match git_cliff::run(args) { + + // run the command or subcommand + let result = match &args.subcommand { + Some(sub_command) => match sub_command { + SubCommands::MigrateConfig(migrate_args) => { + git_cliff_core::config::migrate::run(migrate_args) + } + }, + None => git_cliff::run(args), + }; + + match result { Ok(_) => process::exit(0), Err(e) => { log::error!("{}", e); diff --git a/release.sh b/release.sh index ff741cba1c..317cd211a6 100755 --- a/release.sh +++ b/release.sh @@ -21,7 +21,7 @@ cargo run -- --config cliff.toml --tag "$1" >CHANGELOG.md git add -A && git commit -m "chore(release): prepare for $1" git show # generate a changelog for the tag message -export GIT_CLIFF_TEMPLATE="\ +export GIT_CLIFF_BODY_TEMPLATE="\ {% for group, commits in commits | group_by(attribute=\"group\") %} {{ group | upper_first }}\ {% for commit in commits %} diff --git a/website/docs/configuration/changelog.md b/website/docs/configuration/changelog.md index 22fae40840..2942eea1e5 100644 --- a/website/docs/configuration/changelog.md +++ b/website/docs/configuration/changelog.md @@ -7,7 +7,7 @@ This section contains the configuration options for changelog generation. ```toml [changelog] header = "Changelog" -body = """ +body_template = """ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} {% for commit in commits %} @@ -15,41 +15,46 @@ body = """ {% endfor %} {% endfor %} """ -trim = true -footer = "" +trim_body_whitespace = true +footer_template = "" postprocessors = [{ pattern = "foo", replace = "bar"}] +exclude_ungrouped_changes = false ``` ### header -Header text that will be added to the beginning of the changelog. +A static header that will be added to the beginning of the changelog. -### body +### body_template -Body template that represents a single release in the changelog. +A Tera template to be rendered for each release in the changelog. See [templating](/docs/category/templating) for more detail. -### footer +### footer_template -Footer template that will be rendered and added to the end of the changelog. +A Tera template that will be rendered and added to the end of the changelog. -The template context is the same as [`body`](#body) and contains all the releases instead of a single release. +The template context is the same as [`body_template`](#body_template) and contains all the releases instead of a single release. -For example, to get the list of releases, use the `{{ releases }}` variable in the template. To get information about a single release, iterate over this array and access the fields similar to [`body`](#body). +For example, to get the list of releases, use the `{{ releases }}` variable in the template. To get information about a single release, iterate over this array and access the fields similar to [`body_template`](#body_template). See [Keep a Changelog configuration](/docs/templating/examples#keep-a-changelog) for seeing the example of adding links to the end of the changelog. -### trim +### trim_body_whitespace -If set to `true`, leading and trailing whitespace are removed from the [`body`](#body). +If set to `true`, leading and trailing whitespace are removed from every line in the rendered [`body_template`](#body_template). It is useful for adding indentation to the template for readability, as shown [in the example](#changelog). ### postprocessors -An array of commit postprocessors for manipulating the changelog before outputting. +A list of postprocessors using regex to modify the changelog. Can e.g. be used for replacing commit author with GitHub usernames. -Internally postprocessors and preprocessors are the same. See [commit_preprocessors](/docs/configuration/git#commit_preprocessors) for more detail and examples, it uses the same syntax. +Internally postprocessors and preprocessors are the same. See [commit.message_preprocessors](/docs/configuration/commit#message_preprocessors) for more detail and examples, it uses the same syntax. + +### exclude_ungrouped_changes + +If set to `true`, commits that do not have a group assigned to them by either [`commit.parse_conventional_commits`](/docs/configuration/commit#parse_conventional_commits) or [`commit.commit_parsers`](/docs/configuration/commit#commit_parsers) are excluded from the changelog. diff --git a/website/docs/configuration/commit.md b/website/docs/configuration/commit.md new file mode 100644 index 0000000000..b666a7cfbd --- /dev/null +++ b/website/docs/configuration/commit.md @@ -0,0 +1,205 @@ +# `commit` + +This section contains options regarding processing of individual git commits. + +```toml +[commit] +parse_conventional_commits = true +exclude_unconventional_commits = true +split_by_newline = false +commit_parsers = [ + { message = "^feat", group = "Features"}, + { message = "^fix", group = "Bug Fixes"}, + { message = "^doc", group = "Documentation"}, + { message = "^perf", group = "Performance"}, + { message = "^refactor", group = "Refactor"}, + { message = "^style", group = "Styling"}, + { message = "^test", group = "Testing"}, +] +retain_breaking_changes = false + +exclude_tags_pattern = "v0.1.0-beta.1" +sort_order = "oldest" +link_parsers = [ + { pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"}, + { pattern = "RFC(\\d+)", text = "ietf-rfc$1", href = "https://datatracker.ietf.org/doc/html/rfc$1"}, +] +max_commit_count = 42 +``` + +### parse_conventional_commits + +If set to `true`, commits are parsed according to the [Conventional Commits specifications](https://www.conventionalcommits.org). + +> The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages. + +> The commit message should be structured as follows: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +e.g. `feat(parser): add ability to parse arrays` + +### exclude_unconventional_commits + +Whether to exclude commits that do not match the conventional commits specification from the changelog. +This option can be used to generate changelogs with conventional and unconventional commits mixed together. For example: + +```toml +parse_conventional_commits = true +exclude_unconventional_commits = false +commit_parsers = [ + { message = ".*", group = "Other", default_scope = "other"}, +] +``` + +With the configuration above, conventional commits are parsed as usual and unconventional commits will be also included in the changelog as "Other". + +To completely exclude unconventional commits from the changelog: + +```toml +# default behaviour +parse_conventional_commits = true +exclude_unconventional_commits = true +``` + +To include any type of commit in the changelog without parsing: + +```toml +parse_conventional_commits = false +exclude_unconventional_commits = false +``` + +### split_by_newline + +> This flag violates "conventional commits". It should remain off by default if conventional commits is to be respected. + +If set to `true`, each line of a commit is processed individually, as if it were its own commit message. This may cause +a commit to appear multiple times in a changelog, once for each line. + +```toml +parse_conventional_commits = true +exclude_unconventional_commits = true +split_by_newline = true +commit_parsers = [ + { message = "^feat", group = "Features"}, +] +``` + +With the configuration above, lines are parsed as conventional commits and unconventional lines are omitted. + +If `exclude_unconventional_commits = false`, every line will be processed as an unconventional commit, resulting in each line of +a commit being treated as a changelog entry. + +### message_preprocessors + +An array of commit preprocessors for manipulating the commit messages before parsing/grouping them. These regex-based preprocessors can be used for removing or selecting certain parts of the commit message/body to be used in the following processes. + +Examples: + +- `{ pattern = "foo", replace = "bar"}` + - Replace text. +- `{ pattern = 'Merged PR #[0-9]: (.*)', replace = "$1"}` + - Remove prefix. +- `{ pattern = " +", replace = " "}` + - Replace multiple spaces with a single space. +- `{ pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://github.com/orhun/git-cliff/issues/${1}))"}` + - Replace the issue number with the link. +- `{ pattern = "https://github.com/[^ ]/issues/([0-9]+)", replace = "[Issue #${1}]"}` + - Replace the issue link with the number. +- `{ pattern = "Merge pull request #([0-9]+) from [^ ]+", replace = "PR # [${1}](https://github.com/orhun/git-cliff/pull/${1}):"}` + - Hyperlink PR references from merge commits. +- `{ pattern = "https://github.com/orhun/git-cliff/commit/([a-f0-9]{7})[a-f0-9]*", replace = "commit # [${1}](${0})"}` + - Hyperlink commit links, with short commit hash as description. +- `{ 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 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 + +A list of parsers using regex for extracting data from the commit message. +Sets the commits' `group` and `scope` and can decide to exclude commits from further processing. + +Examples: + +- `{ message = "^feat", group = "Features" }` + - Group the commit as "Features" if the commit message (description) starts with "feat". +- `{ body = ".*security", group = "Security" }` + - Group the commit as "Security" if the commit body contains "security". +- `{ message = '^fix\((.*)\)', group = 'Fix (${1})' }` + - Use the matched scope value from the commit message in the group name. +- `{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation" }` + - Group the commit as "Deprecation" if the commit body and message contains "deprecated". +- `{ message = "^revert", skip = true }` + - Skip processing the commit if the commit message (description) starts with "revert". +- `{ message = "^doc", group = "Documentation", default_scope = "other" },` + - If the commit starts with "doc", group the commit as "Documentation" and set the default scope to "other". (e.g. `docs: xyz` will be processed as `docs(other): xyz`) +- `{ message = "(www)", scope = "Application" }` + - If the commit contains "(www)", override the scope with "Application". Scoping order is: scope specification, conventional commit's scope and default scope. +- `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", skip = true }` + - Skip a specific commit by using its SHA1. +- `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", group = "Stuff" }` + - Set the group of the commit by using its SHA1. +- `{ field = "author.name", pattern = "John Doe", group = "John's stuff" }` + - If the author's name attribute of the commit matches the pattern "John Doe" (as a regex), override the scope with "John' stuff". Supported commit attributes are: + - `id` + - `message` + - `body` + - `author.name` + - `author.email` + - `committer.email` + - `committer.name` + +### retain_breaking_changes + +If set to `true`, any breaking changes will be protected against being excluded by commit parsers. + +### exclude_tags_pattern + +Regex to select git tags that should be excluded from the changelog. + +### sort_order + +Whether to order commits newest to oldest or oldest to newest in their group. + +Possible values: + +- `oldest` +- `newest` + +This can also be achieved by specifying the `--commit-sort-order` command line argument. + +### link_parsers + +A list of parsers using regex for extracting external references found in commit messages, and turning them into links. The gemerated links can be used in the body template as `commit.links`. + +Examples: + +- `{ pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"}` + - Extract all GitHub issues and PRs and generate URLs linking to them. The link text will be the matching pattern. +- `{ pattern = "RFC(\\d+)", text = "ietf-rfc$1", href = "https://datatracker.ietf.org/doc/html/rfc$1"}`, + - Extract mentions of IETF RFCs and generate URLs linking to them. It also rewrites the text as "ietf-rfc...". + +These extracted links can be used in the [template](/docs/templating/context) with `commits.links` variable. + +### max_commit_count + +Whether to limit the total number of commits to be included in the changelog. This is an optional setting. diff --git a/website/docs/configuration/git.md b/website/docs/configuration/git.md index b7d7145919..99918c163d 100644 --- a/website/docs/configuration/git.md +++ b/website/docs/configuration/git.md @@ -1,232 +1,3 @@ # `git` -This section contains the parsing and git related configuration options. - -```toml -[git] -conventional_commits = true -filter_unconventional = true -split_commits = false -commit_parsers = [ - { message = "^feat", group = "Features"}, - { message = "^fix", group = "Bug Fixes"}, - { message = "^doc", group = "Documentation"}, - { message = "^perf", group = "Performance"}, - { message = "^refactor", group = "Refactor"}, - { message = "^style", group = "Styling"}, - { message = "^test", group = "Testing"}, -] -protect_breaking_commits = false -filter_commits = false -tag_pattern = "v[0-9].*" - -skip_tags = "v0.1.0-beta.1" -ignore_tags = "" -topo_order = false -sort_commits = "oldest" -link_parsers = [ - { pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"}, - { pattern = "RFC(\\d+)", text = "ietf-rfc$1", href = "https://datatracker.ietf.org/doc/html/rfc$1"}, -] -limit_commits = 42 -``` - -### conventional_commits - -If set to `true`, commits are parsed according to the [Conventional Commits specifications](https://www.conventionalcommits.org). - -> The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages. - -> The commit message should be structured as follows: - -``` -[optional scope]: - -[optional body] - -[optional footer(s)] -``` - -e.g. `feat(parser): add ability to parse arrays` - -### filter_unconventional - -If set to `true`, commits that are not conventional are excluded. This option can be used to generate changelogs with conventional and unconventional commits mixed together. For example: - -```toml -conventional_commits = true -filter_unconventional = false -commit_parsers = [ - { message = ".*", group = "Other", default_scope = "other"}, -] -``` - -With the configuration above, conventional commits are parsed as usual and unconventional commits will be also included in the changelog as "Other". - -To completely exclude unconventional commits from the changelog: - -```toml -# default behaviour -conventional_commits = true -filter_unconventional = true -``` - -To include any type of commit in the changelog without parsing: - -```toml -conventional_commits = false -filter_unconventional = false -``` - -### split_commits - -> This flag violates "conventional commits". It should remain off by default if conventional commits is to be respected. - -If set to `true`, each line of a commit is processed individually, as if it were its own commit message. This may cause -a commit to appear multiple times in a changelog, once for each match. - -```toml -conventional_commits = true -filter_unconventional = true -split_commits = true -commit_parsers = [ - { message = "^feat", group = "Features"}, -] -``` - -With the configuration above, lines are parsed as conventional commits and unconventional lines are omitted. - -If `filter_unconventional = false`, every line will be processed as an unconventional commit, resulting in each line of -a commit being treated as a changelog entry. - -### commit_preprocessors - -An array of commit preprocessors for manipulating the commit messages before parsing/grouping them. These regex-based preprocessors can be used for removing or selecting certain parts of the commit message/body to be used in the following processes. - -Examples: - -- `{ pattern = "foo", replace = "bar"}` - - Replace text. -- `{ pattern = 'Merged PR #[0-9]: (.*)', replace = "$1"}` - - Remove prefix. -- `{ pattern = " +", replace = " "}` - - Replace multiple spaces with a single space. -- `{ pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://github.com/orhun/git-cliff/issues/${1}))"}` - - Replace the issue number with the link. -- `{ pattern = "https://github.com/[^ ]/issues/([0-9]+)", replace = "[Issue #${1}]"}` - - Replace the issue link with the number. -- `{ pattern = "Merge pull request #([0-9]+) from [^ ]+", replace = "PR # [${1}](https://github.com/orhun/git-cliff/pull/${1}):"}` - - Hyperlink PR references from merge commits. -- `{ pattern = "https://github.com/orhun/git-cliff/commit/([a-f0-9]{7})[a-f0-9]*", replace = "commit # [${1}](${0})"}` - - Hyperlink commit links, with short commit hash as description. -- `{ 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 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 - -An array of commit parsers for determining the commit groups by using regex. - -Examples: - -- `{ message = "^feat", group = "Features" }` - - Group the commit as "Features" if the commit message (description) starts with "feat". -- `{ body = ".*security", group = "Security" }` - - Group the commit as "Security" if the commit body contains "security". -- `{ message = '^fix\((.*)\)', group = 'Fix (${1})' }` - - Use the matched scope value from the commit message in the group name. -- `{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation" }` - - Group the commit as "Deprecation" if the commit body and message contains "deprecated". -- `{ message = "^revert", skip = true }` - - Skip processing the commit if the commit message (description) starts with "revert". -- `{ message = "^doc", group = "Documentation", default_scope = "other" },` - - If the commit starts with "doc", group the commit as "Documentation" and set the default scope to "other". (e.g. `docs: xyz` will be processed as `docs(other): xyz`) -- `{ message = "(www)", scope = "Application" }` - - If the commit contains "(www)", override the scope with "Application". Scoping order is: scope specification, conventional commit's scope and default scope. -- `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", skip = true }` - - Skip a specific commit by using its SHA1. -- `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", group = "Stuff" }` - - Set the group of the commit by using its SHA1. -- `{ field = "author.name", pattern = "John Doe", group = "John's stuff" }` - - If the author's name attribute of the commit matches the pattern "John Doe" (as a regex), override the scope with "John' stuff". Supported commit attributes are: - - `id` - - `message` - - `body` - - `author.name` - - `author.email` - - `committer.email` - - `committer.name` - -### protect_breaking_commits - -If set to `true`, any breaking changes will be protected against being skipped -due to any commit parser. - -### filter_commits - -If set to `true`, commits that are not matched by [`commit_parsers`](#commit_parsers) are filtered out. - -### tag_pattern - -A regular expression for matching the git tags. - -This value can be also overridden with using the `--tag-pattern` argument. - -### skip_tags - -A regex for skip processing the matched tags. - -### ignore_tags - -A regex for ignore processing the matched tags. - -While `skip_tags` drop commits from the changelog, `ignore_tags` include ignored commits into the next tag. - -### topo_order - -If set to `true`, tags are processed in topological order instead of chronological. - -This can also be achieved by using the `--topo-order` command line flag. - -### sort_commits - -Sort the commits inside sections by specified order. - -Possible values: - -- `oldest` -- `newest` - -This can also be achieved by specifying the `--sort` command line argument. - -### link_parsers - -An array of link parsers for extracting external references, and turning them into URLs, using regex. - -Examples: - -- `{ pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"}` - - Extract all GitHub issues and PRs and generate URLs linking to them. The link text will be the matching pattern. -- `{ pattern = "RFC(\\d+)", text = "ietf-rfc$1", href = "https://datatracker.ietf.org/doc/html/rfc$1"}`, - - Extract mentions of IETF RFCs and generate URLs linking to them. It also rewrites the text as "ietf-rfc...". - -These extracted links can be used in the [template](/docs/templating/context) with `commits.links` variable. - -### limit_commits - -`limit_commits` is an **optional** positive integer number that limits the number of included commits in the generated changelog. - -`limit_commits` is not part of the default configuration. +The section `git` has been replaced by [commit](/docs/configuration/commit). diff --git a/website/docs/configuration/index.md b/website/docs/configuration/index.md index 27f65e1d6e..d4e0924e3a 100644 --- a/website/docs/configuration/index.md +++ b/website/docs/configuration/index.md @@ -31,8 +31,8 @@ To override the `footer` element: export GIT_CLIFF__CHANGELOG__FOOTER="" ``` -To override the `ignore_tags` element: +To override the `skip_tags_pattern` element: ```bash -export GIT_CLIFF__GIT__IGNORE_TAGS="v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" +export GIT_CLIFF__RELEASE__SKIP_TAGS_PATTERN="v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" ``` diff --git a/website/docs/configuration/migration.md b/website/docs/configuration/migration.md new file mode 100644 index 0000000000..0e3578e0e7 --- /dev/null +++ b/website/docs/configuration/migration.md @@ -0,0 +1,51 @@ +# `bump` + +## Migrating +A new subcommand `migrate-config` was added to git-cliff to help updating your configuration. It takes the path of your old config and a destination to write the updated version to. + +Example: + +- `git-cliff migrate-config --in cliff.toml --out cliff-new.toml` + +## Backwards compatibility +While configuration version 1 is deprecated, the cli argument `--config-version` can be used to make git-cliff use old configuration files. Keep in mind that new features might not be supported when using the old configuration format. + +Example: + +- `git-cliff --config-version 1 --config cliff.toml` + +## Changes + +### Section `[changelog]` +- Renamed option `body` to `body_template`. +- Renamed option `footer` to `footer_template` to indicate that it is a Tera template instead of a static string. +- Renamed option `trim` to `trim_body_whitespace` to indicate that it is a Tera template instead of static string. +- Moved and renamed option `git.filter_commits` to `changelog.exclude_ungrouped_changes` to be more descriptive. + +### Section `[release]` +- The section `[release]` was introduced to group options that deal with releases. Its options were extracted from the former `[git]` section. This clearly separates options like `release.tags_pattern` from `commit.exclude_tags_pattern` and thus improves comprehensibility. +- Moved and renamed `git.tag_pattern` to `release.tags_pattern` to emphasize them affecting releases instead of individual commits. +- Moved and renamed `git.ignore_tags` to `release.skip_tags_pattern` to differentiate from `git.skip_tags` and emphasize its relation to `release.tags_pattern`. +- Moved and renamed `git.topo_order` to `release.order_by`. +- Changed type of `release.order_by` to an enum with values `time` and `topology` for clarity. + +### Section `[commit]` (formerly `[git]`) +- The section `[git]` has been renamed to `[commit]`, because its options only affect handling of commits and no other git operations. This frees up `git` for possible future additions of options that affect other git structures than commits. +- Renamed option `git.conventional_commits` to `commit.parse_conventional_commits` for clarity. +- Renamed option `git.filter_unconventional` to `commit.exclude_unconventional_commits` to emphasize its effect of actually excluding unconventional commits from processing. +- Renamed option `git.split_commits` to `commit.split_by_newline` to indicate how it splits commits. +- Renamed option `git.commit_preprocessors` to `commit.message_preprocessors` to emphasize that it acts on commit messages. +- Renamed option `git.skip_tags` to `commit.exclude_tags_pattern`. +- Renamed option `git.protect_breaking_changes` to `commit.retain_breaking_changes` to emphasize it retaining commits that would otherwise be skipped. +- Renamed option `git.sort_commits` to `commit.sort_order` to clarify that it sets the ordering instead of enabling/disabling order all together. +- Renamed option `git.limit_commits` to `commit.max_commit_count` to emphasize that it limits the number of commits and not a property of the commits themselves. + +### CLI Arguments +CLI arguments were updated to align with the new names of their respective configuration options. +- Renamed cli argument `--tag-pattern` to `--release-tags-pattern`. +- Renamed cli argument `--body` to `--body-template`. +- Renamed cli argument `--sort` to `--commit-sort.order`. + +### Miscellaneous Changes +- Updated descriptions. +- Updated documentation. diff --git a/website/docs/configuration/release.md b/website/docs/configuration/release.md new file mode 100644 index 0000000000..21931d094d --- /dev/null +++ b/website/docs/configuration/release.md @@ -0,0 +1,36 @@ +# `release` + +This section contains options regarding releases. + +```toml +[release] +tags_pattern = "v[0-9].*" +skip_tags_pattern = "rc" +order_by = "time" +``` + +### tags_pattern + +Regex to select git tags that represent releases. + +Examples: + +- `tags_pattern = "v[0-9].*"` + +This value can be also overridden with using the `--release-tags-pattern` argument. + +### skip_tags_pattern + +Regex to select git tags that do not represent proper releases. Takes precedence over [`release.tags_pattern`](#tags_pattern). +Changes belonging to these releases will be included in the next non-skipped release. + +Examples: + +- `skip_tags_pattern = "rc"` + +### order_by + +Whether to order releases chronologically or topologically. +Must be either `time` or `topology`. + +This value can be also overridden with using the `--release-order-by` argument. diff --git a/website/docs/index.md b/website/docs/index.md index 929a5d7fe7..3030eac31b 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -4,7 +4,7 @@ sidebar_position: 1 # Getting Started -**git-cliff** can generate [changelog](https://en.wikipedia.org/wiki/Changelog) files from the [Git](https://git-scm.com/) history by utilizing [conventional commits](/docs/configuration/git#conventional_commits) as well as regex-powered [custom parsers](/docs/configuration/git#commit_parsers). +**git-cliff** can generate [changelog](https://en.wikipedia.org/wiki/Changelog) files from the [Git](https://git-scm.com/) history by utilizing [conventional commits](/docs/configuration/commit#parse_conventional_commits) as well as regex-powered [custom parsers](/docs/configuration/commit#commit_parsers). The [changelog template](category/templating) can be customized with a [configuration file](configuration) to match the desired format. diff --git a/website/docs/integration/python.md b/website/docs/integration/python.md index eee036abbd..54e021bccf 100644 --- a/website/docs/integration/python.md +++ b/website/docs/integration/python.md @@ -13,13 +13,13 @@ dependencies = [] [tool.git-cliff.changelog] header = "All notable changes to this project will be documented in this file." -body = "..." -footer = "" +body_template = "..." +footer_template = "" +exclude_ungrouped_changes = false # see [changelog] section for more keys -[tool.git-cliff.git] -conventional_commits = true +[tool.git-cliff.commit] +parse_conventional_commits = true commit_parsers = [] -filter_commits = false -# see [git] section for more keys +# see [commit] section for more keys ``` diff --git a/website/docs/integration/rust.md b/website/docs/integration/rust.md index b1f02cee3b..88f3c46073 100644 --- a/website/docs/integration/rust.md +++ b/website/docs/integration/rust.md @@ -14,15 +14,15 @@ name = "..." [package.metadata.git-cliff.changelog] header = "All notable changes to this project will be documented in this file." -body = "..." -footer = "" +body_template = "..." +footer_template = "" +exclude_ungrouped_changes = false # see [changelog] section for more keys -[package.metadata.git-cliff.git] +[package.metadata.git-cliff.commit] conventional_commits = true commit_parsers = [] -filter_commits = false -# see [git] section for more keys +# see [commit] section for more keys ``` For Cargo workspaces, [`workspace.metadata`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-workspacemetadata-table) table can be used. (e.g. `[workspace.metadata.git-cliff.
]`) diff --git a/website/docs/templating/context.md b/website/docs/templating/context.md index 2afebdf4f5..b136e33f00 100644 --- a/website/docs/templating/context.md +++ b/website/docs/templating/context.md @@ -8,9 +8,9 @@ Context is the model that holds the required data for a template rendering. The ## Conventional Commits -> conventional_commits = **true** +> parse_conventional_commits = **true** -For a [conventional commit](/docs/configuration/git#conventional_commits) like this, +For a [conventional commit](/docs/configuration/commit#parse_conventional_commits) like this, ``` [scope]: @@ -45,7 +45,7 @@ following context is generated to use for templating: "conventional": true, "merge_commit": false, "links": [ - { "text": "(set by link_parsers)", "href": "(set by link_parsers)" } + { "text": "(set by commit.link_parsers)", "href": "(set by commit.link_parsers)" } ], "author": { "name": "User Name", @@ -112,7 +112,7 @@ BREAKING CHANGE: this is a breaking change If the `BREAKING CHANGE:` footer is present, the footer will also be included in `commit.footers`. -Breaking changes will be skipped if [`protect_breaking_commits`](/docs/configuration/git#protect_breaking_commits) is set to `true`, even when matched by a skipping [commit_parser](/docs/configuration/git#commit_parsers). +Breaking changes will not be skipped if [`commit.retain_breaking_changes`](/docs/configuration/commit#retain_breaking_changes) is set to `true`, even when matched by a skipping [commit.commit_parser](/docs/configuration/commit#commit_parsers). ### Committer vs Author @@ -122,9 +122,9 @@ From [Git docs](https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-His ## Non-Conventional Commits -> conventional_commits = **false** +> parse_conventional_commits = **false** -If [`conventional_commits`](/docs/configuration/git#conventional_commits) is set to `false`, then some of the fields are omitted from the context or squashed into the `message` field: +If [`commit.parse_conventional_commits`](/docs/configuration/commit#parse_conventional_commits) is set to `false`, then some of the fields are omitted from the context or squashed into the `message` field: ```json { @@ -138,7 +138,7 @@ If [`conventional_commits`](/docs/configuration/git#conventional_commits) is set "conventional": false, "merge_commit": false, "links": [ - { "text": "(set by link_parsers)", "href": "(set by link_parsers)" } + { "text": "(set by commit.link_parsers)", "href": "(set by commit.link_parsers)" } ], "author": { "name": "User Name", diff --git a/website/docs/tips-and-tricks.md b/website/docs/tips-and-tricks.md index 5c135927e5..8677a0f3c8 100644 --- a/website/docs/tips-and-tricks.md +++ b/website/docs/tips-and-tricks.md @@ -9,7 +9,7 @@ sidebar_position: 6 Since the groups come out in alphabetical order, use HTML comments to force them into their desired positions: ```toml -[git] +[commit] commit_parsers = [ { message = "^feat*", group = ":rocket: New features" }, { message = "^fix*", group = ":bug: Bug fixes" }, @@ -54,8 +54,8 @@ Then strip the tags in the template with the series of filters: ## Remove gitmoji ```toml -[git] -commit_preprocessors = [ +[commit] +message_preprocessors = [ # Remove gitmoji, both actual UTF emoji and :emoji: { pattern = ' *(:\w+:|[\p{Emoji_Presentation}\p{Extended_Pictographic}](?:\u{FE0F})?\u{200D}?) *', replace = "" }, ] diff --git a/website/docs/usage/args.md b/website/docs/usage/args.md index 3a79ff630c..b3235dfab9 100644 --- a/website/docs/usage/args.md +++ b/website/docs/usage/args.md @@ -27,23 +27,23 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE] ## Options ``` --i, --init [] Writes the default configuration file to cliff.toml --c, --config Sets the configuration file [env: GIT_CLIFF_CONFIG=] [default: cliff.toml] --w, --workdir Sets the working directory [env: GIT_CLIFF_WORKDIR=] --r, --repository ... Sets the git repository [env: GIT_CLIFF_REPOSITORY=] - --include-path ... Sets the path to include related commits [env: GIT_CLIFF_INCLUDE_PATH=] - --exclude-path ... Sets the path to exclude related commits [env: GIT_CLIFF_EXCLUDE_PATH=] - --tag-pattern Sets the regex for matching git tags [env: GIT_CLIFF_TAG_PATTERN=] - --with-commit ... Sets custom commit messages to include in the changelog [env: GIT_CLIFF_WITH_COMMIT=] - --skip-commit ... Sets commits that will be skipped in the changelog [env: GIT_CLIFF_SKIP_COMMIT=] --p, --prepend Prepends entries to the given changelog file [env: GIT_CLIFF_PREPEND=] --o, --output [] Writes output to the given file [env: GIT_CLIFF_OUTPUT=] --t, --tag Sets the tag for the latest version [env: GIT_CLIFF_TAG=] --b, --body