使用git-cliff生成项目的CHANGELOG.md
更新日志是什么?
更新日志(Change Log)是一个由人工编辑、以时间为倒序的列表,用于记录项目中每个版本的显著变动。
为何要提供更新日志?
为了让用户和开发人员更简单清晰地知晓项目的不同版本之间有哪些显著变动。
哪些人需要更新日志?
人人需要更新日志。无论是用户还是开发者。当软件有变动时,大家希望知道改动是为何、以及如何进行的。
前置条件
在如何开始之前,要求参与者遵循如下的规范:
- git 提交信息格式必须遵循约定式提交 规范 。
- 项目遵循 语义化版本 规范。
- CHANGELOG 遵循Keep a Changelog格式。
如何实现
我选择使用 git-cliff 来实现。理由是与项目语言及管理工具无关,无侵入性,且 git-cliff 跨平台通用(windows/wsl2/linux/macos),是使用Rust开发的,执行效率非常高,速度飞快。
下面简述相关步骤,安装过程略。
- 执行
git-cliff --version验证工具是否已正确安装。 - 在项目根目录下执行
git-cliff --init进行初始化,会生成cliff.toml配置文件。 - 由于默认
git-cliff会采用严格模式,只要有不符合规范格式的commit-message会生成失败,因此对于旧项目来说,需要配置为宽松模式(新项目强烈建议使用严格的提交消息格式规范)。
最终的 cliff.toml 文件内容如下:
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
header = """# 变更日志
本文件记录了该项目的所有重要变更。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
项目遵循 [语义化版本](https://semver.org/zh-CN/spec/v2.0.0.html) 规范。
要求项目开发者与维护者使用Git进行版本控制过程中,其提交信息格式必须遵循[约定式提交](https://www.conventionalcommits.org/zh-hans/v1.0.0/) 规范 。
所有未发布的变更都记录在 [未发布] 中。
"""
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [未发布]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = true
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 新增功能" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug 修复" },
{ message = "^doc", group = "<!-- 3 -->📚 文档更新" },
{ message = "^perf", group = "<!-- 4 -->⚡ 性能优化" },
{ message = "^refactor", group = "<!-- 2 -->🚜 代码重构" },
{ message = "^style", group = "<!-- 5 -->🎨 格式调整" },
{ message = "^test", group = "<!-- 6 -->🧪 测试相关" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ 杂项修改" },
{ body = ".*security", group = "<!-- 8 -->🛡️ 安全更新" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 其他变更" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# Fail on a commit that is not matched by any commit parser.
fail_on_unmatched_commit = false
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order commits topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "oldest"
# Process submodules commits
recurse_submodules = false
# 新增 [settings] 节点:关闭严格解析
[settings]
# 默认 strict = true 严格模式,对于遗留项目可以设置为false
strict = false
# 未发布内容是否也写入
unreleased = true
parse_commits = true
# 变更日志采用倒序的排序方式
sort_commits = "desc"
unreleased_label = "未发布"
# 新增 [groups] 节点:定义分组,包含兜底的 other
[groups]
feat = "Added"
fix = "Fixed"
other = "Other Changes"
# 新增 [[commits]] 节点:兜底匹配规则,承接所有提交
[[commits]]
pattern = ".*"
group = "other"
配置完成后,在项目根目录执行:
git-cliff -o CHANGELOG.md
就会生成 CHANGELOG.md。
进阶
对于使用gitlab等持续集成工具的可以将这个命令加入到Action中去,这样可以在发布版本、创建分支等自动生成。