You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by tv...@apache.org on 2021/02/04 07:46:00 UTC

[buildstream] 01/02: contrib/update_committers: Add script to update committers

This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch tlater/auto-committers
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit d7b1fd1755bf21f8dba6aac99c8dc4523c60d8b6
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Tue Jul 9 15:56:41 2019 +0100

    contrib/update_committers: Add script to update committers
---
 COMMITTERS.rst            |  46 ++++++-------
 contrib/update_committers | 170 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 193 insertions(+), 23 deletions(-)

diff --git a/COMMITTERS.rst b/COMMITTERS.rst
index 9b74d8f..35d5b9e 100644
--- a/COMMITTERS.rst
+++ b/COMMITTERS.rst
@@ -15,27 +15,27 @@ If you have a question or comment, it's probably best to email
 the BuildStream mailing list, rather than any of these people
 directly.
 
-+-------------------------+------------------------------------------+
-| Full Name               | Email Address                            |
-+=========================+==========================================+
-| Tristan Van Berkom      | <tristan.vanberkom@codethink.co.uk       |
-+-------------------------+------------------------------------------+
-| Juerg Billeter          | <ju...@codethink.co.uk>         |
-+-------------------------+------------------------------------------+
-| Ben Schubert            | <bs...@bloomberg.net>              |
-+-------------------------+------------------------------------------+
-| Chandan Singh           | <cs...@bloomberg.net>                 |
-+-------------------------+------------------------------------------+
-| Angelos Evripiotis      | <je...@bloomberg.net>              |
-+-------------------------+------------------------------------------+
-| James Ennis             | <ja...@codethink.co.uk>            |
-+-------------------------+------------------------------------------+
-| Tristan Maat            | <tr...@codethink.co.uk>           |
-+-------------------------+------------------------------------------+
-| Jonathan Maw            | <jo...@codethink.co.uk>           |
-+-------------------------+------------------------------------------+
-| Will Salmon             | <wi...@codethink.co.uk>            |
-+-------------------------+------------------------------------------+
-| Valentin David          | <va...@codethink.co.uk>         |
-+-------------------------+------------------------------------------+
++-------------------------+------------------------------------------+------------------------------------------+
+| Full Name               |Email Address                             |GitLab User                               |
++=========================+==========================================+==========================================+
+| Tristan Van Berkom      | <tr...@codethink.co.uk>      | tristanvb                                |
++-------------------------+------------------------------------------+------------------------------------------+
+| Juerg Billeter          | <ju...@codethink.co.uk>         | juergbi                                  |
++-------------------------+------------------------------------------+------------------------------------------+
+| Ben Schubert            | <bs...@bloomberg.net>              | BenjaminSchubert                         |
++-------------------------+------------------------------------------+------------------------------------------+
+| Chandan Singh           | <cs...@bloomberg.net>                 | cs-shadow                                |
++-------------------------+------------------------------------------+------------------------------------------+
+| Angelos Evripiotis      | <je...@bloomberg.net>              | aevri                                    |
++-------------------------+------------------------------------------+------------------------------------------+
+| James Ennis             | <ja...@codethink.co.uk>            | jennis                                   |
++-------------------------+------------------------------------------+------------------------------------------+
+| Tristan Maat            | <tr...@codethink.co.uk>           | tlater                                   |
++-------------------------+------------------------------------------+------------------------------------------+
+| Jonathan Maw            | <jo...@codethink.co.uk>           | jonathanmaw                              |
++-------------------------+------------------------------------------+------------------------------------------+
+| Will Salmon             | <wi...@codethink.co.uk>            | willsalmon                               |
++-------------------------+------------------------------------------+------------------------------------------+
+| Valentin David          | <va...@codethink.co.uk>         | valentindavid                            |
++-------------------------+------------------------------------------+------------------------------------------+
 
diff --git a/contrib/update_committers b/contrib/update_committers
new file mode 100755
index 0000000..e8fa6f2
--- /dev/null
+++ b/contrib/update_committers
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+"""A script to set gitlab committers according to COMMITTERS.rst."""
+
+import argparse
+import json
+import logging
+import pathlib
+import sys
+import re
+import urllib.request
+import urllib.parse
+
+from pprint import pformat
+from typing import Any, Dict, List, Tuple
+
+
+API_BASE = "https://gitlab.com/api/v4"
+
+
+def main():
+    """Parse CLI arguments and set up application state."""
+
+    parser = argparse.ArgumentParser(
+        description="Update gitlab committers according to COMMITTERS.rst"
+    )
+    parser.add_argument(
+        "token", type=str,
+        help="Your private access token. See https://gitlab.com/profile/personal_access_tokens."
+    )
+    parser.add_argument(
+        "committers", type=pathlib.Path,
+        help="The path to COMMITTERS.rst. Will try "
+        "to find the one in the local git repository by default.",
+        nargs="?",
+        default=find_repository_root() / "COMMITTERS.rst"
+    )
+    parser.add_argument(
+        "--repository", "-r", type=str,
+        help="The repository whose committers to set.",
+        default="BuildStream/buildstream"
+    )
+    parser.add_argument(
+        "--dry-run", "-n",
+        help="Do not update the actual member list.",
+        action="store_false"
+    )
+    parser.add_argument(
+        "--quiet", "-q",
+        help="Run quietly",
+        action="store_true"
+    )
+    parser.add_argument(
+        "--debug", "-d",
+        help="Show debug messages (WARNING: This *will* display the private token).",
+        action="store_true"
+    )
+    args = parser.parse_args()
+
+    if args.debug:
+        logging.basicConfig(level=logging.DEBUG)
+    elif not args.quiet:
+        logging.basicConfig(level=logging.INFO)
+
+    set_committers(args.repository, args.committers.read_text(), args.token, args.dry_run)
+
+
+def set_committers(repository: str, committer_file: str, token: str, commit: bool):
+    """Set GitLab members as defined in the committer_file."""
+    new_committers = [get_user_by_username(committer[2], token)
+                      for committer in parse_committer_list(committer_file)]
+    old_committers = get_project_committers(repository, token)
+
+    new_set = set(committer["id"] for committer in new_committers)
+    old_set = set(committer["id"] for committer in old_committers)
+
+    added = [committer for committer in new_committers if committer["id"] in new_set - old_set]
+    removed = [committer for committer in new_committers if committer["id"] in old_set - new_set]
+
+    logging.info("Adding:\n%s", pformat(added))
+    logging.info("Removing:\n%s", pformat(removed))
+
+    if commit:
+        for committer in added:
+            set_user_access_level(repository, committer, 40, token)
+        for committer in removed:
+            set_user_access_level(repository, committer, 30, token)
+
+
+####################################################################################################
+# Utility functions                                                                                #
+####################################################################################################
+
+class RepositoryException(Exception):
+    """An exception raised when we can't deal with the repository."""
+
+
+def find_repository_root() -> pathlib.Path:
+    """Find the root directory of a git repository, starting at cwd()."""
+    root = pathlib.Path.cwd()
+    while not any(f.name == ".git" for f in root.iterdir()):
+        if root == root.parent:
+            raise RepositoryException("'{}' is not in a git repository.".format(pathlib.Path.cwd()))
+        root = root.parent
+    return root
+
+
+def parse_committer_list(committer_text: str) -> List[Tuple[str, str, str]]:
+    """Parse COMMITTERS.rst and retrieve a map of names, email addresses and usernames."""
+    return [(committer[0].strip(), committer[1].strip(" <>"), committer[2].strip()) for committer in
+            re.findall(r"\|([^|]+)\|([^|]+)\|([^|]+)\|\n\+-", committer_text)]
+
+
+####################################################################################################
+# GitLab API                                                                                       #
+####################################################################################################
+
+def make_request_url(*args: Tuple[str], **kwargs: Dict[str, str]) -> str:
+    """Create a request url for the GitLab API."""
+    return API_BASE + "/" + "/".join(args) + "?" + urllib.parse.urlencode(kwargs, safe='@')
+
+
+def make_project_url(project: str, *args: Tuple[str], **kwargs: Dict[str, str]) -> str:
+    """Create a request url for the given project."""
+    return make_request_url("projects", urllib.parse.quote(project, safe=''), *args, **kwargs)
+
+
+def urlopen(url: str, token: str, data=None, method='GET') -> Any:
+    """Perform a paginated query to the GitLab API."""
+    page = 1
+    res = None
+    result = []
+    while not res or page:
+        req = urllib.request.Request(url=url + "&" + urllib.parse.urlencode({"page": page}),
+                                     data=data, method=method)
+        req.add_header('PRIVATE-TOKEN', token)
+        logging.debug("Making API request: %s", pformat(req.__dict__))
+        try:
+            res = urllib.request.urlopen(req)
+        except urllib.error.HTTPError as err:
+            logging.error("Could not access '%s': %s", url, err)
+            sys.exit(1)
+        result.extend(json.load(res))
+        page = res.getheader('X-Next-Page')
+    return result
+
+
+def get_project_committers(project: str, token: str) -> List[Dict]:
+    """Get a list of current committers."""
+    return [committer for committer in
+            urlopen(make_project_url(project, "members", "all"), token)
+            if committer["access_level"] >= 40]
+
+
+def get_user_by_username(username: str, token: str) -> Dict:
+    """Get a user ID from their email address."""
+    return urlopen(make_request_url("users",
+                                    username=username),
+                   token)[0]
+
+
+def set_user_access_level(project: str, user: Dict, level: int, token: str) -> Dict:
+    """Set a user's project access level."""
+    return urlopen(make_project_url(project, "members", str(user["id"]),
+                                    access_level=str(level)),
+                   token,
+                   method="PUT")
+
+
+if __name__ == '__main__':
+    main()