Skip to content

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Jan 13, 2026

Summary

  • Add new Git REST API router with 6 endpoints for git operations
  • Implement WebSocket broadcast for branch creation events
  • Add 46 comprehensive integration tests following TDD principles
  • Update test conftest to handle git_branches and changelog table cleanup

Endpoints

Endpoint Method Description
/api/projects/{id}/git/branches POST Create feature branch for an issue
/api/projects/{id}/git/branches GET List branches for a project
/api/projects/{id}/git/branches/{name} GET Get branch details
/api/projects/{id}/git/commit POST Create commit for a task
/api/projects/{id}/git/commits GET List commits with optional limit
/api/projects/{id}/git/status GET Get working tree status

Files Changed

  • codeframe/ui/routers/git.py - New router module (620 lines)
  • codeframe/ui/server.py - Register git router
  • codeframe/ui/websocket_broadcasts.py - Add broadcast_branch_created helper
  • tests/api/test_git_api.py - Integration tests (764 lines)
  • tests/api/conftest.py - Add cleanup for git_branches and changelog tables

Unblocks

This PR unblocks the following dependent tickets:

Test plan

  • All 46 new git API tests pass
  • All 274 existing API tests pass
  • Ruff linting passes
  • Manual verification of endpoints via Swagger UI
  • Verify WebSocket broadcasts with connected dashboard

Summary by CodeRabbit

  • New Features

    • Git REST API for projects: create/list/get branches, create/list commits, and repository status — includes branch/file validation and clear error responses.
  • Realtime

    • Broadcasts real-time notifications when branches or commits are created.
  • Tests

    • Comprehensive Git API test suite and improved test cleanup; verifies auth and validation behavior.
  • Chores

    • Git routes mounted in the main UI server.

✏️ Tip: You can customize this high-level summary in your review settings.

Implement REST endpoints for git operations to unblock dependent tickets:
- POST /api/projects/{id}/git/branches - Create feature branch
- GET /api/projects/{id}/git/branches - List branches
- GET /api/projects/{id}/git/branches/{name} - Get branch details
- POST /api/projects/{id}/git/commit - Create commit
- GET /api/projects/{id}/git/commits - List commits
- GET /api/projects/{id}/git/status - Get working tree status

Add broadcast_branch_created WebSocket helper for real-time updates.
Include 46 comprehensive integration tests following TDD principles.

Unblocks: #271 (Git Commit UI), #272 (PR Creation API), #279 (Git Merge API)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Walkthrough

Adds a new Git REST router under /api/projects/{project_id}/git/* for branch and commit management plus repo status, integrates GitWorkflowManager and DB checks, broadcasts branch_created WebSocket events, updates persistence with branch lookup, mounts router in the server, and adds comprehensive tests and cleanup.

Changes

Cohort / File(s) Summary
Git Router
codeframe/ui/routers/git.py
New REST router with endpoints for branch create/list/get, commit create/list, and git status; request/response models; branch & file-path validation; project/access checks; GitWorkflowManager usage; event broadcasts; error handling and logging.
Server Integration
codeframe/ui/server.py
Imports and mounts the new git router in main app and test server setup.
WebSocket Broadcasts
codeframe/ui/websocket_broadcasts.py
Adds broadcast_branch_created(manager, project_id, branch_name, issue_number, issue_id=None) to emit "branch_created" messages with metadata and timestamp; includes error handling and logging.
Persistence: Database Facade
codeframe/persistence/database.py
Adds delegation method get_branch_by_name_and_issues(...) to Database facade.
Persistence: Git Repository
codeframe/persistence/repositories/git_repository.py
Adds get_branch_by_name_and_issues(branch_name, issue_ids) performing a parameterized IN-query returning a matching git_branches row or None.
Tests & Fixtures
tests/api/conftest.py, tests/api/test_git_api.py
Test cleanup updated to drop changelog and git_branches; new comprehensive test module exercising branch/commit/status endpoints, ephemeral Git repo fixtures, validation/security checks, and auth behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as Git Router
    participant DB as Database
    participant GitMgr as GitWorkflowManager
    participant WS as WebSocket Manager

    Client->>Server: POST /api/projects/{id}/git/branches (issue_number, title)
    Server->>DB: get_project_or_404(project_id)
    Server->>DB: check_project_access(user, project_id)
    DB-->>Server: project & access OK
    Server->>GitMgr: create_feature_branch(params)
    GitMgr-->>Server: branch created / error
    Server->>DB: persist git_branches metadata
    Server->>WS: broadcast_branch_created(project_id, branch_name, issue_number, issue_id?)
    Server-->>Client: 201 BranchCreateResponse (branch metadata)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

"I hopped along a branch so bright,
I made a commit by starlit night,
I sent a ping across the wire,
The websocket cheered — a tiny choir,
🐇🌿"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a Git REST API for branch and commit operations, which is the primary purpose of this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/git-rest-api

🧹 Recent nitpick comments
tests/api/test_git_api.py (1)

802-821: Consider using a context manager or try/finally for auth header restoration.

If the assertions fail, the auth header won't be restored, potentially affecting subsequent tests in the same test run. Consider restructuring to ensure cleanup:

♻️ Suggested improvement
     def test_create_branch_requires_auth(self, api_client, test_project_with_issue):
         """Test that branch creation requires authentication."""
         project = test_project_with_issue
 
         # Remove auth header
-        del api_client.headers["Authorization"]
-
-        response = api_client.post(
-            f"/api/projects/{project['project_id']}/git/branches",
-            json={
-                "issue_number": project["issue_number"],
-                "issue_title": project["issue_title"],
-            },
-        )
-
-        # Restore auth header for cleanup
-        from tests.api.conftest import create_test_jwt_token
-        api_client.headers["Authorization"] = f"Bearer {create_test_jwt_token()}"
-
-        assert response.status_code == 401
+        original_auth = api_client.headers.pop("Authorization")
+        try:
+            response = api_client.post(
+                f"/api/projects/{project['project_id']}/git/branches",
+                json={
+                    "issue_number": project["issue_number"],
+                    "issue_title": project["issue_title"],
+                },
+            )
+            assert response.status_code == 401
+        finally:
+            api_client.headers["Authorization"] = original_auth
codeframe/ui/routers/git.py (3)

166-179: Minor: Redundant validation in CommitRequest.

The files_modified field already has min_length=1 in the Field definition (line 170), so the validate_files validator (lines 173-179) is redundant. The Field constraint alone will reject empty lists.

♻️ Suggested simplification
 class CommitRequest(BaseModel):
     """Request model for commit creation."""

     task_id: int = Field(..., description="Task ID this commit is for")
     files_modified: List[str] = Field(..., min_length=1, description="List of modified files")
     agent_id: str = Field(..., min_length=1, description="Agent ID making the commit")
-
-    @field_validator("files_modified")
-    @classmethod
-    def validate_files(cls, v: List[str]) -> List[str]:
-        """Validate files list is not empty."""
-        if not v:
-            raise ValueError("files_modified cannot be empty")
-        return v

419-430: Consider database-level filtering for better performance at scale.

Currently fetches all branches by status and filters in Python (lines 424-430). For projects with many branches, consider adding a database method that filters by both status and issue IDs in a single query.

This is acceptable for typical use cases but may need optimization if the number of branches grows significantly.


536-538: Minor: Redundant validation.

The check if not request.files_modified is redundant since CommitRequest.files_modified already has min_length=1 validation in the Pydantic model. Pydantic will reject empty lists with 422 before this code runs.

♻️ Consider removing redundant check
     # Get project and check access
     project = get_project_or_404(db, project_id)
     check_project_access(db, current_user, project_id)

-    # Validate files_modified is not empty
-    if not request.files_modified:
-        raise HTTPException(status_code=400, detail="No files to commit")
-
     # Get task and verify it belongs to this project
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4765445 and 2392d65.

📒 Files selected for processing (2)
  • codeframe/ui/routers/git.py
  • tests/api/test_git_api.py
🧰 Additional context used
📓 Path-based instructions (1)
codeframe/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/**/*.py: Use Python 3.11+ for backend development
Use FastAPI framework for backend API development
Use SQLite with async support (aiosqlite) for database operations
Use tiktoken for token counting
Run linting with ruff check .
Import and use domain-specific repositories from codeframe/persistence/repositories/ for data access instead of direct database calls

Files:

  • codeframe/ui/routers/git.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
🧬 Code graph analysis (1)
tests/api/test_git_api.py (2)
tests/api/conftest.py (2)
  • api_client (67-177)
  • create_test_jwt_token (43-63)
codeframe/core/models.py (2)
  • issue_number (238-239)
  • title (242-243)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Backend Unit Tests
🔇 Additional comments (18)
tests/api/test_git_api.py (10)

1-15: LGTM!

Module docstring clearly documents the test scope, and imports are appropriate for the test module. Using git (GitPython) directly for repository verification is good practice.


29-66: LGTM!

Well-structured fixture with proper cleanup via TemporaryDirectory. Creating an initial commit before yielding ensures branch operations work correctly (Git requires at least one commit to create branches).


69-102: LGTM!

Good fixture composition building on test_project_with_git. Correctly uses domain model Issue for data creation as per coding guidelines.


105-135: LGTM!

Clean fixture composition using dict spread to extend parent fixture data. Correctly uses Task and TaskStatus domain models.


143-297: LGTM!

Comprehensive test coverage for branch creation including:

  • Happy path (201, response structure)
  • Validation errors (422 for missing fields)
  • Resource not found (404 for project/issue)
  • Conflict handling (409 for duplicates)
  • Actual Git verification (checking branch exists in repo)

305-359: LGTM!

Good test coverage for branch listing including empty state, populated state, and response structure validation.


367-437: LGTM!

Proper coverage of branch details endpoint with required field validation matching the BranchResponse model.


445-644: LGTM!

Excellent test coverage for commit creation including:

  • Success path with response validation
  • Required field validation (422)
  • Resource not found (404)
  • Security tests for path traversal and workspace escape attacks

652-709: LGTM!

Good test coverage for commit listing with proper verification of response structure and limit parameter behavior.


717-791: LGTM!

Comprehensive git status tests covering clean repository state, untracked file detection, and response structure validation.

codeframe/ui/routers/git.py (8)

1-31: LGTM!

Clean imports following Python 3.11+ patterns (datetime.UTC). Proper use of FastAPI dependencies and Pydantic for validation. Imports align with coding guidelines for using FastAPI framework.


33-67: LGTM!

Solid branch name validation covering Git's special refspec characters and dangerous patterns. The regex properly allows valid branch names while blocking injection attempts.


70-110: LGTM!

Comprehensive file path validation with multiple layers of protection:

  • Absolute path rejection
  • Directory traversal (..) segment blocking
  • Symlink resolution via os.path.realpath
  • Workspace escape detection via commonpath

Correctly returns original paths for git operations while validating security.


222-283: LGTM!

Well-structured helper functions with appropriate HTTP status codes for different error conditions. Good separation of concerns for project lookup, access control, and Git workflow manager instantiation.


290-378: LGTM!

Well-implemented branch creation with:

  • Proper project and issue validation
  • Race condition handling (409 Conflict for concurrent creation)
  • WebSocket broadcast for real-time updates
  • Appropriate error mapping (400/404/409/500)

449-499: LGTM!

Good implementation using FastAPI's :path converter to handle branch names with slashes. The single-query lookup via get_branch_by_name_and_issues is efficient.


605-684: LGTM!

Well-implemented commit listing with:

  • Proper input validation (branch name, limit bounds)
  • Graceful handling of commit.stats access failures
  • Appropriate error handling for invalid branch references (BadName)

691-763: LGTM!

Robust git status implementation that correctly handles:

  • Repositories without commits (empty HEAD)
  • Modified vs. staged vs. untracked file categorization
  • Edge cases with BadName exception for invalid HEAD references

Comment @coderabbitai help to get the list of available commands and usage tips.

@frankbria frankbria linked an issue Jan 13, 2026 that may be closed by this pull request
4 tasks
@macroscopeapp
Copy link

macroscopeapp bot commented Jan 13, 2026

Add Git REST API under /api/projects/{project_id}/git/* for branch creation, branch lookup by issues, commit operations, and status reporting, and expose Database.get_branch_by_name_and_issues and GitRepository.get_branch_by_name_and_issues for single-query branch retrieval

Introduce a FastAPI router for Git branches, commits, and status with input validation and project access checks; add a single-query branch lookup in GitRepository.get_branch_by_name_and_issues and a delegating Database.get_branch_by_name_and_issues; register the router in server.py and add websocket broadcasts for branch creation in websocket_broadcasts.py.

📍Where to Start

Start with the HTTP handlers in git.py, then follow calls to Database.get_branch_by_name_and_issues in database.py and GitRepository.get_branch_by_name_and_issues in git_repository.py.


Macroscope summarized 2392d65.

@github-actions
Copy link
Contributor

Code Review

Overall, this is a well-implemented PR with excellent test coverage (46 comprehensive tests). The code follows FastAPI patterns well and includes proper WebSocket broadcasts for real-time updates.

✅ Strengths

  1. Excellent test coverage: 46 tests covering all endpoints, error cases, and authorization
  2. Clean code structure: Proper separation of Pydantic models, helpers, and endpoints
  3. Good error handling: Specific HTTP status codes with clear error messages
  4. WebSocket integration: Real-time broadcasts for branch/commit events
  5. Proper use of async/await: Asynchronous endpoints where appropriate
  6. TDD approach: Tests written following RED-GREEN-REFACTOR cycle

🔍 Issues to Address

1. Security: Unvalidated branch_name parameter in git operations

Location: git.py:393-552 (commit/list endpoints)

The branch query parameter is passed directly to GitPython without validation. While GitPython is generally safe, branch names containing special characters could potentially cause issues.

Recommendation: Add validation using Pydantic's pattern validator to ensure only safe characters are allowed in branch names.

2. Performance: Multiple DB queries for branch lookup

Location: git.py:370-383 (get_branch endpoint)

The endpoint loops through 3 separate DB queries to find a branch (active, merged, abandoned). This is inefficient for large projects.

Recommendation: Add a method to GitRepository for a single-query lookup by branch name and issue IDs.

3. Bug: Incorrect commit message after creation

Location: git.py:453-455

You get commit message from repo.head.commit, which might be wrong if HEAD moves after commit creation.

Recommendation: Use the returned commit_hash to retrieve the correct commit object and message.

4. Error handling: Broad exception catching

Location: git.py:550-552 (list_commits endpoint)

Catching all Exception types could mask unexpected errors and make debugging difficult.

Recommendation: Catch specific exceptions like git.GitCommandError, ValueError, and KeyError.

📋 Minor Suggestions

  1. Add pagination: For list_commits, consider adding offset parameter in addition to limit
  2. Input sanitization: Consider using GitPython's validation utilities for branch names
  3. Audit logging: Consider adding audit logs for branch creation tracking

🎯 Test Coverage

The test suite is excellent with 46 tests covering all endpoints and error cases.

Suggestion: Add a test for list_commits with invalid branch name format to verify validation.

Summary

This PR is ready to merge after addressing the 4 issues above. The code quality is high, tests are comprehensive, and it follows project conventions.

@github-actions
Copy link
Contributor

Review posted to PR #282 at #282 (comment)

Summary:

This is a high-quality PR with excellent test coverage (46 tests) and clean code structure. The implementation follows FastAPI patterns and includes proper WebSocket broadcasts for real-time updates.

Key issues identified:

  1. Security: Unvalidated branch_name parameter passed to GitPython without sanitization
  2. Performance: Multiple DB queries (3 separate calls) for branch lookup - should be a single query
  3. Bug: Incorrect commit message retrieval after creation (uses repo.head.commit instead of returned hash)
  4. Error handling: Broad except Exception catching that could mask unexpected errors

Minor suggestions:

  • Add pagination (offset parameter) for commit listing
  • Consider GitPython validation utilities for branch names
  • Add audit logging for branch creation

Overall: Ready to merge after addressing the 4 main issues above. Test coverage is excellent and code follows project conventions.

New%20session%20-%202026-01-13T23%3A07%3A03.109Z
opencode session  |  github run

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @codeframe/ui/routers/git.py:
- Around line 313-315: The code treats an empty string status as falsy, causing
all_branches to be [] when callers pass status=""; change the conditional from
"if status" to explicitly check for None (e.g., "if status is not None") or
validate allowed status values before calling db.get_branches_by_status so that
an empty string is treated as an explicit value (or rejected) rather than
silently returning an empty list; update the logic around the all_branches
assignment and any upstream validation to ensure expected behavior when
status=="".
- Around line 538-540: The timestamp formatting assumes commits are already in
UTC; update the code that builds the commit dict (the lines using
commit.committed_datetime.isoformat().replace("+00:00", "Z")) to first convert
the datetime to UTC via commit.committed_datetime.astimezone(UTC) and then call
.isoformat().replace("+00:00", "Z") so all timestamps normalize to UTC; keep the
surrounding fields (message, author) unchanged and reuse the existing UTC
import.
- Around line 241-247: The code allows creating a git branch when no matching
issue exists (matching_issue is None), resulting in orphaned branches; stop
branch creation when the issue isn't found and avoid duplicate lookups by
passing project-scoped info to the workflow layer. Specifically, in the endpoint
that uses db.get_project_issues / matching_issue / issue_id and calls
workflow_manager.create_feature_branch, validate matching_issue and return an
error (e.g., 404 or 400) instead of proceeding when it's None; alternatively
change the call to workflow_manager.create_feature_branch to accept project_id +
issue_number (so the workflow can locate the issue only within that project) and
have create_feature_branch return the resolved issue_id which you then store.
Also remove the redundant cross-project issue lookup in
workflow_manager.create_feature_branch so the lookup is performed only once and
branches are always created with a valid issue_id.
🧹 Nitpick comments (3)
tests/api/test_git_api.py (1)

745-764: Consider using try/finally for header restoration to prevent test pollution.

If an exception occurs between deleting the header (Line 750) and restoring it (Line 762), subsequent tests in the class may fail unexpectedly. Using try/finally ensures cleanup even on test failure.

♻️ Suggested safer approach
     def test_create_branch_requires_auth(self, api_client, test_project_with_issue):
         """Test that branch creation requires authentication."""
         project = test_project_with_issue
 
-        # Remove auth header
-        del api_client.headers["Authorization"]
-
-        response = api_client.post(
-            f"/api/projects/{project['project_id']}/git/branches",
-            json={
-                "issue_number": project["issue_number"],
-                "issue_title": project["issue_title"],
-            },
-        )
-
-        # Restore auth header for cleanup
+        # Save and remove auth header
         from tests.api.conftest import create_test_jwt_token
-        api_client.headers["Authorization"] = f"Bearer {create_test_jwt_token()}"
+        original_auth = api_client.headers.get("Authorization")
+        try:
+            del api_client.headers["Authorization"]
 
-        assert response.status_code == 401
+            response = api_client.post(
+                f"/api/projects/{project['project_id']}/git/branches",
+                json={
+                    "issue_number": project["issue_number"],
+                    "issue_title": project["issue_title"],
+                },
+            )
+
+            assert response.status_code == 401
+        finally:
+            # Restore auth header for cleanup
+            api_client.headers["Authorization"] = original_auth or f"Bearer {create_test_jwt_token()}"
codeframe/ui/routers/git.py (2)

370-385: Consider adding a direct branch lookup by name for better performance.

The current implementation iterates through all branches for each status to find a match. This works but could be slow with many branches. A direct db.get_branch_by_name(branch_name) method would be more efficient.

#!/bin/bash
# Check if a direct branch lookup method exists
rg -n "def get_branch" --type py -C2

422-424: Minor: Redundant validation - already enforced by Pydantic.

The files_modified list is already validated by Pydantic's min_length=1 constraint on Line 89. This explicit check is defensive but unnecessary.

♻️ Can be removed if desired
-    # Validate files_modified is not empty
-    if not request.files_modified:
-        raise HTTPException(status_code=400, detail="No files to commit")
-
     # Get task and verify it belongs to this project
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1260477 and 4f2b30c.

📒 Files selected for processing (5)
  • codeframe/ui/routers/git.py
  • codeframe/ui/server.py
  • codeframe/ui/websocket_broadcasts.py
  • tests/api/conftest.py
  • tests/api/test_git_api.py
🧰 Additional context used
📓 Path-based instructions (1)
codeframe/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/**/*.py: Use Python 3.11+ for backend development
Use FastAPI framework for backend API development
Use SQLite with async support (aiosqlite) for database operations
Use tiktoken for token counting
Run linting with ruff check .
Import and use domain-specific repositories from codeframe/persistence/repositories/ for data access instead of direct database calls

Files:

  • codeframe/ui/websocket_broadcasts.py
  • codeframe/ui/routers/git.py
  • codeframe/ui/server.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
🧬 Code graph analysis (2)
codeframe/ui/websocket_broadcasts.py (2)
codeframe/core/models.py (2)
  • project_id (234-235)
  • issue_number (238-239)
codeframe/ui/shared.py (1)
  • broadcast (154-185)
tests/api/test_git_api.py (4)
tests/api/conftest.py (2)
  • api_client (67-177)
  • create_test_jwt_token (43-63)
codeframe/core/models.py (2)
  • issue_number (238-239)
  • title (242-243)
codeframe/cli/api_client.py (1)
  • post (230-240)
tests/integration/conftest.py (1)
  • create_response (261-265)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: Macroscope - Correctness Check
  • GitHub Check: E2E Smoke Tests (Chromium)
  • GitHub Check: opencode-review
🔇 Additional comments (11)
tests/api/conftest.py (1)

215-218: LGTM! Cleanup order correctly handles foreign key constraints.

The additions properly account for the new git_branches table and changelog references. The comment on Line 217 correctly documents the FK dependency on issues.

codeframe/ui/websocket_broadcasts.py (1)

239-271: LGTM! Follows established broadcast patterns.

The broadcast_branch_created function is consistent with other broadcast helpers in the module. Message structure, timestamp formatting, optional field handling, and error logging all align with existing patterns like broadcast_commit_created.

codeframe/ui/server.py (2)

27-27: LGTM! Router import follows alphabetical ordering.


334-334: LGTM! Router mounting follows existing conventions.

tests/api/test_git_api.py (4)

29-66: LGTM! Well-structured fixture with proper git initialization.

The fixture correctly initializes a git repository with an initial commit, which is required for branch operations. The use of tempfile.TemporaryDirectory context manager ensures cleanup.


143-283: LGTM! Comprehensive test coverage for branch creation.

Tests cover success paths, validation errors (422), not found (404), and duplicate handling (400). The test at Line 212-227 correctly verifies the branch actually exists in the git repository.


431-587: LGTM! Commit tests properly create files before testing.

Tests correctly create actual files in the workspace before making commit requests, ensuring realistic test scenarios.


660-734: LGTM! Status tests verify working tree states effectively.

Tests cover clean repo, untracked files, and response field validation. The test_status_with_untracked_file test (Lines 716-729) correctly creates a file and verifies it appears in untracked_files.

codeframe/ui/routers/git.py (3)

42-135: LGTM! Well-structured Pydantic models with appropriate validation.

Field validators handle whitespace stripping, min_length constraints prevent empty inputs, and response models use Optional for nullable fields.


142-202: LGTM! Helper functions properly encapsulate validation and error handling.

Clear separation of concerns: project lookup, access control, and workflow manager initialization. Error handling maps to appropriate HTTP status codes (400, 403, 404, 500).


560-620: LGTM! Correct git status implementation.

The endpoint properly uses:

  • repo.is_dirty(untracked_files=True) for dirty state
  • repo.index.diff(None) for modified (unstaged) files
  • repo.index.diff("HEAD") for staged files
  • repo.untracked_files for untracked files

- Security: Add branch name validation with regex pattern to prevent
  injection attacks via special git characters (~, ^, :, ?, *, etc.)
- Performance: Add single-query get_branch_by_name_and_issues() method
  replacing 3 sequential DB queries in get_branch endpoint
- Bug: Retrieve commit message from specific commit_hash instead of
  repo.head.commit which may have moved after creation
- Error handling: Replace broad Exception catches with specific types
  (git.BadName, git.InvalidGitRepositoryError, ValueError, KeyError)
@github-actions
Copy link
Contributor

Code Review

Looking at the latest code, I can see that most issues from previous reviews have been addressed. However, I found a few areas that need attention:

✅ Previously Addressed Issues

The following issues from earlier reviews appear to be fixed:

  • ✅ Branch name validation is now present ( function with regex pattern)
  • ✅ Single-query branch lookup using
  • ✅ Commit message now retrieved using the returned (line 495)
  • ✅ Specific exception catching in (GitCommandError, BadName, ValueError, KeyError)

🚨 Critical Issue: Branch Creation Without Issue Validation

Location:

The endpoint creates a git branch even when no matching issue exists:

matching_issue = next(
    (i for i in issues if i.issue_number == request.issue_number),
    None
)
issue_id = matching_issue.id if matching_issue else None

Problems:

  1. Branch is created in git repository (line 288) even when issue_id is None
  2. list_branches filters by issue_id (line 356), so branches without an issue_id become orphaned/invisible
  3. Redundant lookup: Endpoint searches for issue, then create_feature_branch searches across all projects again

Recommendation: Return 404 when matching_issue is None before creating the branch:

if not matching_issue:
    raise HTTPException(
        status_code=404, 
        detail=f"Issue {request.issue_number} not found in project"
    )

🐛 Minor Bug: Empty Status String Handling

Location:

The status parameter is validated incorrectly:

all_branches = db.get_branches_by_status(status) if status else []

If a client passes status="", this evaluates to False and returns an empty list instead of filtering by empty string (which would match no branches anyway, but is semantically inconsistent).

Recommendation: Use explicit None check:

all_branches = db.get_branches_by_status(status) if status is not None else []

Or validate status values upfront and reject invalid ones.

⚠️ Potential Performance: Redundant Issue Lookup in create_feature_branch

The create_feature_branch method in GitWorkflowManager likely searches all projects for the issue (based on the PR description mentioning this was an issue), but the endpoint has already found the issue within the project scope. This is inefficient.

Consider passing project_id and issue_id directly to create_feature_branch to avoid the second lookup.

📝 Minor Code Quality Notes

  1. Line 463-465: Redundant validation - files_modified is already validated by Pydantic's min_length=1 (line 126)
  2. Line 588: Timestamp format assumes UTC - should use .astimezone(UTC) first to normalize all timestamps

Summary

This PR has good code structure and excellent test coverage. The critical issue with orphaned branches must be fixed before merging. The empty status string handling is a minor bug that should be addressed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @codeframe/ui/routers/git.py:
- Around line 650-665: The staged-files call repo.index.diff("HEAD") can raise
if the repo has no commits (no HEAD); update the logic that builds staged_files
(used for GitStatusResponse) to first detect or handle a missing HEAD (e.g.,
check repo.head.is_valid() or repo.head.is_detached/exists) and if HEAD is
absent return an empty staged_files list, otherwise call
repo.index.diff("HEAD"); alternatively wrap repo.index.diff("HEAD") in a
try/except catching git.BadName and set staged_files = [] on that error so the
GitStatusResponse always receives a safe list.
- Around line 376-426: The route decorator for get_branch currently uses
"{branch_name}" which cannot match branch names containing slashes; update the
router path in the @router.get decorator to use the path converter
"{branch_name:path}" so FastAPI captures names with '/' (keep the function name
get_branch and existing parameter branch_name and validation via
validate_branch_name), and ensure any related docs/tests or client code that
call this endpoint expect slash-containing branch names to be URL-encoded as
appropriate.
🧹 Nitpick comments (2)
codeframe/ui/routers/git.py (2)

38-66: Consider adding validation for consecutive slashes and leading/trailing slashes.

Git rejects branch names with consecutive slashes (//), leading slashes (/foo), or trailing slashes (foo/). The current regex doesn't explicitly prevent these patterns.

🔧 Suggested enhancement
     # Check for dangerous patterns
-    dangerous_patterns = ['..', '@{', '~', '^', ':', '?', '*', '[', '\\', ' ']
+    dangerous_patterns = ['..', '@{', '~', '^', ':', '?', '*', '[', '\\', ' ', '//']
     for pattern in dangerous_patterns:
         if pattern in branch_name:
             raise ValueError(f"Branch name contains invalid character sequence: {pattern}")
+
+    # Check for leading/trailing slashes
+    if branch_name.startswith('/') or branch_name.endswith('/'):
+        raise ValueError("Branch name cannot start or end with a slash")

463-466: Redundant validation for empty files_modified.

The CommitRequest model already validates that files_modified cannot be empty via Field(..., min_length=1) and the validate_files field validator. This explicit check is defensive but redundant.

Could remove this check since Pydantic will reject requests with empty files_modified before reaching the endpoint handler.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f2b30c and 133a6ff.

📒 Files selected for processing (3)
  • codeframe/persistence/database.py
  • codeframe/persistence/repositories/git_repository.py
  • codeframe/ui/routers/git.py
🧰 Additional context used
📓 Path-based instructions (2)
codeframe/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/**/*.py: Use Python 3.11+ for backend development
Use FastAPI framework for backend API development
Use SQLite with async support (aiosqlite) for database operations
Use tiktoken for token counting
Run linting with ruff check .
Import and use domain-specific repositories from codeframe/persistence/repositories/ for data access instead of direct database calls

Files:

  • codeframe/persistence/repositories/git_repository.py
  • codeframe/persistence/database.py
  • codeframe/ui/routers/git.py
codeframe/persistence/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/persistence/**/*.py: Use Repository pattern for data access with domain-specific repositories in codeframe/persistence/repositories/
Use pre-production flattened v1.0 database schema with direct table creation (no migration system)
Maintain backward compatibility with existing imports and method signatures in Database repository migration

Files:

  • codeframe/persistence/repositories/git_repository.py
  • codeframe/persistence/database.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to codeframe/persistence/**/*.py : Maintain backward compatibility with existing imports and method signatures in Database repository migration

Applied to files:

  • codeframe/persistence/database.py
🧬 Code graph analysis (2)
codeframe/persistence/repositories/git_repository.py (1)
codeframe/persistence/database.py (1)
  • get_branch_by_name_and_issues (634-636)
codeframe/persistence/database.py (1)
codeframe/persistence/repositories/git_repository.py (1)
  • get_branch_by_name_and_issues (211-239)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: opencode-review
  • GitHub Check: E2E Smoke Tests (Chromium)
🔇 Additional comments (7)
codeframe/persistence/database.py (1)

634-637: LGTM!

The delegation method follows the established pattern used by other git branch methods in this facade class, maintaining backward compatibility. Based on learnings, this maintains backward compatibility with existing imports and method signatures.

codeframe/persistence/repositories/git_repository.py (1)

211-239: LGTM!

The implementation is clean and addresses performance optimization by using a single-query lookup with an IN clause. Key points:

  • Empty issue_ids guard prevents invalid SQL and unnecessary queries
  • Proper parameterization protects against SQL injection (placeholders are only ? characters, branch_name is parameterized)
  • LIMIT 1 is appropriate for this lookup pattern
codeframe/ui/routers/git.py (5)

32-36: Branch name regex may reject valid single-character alphanumeric branches.

The regex ^[a-zA-Z0-9][-a-zA-Z0-9_./]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$ correctly handles single-character branch names via the alternation. However, the pattern requires at least 2 characters for the first alternative (start and end alphanumeric with optional middle characters), which is handled by the |^[a-zA-Z0-9]$ branch. This looks correct.


79-172: LGTM!

The Pydantic models are well-structured with proper Pydantic v2 syntax (field_validator with @classmethod). Input validation covers empty/whitespace strings and empty file lists.


179-239: LGTM!

Helper functions are well-factored with appropriate error handling. The specific exception handling for git.InvalidGitRepositoryError and git.NoSuchPathError aligns with the PR's objective to replace broad Exception catches with specific exceptions.


566-604: LGTM!

The implementation efficiently uses iter_commits with max_count to avoid loading all commits. Error handling is comprehensive with specific exceptions (git.BadName for invalid branch references) as noted in the PR objectives. The defensive try/except for files_changed stats is appropriate since not all commits may have accessible stats.


278-300: The concern about None issue_id is unfounded—broadcast_branch_created is explicitly designed to handle Optional[int].

The broadcast_branch_created function signature declares issue_id: Optional[int] = None, and its implementation conditionally includes the issue_id in the message only when it's not None (if issue_id is not None). The broadcast operation itself is a websocket notification that gracefully handles the None case. No issues are expected when proceeding without a matching issue.

- Add 404 response when issue not found in create_branch endpoint
- Add VALID_BRANCH_STATUSES validation for status query parameter
- Use {branch_name:path} to support branch names with slashes
- Handle empty repos safely in get_git_status (no HEAD reference)
- Add test for issue-not-found scenario

Addresses code review feedback for PR #282.
@github-actions
Copy link
Contributor

Code Review

Overall, this is a high-quality PR with excellent test coverage (50 comprehensive tests) and clean implementation. The code follows FastAPI patterns well and includes proper WebSocket broadcasts for real-time updates.

✅ Strengths

  1. Excellent test coverage: 50 tests covering all endpoints, error cases, and authorization
  2. Clean code structure: Proper separation of concerns with Pydantic models, helpers, and endpoints
  3. Good error handling: Specific HTTP status codes with clear error messages
  4. WebSocket integration: Real-time broadcasts for branch/commit events
  5. Proper issue validation: Commit f8407a9 fixed critical issue - now validates issue exists before branch creation
  6. Path converter for branch names: Properly handles branch names containing forward slashes
  7. Single-query branch lookup: Performance optimization using db.get_branch_by_name_and_issues()
  8. HEAD validation in get_git_status: Correctly handles repositories with no commits

🔍 Issues to Address

1. Timestamp normalization in list_commits

Location: git.py:605

The timestamp formatting assumes commits are already in UTC but doesn't normalize:

timestamp=commit.committed_datetime.isoformat().replace("+00:00", "Z"),

If a commit was made in a different timezone (e.g., +05:30), the replacement won't trigger and the timestamp will retain the original offset.

Recommendation:

timestamp=commit.committed_datetime.astimezone(UTC).isoformat().replace("+00:00", "Z"),

2. Redundant validation for files_modified

Location: git.py:480-482

The files_modified list is already validated by Pydantic's min_length=1 constraint (line 126). This explicit check is defensive but unnecessary.

Recommendation: Remove lines 480-482 as Pydantic already enforces this validation.

3. Branch name regex doesn't prevent consecutive slashes

Location: git.py:35,54-57

The regex allows forward slashes but doesn't explicitly prevent consecutive slashes (//) or leading/trailing slashes, which Git rejects.

Recommendation: Add to dangerous_patterns check:

dangerous_patterns = ['..', '@{', '~', '^', ':', '?', '*', '[', '\\', ' ', '//']

# Check for leading/trailing slashes
if branch_name.startswith('/') or branch_name.endswith('/'):
    raise ValueError("Branch name cannot start or end with a slash")

📝 Minor Suggestions

  1. Performance optimization: The create_feature_branch method in workflow_manager.py (lines 91-101) searches across ALL projects for the issue, even though the endpoint has already found the issue within the specific project. Consider passing project_id and issue_id directly to avoid redundant lookup.

  2. Test coverage: Consider adding tests for:

    • Timestamp normalization (commits in different timezones)
    • Branch names with slashes (to verify path converter works)
    • Consecutive slashes in branch names (to verify rejection)

🎯 Test Coverage

The test suite is excellent with 50 tests covering all endpoints and error cases:

  • ✅ Issue not found returns 404 (line 738-749)
  • ✅ Authentication required
  • ✅ Invalid status values rejected
  • ✅ Branch creation, listing, and retrieval
  • ✅ Commit creation and listing
  • ✅ Git status retrieval

Summary

This PR is ready to merge after addressing the 3 minor issues above. The code quality is high, tests are comprehensive, and it follows project conventions. The critical issue validation (commit f8407a9) was properly addressed.

Key improvement from previous reviews: All critical issues have been fixed in recent commits. Only minor polishing items remain.

@github-actions
Copy link
Contributor

Review Complete

Posted comprehensive review to PR #282: #282 (comment)

Overall Assessment: High-quality PR ready to merge after addressing 3 minor issues.

Key Findings

Strengths:

  • Excellent test coverage (50 tests)
  • Proper issue validation before branch creation (fixed in commit f8407a9)
  • Single-query branch lookup for performance
  • Proper HEAD validation and path converter implementation

Minor Issues to Address:

  1. Timestamp normalization (git.py:605) - Should use .astimezone(UTC) before formatting
  2. Redundant validation (git.py:480-482) - Pydantic already validates files_modified
  3. Branch name regex (git.py:35,54) - Doesn't prevent consecutive/leading/trailing slashes

The PR successfully addresses all critical issues from previous reviews.

New%20session%20-%202026-01-13T23%3A24%3A44.983Z
opencode session  |  github run

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @codeframe/ui/routers/git.py:
- Around line 674-682: The code uses repo.index.entries.keys() directly which
yields (path, stage) tuples, causing staged_files to contain tuples instead of
strings expected by GitStatusResponse; update the else branch where staged_files
is set (inside the try when repo.head.is_valid() is false) to extract only the
path component from each key returned by repo.index.entries (e.g., map or list
comprehension pulling key[0] or using tuple unpacking) so staged_files becomes
List[str]; keep the existing logic for repo.index.diff("HEAD") and the
git.BadName except block unchanged.
🧹 Nitpick comments (4)
codeframe/ui/routers/git.py (3)

363-374: Consider performance implications of filtering in-memory.

The current approach fetches all branches by status and filters in Python. For projects with many issues, this could be optimized with a single database query that joins branches with project issues. However, this is acceptable for now given typical usage patterns.


480-482: Redundant validation, but harmless.

This check duplicates the Pydantic min_length=1 constraint on files_modified. It does provide a clearer error message ("No files to commit" vs generic validation error), so keeping it is acceptable.


594-597: Bare except Exception could mask unexpected errors.

While the intent is to handle cases where commit.stats might fail, catching all exceptions could hide bugs. Consider logging the exception at DEBUG level for diagnosability.

♻️ Suggested improvement
             try:
                 files_changed = commit.stats.total.get("files", 0) if commit.stats else None
-            except Exception:
+            except Exception as e:
+                logger.debug(f"Could not get files_changed for commit {commit.hexsha[:7]}: {e}")
                 files_changed = None
tests/api/test_git_api.py (1)

755-777: Consider adding authorization tests for other endpoints.

Only create_branch is tested for authorization. While the auth middleware likely applies uniformly to all endpoints, adding at least one read endpoint (e.g., list_branches) to the auth test class would strengthen confidence that all Git endpoints are protected.

♻️ Suggested addition
    def test_list_branches_requires_auth(self, api_client, test_project_with_git):
        """Test that branch listing requires authentication."""
        project = test_project_with_git

        # Remove auth header
        del api_client.headers["Authorization"]

        response = api_client.get(
            f"/api/projects/{project['id']}/git/branches"
        )

        # Restore auth header for cleanup
        from tests.api.conftest import create_test_jwt_token
        api_client.headers["Authorization"] = f"Bearer {create_test_jwt_token()}"

        assert response.status_code == 401
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 133a6ff and f8407a9.

📒 Files selected for processing (2)
  • codeframe/ui/routers/git.py
  • tests/api/test_git_api.py
🧰 Additional context used
📓 Path-based instructions (1)
codeframe/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/**/*.py: Use Python 3.11+ for backend development
Use FastAPI framework for backend API development
Use SQLite with async support (aiosqlite) for database operations
Use tiktoken for token counting
Run linting with ruff check .
Import and use domain-specific repositories from codeframe/persistence/repositories/ for data access instead of direct database calls

Files:

  • codeframe/ui/routers/git.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
🧬 Code graph analysis (1)
tests/api/test_git_api.py (2)
tests/api/conftest.py (2)
  • api_client (67-177)
  • create_test_jwt_token (43-63)
codeframe/core/models.py (2)
  • issue_number (238-239)
  • title (242-243)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: opencode-review
  • GitHub Check: E2E Smoke Tests (Chromium)
🔇 Additional comments (11)
codeframe/ui/routers/git.py (6)

1-31: LGTM!

Imports are well-organized and appropriate for the module's functionality. Using datetime with UTC from Python 3.11+ is correct per coding guidelines.


32-67: LGTM!

The branch name validation implements defense-in-depth by checking dangerous patterns before the regex match. This follows Git's own branch name restrictions appropriately.


79-93: LGTM!

Proper use of Pydantic v2 @field_validator with @classmethod decorator order. Whitespace stripping prevents empty-but-whitespace inputs.


179-239: LGTM!

Helper functions properly encapsulate common patterns (project lookup, access check, workflow manager instantiation) with appropriate HTTP exceptions.


246-322: LGTM!

The create_branch endpoint properly validates issue existence before creating the branch, handles errors appropriately, and broadcasts the event after successful creation.


393-444: LGTM!

Using {branch_name:path} correctly supports slashes in branch names. The single-query lookup via get_branch_by_name_and_issues is efficient.

tests/api/test_git_api.py (5)

18-27: LGTM!

The get_app() helper correctly imports the app locally to ensure it gets the reloaded instance after the api_client fixture reloads the server module.


29-67: LGTM!

The test_project_with_git fixture properly initializes a git repository with an initial commit, which is required for branch operations. Using tempfile.TemporaryDirectory ensures cleanup.


143-297: LGTM!

Comprehensive test coverage for branch creation including happy path, validation errors (422), not found errors (404), and duplicate branch handling (400).


444-601: LGTM!

Commit tests properly create files in the workspace before attempting commits, ensuring realistic test scenarios. Good coverage of validation and error cases.


673-748: LGTM!

Git status tests cover both clean and dirty repository states, verifying all expected fields in the response.

…zation

- Extract only path component from repo.index.entries.keys() tuples
  (keys are (path, stage) pairs, not plain strings)
- Use .astimezone(UTC) before formatting commit timestamps for proper
  UTC normalization

Addresses additional code review feedback for PR #282.
@github-actions
Copy link
Contributor

Code Review

This PR is in excellent condition with all critical issues from previous reviews addressed. The code is production-ready.

✅ All Previously Identified Issues Fixed

  1. Issue validation before branch creation - Lines 281-286 properly validate issue exists before creating branch
  2. Timestamp normalization - Line 605 correctly uses before formatting
  3. Path converter for branch names - Line 393 uses for slash-containing names
  4. HEAD validation for empty repos - Line 675 checks to handle repos with no commits
  5. Tuple extraction from repo.index.entries - Line 680 correctly extracts just the path from tuples

📝 Minor Suggestions (Non-Blocking)

  1. Redundant validation (lines 480-482): The list is already validated by Pydantic's constraint. This explicit check is defensive but unnecessary. Can be removed if desired.

  2. Status parameter edge case (line 368): When , the endpoint returns an empty list instead of all project branches. While the default value is "active" (line 331), callers explicitly passing might expect all branches. Consider:

  3. Test cleanup safety (test_git_api.py:758-775): Consider using for auth header restoration to prevent test pollution if an exception occurs.

🎯 Overall Assessment

Excellent test coverage (50 tests) with comprehensive coverage of:

  • ✅ All endpoints (branch create/list/get, commit create/list, git status)
  • ✅ Authentication/authorization
  • ✅ Error cases (400, 403, 404, 422)
  • ✅ Git repository operations

The code follows FastAPI patterns, includes proper WebSocket broadcasts, and maintains clean separation of concerns.

Summary

Ready to merge

All critical and most minor issues have been addressed. The remaining suggestions are optional optimizations that don't block merge. The implementation is solid, well-tested, and follows project conventions.

@github-actions
Copy link
Contributor

Code Review

This PR is in excellent condition with all critical issues from previous reviews addressed. The code is production-ready.

✅ All Previously Identified Issues Fixed

  1. Issue validation before branch creation - Lines 281-286 properly validate issue exists before creating branch
  2. Timestamp normalization - Line 605 correctly uses .astimezone(UTC) before formatting
  3. Path converter for branch names - Line 393 uses {branch_name:path} for slash-containing names
  4. HEAD validation for empty repos - Line 675 checks repo.head.is_valid() to handle repos with no commits
  5. Tuple extraction from repo.index.entries - Line 680 correctly extracts just the path from (path, stage) tuples

📝 Minor Suggestions (Non-Blocking)

  1. Redundant validation (lines 480-482): The files_modified list is already validated by Pydantic's min_length=1 constraint. This explicit check is defensive but unnecessary. Can be removed if desired.

  2. Status parameter edge case (line 368): When status=None, the endpoint returns an empty list instead of all project branches. While the default value is "active" (line 331), callers explicitly passing None might expect all branches.

  3. Test cleanup safety (test_git_api.py:758-775): Consider using try/finally for auth header restoration to prevent test pollution if an exception occurs.

🎯 Overall Assessment

Excellent test coverage (50 tests) with comprehensive coverage of:

  • All endpoints (branch create/list/get, commit create/list, git status)
  • Authentication/authorization
  • Error cases (400, 403, 404, 422)
  • Git repository operations

The code follows FastAPI patterns, includes proper WebSocket broadcasts, and maintains clean separation of concerns.

Summary

Ready to merge

All critical and most minor issues have been addressed. The remaining suggestions are optional optimizations that don't block merge. The implementation is solid, well-tested, and follows project conventions.

@github-actions
Copy link
Contributor

Review posted to PR #282: #282 (comment)

Summary

Excellent PR - Production Ready

All critical issues from previous reviews have been addressed:

Fixed Issues

  • Issue validation before branch creation (git.py:281-286)
  • Timestamp normalization with .astimezone(UTC) (git.py:605)
  • Path converter for branch names with slashes (git.py:393)
  • HEAD validation for empty repos (git.py:675)
  • Tuple extraction from repo.index.entries (git.py:680)

Minor Suggestions (Non-blocking)

  1. Lines 480-482: Redundant files_modified validation (Pydantic already enforces this)
  2. Line 368: Consider handling status=None to return all branches instead of empty list
  3. test_git_api.py:758-775: Use try/finally for safer test cleanup

Test Coverage

Excellent with 50 tests covering all endpoints, auth, and error cases.

New%20session%20-%202026-01-13T23%3A35%3A07.288Z
opencode session  |  github run

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @codeframe/ui/routers/git.py:
- Around line 504-509: The commit_task_changes flow currently passes
request.files_modified directly to repo.index.add(), so add path validation
inside commit_task_changes: for each path in files_modified reject any that
contain '..' segments or are absolute (os.path.isabs), resolve the candidate
against the repository/project root (use repo.working_tree_dir or the project
root variable and os.path.realpath) and ensure the resolved path has the project
root as its commonpath (os.path.commonpath) to prevent escaping the workspace;
on validation failure raise or return an explicit error and do not call
repo.index.add() with unsafe paths.
- Around line 246-252: The create_branch handler has a race where both requests
read self.repo.branches before either calls self.repo.create_head, causing a
GitCommandError and 500; fix by enforcing atomicity: add a UNIQUE constraint on
branch name scoped to project in the Branch DB model (project_id + name), wrap
branch creation in a DB transaction and attempt to insert the branch row first,
catching IntegrityError to return 409, then create the git branch
(repo.create_head) — or alternatively serialize git operations using a per-repo
lock around self.repo.create_head to prevent concurrent create_head calls;
update create_branch to handle GitCommandError/IntegrityError and return a
proper 409 conflict instead of 500.
🧹 Nitpick comments (2)
codeframe/ui/routers/git.py (2)

32-66: Branch name validation is solid, consider adding .lock suffix check.

The validation correctly blocks common dangerous patterns like .., @{, and special characters. Git also disallows branch names ending with .lock (e.g., refs/heads/foo.lock). Consider adding this check for completeness.

Optional enhancement
     # Check for dangerous patterns
     dangerous_patterns = ['..', '@{', '~', '^', ':', '?', '*', '[', '\\', ' ']
     for pattern in dangerous_patterns:
         if pattern in branch_name:
             raise ValueError(f"Branch name contains invalid character sequence: {pattern}")
+
+    # Git disallows .lock suffix
+    if branch_name.endswith('.lock'):
+        raise ValueError("Branch name cannot end with '.lock'")

367-374: Clarify the status=None branch handling.

Line 368 returns an empty list when status is None, but with default="active" on the query param, this case shouldn't occur in practice. However, this creates an inconsistent API behavior: omitting the parameter gives "active" branches, but there's no way to get all branches regardless of status.

Consider whether you want to support fetching all branches (e.g., when status is explicitly omitted or set to a sentinel value like "all").

Optional: Support fetching all statuses
 @router.get("/{project_id}/git/branches", response_model=BranchListResponse)
 async def list_branches(
     project_id: int,
-    status: Optional[str] = Query(default="active", description="Filter by status (active, merged, abandoned)"),
+    status: Optional[str] = Query(default=None, description="Filter by status (active, merged, abandoned). Omit to get all."),
     db: Database = Depends(get_db),
     current_user: User = Depends(get_current_user),
 ):

Then adjust the query logic to fetch all branches when status is None.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8407a9 and 4765445.

📒 Files selected for processing (1)
  • codeframe/ui/routers/git.py
🧰 Additional context used
📓 Path-based instructions (1)
codeframe/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

codeframe/**/*.py: Use Python 3.11+ for backend development
Use FastAPI framework for backend API development
Use SQLite with async support (aiosqlite) for database operations
Use tiktoken for token counting
Run linting with ruff check .
Import and use domain-specific repositories from codeframe/persistence/repositories/ for data access instead of direct database calls

Files:

  • codeframe/ui/routers/git.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
🧬 Code graph analysis (1)
codeframe/ui/routers/git.py (4)
codeframe/ui/dependencies.py (1)
  • get_db (14-29)
codeframe/auth/dependencies.py (1)
  • get_current_user (17-121)
codeframe/ui/websocket_broadcasts.py (2)
  • broadcast_branch_created (239-271)
  • broadcast_commit_created (201-236)
codeframe/persistence/repositories/git_repository.py (2)
  • get_branches_by_status (157-172)
  • get_branch_by_name_and_issues (211-239)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Backend Unit Tests
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: opencode-review
  • GitHub Check: E2E Smoke Tests (Chromium)
  • GitHub Check: Macroscope - Correctness Check
🔇 Additional comments (6)
codeframe/ui/routers/git.py (6)

79-171: LGTM!

The Pydantic models are well-structured with appropriate validation. The strip_whitespace field validator provides good input sanitization, and the response models cover all necessary fields.


179-238: LGTM!

The helper functions cleanly separate concerns: project lookup, access control, and workflow manager instantiation. Error handling maps git exceptions to appropriate HTTP status codes.


393-443: LGTM!

Good use of the :path converter to handle branch names containing slashes. The single-query lookup via get_branch_by_name_and_issues is an efficient approach that avoids N+1 queries.


583-610: LGTM!

The commit listing is well-implemented with proper pagination limits, defensive error handling for commit stats, and correct UTC timestamp normalization. Handling git.BadName separately provides clearer error messages for invalid branch references.


673-691: LGTM!

Good handling of edge cases for repositories without commits (empty HEAD). The fix to extract paths from (path, stage) tuples in repo.index.entries.keys() correctly addresses the GitPython API behavior.


291-296: No action required — branch name sanitization is already properly implemented.

The issue_title is sanitized by the _sanitize_branch_name method (called on line 67 of create_feature_branch), which removes special characters, converts to lowercase, collapses consecutive hyphens, and strips leading/trailing hyphens. The implementation is secure and tested.

Likely an incorrect or invalid review comment.

Test User added 2 commits January 13, 2026 16:49
…ndition

Security fixes:
- Add validate_file_paths() to prevent directory traversal attacks
  in commit endpoint (rejects absolute paths, '..' segments, and
  paths that escape the workspace via symlink resolution)
- Handle branch creation race condition by returning 409 Conflict
  instead of 500 when concurrent requests create the same branch

Changes:
- Add os import for path validation
- Add validate_file_paths() function with commonpath check
- Call path validation before commit_task_changes()
- Update create_branch to return 409 for "already exists" errors
- Update test to expect 409 for duplicate branch creation
- Add 3 security tests for path validation (absolute, traversal, escape)

Test coverage: 50 tests passing
@github-actions
Copy link
Contributor

Code Review

This PR is in excellent condition with comprehensive test coverage (50 tests). The code follows FastAPI patterns and includes proper WebSocket broadcasts for real-time updates.

✅ All Previously Identified Issues Fixed

  1. Issue validation before branch creation - Lines 321-331 properly validate issue exists before creating branch
  2. Timestamp normalization - Line 667 correctly uses astimezone(UTC) before formatting
  3. Path converter for branch names - Line 449 uses {branch_name:path} for slash-containing names
  4. HEAD validation for empty repos - Line 737 checks repo.head.is_valid() to handle repos with no commits
  5. Tuple extraction from repo.index.entries - Line 742 correctly extracts just the path from (path, stage) tuples
  6. Race condition handling - Lines 367-377 properly catch GitCommandError and return 409 for concurrent branch creation
  7. Path traversal prevention - Lines 70-110 in validate_file_paths() provide robust protection against directory traversal attacks

📝 Minor Suggestions (Non-Blocking)

  1. Redundant validation (lines 536-538): The files_modified list is already validated by Pydantic's min_length=1 constraint (line 170). This explicit check provides a clearer error message but is technically redundant. Consider removing it if you prefer purely declarative validation.

  2. Status parameter behavior (line 424): When status=None, the endpoint returns an empty list (line 424). With the default value of "active" (line 387), this case doesn't occur in normal usage. However, there's currently no way to fetch branches across all statuses. This appears to be intentional design.

  3. Consider adding .lock suffix check: Git disallows branch names ending with .lock (e.g., refs/heads/foo.lock). The current validation (lines 54-66) doesn't explicitly prevent this. Consider adding:

    if branch_name.endswith('.lock'):
        raise ValueError("Branch name cannot end with '.lock'")

🎯 Overall Assessment

Excellent test coverage with 50 tests covering:

  • ✅ All endpoints (branch create/list/get, commit create/list, git status)
  • ✅ Authentication/authorization
  • ✅ Error cases (400, 403, 404, 422)
  • ✅ Git repository operations

The code follows FastAPI patterns, includes proper WebSocket broadcasts, and maintains clean separation of concerns. The path traversal validation in validate_file_paths() is particularly strong with multiple layers of protection.

Summary

Ready to merge

All critical and most minor issues have been addressed. The remaining suggestions are optional polish items that don't block merge. The implementation is solid, well-tested, and follows project conventions.

@github-actions
Copy link
Contributor

Code Review

After reviewing the latest code changes and previous reviews, I'm impressed with the quality of this PR.

✅ All Critical Issues Addressed

The following issues from earlier reviews have been properly fixed:

  1. Issue validation before branch creation - Lines 321-331 properly validate issue exists before creating branch (no orphaned branches)
  2. Timestamp normalization - Line 667 correctly uses before formatting
  3. Path converter for branch names - Line 449 uses to handle slashes correctly
  4. HEAD validation for empty repos - Line 737 checks to handle repos with no commits
  5. Tuple extraction from index.entries - Line 742 correctly extracts just the path from tuples
  6. Path traversal protection - Lines 70-110 implement comprehensive with directory traversal, absolute path, and workspace escape checks
  7. Race condition handling - Lines 367-377 catch GitCommandError for concurrent branch creation and return 409 Conflict

📝 Minor Suggestions (Non-Blocking)

  1. Redundant validation (lines 537-538): The list is already validated by Pydantic's constraint (line 170). This explicit check is defensive but unnecessary. Can be removed if desired.

  2. Status=None handling (line 424): When , returns an empty list. Since the default is "active" (line 387), this won't occur in practice. Consider clarifying this behavior or supporting "all" status.

  3. Branch name validation (lines 54-58): Git disallows branch names ending with . Consider adding:

  4. Test cleanup safety (test_git_api.py:807-819): Consider using for auth header restoration:

🎯 Overall Assessment

Excellent code quality with:

  • ✅ 50 comprehensive tests covering all endpoints, auth, and error cases
  • ✅ Clean separation of concerns (Pydantic models, helpers, endpoints)
  • ✅ Proper error handling with specific HTTP status codes
  • ✅ Security hardening (path validation, branch name sanitization)
  • ✅ Performance optimizations (single-query branch lookup, GitPython best practices)
  • ✅ WebSocket integration for real-time updates

Summary

Ready to merge

All critical and security issues have been addressed. The remaining suggestions are optional polish items. The implementation is solid, well-tested, and follows project conventions.


Reviewer Context: This review was performed after reviewing all previous comments on the PR to ensure consistency and avoid duplicate feedback.

# Get workflow manager
workflow_manager = get_git_workflow_manager(project, db)

# Validate file paths to prevent directory traversal attacks
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_manager.repo.working_tree_dir can be None (bare repo). Then validate_file_paths(...) resolves against CWD, weakening path checks. Consider guarding for a missing working tree and fail early.

Suggested change
# Validate file paths to prevent directory traversal attacks
# Ensure repo has working tree (non-bare)
if not workflow_manager.repo.working_tree_dir:
raise HTTPException(status_code=500, detail="Project workspace has no working tree")

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment on lines +600 to +602
except git.GitCommandError as e:
logger.error(f"Git error creating commit: {e}")
raise HTTPException(status_code=500, detail=f"Git operation failed: {e}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_manager.repo.commit(commit_hash) can raise git.BadName/git.BadObject, which aren’t caught and will bubble unhandled. Consider catching them like git.GitCommandError so the endpoint returns a consistent 500.

-    except git.GitCommandError as e:
+    except (git.GitCommandError, git.BadName, git.BadObject) as e:
         logger.error(f"Git error creating commit: {e}")
         raise HTTPException(status_code=500, detail=f"Git operation failed: {e}")

🚀 Want me to fix this? Reply ex: "fix it for me".

@github-actions
Copy link
Contributor

Code Review Summary

I've reviewed PR #282 and posted a comprehensive review comment. The PR is in excellent condition and ready to merge.

✅ All Critical Issues Addressed

The PR has successfully addressed all issues from previous reviews:

  1. Issue validation before branch creation (lines 321-331) - Validates issue exists before creating the branch
  2. Timestamp normalization (line 667) - Uses .astimezone(UTC) before formatting
  3. Path converter for branch names (line 449) - Uses {branch_name:path} to handle slashes
  4. HEAD validation for empty repos (line 737) - Checks repo.head.is_valid() to handle repos with no commits
  5. Tuple extraction from repo.index.entries (line 742) - Correctly extracts just the path from (path, stage) tuples
  6. Race condition handling (lines 367-377) - Catches GitCommandError and returns 409 for concurrent branch creation
  7. Path traversal prevention (lines 70-110) - Robust validation in validate_file_paths() prevents directory traversal attacks

📝 Minor Suggestions (Non-Blocking)

Only 3 minor suggestions were identified:

  1. Redundant validation (lines 536-538) - Pydantic already validates files_modified with min_length=1
  2. Status parameter behavior (line 424) - When status=None, returns empty list (appears to be intentional design)
  3. Optional: Add .lock suffix check - Git disallows branch names ending with .lock

🎯 Overall Assessment

  • Test Coverage: Excellent (50 tests covering all endpoints, auth, and error cases)
  • Code Quality: Follows FastAPI patterns with proper separation of concerns
  • Security: Strong path traversal validation and proper error handling
  • Performance: Single-query branch lookup optimization
  • WebSocket Integration: Real-time broadcasts for branch/commit events

Result: Ready to merge

All critical issues have been addressed. The remaining suggestions are optional polish items that don't block merge.

New%20session%20-%202026-01-13T23%3A50%3A25.311Z
opencode session  |  github run

@github-actions
Copy link
Contributor

Code Review

After reviewing the latest code changes and previous reviews, I'm impressed with the quality of this PR.

✅ All Critical Issues Addressed

The following issues from earlier reviews have been properly fixed:

  1. Issue validation before branch creation - Lines 321-331 properly validate issue exists before creating branch (no orphaned branches)
  2. Timestamp normalization - Line 667 correctly uses .astimezone(UTC) before formatting
  3. Path converter for branch names - Line 449 uses {branch_name:path} to handle slashes correctly
  4. HEAD validation for empty repos - Line 737 checks repo.head.is_valid() to handle repos with no commits
  5. Tuple extraction from index.entries - Line 742 correctly extracts just the path from (path, stage) tuples
  6. Path traversal protection - Lines 70-110 implement comprehensive validate_file_paths() with directory traversal, absolute path, and workspace escape checks
  7. Race condition handling - Lines 367-377 catch GitCommandError for concurrent branch creation and return 409 Conflict

📝 Minor Suggestions (Non-Blocking)

  1. Redundant validation (lines 537-538): The files_modified list is already validated by Pydantic's min_length=1 constraint (line 170). This explicit check is defensive but unnecessary. Can be removed if desired.

  2. Status=None handling (line 424): When status is None, all_branches = [] returns an empty list. Since the default is "active" (line 387), this won't occur in practice. Consider clarifying this behavior or supporting an "all" status value.

  3. Branch name validation (lines 54-58): Git disallows branch names ending with .lock. Consider adding a check for this pattern to the validation.

  4. Test cleanup safety (test_git_api.py:807-819): Consider using try/finally for auth header restoration to prevent test pollution if an exception occurs.

🎯 Overall Assessment

Excellent code quality with:

  • ✅ 50 comprehensive tests covering all endpoints, auth, and error cases
  • ✅ Clean separation of concerns (Pydantic models, helpers, endpoints)
  • ✅ Proper error handling with specific HTTP status codes
  • ✅ Security hardening (path validation, branch name sanitization)
  • ✅ Performance optimizations (single-query branch lookup, GitPython best practices)
  • ✅ WebSocket integration for real-time updates

Summary

Ready to merge

All critical and security issues have been addressed. The remaining suggestions are optional polish items. The implementation is solid, well-tested, and follows project conventions.


Reviewer Context: This review was performed after reviewing all previous comments on the PR to ensure consistency and avoid duplicate feedback.

@github-actions
Copy link
Contributor

Review Complete

Posted comprehensive review to PR #282: #282 (comment)

Overall Assessment: Ready to merge

Summary of Findings

All Critical Issues Fixed:

  • Issue validation before branch creation (no orphaned branches)
  • Timestamp normalization with .astimezone(UTC)
  • Path converter for branch names with slashes
  • HEAD validation for empty repos
  • Tuple extraction from (path, stage) tuples
  • Path traversal protection with comprehensive validation
  • Race condition handling returning 409 Conflict

Minor Suggestions (Non-blocking):

  1. Lines 537-538: Redundant files_modified validation (Pydantic already enforces this)
  2. Line 424: Consider handling status=None behavior or supporting "all" status
  3. Lines 54-58: Add .lock suffix validation to branch name check
  4. test_git_api.py:807-819: Use try/finally for safer test cleanup

Strengths:

  • Excellent test coverage (50 comprehensive tests)
  • Clean code structure with proper separation of concerns
  • Security hardening (path validation, branch sanitization)
  • Performance optimizations (single-query branch lookup)
  • WebSocket integration for real-time updates

The code follows FastAPI patterns, includes proper error handling, and maintains clean separation of concerns. All critical and security issues have been addressed.

New%20session%20-%202026-01-13T23%3A51%3A38.997Z
opencode session  |  github run

@frankbria frankbria merged commit fe984f7 into main Jan 13, 2026
17 of 18 checks passed
@frankbria frankbria deleted the feature/git-rest-api branch January 13, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0] Backend: Expose Git branch and commit APIs

2 participants