Thank you for taking the time to contribute! This document covers everything you need to get started — from reporting a bug to shipping a new feature.
- Code of Conduct
- Reporting Bugs
- Suggesting Features
- Development Setup
- Making Changes
- Tests
- Commit Convention
- Pull Request Process
- Release Process
- Secret Configuration (maintainers)
Be respectful and constructive. We follow the Contributor Covenant.
- Search existing issues first.
- If not found, open a new issue and include:
- Your OS and architecture (
git-profile version) - The exact command you ran
- Expected vs actual behaviour
- Any relevant error output
- Your OS and architecture (
Open an issue with the enhancement label. Describe:
- The problem you are trying to solve
- Your proposed solution
- Alternatives you considered
For significant changes, discuss first before writing code — it avoids wasted effort if the direction doesn't align with the project goals.
Prerequisites: Go 1.22+, git
git clone https://github.com/hapiio/git-profile
cd git-profile
go mod download
# Build
make build
# Install locally
make install # installs to $GOPATH/bin| Target | Description |
|---|---|
make build |
Compile ./git-profile |
make test |
Run all tests with -race |
make cover |
Run tests and open HTML coverage report |
make lint |
Run golangci-lint (requires installation) |
make snapshot |
Build a GoReleaser snapshot locally |
make clean |
Remove build artefacts |
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestgit-profile/
├── cmd/ # One file per cobra command + shared helpers
├── internal/
│ ├── config/ # Config file load/save (Manager, Profile, Config)
│ ├── git/ # Thin wrappers over the git binary
│ └── ui/ # Lipgloss styles, Input/Confirm/Select prompts
├── main.go # Entry point — calls cmd.Execute()
- One concern per package.
internal/gitnever importsinternal/config, andinternal/uinever imports either. Commands incmd/are the only place where all three meet. - No interactive prompts in non-TTY contexts.
ui.IsTTY()guards allInput,Confirm, andSelectcalls. Never add a blocking read without checkingIsTTY()first. - Atomic config writes. Config is always written via a temp-file rename.
Do not use
os.WriteFiledirectly on the config path. - Minimal dependencies. The project intentionally keeps its dependency tree
small (
cobra+lipglossonly). Discuss in an issue before adding a new module.
make testAll tests use t.TempDir() for isolation — no test should read or write the
real ~/.config/git-profile/config.json.
internal/config— useconfig.NewManager(path)with a temp path.internal/git— use theinitRepo(t)helper to create a real git repo in a temp dir, andchdir(t, dir)to switch into it.cmd— usecmd.RunArgs([]string{"--config", tmpPath, ...})and assert on the resulting config file contents, not on stdout.
After running make test, open the coverage report:
make coverNew code should maintain or improve the existing coverage level. CI enforces a 60 % overall floor and 50 % per-patch minimum via Codecov.
We follow Conventional Commits:
<type>(<scope>): <short summary>
[optional body]
[optional footer]
| Type | When to use |
|---|---|
feat |
New feature visible to users |
fix |
Bug fix |
docs |
Documentation only |
test |
Adding or fixing tests |
refactor |
Code restructuring with no behaviour change |
chore |
Dependency bumps, CI tweaks, etc. |
ci |
Changes to GitHub Actions workflows |
Examples:
feat(cmd): add `import` command to bootstrap profiles from git config
fix(config): ensure config directory has 0700 permissions on creation
docs: document shell completion setup in README
GoReleaser uses commit messages to auto-generate the changelog, so following this convention directly improves release notes.
- Fork the repo and create a branch from
main:git checkout -b feat/my-feature
- Make your changes and write tests.
- Run the full test suite locally:
make test && make lint
- Push and open a Pull Request against
main. - Fill in the PR template — describe what changed and why.
- A maintainer will review and may request changes.
- Once approved and CI is green, the PR is merged by a maintainer.
- Tests added or updated
-
make testpasses -
make lintpasses (no new warnings) - Commit messages follow the convention above
- Documentation updated if behaviour changed
Releases are fully automated via GoReleaser and GitHub Actions.
# Maintainers only:
git tag v1.2.3
git push origin v1.2.3This triggers .github/workflows/release.yml, which:
- Runs the full test suite
- Builds binaries for all platforms (linux/amd64, linux/arm64, linux/armv7, darwin/amd64, darwin/arm64)
- Creates GitHub Release with checksums
- Publishes archives (
.tar.gz) - Publishes
.deband.rpmpackages - Updates the Homebrew formula (macOS + Linux)
- Publishes to AUR (Arch Linux) — if
AUR_KEYis set
Add these under Settings → Secrets and variables → Actions:
| Secret | Required | Description |
|---|---|---|
HOMEBREW_TAP_TOKEN |
Recommended | PAT with repo write access to hapiio/homebrew-tap |
AUR_KEY |
Optional | Base64-encoded ed25519 private key registered on AUR |
CODECOV_TOKEN |
Optional | Codecov upload token for coverage reporting |
Channels without a configured secret are skipped automatically — the release still completes for all other channels.
# Create a new repo named homebrew-tap under the hapiio org, then:
mkdir -p Formula
# GoReleaser will push the formula here on every release tag.ssh-keygen -t ed25519 -C "git-profile AUR" -f aur_key -N ""
# Register aur_key.pub on https://aur.archlinux.org/account/
# Base64-encode the private key for the secret:
base64 -w0 aur_key