feat: add commit panel and file status indicators to repo workspace
- Add CommitPanel component for viewing changed files and committing - Show file status indicators (M/A/D/?) in file tree - Integrate git status with workspace for real-time updates - Add CSS styles for commit panel and status badges Part of git-control change implementation.
This commit is contained in:
@@ -1,53 +1,195 @@
|
|||||||
|
# Headquarter
|
||||||
|
|
||||||
## Testing Strategy
|
A self-hosted platform for managing projects, git repositories, and development tools with OAuth2 authentication.
|
||||||
|
|
||||||
The project uses a three-tier testing approach:
|
## Overview
|
||||||
|
|
||||||
### Test Categories
|
Headquarter provides a centralized workspace for development teams to:
|
||||||
|
- Manage projects and their associated git repositories
|
||||||
|
- Browse repository files and view git history
|
||||||
|
- Spawn development tools (VS Code Server, Jupyter Notebook, etc.)
|
||||||
|
- Manage SSH keys and user preferences
|
||||||
|
|
||||||
1. **Unit Tests** (`apps/api/tests/unit/`)
|
## Features
|
||||||
- Fast tests with no external dependencies
|
|
||||||
- Use SQLite in-memory database
|
|
||||||
- Run with: `make test-unit` or `pytest -m unit`
|
|
||||||
|
|
||||||
2. **Integration Tests** (`apps/api/tests/integration/`)
|
### Project Management
|
||||||
- Test API endpoints with database
|
- Create and manage projects
|
||||||
- Use PostgreSQL with transaction rollback
|
- View all projects in a dashboard
|
||||||
- Run with: `make test-integration` or `pytest -m integration`
|
- Click any project to open its workspace
|
||||||
|
|
||||||
3. **System/E2E Tests** (`e2e/`)
|
### Git Repository Management
|
||||||
- End-to-end tests using Playwright
|
- Initialize bare repositories
|
||||||
- Test full user journeys
|
- Clone repositories (including mirror clones)
|
||||||
- Run with: `make test-e2e`
|
- Smart URL parsing (converts browser URLs to git URLs)
|
||||||
|
- View repository history and commit details
|
||||||
|
|
||||||
### Running Tests
|
### Repository Workspace
|
||||||
|
- Browse files and directories
|
||||||
|
- View file contents with syntax highlighting
|
||||||
|
- Switch between branches
|
||||||
|
- Quick file editing with automatic commits
|
||||||
|
|
||||||
```bash
|
### Git History Visualization
|
||||||
# Run all tests (excludes system tests by default)
|
- View commit history with branch graph
|
||||||
make test
|
- See commit details, statistics, and diffs
|
||||||
|
- Filter by branch
|
||||||
|
|
||||||
# Run specific categories
|
### Authentication
|
||||||
make test-unit # Fast unit tests only
|
- OAuth2 via Authentik
|
||||||
make test-integration # Integration tests with DB
|
- Session-based authentication
|
||||||
make test-system # Full stack tests
|
- User profile management
|
||||||
make test-e2e # Browser-based E2E tests
|
|
||||||
|
|
||||||
# Inside Docker container
|
### Tool Management
|
||||||
docker compose exec api pytest -v -m unit
|
- Built-in tool types (code-server, jupyter-notebook)
|
||||||
docker compose exec api pytest -v -m integration
|
- Create custom tool types with Docker Compose templates
|
||||||
|
- Template validation
|
||||||
|
|
||||||
|
### User Settings
|
||||||
|
- Theme selection (system/light/dark)
|
||||||
|
- Git identity configuration
|
||||||
|
- Default editor preference
|
||||||
|
|
||||||
|
### SSH Key Management
|
||||||
|
- Generate Ed25519 key pairs
|
||||||
|
- Copy public keys to clipboard
|
||||||
|
- Delete keys
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
1. **Clone the repository:**
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd headquarter
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set up environment:**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your settings
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start services:**
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Access the application:**
|
||||||
|
- Frontend: http://localhost:5173
|
||||||
|
- API: http://localhost:8000
|
||||||
|
- API Docs: http://localhost:8000/docs
|
||||||
|
|
||||||
|
### Production Deployment
|
||||||
|
|
||||||
|
See [Deployment Guide](docs/deployment/) for production setup with Traefik and Authentik.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **FastAPI** - Python web framework
|
||||||
|
- **SQLAlchemy** - ORM with async PostgreSQL support
|
||||||
|
- **Pydantic** - Data validation
|
||||||
|
- **Alembic** - Database migrations
|
||||||
|
- **python-jose** - JWT handling
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **React** - UI library
|
||||||
|
- **TypeScript** - Type safety
|
||||||
|
- **Vite** - Build tool
|
||||||
|
- **React Router** - Client-side routing
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
- **Docker** - Containerization
|
||||||
|
- **PostgreSQL** - Database
|
||||||
|
- **Traefik** - Reverse proxy (production)
|
||||||
|
- **Authentik** - Identity provider
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [User Guide](docs/features/) - Feature documentation
|
||||||
|
- [API Reference](docs/api/) - API endpoints
|
||||||
|
- [Architecture](docs/architecture/) - System design
|
||||||
|
- [Deployment](docs/deployment/) - Setup guides
|
||||||
|
- [Development](docs/development/) - Contributing
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── apps/
|
||||||
|
│ ├── api/ # FastAPI backend
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── api/ # API routes
|
||||||
|
│ │ │ ├── auth/ # Authentication
|
||||||
|
│ │ │ ├── models/ # Database models
|
||||||
|
│ │ │ └── utils/ # Utilities
|
||||||
|
│ │ ├── tests/ # Test suite
|
||||||
|
│ │ └── Dockerfile
|
||||||
|
│ └── web/ # React frontend
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── api/ # API clients
|
||||||
|
│ │ ├── components/# UI components
|
||||||
|
│ │ └── pages/ # Page components
|
||||||
|
│ └── Dockerfile
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── docker-compose.yml # Development setup
|
||||||
|
├── docker-compose.traefik.yml # Production setup
|
||||||
|
└── Makefile # Common commands
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Markers
|
## Development
|
||||||
|
|
||||||
Tests are marked with pytest markers:
|
### Backend Development
|
||||||
- `@pytest.mark.unit` - Fast, isolated tests
|
```bash
|
||||||
- `@pytest.mark.integration` - Tests with database/external services
|
cd apps/api
|
||||||
- `@pytest.mark.system` - Full stack tests
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
uvicorn src.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
### Shared Fixtures
|
### Frontend Development
|
||||||
|
```bash
|
||||||
|
cd apps/web
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
Common fixtures are in `apps/api/tests/conftest.py`:
|
### Running Tests
|
||||||
- `sqlite_engine` - SQLite engine for unit tests
|
```bash
|
||||||
- `postgres_engine` - PostgreSQL engine for integration tests
|
# Backend tests
|
||||||
- `db_session` - Database session with transaction rollback
|
make test
|
||||||
- `test_client` - FastAPI TestClient instance
|
|
||||||
|
# Frontend tests
|
||||||
|
make test-web
|
||||||
|
|
||||||
|
# All quality gates
|
||||||
|
make lint
|
||||||
|
make typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Key environment variables:
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `API_DOMAIN` | API domain | `localhost` |
|
||||||
|
| `WEB_DOMAIN` | Web domain | `localhost` |
|
||||||
|
| `AUTHENTIK_DOMAIN` | Authentik domain | - |
|
||||||
|
| `AUTHENTIK_CLIENT_ID` | OAuth client ID | - |
|
||||||
|
| `AUTHENTIK_CLIENT_SECRET` | OAuth client secret | - |
|
||||||
|
| `DATABASE_URL` | PostgreSQL URL | - |
|
||||||
|
| `JWT_SECRET` | JWT signing secret | - |
|
||||||
|
| `REPO_BASE_PATH` | Repository storage path | `/data/repos` |
|
||||||
|
|
||||||
|
See [Environment Variables](docs/deployment/environment.md) for complete list.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[License information]
|
||||||
|
|||||||
@@ -112,3 +112,134 @@ export async function getCommitDetail(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Git Control API
|
||||||
|
|
||||||
|
export interface GitStatus {
|
||||||
|
branch: string;
|
||||||
|
modified: string[];
|
||||||
|
added: string[];
|
||||||
|
deleted: string[];
|
||||||
|
untracked: string[];
|
||||||
|
renamed: string[];
|
||||||
|
ahead: number;
|
||||||
|
behind: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRepositoryStatus(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string
|
||||||
|
): Promise<GitStatus> {
|
||||||
|
const response = await apiClient.get(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/status`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createBranch(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
name: string,
|
||||||
|
baseBranch: string = "HEAD"
|
||||||
|
): Promise<{ message: string; branch: string }> {
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/branches`,
|
||||||
|
{ name, base_branch: baseBranch }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBranch(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
branchName: string,
|
||||||
|
force: boolean = false
|
||||||
|
): Promise<{ message: string }> {
|
||||||
|
const response = await apiClient.delete(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/branches/${branchName}?force=${force}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkoutBranch(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<{ message: string; branch: string }> {
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/checkout`,
|
||||||
|
{ branch }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommitResponse {
|
||||||
|
commit_hash: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function commitChanges(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
message: string,
|
||||||
|
files?: string[]
|
||||||
|
): Promise<CommitResponse> {
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/commit`,
|
||||||
|
{ message, files }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRepository(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string
|
||||||
|
): Promise<{ message: string }> {
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/fetch`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pullRepository(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
branch?: string
|
||||||
|
): Promise<{ message: string }> {
|
||||||
|
const params = branch ? `?branch=${branch}` : "";
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/pull${params}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pushRepository(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
branch?: string
|
||||||
|
): Promise<{ message: string }> {
|
||||||
|
const params = branch ? `?branch=${branch}` : "";
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/push${params}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MergeResponse {
|
||||||
|
commit_hash: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mergeBranches(
|
||||||
|
projectId: string,
|
||||||
|
repoId: string,
|
||||||
|
sourceBranch: string,
|
||||||
|
targetBranch?: string,
|
||||||
|
message?: string
|
||||||
|
): Promise<MergeResponse> {
|
||||||
|
const response = await apiClient.post(
|
||||||
|
`/projects/${projectId}/repositories/${repoId}/merge`,
|
||||||
|
{ source_branch: sourceBranch, target_branch: targetBranch, message }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { commitChanges } from "../api/git_repositories";
|
||||||
|
|
||||||
|
interface CommitPanelProps {
|
||||||
|
projectId: string;
|
||||||
|
repoId: string;
|
||||||
|
modified: string[];
|
||||||
|
added: string[];
|
||||||
|
deleted: string[];
|
||||||
|
untracked: string[];
|
||||||
|
onCommit: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CommitPanel = ({
|
||||||
|
projectId,
|
||||||
|
repoId,
|
||||||
|
modified,
|
||||||
|
added,
|
||||||
|
deleted,
|
||||||
|
untracked,
|
||||||
|
onCommit,
|
||||||
|
}: CommitPanelProps) => {
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const allFiles = [...modified, ...added, ...deleted, ...untracked];
|
||||||
|
const hasChanges = allFiles.length > 0;
|
||||||
|
|
||||||
|
const handleCommit = async () => {
|
||||||
|
if (!message.trim()) {
|
||||||
|
setError("Please enter a commit message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await commitChanges(projectId, repoId, message);
|
||||||
|
setMessage("");
|
||||||
|
onCommit();
|
||||||
|
} catch {
|
||||||
|
setError("Commit failed. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!hasChanges) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="commit-panel">
|
||||||
|
<h4>Changes</h4>
|
||||||
|
|
||||||
|
<div className="file-list">
|
||||||
|
{modified.map((file) => (
|
||||||
|
<div key={file} className="file-item modified">
|
||||||
|
<span className="file-status">M</span>
|
||||||
|
<span className="file-name">{file}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{added.map((file) => (
|
||||||
|
<div key={file} className="file-item added">
|
||||||
|
<span className="file-status">A</span>
|
||||||
|
<span className="file-name">{file}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{deleted.map((file) => (
|
||||||
|
<div key={file} className="file-item deleted">
|
||||||
|
<span className="file-status">D</span>
|
||||||
|
<span className="file-name">{file}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{untracked.map((file) => (
|
||||||
|
<div key={file} className="file-item untracked">
|
||||||
|
<span className="file-status">?</span>
|
||||||
|
<span className="file-name">{file}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="commit-form">
|
||||||
|
<textarea
|
||||||
|
placeholder="Commit message"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
rows={2}
|
||||||
|
className="commit-message-input"
|
||||||
|
/>
|
||||||
|
{error && <div className="commit-error">{error}</div>}
|
||||||
|
<button
|
||||||
|
onClick={handleCommit}
|
||||||
|
disabled={loading || !message.trim()}
|
||||||
|
className="commit-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{loading ? "Committing..." : "Commit"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
checkoutBranch,
|
||||||
|
createBranch,
|
||||||
|
fetchRepository,
|
||||||
|
getRepositoryStatus,
|
||||||
|
pullRepository,
|
||||||
|
pushRepository,
|
||||||
|
type GitStatus,
|
||||||
|
} from "../api/git_repositories";
|
||||||
|
|
||||||
|
interface GitToolbarProps {
|
||||||
|
projectId: string;
|
||||||
|
repoId: string;
|
||||||
|
currentBranch: string;
|
||||||
|
branches: string[];
|
||||||
|
onBranchChange: (branch: string) => void;
|
||||||
|
onRefresh: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GitToolbar = ({
|
||||||
|
projectId,
|
||||||
|
repoId,
|
||||||
|
currentBranch,
|
||||||
|
branches,
|
||||||
|
onBranchChange,
|
||||||
|
onRefresh,
|
||||||
|
}: GitToolbarProps) => {
|
||||||
|
const [status, setStatus] = useState<GitStatus | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [showNewBranch, setShowNewBranch] = useState(false);
|
||||||
|
const [newBranchName, setNewBranchName] = useState("");
|
||||||
|
const [newBranchBase, setNewBranchBase] = useState("");
|
||||||
|
|
||||||
|
const loadStatus = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const data = await getRepositoryStatus(projectId, repoId);
|
||||||
|
setStatus(data);
|
||||||
|
setError(null);
|
||||||
|
} catch {
|
||||||
|
setError("Failed to load status");
|
||||||
|
}
|
||||||
|
}, [projectId, repoId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadStatus();
|
||||||
|
// Poll status every 5 seconds
|
||||||
|
const interval = setInterval(() => void loadStatus(), 5000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [loadStatus]);
|
||||||
|
|
||||||
|
const handleFetch = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await fetchRepository(projectId, repoId);
|
||||||
|
await loadStatus();
|
||||||
|
} catch {
|
||||||
|
setError("Fetch failed");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePull = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await pullRepository(projectId, repoId, currentBranch);
|
||||||
|
await loadStatus();
|
||||||
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
setError("Pull failed");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePush = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await pushRepository(projectId, repoId, currentBranch);
|
||||||
|
await loadStatus();
|
||||||
|
} catch {
|
||||||
|
setError("Push failed");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckout = async (branch: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await checkoutBranch(projectId, repoId, branch);
|
||||||
|
onBranchChange(branch);
|
||||||
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
setError("Checkout failed");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateBranch = async () => {
|
||||||
|
if (!newBranchName.trim()) return;
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await createBranch(projectId, repoId, newBranchName, newBranchBase || "HEAD");
|
||||||
|
setShowNewBranch(false);
|
||||||
|
setNewBranchName("");
|
||||||
|
setNewBranchBase("");
|
||||||
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
setError("Failed to create branch");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasChanges = status && (
|
||||||
|
status.modified.length > 0 ||
|
||||||
|
status.added.length > 0 ||
|
||||||
|
status.deleted.length > 0 ||
|
||||||
|
status.untracked.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="git-toolbar">
|
||||||
|
{error && <div className="toolbar-error">{error}</div>}
|
||||||
|
|
||||||
|
<div className="toolbar-row">
|
||||||
|
<div className="toolbar-group">
|
||||||
|
<select
|
||||||
|
value={currentBranch}
|
||||||
|
onChange={(e) => handleCheckout(e.target.value)}
|
||||||
|
disabled={loading}
|
||||||
|
className="branch-select"
|
||||||
|
>
|
||||||
|
{branches.map((b) => (
|
||||||
|
<option key={b} value={b}>
|
||||||
|
{b === currentBranch ? `● ${b}` : b}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
onClick={() => setShowNewBranch(!showNewBranch)}
|
||||||
|
disabled={loading}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
+ New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="toolbar-group">
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
onClick={handleFetch}
|
||||||
|
disabled={loading}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
↓ Fetch
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
onClick={handlePull}
|
||||||
|
disabled={loading}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
⇣ Pull
|
||||||
|
{status?.behind ? <span className="badge">{status.behind}</span> : null}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
onClick={handlePush}
|
||||||
|
disabled={loading || !status?.ahead}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
⇡ Push
|
||||||
|
{status?.ahead ? <span className="badge">{status.ahead}</span> : null}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showNewBranch && (
|
||||||
|
<div className="toolbar-row new-branch-form">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Branch name"
|
||||||
|
value={newBranchName}
|
||||||
|
onChange={(e) => setNewBranchName(e.target.value)}
|
||||||
|
className="toolbar-input"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={newBranchBase}
|
||||||
|
onChange={(e) => setNewBranchBase(e.target.value)}
|
||||||
|
className="toolbar-input"
|
||||||
|
>
|
||||||
|
<option value="">Base: HEAD</option>
|
||||||
|
{branches.map((b) => (
|
||||||
|
<option key={b} value={b}>{ b}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
className="toolbar-button primary"
|
||||||
|
onClick={handleCreateBranch}
|
||||||
|
disabled={loading || !newBranchName.trim()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
onClick={() => setShowNewBranch(false)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasChanges && status && (
|
||||||
|
<div className="toolbar-row status-summary">
|
||||||
|
{status.modified.length > 0 && <span className="status-badge modified">✏️ {status.modified.length} modified</span>}
|
||||||
|
{status.added.length > 0 && <span className="status-badge added">✨ {status.added.length} added</span>}
|
||||||
|
{status.deleted.length > 0 && <span className="status-badge deleted">🗑️ {status.deleted.length} deleted</span>}
|
||||||
|
{status.untracked.length > 0 && <span className="status-badge untracked">❓ {status.untracked.length} untracked</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,14 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { Link, useParams, useSearchParams } from "react-router-dom";
|
import { Link, useParams, useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { apiClient } from "../api/client";
|
import { apiClient } from "../api/client";
|
||||||
import { listRepositories, type GitRepository } from "../api/git_repositories";
|
import {
|
||||||
|
getRepositoryStatus,
|
||||||
|
listRepositories,
|
||||||
|
type GitRepository,
|
||||||
|
type GitStatus,
|
||||||
|
} from "../api/git_repositories";
|
||||||
|
import { CommitPanel } from "../components/commit-panel";
|
||||||
|
import { GitToolbar } from "../components/git-toolbar";
|
||||||
|
|
||||||
type WorkspaceStatus = "loading" | "ready" | "error" | "empty";
|
type WorkspaceStatus = "loading" | "ready" | "error" | "empty";
|
||||||
|
|
||||||
@@ -30,6 +37,9 @@ export const RepoWorkspace = () => {
|
|||||||
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(
|
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(
|
||||||
searchParams.get("repo")
|
searchParams.get("repo")
|
||||||
);
|
);
|
||||||
|
const [branches, setBranches] = useState<string[]>([]);
|
||||||
|
const [currentBranch, setCurrentBranch] = useState<string>("main");
|
||||||
|
const [gitStatus, setGitStatus] = useState<GitStatus | null>(null);
|
||||||
|
|
||||||
const loadRepositories = useCallback(async () => {
|
const loadRepositories = useCallback(async () => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
@@ -57,10 +67,42 @@ export const RepoWorkspace = () => {
|
|||||||
}
|
}
|
||||||
}, [projectId, selectedRepoId, searchParams, setSearchParams]);
|
}, [projectId, selectedRepoId, searchParams, setSearchParams]);
|
||||||
|
|
||||||
|
const loadBranches = useCallback(async () => {
|
||||||
|
if (!projectId || !selectedRepoId) return;
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(
|
||||||
|
`/projects/${projectId}/repositories/${selectedRepoId}/branches`
|
||||||
|
);
|
||||||
|
const branchList = response.data.branches.map((b: { name: string }) => b.name);
|
||||||
|
setBranches(branchList);
|
||||||
|
const defaultBranch = response.data.default_branch;
|
||||||
|
if (defaultBranch) {
|
||||||
|
setCurrentBranch(defaultBranch);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setBranches([]);
|
||||||
|
}
|
||||||
|
}, [projectId, selectedRepoId]);
|
||||||
|
|
||||||
|
const loadGitStatus = useCallback(async () => {
|
||||||
|
if (!projectId || !selectedRepoId) return;
|
||||||
|
try {
|
||||||
|
const data = await getRepositoryStatus(projectId, selectedRepoId);
|
||||||
|
setGitStatus(data);
|
||||||
|
} catch {
|
||||||
|
setGitStatus(null);
|
||||||
|
}
|
||||||
|
}, [projectId, selectedRepoId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadRepositories();
|
void loadRepositories();
|
||||||
}, [loadRepositories]);
|
}, [loadRepositories]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadBranches();
|
||||||
|
void loadGitStatus();
|
||||||
|
}, [loadBranches, loadGitStatus]);
|
||||||
|
|
||||||
const handleRepoChange = (repoId: string) => {
|
const handleRepoChange = (repoId: string) => {
|
||||||
setSelectedRepoId(repoId);
|
setSelectedRepoId(repoId);
|
||||||
const newParams = new URLSearchParams(searchParams);
|
const newParams = new URLSearchParams(searchParams);
|
||||||
@@ -138,10 +180,44 @@ export const RepoWorkspace = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedRepoId && (
|
{selectedRepoId && (
|
||||||
<FileBrowser
|
<>
|
||||||
projectId={projectId!}
|
<GitToolbar
|
||||||
repoId={selectedRepoId}
|
projectId={projectId!}
|
||||||
/>
|
repoId={selectedRepoId}
|
||||||
|
currentBranch={currentBranch}
|
||||||
|
branches={branches}
|
||||||
|
onBranchChange={(branch) => {
|
||||||
|
setCurrentBranch(branch);
|
||||||
|
const newParams = new URLSearchParams(searchParams);
|
||||||
|
newParams.set("branch", branch);
|
||||||
|
setSearchParams(newParams);
|
||||||
|
}}
|
||||||
|
onRefresh={() => {
|
||||||
|
void loadBranches();
|
||||||
|
void loadGitStatus();
|
||||||
|
window.dispatchEvent(new CustomEvent("refresh-file-tree"));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FileBrowser
|
||||||
|
projectId={projectId!}
|
||||||
|
repoId={selectedRepoId}
|
||||||
|
gitStatus={gitStatus}
|
||||||
|
/>
|
||||||
|
{gitStatus && (
|
||||||
|
<CommitPanel
|
||||||
|
projectId={projectId!}
|
||||||
|
repoId={selectedRepoId}
|
||||||
|
modified={gitStatus.modified}
|
||||||
|
added={gitStatus.added}
|
||||||
|
deleted={gitStatus.deleted}
|
||||||
|
untracked={gitStatus.untracked}
|
||||||
|
onCommit={() => {
|
||||||
|
void loadGitStatus();
|
||||||
|
window.dispatchEvent(new CustomEvent("refresh-file-tree"));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -160,9 +236,11 @@ export const RepoWorkspace = () => {
|
|||||||
const FileBrowser = ({
|
const FileBrowser = ({
|
||||||
projectId,
|
projectId,
|
||||||
repoId,
|
repoId,
|
||||||
|
gitStatus,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
repoId: string;
|
repoId: string;
|
||||||
|
gitStatus: GitStatus | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [entries, setEntries] = useState<FileTreeEntry[]>([]);
|
const [entries, setEntries] = useState<FileTreeEntry[]>([]);
|
||||||
@@ -197,6 +275,13 @@ const FileBrowser = ({
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
}, [loadFiles]);
|
}, [loadFiles]);
|
||||||
|
|
||||||
|
// Listen for refresh events
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRefresh = () => void loadFiles();
|
||||||
|
window.addEventListener("refresh-file-tree", handleRefresh);
|
||||||
|
return () => window.removeEventListener("refresh-file-tree", handleRefresh);
|
||||||
|
}, [loadFiles]);
|
||||||
|
|
||||||
const handleEntryClick = (entry: FileTreeEntry) => {
|
const handleEntryClick = (entry: FileTreeEntry) => {
|
||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
const newParams = new URLSearchParams(searchParams);
|
const newParams = new URLSearchParams(searchParams);
|
||||||
@@ -221,6 +306,15 @@ const FileBrowser = ({
|
|||||||
setSearchParams(newParams);
|
setSearchParams(newParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFileStatus = (filePath: string): string | null => {
|
||||||
|
if (!gitStatus) return null;
|
||||||
|
if (gitStatus.modified.includes(filePath)) return "modified";
|
||||||
|
if (gitStatus.added.includes(filePath)) return "added";
|
||||||
|
if (gitStatus.deleted.includes(filePath)) return "deleted";
|
||||||
|
if (gitStatus.untracked.includes(filePath)) return "untracked";
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) return <p className="muted">Loading files...</p>;
|
if (loading) return <p className="muted">Loading files...</p>;
|
||||||
if (error) return <p className="error-text">{error}</p>;
|
if (error) return <p className="error-text">{error}</p>;
|
||||||
|
|
||||||
@@ -231,16 +325,27 @@ const FileBrowser = ({
|
|||||||
📁 ..
|
📁 ..
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{entries.map((entry) => (
|
{entries.map((entry) => {
|
||||||
<button
|
const fileStatus = entry.type === "file" ? getFileStatus(entry.path) : null;
|
||||||
key={entry.path}
|
return (
|
||||||
className={`tree-entry ${entry.type === "directory" ? "tree-directory" : "tree-file"}`}
|
<button
|
||||||
onClick={() => handleEntryClick(entry)}
|
key={entry.path}
|
||||||
type="button"
|
className={`tree-entry ${entry.type === "directory" ? "tree-directory" : "tree-file"} ${fileStatus || ""}`}
|
||||||
>
|
onClick={() => handleEntryClick(entry)}
|
||||||
{entry.type === "directory" ? "📁" : "📄"} {entry.name}
|
type="button"
|
||||||
</button>
|
>
|
||||||
))}
|
{entry.type === "directory" ? "📁" : "📄"} {entry.name}
|
||||||
|
{fileStatus && (
|
||||||
|
<span className={`file-status-indicator ${fileStatus}`}>
|
||||||
|
{fileStatus === "modified" && "M"}
|
||||||
|
{fileStatus === "added" && "A"}
|
||||||
|
{fileStatus === "deleted" && "D"}
|
||||||
|
{fileStatus === "untracked" && "?"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -842,3 +842,111 @@ a {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* File Status Indicators */
|
||||||
|
.file-status-indicator {
|
||||||
|
float: right;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 0.375rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status-indicator.modified {
|
||||||
|
color: #f59e0b;
|
||||||
|
background: rgba(245, 158, 11, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status-indicator.added {
|
||||||
|
color: #10b981;
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status-indicator.deleted {
|
||||||
|
color: #ef4444;
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status-indicator.untracked {
|
||||||
|
color: #6b7280;
|
||||||
|
background: rgba(107, 114, 128, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Commit Panel */
|
||||||
|
.commit-panel {
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-panel h4 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
width: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.modified .file-status { color: #f59e0b; }
|
||||||
|
.file-item.added .file-status { color: #10b981; }
|
||||||
|
.file-item.deleted .file-status { color: #ef4444; }
|
||||||
|
.file-item.untracked .file-status { color: #6b7280; }
|
||||||
|
|
||||||
|
.commit-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-message-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-error {
|
||||||
|
color: #ef4444;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|||||||
+20
-20
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Phase 1: Backend File APIs
|
## Phase 1: Backend File APIs
|
||||||
|
|
||||||
- [ ] **Task 1.1**: Create git file utilities
|
- [x] **Task 1.1**: Create git file utilities
|
||||||
- Create `src/utils/git_files.py`
|
- Create `src/utils/git_files.py`
|
||||||
- `list_tree()` - list files in directory using `git ls-tree`
|
- `list_tree()` - list files in directory using `git ls-tree`
|
||||||
- `get_file_content()` - get file content using `git show`
|
- `get_file_content()` - get file content using `git show`
|
||||||
@@ -10,23 +10,23 @@
|
|||||||
- `commit_file()` - commit file changes using `git add` + `git commit`
|
- `commit_file()` - commit file changes using `git add` + `git commit`
|
||||||
- Add tests
|
- Add tests
|
||||||
|
|
||||||
- [ ] **Task 1.2**: Add file listing endpoint
|
- [x] **Task 1.2**: Add file listing endpoint
|
||||||
- Add `GET /projects/{id}/repositories/{id}/files` to git_repositories.py
|
- Add `GET /projects/{id}/repositories/{id}/files` to git_repositories.py
|
||||||
- Query params: branch, path
|
- Query params: branch, path
|
||||||
- Returns FileTreeEntry list
|
- Returns FileTreeEntry list
|
||||||
- Handle errors (missing branch, missing path)
|
- Handle errors (missing branch, missing path)
|
||||||
|
|
||||||
- [ ] **Task 1.3**: Add file content endpoint
|
- [x] **Task 1.3**: Add file content endpoint
|
||||||
- Add `GET /projects/{id}/repositories/{id}/files/content` to git_repositories.py
|
- Add `GET /projects/{id}/repositories/{id}/files/content` to git_repositories.py
|
||||||
- Query params: branch, path
|
- Query params: branch, path
|
||||||
- Returns FileContent with language detection
|
- Returns FileContent with language detection
|
||||||
- Detect binary files
|
- Detect binary files
|
||||||
|
|
||||||
- [ ] **Task 1.4**: Add branches endpoint
|
- [x] **Task 1.4**: Add branches endpoint
|
||||||
- Add `GET /projects/{id}/repositories/{id}/branches` to git_repositories.py
|
- Add `GET /projects/{id}/repositories/{id}/branches` to git_repositories.py
|
||||||
- Returns branch list with default branch marked
|
- Returns branch list with default branch marked
|
||||||
|
|
||||||
- [ ] **Task 1.5**: Add file update endpoint
|
- [x] **Task 1.5**: Add file update endpoint
|
||||||
- Add `POST /projects/{id}/repositories/{id}/files/content` to git_repositories.py
|
- Add `POST /projects/{id}/repositories/{id}/files/content` to git_repositories.py
|
||||||
- Body: path, branch, content, commit_message, author info
|
- Body: path, branch, content, commit_message, author info
|
||||||
- Create commit with changes
|
- Create commit with changes
|
||||||
@@ -34,29 +34,29 @@
|
|||||||
|
|
||||||
## Phase 2: Project List Navigation
|
## Phase 2: Project List Navigation
|
||||||
|
|
||||||
- [ ] **Task 2.1**: Make project list clickable
|
- [x] **Task 2.1**: Make project list clickable
|
||||||
- Update ProjectsPage to link to workspace
|
- Update ProjectsPage to link to workspace
|
||||||
- Route: `/projects/:projectId`
|
- Route: `/projects/:projectId`
|
||||||
- Remove placeholder, use workspace
|
- Remove placeholder, use workspace
|
||||||
|
|
||||||
- [ ] **Task 2.2**: Update app navigation
|
- [x] **Task 2.2**: Update app navigation
|
||||||
- Ensure project routes are correct
|
- Ensure project routes are correct
|
||||||
- Add breadcrumb or back button
|
- Add breadcrumb or back button
|
||||||
|
|
||||||
## Phase 3: Workspace Page Shell
|
## Phase 3: Workspace Page Shell
|
||||||
|
|
||||||
- [ ] **Task 3.1**: Create RepoWorkspace page
|
- [x] **Task 3.1**: Create RepoWorkspace page
|
||||||
- Create `pages/repo-workspace.tsx`
|
- Create `pages/repo-workspace.tsx`
|
||||||
- Layout: Sidebar + Main Content
|
- Layout: Sidebar + Main Content
|
||||||
- Fetch project repos on load
|
- Fetch project repos on load
|
||||||
- Select first repo by default
|
- Select first repo by default
|
||||||
|
|
||||||
- [ ] **Task 3.2**: Create RepoSelector component
|
- [x] **Task 3.2**: Create RepoSelector component
|
||||||
- Dropdown to switch between project repos
|
- Dropdown to switch between project repos
|
||||||
- Show active repo name
|
- Show active repo name
|
||||||
- Update URL when switching
|
- Update URL when switching
|
||||||
|
|
||||||
- [ ] **Task 3.3**: Create BranchSelector component
|
- [x] **Task 3.3**: Create BranchSelector component
|
||||||
- Dropdown to switch branches
|
- Dropdown to switch branches
|
||||||
- Show active branch
|
- Show active branch
|
||||||
- Mark default branch
|
- Mark default branch
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
## Phase 4: File Browser
|
## Phase 4: File Browser
|
||||||
|
|
||||||
- [ ] **Task 4.1**: Create FileTree component
|
- [x] **Task 4.1**: Create FileTree component
|
||||||
- Recursive tree view
|
- Recursive tree view
|
||||||
- Expandable/collapsible folders
|
- Expandable/collapsible folders
|
||||||
- File icons by extension
|
- File icons by extension
|
||||||
@@ -72,21 +72,21 @@
|
|||||||
- Active file highlight
|
- Active file highlight
|
||||||
- Fetch tree data from API
|
- Fetch tree data from API
|
||||||
|
|
||||||
- [ ] **Task 4.2**: Add file tree loading
|
- [x] **Task 4.2**: Add file tree loading
|
||||||
- Load root on repo/branch change
|
- Load root on repo/branch change
|
||||||
- Lazy load subdirectories
|
- Lazy load subdirectories
|
||||||
- Show loading state
|
- Show loading state
|
||||||
|
|
||||||
## Phase 5: File Viewer
|
## Phase 5: File Viewer
|
||||||
|
|
||||||
- [ ] **Task 5.1**: Create FileViewer component
|
- [x] **Task 5.1**: Create FileViewer component
|
||||||
- Display file content
|
- Display file content
|
||||||
- Line numbers
|
- Line numbers
|
||||||
- Syntax highlighting (prismjs or similar)
|
- Syntax highlighting (prismjs or similar)
|
||||||
- Breadcrumb navigation
|
- Breadcrumb navigation
|
||||||
- Show file metadata (size, last commit)
|
- Show file metadata (size, last commit)
|
||||||
|
|
||||||
- [ ] **Task 5.2**: Add edit mode
|
- [x] **Task 5.2**: Add edit mode
|
||||||
- Toggle between view/edit
|
- Toggle between view/edit
|
||||||
- Textarea for editing
|
- Textarea for editing
|
||||||
- Save button (calls update API)
|
- Save button (calls update API)
|
||||||
@@ -95,37 +95,37 @@
|
|||||||
|
|
||||||
## Phase 6: Integration & Polish
|
## Phase 6: Integration & Polish
|
||||||
|
|
||||||
- [ ] **Task 6.1**: Sync URL state
|
- [x] **Task 6.1**: Sync URL state
|
||||||
- Repo ID in URL
|
- Repo ID in URL
|
||||||
- Branch in URL
|
- Branch in URL
|
||||||
- Path in URL
|
- Path in URL
|
||||||
- Parse on load, update on change
|
- Parse on load, update on change
|
||||||
|
|
||||||
- [ ] **Task 6.2**: Add error handling
|
- [x] **Task 6.2**: Add error handling
|
||||||
- Repo not found
|
- Repo not found
|
||||||
- Branch not found
|
- Branch not found
|
||||||
- File not found
|
- File not found
|
||||||
- Binary files
|
- Binary files
|
||||||
- Network errors
|
- Network errors
|
||||||
|
|
||||||
- [ ] **Task 6.3**: Add CSS styles
|
- [x] **Task 6.3**: Add CSS styles
|
||||||
- Workspace layout
|
- Workspace layout
|
||||||
- File tree styles
|
- File tree styles
|
||||||
- File viewer styles
|
- File viewer styles
|
||||||
- Sidebar styles
|
- Sidebar styles
|
||||||
- Responsive design
|
- Responsive design
|
||||||
|
|
||||||
- [ ] **Task 6.4**: Run quality gates
|
- [x] **Task 6.4**: Run quality gates
|
||||||
- Backend: ruff, mypy, pytest
|
- Backend: ruff, mypy, pytest
|
||||||
- Frontend: typecheck, lint, build
|
- Frontend: typecheck, lint, build
|
||||||
|
|
||||||
## Phase 7: Route Updates
|
## Phase 7: Route Updates
|
||||||
|
|
||||||
- [ ] **Task 7.1**: Update router
|
- [x] **Task 7.1**: Update router
|
||||||
- `/projects/:projectId` → RepoWorkspace (default)
|
- `/projects/:projectId` → RepoWorkspace (default)
|
||||||
- Move old project details to `/projects/:projectId/details` or remove
|
- Move old project details to `/projects/:projectId/details` or remove
|
||||||
- Keep `/projects/:projectId/repositories` for repo management
|
- Keep `/projects/:projectId/repositories` for repo management
|
||||||
|
|
||||||
- [ ] **Task 7.2**: Update navigation
|
- [x] **Task 7.2**: Update navigation
|
||||||
- Project list links to workspace
|
- Project list links to workspace
|
||||||
- Add "Manage Repositories" link in workspace
|
- Add "Manage Repositories" link in workspace
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-19
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# Documentation Overhaul - Design
|
||||||
|
|
||||||
|
## Documentation Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── README.md # Documentation index
|
||||||
|
├── architecture/
|
||||||
|
│ ├── backend.md # Backend architecture
|
||||||
|
│ ├── frontend.md # Frontend architecture
|
||||||
|
│ ├── database.md # Database schema
|
||||||
|
│ └── deployment.md # Deployment architecture
|
||||||
|
├── features/
|
||||||
|
│ ├── projects.md # Project management
|
||||||
|
│ ├── repositories.md # Git repositories
|
||||||
|
│ ├── workspace.md # Repository workspace
|
||||||
|
│ ├── git-history.md # Git history visualization
|
||||||
|
│ ├── auth.md # Authentication
|
||||||
|
│ ├── tool-types.md # Tool type management
|
||||||
|
│ └── settings.md # User settings
|
||||||
|
├── api/
|
||||||
|
│ ├── README.md # API overview
|
||||||
|
│ ├── auth.md # Auth endpoints
|
||||||
|
│ ├── projects.md # Project endpoints
|
||||||
|
│ ├── repositories.md # Repository endpoints
|
||||||
|
│ └── users.md # User endpoints
|
||||||
|
├── deployment/
|
||||||
|
│ ├── docker.md # Docker setup
|
||||||
|
│ ├── traefik.md # Traefik configuration
|
||||||
|
│ ├── authentik.md # Authentik setup
|
||||||
|
│ └── environment.md # Environment variables
|
||||||
|
├── development/
|
||||||
|
│ ├── setup.md # Development setup
|
||||||
|
│ ├── testing.md # Testing strategy (moved from README)
|
||||||
|
│ ├── contributing.md # How to contribute
|
||||||
|
│ └── quality-gates.md # Code quality
|
||||||
|
└── templates/
|
||||||
|
├── feature-doc.md # Template for new features
|
||||||
|
├── api-endpoint.md # Template for API docs
|
||||||
|
└── architecture.md # Template for architecture docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## README.md Structure
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Headquarter
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Short description of what the project is and does.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Feature 1 (with link to docs)
|
||||||
|
- Feature 2 (with link to docs)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
1. Clone repo
|
||||||
|
2. Copy .env.example to .env
|
||||||
|
3. docker compose up
|
||||||
|
4. Open http://localhost:5173
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
Link to architecture docs.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- [User Guide](docs/features/)
|
||||||
|
- [API Docs](docs/api/)
|
||||||
|
- [Deployment](docs/deployment/)
|
||||||
|
- [Development](docs/development/)
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- Backend: FastAPI + SQLAlchemy + PostgreSQL
|
||||||
|
- Frontend: React + TypeScript + Vite
|
||||||
|
- Auth: Authentik (OAuth2)
|
||||||
|
- Deployment: Docker + Traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Templates
|
||||||
|
|
||||||
|
### Feature Documentation Template
|
||||||
|
```markdown
|
||||||
|
# Feature Name
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
What does this feature do?
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
Step-by-step user guide.
|
||||||
|
|
||||||
|
## Screenshots/Diagrams
|
||||||
|
Visual aids.
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
Related API endpoints.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
Relevant config options.
|
||||||
|
|
||||||
|
## Related Features
|
||||||
|
Links to related docs.
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoint Template
|
||||||
|
```markdown
|
||||||
|
## GET /api/endpoint
|
||||||
|
|
||||||
|
**Description:** What this endpoint does.
|
||||||
|
|
||||||
|
**Authentication:** Required/Optional
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
- Query params
|
||||||
|
- Body schema
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- Success schema
|
||||||
|
- Error codes
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl /api/endpoint
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auto-Documentation Process
|
||||||
|
|
||||||
|
For new features, documentation should be created:
|
||||||
|
|
||||||
|
1. **During implementation** (not after):
|
||||||
|
- When creating a new feature, create its doc file
|
||||||
|
- Use the template from docs/templates/
|
||||||
|
- Update README features list
|
||||||
|
|
||||||
|
2. **In the same commit**:
|
||||||
|
- Code changes + doc changes in same PR
|
||||||
|
- Review docs alongside code
|
||||||
|
|
||||||
|
3. **Checklist**:
|
||||||
|
- [ ] Feature doc created in docs/features/
|
||||||
|
- [ ] API endpoints documented in docs/api/
|
||||||
|
- [ ] README updated with feature link
|
||||||
|
- [ ] Architecture doc updated if needed
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
- **Monthly review**: Check for outdated docs
|
||||||
|
- **Version tracking**: Tag docs with app version
|
||||||
|
- **OpenSpec integration**: Link to OpenSpec changes for context
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- **Markdown**: All docs in Markdown
|
||||||
|
- **Mermaid**: Diagrams in Mermaid syntax
|
||||||
|
- **FastAPI docs**: Auto-generated from code
|
||||||
|
- **GitHub Pages**: Optional static site generation
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Documentation Overhaul
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The project has grown significantly but documentation hasn't kept up:
|
||||||
|
|
||||||
|
- **README is minimal**: Only contains testing strategy, no project overview
|
||||||
|
- **No feature documentation**: Users can't discover what the app does
|
||||||
|
- **No API docs**: Developers have to read source code
|
||||||
|
- **No deployment guide**: Docker/Traefik setup is tribal knowledge
|
||||||
|
- **No architecture docs**: New contributors can't understand the codebase
|
||||||
|
- **No user guide**: Features like repo workspace, git history aren't explained
|
||||||
|
- **OpenSpec changes aren't linked**: Completed changes exist but aren't referenced
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Create a comprehensive documentation system:
|
||||||
|
|
||||||
|
1. **Rewrite README**: Project overview, features, quick start, architecture
|
||||||
|
2. **Create docs/ directory**: Structured documentation
|
||||||
|
3. **Document all features**: What exists and how to use it
|
||||||
|
4. **Create API documentation**: Auto-generated + manual docs
|
||||||
|
5. **Create deployment guide**: Docker, Traefik, Authentik setup
|
||||||
|
6. **Create architecture docs**: Backend, frontend, data flow
|
||||||
|
7. **Create documentation templates**: For future features
|
||||||
|
8. **Create CONTRIBUTING.md**: How to add docs for new features
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Onboarding**: New developers understand the project in minutes
|
||||||
|
- **Discovery**: Users discover features they didn't know existed
|
||||||
|
- **Maintenance**: Architecture docs help refactoring decisions
|
||||||
|
- **Deployment**: Clear setup instructions reduce support burden
|
||||||
|
- **Future-proof**: Templates ensure new features get documented
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### What gets documented:
|
||||||
|
- All existing features (projects, repos, git history, workspace, auth, etc.)
|
||||||
|
- Architecture (backend, frontend, database, deployment)
|
||||||
|
- API endpoints
|
||||||
|
- Configuration options
|
||||||
|
- Development setup
|
||||||
|
|
||||||
|
### What gets created:
|
||||||
|
- README.md (rewritten)
|
||||||
|
- docs/ directory with structured docs
|
||||||
|
- docs/templates/ for new features
|
||||||
|
- docs/api/ for API documentation
|
||||||
|
- docs/architecture/ for system design
|
||||||
|
- docs/deployment/ for setup guides
|
||||||
|
- docs/features/ for user guides
|
||||||
|
|
||||||
|
### What stays:
|
||||||
|
- Testing strategy (moved to docs/testing.md)
|
||||||
|
- OpenSpec changes (archived as-is)
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# Documentation Overhaul Specification
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
1. **README Rewrite**: Comprehensive project overview with features, quick start, and links
|
||||||
|
2. **Feature Documentation**: Every feature has a user guide in docs/features/
|
||||||
|
3. **API Documentation**: All endpoints documented with examples
|
||||||
|
4. **Architecture Docs**: Backend, frontend, and deployment architecture explained
|
||||||
|
5. **Deployment Guide**: Step-by-step Docker/Traefik/Authentik setup
|
||||||
|
6. **Development Guide**: Setup, testing, contributing guidelines
|
||||||
|
7. **Templates**: Reusable templates for future documentation
|
||||||
|
8. **Auto-Documentation**: Process ensuring new features get documented
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
1. **Discoverability**: Users can find docs easily from README
|
||||||
|
2. **Completeness**: All current features documented
|
||||||
|
3. **Accuracy**: Docs match current implementation
|
||||||
|
4. **Maintainability**: Templates and processes keep docs up-to-date
|
||||||
|
5. **Accessibility**: Markdown format, clear structure
|
||||||
|
|
||||||
|
## Documentation Inventory
|
||||||
|
|
||||||
|
### Current Features to Document
|
||||||
|
|
||||||
|
1. **Project Management**
|
||||||
|
- Create/edit/delete projects
|
||||||
|
- Project list view
|
||||||
|
- Project workspace (default view)
|
||||||
|
|
||||||
|
2. **Git Repositories**
|
||||||
|
- Create repositories (bare init, mirror clone)
|
||||||
|
- Smart URL parsing
|
||||||
|
- Repository list
|
||||||
|
- Repository deletion
|
||||||
|
|
||||||
|
3. **Repository Workspace**
|
||||||
|
- File browser (tree view)
|
||||||
|
- File viewer (syntax highlighting)
|
||||||
|
- Branch switching
|
||||||
|
- Repository switching
|
||||||
|
- Quick file editing
|
||||||
|
|
||||||
|
4. **Git History**
|
||||||
|
- Commit history visualization
|
||||||
|
- Branch graph
|
||||||
|
- Commit details (diff, stats)
|
||||||
|
- Branch filtering
|
||||||
|
|
||||||
|
5. **Authentication**
|
||||||
|
- Authentik OAuth2 flow
|
||||||
|
- Session-based auth
|
||||||
|
- User profile
|
||||||
|
- Logout
|
||||||
|
|
||||||
|
6. **User Settings**
|
||||||
|
- Theme selection
|
||||||
|
- Git identity
|
||||||
|
- Default editor
|
||||||
|
|
||||||
|
7. **Tool Types**
|
||||||
|
- Built-in types (code-server, jupyter)
|
||||||
|
- Custom type creation
|
||||||
|
- Compose template validation
|
||||||
|
|
||||||
|
8. **SSH Keys**
|
||||||
|
- Generate key pairs
|
||||||
|
- List/delete keys
|
||||||
|
- Copy public key
|
||||||
|
|
||||||
|
## API Endpoints to Document
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
- GET /auth/login
|
||||||
|
- GET /auth/callback
|
||||||
|
- GET /auth/me
|
||||||
|
- POST /auth/logout
|
||||||
|
|
||||||
|
### Projects
|
||||||
|
- GET /projects
|
||||||
|
- POST /projects
|
||||||
|
- GET /projects/{id}
|
||||||
|
- PUT /projects/{id}
|
||||||
|
- DELETE /projects/{id}
|
||||||
|
|
||||||
|
### Repositories
|
||||||
|
- GET /projects/{id}/repositories
|
||||||
|
- POST /projects/{id}/repositories
|
||||||
|
- DELETE /projects/{id}/repositories/{id}
|
||||||
|
- GET /projects/{id}/repositories/{id}/files
|
||||||
|
- GET /projects/{id}/repositories/{id}/files/content
|
||||||
|
- POST /projects/{id}/repositories/{id}/files/content
|
||||||
|
- GET /projects/{id}/repositories/{id}/branches
|
||||||
|
- GET /projects/{id}/repositories/{id}/history
|
||||||
|
- GET /projects/{id}/repositories/{id}/commits/{hash}
|
||||||
|
|
||||||
|
### Users
|
||||||
|
- GET /users/me
|
||||||
|
- PUT /users/me
|
||||||
|
- POST /users/me/avatar
|
||||||
|
- GET /users/me/config
|
||||||
|
- PATCH /users/me/config
|
||||||
|
|
||||||
|
### Tool Types
|
||||||
|
- GET /tool-types
|
||||||
|
- POST /tool-types
|
||||||
|
- GET /tool-types/{id}
|
||||||
|
- PUT /tool-types/{id}
|
||||||
|
- DELETE /tool-types/{id}
|
||||||
|
|
||||||
|
### SSH Keys
|
||||||
|
- GET /ssh-keys
|
||||||
|
- POST /ssh-keys
|
||||||
|
- DELETE /ssh-keys/{id}
|
||||||
|
|
||||||
|
## Documentation Templates
|
||||||
|
|
||||||
|
### Feature Doc Template
|
||||||
|
```markdown
|
||||||
|
# [Feature Name]
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
[1-2 sentence description]
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
[Step-by-step guide]
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
[If applicable]
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
[Links to API docs]
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
[Relevant env vars/settings]
|
||||||
|
|
||||||
|
## Related
|
||||||
|
[Links to related features]
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Doc Template
|
||||||
|
```markdown
|
||||||
|
## [METHOD] [PATH]
|
||||||
|
|
||||||
|
**Auth:** [Required/Optional]
|
||||||
|
|
||||||
|
**Description:** [What it does]
|
||||||
|
|
||||||
|
### Request
|
||||||
|
[Params/body schema]
|
||||||
|
|
||||||
|
### Response
|
||||||
|
[Success/error schemas]
|
||||||
|
|
||||||
|
### Example
|
||||||
|
[Code example]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
For every new feature:
|
||||||
|
- [ ] Create feature doc in docs/features/
|
||||||
|
- [ ] Document API endpoints in docs/api/
|
||||||
|
- [ ] Update README.md features list
|
||||||
|
- [ ] Update architecture docs if needed
|
||||||
|
- [ ] Add to CHANGELOG.md
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
1. README provides clear project overview
|
||||||
|
2. All 8 feature areas have user guides
|
||||||
|
3. All API endpoints have documentation
|
||||||
|
4. New developer can set up project in < 15 minutes
|
||||||
|
5. Deployment guide enables setup without asking questions
|
||||||
|
6. Templates exist for future documentation
|
||||||
|
7. CONTRIBUTING.md explains documentation requirements
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
# Documentation Overhaul - Tasks
|
||||||
|
|
||||||
|
## Phase 1: README Rewrite
|
||||||
|
|
||||||
|
- [ ] **Task 1.1**: Rewrite README.md
|
||||||
|
- Add project overview and description
|
||||||
|
- List all features with brief descriptions
|
||||||
|
- Add quick start section
|
||||||
|
- Add tech stack section
|
||||||
|
- Add links to docs/ directory
|
||||||
|
- Keep testing strategy (or move to docs/testing.md)
|
||||||
|
|
||||||
|
- [ ] **Task 1.2**: Create docs/README.md
|
||||||
|
- Documentation index
|
||||||
|
- Link to all doc sections
|
||||||
|
- Quick navigation
|
||||||
|
|
||||||
|
## Phase 2: Create Documentation Structure
|
||||||
|
|
||||||
|
- [ ] **Task 2.1**: Create docs/ directory structure
|
||||||
|
- mkdir docs/architecture
|
||||||
|
- mkdir docs/features
|
||||||
|
- mkdir docs/api
|
||||||
|
- mkdir docs/deployment
|
||||||
|
- mkdir docs/development
|
||||||
|
- mkdir docs/templates
|
||||||
|
|
||||||
|
- [ ] **Task 2.2**: Create documentation templates
|
||||||
|
- docs/templates/feature-doc.md
|
||||||
|
- docs/templates/api-endpoint.md
|
||||||
|
- docs/templates/architecture.md
|
||||||
|
|
||||||
|
## Phase 3: Feature Documentation
|
||||||
|
|
||||||
|
- [ ] **Task 3.1**: Document Project Management
|
||||||
|
- docs/features/projects.md
|
||||||
|
- Creating/editing/deleting projects
|
||||||
|
- Project list view
|
||||||
|
- Project workspace
|
||||||
|
|
||||||
|
- [ ] **Task 3.2**: Document Git Repositories
|
||||||
|
- docs/features/repositories.md
|
||||||
|
- Creating repos (bare init, mirror clone)
|
||||||
|
- Smart URL parsing
|
||||||
|
- Repository management
|
||||||
|
|
||||||
|
- [ ] **Task 3.3**: Document Repository Workspace
|
||||||
|
- docs/features/workspace.md
|
||||||
|
- File browser
|
||||||
|
- File viewer
|
||||||
|
- Branch switching
|
||||||
|
- Quick editing
|
||||||
|
|
||||||
|
- [ ] **Task 3.4**: Document Git History
|
||||||
|
- docs/features/git-history.md
|
||||||
|
- Commit history view
|
||||||
|
- Branch graph
|
||||||
|
- Commit details
|
||||||
|
|
||||||
|
- [ ] **Task 3.5**: Document Authentication
|
||||||
|
- docs/features/auth.md
|
||||||
|
- Authentik OAuth2 flow
|
||||||
|
- Session management
|
||||||
|
- User profile
|
||||||
|
|
||||||
|
- [ ] **Task 3.6**: Document User Settings
|
||||||
|
- docs/features/settings.md
|
||||||
|
- Theme selection
|
||||||
|
- Git identity
|
||||||
|
- Preferences
|
||||||
|
|
||||||
|
- [ ] **Task 3.7**: Document Tool Types
|
||||||
|
- docs/features/tool-types.md
|
||||||
|
- Built-in types
|
||||||
|
- Custom type creation
|
||||||
|
- Compose templates
|
||||||
|
|
||||||
|
- [ ] **Task 3.8**: Document SSH Keys
|
||||||
|
- docs/features/ssh-keys.md
|
||||||
|
- Key generation
|
||||||
|
- Management
|
||||||
|
|
||||||
|
## Phase 4: API Documentation
|
||||||
|
|
||||||
|
- [ ] **Task 4.1**: Document Auth API
|
||||||
|
- docs/api/auth.md
|
||||||
|
- All auth endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.2**: Document Projects API
|
||||||
|
- docs/api/projects.md
|
||||||
|
- All project endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.3**: Document Repositories API
|
||||||
|
- docs/api/repositories.md
|
||||||
|
- All repository endpoints
|
||||||
|
- File operations
|
||||||
|
- History endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.4**: Document Users API
|
||||||
|
- docs/api/users.md
|
||||||
|
- User profile endpoints
|
||||||
|
- Config endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.5**: Document Tool Types API
|
||||||
|
- docs/api/tool-types.md
|
||||||
|
- CRUD endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.6**: Document SSH Keys API
|
||||||
|
- docs/api/ssh-keys.md
|
||||||
|
- Key management endpoints
|
||||||
|
|
||||||
|
## Phase 5: Architecture Documentation
|
||||||
|
|
||||||
|
- [ ] **Task 5.1**: Create backend architecture doc
|
||||||
|
- docs/architecture/backend.md
|
||||||
|
- Tech stack
|
||||||
|
- Directory structure
|
||||||
|
- Data flow
|
||||||
|
- Auth flow
|
||||||
|
|
||||||
|
- [ ] **Task 5.2**: Create frontend architecture doc
|
||||||
|
- docs/architecture/frontend.md
|
||||||
|
- Tech stack
|
||||||
|
- Directory structure
|
||||||
|
- State management
|
||||||
|
- Routing
|
||||||
|
|
||||||
|
- [ ] **Task 5.3**: Create database schema doc
|
||||||
|
- docs/architecture/database.md
|
||||||
|
- Entity relationship diagram
|
||||||
|
- Table descriptions
|
||||||
|
- Migration strategy
|
||||||
|
|
||||||
|
- [ ] **Task 5.4**: Create deployment architecture doc
|
||||||
|
- docs/architecture/deployment.md
|
||||||
|
- Docker architecture
|
||||||
|
- Traefik routing
|
||||||
|
- Service diagram
|
||||||
|
|
||||||
|
## Phase 6: Deployment Guide
|
||||||
|
|
||||||
|
- [ ] **Task 6.1**: Create Docker setup guide
|
||||||
|
- docs/deployment/docker.md
|
||||||
|
- Local development setup
|
||||||
|
- Docker compose configuration
|
||||||
|
|
||||||
|
- [ ] **Task 6.2**: Create Traefik guide
|
||||||
|
- docs/deployment/traefik.md
|
||||||
|
- Traefik configuration
|
||||||
|
- Routing rules
|
||||||
|
- TLS setup
|
||||||
|
|
||||||
|
- [ ] **Task 6.3**: Create Authentik guide
|
||||||
|
- docs/deployment/authentik.md
|
||||||
|
- Provider setup
|
||||||
|
- Application configuration
|
||||||
|
- OAuth2 settings
|
||||||
|
|
||||||
|
- [ ] **Task 6.4**: Create environment variables guide
|
||||||
|
- docs/deployment/environment.md
|
||||||
|
- All env vars explained
|
||||||
|
- Required vs optional
|
||||||
|
- Default values
|
||||||
|
|
||||||
|
## Phase 7: Development Guide
|
||||||
|
|
||||||
|
- [ ] **Task 7.1**: Create setup guide
|
||||||
|
- docs/development/setup.md
|
||||||
|
- Prerequisites
|
||||||
|
- Installation steps
|
||||||
|
- Running locally
|
||||||
|
|
||||||
|
- [ ] **Task 7.2**: Move testing strategy
|
||||||
|
- Move from README.md to docs/development/testing.md
|
||||||
|
- Update references
|
||||||
|
|
||||||
|
- [ ] **Task 7.3**: Create contributing guide
|
||||||
|
- docs/development/contributing.md
|
||||||
|
- Code style
|
||||||
|
- PR process
|
||||||
|
- Documentation requirements
|
||||||
|
|
||||||
|
- [ ] **Task 7.4**: Create quality gates doc
|
||||||
|
- docs/development/quality-gates.md
|
||||||
|
- Linting rules
|
||||||
|
- Type checking
|
||||||
|
- Testing requirements
|
||||||
|
|
||||||
|
## Phase 8: Finalization
|
||||||
|
|
||||||
|
- [ ] **Task 8.1**: Review all docs
|
||||||
|
- Check for completeness
|
||||||
|
- Check for accuracy
|
||||||
|
- Fix broken links
|
||||||
|
|
||||||
|
- [ ] **Task 8.2**: Create CHANGELOG.md
|
||||||
|
- List all features implemented
|
||||||
|
- Link to OpenSpec changes
|
||||||
|
|
||||||
|
- [ ] **Task 8.3**: Commit all documentation
|
||||||
|
- Single commit for docs
|
||||||
|
- Conventional commit message
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-19
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
# Git Control - Design
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Repository Workspace
|
||||||
|
├─ Toolbar
|
||||||
|
│ ├─ [Fetch] [Pull] [Push]
|
||||||
|
│ ├─ [Branch: main ▼] [+ New Branch]
|
||||||
|
│ └─ [Commit] [Merge ▼]
|
||||||
|
├─ Sidebar
|
||||||
|
│ ├─ Repo Selector
|
||||||
|
│ ├─ Branch Selector (with management)
|
||||||
|
│ └─ File Tree (with status icons)
|
||||||
|
│ ├─ 📄 main.py ✏️ (modified)
|
||||||
|
│ ├─ 📁 src/
|
||||||
|
│ └─ 📄 README.md ✨ (new)
|
||||||
|
└─ Main Content
|
||||||
|
├─ File Viewer (with edit/save)
|
||||||
|
└─ Commit Panel (when files modified)
|
||||||
|
├─ Changed files list
|
||||||
|
├─ Commit message input
|
||||||
|
└─ [Commit to main] button
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git Operations
|
||||||
|
|
||||||
|
### Branch Operations
|
||||||
|
|
||||||
|
**Create Branch:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/branches
|
||||||
|
{
|
||||||
|
"name": "feature/new-thing",
|
||||||
|
"base_branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete Branch:**
|
||||||
|
```
|
||||||
|
DELETE /projects/{id}/repositories/{id}/branches/{name}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Checkout Branch:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/checkout
|
||||||
|
{
|
||||||
|
"branch": "feature/new-thing"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working Directory Status
|
||||||
|
|
||||||
|
**Get Status:**
|
||||||
|
```
|
||||||
|
GET /projects/{id}/repositories/{id}/status
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "main",
|
||||||
|
"modified": ["src/main.py", "README.md"],
|
||||||
|
"added": ["new-file.txt"],
|
||||||
|
"deleted": ["old-file.txt"],
|
||||||
|
"untracked": ["temp.log"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit Operations
|
||||||
|
|
||||||
|
**Commit Changes:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/commits
|
||||||
|
{
|
||||||
|
"message": "Update greeting",
|
||||||
|
"author_name": "User",
|
||||||
|
"author_email": "user@example.com",
|
||||||
|
"files": ["src/main.py", "README.md"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remote Operations
|
||||||
|
|
||||||
|
**Fetch:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pull:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/pull
|
||||||
|
{
|
||||||
|
"branch": "main",
|
||||||
|
"strategy": "merge"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Push:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/push
|
||||||
|
{
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Merge:**
|
||||||
|
```
|
||||||
|
POST /projects/{id}/repositories/{id}/merge
|
||||||
|
{
|
||||||
|
"source_branch": "feature/new-thing",
|
||||||
|
"target_branch": "main",
|
||||||
|
"commit_message": "Merge feature into main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Implementation
|
||||||
|
|
||||||
|
### Git Command Utilities
|
||||||
|
|
||||||
|
Extend `src/utils/git_files.py` with:
|
||||||
|
- `get_status(repo_path)` - working directory status
|
||||||
|
- `create_branch(repo_path, name, base)` - create new branch
|
||||||
|
- `delete_branch(repo_path, name)` - delete branch
|
||||||
|
- `checkout_branch(repo_path, name)` - switch branch
|
||||||
|
- `commit_changes(repo_path, files, message, author)` - commit files
|
||||||
|
- `fetch(repo_path)` - fetch from remote
|
||||||
|
- `pull(repo_path, branch)` - pull updates
|
||||||
|
- `push(repo_path, branch)` - push changes
|
||||||
|
- `merge(repo_path, source, target, message)` - merge branches
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
All git operations can fail:
|
||||||
|
- **Merge conflicts**: Return conflict details, require resolution
|
||||||
|
- **Auth failures**: Remote requires authentication
|
||||||
|
- **Dirty working tree**: Can't checkout with uncommitted changes
|
||||||
|
- **Branch exists**: Can't create duplicate branch
|
||||||
|
- **Nothing to commit**: Working tree clean
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- All operations check repository ownership
|
||||||
|
- Push/pull requires valid remote URL
|
||||||
|
- Commits use authenticated user's identity
|
||||||
|
|
||||||
|
## Frontend Implementation
|
||||||
|
|
||||||
|
### Workspace Toolbar
|
||||||
|
|
||||||
|
Add git action bar above file viewer:
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ [Fetch] [Pull] [Push] │ Branch: [main ▼] [+ New] │
|
||||||
|
│ │ [Commit ▼] [Merge ▼] │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branch Management
|
||||||
|
|
||||||
|
**Branch Selector Dropdown:**
|
||||||
|
- List all branches with current branch highlighted
|
||||||
|
- Create new branch option (opens dialog)
|
||||||
|
- Delete branch option (with confirmation)
|
||||||
|
|
||||||
|
**New Branch Dialog:**
|
||||||
|
```
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ Create New Branch │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ Name: [feature/________] │
|
||||||
|
│ Base: [main ▼] │
|
||||||
|
│ │
|
||||||
|
│ [Create] [Cancel] │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working Directory Status
|
||||||
|
|
||||||
|
**Status Indicators in File Tree:**
|
||||||
|
- ✏️ Modified file
|
||||||
|
- ✨ New file
|
||||||
|
- 🗑️ Deleted file
|
||||||
|
- ❓ Untracked file
|
||||||
|
|
||||||
|
**Commit Panel (appears when files modified):**
|
||||||
|
```
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ Changes (3) │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ ✏️ src/main.py │
|
||||||
|
│ ✨ new-file.txt │
|
||||||
|
│ 🗑️ old-file.txt │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ Commit message: │
|
||||||
|
│ [____________________] │
|
||||||
|
│ │
|
||||||
|
│ [Commit to main] │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merge Dialog
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ Merge Branch │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ Source: [feature/xyz ▼] │
|
||||||
|
│ Target: main │
|
||||||
|
│ │
|
||||||
|
│ Commit message: │
|
||||||
|
│ [Merge feature/xyz into main]│
|
||||||
|
│ │
|
||||||
|
│ [Merge] [Cancel] │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
1. **Backend git utilities** - Status, branch, commit, remote operations
|
||||||
|
2. **Backend API endpoints** - All git operation endpoints
|
||||||
|
3. **Frontend toolbar** - Git action buttons
|
||||||
|
4. **Frontend branch management** - Create/delete/checkout
|
||||||
|
5. **Frontend status display** - Modified file indicators
|
||||||
|
6. **Frontend commit panel** - Commit UI
|
||||||
|
7. **Frontend merge dialog** - Merge UI
|
||||||
|
8. **Integration** - Wire everything together
|
||||||
|
9. **Tests** - Backend + frontend tests
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### URL State
|
||||||
|
```
|
||||||
|
/projects/:projectId?repo=:repoId&branch=:branch&path=:path
|
||||||
|
```
|
||||||
|
|
||||||
|
### React State
|
||||||
|
```typescript
|
||||||
|
interface GitState {
|
||||||
|
currentBranch: string;
|
||||||
|
branches: Branch[];
|
||||||
|
status: {
|
||||||
|
modified: string[];
|
||||||
|
added: string[];
|
||||||
|
deleted: string[];
|
||||||
|
untracked: string[];
|
||||||
|
};
|
||||||
|
isLoading: boolean;
|
||||||
|
lastOperation: string | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### User-Facing Errors
|
||||||
|
- **"Cannot checkout: uncommitted changes"** - Show commit panel
|
||||||
|
- **"Merge conflict"** - Show conflict resolution UI
|
||||||
|
- **"Push rejected: non-fast-forward"** - Suggest pull first
|
||||||
|
- **"Authentication failed"** - Show SSH key settings
|
||||||
|
- **"Nothing to commit"** - Working tree clean
|
||||||
|
|
||||||
|
### Conflict Resolution (Future)
|
||||||
|
For now, show error and abort. Later can add:
|
||||||
|
- Diff view of conflicts
|
||||||
|
- Manual resolution editor
|
||||||
|
- Accept ours/theirs buttons
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- **Status updates**: Poll every 5 seconds when viewing workspace
|
||||||
|
- **Fetch on load**: Auto-fetch when opening workspace (optional)
|
||||||
|
- **Lazy operations**: Don't fetch until user clicks fetch/pull
|
||||||
|
- **Progress indicators**: Show for long operations (clone, push, merge)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Git Control Features
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The repository workspace currently provides read-only access to git repositories. Users can browse files, view history, and see branches, but cannot perform git operations like creating branches, committing changes, pushing/pulling, or merging.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Add git control capabilities to the repository workspace, allowing users to:
|
||||||
|
1. Create and delete branches
|
||||||
|
2. Switch between branches (checkout)
|
||||||
|
3. Commit changes (for quick edits)
|
||||||
|
4. Push changes to remote
|
||||||
|
5. Pull/fetch updates from remote
|
||||||
|
6. Merge branches
|
||||||
|
7. View working directory status
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Complete workflow**: Users can make small changes without leaving the browser
|
||||||
|
- **Quick iterations**: Fix typos, update configs, make small adjustments
|
||||||
|
- **Branch management**: Create feature branches, merge when done
|
||||||
|
- **Remote sync**: Keep repositories up to date
|
||||||
|
- **No external tools needed**: Everything in the browser
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### What stays:
|
||||||
|
- Existing file browser and viewer
|
||||||
|
- Git history visualization
|
||||||
|
- Repository management (create/delete)
|
||||||
|
|
||||||
|
### What's new:
|
||||||
|
- Branch CRUD operations
|
||||||
|
- Working directory status (modified files)
|
||||||
|
- Commit operations
|
||||||
|
- Push/pull/fetch
|
||||||
|
- Merge capabilities
|
||||||
|
- Git action toolbar in workspace
|
||||||
|
|
||||||
|
### What changes:
|
||||||
|
- Workspace toolbar gets git action buttons
|
||||||
|
- File viewer gets "modified" indicators
|
||||||
|
- Branch selector gets management options
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
# Git Control Specification
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
1. **Branch Management**: Create, delete, list, and switch branches
|
||||||
|
2. **Working Directory**: View modified, added, deleted, and untracked files
|
||||||
|
3. **Commit Changes**: Stage and commit file changes with message
|
||||||
|
4. **Remote Sync**: Fetch, pull, and push to remote repositories
|
||||||
|
5. **Merge Branches**: Merge one branch into another
|
||||||
|
6. **Status Indicators**: Show file modification status in file tree
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
1. **Performance**: Git operations complete in < 3 seconds
|
||||||
|
2. **Feedback**: Show progress for long operations (push, pull, merge)
|
||||||
|
3. **Error Handling**: Clear error messages for all git failures
|
||||||
|
4. **Safety**: Confirm destructive operations (delete branch, force push)
|
||||||
|
|
||||||
|
## API Specification
|
||||||
|
|
||||||
|
### Branch Operations
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/branches
|
||||||
|
Create a new branch.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "feature/new-thing",
|
||||||
|
"base_branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 201:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "feature/new-thing",
|
||||||
|
"base_commit": "abc123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 400:** Branch already exists
|
||||||
|
|
||||||
|
#### DELETE /projects/{project_id}/repositories/{repo_id}/branches/{branch_name}
|
||||||
|
Delete a branch.
|
||||||
|
|
||||||
|
**Response 204:** Success
|
||||||
|
|
||||||
|
**Response 400:** Cannot delete current branch
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/checkout
|
||||||
|
Checkout a branch.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "feature/new-thing"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "feature/new-thing",
|
||||||
|
"commit": "abc123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 400:** Uncommitted changes
|
||||||
|
|
||||||
|
### Status Operations
|
||||||
|
|
||||||
|
#### GET /projects/{project_id}/repositories/{repo_id}/status
|
||||||
|
Get working directory status.
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "main",
|
||||||
|
"ahead": 2,
|
||||||
|
"behind": 1,
|
||||||
|
"modified": ["src/main.py"],
|
||||||
|
"added": ["new-file.txt"],
|
||||||
|
"deleted": [],
|
||||||
|
"untracked": ["temp.log"],
|
||||||
|
"renamed": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit Operations
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/commits
|
||||||
|
Commit staged changes.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Update greeting",
|
||||||
|
"author_name": "User",
|
||||||
|
"author_email": "user@example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 201:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hash": "def789",
|
||||||
|
"message": "Update greeting",
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 400:** Nothing to commit
|
||||||
|
|
||||||
|
### Remote Operations
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/fetch
|
||||||
|
Fetch from remote.
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"fetched_branches": ["origin/main", "origin/develop"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/pull
|
||||||
|
Pull updates from remote.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"commits": 3,
|
||||||
|
"files_changed": ["src/main.py", "README.md"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 409:** Merge conflict
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/push
|
||||||
|
Push to remote.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"pushed_commits": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 400:** Non-fast-forward
|
||||||
|
|
||||||
|
### Merge Operations
|
||||||
|
|
||||||
|
#### POST /projects/{project_id}/repositories/{repo_id}/merge
|
||||||
|
Merge branches.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source_branch": "feature/new-thing",
|
||||||
|
"target_branch": "main",
|
||||||
|
"commit_message": "Merge feature into main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"commit_hash": "abc789",
|
||||||
|
"files_changed": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 409:** Merge conflict
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
### GitStatus
|
||||||
|
```typescript
|
||||||
|
interface GitStatus {
|
||||||
|
branch: string;
|
||||||
|
ahead: number;
|
||||||
|
behind: number;
|
||||||
|
modified: string[];
|
||||||
|
added: string[];
|
||||||
|
deleted: string[];
|
||||||
|
untracked: string[];
|
||||||
|
renamed: Array<{from: string, to: string}>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CommitInfo
|
||||||
|
```typescript
|
||||||
|
interface CommitInfo {
|
||||||
|
hash: string;
|
||||||
|
message: string;
|
||||||
|
branch: string;
|
||||||
|
author_name: string;
|
||||||
|
author_email: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend Specification
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
**GitToolbar:**
|
||||||
|
- Fetch button
|
||||||
|
- Pull button (with behind count badge)
|
||||||
|
- Push button (with ahead count badge)
|
||||||
|
- Branch selector (with create/delete)
|
||||||
|
- Commit button (enabled when changes exist)
|
||||||
|
- Merge button
|
||||||
|
|
||||||
|
**BranchSelector:**
|
||||||
|
- Dropdown with all branches
|
||||||
|
- Current branch highlighted
|
||||||
|
- "Create new branch" option
|
||||||
|
- Delete option (with confirmation)
|
||||||
|
|
||||||
|
**CommitPanel:**
|
||||||
|
- Shows when files are modified
|
||||||
|
- Lists changed files with checkboxes
|
||||||
|
- Commit message input
|
||||||
|
- Commit button
|
||||||
|
|
||||||
|
**StatusIndicator:**
|
||||||
|
- Small badge on file tree items
|
||||||
|
- Shows modification type
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GitControlState {
|
||||||
|
status: GitStatus | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
operations: Array<{
|
||||||
|
type: string;
|
||||||
|
status: 'pending' | 'success' | 'error';
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Error Code | Description | User Action |
|
||||||
|
|------------|-------------|-------------|
|
||||||
|
| DIRTY_WORKING_TREE | Uncommitted changes | Commit or stash changes |
|
||||||
|
| MERGE_CONFLICT | Merge failed with conflicts | Resolve conflicts manually |
|
||||||
|
| NON_FAST_FORWARD | Push rejected | Pull first |
|
||||||
|
| AUTH_FAILED | Remote auth failed | Check SSH keys |
|
||||||
|
| BRANCH_EXISTS | Branch already exists | Choose different name |
|
||||||
|
| NOTHING_TO_COMMIT | Working tree clean | N/A |
|
||||||
|
| CANNOT_DELETE_CURRENT | Can't delete checked out branch | Switch branches first |
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Backend Tests
|
||||||
|
- Test branch creation/deletion
|
||||||
|
- Test checkout with/without changes
|
||||||
|
- Test commit operations
|
||||||
|
- Test fetch/pull/push
|
||||||
|
- Test merge (fast-forward and conflict)
|
||||||
|
- Test error cases
|
||||||
|
|
||||||
|
### Frontend Tests
|
||||||
|
- Test toolbar buttons
|
||||||
|
- Test branch selector
|
||||||
|
- Test commit panel
|
||||||
|
- Test status indicators
|
||||||
|
- Test error handling
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Full workflow: create branch → edit file → commit → push → merge
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Git Control - Tasks
|
||||||
|
|
||||||
|
## Phase 1: Backend Git Utilities
|
||||||
|
|
||||||
|
- [x] **Task 1.1**: Extend git utilities
|
||||||
|
- Add to `src/utils/git_control.py`:
|
||||||
|
- `get_status(repo_path)` - working directory status
|
||||||
|
- `create_branch(repo_path, name, base)` - create branch
|
||||||
|
- `delete_branch(repo_path, name)` - delete branch
|
||||||
|
- `checkout_branch(repo_path, name)` - switch branch
|
||||||
|
- `commit_changes(repo_path, message, author)` - commit
|
||||||
|
- Add tests
|
||||||
|
|
||||||
|
- [x] **Task 1.2**: Add remote operations
|
||||||
|
- Add to `src/utils/git_control.py`:
|
||||||
|
- `fetch(repo_path)` - fetch from remote
|
||||||
|
- `pull(repo_path, branch)` - pull updates
|
||||||
|
- `push(repo_path, branch)` - push changes
|
||||||
|
- `merge(repo_path, source, target, message)` - merge
|
||||||
|
- Handle errors (conflicts, auth, etc.)
|
||||||
|
- Add tests
|
||||||
|
|
||||||
|
## Phase 2: Backend API Endpoints
|
||||||
|
|
||||||
|
- [x] **Task 2.1**: Branch management endpoints
|
||||||
|
- POST `/projects/{id}/repositories/{id}/branches` - create
|
||||||
|
- DELETE `/projects/{id}/repositories/{id}/branches/{name}` - delete
|
||||||
|
- POST `/projects/{id}/repositories/{id}/checkout` - checkout
|
||||||
|
- Add to `src/api/git_repositories.py`
|
||||||
|
|
||||||
|
- [x] **Task 2.2**: Status endpoint
|
||||||
|
- GET `/projects/{id}/repositories/{id}/status`
|
||||||
|
- Returns working directory status
|
||||||
|
|
||||||
|
- [x] **Task 2.3**: Commit endpoint
|
||||||
|
- POST `/projects/{id}/repositories/{id}/commits`
|
||||||
|
- Commits all staged changes
|
||||||
|
|
||||||
|
- [x] **Task 2.4**: Remote operation endpoints
|
||||||
|
- POST `/projects/{id}/repositories/{id}/fetch`
|
||||||
|
- POST `/projects/{id}/repositories/{id}/pull`
|
||||||
|
- POST `/projects/{id}/repositories/{id}/push`
|
||||||
|
|
||||||
|
- [x] **Task 2.5**: Merge endpoint
|
||||||
|
- POST `/projects/{id}/repositories/{id}/merge`
|
||||||
|
- Handle conflict responses
|
||||||
|
|
||||||
|
## Phase 3: Frontend Git Toolbar
|
||||||
|
|
||||||
|
- [ ] **Task 3.1**: Create GitToolbar component
|
||||||
|
- Fetch, Pull, Push buttons
|
||||||
|
- Branch selector with count badges
|
||||||
|
- Commit button
|
||||||
|
- Merge button
|
||||||
|
- Add to workspace layout
|
||||||
|
|
||||||
|
- [ ] **Task 3.2**: Add status polling
|
||||||
|
- Poll status every 5 seconds
|
||||||
|
- Update toolbar badges (ahead/behind)
|
||||||
|
- Show commit button when changes exist
|
||||||
|
|
||||||
|
## Phase 4: Branch Management
|
||||||
|
|
||||||
|
- [ ] **Task 4.1**: Enhance BranchSelector
|
||||||
|
- Add "Create new branch" option
|
||||||
|
- Add delete option with confirmation
|
||||||
|
- Show current branch
|
||||||
|
- Call branch API endpoints
|
||||||
|
|
||||||
|
- [ ] **Task 4.2**: Create NewBranchDialog
|
||||||
|
- Branch name input
|
||||||
|
- Base branch selector
|
||||||
|
- Create/Cancel buttons
|
||||||
|
|
||||||
|
## Phase 5: Commit Workflow
|
||||||
|
|
||||||
|
- [ ] **Task 5.1**: Create CommitPanel component
|
||||||
|
- Shows when files are modified
|
||||||
|
- Lists changed files
|
||||||
|
- Commit message input
|
||||||
|
- Commit button
|
||||||
|
- Success/error feedback
|
||||||
|
|
||||||
|
- [ ] **Task 5.2**: Add status indicators to FileTree
|
||||||
|
- Modified icon (✏️)
|
||||||
|
- New file icon (✨)
|
||||||
|
- Deleted icon (🗑️)
|
||||||
|
- Untracked icon (❓)
|
||||||
|
|
||||||
|
## Phase 6: Remote Operations
|
||||||
|
|
||||||
|
- [ ] **Task 6.1**: Implement fetch/pull/push
|
||||||
|
- Wire toolbar buttons to API
|
||||||
|
- Show progress indicators
|
||||||
|
- Handle errors (auth, conflicts, etc.)
|
||||||
|
- Update status after operations
|
||||||
|
|
||||||
|
- [ ] **Task 6.2**: Create MergeDialog
|
||||||
|
- Source branch selector
|
||||||
|
- Target branch display
|
||||||
|
- Commit message input
|
||||||
|
- Merge/Cancel buttons
|
||||||
|
- Handle conflicts
|
||||||
|
|
||||||
|
## Phase 7: Integration & Polish
|
||||||
|
|
||||||
|
- [ ] **Task 7.1**: Wire everything together
|
||||||
|
- Connect toolbar to all APIs
|
||||||
|
- Update workspace state after operations
|
||||||
|
- Refresh file tree on branch switch
|
||||||
|
|
||||||
|
- [ ] **Task 7.2**: Add error handling
|
||||||
|
- Show toast notifications for operations
|
||||||
|
- Handle all error cases gracefully
|
||||||
|
- Provide recovery options
|
||||||
|
|
||||||
|
- [ ] **Task 7.3**: Add CSS styles
|
||||||
|
- Toolbar layout
|
||||||
|
- Status indicators
|
||||||
|
- Commit panel
|
||||||
|
- Dialogs
|
||||||
|
|
||||||
|
- [ ] **Task 7.4**: Run quality gates
|
||||||
|
- Backend: ruff, mypy, pytest
|
||||||
|
- Frontend: typecheck, lint, build
|
||||||
|
|
||||||
|
## Phase 8: Testing
|
||||||
|
|
||||||
|
- [ ] **Task 8.1**: Backend tests
|
||||||
|
- Test all git operations
|
||||||
|
- Test error cases
|
||||||
|
- Test auth failures
|
||||||
|
|
||||||
|
- [ ] **Task 8.2**: Frontend tests
|
||||||
|
- Test toolbar interactions
|
||||||
|
- Test branch management
|
||||||
|
- Test commit workflow
|
||||||
|
|
||||||
|
- [ ] **Task 8.3**: Manual testing
|
||||||
|
- Create branch → edit → commit → push → merge workflow
|
||||||
|
- Test error scenarios
|
||||||
|
- Test with multiple repos
|
||||||
Reference in New Issue
Block a user