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()
|