From 357980e7652022e42ff0c1a10237ec08ab913dae Mon Sep 17 00:00:00 2001 From: Christian Lempa Date: Thu, 1 Jun 2023 09:48:30 +0200 Subject: [PATCH] added repo script --- repos/__main__.py | 196 +++++++++++++++++++++ repos/__pycache__/__main__.cpython-311.pyc | Bin 0 -> 9022 bytes repos/requirements.txt | 2 + 3 files changed, 198 insertions(+) create mode 100644 repos/__main__.py create mode 100644 repos/__pycache__/__main__.cpython-311.pyc create mode 100644 repos/requirements.txt diff --git a/repos/__main__.py b/repos/__main__.py new file mode 100644 index 0000000..0364bd7 --- /dev/null +++ b/repos/__main__.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path +import typer +import requests + +app = typer.Typer() + +ignored_folders = [ + '.git', + '.DS_Store', + '.obsidian', +] + + +def getReposFromPage(github_token: str, organization: str, page: int): + # Get all repos from a specific page for an organization from GitHub. + # https://docs.github.com/en/rest/reference/repos#list-organization-repositories + try: + url = f"https://api.github.com/orgs/{organization}/repos" + payload = {} + headers = { + 'Authorization': f'token {github_token}' + } + params = { + 'per_page': '100', + 'page': page, + 'sort': 'full_name' + } + + response = requests.request("GET", url, headers=headers, data=payload, params=params) + + if response.status_code == 200: + return response.json() + else: + raise Exception(f"status code {response.status_code}") + except Exception as e: + print(f"error: {e}") + return None + + +def getRepos(github_token: str, organization: str): + # Get all repos for an organization from GitHub by iterating over all pages. + # https://docs.github.com/en/rest/reference/repos#list-organization-repositories + try: + repos = [] + page = 1 + while True: + repos_page = getReposFromPage(github_token, organization, page) + if repos_page is not None: + repos.extend(repos_page) + if len(repos_page) < 100: + break + else: + page += 1 + else: + raise Exception("repos_page is None") + return repos + except Exception as e: + print(f"error: {e}") + return None + + +@app.command() +def list(github_token: str = typer.Argument("--token", envvar="GITHUB_TOKEN")): + try: + # List all repos for an organization from GitHub + # https://docs.github.com/en/rest/reference/repos#list-organization-repositories + + repos = getRepos(github_token, 'clcreative') + if repos is not None: + for repo in repos: + print(f"{repo['name']}, {repo['owner']['login']}, {repo['description']}") + else: + raise Exception("repos is None") + + # Sum all repos + print(f"---\nTotal: {len(repos)}") + + except Exception as e: + print(f"error: {e}") + + +@app.command() +def check(github_token: str = typer.Argument("--token", envvar="GITHUB_TOKEN"), list_orphaned: bool = False): + try: + # Get all repos for an organization from GitHub + # https://docs.github.com/en/rest/reference/repos#list-organization-repositories + + repos = getRepos(github_token, 'clcreative') + repos_need_changes = [] + repos_not_cloned = [] + + if repos is not None: + for repo in repos: + # set repo path on local workstation + repo_path = f'{ Path.home() }/projects/{repo["owner"]["login"]}/{repo["name"]}' + + # when repo is existing + if os.path.exists(repo_path): + try: + os.chdir(repo_path) + + # check if there are any changes + # changes = os.system('git diff --exit-code --quiet') + + # check if there are any changes to the repo, and if something needs to be pulled or committed + changes = os.system('git diff --exit-code --quiet') + + if changes != 0: + print(f" {repo['name']} - {repo['description']}") + repos_need_changes.append(repo) + else: + print(f" {repo['name']} - {repo['description']}") + + except Exception as e: + print(f"error: {e}") + else: + print(f" {repo['name']} - {repo['description']}") + repos_not_cloned.append(repo) + + # Check orphaned repos + repos_orphaned = [] + + # read all folders in projects folder + for item in os.listdir(f'{ Path.home() }/projects/clcreative'): + # check if item is in repos list, and if item is not in ignored folders + if item not in [repo['name'] for repo in repos] and item not in ignored_folders: + repos_orphaned.append(item) + + # List all orphaned repos + if list_orphaned: + print("---\nOrphaned:") + for repo in repos_orphaned: + print(f" {repo}") + + # Get Information about all Repos + print(f"---\nTotal: {len(repos)}, Need changes: {len(repos_need_changes)}, Not cloned: {len(repos_not_cloned)}, Orphaned: {len(repos_orphaned)}") + + except Exception as e: + print(f"error: {e}") + + +@app.command() +def clone(github_token: str = typer.Argument("--token", envvar="GITHUB_TOKEN")): + # Clone all missing repos for an organization from GitHub + + try: + repos = getRepos(github_token, 'clcreative') + if repos is not None: + for repo in repos: + # set repo path on local workstation + repo_path = f'{ Path.home() }/projects/{repo["owner"]["login"]}/{repo["name"]}' + + # when repo is not existing + if not os.path.exists(repo_path): + print(f" {repo['name']} - {repo['description']}") + os.system(f'git clone git@github.com:{repo["owner"]["login"]}/{repo["name"]}.git {repo_path}') + + except Exception as e: + print(f"error: {e}") + + +@app.command() +def clear(github_token: str = typer.Argument("--token", envvar="GITHUB_TOKEN")): + # Clear all orphaned repos from workstation + + repos_orphaned = [] + + try: + repos = getRepos(github_token, 'clcreative') + if repos is not None: + # read all folders in projects folder + for item in os.listdir(f'{ Path.home() }/projects/clcreative'): + # check if item is in repos list, and if item is not in ignored folders + if item not in [repo['name'] for repo in repos] and item not in ignored_folders: + repos_orphaned.append(item) + + # ask for confirmation + if len(repos_orphaned) > 0: + print("---\nOrphaned:") + for repo in repos_orphaned: + print(f" {repo}") + print(f"---\nTotal: {len(repos_orphaned)}") + if typer.confirm("Do you want to delete all orphaned repos?"): + for repo in repos_orphaned: + # delete folder using rm + os.system(f'rm -rf { Path.home() }/projects/clcreative/{repo}') + + except Exception as e: + print(f"error: {e}") + + +if __name__ == "__main__": + app() diff --git a/repos/__pycache__/__main__.cpython-311.pyc b/repos/__pycache__/__main__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec6609ee28f51e5e8518e1f0daf765e32a8206a6 GIT binary patch literal 9022 zcmb_BYitwOn)CGA@x-14hd9AaLQ0y4d4v`c9!)T`KnZEXqokLcn#?3I@gp5ic=$Rf zut*ze`H@ygc=y6y_lmOV!j-uHSm~|=1+Ca0D~;K?!WwNPq!rTk$7)@%NKt<6ch2~o zK)GGUXTJH)W4`m9GvD+0AMADu0oPycKPUYAT7vjbRLNh49P*-zB8Y2*KnP@r7)DPj zM24whiqzZaVH)=_!wf};^l3Iv;1(da^A%#)Fir^c@5y1Kz=$S+h1(#S#|(n;GBa$+ zw~aA^3F=nSkg^qCEkVj@;`!|PZow>A?pSXDx3}TeuQ3u)9-{=?Ww!j?;i|VahhPW# zXiZh31Ne8Wn2CE^GZj|Dt(c?wt!3s{6ix{a#Ruljh>1HgN#${X|y9VBKLffkqq@mt~s6(na^flW{X zNZECI#vsER`RjB67fcB{!6ul5;g0zhNbl`}1kV@~#?u8^5-hp3^9f_Bl%qUJqI^Gw z2-#fdA_QwGEyUd|;NW(Fi%VoPA)AWhTL`IP#w1&dErKm!TKOJHoB%zXF z&XQ+|bMy(~EZJrshPlqij$uOb!g;=ID%* zpEEwQ)hi9%sSp4tS(J*ulYwRd65J>&gx z<=C$j*U7Z&r0P03XL@FJsn&I=El;eiinTT4s=v1Lo1I_pzPeknH9dFME1tfEZEE{& zctS}#`&DPZV(VX8wDMVkVoSiH$vziOl5^p?aN6WiO&-PM$v9kda{jjjSC)l$;2A?J zz0_yxYb72y)*#qw>9^AlHah!F^baN$@X>(DOV(tkP8Y-5S6_GJ&E6x$VhF17RWfbj~P1R zkdax*T|x!LOXQW$64Vd@3W`KNcVPu(%T%0Au&-25_5zzzP{2wQ)G+WPTGo*t1%PoU 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 kwe}E