git-ca
git-ca is a Git subcommand that drafts commit messages and pull request text using either GitHub Copilot or the OpenAI Codex (ChatGPT) backend. It reads git diff --cached for commits, or branch changes for PRs, asks the configured backend for a draft, opens the result in your editor by default, and then lets Git or GitHub CLI finish the action.
Quick Start
Prerequisites:
- Rust toolchain with Cargo
- Git
- Lefthook for Git hooks
- One of: a GitHub account with Copilot access, or a ChatGPT account (Plus/Pro/Team) for the Codex backend
Install from this checkout:
Install from Homebrew after the first release is published:
Install from npm or Bun after the first release is published:
Run without a global install:
The man page install lets Git's own help path resolve git ca --help. For the
clap-generated command help, git ca -h and git-ca --help work directly.
Authenticate. With no --provider, git ca auth login prompts for a backend on a TTY (and defaults to copilot when stdin is piped or running in CI):
Pick a backend non-interactively:
The Codex flow opens your browser, completes the same login codex itself uses, and stores ChatGPT tokens locally. It needs to bind a loopback callback on 127.0.0.1:1455 (fallback :1457).
Or store a GitHub token manually for Copilot (Codex is OAuth-only):
Create a commit:
git-ca writes a draft commit message, opens your configured Git editor, and passes the edited message to git commit. If you save an empty message, Git aborts the commit as usual.
Create a pull request:
git ca pr compares the current branch to the base branch, drafts a PR title and Markdown body, opens the draft in your editor, and then runs gh pr create. It requires the GitHub CLI (gh) to be installed and authenticated. By default it summarizes the branch diff; use --source commits to summarize commit messages instead. --yes skips the editor and creates the PR directly. Persisted config.auto_accept_pr does the same for PRs.
Useful commands:
Key Features
- Drafts commit messages from the staged diff only.
- Drafts pull request title/body text from branch diff or commit log and creates the PR with
gh. - Two interchangeable backends: GitHub Copilot or OpenAI Codex (ChatGPT).
- Prompts the active backend to produce Conventional Commits output.
- Opens the generated message in the normal Git commit editor before committing.
- Can commit the generated message directly with
--yes/-yor persisted commit auto-accept config. - Can create generated pull requests directly with
--yes/-yor persisted PR auto-accept config. - Supports per-command model override with
--model. - Supports a persisted default model via
git ca config set-model. - Supports persisted commit auto-accept via
git ca config set-auto-accept. - Supports persisted PR auto-accept via
git ca config set-auto-accept-pr. - Lists chat models available to the authenticated Copilot account; for Codex, prints the known model slugs.
- Copilot login uses GitHub device flow; Codex login uses ChatGPT OAuth (PKCE) with a localhost callback.
- Can store a GitHub token manually for Copilot in environments where device flow is not practical.
- Supports multiple named accounts (mix of Copilot and Codex) with an active-account selector.
- Stores local auth/config files under
$XDG_CONFIG_HOME/git-caor~/.config/git-cawith restrictive Unix permissions. - Caches Copilot API tokens and refreshes them when expired or rejected; refreshes ChatGPT access tokens via the rotated refresh token on 401.
- Retries transient backend/network failures with short backoff.
- Applies HTTP connect and request timeouts so stalled endpoints do not hang the CLI indefinitely.
Commands
| Command | Description |
|---|---|
git ca |
Draft a message for staged changes and run git commit -e -F <message> |
git ca pr |
Draft a PR title/body from current branch changes and run gh pr create |
git ca pr --base <branch> |
Compare the current branch against a specific PR base branch |
git ca pr --source commits |
Draft PR text from commit messages instead of the branch diff |
git ca --model <id>, git ca -m <id> |
Use a specific Copilot model for this commit |
git ca --yes, git ca -y |
Accept generated text without opening the editor; for PRs this creates the PR directly |
git ca --no-verify |
Pass --no-verify through to git commit |
git ca auth login |
Prompt for backend on a TTY, then log in (defaults to Copilot when stdin is not a TTY) |
git ca auth login <account> |
Same prompt behavior, then store credentials for the named account |
git ca auth login --provider codex [account] |
Log in via ChatGPT OAuth (PKCE) for a Codex account |
git ca auth set-token <token> |
Store a GitHub token manually as the default active account (Copilot only) |
git ca auth set-token --account <account> <token> |
Store a GitHub token manually for a named Copilot account |
git ca auth use <account> |
Select the named account; the active account decides the backend |
git ca auth logout |
Delete locally stored tokens |
git ca auth logout <account> |
Delete locally stored tokens for one named account |
git ca auth status |
Show local auth state, active account's provider, and per-provider token state |
git ca models |
List available models for the active account's backend |
git ca config list |
Print all persisted config values |
git ca config set-model <id> |
Persist the default model |
git ca config get-model |
Print the persisted default model |
| `git ca config set-auto-accept <true | false>` |
git ca config get-auto-accept |
Print the persisted commit auto-accept setting |
| `git ca config set-auto-accept-pr <true | false>` |
git ca config get-auto-accept-pr |
Print the persisted PR auto-accept setting |
auth logout only removes local credentials. Revoke the OAuth grant separately from GitHub account settings if the server-side grant should be invalidated.
System Architecture
The binary is intentionally small and split by responsibility:
src/main.rs
CLI dispatch, high-level command orchestration, HTTP client construction
src/cli.rs
clap argument and subcommand definitions
src/git/
staged diff/branch PR source reading, editor-backed `git commit`, and `gh pr create` execution
src/commit_msg/
Copilot prompt construction, diff truncation, and generated-message cleanup
src/pr_msg/
PR prompt construction, source truncation, and generated JSON parsing
src/auth/
GitHub device flow, ChatGPT OAuth (PKCE + loopback), local auth file
storage, Copilot/Codex token exchange/refresh
src/copilot/
Copilot HTTP client, chat completion calls, model listing, retry/auth wrapper
src/codex/
Codex HTTP client (chatgpt.com/backend-api/codex), Responses-API request
builder, SSE event parser, retry/auth wrapper
src/config/
config/auth paths, JSON persistence, restrictive file/directory permissions
src/error.rs
typed error model and CLI exit-code mapping
Runtime flow for git ca:
- Read the staged diff with
git diff --cached --no-color -U3. - Resolve the active account's backend (Copilot or Codex).
- Load the persisted default model, unless
--modelwas passed; fall back to a backend-specific default (gpt-4ofor Copilot,gpt-5.5for Codex). - For Copilot: refresh the Copilot API token from the stored GitHub token. For Codex: use the cached ChatGPT access token, refreshing via
/oauth/tokenon 401. - Send the chat request with the Conventional Commits prompt — chat-completions for Copilot, Responses-API streamed over SSE for Codex.
- Strip an accidental outer code fence from the model response.
- Write
.git/COMMIT_EDITMSG. - Run
git commit -e -F .git/COMMIT_EDITMSG, optionally with--no-verify. - If
--yes/-yorconfig.auto_acceptis enabled, rungit commit -F .git/COMMIT_EDITMSGinstead so Git commits the generated message directly.
Runtime flow for git ca pr:
- Resolve the base branch from
--base, elseorigin/HEAD, elsemain. - Resolve
git merge-base <base> HEAD. - Read either
git diff --no-color -U3 <merge-base>...HEADorgit log --no-merges --format=%s%n%n%b <merge-base>..HEAD. - Resolve the active account's backend and model the same way
git cadoes. - Ask the backend for compact JSON containing
titleandbody. - Parse and validate the generated PR text.
- Unless
--yes/-yorconfig.auto_accept_pris enabled, write.git/PULL_REQUEST_EDITMSG, open the configured Git editor, and read back the edited title/body. - Write
.git/PULL_REQUEST_BODYand rungh pr create --base <base> --title <title> --body-file <path>.
Codex backend caveat
The Codex backend talks to https://chatgpt.com/backend-api/codex/responses, which is the same undocumented endpoint OpenAI's codex CLI uses. git-ca mimics codex's originator/header values to avoid being singled out if OpenAI ever tightens client verification — same posture this project already takes for Copilot, where the request mimics the VS Code Copilot Chat extension. The endpoint can change without notice; if Codex chats start failing with Codex API …, expect a follow-up release that tracks the new wire format.
Copilot Free and Model Multipliers
GitHub Copilot request accounting depends on both plan and model. GitHub's documentation is the source of truth because included models and multipliers can change: https://docs.github.com/en/copilot/concepts/billing/copilot-requests#model-multipliers
As of the referenced GitHub documentation:
- Copilot Free includes up to 2,000 inline suggestion requests and up to 50 premium requests per month.
- All chat interactions count as premium requests on Copilot Free.
- On paid Copilot plans, GPT-5 mini, GPT-4.1, and GPT-4o are included models and have a 0 premium-request multiplier.
- On Copilot Free, GPT-5 mini, GPT-4.1, and GPT-4o each consume 1 premium request.
- Other premium model multipliers vary by model and may be unavailable on Copilot Free.
Development Flow
Enable the project Git hooks once per checkout:
Run the standard checks before committing:
During local development, cargo test uses wiremock and needs permission to bind local ports.
Recommended change flow:
- Keep changes focused and match the existing module boundaries.
- Add or update tests for behavior changes and bug fixes.
- Run
cargo fmtbefore final verification. - Run the full check set above.
- Commit related changes atomically with an informative Conventional Commits message.
Release Flow
Releases are built by cargo-dist and can be published either from GitHub Actions or from a local maintainer machine.
GitHub Actions release
Push a version tag to publish GitHub Release artifacts, crates.io, Homebrew, and npm from CI.
Bump version
Use cargo-release to bump Cargo.toml, refresh Cargo.lock, update the manual page header, create the version commit, and create the matching v* tag. The project release settings live in release.toml.
Install the release helper once:
Preview a patch release without writing changes:
Create the version commit and tag:
Use minor, major, or an exact SemVer version when needed:
cargo-release runs cargo check before committing so Cargo.lock is included when the package version changes. It also updates docs/man/git-ca.1 from this template:
.TH GIT-CA 1 "{{date}}" "git-ca {{version}}" "User Commands"
The generated tag uses v{{version}}, which is the tag format cargo-dist uses for GitHub Actions releases. release.toml keeps publish = false and push = false so publishing stays in GitHub Actions and the maintainer explicitly pushes the release tag.
Do not reuse a version already published to crates.io or npm. Published package versions are immutable.
Release checklist:
- Run
cargo release patch,cargo release minor, orcargo release <version>and inspect the dry-run output. - Run
cargo release patch --execute,cargo release minor --execute, orcargo release <version> --execute. - Run
cargo fmt --check. - Run
cargo clippy --all-targets --all-features -- -D warnings. - Run
cargo test. - Run
cargo publish --dry-run --locked. - Run
dist plan --allow-dirty. - Build the release binary locally if you want an extra smoke test:
- Push the version commit and matching tag:
The release workflow uploads archives and checksums to GitHub Releases, then runs cargo-dist custom publish jobs for crates.io, Homebrew, and npm. Keep these jobs configured through dist-workspace.toml instead of editing the generated .github/workflows/release.yml directly:
= ["./publish-homebrew", "./publish-npm", "./publish-crates"]
dist init may warn that the built-in Homebrew publish job is disabled. That is expected because .github/workflows/publish-homebrew.yml owns Homebrew publishing so the tap commit author can be customized.
Configure these repository secrets before pushing release tags:
CARGO_REGISTRY_TOKENwith publish access to thegit-cacrate on crates.io.HOMEBREW_TAP_TOKENwith write access tohankcraft/homebrew-tap.
The generated release workflow uses GitHub Actions' automatic GITHUB_TOKEN for GitHub Releases. Do not add a repository secret named GITHUB_TOKEN.
npm publishing uses npm Trusted Publishing with GitHub Actions OIDC, not NPM_TOKEN. Configure the npm package trusted publisher on npmjs.com with:
- Provider: GitHub Actions
- Organization or user:
hankcraft - Repository:
git-ca - Workflow filename:
release.yml - Environment name: unset unless the workflow is changed to use a GitHub environment
The publish command lives in the reusable .github/workflows/publish-npm.yml workflow, but cargo-dist calls it from .github/workflows/release.yml. npm validates the calling workflow for workflow_call publishes, so the trusted publisher must use release.yml.
Local release
Use the local release script when you want to publish from your own machine instead of relying on GitHub Actions. It defaults to a dry run and requires --execute before creating or publishing anything.
Dry-run the local release checks:
Publish all local release channels:
Skip one channel if it was already published or should stay manual:
Local publishing requirements:
- GitHub Release hosting:
GH_TOKENor an authenticated cargo-dist/GitHub CLI setup with write access tohankcraft/git-ca. - crates.io:
cargo loginorCARGO_REGISTRY_TOKENwith publish access togit-ca. - npm:
npm login,.npmrc, orNODE_AUTH_TOKENwith publish access to@hankcraft/git-ca. - Homebrew: a clean local checkout of
hankcraft/homebrew-tapwith push access, passed with--homebrew-tap-dirorHOMEBREW_TAP_DIR.
Copy .env.example to .env if you want a local template for release-related environment variables. Do not commit .env.
cargo-dist currently builds git-ca for x86_64-apple-darwin, aarch64-apple-darwin, x86_64-unknown-linux-gnu, and aarch64-unknown-linux-gnu, so the Homebrew formula supports macOS and Linuxbrew on x86_64 and aarch64 Linux.
Production Homebrew releases install docs/man/git-ca.1 automatically, so git ca --help can resolve Git's manual page after brew install hankcraft/tap/git-ca.
Configuration Files
git-ca stores configuration under $XDG_CONFIG_HOME/git-ca when XDG_CONFIG_HOME is set, otherwise under ~/.config/git-ca:
~/.config/git-ca/config.json
~/.config/git-ca/auth.json
On Unix, the config directory is set to 0700 and JSON files are written with 0600 permissions.