Credit all contributors in release notes

This commit is contained in:
Robert Hensing 2024-07-29 23:25:31 +02:00
parent 84243027ec
commit f380becffa
5 changed files with 282 additions and 0 deletions

View 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"
}

View 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
View 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]}")

View file

@ -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"

View 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