Skip to content

Commit fe984f7

Browse files
frankbriaTest User
andauthored
feat(api): add Git REST API for branch and commit operations (#270) (#282)
* feat(api): add Git REST API for branch and commit operations (#270) 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) * fix(api): address code review feedback for git API - 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) * fix(api): validate issue exists and handle edge cases in git API - 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. * fix(api): correct staged_files tuple extraction and timestamp normalization - 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. * fix(security): add path validation and handle branch creation race condition 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 * style: remove extraneous f-string prefix (ruff F541) --------- Co-authored-by: Test User <test@example.com>
1 parent 1260477 commit fe984f7

7 files changed

Lines changed: 1657 additions & 0 deletions

File tree

codeframe/persistence/database.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,10 @@ def count_branches_for_issue(self, *args, **kwargs):
631631
"""Delegate to git_branches.count_branches_for_issue()."""
632632
return self.git_branches.count_branches_for_issue(*args, **kwargs)
633633

634+
def get_branch_by_name_and_issues(self, *args, **kwargs):
635+
"""Delegate to git_branches.get_branch_by_name_and_issues()."""
636+
return self.git_branches.get_branch_by_name_and_issues(*args, **kwargs)
637+
634638
def create_test_result(self, *args, **kwargs):
635639
"""Delegate to test_results.create_test_result()."""
636640
return self.test_results.create_test_result(*args, **kwargs)

codeframe/persistence/repositories/git_repository.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,33 @@ def count_branches_for_issue(self, issue_id: int) -> int:
208208
)
209209
return cursor.fetchone()[0]
210210

211+
def get_branch_by_name_and_issues(
212+
self, branch_name: str, issue_ids: List[int]
213+
) -> Optional[Dict[str, Any]]:
214+
"""Get a branch by name that belongs to one of the given issues.
215+
216+
Single-query lookup for performance optimization.
217+
218+
Args:
219+
branch_name: Git branch name to find
220+
issue_ids: List of issue IDs the branch must belong to
221+
222+
Returns:
223+
Branch dictionary or None if not found
224+
"""
225+
if not issue_ids:
226+
return None
227+
228+
cursor = self.conn.cursor()
229+
placeholders = ",".join("?" * len(issue_ids))
230+
cursor.execute(
231+
f"""
232+
SELECT * FROM git_branches
233+
WHERE branch_name = ? AND issue_id IN ({placeholders})
234+
LIMIT 1
235+
""",
236+
(branch_name, *issue_ids),
237+
)
238+
row = cursor.fetchone()
239+
return dict(row) if row else None
240+

0 commit comments

Comments
 (0)