Spaces:
Running
Running
| import asyncio | |
| import sqlite3 | |
| import logging | |
| import re | |
| import time | |
| from backend.services.usb_vault import get_secret | |
| from backend.services.usb_monitor import get_db_path | |
| from backend.ws.agent_ws import ws_manager | |
| # Ensure DB table exists | |
| def init_github_db(): | |
| try: | |
| with sqlite3.connect(get_db_path()) as conn: | |
| conn.execute('PRAGMA journal_mode=WAL') | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS commit_history ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| commit_hash TEXT, | |
| message TEXT, | |
| timestamp DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| """) | |
| conn.commit() | |
| except Exception as e: | |
| logging.error(f"Failed to init github db: {e}") | |
| init_github_db() | |
| # Mutex to prevent concurrent commits | |
| commit_lock = asyncio.Lock() | |
| async def run_git(cmd: str, cwd: str = None) -> tuple[int, str, str]: | |
| """Runs a git command and returns (returncode, stdout, stderr).""" | |
| proc = await asyncio.create_subprocess_shell( | |
| cmd, | |
| cwd=cwd or '.', | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE | |
| ) | |
| stdout, stderr = await proc.communicate() | |
| return proc.returncode, stdout.decode().strip(), stderr.decode().strip() | |
| async def execute_commit(message: str) -> dict: | |
| """Executes a git commit and push with safety checks, queueing, and rate-limit backoff.""" | |
| async with commit_lock: | |
| logging.info("GITHUB_ENGINE: Acquired commit lock.") | |
| # 1. Fetch token from Vault | |
| token = get_secret("GITHUB_TOKEN") | |
| if not token: | |
| return {"status": "error", "message": "GitHub Token not found in vault. Please inject it securely via EXE."} | |
| # 2. Incremental Diff Compute | |
| # Check if there are unstaged changes or untracked files | |
| await run_git("git add -A") | |
| code, stdout, stderr = await run_git("git diff --cached --name-only") | |
| if not stdout: | |
| return {"status": "skipped", "message": "No incremental changes detected. Aborting commit."} | |
| logging.info(f"GITHUB_ENGINE: Staged files for commit:\n{stdout}") | |
| # 3. Commit | |
| code, c_out, c_err = await run_git(f'git commit -m "{message}"') | |
| if code != 0: | |
| return {"status": "error", "message": f"Commit failed: {c_err or c_out}"} | |
| # Extract commit hash | |
| hash_code, h_out, h_err = await run_git("git rev-parse HEAD") | |
| commit_hash = h_out if hash_code == 0 else "UNKNOWN_HASH" | |
| # 4. Push with Rate Limit Backoff | |
| # Using a dummy remote URL structure that includes the PAT. | |
| # Ensure your git remote is configured correctly if relying on system git. | |
| # For simplicity, we just run git push. | |
| max_retries = 3 | |
| attempt = 0 | |
| push_success = False | |
| while attempt < max_retries: | |
| attempt += 1 | |
| logging.info(f"GITHUB_ENGINE: Pushing to remote (Attempt {attempt})...") | |
| p_code, p_out, p_err = await run_git("git push") | |
| if p_code == 0: | |
| push_success = True | |
| break | |
| # 5. Rate-Limit 403/429 Backoff Parsing | |
| # GitHub usually responds with HTTP 403 or 429 and a "retry-after" header embedded in the stderr proxy response. | |
| combined_err = (p_err + " " + p_out).lower() | |
| if "403" in combined_err or "429" in combined_err or "rate limit" in combined_err: | |
| retry_match = re.search(r"retry-after[:\s]+(\d+)", combined_err) | |
| sleep_time = int(retry_match.group(1)) if retry_match else 60 | |
| logging.warning(f"GITHUB_ENGINE: Rate limit hit. Backing off for {sleep_time} seconds.") | |
| await ws_manager.broadcast("github:rate_limit", {"wait_seconds": sleep_time}) | |
| await asyncio.sleep(sleep_time) | |
| else: | |
| return {"status": "error", "message": f"Push failed: {p_err}"} | |
| if not push_success: | |
| return {"status": "error", "message": "Push failed after max retries."} | |
| # 6. SQLite History Logging | |
| try: | |
| with sqlite3.connect(get_db_path()) as conn: | |
| conn.execute('PRAGMA journal_mode=WAL') | |
| conn.execute("INSERT INTO commit_history (commit_hash, message) VALUES (?, ?)", (commit_hash, message)) | |
| conn.commit() | |
| except Exception as e: | |
| logging.error(f"Failed to log commit history: {e}") | |
| # 7. WebSocket Event Emitted | |
| await ws_manager.broadcast("github:commit_success", { | |
| "hash": commit_hash, | |
| "message": message, | |
| "timestamp": time.time() | |
| }) | |
| logging.info(f"GITHUB_ENGINE: Commit and push successful! Hash: {commit_hash}") | |
| return {"status": "success", "hash": commit_hash, "message": "Successfully committed and pushed to remote."} | |