Case sync: refresh remote URL with current token before each push
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
Cases failed to push silently after the Gitea token in Infisical was rotated: the embedded credential in each case repo's origin URL was the old token, the rotation never propagated, and capture_output=True hid the auth failure as a logger.warning. Three cases (1033-25, 1130-25, 1194-25) accumulated unpushed commits over weeks before this was noticed. Fixes the root cause in two places: web/gitea_client.py for uploads through the FastAPI endpoint, and mcp-server/services/git_sync.py for case_update / document_upload through MCP tools (which previously committed but never pushed at all). The new commit_and_push helper: - re-injects the current GITEA_ACCESS_TOKEN into the existing origin URL on every call, so pushes survive token rotation - logs push failures at WARNING with the actual stderr (the previous code suppressed errors entirely) - continues to push even when the commit was a no-op, in case earlier commits are still unpushed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,24 +48,96 @@ async def create_repo(case_number: str, title: str, description: str = "") -> di
|
||||
return resp.json()
|
||||
|
||||
|
||||
def setup_remote_and_push(case_dir: str | Path, repo_clone_url: str) -> bool:
|
||||
"""Add Gitea as remote 'origin' (or update it) and push."""
|
||||
case_dir = Path(case_dir)
|
||||
if not (case_dir / ".git").exists():
|
||||
return False
|
||||
|
||||
env = {
|
||||
def _git_env() -> dict:
|
||||
return {
|
||||
"GIT_AUTHOR_NAME": "Ezer Mishpati",
|
||||
"GIT_AUTHOR_EMAIL": "legal@local",
|
||||
"GIT_COMMITTER_NAME": "Ezer Mishpati",
|
||||
"GIT_COMMITTER_EMAIL": "legal@local",
|
||||
"PATH": os.environ.get("PATH", "/usr/bin:/bin"),
|
||||
"GIT_TERMINAL_PROMPT": "0",
|
||||
}
|
||||
|
||||
# Inject token into clone URL for auth
|
||||
|
||||
def _refresh_remote_url(case_dir: Path, env: dict) -> bool:
|
||||
"""Re-inject current token into existing origin URL.
|
||||
|
||||
Handles the case where the token in Infisical was rotated but the
|
||||
case repo's origin URL still has the old token embedded.
|
||||
Returns True if origin exists (and was refreshed), False otherwise.
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["git", "remote", "get-url", "origin"],
|
||||
cwd=case_dir, capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return False
|
||||
current_url = result.stdout.strip()
|
||||
# Strip any embedded credentials (https://user:token@host -> https://host)
|
||||
if "@" in current_url and current_url.startswith("https://"):
|
||||
bare_url = "https://" + current_url.split("@", 1)[1]
|
||||
else:
|
||||
bare_url = current_url
|
||||
auth_url = bare_url.replace("https://", f"https://chaim:{_token()}@")
|
||||
if auth_url != current_url:
|
||||
subprocess.run(
|
||||
["git", "remote", "set-url", "origin", auth_url],
|
||||
cwd=case_dir, capture_output=True, env=env,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def commit_and_push(case_dir: str | Path, message: str) -> bool:
|
||||
"""Stage, commit, refresh remote URL with current token, and push.
|
||||
|
||||
Best-effort: returns False on any failure, but logs the reason at
|
||||
WARNING level so issues are visible (the previous code swallowed
|
||||
push failures silently).
|
||||
"""
|
||||
case_dir = Path(case_dir)
|
||||
if not (case_dir / ".git").exists():
|
||||
return False
|
||||
|
||||
env = _git_env()
|
||||
|
||||
subprocess.run(["git", "add", "."], cwd=case_dir, capture_output=True, env=env)
|
||||
commit = subprocess.run(
|
||||
["git", "commit", "-m", message],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
if commit.returncode != 0 and "nothing to commit" not in commit.stdout:
|
||||
logger.warning("Git commit failed in %s: %s", case_dir, commit.stderr or commit.stdout)
|
||||
# Continue anyway — maybe there were no new changes but we still
|
||||
# want to push earlier unpushed commits.
|
||||
|
||||
if not _refresh_remote_url(case_dir, env):
|
||||
logger.warning("No origin remote configured in %s — skipping push", case_dir)
|
||||
return False
|
||||
|
||||
push = subprocess.run(
|
||||
["git", "push"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
if push.returncode != 0:
|
||||
logger.warning("Git push failed in %s: %s", case_dir, push.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def setup_remote_and_push(case_dir: str | Path, repo_clone_url: str) -> bool:
|
||||
"""Add Gitea as remote 'origin' (or update it) and push.
|
||||
|
||||
Used on initial case creation. For routine commits + push on
|
||||
existing repos, prefer ``commit_and_push``.
|
||||
"""
|
||||
case_dir = Path(case_dir)
|
||||
if not (case_dir / ".git").exists():
|
||||
return False
|
||||
|
||||
env = _git_env()
|
||||
|
||||
auth_url = repo_clone_url.replace("https://", f"https://chaim:{_token()}@")
|
||||
|
||||
# Check if remote exists
|
||||
result = subprocess.run(
|
||||
["git", "remote", "get-url", "origin"],
|
||||
cwd=case_dir, capture_output=True, text=True,
|
||||
@@ -81,13 +153,11 @@ def setup_remote_and_push(case_dir: str | Path, repo_clone_url: str) -> bool:
|
||||
cwd=case_dir, capture_output=True, env=env,
|
||||
)
|
||||
|
||||
# Push
|
||||
push_result = subprocess.run(
|
||||
["git", "push", "-u", "origin", "main"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
if push_result.returncode != 0:
|
||||
# Try master branch
|
||||
push_result = subprocess.run(
|
||||
["git", "push", "-u", "origin", "master"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
|
||||
Reference in New Issue
Block a user