Last active
March 9, 2023 14:44
-
-
Save psa-jforestier/28a5de5d262ac2af5b00595e651696d0 to your computer and use it in GitHub Desktop.
List all inactive user of a GitHub organization using GitHub API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
List all inactive user of a GitHub organization | |
See user.py --help for usage. | |
Partially inspired by https://gist.github.com/morido/9817399 | |
''' | |
import sys # to use sys.stdout | |
import os | |
from datetime import datetime | |
from time import strftime | |
import datetime | |
import json | |
import urllib2 | |
import argparse | |
import base64 | |
import pprint | |
import requests | |
import string | |
from urlparse import urlparse, parse_qs | |
def printf(format, *args): | |
sys.stdout.write(format % args) | |
def githubDateToUSdate(date): | |
dt = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") | |
return dt | |
if hasattr(__builtins__, 'raw_input'): | |
input = raw_input | |
# Turn off console output buffering | |
buf_arg = 0 | |
if sys.version_info[0] == 3: | |
os.environ['PYTHONUNBUFFERED'] = '1' | |
buf_arg = 1 | |
try: | |
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg) | |
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', buf_arg) | |
except: | |
pass | |
parser = argparse.ArgumentParser(description=''' | |
List all repository of a GitHub organization.''') | |
parser.add_argument('-u',metavar='USER', dest='github_user', action='store', required=False, | |
help='GitHub user', | |
default=None) | |
parser.add_argument('-p',metavar='PASSWORD', dest='github_pass', action='store', required=False, | |
help='GitHub password', | |
default=None) | |
parser.add_argument('-o',metavar='ORG', dest='github_org', action='store', required=False, | |
help='GitHub organization to list. Default %(default)s', | |
default='D4UDigitalPlatform') | |
parser.add_argument('-w',metavar='WEEKS', dest='weeks', action='store', required=False, | |
help='Time of interests, in weeks. Default %(default)s', | |
default='52') | |
parser.add_argument('--repo',metavar='REPONAME', dest='repo', action='store', required=False, | |
help='GitHub repository name (without orga name)', | |
default=None) | |
parser.add_argument('--userdetails', dest='repo', action='store', required=False, | |
help='Show detailed information about each organization users', | |
default=False) | |
parser.add_argument('--url',metavar='URL', dest='github_url', action='store', required=False, | |
help='GitHub API url. Default %(default)s', | |
default='https://api.github.com') | |
args = parser.parse_args() | |
if (args.github_user == None): | |
args.github_user = input("GitHub Username :") | |
if (args.github_pass == None): | |
print "GitHub Password (sorry, it will be displayed in the console. Use -p instead" | |
args.github_pass = input(":") | |
time_of_interest_in_weeks = int(args.weeks) | |
time_of_interest_in_weeks_absolute = datetime.datetime.today() - datetime.timedelta(weeks = time_of_interest_in_weeks) | |
base64string = base64.b64encode(args.github_user + ':' + args.github_pass ) | |
#headers = {"Authorization": "Basic %s" % base64string, "Accept": "application/vnd.github.inertia-preview+json"} | |
headers = {"Authorization": "Basic %s" % base64string, "Accept": "application/vnd.github.mercy-preview+json"} | |
printf("Find inactive user :\n\tSince %s\n\tFor organization %s\n\tFor %s repo\n\tUsing authent provided by %s\n", | |
time_of_interest_in_weeks_absolute, | |
args.github_org, | |
"all" if args.repo == None else args.repo, | |
args.github_user | |
) | |
url = args.github_url + "/orgs/" + args.github_org | |
print "Get organization information ..." | |
res=requests.get(url,headers=headers) | |
if (res.status_code <> 200): | |
printf("ERROR : API reply HTTP code %d\n", res.status_code) | |
sys.exit(res.status_code) | |
repos=res.json() | |
printf("Organization informations :\n") | |
printf("\tDisk usage : %d\n", repos.get("disk_usage")) | |
printf("\tTotal privat repo : %d\n", repos.get("total_private_repos")) | |
printf("\tCollaborators : %d\n", repos.get("collaborators")) | |
printf("\tPlan name : %s\n", repos.get("plan")['name']) | |
printf("\tPlan max repo : %d\n", repos.get("plan")['private_repos']) | |
printf("\tPlan used space : %d\n", repos.get("plan")['space']) | |
# | |
#See https://developer.github.com/v3/repos/#list-organization-repositories | |
# | |
url = args.github_url + "/orgs/"+args.github_org+"/members?per_page=100" | |
print "Get all users member of the organization" | |
members = [] # all members subscribe to the orga | |
activemembers = [] # all orga members with activity | |
inactivemembers = []# all orga member without activity | |
def get_user_info(login): | |
url = args.github_url + "/users/" + login | |
res=requests.get(url,headers=headers) | |
mem=res.json() | |
return ({ | |
'login': mem.get('login'), | |
'location': "" if (mem.get('location')==None) else mem.get('location').encode('utf-8').encode('ascii', 'backslashreplace'), | |
'name':"" if (mem.get('name') == None) else mem.get('name').encode('ascii', 'backslashreplace'), | |
'email': "" if (mem.get('email') == None) else mem.get('email'), | |
'created_at':mem.get('created_at') | |
}) | |
# create the members list containing all member subribing the orga | |
while True: | |
res=requests.get(url,headers=headers) | |
mem=res.json() | |
for member in mem: | |
login = member.get('login') | |
members.append(login) | |
#info = get_user_info(login) | |
#printf("%s;%s;%s;%s;%s\n", info.get('login'), info.get('location'), info.get('name'), info.get('email'), info.get('created_at')) | |
printf("%d members fetched...\r", len(members)) | |
if 'next' in res.links.keys(): | |
url = res.links['next']['url'] | |
else: | |
break | |
printf("Found %d members \n", len(members)) | |
def get_contributors_from_contributors_url(url, headers): | |
contributors = [] | |
while True: | |
res=requests.get(url+"?per_page=100",headers=headers) | |
if (res.status_code == 204): | |
break | |
ctr=res.json() | |
for contributor in ctr: | |
contributors.append({ | |
'login':contributor.get('login'), | |
'contributions':contributor.get('contributions') | |
}) | |
#printf("%d contributors fetched...\r", len(contributors)) | |
if 'next' in res.links.keys(): | |
url = res.links['next']['url'] | |
else: | |
break | |
return contributors | |
def get_nbcommits_from(url, author, since, header): | |
url = string.replace(url, "{/sha}", "") | |
url = url + "?per_page=1&since="+since.isoformat()+"&author="+author # Do I need urlencode here ? | |
nbcommits = 0 | |
res=requests.get(url,headers=headers) | |
com = res.json() | |
nbcommits = len(com) | |
if (len(com) != 0): | |
prevurl = res.links['last']['url'] | |
parsed_url = urlparse(prevurl) | |
qs = parse_qs(parsed_url.query) | |
nbcommits = int(qs.get('page')[0]) | |
return nbcommits | |
def get_commits_from(url, author, since, header): | |
# {/sha} | |
url = string.replace(url, "{/sha}", "") | |
url = url + "?per_page=100&since="+since.isoformat() | |
if (author != None): | |
url = url + "&author="+author # Do I need urlencode here ? | |
commits=[] | |
while True: | |
res=requests.get(url,headers=headers) | |
com = res.json() | |
if (res.status_code == 409): | |
return [] | |
for commit in com: | |
author = "" if (commit.get('author') == None) else commit.get('author').get('login') | |
committer = commit.get('commit').get('committer').get('name') | |
commit_author = commit.get('commit').get('author').get('name') | |
committer_login = "" if (commit.get('committer') == None) else commit.get('committer').get('login') | |
sha = commit.get('sha') | |
if author == "" or author == None: | |
author = committer_login | |
if author == "" or author == None: | |
author = commit_author | |
if author == "" or author == None: | |
author = committer | |
#printf("%s %s %s %s %s\n", sha, author, committer, commit_author, committer_login) | |
commits.append({ | |
'login':author.encode('ascii', 'backslashreplace'), | |
'sha':sha, | |
'date':commit.get('commit').get('committer').get('date') | |
}) | |
if 'next' in res.links.keys(): | |
url = res.links['next']['url'] | |
else: | |
break | |
return commits | |
# for each repo of the orga, | |
# fetch all commits since a date. | |
# for each commit, extract committer and add it to the active list if he is memeber of the orga | |
print "Get all users of repo of the organization" | |
url = args.github_url + "/orgs/"+args.github_org+"/repos?type=all&per_page=100" | |
repos = [] | |
nonmembers = [] | |
while True: | |
res=requests.get(url,headers=headers) | |
rep=res.json() | |
for repo in rep: | |
reponame = repo.get('name') | |
repos.append(reponame) | |
printf("- %s\n", reponame) | |
if (args.repo == None or reponame == args.repo): | |
commits = get_commits_from(repo.get('commits_url'), None, time_of_interest_in_weeks_absolute, headers) #Get all commits since a date | |
printf("\tFound %5d commits ", len(commits)) | |
nbcommit_by_login = {} | |
for com in commits: | |
login = com.get('login') | |
if (login in nbcommit_by_login) : | |
nbcommit_by_login[login] = nbcommit_by_login[login] + 1 | |
else : | |
nbcommit_by_login[login] = 1 | |
printf("by %d users\n", len(nbcommit_by_login)) | |
for login,nbcom in sorted(nbcommit_by_login.iteritems(), key=lambda (k,v): (v,k), reverse=True): | |
printf("\t\t%20s : %5d commits", login, nbcom) | |
if (login in members): | |
if (login not in activemembers): | |
activemembers.append(login) | |
else: | |
printf(" not in organization") | |
printf("\n") | |
''' | |
contributors_url = repo.get('contributors_url') | |
contributors = get_contributors_from_contributors_url(contributors_url, headers) | |
printf("\t%d contributors :\n", len(contributors)) | |
for c in contributors: | |
login = c.get('login') | |
printf("\t\t%20s (%5d total contributions) ", login, c.get('contributions')) | |
if login not in members and login not in nonmembers: | |
nonmembers.append(login) | |
# if login not in activemembers and login in members: | |
# activemembers.append(login) | |
#commits = get_commits_from(repo.get('commits_url'), login, time_of_interest_in_weeks_absolute, headers) | |
#printf("%5d commits\n", len(commits)) | |
if (login in members): | |
nbcommit = get_nbcommits_from(repo.get('commits_url'), login, time_of_interest_in_weeks_absolute, headers) | |
#if (nbcommit > 0): | |
# printf("had commited\n") | |
#else: | |
# printf("did not commit\n") | |
printf("%4d commits\n", nbcommit) | |
else: | |
printf("not in organization\n") | |
if (nbcommit > 0 and login in members and login not in activemembers): | |
activemembers.append(login) | |
printf("\n") | |
''' | |
if 'next' in res.links.keys(): | |
url = res.links['next']['url'] | |
else: | |
break | |
inactivemembers = members | |
for m in activemembers: | |
inactivemembers.remove(m) | |
printf("Found %4d members of the organization\n", len(members)) | |
printf("Found %4d contributors not existing anymore in organization\n", len(nonmembers)) | |
printf("Found %4d active member of the organization\n", len(activemembers)) | |
printf("Found %4d inactive member of the organization :\n", len(inactivemembers)) | |
for m in inactivemembers: | |
printf("%s\n", m) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment