kopia lustrzana https://github.com/micropython/micropython-lib
tools/verifygitlog.py: Add git commit message checking.
This adds verifygitlog.py from the main repo, adds it to GitHub workflows, and also pre-commit. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>pull/758/head
rodzic
d8e163bb5f
commit
ad0a2590cc
|
@ -0,0 +1,18 @@
|
||||||
|
name: Check commit message formatting
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: '100'
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- name: Check commit message formatting
|
||||||
|
run: source tools/ci.sh && ci_commit_formatting_run
|
|
@ -5,6 +5,12 @@ repos:
|
||||||
name: MicroPython codeformat.py for changed files
|
name: MicroPython codeformat.py for changed files
|
||||||
entry: tools/codeformat.py -v -f
|
entry: tools/codeformat.py -v -f
|
||||||
language: python
|
language: python
|
||||||
|
- id: verifygitlog
|
||||||
|
name: MicroPython git commit message format checker
|
||||||
|
entry: tools/verifygitlog.py --check-file --ignore-rebase
|
||||||
|
language: python
|
||||||
|
verbose: true
|
||||||
|
stages: [commit-msg]
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.1.0
|
rev: v0.1.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|
12
tools/ci.sh
12
tools/ci.sh
|
@ -15,6 +15,18 @@ function ci_code_formatting_run {
|
||||||
tools/codeformat.py -v
|
tools/codeformat.py -v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
########################################################################################
|
||||||
|
# commit formatting
|
||||||
|
|
||||||
|
function ci_commit_formatting_run {
|
||||||
|
git remote add upstream https://github.com/micropython/micropython-lib.git
|
||||||
|
git fetch --depth=100 upstream master
|
||||||
|
# If the common ancestor commit hasn't been found, fetch more.
|
||||||
|
git merge-base upstream/master HEAD || git fetch --unshallow upstream master
|
||||||
|
# For a PR, upstream/master..HEAD ends with a merge commit into master, exclude that one.
|
||||||
|
tools/verifygitlog.py -v upstream/master..HEAD --no-merges
|
||||||
|
}
|
||||||
|
|
||||||
########################################################################################
|
########################################################################################
|
||||||
# build packages
|
# build packages
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This is an exact duplicate of verifygitlog.py from the main repo.
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
verbosity = 0 # Show what's going on, 0 1 or 2.
|
||||||
|
suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages.
|
||||||
|
|
||||||
|
ignore_prefixes = []
|
||||||
|
|
||||||
|
|
||||||
|
def verbose(*args):
|
||||||
|
if verbosity:
|
||||||
|
print(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def very_verbose(*args):
|
||||||
|
if verbosity > 1:
|
||||||
|
print(*args)
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorCollection:
|
||||||
|
# Track errors and warnings as the program runs
|
||||||
|
def __init__(self):
|
||||||
|
self.has_errors = False
|
||||||
|
self.has_warnings = False
|
||||||
|
self.prefix = ""
|
||||||
|
|
||||||
|
def error(self, text):
|
||||||
|
print("error: {}{}".format(self.prefix, text))
|
||||||
|
self.has_errors = True
|
||||||
|
|
||||||
|
def warning(self, text):
|
||||||
|
print("warning: {}{}".format(self.prefix, text))
|
||||||
|
self.has_warnings = True
|
||||||
|
|
||||||
|
|
||||||
|
def git_log(pretty_format, *args):
|
||||||
|
# Delete pretty argument from user args so it doesn't interfere with what we do.
|
||||||
|
args = ["git", "log"] + [arg for arg in args if "--pretty" not in args]
|
||||||
|
args.append("--pretty=format:" + pretty_format)
|
||||||
|
very_verbose("git_log", *args)
|
||||||
|
# Generator yielding each output line.
|
||||||
|
for line in subprocess.Popen(args, stdout=subprocess.PIPE).stdout:
|
||||||
|
yield line.decode().rstrip("\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def diagnose_subject_line(subject_line, subject_line_format, err):
|
||||||
|
err.error("Subject line: " + subject_line)
|
||||||
|
if not subject_line.endswith("."):
|
||||||
|
err.error('* must end with "."')
|
||||||
|
if not re.match(r"^[^!]+: ", subject_line):
|
||||||
|
err.error('* must start with "path: "')
|
||||||
|
if re.match(r"^[^!]+: *$", subject_line):
|
||||||
|
err.error("* must contain a subject after the path.")
|
||||||
|
m = re.match(r"^[^!]+: ([a-z][^ ]*)", subject_line)
|
||||||
|
if m:
|
||||||
|
err.error('* first word of subject ("{}") must be capitalised.'.format(m.group(1)))
|
||||||
|
if re.match(r"^[^!]+: [^ ]+$", subject_line):
|
||||||
|
err.error("* subject must contain more than one word.")
|
||||||
|
err.error("* must match: " + repr(subject_line_format))
|
||||||
|
err.error('* Example: "py/runtime: Add support for foo to bar."')
|
||||||
|
|
||||||
|
|
||||||
|
def verify(sha, err):
|
||||||
|
verbose("verify", sha)
|
||||||
|
err.prefix = "commit " + sha + ": "
|
||||||
|
|
||||||
|
# Author and committer email.
|
||||||
|
for line in git_log("%ae%n%ce", sha, "-n1"):
|
||||||
|
very_verbose("email", line)
|
||||||
|
if "noreply" in line:
|
||||||
|
err.error("Unwanted email address: " + line)
|
||||||
|
|
||||||
|
# Message body.
|
||||||
|
raw_body = list(git_log("%B", sha, "-n1"))
|
||||||
|
verify_message_body(raw_body, err)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_message_body(raw_body, err):
|
||||||
|
if not raw_body:
|
||||||
|
err.error("Message is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Subject line.
|
||||||
|
subject_line = raw_body[0]
|
||||||
|
for prefix in ignore_prefixes:
|
||||||
|
if subject_line.startswith(prefix):
|
||||||
|
verbose("Skipping ignored commit message")
|
||||||
|
return
|
||||||
|
very_verbose("subject_line", subject_line)
|
||||||
|
subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$"
|
||||||
|
if not re.match(subject_line_format, subject_line):
|
||||||
|
diagnose_subject_line(subject_line, subject_line_format, err)
|
||||||
|
if len(subject_line) >= 73:
|
||||||
|
err.error("Subject line must be 72 or fewer characters: " + subject_line)
|
||||||
|
|
||||||
|
# Second one divides subject and body.
|
||||||
|
if len(raw_body) > 1 and raw_body[1]:
|
||||||
|
err.error("Second message line must be empty: " + raw_body[1])
|
||||||
|
|
||||||
|
# Message body lines.
|
||||||
|
for line in raw_body[2:]:
|
||||||
|
# Long lines with URLs are exempt from the line length rule.
|
||||||
|
if len(line) >= 76 and "://" not in line:
|
||||||
|
err.error("Message lines should be 75 or less characters: " + line)
|
||||||
|
|
||||||
|
if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]:
|
||||||
|
err.error('Message must be signed-off. Use "git commit -s".')
|
||||||
|
|
||||||
|
|
||||||
|
def run(args):
|
||||||
|
verbose("run", *args)
|
||||||
|
|
||||||
|
err = ErrorCollection()
|
||||||
|
|
||||||
|
if "--check-file" in args:
|
||||||
|
filename = args[-1]
|
||||||
|
verbose("checking commit message from", filename)
|
||||||
|
with open(args[-1]) as f:
|
||||||
|
# Remove comment lines as well as any empty lines at the end.
|
||||||
|
lines = [line.rstrip("\r\n") for line in f if not line.startswith("#")]
|
||||||
|
while not lines[-1]:
|
||||||
|
lines.pop()
|
||||||
|
verify_message_body(lines, err)
|
||||||
|
else: # Normal operation, pass arguments to git log
|
||||||
|
for sha in git_log("%h", *args):
|
||||||
|
verify(sha, err)
|
||||||
|
|
||||||
|
if err.has_errors or err.has_warnings:
|
||||||
|
if suggestions:
|
||||||
|
print("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md")
|
||||||
|
else:
|
||||||
|
print("ok")
|
||||||
|
if err.has_errors:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def show_help():
|
||||||
|
print("usage: verifygitlog.py [-v -n -h --check-file] ...")
|
||||||
|
print("-v : increase verbosity, can be specified multiple times")
|
||||||
|
print("-n : do not print multi-line suggestions")
|
||||||
|
print("-h : print this help message and exit")
|
||||||
|
print(
|
||||||
|
"--check-file : Pass a single argument which is a file containing a candidate commit message"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"--ignore-rebase : Skip checking commits with git rebase autosquash prefixes or WIP as a prefix"
|
||||||
|
)
|
||||||
|
print("... : arguments passed to git log to retrieve commits to verify")
|
||||||
|
print(" see https://www.git-scm.com/docs/git-log")
|
||||||
|
print(" passing no arguments at all will verify all commits")
|
||||||
|
print("examples:")
|
||||||
|
print("verifygitlog.py -n10 # Check last 10 commits")
|
||||||
|
print("verifygitlog.py -v master..HEAD # Check commits since master")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = sys.argv[1:]
|
||||||
|
verbosity = args.count("-v")
|
||||||
|
suggestions = args.count("-n") == 0
|
||||||
|
if "--ignore-rebase" in args:
|
||||||
|
args.remove("--ignore-rebase")
|
||||||
|
ignore_prefixes = ["squash!", "fixup!", "amend!", "WIP"]
|
||||||
|
|
||||||
|
if "-h" in args:
|
||||||
|
show_help()
|
||||||
|
else:
|
||||||
|
args = [arg for arg in args if arg not in ["-v", "-n", "-h"]]
|
||||||
|
run(args)
|
Ładowanie…
Reference in New Issue