diff --git a/docs/specs/DEVX-170.md b/docs/specs/DEVX-170.md new file mode 100644 index 0000000..f4f2afe --- /dev/null +++ b/docs/specs/DEVX-170.md @@ -0,0 +1,33 @@ +# DEVX-170: create_dependency_pr checks out the API-created branch + +## Problem + +After DEVX-169 the dependency branch is created via `POST /branches`, but +`git fetch origin ` only populates `FETCH_HEAD`. The follow-up +`git checkout ` fails silently (`check=False`), commits land on +the wrong ref, and `git push origin ` fails with +`src refspec does not match any`. Observed creating the sso_bridge 0.4.1 +infra dependency PR. + +## Approach + +REQ-1: Fetch the API-created branch into `refs/remotes/origin/` +and force-create the local branch with `git checkout -B +origin/`, both with `check=True` so failures surface. + +## Test Plan + +- Unit test asserts the fetch refspec and `checkout -B` invocations. + +## Deploy Plan + +devx release tag; producers pick it up via pin bumps. + +## Rollback Plan + +Revert; dep-PR creation stays broken (status quo). + +## Acceptance Criteria + +- [x] REQ-1: the clone checks out the API-created branch so commit and + push target `deps/-`; covered by unit tests. diff --git a/src/devx/ci/create_dependency_pr.py b/src/devx/ci/create_dependency_pr.py index c84195a..7ffb7d7 100644 --- a/src/devx/ci/create_dependency_pr.py +++ b/src/devx/ci/create_dependency_pr.py @@ -357,9 +357,21 @@ def cli( else: raise click.ClickException(_("Failed to create branch: {error}", error=str(e))) from None - # Check out the API-created branch inside the target clone. - subprocess.run(["git", "fetch", "origin", f"{branch_name}"], check=False, capture_output=True, cwd=workdir) # nosec B603 B607 - subprocess.run(["git", "checkout", branch_name], check=False, capture_output=True, cwd=workdir) # nosec B603 B607 + # Check out the API-created branch inside the target clone. A plain + # fetch only populates FETCH_HEAD — fetch into the remote-tracking ref + # and force-create the local branch from it. + subprocess.run( # nosec B603 B607 + ["git", "fetch", "origin", f"{branch_name}:refs/remotes/origin/{branch_name}"], + check=True, + capture_output=True, + cwd=workdir, + ) + subprocess.run( # nosec B603 B607 + ["git", "checkout", "-B", branch_name, f"origin/{branch_name}"], + check=True, + capture_output=True, + cwd=workdir, + ) if manifest_path: fields = { diff --git a/tests/unit/test_create_dependency_pr.py b/tests/unit/test_create_dependency_pr.py index af84b09..76ae53e 100644 --- a/tests/unit/test_create_dependency_pr.py +++ b/tests/unit/test_create_dependency_pr.py @@ -557,3 +557,27 @@ class TestBranchCreation: result = self._invoke() assert result.exit_code != 0 assert "Failed to create branch" in result.output + + @patch("devx.ci.create_dependency_pr.create_vikunja_task", return_value=None) + @patch("devx.ci.create_dependency_pr.update_manifest", return_value=True) + @patch("devx.ci.create_dependency_pr.read_manifest_version", return_value="0.9.0") + @patch("devx.ci.create_dependency_pr.find_existing_pr", return_value=None) + @patch("devx.ci.create_dependency_pr.GiteaClient") + @patch("devx.ci.create_dependency_pr.get_ci_token", return_value="tok") + def test_checks_out_remote_tracking_branch( + self, + _token: MagicMock, + mock_client_cls: MagicMock, + _find: MagicMock, + _read: MagicMock, + _update: MagicMock, + _task: MagicMock, + ) -> None: + import subprocess + + self._client(mock_client_cls) + result = self._invoke() + assert result.exit_code == 0 + calls = [c.args[0] for c in subprocess.run.call_args_list] + assert ["git", "fetch", "origin", "deps/sso_bridge-0.9.1:refs/remotes/origin/deps/sso_bridge-0.9.1"] in calls + assert ["git", "checkout", "-B", "deps/sso_bridge-0.9.1", "origin/deps/sso_bridge-0.9.1"] in calls