mirror of
https://github.com/NixOS/nix.git
synced 2024-11-21 22:32:26 +00:00
Credit all contributors in release notes
This commit is contained in:
parent
84243027ec
commit
f380becffa
51
maintainers/data/release-credits-email-to-handle.json
Normal file
51
maintainers/data/release-credits-email-to-handle.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"bogus": "bogus",
|
||||
"edolstra@gmail.com": "edolstra",
|
||||
"roberth@users.noreply.github.com": "roberth",
|
||||
"toscano.pino@tiscali.it": "pinotree",
|
||||
"valentin@gagarin.work": "fricklerhandwerk",
|
||||
"mr.trubach@icloud.com": "tie",
|
||||
"robert@roberthensing.nl": "roberth",
|
||||
"lix@jade.fyi": "lf-",
|
||||
"cole.e.helbling@outlook.com": "cole-h",
|
||||
"joerg@thalheim.io": "Mic92",
|
||||
"John.Ericson@Obsidian.Systems": "Ericson2314",
|
||||
"ryan.hendrickson@alum.mit.edu": "rhendric",
|
||||
"67135060+poweredbypie@users.noreply.github.com": "poweredbypie",
|
||||
"detroyejr@outlook.com": "detroyejr",
|
||||
"silvan.mosberger@tweag.io": "infinisil",
|
||||
"vcs@emily.moe": "emilazy",
|
||||
"farid.m.zakaria@gmail.com": "fzakaria",
|
||||
"22859658+RTUnreal@users.noreply.github.com": "RTUnreal",
|
||||
"me@las.rs": "L-as",
|
||||
"philip.taron@gmail.com": "philiptaron",
|
||||
"root@goldstein.rs": "GoldsteinE",
|
||||
"tomberek@users.noreply.github.com": "tomberek",
|
||||
"lexi.mattick@neuralink.com": "kognise",
|
||||
"andrew@johnandrewmarshall.com": "amarshall",
|
||||
"contact@romain-neil.fr": "romain-neil",
|
||||
"Mic92@users.noreply.github.com": "Mic92",
|
||||
"valentin.gagarin@tweag.io": "fricklerhandwerk",
|
||||
"siddhantk232@gmail.com": "siddhantk232",
|
||||
"kn@openbsd.org": "klemensn",
|
||||
"slyich@gmail.com": "trofi",
|
||||
"theophane.hufschmitt@tweag.io": "thufschmitt",
|
||||
"alicebob@lijzij.de": "alicebob",
|
||||
"winter@winter.cafe": "winterqt",
|
||||
"brian@brianmckenna.org": "puffnfresh",
|
||||
"git@haenoe.party": "haenoe",
|
||||
"peshogo@gmail.com": "pineapplehunter",
|
||||
"poweredbypie@users.noreply.github.com": "poweredbypie",
|
||||
"arthur200126@gmail.com": "Artoria2e5",
|
||||
"tomberek@gmail.com": "tomberek",
|
||||
"jaredbaur@fastmail.com": "jmbaur",
|
||||
"andreas@rammhold.de": "andir",
|
||||
"hamirmahal@gmail.com": "hamirmahal",
|
||||
"git@JohnEricson.me": "Ericson2314",
|
||||
"8763518+SkamDart@users.noreply.github.com": "SkamDart",
|
||||
"kirillrdy@gmail.com": "kirillrdy",
|
||||
"pennae@lix.systems": "pennae",
|
||||
"delroth@gmail.com": "delroth",
|
||||
"enno@nerdworks.de": "elohmeier",
|
||||
"mjbauer95@gmail.com": "matthewbauer"
|
||||
}
|
44
maintainers/data/release-credits-handle-to-name.json
Normal file
44
maintainers/data/release-credits-handle-to-name.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"fzakaria": "Farid Zakaria",
|
||||
"kognise": "Lexi Mattick",
|
||||
"L-as": "Las Safin",
|
||||
"haenoe": "HaeNoe",
|
||||
"andir": "Andreas Rammhold",
|
||||
"matthewbauer": "Matthew Bauer",
|
||||
"emilazy": "Emily",
|
||||
"pineapplehunter": "Shogo Takata",
|
||||
"RTUnreal": null,
|
||||
"jmbaur": "Jared Baur",
|
||||
"Ericson2314": "John Ericson",
|
||||
"pinotree": "Pino Toscano",
|
||||
"tie": "Ivan Trubach",
|
||||
"poweredbypie": null,
|
||||
"fricklerhandwerk": "Valentin Gagarin",
|
||||
"Mic92": "J\u00f6rg Thalheim",
|
||||
"alicebob": "Harmen",
|
||||
"elohmeier": "Enno Richter",
|
||||
"delroth": "Pierre Bourdon",
|
||||
"kirillrdy": null,
|
||||
"thufschmitt": "Th\u00e9ophane Hufschmitt",
|
||||
"detroyejr": "Jonathan De Troye",
|
||||
"klemensn": "Klemens Nanni",
|
||||
"tomberek": null,
|
||||
"rhendric": "Ryan Hendrickson",
|
||||
"philiptaron": "Philip Taron",
|
||||
"puffnfresh": "Brian McKenna",
|
||||
"lf-": "jade",
|
||||
"romain-neil": "Romain Neil",
|
||||
"hamirmahal": "Hamir Mahal",
|
||||
"edolstra": "Eelco Dolstra",
|
||||
"Artoria2e5": "Mingye Wang",
|
||||
"SkamDart": "Cameron",
|
||||
"roberth": "Robert Hensing",
|
||||
"amarshall": "Andrew Marshall",
|
||||
"trofi": "Sergei Trofimovich",
|
||||
"cole-h": "Cole Helbling",
|
||||
"infinisil": "Silvan Mosberger",
|
||||
"siddhantk232": "Siddhant Kumar",
|
||||
"winterqt": "Winter",
|
||||
"GoldsteinE": "Max \u201cGoldstein\u201d Siling",
|
||||
"pennae": null
|
||||
}
|
176
maintainers/release-credits
Executable file
176
maintainers/release-credits
Executable file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env nix
|
||||
#!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()
|
||||
|
||||
for handle in handles:
|
||||
name = handle_to_name_cache.values[handle]
|
||||
if name is None:
|
||||
# This way it looks more regular
|
||||
name = 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]}")
|
@ -151,6 +151,13 @@ section_title="Release $version_full ($DATE)"
|
||||
echo "# $section_title"
|
||||
echo
|
||||
changelog-d doc/manual/rl-next | sed -e 's/ *$//'
|
||||
|
||||
if ! $IS_PATCH; then
|
||||
echo
|
||||
echo "# Contributors"
|
||||
echo
|
||||
VERSION=$version_full ./maintainers/release-credits
|
||||
fi
|
||||
) | tee -a $file
|
||||
|
||||
log "Wrote $file"
|
||||
|
@ -39,6 +39,10 @@ release:
|
||||
* Proof-read / edit / rearrange the release notes if needed. Breaking changes
|
||||
and highlights should go to the top.
|
||||
|
||||
* Run `maintainers/release-credits` to make sure the credits script works
|
||||
and produces a sensible output. Some emails might not automatically map to
|
||||
a GitHub handle.
|
||||
|
||||
* Push.
|
||||
|
||||
```console
|
||||
|
Loading…
Reference in New Issue
Block a user