From 1812f55a1f18581e8c3392259cbcb079526b4506 Mon Sep 17 00:00:00 2001 From: Arnaud Pouliquen Date: Tue, 21 Apr 2020 14:01:35 +0200 Subject: [PATCH] gitlint: add giltlint configuration files Add gitlint configuration to allow contributor to check its commit message compliance before sending its pull request. The configuration files has been copied from Zephyr project. Signed-off-by: Arnaud Pouliquen --- .gitlint | 99 ++++++++++++++++++++++++++ scripts/gitlint/commit_rules.py | 119 ++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 .gitlint create mode 100644 scripts/gitlint/commit_rules.py diff --git a/.gitlint b/.gitlint new file mode 100644 index 000000000..92d9e1b8d --- /dev/null +++ b/.gitlint @@ -0,0 +1,99 @@ +# All these sections are optional, edit this file as you like. +[general] +# Ignore certain rules, you can reference them by their id or by their full name +ignore=title-trailing-punctuation, T3, title-max-length, T1, body-hard-tab, B1, B3 + +# verbosity should be a value between 1 and 3, the commandline -v flags take precedence over this +verbosity = 3 + +# By default gitlint will ignore merge commits. Set to 'false' to disable. +ignore-merge-commits=true + +# By default gitlint will ignore fixup commits. Set to 'false' to disable. +# ignore-fixup-commits=false + +# By default gitlint will ignore squash commits. Set to 'false' to disable. +# ignore-squash-commits=true + +# Ignore any data send to gitlint via stdin +# ignore-stdin=true + +# Enable debug mode (prints more output). Disabled by default. +debug=true + +# Enable community contributed rules +# See http://jorisroovers.github.io/gitlint/contrib_rules for details +# contrib=contrib-title-conventional-commits,CC1 + +# Set the extra-path where gitlint will search for user defined rules +# See http://jorisroovers.github.io/gitlint/user_defined_rules for details +extra-path=scripts/gitlint + +[title-max-length] +line-length=75 + +[body-min-line-count] +min-line-count=1 + +[body-max-line-count] +max-line-count=200 + +[title-must-not-contain-word] +# Comma-separated list of words that should not occur in the title. Matching is case +# insensitive. It's fine if the keyword occurs as part of a larger word (so "WIPING" +# will not cause a violation, but "WIP: my title" will. +words=wip + +# [title-match-regex] +# python like regex (https://docs.python.org/2/library/re.html) that the +# commit-msg title must be matched to. +# Note that the regex can contradict with other rules if not used correctly +# (e.g. title-must-not-contain-word). +# regex=^US[0-9]* + +[body-max-line-length] +# B1 = body-max-line-length +line-length=80 + +[body-min-length] +min-length=3 + +[body-is-missing] +# Whether to ignore this rule on merge commits (which typically only have a title) +# default = True +ignore-merge-commits=false + +# [body-changed-file-mention] +# List of files that need to be explicitly mentioned in the body when they are changed +# This is useful for when developers often erroneously edit certain files or git submodules. +# By specifying this rule, developers can only change the file when they explicitly reference +# it in the commit message. +# files=gitlint/rules.py,README.md + +# [author-valid-email] +# python like regex (https://docs.python.org/2/library/re.html) that the +# commit author email address should be matched to +# For example, use the following regex if you only want to allow email addresses from foo.com +# regex=[^@]+@foo.com + +# [ignore-by-title] +# Ignore certain rules for commits of which the title matches a regex +# E.g. Match commit titles that start with "Release" +# regex=^Release(.*) +# +# Ignore certain rules, you can reference them by their id or by their full name +# Use 'all' to ignore all rules +# ignore=T1,body-min-length + +# [ignore-by-body] +# Ignore certain rules for commits of which the body has a line that matches a regex +# E.g. Match bodies that have a line that that contain "release" +# regex=(.*)release(.*) +# +# Ignore certain rules, you can reference them by their id or by their full name +# Use 'all' to ignore all rules +# ignore=T1,body-min-length + +# [contrib-title-conventional-commits] +# Specify allowed commit types. For details see: https://www.conventionalcommits.org/ +# types = bugfix,user-story,epic \ No newline at end of file diff --git a/scripts/gitlint/commit_rules.py b/scripts/gitlint/commit_rules.py new file mode 100644 index 000000000..915f08544 --- /dev/null +++ b/scripts/gitlint/commit_rules.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: Apache-2.0 + +""" +The classes below are examples of user-defined CommitRules. Commit rules are gitlint rules that +act on the entire commit at once. Once the rules are discovered, gitlint will automatically take care of applying them +to the entire commit. This happens exactly once per commit. + +A CommitRule contrasts with a LineRule (see examples/my_line_rules.py) in that a commit rule is only applied once on +an entire commit. This allows commit rules to implement more complex checks that span multiple lines and/or checks +that should only be done once per gitlint run. + +While every LineRule can be implemented as a CommitRule, it's usually easier and more concise to go with a LineRule if +that fits your needs. +""" + +from gitlint.rules import CommitRule, RuleViolation, CommitMessageTitle, LineRule, CommitMessageBody +from gitlint.options import IntOption, StrOption +import re + +class BodyMinLineCount(CommitRule): + # A rule MUST have a human friendly name + name = "body-min-line-count" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). + id = "UC6" + + # A rule MAY have an option_spec if its behavior should be configurable. + options_spec = [IntOption('min-line-count', 2, "Minimum body line count excluding Signed-off-by")] + + def validate(self, commit): + filtered = [x for x in commit.message.body if not x.lower().startswith("signed-off-by") and x != ''] + line_count = len(filtered) + min_line_count = self.options['min-line-count'].value + if line_count < min_line_count: + message = "Body has no content, should at least have {} line.".format(min_line_count) + return [RuleViolation(self.id, message, line_nr=1)] + +class BodyMaxLineCount(CommitRule): + # A rule MUST have a human friendly name + name = "body-max-line-count" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). + id = "UC1" + + # A rule MAY have an option_spec if its behavior should be configurable. + options_spec = [IntOption('max-line-count', 3, "Maximum body line count")] + + def validate(self, commit): + line_count = len(commit.message.body) + max_line_count = self.options['max-line-count'].value + if line_count > max_line_count: + message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count) + return [RuleViolation(self.id, message, line_nr=1)] + +class SignedOffBy(CommitRule): + """ This rule will enforce that each commit contains a "Signed-off-by" line. + We keep things simple here and just check whether the commit body contains a line that starts with "Signed-off-by". + """ + + # A rule MUST have a human friendly name + name = "body-requires-signed-off-by" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). + id = "UC2" + + def validate(self, commit): + flags = re.UNICODE + flags |= re.IGNORECASE + for line in commit.message.body: + if line.lower().startswith("signed-off-by"): + if not re.search(r"(^)Signed-off-by: ([-'\w.]+) ([-'\w.]+) (.*)", line, flags=flags): + return [RuleViolation(self.id, "Signed-off-by: must have a full name", line_nr=1)] + else: + return + return [RuleViolation(self.id, "Body does not contain a 'Signed-off-by:' line", line_nr=1)] + +class TitleMaxLengthRevert(LineRule): + name = "title-max-length-no-revert" + id = "UC5" + target = CommitMessageTitle + options_spec = [IntOption('line-length', 72, "Max line length")] + violation_message = "Title exceeds max length ({0}>{1})" + + def validate(self, line, _commit): + max_length = self.options['line-length'].value + if len(line) > max_length and not line.startswith("Revert"): + return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)] + +class TitleStartsWithSubsystem(LineRule): + name = "title-starts-with-subsystem" + id = "UC3" + target = CommitMessageTitle + options_spec = [StrOption('regex', ".*", "Regex the title should match")] + + def validate(self, title, _commit): + regex = self.options['regex'].value + pattern = re.compile(regex, re.UNICODE) + violation_message = "Title does not follow [subsystem]: [subject] (and should not start with literal subsys:)" + if not pattern.search(title): + return [RuleViolation(self.id, violation_message, title)] + +class MaxLineLengthExceptions(LineRule): + name = "max-line-length-with-exceptions" + id = "UC4" + target = CommitMessageBody + options_spec = [IntOption('line-length', 80, "Max line length")] + violation_message = "Line exceeds max length ({0}>{1})" + + def validate(self, line, _commit): + max_length = self.options['line-length'].value + urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line) + if line.startswith('Signed-off-by'): + return + + if urls: + return + + if len(line) > max_length: + return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]