Conventional Commits¶
Keywords: Conventional, Commit, Commits
Conventional Commits 是一种 Git Commit Message 的一种约定.
fix
: 类型 为 fix 的提交表示在代码库中修复了一个 bug (这和语义化版本中的PATCH
相对应).feat
: 类型 为 feat 的提交表示在代码库中新增了一个功能 (这和语义化版本中的MINOR
相对应).BREAKING CHANGE
: 在脚注中包含 BREAKING CHANGE: 或 <类型>(范围) 后面有一个 ! 的提交, 表示引入了破坏性 API 变更 (这和语义化版本中的 MAJOR 相对应) . 破坏性变更可以是任意 类型 提交的一部分.除
fix:
和feat:
之外, 也可以使用其它提交 类型 , 例如@commitlint/config-conventional
(基于 Angular 约定) 中推荐的build:
,chore:
,ci:
,docs:
,style:
,refactor:
,perf:
,test:
等等.脚注中除了 BREAKING CHANGE: <description> , 其它条目应该采用类似 git trailer format 这样的惯例.
Parse Conventional Commits¶
这里我写了一个基于 Python 3.7+ 的脚本, 可以用于解析 conventional commits.
# -*- coding: utf-8 -*-
"""
A simple regex parser to parse conventional commit message.
"""
import dataclasses
import re
import string
from typing import Optional, List, Pattern
DELIMITERS = "!@#$%^&*()_+-=~`[{]}\\|;:'\",<.>/? \t\n"
CHARSET = string.ascii_letters
def tokenize(text: str) -> List[str]:
cleaner_text = text
for delimiter in DELIMITERS:
cleaner_text = cleaner_text.replace(delimiter, " ")
words = [word.strip() for word in cleaner_text.split(" ") if word.strip()]
return words
def _get_subject_regex(_types: List[str]) -> Pattern:
return re.compile(
fr"^(?P<types>[\w ,]+)(?:\((?P<scope>[\w-]+)\))?(?P<breaking>!)?:[ \t]?(?P<description>.+)$"
)
@dataclasses.dataclass
class Commit:
"""
Data container class for conventional commits message.
"""
types: List[str]
description: str = None
scope: Optional[str] = None
breaking: Optional[str] = None
class ConventionalCommitParser:
def __init__(self, types: List[str]):
self.types = types
self.subject_regex = _get_subject_regex(types)
def extract_subject(self, msg: str) -> str:
return msg.split("\n")[0].strip()
def extract_commit(self, subject: str) -> Commit:
match = self.subject_regex.match(subject)
types = [
word.strip()
for word in match["types"].split(",")
if word.strip() in self.types
]
# Debug only
# print(match)
# print([match["types"],])
# print([match["description"], ])
# print([match["scope"], ])
# print([match["breaking"], ])
return Commit(
types=types,
description=match["description"],
scope=match["scope"],
breaking=match["breaking"],
)
parser = ConventionalCommitParser(
types=[
"chore",
"feat",
"test",
"utest",
"itest",
"build",
"pub",
"fix",
"rls",
"doc",
"style",
"lint",
"ci",
"noci",
]
)
def parse_commit(msg: str) -> Commit:
subject = parser.extract_subject(msg)
return parser.extract_commit(subject)
这是测试用例:
# -*- coding: utf-8 -*-
"""
A simple regex parser to parse conventional commit message.
"""
import dataclasses
import re
import string
from typing import Optional, List, Pattern
DELIMITERS = "!@#$%^&*()_+-=~`[{]}\\|;:'\",<.>/? \t\n"
CHARSET = string.ascii_letters
def tokenize(text: str) -> List[str]:
cleaner_text = text
for delimiter in DELIMITERS:
cleaner_text = cleaner_text.replace(delimiter, " ")
words = [word.strip() for word in cleaner_text.split(" ") if word.strip()]
return words
def _get_subject_regex(_types: List[str]) -> Pattern:
return re.compile(
fr"^(?P<types>[\w ,]+)(?:\((?P<scope>[\w-]+)\))?(?P<breaking>!)?:[ \t]?(?P<description>.+)$"
)
@dataclasses.dataclass
class Commit:
"""
Data container class for conventional commits message.
"""
types: List[str]
description: str = None
scope: Optional[str] = None
breaking: Optional[str] = None
class ConventionalCommitParser:
def __init__(self, types: List[str]):
self.types = types
self.subject_regex = _get_subject_regex(types)
def extract_subject(self, msg: str) -> str:
return msg.split("\n")[0].strip()
def extract_commit(self, subject: str) -> Commit:
match = self.subject_regex.match(subject)
types = [
word.strip()
for word in match["types"].split(",")
if word.strip() in self.types
]
# Debug only
# print(match)
# print([match["types"],])
# print([match["description"], ])
# print([match["scope"], ])
# print([match["breaking"], ])
return Commit(
types=types,
description=match["description"],
scope=match["scope"],
breaking=match["breaking"],
)
parser = ConventionalCommitParser(
types=[
"chore",
"feat",
"test",
"utest",
"itest",
"build",
"pub",
"fix",
"rls",
"doc",
"style",
"lint",
"ci",
"noci",
]
)
def parse_commit(msg: str) -> Commit:
subject = parser.extract_subject(msg)
return parser.extract_commit(subject)
社区还有其他的包实现了更强的功能: