inital commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# build outputs
|
||||
/gitocean
|
||||
/bin/
|
||||
|
||||
# runtime storage
|
||||
/storage/
|
||||
|
||||
# local env
|
||||
.env
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
# Gitocean Phase 2 Plan: Usability
|
||||
|
||||
Phase 2 focuses on making Gitocean feel usable as a day-to-day personal Git hosting environment. This phase is not about GitLab enterprise parity, CI/CD, or a web UI. It is about setup, common workflows, repository inspection, pull request ergonomics, and maintenance.
|
||||
|
||||
## Goals
|
||||
|
||||
- Make first-time setup simple.
|
||||
- Make CLI output readable by humans by default.
|
||||
- Support the common workflow of publishing an existing local repository.
|
||||
- Provide enough CLI inspection tools to compensate for no web UI.
|
||||
- Improve pull request workflows with diff and checkout commands.
|
||||
- Add simple repository metadata and settings.
|
||||
- Add lightweight collaboration support.
|
||||
- Add comments, token management, and basic admin/backup tooling.
|
||||
|
||||
---
|
||||
|
||||
## 1. Config + `gitocean init`
|
||||
|
||||
### Add server config file
|
||||
|
||||
Create a persistent server config file, likely:
|
||||
|
||||
```text
|
||||
storage/config.json
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"addr": ":8080",
|
||||
"public_url": "http://localhost:8080",
|
||||
"storage": "storage",
|
||||
"mysql_dsn": "root:pass@tcp(127.0.0.1:3306)/gitocean?parseTime=true"
|
||||
}
|
||||
```
|
||||
|
||||
### Add command
|
||||
|
||||
```bash
|
||||
gitocean init
|
||||
```
|
||||
|
||||
It should:
|
||||
|
||||
1. Prompt for server listen address.
|
||||
2. Prompt for public URL.
|
||||
3. Prompt for storage directory.
|
||||
4. Prompt for MySQL DSN or individual MySQL fields.
|
||||
5. Create the database if missing.
|
||||
6. Run migrations.
|
||||
7. Create storage directories.
|
||||
8. Optionally create the first user account.
|
||||
9. Write config file.
|
||||
10. Print next-step instructions.
|
||||
|
||||
### Update server command
|
||||
|
||||
Allow:
|
||||
|
||||
```bash
|
||||
gitocean server
|
||||
```
|
||||
|
||||
without requiring `--dsn` if config exists.
|
||||
|
||||
Still allow overrides:
|
||||
|
||||
```bash
|
||||
gitocean server --config storage/config.json
|
||||
```
|
||||
|
||||
```bash
|
||||
gitocean server --dsn '...' --addr :8080 --storage storage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Friendly CLI output
|
||||
|
||||
Most commands currently print JSON. Phase 2 should make human-friendly output the default.
|
||||
|
||||
### Add global or per-command JSON output
|
||||
|
||||
Support:
|
||||
|
||||
```bash
|
||||
gitocean repo search test --json
|
||||
```
|
||||
|
||||
or eventually a global flag:
|
||||
|
||||
```bash
|
||||
gitocean --json repo search test
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Repo creation should print:
|
||||
|
||||
```text
|
||||
Created public repository owen/test
|
||||
|
||||
Clone:
|
||||
git clone http://localhost:8080/owen/test.git
|
||||
|
||||
Add existing repo:
|
||||
git remote add origin http://localhost:8080/owen/test.git
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Search should print a table:
|
||||
|
||||
```text
|
||||
REPOSITORY VISIBILITY DESCRIPTION UPDATED
|
||||
owen/test public Test repository 2m ago
|
||||
alice/demo public Demo project 3h ago
|
||||
```
|
||||
|
||||
PR list should print:
|
||||
|
||||
```text
|
||||
#1 open Add config support bob:feature-config -> main
|
||||
#2 merged Fix clone auth alice:fix-auth -> main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `repo publish`
|
||||
|
||||
Add a command for publishing an existing local Git repository.
|
||||
|
||||
```bash
|
||||
gitocean repo publish NAME --public
|
||||
gitocean repo publish NAME --private
|
||||
```
|
||||
|
||||
Optional flags:
|
||||
|
||||
```bash
|
||||
gitocean repo publish NAME --public --remote origin
|
||||
gitocean repo publish NAME --public --branch main
|
||||
gitocean repo publish NAME --public --description "My project"
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
From inside a local Git repository:
|
||||
|
||||
1. Verify current directory is a Git repository.
|
||||
2. Create remote repository through API.
|
||||
3. Add remote if missing.
|
||||
4. Set or rename current branch to `main` by default.
|
||||
5. Push current branch.
|
||||
6. Set upstream.
|
||||
7. Print success and clone URL.
|
||||
|
||||
Equivalent workflow:
|
||||
|
||||
```bash
|
||||
gitocean repo create test --public
|
||||
git remote add origin http://localhost:8080/owen/test.git
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `repo view`, branches, tags
|
||||
|
||||
Since there is no web UI, add basic repository inspection commands.
|
||||
|
||||
### Repo view
|
||||
|
||||
```bash
|
||||
gitocean repo view OWNER/REPO
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
owen/test
|
||||
Visibility: public
|
||||
Default branch: main
|
||||
Description: My test project
|
||||
|
||||
Clone:
|
||||
git clone http://localhost:8080/owen/test.git
|
||||
|
||||
Open PRs: 2
|
||||
Branches: 4
|
||||
Tags: 1
|
||||
```
|
||||
|
||||
### Branch listing
|
||||
|
||||
```bash
|
||||
gitocean repo branches OWNER/REPO
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
BRANCH COMMIT UPDATED MESSAGE
|
||||
main 8f31abc 2 hours ago Initial commit
|
||||
feature-x 1ab22df 1 day ago Add feature
|
||||
```
|
||||
|
||||
### Tag listing
|
||||
|
||||
```bash
|
||||
gitocean repo tags OWNER/REPO
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
TAG COMMIT DATE MESSAGE
|
||||
v0.1.0 8f31abc 2026-06-08 First release
|
||||
```
|
||||
|
||||
### API additions
|
||||
|
||||
```http
|
||||
GET /api/repos/{owner}/{repo}/branches
|
||||
GET /api/repos/{owner}/{repo}/tags
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. PR diff + PR checkout
|
||||
|
||||
Pull requests are difficult to use without seeing and checking out changes.
|
||||
|
||||
### PR diff
|
||||
|
||||
```bash
|
||||
gitocean pr diff OWNER/REPO NUMBER
|
||||
```
|
||||
|
||||
Server should generate a diff between:
|
||||
|
||||
```text
|
||||
source_repo:source_branch
|
||||
```
|
||||
|
||||
and:
|
||||
|
||||
```text
|
||||
target_repo:target_branch
|
||||
```
|
||||
|
||||
API:
|
||||
|
||||
```http
|
||||
GET /api/repos/{owner}/{repo}/pulls/{number}/diff
|
||||
```
|
||||
|
||||
Return `text/plain` patch/diff output.
|
||||
|
||||
### PR checkout
|
||||
|
||||
```bash
|
||||
gitocean pr checkout OWNER/REPO NUMBER
|
||||
```
|
||||
|
||||
From inside a local Git repo, it should:
|
||||
|
||||
1. Fetch PR source branch from source repository URL.
|
||||
2. Create or update a local branch such as `pr-1`.
|
||||
3. Check out that branch.
|
||||
|
||||
Equivalent:
|
||||
|
||||
```bash
|
||||
git fetch http://localhost:8080/bob/test.git fix:pr-1
|
||||
git checkout pr-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Repo description/default branch/visibility update
|
||||
|
||||
Add basic repository settings.
|
||||
|
||||
### Schema additions
|
||||
|
||||
Add columns to `repositories`:
|
||||
|
||||
- `description TEXT NOT NULL DEFAULT ''`
|
||||
- `default_branch VARCHAR(200) NOT NULL DEFAULT 'main'`
|
||||
- `archived BOOLEAN NOT NULL DEFAULT false`
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
gitocean repo set OWNER/REPO --description "My project"
|
||||
gitocean repo set OWNER/REPO --visibility private
|
||||
gitocean repo set OWNER/REPO --visibility public
|
||||
gitocean repo set OWNER/REPO --default-branch main
|
||||
gitocean repo archive OWNER/REPO
|
||||
gitocean repo unarchive OWNER/REPO
|
||||
```
|
||||
|
||||
### API additions
|
||||
|
||||
```http
|
||||
PATCH /api/repos/{owner}/{repo}
|
||||
```
|
||||
|
||||
Owner-only.
|
||||
|
||||
---
|
||||
|
||||
## 7. Collaborators
|
||||
|
||||
Owner-only permissions are too limiting for personal collaboration. Add simple collaborators.
|
||||
|
||||
### Roles
|
||||
|
||||
- `read`: can clone/fetch private repositories.
|
||||
- `write`: can clone/fetch/push.
|
||||
|
||||
Owner remains the only user who can:
|
||||
|
||||
- delete repository
|
||||
- update settings
|
||||
- manage collaborators
|
||||
- merge/close PRs, unless explicitly changed later
|
||||
|
||||
### Schema
|
||||
|
||||
Add table:
|
||||
|
||||
```text
|
||||
repository_collaborators
|
||||
id
|
||||
repository_id
|
||||
user_id
|
||||
role: read/write
|
||||
created_at
|
||||
```
|
||||
|
||||
Unique key:
|
||||
|
||||
```text
|
||||
(repository_id, user_id)
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
gitocean repo collaborators OWNER/REPO
|
||||
gitocean repo collaborator add OWNER/REPO USER --role read
|
||||
gitocean repo collaborator add OWNER/REPO USER --role write
|
||||
gitocean repo collaborator remove OWNER/REPO USER
|
||||
```
|
||||
|
||||
### API additions
|
||||
|
||||
```http
|
||||
GET /api/repos/{owner}/{repo}/collaborators
|
||||
POST /api/repos/{owner}/{repo}/collaborators
|
||||
DELETE /api/repos/{owner}/{repo}/collaborators/{username}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. PR comments
|
||||
|
||||
Add simple general comments on pull requests. Line comments can wait.
|
||||
|
||||
### Schema
|
||||
|
||||
Add table:
|
||||
|
||||
```text
|
||||
pull_request_comments
|
||||
id
|
||||
pull_request_id
|
||||
author_user_id
|
||||
body
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
gitocean pr comment OWNER/REPO NUMBER "Looks good"
|
||||
gitocean pr comments OWNER/REPO NUMBER
|
||||
```
|
||||
|
||||
Optional editor support later:
|
||||
|
||||
```bash
|
||||
gitocean pr comment OWNER/REPO NUMBER --editor
|
||||
```
|
||||
|
||||
### API additions
|
||||
|
||||
```http
|
||||
GET /api/repos/{owner}/{repo}/pulls/{number}/comments
|
||||
POST /api/repos/{owner}/{repo}/pulls/{number}/comments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Token management
|
||||
|
||||
Tokens expire after 7 days, but users need visibility and revocation.
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
gitocean token list
|
||||
gitocean token revoke TOKEN_ID
|
||||
gitocean token prune
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
- `token list`: show active tokens for current user.
|
||||
- `token revoke`: revoke one token.
|
||||
- `token prune`: remove expired/revoked tokens from database. Owner/admin-only if global, current-user only otherwise.
|
||||
|
||||
### API additions
|
||||
|
||||
```http
|
||||
GET /api/tokens
|
||||
DELETE /api/tokens/{id}
|
||||
POST /api/tokens/prune
|
||||
```
|
||||
|
||||
### CLI expired-session UX
|
||||
|
||||
When API returns unauthorized, show:
|
||||
|
||||
```text
|
||||
Your session may have expired. Run:
|
||||
gitocean login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Backup/admin commands
|
||||
|
||||
Personal servers need reliable maintenance tooling.
|
||||
|
||||
### Admin commands
|
||||
|
||||
```bash
|
||||
gitocean admin users list
|
||||
gitocean admin users delete USER
|
||||
gitocean admin repos list
|
||||
gitocean admin storage check
|
||||
gitocean admin tokens prune
|
||||
```
|
||||
|
||||
For MVP, admin can be:
|
||||
|
||||
- the first registered user, or
|
||||
- a boolean `is_admin` column on `users`.
|
||||
|
||||
### Backup commands
|
||||
|
||||
```bash
|
||||
gitocean backup create backup.tar.gz
|
||||
gitocean backup restore backup.tar.gz
|
||||
```
|
||||
|
||||
Backup should include:
|
||||
|
||||
- server config
|
||||
- `storage/repos/`
|
||||
- MySQL dump of metadata
|
||||
|
||||
Possible implementation:
|
||||
|
||||
```bash
|
||||
mysqldump ... > metadata.sql
|
||||
tar -czf backup.tar.gz storage/config.json storage/repos metadata.sql
|
||||
```
|
||||
|
||||
Restore should:
|
||||
|
||||
1. Stop or warn about running server.
|
||||
2. Restore config.
|
||||
3. Restore repositories.
|
||||
4. Restore database from SQL dump.
|
||||
5. Run migrations after restore.
|
||||
|
||||
---
|
||||
|
||||
## Recommended implementation order
|
||||
|
||||
1. Config file support and `gitocean init`.
|
||||
2. Friendly CLI output with `--json` escape hatch.
|
||||
3. `repo publish`.
|
||||
4. Repository metadata fields and `repo view`.
|
||||
5. Branch/tag listing.
|
||||
6. PR diff.
|
||||
7. PR checkout.
|
||||
8. Collaborators.
|
||||
9. PR comments.
|
||||
10. Token management.
|
||||
11. Backup/admin commands.
|
||||
|
||||
## Success criteria
|
||||
|
||||
Phase 2 is successful when a user can comfortably:
|
||||
|
||||
1. Initialize the server without memorizing flags.
|
||||
2. Register/login from CLI.
|
||||
3. Publish an existing local Git repository with one command.
|
||||
4. Clone, push, search, and inspect repositories.
|
||||
5. Open, inspect, checkout, and merge PRs.
|
||||
6. Share private repositories with specific users.
|
||||
7. Revoke tokens and back up the server.
|
||||
@@ -0,0 +1,194 @@
|
||||
# Gitocean Implementation Plan
|
||||
|
||||
## Product decisions
|
||||
|
||||
- Build a Git repository hosting platform in Go.
|
||||
- Store bare Git repositories under `storage/repos/{owner}/{repo}.git`.
|
||||
- Use MySQL for metadata.
|
||||
- Support HTTP Smart Git only. SSH is out of scope for MVP.
|
||||
- Support CLI + API only. No web UI.
|
||||
- Registration is open, but registration happens through the CLI/API only.
|
||||
- Users register with email, username, and password.
|
||||
- Users can log in with either email + password or username + password.
|
||||
- Login returns a temporary auth token that expires after 7 days.
|
||||
- The CLI stores this token locally for API calls and can approve it into Git's credential helper for Git HTTP operations.
|
||||
- Repository names allow lowercase letters, numbers, `.`, `_`, and `-`.
|
||||
- Usernames allow lowercase letters, numbers, `_`, and `-`.
|
||||
- New repositories use `main` as the default branch.
|
||||
- Pull request numbers are per target repository.
|
||||
|
||||
## Permissions
|
||||
|
||||
### Repository visibility
|
||||
|
||||
- `public`: anyone can clone/fetch. Only the owner can push/delete/manage.
|
||||
- `private`: only the owner can clone/fetch/push/delete/manage.
|
||||
|
||||
### Writes
|
||||
|
||||
- MVP is owner-only.
|
||||
- There are no collaborators, organizations, teams, or fine-grained permissions yet.
|
||||
- Open collaboration is supported through pull requests against public repositories.
|
||||
|
||||
## Storage layout
|
||||
|
||||
```text
|
||||
storage/
|
||||
repos/
|
||||
alice/
|
||||
demo.git/
|
||||
bob/
|
||||
project.git/
|
||||
```
|
||||
|
||||
Each repository directory is a bare Git repository.
|
||||
|
||||
## MySQL schema
|
||||
|
||||
Core tables:
|
||||
|
||||
- `users`
|
||||
- id
|
||||
- email
|
||||
- username
|
||||
- password_hash
|
||||
- created_at
|
||||
- `auth_tokens`
|
||||
- id
|
||||
- user_id
|
||||
- token_hash
|
||||
- expires_at
|
||||
- revoked_at
|
||||
- created_at
|
||||
- `repositories`
|
||||
- id
|
||||
- owner_user_id
|
||||
- name
|
||||
- visibility: `public` or `private`
|
||||
- forked_from_repository_id nullable
|
||||
- created_at
|
||||
- updated_at
|
||||
- `pull_requests`
|
||||
- id
|
||||
- target_repository_id
|
||||
- number
|
||||
- author_user_id
|
||||
- source_repository_id
|
||||
- source_branch
|
||||
- target_branch
|
||||
- title
|
||||
- description
|
||||
- status: `open`, `closed`, or `merged`
|
||||
- closed_at nullable
|
||||
- merged_at nullable
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
## API
|
||||
|
||||
### Auth
|
||||
|
||||
```http
|
||||
POST /api/register
|
||||
POST /api/login
|
||||
POST /api/logout
|
||||
GET /api/me
|
||||
```
|
||||
|
||||
### Repositories
|
||||
|
||||
```http
|
||||
POST /api/repos
|
||||
GET /api/repos/search?q=demo&scope=all
|
||||
GET /api/repos/search?q=demo&scope=mine
|
||||
GET /api/repos/{owner}/{repo}
|
||||
DELETE /api/repos/{owner}/{repo}?force=false
|
||||
POST /api/repos/{owner}/{repo}/fork
|
||||
```
|
||||
|
||||
### Pull requests
|
||||
|
||||
```http
|
||||
POST /api/repos/{owner}/{repo}/pulls
|
||||
GET /api/repos/{owner}/{repo}/pulls
|
||||
GET /api/repos/{owner}/{repo}/pulls/{number}
|
||||
POST /api/repos/{owner}/{repo}/pulls/{number}/close
|
||||
POST /api/repos/{owner}/{repo}/pulls/{number}/merge
|
||||
```
|
||||
|
||||
### Git HTTP
|
||||
|
||||
```http
|
||||
/{owner}/{repo}.git/info/refs
|
||||
/{owner}/{repo}.git/git-upload-pack
|
||||
/{owner}/{repo}.git/git-receive-pack
|
||||
```
|
||||
|
||||
## CLI commands
|
||||
|
||||
```bash
|
||||
gitocean server --addr :8080 --dsn 'user:pass@tcp(127.0.0.1:3306)/gitocean?parseTime=true' --storage storage
|
||||
|
||||
gitocean register
|
||||
gitocean login
|
||||
gitocean logout
|
||||
gitocean whoami
|
||||
|
||||
gitocean repo create NAME --public
|
||||
gitocean repo create NAME --private
|
||||
gitocean repo delete OWNER/NAME [--force]
|
||||
gitocean repo search QUERY --all
|
||||
gitocean repo search QUERY --mine
|
||||
gitocean repo fork OWNER/NAME [--name NEW_NAME]
|
||||
|
||||
gitocean clone OWNER/NAME
|
||||
|
||||
gitocean pr create --from OWNER/REPO:BRANCH --to OWNER/REPO:BRANCH --title TITLE --description DESC
|
||||
gitocean pr create --repo OWNER/REPO --from BRANCH --to BRANCH --title TITLE --description DESC
|
||||
gitocean pr list OWNER/REPO
|
||||
gitocean pr view OWNER/REPO NUMBER
|
||||
gitocean pr close OWNER/REPO NUMBER
|
||||
gitocean pr merge OWNER/REPO NUMBER
|
||||
```
|
||||
|
||||
## Pull request rules
|
||||
|
||||
### Same-repository PRs
|
||||
|
||||
- Source and target repository are the same.
|
||||
- Only the repository owner can create same-repo PRs.
|
||||
- Source and target branches must both exist.
|
||||
|
||||
### Cross-repository PRs
|
||||
|
||||
- Source repository branch points to target repository branch.
|
||||
- Source repository must be owned by the PR author.
|
||||
- Target repository must be public, unless the author is also the target owner.
|
||||
- Source and target branches must both exist.
|
||||
- Only the target repository owner can close or merge the PR.
|
||||
|
||||
## Merge behavior
|
||||
|
||||
MVP merge mode is a normal merge commit:
|
||||
|
||||
1. Clone target repo into a temporary worktree.
|
||||
2. Checkout target branch.
|
||||
3. Add/fetch source repo branch.
|
||||
4. Run `git merge --no-ff FETCH_HEAD`.
|
||||
5. Push the result back to the target branch.
|
||||
6. Mark PR as merged.
|
||||
|
||||
If conflicts occur, return an error and keep the PR open.
|
||||
|
||||
## Implementation milestones
|
||||
|
||||
1. Project scaffold and build setup.
|
||||
2. MySQL migrations.
|
||||
3. User registration, login, logout, and token auth.
|
||||
4. Repository create, delete, detail, search.
|
||||
5. HTTP Smart Git integration using `git http-backend`.
|
||||
6. CLI auth and repository commands.
|
||||
7. Fork support.
|
||||
8. Pull request create/list/view/close.
|
||||
9. Pull request merge.
|
||||
10. Tests, hardening, and UX polish.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
module gitocean
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.9.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
)
|
||||
|
||||
require filippo.io/edwards25519 v1.1.0 // indirect
|
||||
@@ -0,0 +1,6 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
Submodule
+1
Submodule test added at b00f141889
Reference in New Issue
Block a user