File: checkconventionalcommit.py

package info (click to toggle)
crawl 2%3A0.33.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 95,264 kB
  • sloc: cpp: 358,145; ansic: 27,203; javascript: 9,491; python: 8,359; perl: 3,327; java: 2,667; xml: 2,191; makefile: 1,830; sh: 611; objc: 250; cs: 15; sed: 9; lisp: 3
file content (96 lines) | stat: -rwxr-xr-x 3,697 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env python3
import os
import subprocess
import sys

allowed_subject_len = 72
allowed_body_len = 72

# Handy function borrowed from coverage.py
def die(msg: str):
    print(msg, file=sys.stderr)
    sys.exit(1)

def contains_newline(s):
    return '\r' in s or '\n' in s
def print_err(msg: str):
    print(msg, file=sys.stderr)
def main():
    # https://git-scm.com/docs/git-log
    # HEAD ^master gives us all commits not on master
    # The pretty argument lets us make out own more parsing friendly output.
    # %xNN = literal byte %xNN.
    # %x1E = ASCII byte 30, aka "Record Separator". We're using it to separate commits
    # %x1F = ASCII byte 31, aka "Unit Separator". We're using it to separate hash/committer/title/body
    # We're using these because we can be pretty sure they won't show up in commit messages proper.
    # %h = commit hash
    # %cn = committer name
    # %s = subject
    # %b = body
    pretty = '--pretty=%h%x1F%cn%x1F%s%x1F%b%x1E'
    branch_to_check = os.environ.get("GITHUB_HEAD_REF","HEAD")
    base_branch = os.environ.get("GITHUB_BASE_REF", "origin/master")
    print(f"Checking commits being merged from {branch_to_check} to {base_branch}")

    git_args = [ "git", "log", "--no-merges", pretty, branch_to_check, f"^{base_branch}" ]
    command_to_print = " ".join(git_args)
    print(f"Running: {command_to_print}")
    ret = subprocess.run(git_args, capture_output = True, timeout=15)

    if ret.returncode != 0:
        print("Command failed, stdout:", file=sys.stderr)
        print(ret.stdout, file=sys.stderr)
        print("Command failed, stderr:", file=sys.stderr)
        print(ret.stderr, file=sys.stderr)
        die(f"Command failed, exiting {sys.argv[0]}")

    if ret.stdout is None:
        die(f"Command returned no output, exiting {sys.argv[0]}")

    output = ret.stdout.decode("utf-8")
    commits = output.split('\x1E')
    failure = False
    for commit in commits:
        pieces = commit.split('\x1F')
        if len(pieces) == 1 and (pieces[0] == '\n' or pieces[0] == '\r\n'):
            # Only should happen e.g. after the last commit
            # Probably is a better way...
            continue

        if len(pieces) != 4:
            print_err(f"Command output had unexpected number of results ({len(pieces)}), exiting")
            failure = True
            continue

        (commit_hash, committer_name, subject, body) = pieces
        commit_hash = commit_hash.replace('\r\n', '').replace('\n', '')
        print(f"Checking commit {commit_hash} written by {committer_name}")
        if len(subject) > allowed_subject_len:
            print_err(f"Commit {committer_name} from {committer_name} contained a subject "
                f"{len(subject)} characters long,\n"
                f"maximum allowed is {allowed_subject_len}.")
            failure = True
            continue

        if contains_newline(subject):
            print_err(f"Commit {committer_name} from {committer_name} contained a newline in the subject!")
            failure = True
            continue

        for line_number, line in enumerate(body.split('\n')):
            if len(line) > allowed_body_len:
                print_err(f"Commit {committer_name} from {committer_name} "
                    f"has an overly long line in its body,\n"
                    f"body line {line_number}, length {len(line)},\n"
                    f"maximum allowed is {allowed_body_len}\n"
                    f"'{line}'")
                failure = True
                continue

    if failure:
        die("Failed commit message validation!")
    else:
        print("Passed commit message validation!")

if __name__ == "__main__":
    main()