Developer Guide
March 26, 2026 · View on GitHub
Everything a contributor needs to know to work on gitconfig.
Prerequisites
| 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.
Getting started
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
Development script — hack/dev.sh
All 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
Project structure
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
Adding a new feature
Adding a new CLI command
- Create
internal/adapter/cli/<command>.gofollowing the pattern inlocation_new.go. - The function signature must accept
factory LocationServiceFactory(and any shared flags pointer). - Use
cmd.OutOrStdout()andcmd.InOrStdin()— neverfmt.Printlnoros.Stdindirectly. - Register the command in
root.goor as a subcommand of an existing command. - Add a matching
<command>_test.gousingRootCmdWithDeps+ the in-memorytestEnvhelper.
Extending the Service interface
- Add the method signature to
internal/domain/location/service.go. - Implement it on
LocationManagerininternal/application/locations/manager.go. - The compile-time check
var _ location.Service = (*LocationManager)(nil)will fail until the implementation is complete — that's intentional. - Add tests in
manager_test.gousingafero.NewMemMapFs().
Testing
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")
}
}
Code style
- Follow standard Go idioms. Run
gofmtbefore committing. - All exported symbols must have a godoc comment.
- Unexported helpers used only in tests should live in
*_test.gofiles. - Errors should be propagated with
fmt.Errorf("context: %w", err)where additional context is useful. - Avoid
panicoutsideinit()andmain().
Commit & branch conventions
- Branch from
main. - Use descriptive branch names:
feature/<name>,fix/<name>,chore/<name>. - Write conventional commit messages:
feat:,fix:,docs:,chore:,test:,refactor:. - Open a Pull Request against
main; CI must pass before merging.
Releasing
See RELEASE.md.