Credits Script: improve author de-duplication

- Add git_data_canonical_authors module with maps various
  "Author <email>" combinations to a single author.
- Add git_data_sha1_override_authors module which
  overrides authors for individual commits.
- GitCommit.author now includes the email.
- Move co-author access into GitCommit.co_authors.

Using name & email, then de-duplicating removes 43 duplicates in the
generated credits.

Moving the author & override mapping out of credits_git_gen.py
allows this data to be shared by a utility to update AUTHORS
(not yet committed).
This commit is contained in:
Campbell Barton 2023-08-16 17:10:57 +10:00
parent 27eb6d48d3
commit 139dc716f1
4 changed files with 844 additions and 153 deletions

@ -10,15 +10,11 @@ Example use:
"""
import argparse
import io
import multiprocessing
import re
import os
import unicodedata
from git_log import (
GitCommitIter,
GitCommit,
)
from typing import (
Dict,
Tuple,
@ -26,97 +22,17 @@ from typing import (
List,
)
from git_log import (
GitCommitIter,
GitCommit,
)
# -----------------------------------------------------------------------------
# Lookup Table to clean up the credits
#
# This is a combination of unifying git logs as well as
# name change requested by the authors.
import git_data_canonical_authors
import git_data_sha1_override_authors
author_table = {
"Aaron": "Aaron Carlisle",
"Your Name": "Aaron Carlisle",
"Alan": "Alan Troth",
"andreas atteneder": "Andreas Atteneder",
"Ankit": "Ankit Meel",
"Antonioya": "Antonio Vazquez",
"Antonio Vazquez": "Antonio Vazquez",
"Antony Ryakiotakis": "Antony Riakiotakis",
"Amélie Fondevilla": "Amelie Fondevilla",
"bastien": "Bastien Montagne",
"mont29": "Bastien Montagne",
"bjornmose": "Bjorn Mose",
"meta-androcto": "Brendon Murphy",
"Brecht van Lommel": "Brecht Van Lommel",
"Brecht Van Lömmel": "Brecht Van Lommel",
"recht Van Lommel": "Brecht Van Lommel",
"Clément Foucault": "Clément Foucault",
"Clément": "Clément Foucault",
"fclem": "Clément Foucault",
"Clment Foucault": "Clément Foucault",
"christian brinkmann": "Christian Brinkmann",
"ZanQdo": "Daniel Salazar",
"unclezeiv": "Davide Vercelli",
"dilithjay": "Dilith Jayakody",
"gaiaclary": "Gaia Clary",
"DESKTOP-ON14TH5\\Sonny Campbell": "Sonny Campbell",
"demeterdzadik@gmail.com": "Demeter Dzadik",
"Diego Hernan Borghetti": "Diego Borghetti",
"Dotsnov Valentin": "Dontsov Valentin",
"Eitan": "Eitan Traurig",
"EitanSomething": "Eitan Traurig",
"Erik": "Erik Abrahamsson",
"Erick Abrahammson": "Erik Abrahamsson",
"Eric Abrahamsson": "Erik Abrahamsson",
"Ethan-Hall": "Ethan Hall",
"filedescriptor": "Falk David",
"Germano": "Germano Cavalcante",
"Germano Cavalcantemano-wii": "Germano Cavalcante",
"mano-wii": "Germano Cavalcante",
"gsr": "Guillermo S. Romero",
"Henrik Dick (weasel)": "Henrik Dick",
"howardt": "Howard Trickey",
"Iliay Katueshenock": "Iliya Katueshenock",
"MOD": "Iliya Katueshenock",
"Inês Almeida": "Ines Almeida",
"brita": "Ines Almeida",
"Ivan": "Ivan Perevala",
"jensverwiebe": "Jens Verwiebe",
"Jesse Y": "Jesse Yurkovich",
"Joe Eagar": "Joseph Eagar",
"Johnny Matthews (guitargeek)": "Johnny Matthews",
"guitargeek": "Johnny Matthews",
"jon denning": "Jon Denning",
"julianeisel": "Julian Eisel",
"Severin": "Julian Eisel",
"Alex Strand": "Kenzie Strand",
"Kevin Dietrich": "Kévin Dietrich",
"Leon Leno": "Leon Schittek",
"Lukas Toenne": "Lukas Tönne",
"Mikhail": "Mikhail Matrosov",
"OmarSquircleArt": "Omar Emara",
"lazydodo": "Ray Molenkamp",
"Ray molenkamp": "Ray Molenkamp",
"Author Name": "Robert Guetzkow",
"Sybren A. Stüvel": "Sybren A. Stüvel",
"Simon": "Simon G",
"Stephan": "Stephan Seitz",
"Sebastian Herhoz": "Sebastian Herholz",
"blender": "Sergey Sharybin",
"Vuk GardaÅ¡ević": "Vuk Gardašević",
"ianwill": "Willian Padovani Germano",
"Yiming Wu": "YimingWu",
}
# Mapping from a comit hash to additional authors.
# Fully overwrite authors gathered from git commit info.
# Intended usage: Correction of info stored in git commit itself.
# Note that the names of the authors here are assumed fully valid and usable as-is.
commit_authors_overwrite: Dict[bytes, Tuple[str, str]] = {
# Format: {full_git_hash: (tuple, of, authors),}.
# Example:
# b"a60c1e5bb814078411ce105b7cf347afac6f2afd": ("Blender Authors", "Suzanne", "Ton"),
}
author_table = git_data_canonical_authors.canonical_author_map()
author_override_table = git_data_sha1_override_authors.sha1_authors_map()
# -----------------------------------------------------------------------------
@ -151,22 +67,16 @@ class Credits:
"users",
)
# Expected to cover the following formats (the e-mail address is not captured if present):
# `Co-authored-by: Blender Authors`
# `Co-authored-by: Blender Authors <foundation@blender.org>`
# `Co-authored-by: Blender Authors <Suzanne>`
GIT_COMMIT_COAUTHORS_RE = re.compile(r"^Co-authored-by:[ \t]*(?P<author>[ \w\t]*\w)(?:$|[ \t]*<)", re.MULTILINE)
def __init__(self) -> None:
self.users: Dict[str, CreditUser] = {}
@classmethod
def commit_authors_get(cls, c: GitCommit) -> List[str]:
if (authors_overwrite := commit_authors_overwrite.get(c.sha1, None)) is not None:
# Ignore git commit info for these having an entry in commit_authors_overwrite.
if (authors_overwrite := author_override_table.get(c.sha1, None)) is not None:
# Ignore git commit info for these having an entry in `author_override_table`.
return [author_table.get(author, author) for author in authors_overwrite]
authors = [c.author] + cls.GIT_COMMIT_COAUTHORS_RE.findall(c.body)
authors = [c.author] + c.co_authors
# Normalize author string into canonical form, prevents duplicate credit users
authors = [unicodedata.normalize('NFC', author) for author in authors]
return [author_table.get(author, author) for author in authors]
@ -233,7 +143,7 @@ class Credits:
total_commits = (max(len(chunk_list) - 1, 0) * chunk_size) + len(chunk)
print("Found {:d} commits, processing...".format(total_commits))
print("Found {:,d} commits, processing...".format(total_commits))
with multiprocessing.Pool(processes=jobs) as pool:
for i, result in enumerate(pool.imap_unordered(process_commits_for_map, chunk_list)):
print("{:d} of {:d}".format(i, len(chunk_list)))
@ -250,15 +160,15 @@ class Credits:
if not (i % 100):
print(i)
def write(
def write_object(
self,
filepath: str,
fh: io.TextIOWrapper,
*,
is_main_credits: bool = True,
contrib_companies: Tuple[str, ...] = (),
sort: str = "name",
use_email: bool = False,
) -> None:
# patch_word = "patch", "patches"
commit_word = "commit", "commits"
sorted_authors = {}
@ -267,38 +177,59 @@ class Credits:
else:
sorted_authors = dict(sorted(self.users.items()))
with open(filepath, 'w', encoding="ascii", errors='xmlcharrefreplace') as file:
file.write("<h3>Individual Contributors</h3>\n\n")
for author, cu in sorted_authors.items():
file.write("{:s}, {:,d} {:s} {:s}<br />\n".format(
author,
cu.commit_total,
commit_word[cu.commit_total > 1],
("" if not is_main_credits else
("- {:d}".format(cu.year_min) if cu.year_min == cu.year_max else
("({:d} - {:d})".format(cu.year_min, cu.year_max))))))
fh.write("<h3>Individual Contributors</h3>\n\n")
for author, cu in sorted_authors.items():
fh.write("{:s}, {:,d} {:s} {:s}<br />\n".format(
author if use_email else author.split("<", 1)[0].rstrip(),
cu.commit_total,
commit_word[cu.commit_total > 1],
(
"" if not is_main_credits else (
"- {:d}".format(cu.year_min) if cu.year_min == cu.year_max else
"({:d} - {:d})".format(cu.year_min, cu.year_max)
)
),
))
# -------------------------------------------------------------------------
# Companies, hard coded
if is_main_credits:
file.write("<h3>Contributions from Companies & Organizations</h3>\n")
file.write("<p>\n")
for line in contrib_companies:
file.write("{:s}<br />\n".format(line))
file.write("</p>\n")
# -------------------------------------------------------------------------
# Companies, hard coded
if is_main_credits:
fh.write("<h3>Contributions from Companies & Organizations</h3>\n")
fh.write("<p>\n")
for line in contrib_companies:
fh.write("{:s}<br />\n".format(line))
fh.write("</p>\n")
import datetime
now = datetime.datetime.now()
fn = __file__.split("\\")[-1].split("/")[-1]
file.write(
"<p><center><i>Generated by '{:s}' {:d}/{:d}/{:d}</i></center></p>\n".format(
fn, now.year, now.month, now.day
))
import datetime
now = datetime.datetime.now()
fn = os.path.basename(__file__)
fh.write(
"<p><center><i>Generated by '{:s}' {:d}/{:d}/{:d}</i></center></p>\n".format(
fn, now.year, now.month, now.day,
))
def write(
self,
filepath: str,
*,
is_main_credits: bool = True,
contrib_companies: Tuple[str, ...] = (),
sort: str = "name",
use_email: bool = False,
) -> None:
with open(filepath, 'w', encoding="ascii", errors='xmlcharrefreplace') as fh:
self.write_object(
fh,
is_main_credits=is_main_credits,
contrib_companies=contrib_companies,
sort=sort,
use_email=use_email,
)
def argparse_create() -> argparse.ArgumentParser:
# When --help or no args are given, print this help
# When `--help` or no arguments are given, print this help.
usage_text = "Review revisions."
epilog = "This script is used to generate credits"
@ -306,7 +237,8 @@ def argparse_create() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=usage_text, epilog=epilog)
parser.add_argument(
"--source", dest="source_dir",
"--source",
dest="source_dir",
metavar='PATH',
required=True,
help="Path to git repository",
@ -320,11 +252,21 @@ def argparse_create() -> argparse.ArgumentParser:
)
parser.add_argument(
"--sort", dest="sort",
"--sort",
dest="sort",
metavar='METHOD',
required=False,
help="Sort credits by 'name' (default) or 'commit'",
)
# Don't include email addresses, they're useful for identifying developers but better not include in credits.
# Even though it's publicly available, developers may not want this so easily accessible on the credits page.
parser.add_argument(
"--use-email",
dest="use_email",
required=False,
action='store_true',
help="Include the email address in the credits (useful for debugging/investigating issues)",
)
parser.add_argument(
"--jobs",
dest="jobs",
@ -351,8 +293,7 @@ def main() -> None:
# 1 liners for in html syntax
contrib_companies = (
"<b>Unity Technologies</b> - FBX Exporter",
"<b>BioSkill GmbH</b> - H3D compatibility for X3D Exporter, "
"OBJ Nurbs Import/Export",
"<b>BioSkill GmbH</b> - H3D compatibility for X3D Exporter, OBJ Nurbs Import/Export",
"<b>AutoCRC</b> - Improvements to fluid particles, vertex color baking",
"<b>Adidas</b> - Principled BSDF shader in Cycles",
"<b>AMD</b> - Cycles HIP GPU rendering, CPU optimizations",
@ -376,10 +317,13 @@ def main() -> None:
credits.process(GitCommitIter(args.source_dir, commit_range), jobs=jobs)
credits.write("credits.html",
is_main_credits=True,
contrib_companies=contrib_companies,
sort=sort)
credits.write(
"credits.html",
is_main_credits=True,
contrib_companies=contrib_companies,
sort=sort,
use_email=args.use_email,
)
print("Written: credits.html")

@ -0,0 +1,710 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
The intent of this map is to be able to a single canonical author
for every ``Name <email>`` combination.
"""
__all__ = (
"canonical_author_map",
)
from typing import (
Dict,
)
def canonical_author_map() -> Dict[str, str]:
"""
Return a map of authors to their canonical author & email.
"""
# NOTE:
# - Keys must be ordered,
# - Values must also be ordered.
author_map_valid_to_invalid = {
"Aaron Carlisle <carlisle.aaron00@gmail.com>": (
"Aaron <Aaron>",
"Aaron <carlisle.b3d@gmail.com>",
"Aaron Carlisle <Blendify>",
"Aaron Carlisle <blendify@noreply.localhost>",
"Aaron Carlisle <carlisle.b3d@gmail.com>",
"Your Name <Aaron Carlisle>",
),
"Alan Troth <Al>": (
"Alan <Al@AlanTroth.me.uk>",
),
"Alaska <alaskayou01@gmail.com>": (
"Alaska <Alaska>",
"Alaska <alaska@noreply.localhost>",
),
"Aleksandr Zinovev <roaoao@gmail.com>": (
"raa <roaoao@gmail.com>",
),
"Alexander Gavrilov <angavrilov@gmail.com>": (
"Alexander Gavrilov <alexander.gavrilov@jetbrains.com>",
),
"Alexander Romanov <a.romanov@blend4web.com>": (
"Romanov Alexander <a.romanov@blend4web.com>",
),
"Alexandr Kuznetsov <ak3636@nyu.edu>": (
"Alexandr Kuznetsov <kuzsasha@gmail.com>",
),
"Aleš Jelovčan <frogstomp>": (
"frogstomp <email.frogstomp@gmail.com>",
),
"Almaz Shinbay <almaz.shinbay@nu.edu.kz>": (
"Almaz-Shinbay <almaz-shinbay@noreply.localhost>",
),
"Amélie Fondevilla <amelie.fondevilla@les-fees-speciales.coop>": (
"Amelie <amelie.fondevilla@les-fees-speciales.coop>",
"Amelie Fondevilla <afonde>",
"Amelie Fondevilla <amelie.fondevilla@les-fees-speciales.coop>",
"Amelie Fondevilla <amelie@les-fees-speciales.coop>",
"Amelie Fondevilla <amelief@noreply.localhost>",
"Amélie Fondevilla <afonde>",
),
"Andrii Symkin <pembem22>": (
"Andrii <pembem22>",
"pembem22 <pembem22>",
),
"Ankit Meel <ankitjmeel@gmail.com>": (
"Ankit <ankitm>",
"Ankit Meel <ankitm>",
),
"Anthony Edlin <akrashe@gmail.com>": (
"Anthony Edlin <krash>",
),
"Anthony Roberts <anthony.roberts@linaro.org>": (
"Anthony <anthony-roberts@noreply.localhost>",
),
"Antonio Vazquez <blendergit@gmail.com>": (
"Antonio Vazquez <blendergit@gmail.com>",
"Antonio Vazquez <antoniov>",
"Antonio Vazquez <antoniov@noreply.localhost>",
"Antonioya <blendergit@gmail.com>",
),
"Antony Riakiotakis <kalast@gmail.com>": (
"Antony Ryakiotakis <kalast@gmail.com>",
),
"Aras Pranckevicius <aras@nesnausk.org>": (
"Aras Pranckevicius <aras_p>",
),
"Bastien Montagne <bastien@blender.org>": (
"Bastien Montagne (@mont29) <>",
"Bastien Montagne <b.mont29@gmail.com>",
"Bastien Montagne <mont29>",
"Bastien Montagne <montagne29@wanadoo.fr>",
"bastien <bastien@blender.org>",
"mont29 <montagne29@wanadoo.fr>",
),
"Bogdan Nagirniak <bodyan@gmail.com>": (
"Bogdan Nagirniak <bnagirniak>",
),
"Brecht Van Lommel <brecht@blender.org>": (
"Brecht Van Lommel <brecht>",
"Brecht Van Lommel <brecht@noreply.localhost>",
"Brecht Van Lommel <brecht@solidangle.com>",
"Brecht Van Lommel <brechtvanlommel@gmail.com>",
"Brecht Van Lommel <brechtvanlommel@pandora.be>",
"Brecht Van Lömmel <brechtvanlommel@gmail.com>",
"Brecht van Lommel <brechtvanlommel@gmail.com>",
"recht Van Lommel <brecht@blender.org>",
),
"Brendon Murphy <meta.androcto1@gmail.com>": (
"meta-androcto <meta.androcto1@gmail.com>",
),
"Brian Savery <brian.savery@gmail.com>": (
"Brian Savery (AMD) <briansavery@noreply.localhost>",
"Brian Savery <bsavery>",
),
"Campbell Barton <campbell@blender.org>": (
"Campbell Barton <campbellbarton>",
"Campbell Barton <ideasman42@gmail.com>",
),
"Casey Bianco-Davis <caseycasey739@gmail.com>": (
"casey bianco-davis <caseycasey739@gmail.com>",
),
"Charles Wardlaw <cwardlaw@nvidia.com>": (
"Charles Wardlaw (@CharlesWardlaw) <>",
"Charles Wardlaw <charleswardlaw@noreply.localhost>",
"Charles Wardlaw <kattkieru@users.noreply.github.com>",
),
"Charlie Jolly <mistajolly@gmail.com>": (
"Charlie Jolly <charlie>",
"charlie <mistajolly@gmail.com>",
),
"Christian Brinkmann <hallo@zblur.de>": (
"christian brinkmann <>",
),
"Christian Rauch <Rauch.Christian@gmx.de>": (
"Christian Rauch <christian.rauch>",
),
"Christoph Lendenfeld <chris.lenden@gmail.com>": (
"Christoph Lendenfeld <ChrisLend>",
"Christoph Lendenfeld <chris.lend@gmx.at>",
"Christoph Lendenfeld <chrislend@noreply.localhost>",
),
"Clément Foucault <foucault.clem@gmail.com>": (
"Clment Foucault <fclem>",
"Clment Foucault <foucault.clem@gmail.com>",
"Clément Foucault <foucault.clem@gmail.com>",
"Clément <clement@Clements-MacBook-Pro.local>",
"Clément Foucault <fclem>",
"Clément Foucault <fclem@noreply.localhost>",
"fclem <foucault.clem@gmail.com>",
),
"Colin Basnett <cmbasnett@gmail.com>": (
"Colin Basnett <cmbasnett>",
"Colin Basnett <cmbasnett@noreply.localhost>",
),
"Colin Marmond <kdblender@gmail.com>": (
"Colin <Kdaf>",
"Colin Marmond <Kdaf>",
"Colin Marmond <kdaf@noreply.localhost>",
"Colin Marmont <Kdaf>",
),
"Dalai Felinto <dalai@blender.org>": (
"Dalai Felinto <dfelinto>",
"Dalai Felinto <dfelinto@gmail.com>",
"Dalai Felinto <dfelinto@noreply.localhost>",
),
"Damien Picard <dam.pic@free.fr>": (
"Damien Picard <pioverfour>",
"Damien Picard <pioverfour@noreply.localhost>",
),
"Daniel Salazar <zanqdo@gmail.com>": (
"Daniel Salazar <zanqdo>",
"Daniel Salazar <zanqdo@noreply.localhost>",
"Daniel Santana <dgsantana>",
"ZanQdo <zanqdo@gmail.com>",
"zanqdo <zanqdo@gmail.com>",
),
"David Friedli <hlorus>": (
"David <hlorus>",
),
"Demeter Dzadik <Mets>": (
"demeterdzadik@gmail.com <demeterdzadik@gmail.com>",
),
"Diego Borghetti <bdiego@gmail.com>": (
"Diego Hernan Borghetti <bdiego@gmail.com>",
),
"Dilith Jayakody <dilithjay@gmail.com>": (
"dilithjay <dilithjay@gmail.com>",
),
"Dmitry Dygalo <noreply@developer.blender.org>": (
"Dmitry Dygalo <>",
),
"Dontsov Valentin <@blend4web.com>": (
"Dotsnov Valentin <invalid@nodomain.com>",
),
"EitanSomething <eitant13@gmail.com>": (
"Eitan <EitanSomething>",
"EitanSomething <EitanSomething>",
),
"Ejner Fergo <ejnersan@gmail.com>": (
"Ejner Fergo <ejnersan>",
),
"Erik Abrahamsson <ecke101@gmail.com>": (
"Eric Abrahamsson <ecke101@gmail.com>",
"Erick Abrahammson <ecke101@gmail.com>",
"Erik <ecke101@gmail.com>",
"Erik Abrahamsson <erik85>",
"Erik Abrahamsson <erik85@noreply.localhost>",
),
"Ethan Hall <Ethan1080>": (
"Ethan-Hall <Ethan1080>",
),
"Fabian Schempp <fabianschempp@googlemail.com>": (
"Fabian Schempp <fabian_schempp>",
),
"Falk David <falk@blender.org>": (
"Falk David <falkdavid@gmx.de>",
"Falk David <filedescriptor>",
"Falk David <filedescriptor@noreply.localhost>",
"filedescriptor <falkdavid@gmx.de>",
),
"Francesco Siddi <francesco@blender.org>": (
"Francesco Siddi <francesco.siddi@gmail.com>",
"Francesco Siddi <fsiddi>",
),
"Fynn Grotehans <fynngr@noreply.localhost>": (
"Fynn Grotehans <68659993+Fynn-G@users.noreply.github.com>",
"Fynn Grotehans <TheFynn>",
),
"Félix <Miadim>": (
"Flix <Miadim>",
),
"Gaia Clary <gaia.clary@machinimatrix.org>": (
"Gaia Clary <gaiaclary>",
"gaiaclary <gaia.clary@machinimatrix.org>",
),
"Germano Cavalcante <germano.costa@ig.com.br>": (
"Germano <germano.costa@ig.com.br>",
"Germano Cavalcante <grmncv@gmail.com>",
"Germano Cavalcante <mano-wii>",
"Germano Cavalcante <mano-wii@noreply.localhost>",
"Germano Cavalcantemano-wii <germano.costa@ig.com.br>",
"mano-wii <germano.costa@ig.com.br>",
"mano-wii <grmncv@gmail.com>",
),
"Guillermo S. Romero <gsr.b3d@infernal-iceberg.com>": (
"gsr b3d <gsr.b3d@infernal-iceberg.com>",
),
"Guillermo Venegas <guillermovcra@gmail.com>": (
"Guillermo <guillermovcra@gmail.com>",
"guishe <guillermovcra@gmail.com>",
),
"Habib Gahbiche <habibgahbiche@gmail.com>": (
"Habib Gahbiche <zazizizou>",
),
"Hans Goudey <h.goudey@me.com>": (
"Hans Goudey <HooglyBoogly>",
"Hans Goudey <hooglyboogly@noreply.localhost>",
),
"Harley Acheson <harley.acheson@gmail.com>": (
"Harley Acheson <harley>",
"Harley Acheson <harley@noreply.localhost>",
),
"Henrik Dick <hen-di@web.de>": (
"Henrik Dick (weasel) <>",
"Henrik Dick <weasel>",
),
"Himanshi Kalra <himanshikalra98@gmail.com>": (
"Himanshi Kalra <calra>",
),
"Howard Trickey <howard.trickey@gmail.com>": (
"howardt <howard.trickey@gmail.com>",
),
"Hristo Gueorguiev <prem.nirved@gmail.com>": (
"Hristo Gueorguiev <>",
),
"IRIE Shinsuke <irieshinsuke@yahoo.co.jp>": (
"Irie Shinsuke <irieshinsuke@yahoo.co.jp>",
),
"Iliya Katueshenock <modormoder@gmail.com>": (
"Iliay Katueshenock <Moder>",
"Iliya Katueshenock <Moder>",
"Iliya Katueshenock <mod_moder@noreply.localhost>",
"MOD <Moder>",
"illua1 <modormoder@gmail.com>",
),
"Inês Almeida <britalmeida@gmail.com>": (
"Ines Almeida <britalmeida@gmail.com>",
"brita <britalmeida@gmail.com>",
),
"Ish Bosamiya <ish_bosamiya>": (
"Ish Bosamiya <ishbosamiya>",
"Ish Bosamiya <ishbosamiya@gmail.com>",
),
"Iyad Ahmed <iyadahmed430@gmail.com>": (
"Iyad Ahmed <iyadahmed2001>",
),
"Jacques Lucke <jacques@blender.org>": (
"Jacques Lucke <jacqueslucke@noreply.localhost>",
"Jacques Lucke <mail@jlucke.com>",
),
"Jason Fielder <jason-fielder@noreply.localhost>": (
"Jason Fielder <jason_apple>",
),
"Jens Ole Wund <bjornmose@gmx.net>": (
"bjornmose <bjornmose@gmx.net>",
),
"Jens Verwiebe <info@jensverwiebe.de>": (
"Jens <info@jensverwiebe.de>",
"Jens Verwiebe <jensverwiebe@jens-macpro.fritz.box>",
"jensverwiebe <info@jensverwiebe.de>",
),
"Jeroen Bakker <jeroen@blender.org>": (
"Jeroen Bakker <88891617+jeroen-blender@users.noreply.github.com>",
"Jeroen Bakker <j.bakker@atmind.nl>",
"Jeroen Bakker <jbakker>",
),
"Jesse Yurkovich <jesse.y@gmail.com>": (
"Jesse Y <deadpin>",
"Jesse Yurkovich <deadpin>",
),
"Johannes J <johannesj@noreply.localhost>": (
"Johannes J <johannesj>",
),
"Johnny Matthews <johnny.matthews@gmail.com>": (
"Johnny Matthews (guitargeek) <johnny.matthews@gmail.com>",
"Johnny Matthews <guitargeek>",
"guitargeek <johnny.matthews@gmail.com>",
),
"Jorijn de Graaf <bonj@noreply.localhost>": (
"Jorijn de Graaf <bonj>",
"bonj <jorijndegraaf@gmail.com>",
),
"Joseph Eagar <joeedh@gmail.com>": (
"Joe Eagar <joeedh@gmail.com>",
"Joseph Eagar <joeedh>",
"Joseph Eagar <josepheagar@noreply.localhost>",
),
"Josh Maros <joshm-2@noreply.localhost>": (
"Josh Maros <60271685+joshua-maros@users.noreply.github.com>",
"joshua-maros <60271685+joshua-maros@users.noreply.github.com>",
),
"Julian Eisel <julian@blender.org>": (
"Julian Eisel <Severin>",
"Julian Eisel <eiseljulian@gmail.com>",
"Julian Eisel <julian@linux-chl2.site>",
"Julian Eisel <julian_eisel@web.de>",
"Julian Eisel <julianeisel@Julians-MacBook-Pro.local>",
"Julian Eisel <julianeisel@noreply.localhost>",
"Severin <eiseljulian@gmail.com>",
"Severin <julian_eisel@web.de>",
"julianeisel <julian_eisel@web.de>",
),
"Jun Mizutani <jmztn@noreply.localhost>": (
"Jun Mizutani <jmztn>",
),
"Jörg Müller <nexyon@gmail.com>": (
"Joerg Mueller <nexyon@gmail.com>",
),
"Jürgen Herrmann <shadowrom@me.com>": (
"Juergen Herrmann <shadowrom@me.com>",
),
"Kaspian Jakobsson <kaspian.jakobsson@gmail.com>": (
"\x96kaspian.jakobssongmail.com <kaspian.jakobsson@gmail.com>",
),
"Kevin C. Burke <kevincburke@noreply.localhost>": (
"Kevin C. Burke <blastframe>",
),
"Kévin Dietrich <kevin.dietrich@mailoo.org>": (
"Kevin Dietrich <kevin.dietrich@mailoo.org>",
"Kévin Dietrich <kevindietrich>",
),
"Leon Schittek <leon.schittek@gmx.net>": (
"Leon Leno <lone_noel>",
"Leon Schittek <lone_noel>",
"Leon Schittek <lone_noel@noreply.localhost>",
),
"Lictex Steaven <lictex_>": (
"lictex_ <lictex_>",
),
"Luca Rood <dev@lucarood.com>": (
"Luca Rood <LucaRood>",
),
"Lucas Tadeu Teixeira <lucas@lucastadeu.com>": (
"Lucas Tadeu <yup_lucas@noreply.localhost>",
),
"Lukas Stockner <lukas.stockner@freenet.de>": (
"Lukas Stockner <lukasstockner97>",
),
"Lukas Tönne <lukas@blender.org>": (
"Lukas Toenne <lukas.toenne@googlemail.com>",
"Lukas Tönne <lukas.toenne@gmail.com>",
"Lukas Tönne <lukastonne@noreply.localhost>",
),
"Léo Depoix <PiloeGAO>": (
"PiloeGAO <leonumerique@gmail.com>",
),
"Mai Lavelle <mai.lavelle@gmail.com>": (
"Mai Lavelle <lavelle@gmail.com>",
),
"Mal Duffin <malachyduffin@gmail.com>": (
"Mal Duffin <mal_cando>",
),
"Manuel Castilla <manzanillawork@gmail.com>": (
"Manuel Castilla <manzanilla>",
),
"Marc Chehab <marcchehab@protonmail.ch>": (
"Marc Chéhab <marcchehab@noreply.localhost>",
"Marc Chéhab <marcluzmedia>",
),
"Martijn Berger <mberger@denc.com>": (
"Martijn Berger <martijn.berger@gmail.com>",
"Martijn Berger <mberger@denc.nl>",
"Martijn Berger <mberger@martijns-mbp.lan>",
),
"Martijn Versteegh <martijn@aaltjegron.nl>": (
"Martijn Versteegh <Baardaap>",
"Martijn Versteegh <baardaap@noreply.localhost>",
"Martijn Versteegh <blender@aaltjegron.nl>",
),
"Matias Mendiola <matias.mendiola@gmail.com>": (
"Matias Mendiola <mendio>",
),
"Matt Heimlich <matt.heimlich@gmail.com>": (
"Matt Heimlich <m9105826>",
),
"Matteo F. Vescovi <mfvescovi@gmail.com>": (
"Matteo F. Vescovi <mfv>",
),
"Max Schlecht <bobbe@noreply.localhost>": (
"Max Schlecht <Bobbe>",
),
"Maxime Casas <maxime_casas@orange.fr>": (
"Maxime Casas <troopy28>",
),
"Michael Jones <michael_p_jones@apple.com>": (
"Michael Jones (Apple) <michael-jones@noreply.localhost>",
"Michael Jones <michael_jones>",
),
"Michael Kowalski <makowalski@nvidia.com>": (
"Michael Kowalski <makowalski>",
"Michael Kowalski <makowalski@noreply.localhost>",
),
"Miguel Pozo <pragma37@gmail.com>": (
"Miguel Pozo <pragma37>",
"Miguel Pozo <pragma37@noreply.localhost>",
),
"Mikhail Matrosov <ktdfly>": (
"Mikhail <ktdfly>",
"Mikhail Matrosov <kdtfly>",
),
"Monique Dewanchand <m.dewanchand@atmind.nl>": (
"Monique Dewanchand <mdewanchand>",
),
"Nate Rupsis <nrupsis@gmail.com>": (
"Nate Rupsis <C-Nathaniel.Rupsis@charter.com>",
"Nate Rupsis <C-nathaniel.rupsis@charter.com>",
"Nate Rupsis <nrupsis>",
"Nate Rupsis <nrupsis@noreply.localhost>",
),
"Nathan Craddock <nzcraddock@gmail.com>": (
"Nathan Craddock <Zachman>",
),
"Nathan Letwory <nathan@blender.org>": (
"Nathan Letwory <jesterking>",
"Nathan Letwory <nathan@letworyinteractive.com>",
"Nathan Letwory <nathan@mcneel.com>",
),
"Nathan Vegdahl <cessen@cessen.com>": (
"Nathan Vegdahl <cessen>",
),
"Nicholas Bishop <nicholasbishop@gmail.com>": (
"Nicholas Bishop <nicholas.bishop@floored.com>",
),
"Nicholas Rishel <rishel.nick@gmail.com>": (
"Nicholas Rishel <nicholas_rishel>",
),
"Nick Milios <semaphore>": (
"milios <n_mhlios@hotmail.com>",
),
"Nikita Sirgienko <nikita.sirgienko@intel.com>": (
"Nikita Sirgienko <sirgienko>",
),
"Omar Emara <mail@OmarEmara.dev>": (
"Omar Emara <OmarSquircleArt>",
"OmarSquircleArt <mail@OmarEmara.dev>",
"OmarSquircleArt <omar.squircleart@gmail.com>",
),
"Pablo Dobarro <pablodp606@gmail.com>": (
"Pablo Dobarro <pablodp606>",
),
"Pablo Vazquez <pablo@blender.org>": (
"Pablo Vazquez <contact@pablovazquez.art>",
"Pablo Vazquez <pablovazquez>",
"Pablo Vazquez <venomgfx@gmail.com>",
),
"Pascal Schoen <pascal.schoen@adidas-group.com>": (
"Pascal Schön <VanCantus>",
),
"Patrick Busch <xylvier@noreply.localhost>": (
"Patrick Busch <Xylvier>",
),
"Patrick Mours <pmours@nvidia.com>": (
"Patrick Mours <pmoursnv@noreply.localhost>",
),
"Paul Golter <paulgolter>": (
"Paul Golter <pullpullson>",
),
"Philipp Oeser <philipp@blender.org>": (
"Philipp Oeser <info@graphics-engineer.com>",
"Philipp Oeser <lichtwerk>",
"Philipp Oeser <lichtwerk@noreply.localhost>",
"Philipp Oeser <noreply@developer.blender.org>",
"Philipp Oeser <poeser@posteo.de>",
),
"Pratik Borhade <pratikborhade302@gmail.com>": (
"Pratik Borhade <PratikPB2123>",
),
"Ray Molenkamp <github@lazydodo.com>": (
"Lazydodo <github@lazydodo.com>",
"Ray Molenkamp <LazyDodo>",
"Ray molenkamp <LazyDodo>",
"Ray molenkamp <lazydodo@noreply.localhost>",
"lazydodo <github@lazydodo.com>",
),
"Red Mser <RedMser>": (
"RedMser <RedMser>",
"RedMser <redmser.jj2@gmail.com>",
),
"Richard Antalik <richardantalik@gmail.com>": (
"Richard Antalik <ISS>",
"Richard Antalik <iss@noreply.localhost>",
),
"Robert Guetzkow <gitcommit@outlook.de>": (
"Robert Guetzkow <rjg>",
),
"Robin Hohnsbeen <robin@hohnsbeen.de>": (
"Robin Hohnsbeen <robin-4@noreply.localhost>",
),
"Sahar A. Kashi <sahar.alipourkashi@amd.com>": (
"Sahar A. Kashi <salipour@noreply.localhost>",
"salipourto <sahar.alipourkashi@amd.com>",
),
"Sebastian Herholz <sebastian.herholz@intel.com>": (
"Sebastian Herholz <Sebastian.Herholz@gmail.com>",
"Sebastian Herholz <sherholz>",
"Sebastian Herholz <sherholz@noreply.localhost>",
"Sebastian Herhoz <sebastian.herholz@intel.com>",
),
"Sebastian Koenig <sebastiankoenig@posteo.de>": (
"Sebastian Koenig <sebastian_k>",
"Sebastian Koenig <sebastian_k@gmail.com>",
),
"Sebastian Parborg <darkdefende@gmail.com>": (
"Sebastian Parborg <zeddb>",
"Sebastian Parborg <zeddb@noreply.localhost>",
"Sebastian Ramacher <sramacher@debian.org>",
),
"Sergey Sharybin <sergey@blender.org>": (
"Sergey Sharybin <sergey.vfx@gmail.com>",
"Sergey Sharybin <sergey>",
"Sergey Sharybin <sergey@noreply.localhost>",
"blender <sergey.vfx@gmail.com>",
),
"Shane Ambler <Shane@ShaneWare.Biz>": (
"Shane Ambler <shaneambler@noreply.localhost>",
),
"Shashank Shekhar <secondary.cmdr2@gmail.com>": (
"Shashank Shekhar <cmdr2>",
),
"Siddhartha Jejurkar <f20180617@goa.bits-pilani.ac.in>": (
"Siddhartha Jejurkar <sidd017>",
),
"Sietse Brouwer <sietse@hetvrijeoog.nl>": (
"Sietse Brouwer <SietseB>",
),
"Simon G <intrigus>": (
"Simon <intrigus>",
),
"Sonny Campbell <sonny.campbell@unity3d.com>": (
"DESKTOP-ON14TH5\\Sonny Campbell <sonny.campbell@unity3d.com>",
"Sonny Campbell (@SonnyCampbell_Unity) <>",
"Sonny Campbell <SonnyCampbell_Unity>",
"Sonny Campbell <sonnycampbell_unity@noreply.localhost>",
),
"Stefan Werner <stefan.werner@intel.com>": (
"Stefan Werner <stefan.werner@tangent-animation.com>",
"Stefan Werner <stefan@keindesign.de>",
"Stefan Werner <stefan_werner@noreply.localhost>",
"Stefan Werner <stewreo@gmail.com>",
"Stefan Werner <swerner@smithmicro.com>",
"Werner, Stefan <stefan.werner@intel.com>",
),
"Stephan Seitz <theHamsta>": (
"Stephan <theHamsta>",
),
"Sybren A. Stüvel <sybren@blender.org>": (
"Sybren A. StÃ\x83¼vel <sybren@stuvel.eu>",
"Sybren A. Stüvel <sybren>",
"Sybren A. Stüvel <sybren@stuvel.eu>",
),
"Thomas Dinges <thomas@blender.org>": (
"Thomas Dinges <blender@dingto.org>",
"Thomas Dinges <dingto>",
),
"Thomas Szepe <HG1_public@gmx.net>": (
"HG1 <HG1_public@gmx.net>",
),
"Tom Edwards <contact@steamreview.org>": (
"Tom Edwards <artfunkel>",
),
"Tristan Porteries <republicthunderbolt9@gmail.com>": (
"Porteries Tristan <republicthunderbolt9@gmail.com>",
),
"Troy Sobotka <troy.sobotka@gmail.com>": (
"Troy Sobotka <sobotka>",
),
"Tuomo Keskitalo <tuomo.keskitalo@iki.fi>": (
"Tuomo Keskitalo <tkeskita>",
),
"Ulysse Martin <you.le@live.fr>": (
"Ulysse Martin <youle>",
),
"Vitor Boschi da Silva <vitorboschi>": (
"Vitor Boschi <vitorboschi>",
),
"Vuk Gardašević <lijenstina>": (
"Vuk GardaÅ¡eviÄ\x87 <lijenstina>",
),
"Wayde Moss <wbmoss_dev@yahoo.com>": (
"Wayde Moss <GuiltyGhost>",
),
"Weizhen Huang <weizhen@blender.org>": (
"RiverIntheSky <itsnotrj@hotmail.com>",
"Weizhen Huang <itsnotrj@gmail.com>",
"Weizhen Huang <weizhen@noreply.localhost>",
),
"Welp <jtf515@gmail.com>": (
"Jake <Welp>",
),
"William Leeson <william@blender.org>": (
"William Leeson <leesonw>",
"William Leeson <william.leeson@gmail.com>",
),
"William Reynish <william@reynish.com>": (
"William Reynish <billrey>",
"William Reynish <billrey@me.com>",
"William Reynish <billreynish>",
),
"Willian Padovani Germano <wpgermano@gmail.com>": (
"ianwill <wpgermano@gmail.com>",
),
"Xavier Hallade <xavier.hallade@intel.com>": (
"Xavier Hallade <xavierh@noreply.localhost>",
),
"Yann Lanthony <yann-lty>": (
"@yann-lty <>",
),
"Yiming Wu <xp8110@outlook.com>": (
"ChengduLittleA <xp8110@outlook.com>",
"YimingWu <NicksBest>",
"YimingWu <chengdulittlea@noreply.localhost>",
"YimingWu <xp8110@outlook.com>",
"YimingWu <xp8110t@outlook.com>",
),
"jon denning <gfxcoder@gmail.com>": (
"Jon Denning <gfxcoder>",
),
"nBurn <nbwashburn@gmail.com>": (
"nBurn <nBurn>",
),
"nutti <nutti.metro@gmail.com>": (
"nutti <Nutti>",
),
"ok_what <ip1149a@gmail.com>": (
"ok what <ok_what>",
),
}
# Some validation, ensure ordered, consistent to help with maintenance.
keys = list(author_map_valid_to_invalid.keys())
for i, (key, key_ordered) in enumerate(zip(keys, sorted(keys))):
if key != key_ordered:
raise RuntimeError((
"Names unordered: {:d}\n"
" {:s} ~ (found)\n"
" {:s} ~ (expected)\n"
""
).format(i, key, key_ordered))
for i, (key, value) in enumerate(author_map_valid_to_invalid.items()):
if value != tuple(sorted(value)):
raise RuntimeError("Name values: {:s} at index {:d} is not ordered".format(key, i))
if len(set(value)) != len(value):
raise RuntimeError("Name values: {:s} at index {:d} contains duplicate values".format(key, i))
if key in value:
raise RuntimeError("Name values: {:s} at index {:d} contains the key in the values body".format(key, i))
table = {}
for key, values in author_map_valid_to_invalid.items():
for value_old in values:
table[value_old] = key
return table

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
The intent of this map is to be able to a single canonical author
for every ``Name <email>`` combination.
"""
__all__ = (
"sha1_authors_map",
)
from typing import (
Dict,
Tuple,
)
def sha1_authors_map() -> Dict[bytes, Tuple[str, ...]]:
"""
Return SHA1 to authors map.
"""
# Mapping from a commit hash to additional authors.
# Fully overwrite authors gathered from git commit info.
# Intended usage: Correction of info stored in git commit itself.
# Note that the names of the authors here are assumed fully valid and usable as-is.
return {
# Format: {full_git_hash: (tuple, of, authors),}.
# Author was: `blender <blender@localhost.localdomain>`.
b"ba3d49225c9ff3514fb87ae5d692baefe5edec30": ("Sergey Sharybin <sergey@blender.org>", ),
# Author was: `Author Name <email@address.com>`.
b"4b6a4b5bc25bce10367dffadf7718e373f81f299": ("Antonio Vazquez <blendergit@gmail.com>", ),
}

@ -7,6 +7,7 @@
import os
import subprocess
import datetime
import re
from typing import (
List,
@ -16,6 +17,9 @@ from typing import (
)
_GIT_COMMIT_COAUTHORS_RE = re.compile(r"^Co-authored-by:[ \t]*([^\n]+)$", re.MULTILINE)
class GitCommit:
__slots__ = (
"sha1",
@ -24,7 +28,6 @@ class GitCommit:
# cached values
"_author",
"_email",
"_date",
"_body",
"_files",
@ -37,7 +40,6 @@ class GitCommit:
self._git_dir = git_dir
self._author: Optional[str] = None
self._email: Optional[str] = None
self._date: Optional[datetime.datetime] = None
self._body: Optional[str] = None
self._files: Optional[List[bytes]] = None
@ -50,7 +52,6 @@ class GitCommit:
(except for diff as it's significantly larger than other members).
"""
self.author
self.email
self.date
self.body
self.files
@ -99,19 +100,20 @@ class GitCommit:
def author(self) -> str:
ret = self._author
if ret is None:
content = self._log_format("%an")[:-1]
content = self._log_format("%an <%ae>")[:-1]
ret = content.decode("utf8", errors="ignore")
self._author = ret
return ret
@property
def email(self) -> str:
ret = self._email
if ret is None:
content = self._log_format("%ae")[:-1]
ret = content.decode("utf8", errors="ignore")
self._email = ret
return ret
def co_authors(self) -> List[str]:
authors = []
for author in _GIT_COMMIT_COAUTHORS_RE.findall(self.body):
if not ("<" in author and ">" in author):
# Always follow `Name <>` spec, even when no email is given.
author = author + " <>"
authors.append(author)
return authors
@property
def date(self) -> datetime.datetime: