Skip to content

Commit f2411ff

Browse files
committed
Support authenticated GitHub downloads
1 parent 0b7fae5 commit f2411ff

File tree

1 file changed

+52
-8
lines changed

1 file changed

+52
-8
lines changed

scripts/generate_missing_skins.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
import io
1010
import json
1111
import math
12+
import os
1213
import re
1314
import shutil
1415
import tempfile
1516
from pathlib import Path
1617
from typing import Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple
17-
from urllib.parse import urlparse
18+
from urllib.parse import urlparse, urlunparse
1819
from urllib.request import Request, urlopen
1920
from zipfile import ZIP_DEFLATED, ZipFile
2021
import subprocess
@@ -89,6 +90,47 @@ class AndroidSkinSource:
8990
)
9091

9192

93+
def _github_token() -> Optional[str]:
94+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
95+
if token:
96+
return token.strip()
97+
return None
98+
99+
100+
def _github_headers(url: Optional[str] = None) -> Dict[str, str]:
101+
headers: Dict[str, str] = {"User-Agent": "codenameone-skin-generator/1.0"}
102+
token = _github_token()
103+
if not token:
104+
return headers
105+
if url is None:
106+
headers["Authorization"] = f"Bearer {token}"
107+
return headers
108+
host = urlparse(url).netloc.lower()
109+
if "github.com" in host or "githubusercontent.com" in host or host.startswith("api.github"):
110+
headers["Authorization"] = f"Bearer {token}"
111+
return headers
112+
113+
114+
def _authenticated_git_url(url: str) -> str:
115+
token = _github_token()
116+
if not token:
117+
return url
118+
parsed = urlparse(url)
119+
host = parsed.netloc.lower()
120+
if "github.com" not in host:
121+
return url
122+
safe_netloc = f"{token}:x-oauth-basic@{parsed.netloc}"
123+
return urlunparse(parsed._replace(netloc=safe_netloc))
124+
125+
126+
def _sanitize_url(url: str) -> str:
127+
token = _github_token()
128+
if not token:
129+
return url
130+
sanitized = url.replace(token, "***")
131+
return sanitized.replace(f"{token}:x-oauth-basic", "***:x-oauth-basic")
132+
133+
92134
@dataclasses.dataclass(frozen=True)
93135
class SkinGeneration:
94136
"""Description of a generated skin archive."""
@@ -601,12 +643,11 @@ def _github_repo_from_url(url: str) -> Optional[Tuple[str, str]]:
601643
def _branch_candidates(base_url: str) -> List[str]:
602644
owner_repo = _github_repo_from_url(base_url)
603645
candidates: List[str] = []
604-
headers = {"User-Agent": "codenameone-skin-generator/1.0"}
605646
if owner_repo:
606647
owner, repo = owner_repo
607648
api_url = f"https://api.github.com/repos/{owner}/{repo}"
608649
try:
609-
request = Request(api_url, headers=headers)
650+
request = Request(api_url, headers=_github_headers(api_url))
610651
with urlopen(request) as response: # type: ignore[arg-type]
611652
payload = json.load(response)
612653
default_branch = payload.get("default_branch")
@@ -628,7 +669,6 @@ def _download_android_skin_repo(source: AndroidSkinSource) -> Tuple[Path, Path]:
628669

629670
tmp_root = Path(tempfile.mkdtemp(prefix="android-skins-"))
630671
archive_path = tmp_root / "repo.zip"
631-
headers = {"User-Agent": "codenameone-skin-generator/1.0"}
632672
errors: List[str] = []
633673

634674
base_urls: Tuple[str, ...] = (source.url, *source.alternate_urls)
@@ -646,7 +686,7 @@ def _download_android_skin_repo(source: AndroidSkinSource) -> Tuple[Path, Path]:
646686

647687
for candidate in archive_candidates:
648688
try:
649-
request = Request(candidate, headers=headers)
689+
request = Request(candidate, headers=_github_headers(candidate))
650690
with urlopen(request) as response, archive_path.open("wb") as fh: # type: ignore[arg-type]
651691
shutil.copyfileobj(response, fh)
652692
with ZipFile(archive_path) as zf:
@@ -677,14 +717,17 @@ def _download_android_skin_repo(source: AndroidSkinSource) -> Tuple[Path, Path]:
677717
repo_root = tmp_root
678718
return repo_root, tmp_root
679719
except Exception as exc: # pylint: disable=broad-except
680-
errors.append(f"{candidate}: {exc}")
720+
errors.append(f"{_sanitize_url(candidate)}: {exc}")
681721

682722
# Zip downloads failed, try shallow git clones as fallbacks
683723
for attempt_index, base_url in enumerate(base_urls):
684724
branch_candidates = _branch_candidates(base_url)
685725
for branch in branch_candidates:
686726
clone_dir = tmp_root / f"repo-{attempt_index}-{branch}"
687727
try:
728+
clone_url = _authenticated_git_url(base_url)
729+
env = os.environ.copy()
730+
env.setdefault("GIT_TERMINAL_PROMPT", "0")
688731
subprocess.run(
689732
[
690733
"git",
@@ -695,18 +738,19 @@ def _download_android_skin_repo(source: AndroidSkinSource) -> Tuple[Path, Path]:
695738
"--filter=blob:none",
696739
"--branch",
697740
branch,
698-
base_url,
741+
clone_url,
699742
str(clone_dir),
700743
],
701744
check=True,
702745
capture_output=True,
703746
text=True,
747+
env=env,
704748
)
705749
except subprocess.CalledProcessError as exc: # pragma: no cover - network dependent
706750
stderr = exc.stderr.strip()
707751
stdout = exc.stdout.strip()
708752
details = stderr or stdout or str(exc)
709-
errors.append(f"git clone ({base_url}@{branch}): {details}")
753+
errors.append(f"git clone ({_sanitize_url(base_url)}@{branch}): {details}")
710754
continue
711755
except FileNotFoundError as exc: # pragma: no cover - git missing
712756
errors.append(f"git clone unavailable: {exc}")

0 commit comments

Comments
 (0)