mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(git): hard-sync target branch to origin instead of bare pull
_sync_target_branch ran a bare `git pull` after checkout; on a workspace clone whose local target branch diverged from origin (inevitable once remote history is rewritten) modern git fatals with "Need to specify how to reconcile divergent branches". On the CEO approve-and-merge path the GitHub merge had already landed, so the failure surfaced as a spurious 400 "Merge failed" and every retry re-hit the same wedged clone. Sync is now fetch + `reset --hard origin/<branch>` — remote-authoritative, matching the helper's intent and self-healing the divergence for all call sites (CEO merge, PM 409-retry sync, post-merge best-effort sync).
This commit is contained in:
+13
-4
@@ -3405,13 +3405,19 @@ class GitService(BaseService):
|
||||
async def _sync_target_branch(
|
||||
self, workspace: Path, target_branch: str, git_token: str
|
||||
) -> str:
|
||||
"""Checkout + pull the target branch, return the tip commit hash.
|
||||
"""Checkout + hard-sync the target branch to origin, return its tip.
|
||||
|
||||
If the target branch has no local ref (common in agent workspaces that
|
||||
only ever checked out their own task branch), fetch it from origin and
|
||||
create a tracking branch before pulling. This prevents the "parent
|
||||
branch doesn't exist locally" SERVICE_ERROR that blocks every leaf→cell
|
||||
create a tracking branch first. This prevents the "parent branch
|
||||
doesn't exist locally" SERVICE_ERROR that blocks every leaf→cell
|
||||
merge in a shared workspace.
|
||||
|
||||
The sync is fetch + ``reset --hard origin/<branch>``, never ``pull``:
|
||||
a bare pull fatals on a divergent local ref ("Need to specify how to
|
||||
reconcile divergent branches"), and a local target branch that has
|
||||
drifted from origin in a workspace clone is cruft by definition — the
|
||||
remote side of the merge is authoritative.
|
||||
"""
|
||||
checkout = await self._run_git(
|
||||
workspace, ["checkout", target_branch], check=False
|
||||
@@ -3439,7 +3445,10 @@ class GitService(BaseService):
|
||||
"tracking_stderr": tracking.stderr.strip(),
|
||||
},
|
||||
)
|
||||
await self._run_git(workspace, ["pull"], token=git_token)
|
||||
await self._run_git(
|
||||
workspace, ["fetch", "origin", target_branch], token=git_token
|
||||
)
|
||||
await self._run_git(workspace, ["reset", "--hard", f"origin/{target_branch}"])
|
||||
log_result = await self._run_git(workspace, ["log", "-1", "--format=%H"])
|
||||
return log_result.stdout.strip()
|
||||
|
||||
|
||||
@@ -918,7 +918,7 @@ async def test_push_propagates_non_gh001_error_unchanged() -> None:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_target_branch_uses_local_ref_when_present() -> None:
|
||||
"""If the target branch already exists locally, just checkout + pull."""
|
||||
"""If the target branch exists locally, checkout + hard-sync to origin."""
|
||||
svc = _service()
|
||||
calls: list[list[str]] = []
|
||||
|
||||
@@ -926,8 +926,6 @@ async def test_sync_target_branch_uses_local_ref_when_present() -> None:
|
||||
calls.append(args)
|
||||
if args[:2] == ["checkout", "feature/backend/parent"]:
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
if args[:1] == ["pull"]:
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
if args[:2] == ["log", "-1"]:
|
||||
return MagicMock(returncode=0, stdout="local-tip-sha", stderr="")
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
@@ -938,8 +936,10 @@ async def test_sync_target_branch_uses_local_ref_when_present() -> None:
|
||||
)
|
||||
assert sha == "local-tip-sha"
|
||||
assert ["checkout", "feature/backend/parent"] in calls
|
||||
assert ["pull"] in calls
|
||||
assert ["fetch", "origin", "feature/backend/parent"] not in calls
|
||||
assert ["fetch", "origin", "feature/backend/parent"] in calls
|
||||
assert ["reset", "--hard", "origin/feature/backend/parent"] in calls
|
||||
assert ["pull"] not in calls
|
||||
assert ["checkout", "-b", "feature/backend/parent"] not in [c[:3] for c in calls]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -956,8 +956,6 @@ async def test_sync_target_branch_fetches_and_tracks_when_local_ref_missing() ->
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
if args[:3] == ["checkout", "-b", "feature/backend/parent"]:
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
if args[:1] == ["pull"]:
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
if args[:2] == ["log", "-1"]:
|
||||
return MagicMock(returncode=0, stdout="origin-tip-sha", stderr="")
|
||||
return MagicMock(returncode=0, stdout="", stderr="")
|
||||
@@ -975,7 +973,8 @@ async def test_sync_target_branch_fetches_and_tracks_when_local_ref_missing() ->
|
||||
"feature/backend/parent",
|
||||
"origin/feature/backend/parent",
|
||||
] in calls
|
||||
assert ["pull"] in calls
|
||||
assert ["reset", "--hard", "origin/feature/backend/parent"] in calls
|
||||
assert ["pull"] not in calls
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user