-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpush_to_mirror.py
More file actions
107 lines (81 loc) · 3.21 KB
/
push_to_mirror.py
File metadata and controls
107 lines (81 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import json
import os
import subprocess
import sys
from pathlib import Path
from urllib.parse import urlparse, urlunparse
import requests
import jsonschema
import yaml
CONFIG_FILE = Path("config.yaml")
SCHEMA_FILE = Path("schema.json")
CLONE_BASE_DIR = Path("repos")
CODEBERG_API = "https://codeberg.org/api/v1"
def load_config(config_path: Path) -> dict:
with config_path.open() as f:
return yaml.safe_load(f)
def load_schema(schema_path: Path) -> dict:
with schema_path.open() as f:
return json.load(f)
def validate_config(config: dict, schema: dict) -> None:
jsonschema.validate(instance=config, schema=schema)
def clone_from_github(url: str, dest_dir: Path) -> None:
token = os.environ.get("GITHUB_TOKEN")
if token:
parsed = urlparse(url)
clone_url = urlunparse(
parsed._replace(netloc=f"oauth2:{token}@{parsed.hostname}")
)
else:
print(" GITHUB_TOKEN not set, attempting unauthenticated clone")
clone_url = url
print(f" cloning {url} -> {dest_dir}")
subprocess.run(["git", "clone", clone_url, str(dest_dir)], check=True)
def ensure_codeberg_repo(destination: str) -> None:
token = os.environ.get("CODEBERG_TOKEN")
if not token:
raise RuntimeError("CODEBERG_TOKEN environment variable is not set")
_, owner, repo = urlparse(destination).path.rstrip("/").split("/", 2)
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
r = requests.get(f"{CODEBERG_API}/repos/{owner}/{repo}", headers=headers)
if r.status_code == 200:
return
payload = {"name": repo, "private": False, "auto_init": False}
r = requests.post(f"{CODEBERG_API}/orgs/{owner}/repos", json=payload, headers=headers)
r.raise_for_status()
print(f" created {destination}")
def push_to_codeberg(repo_dir: Path, destination: str) -> None:
token = os.environ.get("CODEBERG_TOKEN")
if not token:
raise RuntimeError("CODEBERG_TOKEN environment variable is not set")
parsed = urlparse(destination)
authed_url = urlunparse(parsed._replace(netloc=f"oauth2:{token}@{parsed.hostname}"))
print(f" pushing to {destination}")
subprocess.run(
["git", "-C", str(repo_dir), "push", "--mirror", authed_url], check=True
)
def clone_repository(source: str, dest_dir: Path) -> None:
if dest_dir.exists():
print(f" already cloned, skipping: {dest_dir}")
return
clone_from_github(source, dest_dir)
def main():
schema = load_schema(SCHEMA_FILE)
config = load_config(CONFIG_FILE)
try:
validate_config(config, schema)
except jsonschema.ValidationError as e:
print(f"Invalid configuration: {e.message}", file=sys.stderr)
sys.exit(1)
repositories = config["repositories"]
CLONE_BASE_DIR.mkdir(exist_ok=True)
for repo in repositories:
source = repo["source"]
destination = repo["destination"]
dest_dir = CLONE_BASE_DIR / urlparse(source).path.strip("/").replace("/", "_")
print(f"Syncing {source} -> {destination}")
ensure_codeberg_repo(destination)
clone_repository(source, dest_dir)
push_to_codeberg(dest_dir, destination)
if __name__ == "__main__":
main()