fix(api): avoid slow weekly git fallback
Some checks failed
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / tests (push) Successful in 1m44s
CD Pipeline / build-and-deploy (push) Successful in 5m48s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 16:23:28 +08:00
parent 4a7f804731
commit 1847d5a2e5
2 changed files with 74 additions and 30 deletions

View File

@@ -23,6 +23,7 @@ import asyncio
import json
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from typing import Protocol, runtime_checkable
@@ -100,6 +101,9 @@ class WeeklyReportService:
def _get_git_stats(self, since: datetime) -> tuple[int, int, bool]:
"""取得 Git 統計 (commits, deploys)"""
git_cwd = "/app"
if not self._has_local_git_worktree(git_cwd):
return self._get_gitea_commit_stats(since)
try:
# 取得本週 commits 數量
since_str = since.strftime("%Y-%m-%d")
@@ -108,7 +112,7 @@ class WeeklyReportService:
capture_output=True,
text=True,
timeout=10,
cwd="/app", # K8s 容器內的工作目錄
cwd=git_cwd, # K8s 容器內的工作目錄
)
if result.returncode != 0:
logger.warning(
@@ -125,7 +129,7 @@ class WeeklyReportService:
capture_output=True,
text=True,
timeout=10,
cwd="/app",
cwd=git_cwd,
)
if result_deploy.returncode != 0:
logger.warning(
@@ -141,9 +145,12 @@ class WeeklyReportService:
logger.warning("git_stats_failed", error=str(e))
return self._get_gitea_commit_stats(since)
def _has_local_git_worktree(self, cwd: str) -> bool:
"""確認容器內是否真的保留 Git metadata。"""
return Path(cwd, ".git").exists()
def _get_gitea_commit_stats(self, since: datetime) -> tuple[int, int, bool]:
"""從 Gitea commits API 讀取週報開發活動統計。"""
api_url = settings.GITEA_API_URL.rstrip("/")
owner = quote(str(settings.GITEA_REPO_OWNER), safe="")
repo = quote(str(settings.GITEA_REPO_NAME), safe="")
since_utc = since.astimezone(ZoneInfo("UTC")).isoformat().replace("+00:00", "Z")
@@ -156,34 +163,53 @@ class WeeklyReportService:
page = 1
limit = 50
try:
while page <= 20:
query = urlencode({
"sha": "main",
"since": since_utc,
"page": page,
"limit": limit,
})
url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
request = Request(url, headers=headers)
with urlopen(request, timeout=8) as response:
payload = json.loads(response.read().decode("utf-8"))
if not isinstance(payload, list):
logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
return 0, 0, False
commits += len(payload)
for item in payload:
commit = item.get("commit") if isinstance(item, dict) else {}
message = str((commit or {}).get("message") or "")
subject = message.splitlines()[0].lower()
if "deploy" in subject or subject.startswith("chore(cd):"):
deploys += 1
if len(payload) < limit:
break
page += 1
return commits, deploys, True
for api_url in self._gitea_api_candidates():
commits = deploys = 0
page = 1
try:
while page <= 20:
query = urlencode({
"sha": "main",
"since": since_utc,
"page": page,
"limit": limit,
})
url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
request = Request(url, headers=headers)
with urlopen(request, timeout=3) as response:
payload = json.loads(response.read().decode("utf-8"))
if not isinstance(payload, list):
logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
break
commits += len(payload)
for item in payload:
commit = item.get("commit") if isinstance(item, dict) else {}
message = str((commit or {}).get("message") or "")
subject = message.splitlines()[0].lower()
if "deploy" in subject or subject.startswith("chore(cd):"):
deploys += 1
if len(payload) < limit:
return commits, deploys, True
page += 1
except Exception as candidate_exc:
logger.warning(
"gitea_git_stats_candidate_failed",
api_url=api_url,
error=str(candidate_exc),
)
continue
except Exception as exc:
logger.warning("gitea_git_stats_failed", error=str(exc))
return 0, 0, False
return 0, 0, False
def _gitea_api_candidates(self) -> list[str]:
"""取得 Git 統計可用的 Gitea read-only API base URLs。"""
candidates = [settings.GITEA_API_URL.rstrip("/"), "https://gitea.wooo.work"]
deduped: list[str] = []
for candidate in candidates:
if candidate and candidate not in deduped:
deduped.append(candidate)
return deduped
async def generate_report(self) -> WeeklyReportMessage:
"""

View File

@@ -58,6 +58,7 @@ class TestWeeklyReportGitStats:
)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_has_local_git_worktree", lambda cwd: True)
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (0, 0, False))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
@@ -78,6 +79,7 @@ class TestWeeklyReportGitStats:
)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_has_local_git_worktree", lambda cwd: True)
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (12, 4, True))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
@@ -105,6 +107,7 @@ class TestWeeklyReportGitStats:
monkeypatch.setattr(weekly_report_module.subprocess, "run", fake_run)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_has_local_git_worktree", lambda cwd: True)
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (0, 0, False))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
@@ -112,6 +115,21 @@ class TestWeeklyReportGitStats:
assert deploys == 0
assert source_ok is False
def test_missing_local_git_worktree_uses_gitea_without_subprocess(self, monkeypatch):
def fail_run(*_args, **_kwargs):
raise AssertionError("subprocess git log should not run without local .git")
monkeypatch.setattr(weekly_report_module.subprocess, "run", fail_run)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_has_local_git_worktree", lambda cwd: False)
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (21, 7, True))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 21
assert deploys == 7
assert source_ok is True
def test_gitea_commit_stats_counts_deploy_markers(self, monkeypatch):
payload = [
{"commit": {"message": "chore(cd): deploy abc123 [skip ci]\n"}},
@@ -131,7 +149,7 @@ class TestWeeklyReportGitStats:
def fake_urlopen(request, timeout):
assert "/api/v1/repos/wooo/awoooi/commits?" in request.full_url
assert timeout == 8
assert timeout == 3
return Response()
monkeypatch.setattr(weekly_report_module, "urlopen", fake_urlopen)