Last active
August 29, 2022 19:28
-
-
Save peter-gy/7978133d7cc1435b586add8421984b12 to your computer and use it in GitHub Desktop.
List your GitHub commits across all repositories you have contributed to.
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
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