Everything a contributor needs to know to work on gitconfig.
| Tool | Version | Purpose |
|---|---|---|
| Go | ≥ 1.24 | Build & test |
| git | any | Required at runtime for config command |
| golangci-lint | latest | Linting (optional but recommended) |
| goreleaser | latest | Building release binaries |
Install Go from https://go.dev/dl/. All other dependencies are pulled via go mod.
git clone https://github.com/0ghny/gitconfig
cd gitconfig
# Download dependencies
go mod download
# Build a local binary for your current OS/arch
./hack/dev.sh build
# Run the full test suite
./hack/dev.sh test
hack/dev.shAll common development tasks are available through hack/dev.sh. Run it without arguments (requires fzf) or pass a task name directly:
./hack/dev.sh build # compile for current OS/arch → target/<timestamp>/gitconfig
./hack/dev.sh test # go test ./...
./hack/dev.sh test --coverage|-c # test with coverage report → target/<timestamp>/
./hack/dev.sh lint # golangci-lint run
./hack/dev.sh fmt # gofmt -l check
./hack/dev.sh deps # go mod tidy + list available upgrades
./hack/dev.sh clean # remove target/ and purge Go build/test/module caches
./hack/dev.sh all # fmt + lint + test
See ARCHITECTURE.md for a full description of the hexagonal layering. The short version:
internal/domain/ ← pure business logic, no I/O
internal/application/ ← use-case orchestration (LocationManager)
internal/adapter/ ← CLI (cobra), filesystem (afero), git process
cmd/ ← entry point only
internal/adapter/cli/<command>.go following the pattern in location_new.go.factory LocationServiceFactory (and any shared flags pointer).cmd.OutOrStdout() and cmd.InOrStdin() — never fmt.Println or os.Stdin directly.root.go or as a subcommand of an existing command.<command>_test.go using RootCmdWithDeps + the in-memory testEnv helper.internal/domain/location/service.go.LocationManager in internal/application/locations/manager.go.var _ location.Service = (*LocationManager)(nil) will fail until the implementation is complete — that’s intentional.manager_test.go using afero.NewMemMapFs().The project uses three kinds of tests:
| Kind | Location | How it works |
|---|---|---|
| Unit | *_test.go beside source files |
Pure Go, no filesystem, no process |
| Application | internal/application/locations/manager_test.go |
afero.MemMapFs — no real disk |
| CLI integration | internal/adapter/cli/*_test.go |
RootCmdWithDeps + afero.MemMapFs — no real disk and no real git |
# Run everything
go test ./...
# Run a specific package
go test ./internal/adapter/cli/...
# Run with coverage
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
Tests that require the git binary are guarded with:
func skipIfGitNotFound(t *testing.T) {
t.Helper()
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git binary not found in PATH")
}
}
gofmt before committing.*_test.go files.fmt.Errorf("context: %w", err) where additional context is useful.panic outside init() and main().main.feature/<name>, fix/<name>, chore/<name>.feat:, fix:, docs:, chore:, test:, refactor:.main; CI must pass before merging.See RELEASE.md.