gitlab CI: add a check-commit stage
authorPeter Hutterer <peter.hutterer@who-t.net>
Thu, 13 Feb 2020 22:20:27 +0000 (08:20 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 13 Feb 2020 22:30:29 +0000 (08:30 +1000)
Taken from libinput, checks for signed-off-by and other things.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
.gitlab-ci.yml
.gitlab-ci/check-commit.py [new file with mode: 0755]

index 9f42caca6ebe9a2492c023dd675ce472c6724659..43e854fbc3c06ce9c88690a368ebb19105575c4a 100644 (file)
@@ -28,7 +28,7 @@ include:
     file: '/templates/centos.yml'
 
 stages:
-  - container_prep   # rebuild the container images if there is a change
+  - prep             # rebuild the container images if there is a change
   - build            # for actually building and testing things in a container
   - VM               # for running the test suite in a VM
   - distro           # distribs test
@@ -79,6 +79,8 @@ variables:
       - test/test-suite.log
     expire_in: 1 week
     when: on_failure
+    reports:
+      junit: junit-*.xml
 
 .default_build: &default_build
   script:
@@ -91,10 +93,31 @@ variables:
 
 #################################################################
 #                                                               #
-#                    container prep stage                       #
+#                          prep stage                           #
 #                                                               #
 #################################################################
 
+check-commit:
+  image: golang:alpine
+  stage: prep
+  before_script:
+    - apk add python3 git
+  script:
+    - pip3 install GitPython
+    - pip3 install pytest
+    - |
+      pytest --junitxml=results.xml \
+             --tb=line \
+             --assert=plain \
+             ./.gitlab-ci/check-commit.py
+  except:
+    - master@libinput/libinput
+  variables:
+    GIT_DEPTH: 100
+  artifacts:
+    reports:
+      junit: results.xml
+
 .pull_upstream_or_rebuild: &pull_upstream_or_rebuild
   before_script:
     # log in to the registry
@@ -117,7 +140,7 @@ variables:
 
 fedora:30@container-prep:
   extends: .fedora@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     FEDORA_VERSION: 30
@@ -128,7 +151,7 @@ fedora:30@container-prep:
 
 fedora:31@container-prep:
   extends: .fedora@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     FEDORA_VERSION: 31
@@ -140,7 +163,7 @@ fedora:31@container-prep:
 
 ubuntu:19.10@container-prep:
   extends: .ubuntu@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     UBUNTU_VERSION: "19.10"
@@ -151,7 +174,7 @@ ubuntu:19.10@container-prep:
 
 ubuntu:19.04@container-prep:
   extends: .ubuntu@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     UBUNTU_VERSION: "19.04"
@@ -162,7 +185,7 @@ ubuntu:19.04@container-prep:
 
 debian:stable@container-prep:
   extends: .debian@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     DEBIAN_VERSION: stable
@@ -173,7 +196,7 @@ debian:stable@container-prep:
 
 debian:sid@container-prep:
   extends: .debian@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     DEBIAN_VERSION: sid
@@ -184,7 +207,7 @@ debian:sid@container-prep:
 
 .centos@container-prep:
   extends: .centos@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     CENTOS_VERSION: 7
@@ -205,7 +228,7 @@ centos:8@container-prep:
 
 arch:rolling@container-prep:
   extends: .arch@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     ARCH_VERSION: rolling
@@ -216,7 +239,7 @@ arch:rolling@container-prep:
 
 alpine:latest@container-prep:
   extends: .alpine@container-build
-  stage: container_prep
+  stage: prep
   variables:
     GIT_STRATEGY: none
     ALPINE_VERSION: latest
diff --git a/.gitlab-ci/check-commit.py b/.gitlab-ci/check-commit.py
new file mode 100755 (executable)
index 0000000..b533bc4
--- /dev/null
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# vim: set expandtab shiftwidth=4:
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+# This script tests a few things against the commit messages, search for
+# `def test_` to see the actual tests run.
+
+import git
+import os
+import pytest
+
+if os.environ.get('CI'):
+    # Environment variables set by gitlab
+    CI_COMMIT_SHA = os.environ['CI_COMMIT_SHA']
+    # This is intentionally hardcoded to master. CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+    # is only available when run with only: [merge_requests]
+    # but that generates a detached pipeline with only this job in it.
+    # Since merging into a non-master branch is not a thing in libevdev
+    # anyway, we can hardcode this here.
+    CI_MERGE_REQUEST_TARGET_BRANCH_NAME = 'master'
+    CI_SERVER_HOST = os.environ['CI_SERVER_HOST']
+    UPSTREAM = 'upstream'
+else:
+    # Local emulation mode when called directly
+    import argparse
+
+    parser = argparse.ArgumentParser(description='Commit message checker - local emulation mode')
+    parser.add_argument('--sha', help='The commit message to start at (default: HEAD}',
+                        default='HEAD')
+    parser.add_argument('--branch', help='The branch name to merge to (default: master)',
+                        default='master')
+    parser.add_argument('--remote', help='The remote name (default: origin)',
+                        default='origin')
+    args = parser.parse_args()
+
+    CI_COMMIT_SHA = args.sha
+    CI_MERGE_REQUEST_TARGET_BRANCH_NAME = args.branch
+    CI_SERVER_HOST = None
+    UPSTREAM = 'origin'
+    print(f'Running in local testing mode.')
+
+print(f'Merging {CI_COMMIT_SHA} into {CI_MERGE_REQUEST_TARGET_BRANCH_NAME}')
+
+# We need to add the real libevdev as remote, our origin here is the user's
+# fork.
+repo = git.Repo('.')
+if UPSTREAM not in repo.remotes:
+    upstream = repo.create_remote('upstream', f'https://{CI_SERVER_HOST}/libevdev/libevdev.git')
+    upstream.fetch()
+
+sha = CI_COMMIT_SHA
+branch = CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+
+commits = list(repo.iter_commits(f'{UPSTREAM}/{branch}..{sha}'))
+
+
+def error(commit, message, long_message=''):
+    info = ('After correcting the above issue(s), force-push to the same branch.\n'
+            'This will re-trigger the CI.\n\n'
+            'A list of requirements for commit messages is available at\n'
+            'https://gitlab.freedesktop.org/libevdev/libevdev/blob/master/CODING_STYLE.md')
+
+    msg = (f'\n'
+           f'Commit message check failed: {message}\n\n'
+           f'  commit: {str(commit)}\n'
+           f'  author: {commit.author.name} <{commit.author.email}>\n'
+           f'\n'
+           f'  {commit.summary}\n'
+           f'\n'
+           f'\n'
+           f'{long_message}\n\n'
+           f'{info}\n\n')
+    return msg
+
+
+@pytest.mark.parametrize('commit', commits)
+class TestCommits:
+    def test_author_email(self, commit):
+        assert '@users.noreply.gitlab.freedesktop.org' not in commit.author.email, \
+            error(commit, 'git author email invalid',
+                  ('Please set your name and email with the commands\n',
+                   '    git config --global user.name Your Name\n'
+                   '    git config --global user.email your.email@provider.com\n'))
+
+    def test_signed_off_by(self, commit):
+        if not commit.message.startswith('Revert "'):
+            assert 'Signed-off-by:' in commit.message, \
+                error(commit, 'missing Signed-off-by tag',
+                      'Please add the required "Signed-off-by: author information" line\n'
+                      'to the commit message')
+
+    def test_fixup(self, commit):
+        assert not commit.message.startswith('fixup!'), \
+            error(commit, 'Remove fixup! tag',
+                  'Leftover "fixup!" commit message detected, please squash')
+        assert not commit.message.startswith('squash!'), \
+            error(commit, 'Remove squash! tag',
+                  'Leftover "squash!" commit message detected, please squash')
+
+    def test_line_length(self, commit):
+        lines = commit.message.split('\n')
+        first_line = lines[0]
+
+        assert len(first_line) < 85, \
+            error(commit, 'Commit message subject line too long')
+
+        try:
+            second_line = lines[1]
+            assert second_line == '', \
+                error(commit, 'Second line in commit message must be emtpy')
+        except IndexError:
+            pass
+
+
+if __name__ == '__main__':
+    pytest.main([__file__])