forked from NixOS/nix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrelease-credits
executable file
·184 lines (148 loc) · 5.84 KB
/
release-credits
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env nix
# vim: set filetype=python:
#!nix develop --impure --expr
#!nix ``
#!nix let flake = builtins.getFlake ("git+file://" + toString ../.);
#!nix pkgs = flake.inputs.nixpkgs.legacyPackages.${builtins.currentSystem};
#!nix in pkgs.mkShell { nativeBuildInputs = [
#!nix (pkgs.python3.withPackages (ps: with ps; [ requests ]))
#!nix ]; }
#!nix `` --command python3
# This script lists out the contributors for a given release.
# It must be run from the root of the Nix repository.
import os
import sys
import json
import requests
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
print("GITHUB_TOKEN is not set. If you hit the rate limit, set it", file=sys.stderr)
# Might be ok, as we have a cache.
# raise ValueError("GITHUB_TOKEN must be set")
# 1. Read the current version in .version
version = os.environ.get("VERSION")
if not version:
version = open(".version").read().strip()
print(f"Generating release credits for Nix {version}", file=sys.stderr)
# 2. Compute previous version
vcomponents = version.split(".")
if len(vcomponents) >= 2:
prev_version = f"{vcomponents[0]}.{int(vcomponents[1])-1}.0"
else:
raise ValueError(".version must have at least two components")
# For unreleased versions
endref = "HEAD"
# For older releases
# endref = version
# 2. Find the merge base between the current version and the previous version
mergeBase = os.popen(f"git merge-base {prev_version} {endref}").read().strip()
print(f"Merge base between {prev_version} and {endref} is {mergeBase}", file=sys.stderr)
# 3. Find the date of the merge base
mergeBaseDate = os.popen(f"git show -s --format=%ci {mergeBase}").read().strip()[0:10]
print(f"Merge base date is {mergeBaseDate}", file=sys.stderr)
# 4. Get the commits between the merge base and the current version
def get_commits():
raw = os.popen(f"git log --pretty=format:'%H\t%an\t%ae' {mergeBase}..{endref}").read().strip()
lines = raw.split("\n")
return [ { "hash": items[0], "author": items[1], "email": items[2] }
for line in lines
for items in (line.split("\t"),)
]
def commits_to_first_commit_by_email(commits):
by_email = dict()
for commit in commits:
email = commit["email"]
if email not in by_email:
by_email[email] = commit
return by_email
samples = commits_to_first_commit_by_email(get_commits())
# For quick testing, only pick two samples from the dict
# samples = dict(list(samples.items())[:2])
# Query the GitHub API to get handle
def get_github_commit(commit):
url = f"https://api.github.com/repos/NixOS/nix/commits/{commit['hash']}"
headers = {'Authorization': f'token {github_token}'}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
class Cache:
def __init__(self, filename, require = True):
self.filename = filename
try:
with open(filename, "r") as f:
self.values = json.load(f)
except FileNotFoundError:
if require:
raise
self.values = dict()
def save(self):
with open(self.filename, "w") as f:
json.dump(self.values, f, indent=4)
print(f"Saved cache to {self.filename}", file=sys.stderr)
# The email to handle cache maps email addresses to either
# - a handle (string)
# - None (if no handle was found)
email_to_handle_cache = Cache("maintainers/data/release-credits-email-to-handle.json")
handles = set()
emails = dict()
for sample in samples:
s = samples[sample]
email = s["email"]
if not email in email_to_handle_cache.values:
print(f"Querying GitHub API for {s['hash']}, to get handle for {s['email']}")
ghc = get_github_commit(samples[sample])
gha = ghc["author"]
if gha and gha["login"]:
handle = gha["login"]
print(f"Handle: {handle}")
email_to_handle_cache.values[email] = handle
else:
print(f"Found no handle for {s['email']}")
email_to_handle_cache.values[email] = None
handle = email_to_handle_cache.values[email]
if handle is not None:
handles.add(handle)
else:
emails[email] = s["author"]
# print(email_to_handle_cache.values)
email_to_handle_cache.save()
handle_to_name_cache = Cache("maintainers/data/release-credits-handle-to-name.json")
print(f"Found {len(handles)} handles", file=sys.stderr)
for handle in handles:
if not handle in handle_to_name_cache.values:
print(f"Querying GitHub API for {handle}, to get name", file=sys.stderr)
url = f"https://api.github.com/users/{handle}"
headers = {'Authorization': f'token {github_token}'}
response = requests.get(url, headers=headers)
response.raise_for_status()
user = response.json()
name = user["name"]
print(f"Name: {name}", file=sys.stderr)
handle_to_name_cache.values[handle] = name
handle_to_name_cache.save()
entries = list()
def is_bot(handle):
return handle.endswith("[bot]")
for handle in handles:
name = handle_to_name_cache.values[handle]
if name is None:
# This way it looks more regular
name = handle
# Note to bots: I'm only doing this because it would distract humans, and
# therefore delay your creation. I'm sorry; it's for your benefit.
if not is_bot(handle):
entries += [ f"- {name} [**(@{handle})**](https://github.com/{handle})" ]
def shuffle(entries):
salt = os.urandom(16)
return sorted(entries, key=lambda x: hash((x, salt)))
# Fair ordering is undecidable
entries = shuffle(entries)
# For a sanity check, we could sort the entries by handle instead.
# entries = sorted(entries)
print("")
print(f"This release was made possible by the following {len(entries)} contributors:")
print("")
for entry in entries:
print(entry)
for email in emails:
print(f"- {emails[email]}")