summaryrefslogtreecommitdiff
path: root/.github/workflows/tools/cla_check.py
blob: 9f13051a64d70a5148eaa8058dbcaaabeff0d7c9 (plain)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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()