mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(git): push + open PR on the task branch by name, not the current checkout
A developer's single clone is shared across all of their tasks, so by the QA-submission / open_pr boundary the workspace is usually parked on a LATER task's branch. push_task_branch / push_for_task asserted the current branch and pushed it, and create_pull_request used get_current_branch as the PR head — so for an earlier task the push was rejected (BRANCH_MISMATCH) and the locally-committed work never reached origin, leaving the task branch empty and open_pr failing with GitHub's "No commits between" 422. The work was committed correctly on the local task branch, just never pushed. Operate on the task's recorded branch by name, independent of the checkout: - push() takes an explicit branch and pushes that named ref - push_task_branch / push_for_task push the task's branch by name (drop the assert-on-current-branch gate that rejected the shared-clone case) - create_pull_request uses the task's branch_name as the PR head via the new _pr_head_branch helper Regression tests assert push and PR-head target the task branch from any checkout. 45 git + 735 gateway + 59 git-integration tests green; ruff/mypy/ xenon clean.
This commit is contained in:
@@ -134,22 +134,79 @@ async def test_project_for_task_uses_project_id_when_present() -> None:
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_push_task_branch_resolves_workspace_and_pushes() -> None:
|
||||
"""Resolves the task's project + workspace, then pushes; returns the count."""
|
||||
async def test_push_task_branch_pushes_task_branch_by_name() -> None:
|
||||
"""Pushes the task's branch BY NAME (independent of the current checkout).
|
||||
|
||||
A dev's clone is shared across many tasks, so by the QA-submission /
|
||||
open_pr boundary it is usually parked on a LATER task's branch. The old
|
||||
assert-on-current-branch gate rejected the push and the locally-committed
|
||||
work never reached origin; the push must target the named ref instead.
|
||||
"""
|
||||
task = MagicMock(branch_name="feature/backend/abc")
|
||||
project = MagicMock(slug="roboco")
|
||||
svc = _service()
|
||||
_bind(svc, "_assert_task_owned_with_branch", AsyncMock(return_value=task))
|
||||
_bind(svc, "_project_for_task", AsyncMock(return_value=project))
|
||||
_bind(svc, "get_workspace", AsyncMock(return_value=Path("/tmp/ws")))
|
||||
_bind(svc, "_assert_on_task_branch", AsyncMock())
|
||||
assert_branch = AsyncMock()
|
||||
_bind(svc, "_assert_on_task_branch", assert_branch)
|
||||
push_mock = AsyncMock(return_value=("feature/backend/abc", _PUSHED_COMMIT_COUNT))
|
||||
_bind(svc, "push", push_mock)
|
||||
|
||||
pushed = await svc.push_task_branch(uuid4(), uuid4())
|
||||
|
||||
assert pushed == _PUSHED_COMMIT_COUNT
|
||||
push_mock.assert_awaited_once_with(Path("/tmp/ws"))
|
||||
push_mock.assert_awaited_once_with(Path("/tmp/ws"), branch="feature/backend/abc")
|
||||
# The old current-branch gate is no longer consulted on the push path.
|
||||
assert_branch.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_push_targets_explicit_branch_not_current_checkout() -> None:
|
||||
"""push(branch=X) pushes X by ref even when the workspace is on Y."""
|
||||
svc = _service()
|
||||
_bind(svc, "get_current_branch", AsyncMock(return_value="feature/frontend/OTHER"))
|
||||
_bind(svc, "_token_for_workspace", AsyncMock(return_value=None))
|
||||
calls: list[list[str]] = []
|
||||
|
||||
async def _run_git(_ws: object, args: list[str], **_kw: object) -> MagicMock:
|
||||
calls.append(args)
|
||||
res = MagicMock()
|
||||
res.returncode = 0
|
||||
res.stdout = "3" if args[:2] == ["rev-list", "--count"] else ""
|
||||
return res
|
||||
|
||||
_bind(svc, "_run_git", AsyncMock(side_effect=_run_git))
|
||||
|
||||
branch, _pushed = await svc.push(Path("/tmp/ws"), branch="feature/frontend/TASK")
|
||||
|
||||
assert branch == "feature/frontend/TASK"
|
||||
push_args = next(a for a in calls if a and a[0] == "push")
|
||||
assert "feature/frontend/TASK" in push_args
|
||||
assert "feature/frontend/OTHER" not in push_args
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pr_head_is_task_branch_not_current() -> None:
|
||||
"""The PR head is the task's recorded branch, not the workspace checkout."""
|
||||
task = MagicMock(branch_name="feature/frontend/TASK")
|
||||
svc = _service()
|
||||
_bind(svc, "get_current_branch", AsyncMock(return_value="feature/frontend/OTHER"))
|
||||
req = MagicMock(task_id=uuid4())
|
||||
with patch("roboco.services.git.get_task_service") as gts:
|
||||
gts.return_value.get = AsyncMock(return_value=task)
|
||||
head = await svc._pr_head_branch(Path("/tmp/ws"), req)
|
||||
assert head == "feature/frontend/TASK"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pr_head_falls_back_to_current_when_no_task() -> None:
|
||||
"""No task_id → the PR head is the current checkout (unchanged behavior)."""
|
||||
svc = _service()
|
||||
_bind(svc, "get_current_branch", AsyncMock(return_value="feature/frontend/CUR"))
|
||||
req = MagicMock(task_id=None)
|
||||
head = await svc._pr_head_branch(Path("/tmp/ws"), req)
|
||||
assert head == "feature/frontend/CUR"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user