Skip to content

Instantly share code, notes, and snippets.

@peter-gy
Last active August 29, 2022 19:28
Show Gist options
  • Save peter-gy/7978133d7cc1435b586add8421984b12 to your computer and use it in GitHub Desktop.
Save peter-gy/7978133d7cc1435b586add8421984b12 to your computer and use it in GitHub Desktop.
List your GitHub commits across all repositories you have contributed to.
import subprocess
from shutil import which
from typing import NamedTuple, List
from datetime import datetime
import json
import re
import sys
class Commit(NamedTuple):
"""Simplified model to represent a single commit"""
author_name: str
author_email: str
date: datetime
message: str
url: str
class Repository(NamedTuple):
"""Simplified model to represent a repository"""
private: bool
name: str
owner: str
url: str
def sh(args: list) -> str:
"""
Run a shell command and return the output.
"""
return subprocess.run(args, capture_output=True, text=True).stdout
def gh(args: List[str]) -> str:
"""
Wrapper around the `gh` CLI tool.
:param args: the `gh` command arguments without the `gh` prefix.
:return: the output of the command.
"""
return sh(['gh'] + args)
def parse_repository_item(item: dict) -> Repository:
"""
Parse a single repository item from the GitHub API response.
:param item: the repository item from the GitHub API response.
:return: a `Repository` object.
"""
owner, name = item['nameWithOwner'].split('/')
return Repository(
private=item['isPrivate'],
name=name,
owner=owner,
url=item['url'],
)
def get_repositories_contributed_to(author: str) -> List[Repository]:
query = """query($endCursor: String) {
user(login: "{author}") {
repositoriesContributedTo(first: 100, after: $endCursor) {
nodes {
isPrivate,
nameWithOwner,
url
}
pageInfo {
hasNextPage,
endCursor
}
}
}
}""".replace("{author}", author) # hacky interpolation to avoid escaping all the curly braces
query = query.replace('\n', '').strip()
query = re.sub(r'\s+', ' ', query)
args = ['api', 'graphql', '--paginate', '-f', f"query={query}"]
result = gh(args)
response = json.loads(result)
return [parse_repository_item(item) for item in response['data']['user']['repositoriesContributedTo']['nodes']]
def parse_commit_item(item: dict) -> Commit:
"""
Parse a single commit item from the GitHub API response.
:param item: the commit item from the GitHub API response.
:return: a `Commit` object.
"""
return Commit(
author_name=item['commit']['author']['name'],
author_email=item['commit']['author']['email'],
date=datetime.strptime(item['commit']['author']['date'], '%Y-%m-%dT%H:%M:%SZ'),
message=item['commit']['message'],
url=item['html_url'],
)
def get_commits_by_author_in_repo(repo_owner: str, repo_name: str, author: str) -> List[Commit]:
args = [
'api',
'--header', "'Accept: application/vnd.github.v3+json'",
'--method', 'GET',
'--paginate',
'-f', f'author={author}',
f'/repos/{repo_owner}/{repo_name}/commits',
]
result = gh(args)
response: list = json.loads(result)
return [parse_commit_item(item) for item in response]
if __name__ == '__main__':
if len(sys.argv) == 1:
print("Usage: python3 gh_contribs.py <gh_username> [show_private]")
exit(1)
gh_username = sys.argv[1]
show_private = len(sys.argv) > 2
if which('gh') is None:
print("The GitHub CLI is not on your path.\n"
"Please make sure that it is available as `gh`.\n"
"You can install it from https://cli.github.com/.")
exit(1)
repos = get_repositories_contributed_to(gh_username)
if not show_private:
repos = [repo for repo in repos if not repo.private]
for repo in repos:
commits = get_commits_by_author_in_repo(repo.owner, repo.name, gh_username)
# Non-code contribution, such as an issue
num_commits = len(commits)
if num_commits == 0:
continue
print(f"{num_commits} Contribution{'' if num_commits == 1 else 's'} to {repo.url}")
for commit in commits:
print(f"{commit.date} - {commit.url}")
print()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment