forgexa-cli 1.18.9__tar.gz → 1.18.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/PKG-INFO +1 -1
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli/daemon.py +17 -3
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/pyproject.toml +1 -1
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/tests/test_auth_and_runtime_commands.py +44 -1
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/README.md +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.18.9 → forgexa_cli-1.18.10}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.18.
|
|
2
|
+
__version__ = "1.18.10"
|
|
@@ -555,7 +555,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
555
555
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
556
556
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
557
557
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
558
|
-
DAEMON_VERSION = "1.18.
|
|
558
|
+
DAEMON_VERSION = "1.18.10"
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
def _detect_client_type() -> str:
|
|
@@ -1716,6 +1716,13 @@ class WorkspaceManager:
|
|
|
1716
1716
|
"""Register an SSH private key for a project."""
|
|
1717
1717
|
self._ssh_keys[project_key] = ssh_private_key
|
|
1718
1718
|
|
|
1719
|
+
@staticmethod
|
|
1720
|
+
def _summarize_git_error(error: Exception) -> str:
|
|
1721
|
+
"""Return a compact Git error without exposing embedded HTTPS credentials."""
|
|
1722
|
+
detail = " ".join(str(error).split())
|
|
1723
|
+
detail = re.sub(r"(https?://)[^/@\s]+@", r"\1***@", detail)
|
|
1724
|
+
return detail[:500]
|
|
1725
|
+
|
|
1719
1726
|
def _migrate_legacy_project_dirs(self) -> None:
|
|
1720
1727
|
"""Move project directories from root/ to root/projects/ (one-time migration).
|
|
1721
1728
|
|
|
@@ -3042,6 +3049,7 @@ class WorkspaceManager:
|
|
|
3042
3049
|
# Retry up to 2 times with short delay for cross-runtime timing issues
|
|
3043
3050
|
# (analysis daemon may have just pushed the branch).
|
|
3044
3051
|
branch_exists_remote = False
|
|
3052
|
+
last_branch_fetch_error: str | None = None
|
|
3045
3053
|
for _check_attempt in range(3 if not fresh_start else 1):
|
|
3046
3054
|
try:
|
|
3047
3055
|
check_proc = await asyncio.create_subprocess_exec(
|
|
@@ -3070,8 +3078,8 @@ class WorkspaceManager:
|
|
|
3070
3078
|
timeout=settings.GIT_FETCH_TIMEOUT,
|
|
3071
3079
|
project_key=project_key,
|
|
3072
3080
|
)
|
|
3073
|
-
except RuntimeError:
|
|
3074
|
-
|
|
3081
|
+
except RuntimeError as exc:
|
|
3082
|
+
last_branch_fetch_error = self._summarize_git_error(exc)
|
|
3075
3083
|
|
|
3076
3084
|
# Fresh analysis with an existing remote branch that has implementation commits:
|
|
3077
3085
|
# delete the remote branch so the agent starts from a clean slate.
|
|
@@ -3138,10 +3146,16 @@ class WorkspaceManager:
|
|
|
3138
3146
|
|
|
3139
3147
|
if expect_branch and not fresh_start and not branch_exists_remote:
|
|
3140
3148
|
self._safe_rmtree(ws_path)
|
|
3149
|
+
fetch_detail = (
|
|
3150
|
+
f" Last branch fetch error: {last_branch_fetch_error}"
|
|
3151
|
+
if last_branch_fetch_error
|
|
3152
|
+
else ""
|
|
3153
|
+
)
|
|
3141
3154
|
raise RuntimeError(
|
|
3142
3155
|
f"Expected remote branch '{branch_name}' to exist, but it could not be "
|
|
3143
3156
|
"fetched or resolved. Refusing to create a continuation workspace from "
|
|
3144
3157
|
f"origin/{default_branch} because that risks overwriting requirement-scoped history."
|
|
3158
|
+
f"{fetch_detail}"
|
|
3145
3159
|
)
|
|
3146
3160
|
|
|
3147
3161
|
if branch_exists_remote and not fresh_start:
|
|
@@ -5,7 +5,7 @@ import io
|
|
|
5
5
|
import json
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from types import SimpleNamespace
|
|
8
|
-
from unittest.mock import MagicMock
|
|
8
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
import pytest
|
|
@@ -196,6 +196,49 @@ def test_httpx_recovery_hints_use_platform_specific_upgrade_commands() -> None:
|
|
|
196
196
|
assert linux_hints[0] == "python3 -m pip install --upgrade forgexa-cli"
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_workspace_reports_last_expected_branch_fetch_error(
|
|
201
|
+
tmp_path: Path,
|
|
202
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
203
|
+
) -> None:
|
|
204
|
+
manager = daemon.WorkspaceManager(str(tmp_path / "daemon-root"))
|
|
205
|
+
project_dir = tmp_path / "project"
|
|
206
|
+
(project_dir / "_main").mkdir(parents=True)
|
|
207
|
+
|
|
208
|
+
class DummyProc:
|
|
209
|
+
returncode = 1
|
|
210
|
+
|
|
211
|
+
async def communicate(self):
|
|
212
|
+
return b"", b""
|
|
213
|
+
|
|
214
|
+
async def fake_git(*args, **kwargs):
|
|
215
|
+
if args[:2] == ("fetch", "origin") and args[2].startswith("feature/SI-647:"):
|
|
216
|
+
raise RuntimeError("Permission denied (publickey).")
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
manager._git = AsyncMock(side_effect=fake_git)
|
|
220
|
+
monkeypatch.setattr(
|
|
221
|
+
daemon.asyncio,
|
|
222
|
+
"create_subprocess_exec",
|
|
223
|
+
AsyncMock(return_value=DummyProc()),
|
|
224
|
+
)
|
|
225
|
+
monkeypatch.setattr(daemon.asyncio, "sleep", AsyncMock())
|
|
226
|
+
|
|
227
|
+
with pytest.raises(
|
|
228
|
+
RuntimeError,
|
|
229
|
+
match="Last branch fetch error: Permission denied \\(publickey\\)\\.",
|
|
230
|
+
):
|
|
231
|
+
await manager._create_worktree_impl(
|
|
232
|
+
project_dir,
|
|
233
|
+
"ssh://git@example.com/repo.git",
|
|
234
|
+
"develop",
|
|
235
|
+
"workflow-keep-branch",
|
|
236
|
+
"feature/SI-647",
|
|
237
|
+
project_key="SI",
|
|
238
|
+
expect_branch=True,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
199
242
|
def test_normalize_target_version_accepts_plain_and_v_prefixed_tags() -> None:
|
|
200
243
|
assert main._normalize_target_version("1.12.3") == "1.12.3"
|
|
201
244
|
assert main._normalize_target_version("v1.12.3") == "1.12.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|