From 87cc983cb2d1e9b70941e5c0e4a89325174860a8 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Tue, 8 Sep 2026 20:01:21 -0700 Subject: [PATCH] util script --- setup_dual_push.py | 148 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 setup_dual_push.py diff --git a/setup_dual_push.py b/setup_dual_push.py new file mode 100644 index 0000000..6810060 --- /dev/null +++ b/setup_dual_push.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 + +import re +import subprocess +from pathlib import Path + +PROJECTS_DIR = Path.home() / "Projects" +GITHUB_USER = "SowinskiBraeden" +GITEA_HOST = "git.sowinski.dev" + + +def git(repo: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess: + return subprocess.run( + ["git", "-C", str(repo), *args], + text=True, + capture_output=True, + check=check, + ) + + +def find_git_repositories(root: Path): + """ + Find repositories recursively under ~/Projects. + + Once a .git directory/file is found, don't recurse further into that + repository looking for nested repositories. + """ + repositories = [] + + for path in root.rglob(".git"): + if path.is_dir() or path.is_file(): + repositories.append(path.parent) + + return sorted(set(repositories)) + + +def github_repo_name(origin: str): + """ + Recognize origins such as: + + git@github.com:SowinskiBraeden/example.git + https://github.com/SowinskiBraeden/example.git + ssh://git@github.com/SowinskiBraeden/example.git + + Returns "example" or None. + """ + + patterns = [ + rf"^git@github\.com:{re.escape(GITHUB_USER)}/([^/]+?)(?:\.git)?$", + rf"^https?://github\.com/{re.escape(GITHUB_USER)}/([^/]+?)(?:\.git)?/?$", + rf"^ssh://git@github\.com/{re.escape(GITHUB_USER)}/([^/]+?)(?:\.git)?/?$", + ] + + for pattern in patterns: + match = re.match(pattern, origin) + + if match: + return match.group(1) + + return None + + +def configure_repository(repo: Path): + result = git(repo, "remote", "get-url", "origin", check=False) + + if result.returncode != 0: + print(f"SKIP {repo}") + print(" No origin remote") + return + + origin = result.stdout.strip() + repo_name = github_repo_name(origin) + + if not repo_name: + print(f"SKIP {repo}") + print(f" Origin is not {GITHUB_USER}'s GitHub: {origin}") + return + + github_url = f"git@github.com:{GITHUB_USER}/{repo_name}.git" + gitea_url = f"git@{GITEA_HOST}:{GITHUB_USER}/{repo_name}.git" + + existing_push_urls = git( + repo, + "remote", + "get-url", + "--all", + "--push", + "origin", + ).stdout.splitlines() + + print(f"FOUND {repo}") + print(f" Repository: {GITHUB_USER}/{repo_name}") + + # Explicitly establish GitHub as the first push destination. + # + # If no separate pushurl exists yet, this creates one without changing + # the fetch URL. + if github_url not in existing_push_urls: + git(repo, "remote", "set-url", "--push", "origin", github_url) + print(f" + push -> {github_url}") + else: + print(" = GitHub push already configured") + + # Re-read because set-url --push may have replaced the implicit + # origin push URL. + existing_push_urls = git( + repo, + "remote", + "get-url", + "--all", + "--push", + "origin", + ).stdout.splitlines() + + if gitea_url not in existing_push_urls: + git( + repo, + "remote", + "set-url", + "--add", + "--push", + "origin", + gitea_url, + ) + print(f" + push -> {gitea_url}") + else: + print(" = Gitea push already configured") + + print() + + +def main(): + if not PROJECTS_DIR.exists(): + raise SystemExit(f"Projects directory does not exist: {PROJECTS_DIR}") + + repositories = find_git_repositories(PROJECTS_DIR) + + print(f"Searching {PROJECTS_DIR}") + print(f"Found {len(repositories)} Git repositories.\n") + + for repo in repositories: + configure_repository(repo) + + print("Done.") + + +if __name__ == "__main__": + main()