Skip to content

Instantly share code, notes, and snippets.

@psa-jforestier
Last active March 9, 2023 14:44
Show Gist options
  • Save psa-jforestier/28a5de5d262ac2af5b00595e651696d0 to your computer and use it in GitHub Desktop.
Save psa-jforestier/28a5de5d262ac2af5b00595e651696d0 to your computer and use it in GitHub Desktop.
List all inactive user of a GitHub organization using GitHub API
'''
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