forgexa-cli 1.22.3__tar.gz → 1.22.5__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.22.3 → forgexa_cli-1.22.5}/PKG-INFO +1 -1
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/daemon.py +54 -6
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/pyproject.toml +1 -1
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/README.md +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/setup.cfg +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.22.3 → forgexa_cli-1.22.5}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.22.
|
|
2
|
+
__version__ = "1.22.5"
|
|
@@ -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.22.
|
|
558
|
+
DAEMON_VERSION = "1.22.5"
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
def _detect_client_type() -> str:
|
|
@@ -2617,8 +2617,18 @@ class WorkspaceManager:
|
|
|
2617
2617
|
|
|
2618
2618
|
async def _remove_broken_worktree(self, main_repo: Path, ws_path: Path, workspace_key: str):
|
|
2619
2619
|
"""Remove a broken worktree directory and clean up stale worktree refs."""
|
|
2620
|
-
#
|
|
2620
|
+
# `git worktree prune` keeps an entry while its directory exists, even
|
|
2621
|
+
# when that directory's .git pointer is corrupt.
|
|
2622
|
+
self._safe_rmtree(ws_path)
|
|
2623
|
+
|
|
2621
2624
|
if main_repo.exists():
|
|
2625
|
+
# Prune also preserves explicitly locked worktrees, even after
|
|
2626
|
+
# their directories disappear. Unlock before pruning so a stale
|
|
2627
|
+
# registration cannot keep its branch ref occupied forever.
|
|
2628
|
+
try:
|
|
2629
|
+
await self._git("worktree", "unlock", str(ws_path), cwd=main_repo)
|
|
2630
|
+
except RuntimeError:
|
|
2631
|
+
pass
|
|
2622
2632
|
try:
|
|
2623
2633
|
await self._git("worktree", "prune", cwd=main_repo)
|
|
2624
2634
|
except RuntimeError:
|
|
@@ -2627,9 +2637,6 @@ class WorkspaceManager:
|
|
|
2627
2637
|
wt_ref = main_repo / ".git" / "worktrees" / workspace_key
|
|
2628
2638
|
if wt_ref.exists():
|
|
2629
2639
|
self._safe_rmtree(wt_ref)
|
|
2630
|
-
# Remove the broken worktree directory using _safe_rmtree so that
|
|
2631
|
-
# read-only git objects on Windows are properly handled.
|
|
2632
|
-
self._safe_rmtree(ws_path)
|
|
2633
2640
|
|
|
2634
2641
|
async def _safe_rmtree_main(self, main_repo: Path) -> None:
|
|
2635
2642
|
"""Remove _main repo, first evicting all linked worktrees to prevent orphans.
|
|
@@ -2677,8 +2684,10 @@ class WorkspaceManager:
|
|
|
2677
2684
|
self,
|
|
2678
2685
|
main_repo: Path,
|
|
2679
2686
|
branch_name: str,
|
|
2687
|
+
*,
|
|
2688
|
+
healthy_only: bool = True,
|
|
2680
2689
|
) -> Path | None:
|
|
2681
|
-
"""Return
|
|
2690
|
+
"""Return a linked worktree already on branch_name."""
|
|
2682
2691
|
try:
|
|
2683
2692
|
raw = await self._git("worktree", "list", "--porcelain", cwd=main_repo)
|
|
2684
2693
|
except RuntimeError:
|
|
@@ -2704,6 +2713,8 @@ class WorkspaceManager:
|
|
|
2704
2713
|
for candidate_path, candidate_branch in entries:
|
|
2705
2714
|
if candidate_path == main_repo or candidate_branch != target_ref:
|
|
2706
2715
|
continue
|
|
2716
|
+
if not healthy_only:
|
|
2717
|
+
return candidate_path
|
|
2707
2718
|
if candidate_path.exists() and await self._is_healthy_worktree(candidate_path, main_repo=main_repo):
|
|
2708
2719
|
return candidate_path
|
|
2709
2720
|
|
|
@@ -3827,6 +3838,43 @@ class WorkspaceManager:
|
|
|
3827
3838
|
cwd=main_repo,
|
|
3828
3839
|
)
|
|
3829
3840
|
except Exception as exc:
|
|
3841
|
+
existing_branch_worktree = await self._find_existing_worktree_for_branch(
|
|
3842
|
+
main_repo, branch_name,
|
|
3843
|
+
)
|
|
3844
|
+
if existing_branch_worktree is not None:
|
|
3845
|
+
logger.info(
|
|
3846
|
+
"Fresh start reusing worktree %s after resetting %s to origin/%s",
|
|
3847
|
+
existing_branch_worktree, branch_name, default_branch,
|
|
3848
|
+
)
|
|
3849
|
+
await self._git(
|
|
3850
|
+
"checkout", "-B", branch_name, f"origin/{default_branch}",
|
|
3851
|
+
cwd=existing_branch_worktree,
|
|
3852
|
+
)
|
|
3853
|
+
return existing_branch_worktree
|
|
3854
|
+
|
|
3855
|
+
stale_branch_worktree = await self._find_existing_worktree_for_branch(
|
|
3856
|
+
main_repo, branch_name, healthy_only=False,
|
|
3857
|
+
)
|
|
3858
|
+
if stale_branch_worktree is not None:
|
|
3859
|
+
logger.warning(
|
|
3860
|
+
"Fresh start found broken worktree %s holding branch %s; removing it before retrying",
|
|
3861
|
+
stale_branch_worktree, branch_name,
|
|
3862
|
+
)
|
|
3863
|
+
try:
|
|
3864
|
+
await self._remove_broken_worktree(
|
|
3865
|
+
main_repo, stale_branch_worktree, stale_branch_worktree.name,
|
|
3866
|
+
)
|
|
3867
|
+
await self._git(
|
|
3868
|
+
"branch", "-f", branch_name, f"origin/{default_branch}",
|
|
3869
|
+
cwd=main_repo,
|
|
3870
|
+
)
|
|
3871
|
+
await self._git(
|
|
3872
|
+
"worktree", "add", str(ws_path), branch_name,
|
|
3873
|
+
cwd=main_repo,
|
|
3874
|
+
)
|
|
3875
|
+
return ws_path
|
|
3876
|
+
except Exception as recovery_exc:
|
|
3877
|
+
exc = recovery_exc
|
|
3830
3878
|
self._safe_rmtree(ws_path)
|
|
3831
3879
|
raise RuntimeError(
|
|
3832
3880
|
f"Failed to create clean fresh worktree for branch '{branch_name}'. "
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|