Skip to content

Instantly share code, notes, and snippets.

@BenTheElder
Created August 10, 2017 00:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenTheElder/de2688f28069e7289cd6ba4997446c88 to your computer and use it in GitHub Desktop.
Save BenTheElder/de2688f28069e7289cd6ba4997446c88 to your computer and use it in GitHub Desktop.
find contributors that are not members of an organization
#!/usr/bin/env python
from __future__ import print_function
import re
import json
import argparse
from collections import defaultdict
import requests
parser = argparse.ArgumentParser(
description="find non-members of a GitHub org by contributions to repos")
parser.add_argument("--token", nargs="?", type=str, help="GitHub API token")
parser.add_argument("--org", nargs="?", type=str, default="kubernetes",
help="the GitHub org to find non-member contirbutions for")
def get_pagination(response):
if "Link" not in response.headers:
return None
parts = response.headers["Link"].split(",")
res = {}
for part in parts:
match = re.search('<(.*)>; rel="(.*)"', part)
if match:
res[match.group(2)] = match.group(1)
return res
def get_all_paginated(url, token=None):
res = []
while True:
headers = {"Accept": "application/vnd.github.v3+json"}
if token is not None:
headers["Authorization"] = "token "+token
r = requests.get(url, headers=headers, params={"per_page": "100"})
res.extend(r.json())
pagination = get_pagination(r)
if pagination is None or "next" not in pagination:
break
url = pagination["next"]
return res
def get_members(org, token=None):
url = "https://api.github.com/orgs/%s/members" % org
return get_all_paginated(url, token)
def get_contributors(repo, token=None):
url = "https://api.github.com/repos/%s/stats/contributors" % repo
return get_all_paginated(url, token)
def get_repos(org, token=None):
url = "https://api.github.com/orgs/%s/repos" % org
return get_all_paginated(url, token)
def main(args):
org = "kubernetes"
members_json = get_members(org, args.token)
members = set(m["login"] for m in members_json)
repos_json = get_repos(org, args.token)
repos = [r["full_name"] for r in repos_json]
# TODO: this is naive, we're just taking the "total" returned by GitHub
non_member_contributions = defaultdict(int)
for repo in repos:
print("getting results for repo: %s ..." % repo)
contributors = get_contributors(repo, args.token)
for c in contributors:
login = c["author"]["login"]
if login not in members:
non_member_contributions[login.encode("utf-8")] += int(c["total"])
by_contributions = sorted(
((k, v) for k,v in non_member_contributions.iteritems()), key=lambda v: -v[1])
print("Non-Member GitHub Logins sorted by total contributions in %s:" % args.org)
for nm in by_contributions:
print("@{: <25}| {: >4} total contributions".format(*nm))
if __name__ == "__main__":
args = parser.parse_args()
main(args)
@cjwagner
Copy link

You should get the collaborators not the members in order to include outside collaborators who are still valid for assigning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment