Created
May 23, 2026 18:39
-
-
Save iccir/e0015bcc906286ca6e34ed009bcb9231 to your computer and use it in GitHub Desktop.
Check settings of all GitHub repositories
This file contains hidden or 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
| #!/usr/bin/env python3 | |
| """ | |
| check_repos.py | |
| This is a tool to check the GitHub settings of all of my repositories. | |
| - "Wiki", "Projects", "Discussions", and "Pull Requests" should be disabled. | |
| - "Issues" should be enabled for select repositories. | |
| - Watch should be set to "All Activity" when Issues are enabled. | |
| Usage: | |
| python3 check_repos.py --token ghp_token_here | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| import textwrap | |
| def gh_get(token: str, path: str, params: dict, allow_404 = False): | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Accept": "application/vnd.github+json", | |
| "X-GitHub-Api-Version": "2022-11-28", | |
| } | |
| url = f"{path}?{urllib.parse.urlencode(params)}" | |
| req = urllib.request.Request(url, headers=headers) | |
| try: | |
| with urllib.request.urlopen(req) as resp: | |
| return json.loads(resp.read().decode()) | |
| except urllib.error.HTTPError as e: | |
| body = e.read().decode() | |
| if (e.code == 404 and allow_404): | |
| return None | |
| else: | |
| print(f"GitHub API error: {e.code} - {body}") | |
| sys.exit(1) | |
| def fetch_all_repos(token: str) -> list: | |
| repos = [] | |
| page = 1 | |
| while True: | |
| batch = gh_get(token, "https://api.github.com/user/repos", { | |
| "affiliation": "owner,organization_member", | |
| "per_page": 100, | |
| "page": page, | |
| "sort": "full_name", | |
| }) | |
| if not batch: | |
| break | |
| repos.extend(batch) | |
| page += 1 | |
| return repos | |
| def get_subscription_status(token: str, repo_name: str) -> list: | |
| result = gh_get(token, f"https://api.github.com/repos/{repo_name}/subscription", { }, allow_404 = True) | |
| if result: | |
| return result.get("subscribed") | |
| else: | |
| return False | |
| def print_repo_list(header: str, repos: list) -> None: | |
| if (len(repos) == 0): return | |
| print("") | |
| print(f"{header}:") | |
| sorted_repos = sorted(repos, key=lambda d: d["full_name"]) | |
| sorted_names = ", ".join(d["full_name"] for d in sorted_repos) | |
| print(textwrap.fill(sorted_names, width=80, break_on_hyphens=False)) | |
| def make_subscribed_table(repos: list) -> list: | |
| def yes_or_no(value): | |
| return "YES" if value else "no" | |
| rows = [] | |
| for repo in repos: | |
| rows.append([ | |
| [ "name", repo["full_name"] ], | |
| [ "issues?", yes_or_no(repo.get("has_issues")) ], | |
| [ "pull?", yes_or_no(repo.get("has_pull_requests")) ], | |
| [ "subscribed?", yes_or_no(repo.get("is_subscribed")) ] | |
| ]) | |
| return rows | |
| def make_table(repos: list) -> list: | |
| def yes_or_no(value): | |
| return "YES" if value else "no" | |
| rows = [] | |
| for repo in repos: | |
| rows.append([ | |
| [ "name", repo["full_name"] ], | |
| [ "issues?", yes_or_no(repo.get("has_issues")) ], | |
| [ "wiki?", yes_or_no(repo.get("has_wiki")) ], | |
| [ "projects?", yes_or_no(repo.get("has_projects")) ], | |
| [ "pages?", yes_or_no(repo.get("has_pages")) ], | |
| [ "dis?", yes_or_no(repo.get("has_discussions")) ], | |
| [ "pull?", yes_or_no(repo.get("has_pull_requests")) ] | |
| ]) | |
| return rows | |
| def print_table(rows: list) -> None: | |
| def make_row(values): | |
| return "| " + " | ".join(v.ljust(w) for v, w in zip(values, col_widths)) + " |" | |
| def make_separator(): | |
| return "+-" + "-+-".join("-" * w for w in col_widths) + "-+" | |
| headers = [ cell[0] for cell in rows[0] ] | |
| col_widths = [] | |
| for i, header in enumerate(headers): | |
| max_val_len = max(len(row[i][1]) for row in rows) | |
| col_widths.append(max(len(header), max_val_len)) | |
| sep = make_separator() | |
| print(sep) | |
| print(make_row(headers)) | |
| print(sep) | |
| for row in rows: | |
| print(make_row([cell[1] for cell in row])) | |
| print(sep) | |
| def print_repo_table(header: str, repos: list) -> None: | |
| print("") | |
| print(f"{header}:") | |
| rows = make_table(repos) | |
| if len(rows) > 0: | |
| print_table(rows) | |
| else: | |
| print("No repositories") | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Check settings across all GitHub repositories.") | |
| parser.add_argument("--token", help="GitHub personal access token") | |
| parser.add_argument("--subscribed", help="Check subscribed status", action="store_true") | |
| parser.add_argument("--archived", help="List archived repositories", action="store_true") | |
| args = parser.parse_args() | |
| token = args.token | |
| if not token: | |
| print("Error: provide a GitHub token via --token") | |
| sys.exit(1) | |
| print("Fetching repositories...", flush=True) | |
| repos = fetch_all_repos(token) | |
| def extract(list, condition): | |
| return ( | |
| [x for x in list if not condition(x)], | |
| [x for x in list if condition(x) ] | |
| ) | |
| repos, archived_repos = extract(repos, lambda x: x.get("archived")) | |
| if args.subscribed: | |
| _, repos = extract(repos, lambda x: x.get("has_pull_requests") or x.get("has_issues")) | |
| for repo in repos: | |
| repo["is_subscribed"] = get_subscription_status(token, repo["full_name"]) | |
| rows = make_subscribed_table(repos) | |
| print("") | |
| print_table(rows) | |
| else: | |
| repos, pr_repos = extract(repos, lambda x: x.get("has_pull_requests")) | |
| repos, issue_repos = extract(repos, lambda x: x.get("has_issues")) | |
| repos, forked_repos = extract(repos, lambda x: x.get("fork")) | |
| print_repo_table("Pull Requests Enabled", pr_repos) | |
| print_repo_table("Issues Enabled", issue_repos) | |
| print_repo_table("Standard", repos) | |
| print_repo_table("Forked", forked_repos) | |
| if args.archived: | |
| print_repo_list("Archived", archived_repos) | |
| if __name__ == "__main__": | |
| main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment