diff options
author | Robert Ancell <robert.ancell@canonical.com> | 2021-02-12 10:23:40 +1300 |
---|---|---|
committer | Robert Ancell <robert.ancell@gmail.com> | 2021-02-12 10:42:42 +1300 |
commit | f3fa27eeeef9aa902d27ec33b90111c79525635d (patch) | |
tree | 95172f5c75f0ac8a94df3753c42ca76333f87a0f | |
parent | 6b9b538d76a52570df8f4999ff2e0c482913dd23 (diff) | |
download | lightdm-git-f3fa27eeeef9aa902d27ec33b90111c79525635d.tar.gz |
Add CLA check
-rw-r--r-- | .github/workflows/cla-check.yaml | 21 | ||||
-rwxr-xr-x | .github/workflows/tools/cla_check.py | 153 |
2 files changed, 174 insertions, 0 deletions
diff --git a/.github/workflows/cla-check.yaml b/.github/workflows/cla-check.yaml new file mode 100644 index 00000000..6d1f4dea --- /dev/null +++ b/.github/workflows/cla-check.yaml @@ -0,0 +1,21 @@ +name: cla-check +on: [pull_request] + +jobs: + cla-check: + runs-on: ubuntu-18.04 + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install python3-launchpadlib git + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + # ensure we pull PR /head, not autogenerated /merge commit + ref: ${{ github.event.pull_request.head.sha }} + - name: Fetching base ref ${{ github.base_ref }} + run: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + - name: Perform CLA check + run: ./.github/workflows/tools/cla_check.py "${{ github.base_ref }}..HEAD" diff --git a/.github/workflows/tools/cla_check.py b/.github/workflows/tools/cla_check.py new file mode 100755 index 00000000..9f13051a --- /dev/null +++ b/.github/workflows/tools/cla_check.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function + +# XXX copied from https://github.com/kyrofa/cla-check +# (and then heavily modified) +import os +import re +import sys +import argparse +from subprocess import check_call, check_output + + +try: + from launchpadlib.launchpad import Launchpad +except ImportError: + sys.exit( + "Install launchpadlib: sudo apt install python-launchpadlib python3-launchpadlib" + ) + +shortlog_email_rx = re.compile(r"^\s*\d+\s+.*<(\S+)>$", re.M) + + +is_travis = os.getenv("TRAVIS", "") == "true" +is_github_actions = os.getenv("GITHUB_ACTIONS", "") == "true" + + +def get_emails_for_range(r): + output = check_output(["git", "shortlog", "-se", r]).decode("utf-8") + return set(m.group(1) for m in shortlog_email_rx.finditer(output)) + + +if sys.stdout.isatty() or is_travis or is_github_actions: + green = "\033[32;1m" + red = "\033[31;1m" + yellow = "\033[33;1m" + reset = "\033[0m" + clear = "\033[0K" +else: + green = "" + red = "" + yellow = "" + reset = "" + clear = "" + +if is_travis: + fold_start = "travis_fold:start:{{tag}}\r{}{}{{message}}{}".format( + clear, yellow, reset + ) + fold_end = "travis_fold:end:{{tag}}\r{}".format(clear) +elif is_github_actions: + fold_start = "::group::{message}" + fold_end = "::endgroup::" +else: + fold_start = "{}{{message}}{}".format(yellow, reset) + fold_end = "" + + +def static_email_check(email, master_emails, width): + if email in master_emails: + print("{}✓{} {:<{}} already on master".format(green, reset, email, width)) + return True + if email.endswith("@canonical.com"): + print("{}✓{} {:<{}} @canonical.com account".format(green, reset, email, width)) + return True + if email.endswith("@mozilla.com"): + print( + "{}✓{} {:<{}} @mozilla.com account (mozilla corp has signed the corp CLA)".format( + green, reset, email, width + ) + ) + return True + if email.endswith("@users.noreply.github.com"): + print( + "{}‽{} {:<{}} privacy-enabled github web edit email address".format( + yellow, reset, email, width + ) + ) + return False + return False + + +def lp_email_check(email, lp, cla_folks, width): + contributor = lp.people.getByEmail(email=email) + if not contributor: + print("{}🛇{} {:<{}} has no Launchpad account".format(red, reset, email, width)) + return False + + if contributor in cla_folks: + print( + "{}✓{} {:<{}} ({}) has signed the CLA".format( + green, reset, email, width, contributor + ) + ) + return True + else: + print( + "{}🛇{} {:<{}} ({}) has NOT signed the CLA".format( + red, reset, email, width, contributor + ) + ) + return False + + +def print_checkout_info(travis_commit_range): + # This is just to have information in case things go wrong + print(fold_start.format(tag="checkout_info", message="Debug information")) + print("Commit range:", travis_commit_range) + print("Remotes:") + sys.stdout.flush() + check_call(["git", "remote", "-v"]) + print("Branches:") + sys.stdout.flush() + check_call(["git", "branch", "-v"]) + sys.stdout.flush() + print(fold_end.format(tag="checkout_info")) + + +def main(): + parser = argparse.ArgumentParser(description="") + parser.add_argument( + "commit_range", help="Commit range in format <upstream-head>..<fork-head>" + ) + opts = parser.parse_args() + master, _ = opts.commit_range.split("..") + print_checkout_info(opts.commit_range) + emails = get_emails_for_range(opts.commit_range) + if len(emails) == 0: + sys.exit("No emails found in in the given commit range.") + + master_emails = get_emails_for_range(master) + + width = max(map(len, emails)) + lp = None + failed = False + print("Need to check {} emails:".format(len(emails))) + for email in emails: + if static_email_check(email, master_emails, width): + continue + # in the normal case this isn't reached + if lp is None: + print("Logging into Launchpad...") + lp = Launchpad.login_anonymously("check CLA", "production") + cla_folks = lp.people["contributor-agreement-canonical"].participants + if not lp_email_check(email, lp, cla_folks, width): + failed = True + + if failed: + sys.exit(1) + + +if __name__ == "__main__": + main() |