16 KiB
Phase 3 Plan: Split the God File into an Organized Codebase
Goal
Refactor the current monolithic cmd/gitocean/main.go into a maintainable, testable, multi-package Go codebase without changing user-facing behavior.
Phase 3 is primarily a structural refactor. The CLI commands, API routes, Git HTTP behavior, MySQL schema, storage layout, and auth rules should continue to work exactly as they do after Phase 2.
Phase 3 should also finish documenting and preserving the CLI UX direction from Phase 2: human-readable CLI output by default, with JSON available only as an explicit opt-in where useful. This does not mean removing JSON from the HTTP API; API requests and responses should remain JSON.
Non-goals
- Do not add a Web UI.
- Do not add SSH Git transport.
- Do not replace MySQL.
- Do not redesign API routes unless a compatibility shim is kept.
- Do not introduce large frameworks.
- Do not implement new product features until the refactor is stable.
- Do not remove JSON from the HTTP API. Only CLI output should become human-readable by default.
Current problem
cmd/gitocean/main.go currently contains everything:
- Type definitions
- CLI command routing and command implementations
- HTTP server bootstrapping
- API route dispatch
- Auth/token logic
- User/repository/PR/collaborator handlers
- Git HTTP backend integration
- MySQL migrations and DB helpers
- Config loading/saving
- Git helper functions
- Backup/restore logic
- Output formatting
- Generic HTTP JSON helpers
This makes the code hard to test, hard to extend, and risky to modify.
Refactor principles
-
No behavior changes first
- Move code into packages with minimal edits.
- Keep route paths, JSON payloads, CLI command names, flags, and output stable unless explicitly noted.
-
Small, reviewable steps
- Move one domain at a time.
- Run
gofmt,go test ./..., andgo vet ./...after each major move.
-
Human-readable CLI by default
- During CLI extraction, audit commands that still print raw JSON.
- Convert default CLI output to concise human-readable text/tables.
- Keep
--jsonas an explicit escape hatch for scripting where appropriate. - Do not change API JSON payloads.
-
Thin
mainpackagecmd/gitocean/main.goshould only call into an application package.
-
Reusable packages
- Shared utilities should live in
internal/packages, not incmd/.
- Shared utilities should live in
-
Domain boundaries over technical dumping grounds
- Prefer packages like
repos,pulls,auth, andgitserverover one hugeutilspackage.
- Prefer packages like
-
Testable dependencies
- Handlers and services should accept dependencies through structs.
- Avoid package-level global state except constants and regex validators.
Target directory layout
cmd/
gitocean/
main.go # Tiny entrypoint only
internal/
app/
app.go # Top-level CLI dispatch / application orchestration
usage.go # Help text
config/
client.go # CLI config: ~/.config/gitocean/config.json
server.go # Server config: storage/config.json
env.go # Environment defaults
db/
mysql.go # MySQL open/create database logic
migrate.go # Migrations
model/
user.go
repository.go
pull_request.go
collaborator.go
token.go
ref.go
httpapi/
server.go # Server struct and ServeHTTP
router.go # API route dispatch
json.go # decodeJSON/writeJSON/writeError
auth/
handlers.go # register/login/logout/me handlers
service.go # token generation, hashing, bearer/basic auth
password.go # password hashing helpers if needed
tokens/
handlers.go # token list/revoke/prune handlers
admin/
handlers.go # admin users/repos/storage/token routes
repos/
handlers.go # repo create/get/update/delete/search/fork
service.go # repo access checks and repo loading
collaborators.go # collaborator handlers and role logic
validate.go # repo/user name validation helpers if not shared
pulls/
handlers.go # PR create/list/view/close/merge/diff/comments
service.go # PR loading, scanning, merge/diff helpers
gitserver/
http.go # Smart HTTP route parsing and permissions
backend.go # git http-backend CGI bridge
gitutil/
git.go # runGit, gitInitBare, gitBranchExists, gitRefs
cli/
root.go # CLI command dispatch
auth.go # register/login/logout/whoami
repo.go # repo command dispatch
repo_create.go
repo_publish.go
repo_view.go
repo_collaborators.go
pr.go
token.go
admin.go
backup.go
output.go # CLI formatting helpers
parse.go # splitOwnerRepo, splitRepoBranch, flag parsing
prompt.go
api_client.go # CLI HTTP client helper
backup/
backup.go # createBackup/restoreBackup/mysqlCLIArgs/copyDir
validate/
names.go # username/repo/branch validation and reserved names
The exact final package names can change during implementation, but the direction should stay the same: small domain packages with clear responsibilities.
Dependency direction
Recommended dependency flow:
cmd/gitocean
-> internal/app
-> internal/cli
-> internal/httpapi
-> internal/config
-> internal/db
httpapi
-> auth, tokens, admin, repos, pulls, gitserver
-> model
repos/pulls/auth/etc.
-> model
-> gitutil where needed
-> validate where needed
cli
-> config
-> model
-> backup where needed
Avoid circular dependencies by keeping shared types in internal/model and shared helpers in targeted utility packages.
Proposed milestones
Milestone 1: Prepare shared model and validation packages
Move pure data/types and validators first.
Create:
internal/modelinternal/validate
Move:
UserRepositoryPullRequestServerConfigRefInfoCollaboratorPRCommentTokenInfo- username/repo/branch regex validation
isReservedName
Acceptance:
cmd/gitocean/main.gostill builds.- All references use
model.User,model.Repository, etc. go test ./...passes.
Milestone 2: Extract config and generic helpers
Create:
internal/configinternal/httpapi/json.gointernal/gitutil
Move:
- CLI config path/load/save
- server config path/load/save
- env server URL helper
- JSON request/response helpers where appropriate
gitInitBaregitBranchExistsgitRefsrunGit
Acceptance:
- No behavior changes.
- CLI login still saves config.
- Server still reads config and env overrides.
- Git helper functions are reusable outside
main.
Milestone 3: Extract DB connection and migrations
Create:
internal/db
Move:
openMySQLAndCreateDatabaseIfMissingquoteMySQLIdentifiermigrateisDuplicateColumnError
Acceptance:
- Server starts from empty DB.
- Missing database auto-create behavior still works.
- Migrations are isolated and testable.
Milestone 4: Extract HTTP API server shell
Create:
internal/httpapi/server.gointernal/httpapi/router.go
Move:
ServerstructServeHTTP- top-level API route dispatch
- subroute dispatch helpers
Keep domain handler method bodies temporarily in httpapi if needed, then move them by domain in later milestones.
Acceptance:
runServerconstructshttpapi.Server.- API route paths remain unchanged.
- Git HTTP routes still reach the backend.
Milestone 5: Extract auth and token domains
Create:
internal/authinternal/tokens
Move:
- register/login/logout/me handlers
- token creation/hash helpers
- bearer/basic auth helpers
- token list/revoke/prune handlers
- direct admin-user creation helper used by
init
Acceptance:
- Registration still makes the first user admin.
- Login still returns 7-day tokens.
- CLI login/whoami/logout still work.
- Git basic auth still works with username/token.
Milestone 6: Extract repository domain
Create:
internal/repos
Move:
- repo create/get/update/delete/search/fork handlers
- repository load/scan helpers
- repo access helpers:
requireReadableRepocanReadRepocanWriteRepocollaboratorRole
- collaborator list/add/remove handlers
Acceptance:
- Public/private visibility rules still work.
- Private collaborators can read when allowed.
- Write collaborators can push when allowed.
- Archived repositories still reject pushes.
- Repo CLI commands still work.
Milestone 7: Extract pull request domain
Create:
internal/pulls
Move:
- PR create/list/view/close/merge handlers
- PR comments handlers
- PR diff handler
prSelectSQLloadPRscanOnePRscanPRsmergePRprDiff
Acceptance:
- Same-repo PRs still work.
- Cross-repo PRs still work.
- PR merge still uses Git commands correctly.
- PR diff/comments endpoints still work.
Milestone 8: Extract Git Smart HTTP server
Create:
internal/gitserver
Move:
handleGitHTTPparseGitPathgitServicerunGitHTTPBackendwriteCGIResponse
Design note:
gitserver should depend on a small permission interface instead of importing the entire repo handler package if possible:
type RepoAccess interface {
LoadRepo(owner, name string) (model.Repository, error)
CanReadRepo(repo model.Repository, user model.User, authed bool) bool
CanWriteRepo(repo model.Repository, user model.User) bool
UserFromBasic(r *http.Request) (model.User, bool)
}
Acceptance:
- Public clone/fetch still works without auth.
- Private clone/fetch works for owner/collaborators only.
- Push requires auth.
- Archived repos reject pushes.
- Git HTTP backend response handling remains correct.
Milestone 9: Extract CLI package
Create:
internal/cli
Move:
- command dispatch
usagecliInit- auth CLI commands
- repo CLI commands
- PR CLI commands
- token/admin/backup CLI commands
- CLI API request helper
- output formatting helpers
- prompt helpers
- parsing helpers
- Git credential approval helper
Acceptance:
cmd/gitocean/main.gois reduced to roughly:
package main
import (
"fmt"
"os"
"gitocean/internal/app"
)
func main() {
if err := app.Run(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
- Every existing CLI command still works.
- Help text is unchanged except for intentional wording cleanup.
Milestone 10: Extract backup package
Create:
internal/backup
Move:
createBackuprestoreBackupmysqlCLIArgscopyDir
Acceptance:
gitocean backup create FILEstill works.gitocean backup restore FILEstill works.- Backup logic is testable independently.
Milestone 11: Add package-level tests
Add focused unit tests where possible.
Suggested tests:
-
internal/validate- username validation
- repo name validation
- branch validation
- reserved names
-
internal/clisplitOwnerReposplitRepoBranchparseRepoCreateArgsparseRepoPublishArgs- boolean flag removal
-
internal/config- client config load/save round trip
- server config load/save round trip
- env override behavior
-
internal/gitserverparseGitPathgitService- CGI response parsing
-
internal/db- MySQL identifier quoting
Acceptance:
go test ./...includes meaningful tests.- Tests avoid requiring a live MySQL instance unless explicitly integration-tagged.
Milestone 12: CLI output audit
After the CLI package has been extracted, audit every CLI command for output style.
Commands should default to human-readable output:
- Short success messages for create/update/delete actions.
- Tables for lists.
- Labeled fields for detail views.
- Helpful next-step commands where useful.
- Clear auth/session error messages.
JSON should be retained only as an explicit opt-in for scripting, for example:
gitocean repo view OWNER/REPO --json
gitocean repo search QUERY --json
gitocean pr view OWNER/REPO NUMBER --json
gitocean token list --json
Acceptance:
- No normal CLI command prints raw JSON by default.
- Commands that are useful in scripts have a documented
--jsonoption. - HTTP API responses remain JSON and are not changed by this milestone.
Milestone 13: Documentation cleanup
Update docs/plans after the refactor:
- Update
plans/PLAN.mdif architecture descriptions changed. - Update
plans/PHASE_2_PLAN.mdif current layout references are stale. - Add a short
README.mdif missing with:- quickstart
- server setup
- CLI commands
- storage layout
- development workflow
Acceptance:
- New contributors can find where CLI, API, DB, Git HTTP, and models live.
Suggested implementation order
Recommended commit sequence:
Add Phase 3 refactor planMove shared models and validatorsExtract config and Git utilitiesExtract database setup and migrationsExtract HTTP API server shellExtract auth and token handlersExtract repository handlersExtract pull request handlersExtract Git HTTP backendExtract CLI commandsExtract backup utilitiesAudit CLI output and JSON escape hatchesAdd package-level tests and documentation
Compatibility checklist
After each milestone, run:
gofmt -w .
go test ./...
go vet ./...
Before declaring Phase 3 complete, manually verify:
gitocean init
gitocean server --config storage/config.json
gitocean register
gitocean login
gitocean whoami
gitocean repo create demo --public
gitocean repo view USER/demo
gitocean repo branches USER/demo
gitocean repo tags USER/demo
gitocean repo search demo --all
gitocean clone USER/demo
gitocean repo publish demo2 --public
gitocean repo fork USER/demo
gitocean pr create --from USER/demo-fork:main --to USER/demo:main --title "Test PR"
gitocean pr list USER/demo
gitocean pr view USER/demo 1
gitocean pr diff USER/demo 1
gitocean pr comment USER/demo 1 "Looks good"
gitocean pr comments USER/demo 1
gitocean token list
gitocean admin users list
gitocean backup create backup.tar.gz
Also verify Git HTTP manually:
git clone http://localhost:8080/USER/demo.git
cd demo
echo test >> README.md
git add README.md
git commit -m "test push"
git push origin main
Risks and mitigations
Risk: Circular package dependencies
Mitigation:
- Put shared structs in
internal/model. - Put small interfaces between domains where needed.
- Keep HTTP response helpers separate from domain services.
Risk: Refactor changes behavior accidentally
Mitigation:
- Move code first, improve code second.
- Keep commits small.
- Add tests for parsing/routing helpers early.
Risk: Handlers still know too much about SQL
Mitigation:
- Phase 3 can keep SQL in domain packages.
- A later phase can introduce repository/store interfaces if needed.
Risk: utils package becomes another god package
Mitigation:
- Only use utility packages for genuinely cross-domain helpers.
- Prefer domain packages.
Phase 3 completion criteria
Phase 3 is complete when:
cmd/gitocean/main.gois a thin entrypoint.- No package contains unrelated CLI, API, DB, Git, and model code together.
- Domain packages have clear responsibilities.
- Existing CLI commands and API routes still work.
- CLI output is human-readable by default, with
--jsonopt-in where useful. go test ./...passes.go vet ./...passes.- Meaningful unit tests exist for parsing, validation, config, and Git HTTP helpers.