From 95a7454bee0ef7231ad93cafbfa25079422c6fa8 Mon Sep 17 00:00:00 2001 From: marxlaml Date: Fri, 22 May 2026 21:32:51 +0200 Subject: [PATCH 1/3] fix: handle bare repos in branch creation and checkout - Fall back to symbolic-ref when checkout --orphan fails on bare repos\n- Fall back to symbolic-ref when checkout fails on bare repos\n- Make get_current_branch handle bare repos with unborn branches\n- Add integration tests for bare repo branch operations\n\nQuality gates: pytest integration tests (12 passed) --- apps/api/src/utils/git_control.py | 27 ++++++++++++++++--- .../api/tests/integration/test_git_control.py | 14 ++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/apps/api/src/utils/git_control.py b/apps/api/src/utils/git_control.py index becebc1..92e57ba 100644 --- a/apps/api/src/utils/git_control.py +++ b/apps/api/src/utils/git_control.py @@ -124,7 +124,15 @@ def create_branch(repo_path: str, name: str, base_branch: str = "HEAD") -> None: try: _run_git_command(repo_path, "rev-parse", "--verify", "HEAD^{commit}") except RuntimeError: - _run_git_command(repo_path, "checkout", "--orphan", name) + # No commits yet - empty repository + try: + _run_git_command(repo_path, "checkout", "--orphan", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise return _run_git_command(repo_path, "branch", name, base_branch) @@ -155,7 +163,14 @@ def checkout_branch(repo_path: str, name: str) -> None: Raises: RuntimeError: If checkout fails """ - _run_git_command(repo_path, "checkout", name) + try: + _run_git_command(repo_path, "checkout", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise def commit_changes( @@ -290,6 +305,10 @@ def get_current_branch(repo_path: str) -> str: Current branch name """ try: - return _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + branch = _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + if branch != "HEAD": + return branch except RuntimeError: - return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() + pass + + return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() diff --git a/apps/api/tests/integration/test_git_control.py b/apps/api/tests/integration/test_git_control.py index a7ab139..37d5425 100644 --- a/apps/api/tests/integration/test_git_control.py +++ b/apps/api/tests/integration/test_git_control.py @@ -70,6 +70,20 @@ def test_get_current_branch_handles_unborn_main() -> None: assert get_current_branch(tmpdir) == "main" +def test_create_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + create_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + +def test_checkout_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + checkout_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + class TestBranchOperations: """Tests for branch management functions.""" From 1c9458330700dc23089409df5ff4668a984f7f9f Mon Sep 17 00:00:00 2001 From: marxlaml Date: Fri, 22 May 2026 21:32:51 +0200 Subject: [PATCH 2/3] fix: handle bare repos in branch creation and checkout - Fall back to symbolic-ref when checkout --orphan fails on bare repos\n- Fall back to symbolic-ref when checkout fails on bare repos\n- Make get_current_branch handle bare repos with unborn branches\n- Add integration tests for bare repo branch operations\n\nQuality gates: pytest integration tests (12 passed) --- apps/api/src/utils/git_control.py | 27 ++++++++++++++++--- .../api/tests/integration/test_git_control.py | 14 ++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/apps/api/src/utils/git_control.py b/apps/api/src/utils/git_control.py index becebc1..92e57ba 100644 --- a/apps/api/src/utils/git_control.py +++ b/apps/api/src/utils/git_control.py @@ -124,7 +124,15 @@ def create_branch(repo_path: str, name: str, base_branch: str = "HEAD") -> None: try: _run_git_command(repo_path, "rev-parse", "--verify", "HEAD^{commit}") except RuntimeError: - _run_git_command(repo_path, "checkout", "--orphan", name) + # No commits yet - empty repository + try: + _run_git_command(repo_path, "checkout", "--orphan", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise return _run_git_command(repo_path, "branch", name, base_branch) @@ -155,7 +163,14 @@ def checkout_branch(repo_path: str, name: str) -> None: Raises: RuntimeError: If checkout fails """ - _run_git_command(repo_path, "checkout", name) + try: + _run_git_command(repo_path, "checkout", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise def commit_changes( @@ -290,6 +305,10 @@ def get_current_branch(repo_path: str) -> str: Current branch name """ try: - return _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + branch = _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + if branch != "HEAD": + return branch except RuntimeError: - return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() + pass + + return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() diff --git a/apps/api/tests/integration/test_git_control.py b/apps/api/tests/integration/test_git_control.py index a7ab139..37d5425 100644 --- a/apps/api/tests/integration/test_git_control.py +++ b/apps/api/tests/integration/test_git_control.py @@ -70,6 +70,20 @@ def test_get_current_branch_handles_unborn_main() -> None: assert get_current_branch(tmpdir) == "main" +def test_create_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + create_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + +def test_checkout_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + checkout_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + class TestBranchOperations: """Tests for branch management functions.""" From 20a5f6a9a1f69e92315d25a39546580ec9ef5b4a Mon Sep 17 00:00:00 2001 From: marxlaml Date: Fri, 22 May 2026 21:39:28 +0200 Subject: [PATCH 3/3] feat: session management fixes and sessions hub - Add confirmation dialogs for stop/delete on dashboard - Filter deleted sessions immediately without reload - Add tunnel health polling with error badges - Add Sessions nav item with active count badge - Route /sessions to SessionsPage component Quality gates: 43/43 tests pass, typecheck pass, lint pass Refs: openspec/changes/session-management-fixes Refs: openspec/changes/sessions-hub --- apps/web/src/components/app-shell.tsx | 6 +- apps/web/src/pages/dashboard.tsx | 149 ++++++++++++++++++++------ apps/web/src/router.tsx | 3 +- 3 files changed, 123 insertions(+), 35 deletions(-) diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx index fd2a02f..8636e49 100644 --- a/apps/web/src/components/app-shell.tsx +++ b/apps/web/src/components/app-shell.tsx @@ -9,8 +9,9 @@ import { useSessions } from "../state/sessions"; import { Icon } from "./icon"; import type { IconName } from "../utils/icons"; -const NAV_ITEMS: { to: string; label: string; icon: IconName }[] = [ +const NAV_ITEMS: { to: string; label: string; icon: IconName; badge?: "sessions" }[] = [ { to: "/", label: "Home", icon: "dashboard" }, + { to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" }, { to: "/projects", label: "Projects", icon: "projects" }, { to: "/tool-workshop", label: "Tool Workshop", icon: "settings" }, { to: "/settings", label: "Settings", icon: "settings" } @@ -83,7 +84,6 @@ export const AppShell = () => {