mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(git): idempotent branch creation — checkout existing branch instead of failing 128
A prior claim attempt can create the branch on disk before the DB records branch_name (the claim rolls its fields back, but the on-disk branch persists). A plain checkout -b then fails 'already exists' (exit 128), and the resulting error-handling cascade is how branch creation spiraled into INTERNAL_ERROR. Fall back to checkout <branch> when checkout -b returns non-zero.
This commit is contained in:
+11
-1
@@ -835,7 +835,17 @@ class GitService(BaseService):
|
|||||||
token=project_token,
|
token=project_token,
|
||||||
timeout=_network_git_timeout(),
|
timeout=_network_git_timeout(),
|
||||||
)
|
)
|
||||||
await self._run_git(workspace, ["checkout", "-b", branch_name])
|
# Idempotent branch creation: a prior attempt may have created the
|
||||||
|
# branch on disk but failed before the DB recorded branch_name (the
|
||||||
|
# claim rolls back its fields, but the on-disk branch persists). A
|
||||||
|
# plain `checkout -b` then fails "already exists" (exit 128), and the
|
||||||
|
# resulting error-handling cascade is how a retry spirals. Switch to
|
||||||
|
# the existing branch instead.
|
||||||
|
created = await self._run_git(
|
||||||
|
workspace, ["checkout", "-b", branch_name], check=False
|
||||||
|
)
|
||||||
|
if created.returncode != 0:
|
||||||
|
await self._run_git(workspace, ["checkout", branch_name])
|
||||||
await self._run_git(
|
await self._run_git(
|
||||||
workspace,
|
workspace,
|
||||||
["push", "-u", "origin", branch_name],
|
["push", "-u", "origin", branch_name],
|
||||||
|
|||||||
@@ -299,3 +299,51 @@ async def test_commit_uses_longer_timeout_for_staging_and_commit() -> None:
|
|||||||
assert timeouts_by_subcmd["commit"] == settings.git_commit_timeout_seconds
|
assert timeouts_by_subcmd["commit"] == settings.git_commit_timeout_seconds
|
||||||
# ...while the cheap read-only ops kept the default (None → default budget).
|
# ...while the cheap read-only ops kept the default (None → default budget).
|
||||||
assert timeouts_by_subcmd["log"] is None
|
assert timeouts_by_subcmd["log"] is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_branch_idempotent_when_branch_already_exists() -> None:
|
||||||
|
# A prior attempt may have created the branch on disk before the DB recorded
|
||||||
|
# branch_name; `checkout -b` then fails 128. create_branch must switch to the
|
||||||
|
# existing branch instead of raising (the raise triggered a retry cascade).
|
||||||
|
from roboco.api.schemas.git import GitCreateBranchRequest
|
||||||
|
|
||||||
|
branch = "feature/backend/abc12345--def67890"
|
||||||
|
svc = _service()
|
||||||
|
object.__setattr__(svc, "_resolve_base_branch", AsyncMock(return_value="master"))
|
||||||
|
object.__setattr__(svc, "_project_default_branch", AsyncMock(return_value="master"))
|
||||||
|
object.__setattr__(svc, "_token_for_project", AsyncMock(return_value=None))
|
||||||
|
object.__setattr__(
|
||||||
|
svc, "_checkout_base_with_fallback", AsyncMock(return_value="master")
|
||||||
|
)
|
||||||
|
|
||||||
|
calls: list[list[str]] = []
|
||||||
|
|
||||||
|
async def fake_run_git(_workspace: object, args: list[str], **_kw: object) -> object:
|
||||||
|
calls.append(list(args))
|
||||||
|
rc = 1 if list(args[:2]) == ["checkout", "-b"] else 0
|
||||||
|
return MagicMock(stdout="", returncode=rc)
|
||||||
|
|
||||||
|
object.__setattr__(svc, "_run_git", fake_run_git)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("roboco.services.git.build_branch_name", AsyncMock(return_value=branch)),
|
||||||
|
patch(
|
||||||
|
"roboco.services.git.get_task_service",
|
||||||
|
MagicMock(return_value=MagicMock(update=AsyncMock())),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await svc.create_branch(
|
||||||
|
Path("/tmp/ws"),
|
||||||
|
"backend",
|
||||||
|
GitCreateBranchRequest(
|
||||||
|
project_slug="roboco-api",
|
||||||
|
task_id=uuid4(),
|
||||||
|
branch_type="feature",
|
||||||
|
agent_id=str(uuid4()),
|
||||||
|
parent_branch=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ["checkout", "-b", branch] in calls, "checkout -b attempted"
|
||||||
|
assert ["checkout", branch] in calls, "fell back to existing branch on 128"
|
||||||
|
|||||||
Reference in New Issue
Block a user