https://gcc.gnu.org/g:c963db2262568530628f406d3776b9e4ca3831f1
commit r17-2189-gc963db2262568530628f406d3776b9e4ca3831f1 Author: Richard Earnshaw <[email protected]> Date: Thu Jun 25 18:02:47 2026 +0100 MAINTAINERS: Add a script to create a new entry in the mainainers data This script is intended to help with the most common case of creating a new entry in the MAINTAINERS.yml data - adding the basic data for a new account. It takes care of creating the entry in the canonical location within the file and collecting the basic data. It only adds the write-after role, but it should be trivial to add any additional roles if necessary following the examples elsewhere. contrib/ChangeLog: * add-write-after.py: New file. Diff: --- contrib/add-write-after.py | 150 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/contrib/add-write-after.py b/contrib/add-write-after.py new file mode 100755 index 000000000000..9c93324bc909 --- /dev/null +++ b/contrib/add-write-after.py @@ -0,0 +1,150 @@ +#! /usr/bin/env python3 + +# Add a new Write-After account to MAINTAINERS.yml + +# Copyright (C) 2026 Free Software Foundation, Inc. +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING. If not, write to +# the Free Software Foundation, 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import pwd +import re +import sys +import unidecode +import yaml + +from optparse import OptionParser + +import maintainer_utils as maintutils + +def unilower(txt): + """return a lower-case version of txt, mapping accented characters + onto their ASCII near equivalents.""" + return unidecode.unidecode(txt).lower() + +def get_surname(name): + parts = name.split() + surname = parts[-1] + + if surname.startswith("d'"): + surname = surname[2:] + + return surname + +def ask(prompt, default, required=True): + while True: + print(f"{prompt} [{default if default else '(no default)'}]: ", end="", + flush=True) + result = sys.stdin.readline().strip() + if result != "": + return result + if default or not required: + return default + +def email_valid(e): + template = r"^[A-Za-z0-9._%+-]+@(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}$" + if re.match(template, e): + return True + return False + +def getuserdata(): + user = pwd.getpwuid(os.getuid()) + # Make an initial guess that the name and GCC account will match + # the local account name. + # GECOS fields sometime contain a comma-separated list; in that case + # we only want the first component (the user's name) + print ("Please enter the details of your Sourceware account.") + print ("Some details have been guessed, based on your local account,") + print ("but you can adjust as required.") + newuser = { + 'sn': "", + 'cn': "", + 'email': [], + 'roles': [], + 'account': "", + } + newuser['cn'] = ask("Your name", user.pw_gecos.split(",", 1)[0]) + newuser['sn'] = ask("Your surname (only used for sorting entries)", + get_surname(newuser['cn'])) + account = ask("Your sourceware account", user.pw_name) + while True: + e = ask("Primary email address", None) + if email_valid(e): + break + print ("That address does not look valid.") + newuser['email'].append(e) + while (e := ask("Additional email (return to stop)", None, + required=False)): + if email_valid(e): + newuser['email'].append(e) + else: + print ("That address does not look valid. Ignored.") + gcc_email = account + "@gcc.gnu.org" + if not gcc_email in newuser['email']: + print(f"Appending {gcc_email} to the list of email addresses") + newuser['email'].append(gcc_email) + newuser['roles'] = list() + newuser['roles'].append('WriteAfter') + newuser['account'] = account + print("If you are using a Developer Certificate of Origin (DCO)") + print("you can add appropriate email addresses here") + while (e := ask("DCO email (return to stop)", None, required=False)): + if email_valid(e): + newuser['roles'].append({'DCO': e}) + else: + print ("That address does not look valid. Ignored.") + return newuser + +def main(): + optp = OptionParser(usage="Usage: %prog [<options>] <maintainers.yml>") + optp.add_option("-o", "--output", metavar="FILE", + default=None, dest="outfilename", + help="Write to FILE instead of updating original file") + opts, args = optp.parse_args() + if len(args) != 1: + optp.print_help() + sys.exit(1) + + data = maintutils.load(args[0]) + # Check the data before we try to update it. + maintutils.validate(data) + newuser = getuserdata() + if not newuser: + return 0 + existing = [u for u in filter(lambda x: (x.get('account') + == newuser['account']), + data['users'])] + if len(existing): + print(f"Sorry that account name ({newuser['account']}) has already been used.") + print("Note, this script can only be used to add new accounts.") + return 1 + data['users'].append (newuser) + data['users'] = sorted(data['users'], + key = lambda k: (unilower(k['sn']), + unilower(k['cn']))) + if opts.outfilename and opts.outfilename != '-': + outfd = open (opts.outfilename, "w", encoding="utf-8") + elif opts.outfilename and opts.outfilename == '-': + outfd = sys.stdout + else: + outfd = open (args[0], "w", encoding="utf-8") + yaml.dump (data, outfd, allow_unicode = True, sort_keys = False) + return 0 + +if __name__ == "__main__": + sys.exit (main())
