Fix:repo sync auth flow and tighten git utility typing

This commit is contained in:
aran-yogesh 2026-02-17 11:24:22 -08:00
parent 7b1c9bfdca
commit 1a796c1a14
2 changed files with 87 additions and 77 deletions

View file

@ -50,7 +50,6 @@ from .utils.github import (
git_has_uncommitted_changes,
is_valid_git_repo,
remove_directory,
repo_directory_exists,
)
@ -82,43 +81,62 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915
repo_dir = f"/workspace/{repo}"
# Check if directory exists
logger.debug("Checking if repo directory exists at %s", repo_dir)
dir_exists = await loop.run_in_executor(None, repo_directory_exists, sandbox_backend, repo_dir)
is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir)
if dir_exists:
is_git_repo = await loop.run_in_executor(None, is_valid_git_repo, sandbox_backend, repo_dir)
if not is_git_repo:
# Directory missing or not a valid git repo - remove and clone
logger.warning(
"Repo directory missing or not a valid git repo at %s, removing", repo_dir
)
try:
removed = await loop.run_in_executor(None, remove_directory, sandbox_backend, repo_dir)
if not removed:
msg = f"Failed to remove invalid directory at {repo_dir}"
logger.error(msg)
raise RuntimeError(msg)
logger.info("Removed invalid directory, will clone fresh repo")
except Exception:
logger.exception("Failed to remove invalid directory")
raise
# Fall through to clone below
else:
# Valid git repo exists, check for uncommitted changes
logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir)
has_changes = await loop.run_in_executor(
None, git_has_uncommitted_changes, sandbox_backend, repo_dir
)
if not is_git_repo:
# Directory exists but is not a valid git repo - remove and clone
logger.warning("Directory exists but is not a valid git repo at %s, removing", repo_dir)
try:
removed = await loop.run_in_executor(None, remove_directory, sandbox_backend, repo_dir)
if not removed:
msg = f"Failed to remove invalid directory at {repo_dir}"
logger.error(msg)
raise RuntimeError(msg)
logger.info("Removed invalid directory, will clone fresh repo")
except Exception:
logger.exception("Failed to remove invalid directory")
raise
# Fall through to clone below
else:
# Valid git repo exists, check for uncommitted changes
logger.info("Repo exists at %s, checking for uncommitted changes", repo_dir)
has_changes = await loop.run_in_executor(
None, git_has_uncommitted_changes, sandbox_backend, repo_dir
if has_changes:
logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir)
return repo_dir
# No uncommitted changes, safe to pull
logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo)
auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git"
clean_url = f"https://github.com/{owner}/{repo}.git"
# Set authenticated URL for private repos, then restore clean URL.
try:
await loop.run_in_executor(
None,
sandbox_backend.execute,
f"cd {repo_dir} && git remote set-url origin {auth_url}",
)
if has_changes:
logger.warning("Repo has uncommitted changes at %s, skipping pull", repo_dir)
return repo_dir
# No uncommitted changes, safe to pull
logger.info("Repo is clean, pulling latest changes from %s/%s", owner, repo)
# CRITICAL: Ensure remote URL doesn't contain token (clean up from previous runs)
clean_url = f"https://github.com/{owner}/{repo}.git"
pull_result = await loop.run_in_executor(
None, sandbox_backend.execute, f"cd {repo_dir} && git pull origin"
)
logger.debug("Git pull result: exit_code=%s", pull_result.exit_code)
if pull_result.exit_code != 0:
logger.warning(
"Git pull failed with exit code %s: %s",
pull_result.exit_code,
pull_result.output[:200] if pull_result.output else "",
)
except Exception:
logger.exception("Failed to execute git pull")
raise
finally:
try:
await loop.run_in_executor(
None,
@ -126,28 +144,11 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915
f"cd {repo_dir} && git remote set-url origin {clean_url}",
)
except Exception:
logger.exception("Failed to set remote URL")
logger.exception("Failed to restore clean remote URL")
raise
# Pull with authenticated URL
auth_url = f"https://git:{token}@github.com/{owner}/{repo}.git"
try:
pull_result = await loop.run_in_executor(
None, sandbox_backend.execute, f"cd {repo_dir} && git pull {auth_url}"
)
logger.debug("Git pull result: exit_code=%s", pull_result.exit_code)
if pull_result.exit_code != 0:
logger.warning(
"Git pull failed with exit code %s: %s",
pull_result.exit_code,
pull_result.output[:200] if pull_result.output else "",
)
except Exception:
logger.exception("Failed to execute git pull")
raise
logger.info("Repo updated at %s", repo_dir)
return repo_dir
logger.info("Repo updated at %s", repo_dir)
return repo_dir
# Directory doesn't exist or was removed - clone it
logger.info("Cloning repo %s/%s to %s", owner, repo, repo_dir)

View file

@ -4,9 +4,9 @@ from __future__ import annotations
import logging
import shlex
from typing import Any
import httpx
from deepagents.backends.protocol import ExecuteResponse, SandboxBackendProtocol
logger = logging.getLogger(__name__)
@ -15,19 +15,14 @@ HTTP_CREATED = 201
HTTP_UNPROCESSABLE_ENTITY = 422
def _run_git(sandbox_backend: Any, repo_dir: str, command: str) -> Any:
def _run_git(
sandbox_backend: SandboxBackendProtocol, repo_dir: str, command: str
) -> ExecuteResponse:
"""Run a git command in the sandbox repo directory."""
return sandbox_backend.execute(f"cd {repo_dir} && {command}")
def repo_directory_exists(sandbox_backend: Any, repo_dir: str) -> bool:
"""Check if repository directory exists."""
safe_repo_dir = shlex.quote(repo_dir)
result = sandbox_backend.execute(f"test -d {safe_repo_dir} && echo exists")
return result.exit_code == 0 and "exists" in result.output
def is_valid_git_repo(sandbox_backend: Any, repo_dir: str) -> bool:
def is_valid_git_repo(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> bool:
"""Check if directory is a valid git repository."""
git_dir = f"{repo_dir}/.git"
safe_git_dir = shlex.quote(git_dir)
@ -35,25 +30,31 @@ def is_valid_git_repo(sandbox_backend: Any, repo_dir: str) -> bool:
return result.exit_code == 0 and "exists" in result.output
def remove_directory(sandbox_backend: Any, repo_dir: str) -> bool:
def remove_directory(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> bool:
"""Remove a directory and all its contents."""
safe_repo_dir = shlex.quote(repo_dir)
result = sandbox_backend.execute(f"rm -rf {safe_repo_dir}")
return result.exit_code == 0
def git_has_uncommitted_changes(sandbox_backend: Any, repo_dir: str) -> bool:
def git_has_uncommitted_changes(
sandbox_backend: SandboxBackendProtocol, repo_dir: str
) -> bool:
"""Check whether the repo has uncommitted changes."""
result = _run_git(sandbox_backend, repo_dir, "git status --porcelain")
return result.exit_code == 0 and bool(result.output.strip())
def git_fetch_origin(sandbox_backend: Any, repo_dir: str) -> Any:
def git_fetch_origin(
sandbox_backend: SandboxBackendProtocol, repo_dir: str
) -> ExecuteResponse:
"""Fetch latest from origin (best-effort)."""
return _run_git(sandbox_backend, repo_dir, "git fetch origin 2>/dev/null || true")
def git_has_unpushed_commits(sandbox_backend: Any, repo_dir: str) -> bool:
def git_has_unpushed_commits(
sandbox_backend: SandboxBackendProtocol, repo_dir: str
) -> bool:
"""Check whether there are commits not pushed to upstream."""
git_log_cmd = (
"git log --oneline @{upstream}..HEAD 2>/dev/null "
@ -63,13 +64,15 @@ def git_has_unpushed_commits(sandbox_backend: Any, repo_dir: str) -> bool:
return result.exit_code == 0 and bool(result.output.strip())
def git_current_branch(sandbox_backend: Any, repo_dir: str) -> str:
def git_current_branch(sandbox_backend: SandboxBackendProtocol, repo_dir: str) -> str:
"""Get the current git branch name."""
result = _run_git(sandbox_backend, repo_dir, "git rev-parse --abbrev-ref HEAD")
return result.output.strip() if result.exit_code == 0 else ""
def git_checkout_branch(sandbox_backend: Any, repo_dir: str, branch: str) -> bool:
def git_checkout_branch(
sandbox_backend: SandboxBackendProtocol, repo_dir: str, branch: str
) -> bool:
"""Checkout branch, creating it if needed."""
safe_branch = shlex.quote(branch)
checkout_result = _run_git(
@ -82,7 +85,7 @@ def git_checkout_branch(sandbox_backend: Any, repo_dir: str, branch: str) -> boo
def git_config_user(
sandbox_backend: Any,
sandbox_backend: SandboxBackendProtocol,
repo_dir: str,
name: str,
email: str,
@ -94,18 +97,24 @@ def git_config_user(
_run_git(sandbox_backend, repo_dir, f"git config user.email {safe_email}")
def git_add_all(sandbox_backend: Any, repo_dir: str) -> Any:
def git_add_all(
sandbox_backend: SandboxBackendProtocol, repo_dir: str
) -> ExecuteResponse:
"""Stage all changes."""
return _run_git(sandbox_backend, repo_dir, "git add -A")
def git_commit(sandbox_backend: Any, repo_dir: str, message: str) -> Any:
def git_commit(
sandbox_backend: SandboxBackendProtocol, repo_dir: str, message: str
) -> ExecuteResponse:
"""Commit staged changes with the given message."""
safe_message = shlex.quote(message)
return _run_git(sandbox_backend, repo_dir, f"git commit -m {safe_message}")
def git_get_remote_url(sandbox_backend: Any, repo_dir: str) -> str | None:
def git_get_remote_url(
sandbox_backend: SandboxBackendProtocol, repo_dir: str
) -> str | None:
"""Get the origin remote URL."""
result = _run_git(sandbox_backend, repo_dir, "git remote get-url origin")
if result.exit_code != 0:
@ -114,11 +123,11 @@ def git_get_remote_url(sandbox_backend: Any, repo_dir: str) -> str | None:
def git_push(
sandbox_backend: Any,
sandbox_backend: SandboxBackendProtocol,
repo_dir: str,
branch: str,
github_token: str | None = None,
) -> Any:
) -> ExecuteResponse:
"""Push the branch to origin, using a token if needed."""
safe_branch = shlex.quote(branch)
remote_url = git_get_remote_url(sandbox_backend, repo_dir)