[libcamera-devel] utils: checkstyle.py: Add commit title checker
diff mbox series

Message ID 20221221220005.1374-1-laurent.pinchart@ideasonboard.com
State Superseded
Headers show
Series
  • [libcamera-devel] utils: checkstyle.py: Add commit title checker
Related show

Commit Message

Laurent Pinchart Dec. 21, 2022, 10 p.m. UTC
Add a commit checker to ensure that commit titles start with a prefix.
The commit issue message lists prefix candidates retrieved from the git
log.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 utils/checkstyle.py | 63 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)


base-commit: f66a5c447b65bce774a1bc2d01034f437bf764b5

Patch
diff mbox series

diff --git a/utils/checkstyle.py b/utils/checkstyle.py
index a11d95cc5808..c40b79dd46fc 100755
--- a/utils/checkstyle.py
+++ b/utils/checkstyle.py
@@ -352,6 +352,69 @@  class HeaderAddChecker(CommitChecker):
         return issues
 
 
+class TitleChecker(CommitChecker):
+    prefix_regex = re.compile(r'[0-9a-f]+ (([a-zA-Z0-9_.-]+: )+)')
+    release_regex = re.compile(r'libcamera v[0-9]+\.[0-9]+\.[0-9]+')
+
+    @classmethod
+    def check(cls, commit, top_level):
+        issues = []
+
+        title = commit.title
+        if not TitleChecker.release_regex.fullmatch(title):
+            prefix_pos = title.find(': ')
+            if prefix_pos == -1 or prefix_pos == len(title) - 2:
+                # Find prefix candidates by searching the git history
+                msgs = subprocess.run(['git', 'log', '--no-decorate', '--oneline', '-n100', '--'] + commit.files(),
+                                      stdout=subprocess.PIPE).stdout.decode('utf-8')
+                prefixes = {}
+                prefixes_count = 0
+                for msg in msgs.splitlines():
+                    prefix = TitleChecker.prefix_regex.match(msg)
+                    if not prefix:
+                        continue
+
+                    prefix = prefix.group(1)
+                    if prefix in prefixes:
+                        prefixes[prefix] += 1
+                    else:
+                        prefixes[prefix] = 1
+
+                    prefixes_count += 1
+
+                if prefixes:
+                    # Sort the candidates by number of occurrences and pick the
+                    # best ones. When multiple prefixes are possible without a
+                    # clear winner, we want to display the most common options
+                    # to the user, but without the most unlikely options to
+                    # avoid too long messages. As a heuristic, select enough
+                    # candidates to cover at least 2/3 of the possible
+                    # prefixes, but never more than 4 candidates.
+                    prefixes = list(prefixes.items())
+                    prefixes.sort(key=lambda x: x[1], reverse=True)
+
+                    candidates = []
+                    candidates_count = 0
+                    for prefix in prefixes:
+                        candidates.append(f"`{prefix[0]}'")
+                        candidates_count += prefix[1]
+                        if candidates_count >= prefixes_count * 2 / 3 or \
+                           len(candidates) == 4:
+                            break
+
+                    candidates = candidates[:-2] + [' and '.join(candidates[-2:])]
+                    candidates = ', '.join(candidates)
+
+                    issue = CommitIssue('Commit title is missing prefix, '
+                                        'possible candidates are ' + candidates)
+                else:
+                    issue = CommitIssue('Commit title is missing prefix')
+
+                issues.append(issue)
+
+        return issues
+
+
 # ------------------------------------------------------------------------------
 # Style Checkers
 #