git-agent 0.8.0

Generate git commit messages from your changes with a language model
git-agent-0.8.0 is not a library.

git-agent

crates.io

git-agent writes git commit messages for your changes with a language model, either one running on your own machine (LM Studio, Ollama, llama.cpp, vLLM) or one from a cloud provider such as OpenAI, Anthropic or OpenRouter. You review each proposed message, then commit it, edit it, regenerate it, or tell the model what to change.

The same model also names new branches, splits a large change into several commits, tidies commits before you push them, and writes pull requests for GitHub. It can explain why code looks the way it does, or what a set of commits changed.

Why I made git-agent

I made git-agent mainly to get the commit message generation from IntelliJ IDEA without running IDEA. IDEA uses nearly 3-4 GB of RAM, and I need that memory for running local language models. git-agent also has stronger automation features, for use in pipelines or with lightweight code editors. The code was developed using Gemma 4 locally, and refactored with Claude.

Install

Because the binary is called git-agent, git also runs it as git agent.

With Homebrew

brew install dervism/tap/git-agent

Homebrew builds git-agent from source, and installs its own Rust for the build if you don't have it yet. brew upgrade git-agent updates git-agent, and brew uninstall git-agent removes it.

From crates.io

git-agent is published on crates.io. With Rust 1.88 or newer installed, run:

cargo install git-agent --locked

Cargo builds an optimized binary and copies it to ~/.cargo/bin/ (or $CARGO_HOME/bin/). --locked uses the exact dependency versions the release was tested with. Run the same command again to update to the latest release, and cargo uninstall git-agent to remove it.

From a local checkout

To try changes that aren't released yet, install from a clone of the repository:

cargo install --path . --locked

If you installed Rust with Homebrew or a Linux package manager

~/.cargo/bin must be on your PATH. rustup adds it for you, but Homebrew and Linux package managers don't, so add it yourself, for example in ~/.zshrc:

export PATH="$HOME/.cargo/bin:$PATH"

Usage

Commands

git agent                  # describe staged changes, then review
git agent --all            # stage everything (including untracked files) first
git agent -H "fixes #42"   # give the model extra context
git agent --commit         # commit right away, no review
git agent --push           # review, then push after committing
git agent --commit --push  # commit and push, no review
git agent --raw            # print just the message, e.g. for `| git commit -F -`
git agent -- --signoff     # pass extra arguments to `git commit`
git agent branch fix login # name a new branch after what it is for, then create it
git agent split            # split the changes into several commits
git agent tidy             # reword unclear messages and fold fixes, before you push
git agent pr               # write a pull request for the branch, then create it
git agent why src/fit.rs:120  # explain why a line is the way it is
git agent explain          # explain the last commit, a range, a branch or a pull

Which changes are included

If you have staged changes, git-agent describes and commits only those. If nothing is staged, it describes all changes to tracked files and commits them with git commit --all. Untracked files are left out unless you use --all.

Reviewing the message

git-agent shows the proposed message and waits for one of these keys:

Key Action
c Commit with this message
p Commit with this message, then push
e Open it in your git editor (GIT_EDITOR, core.editor, VISUAL, EDITOR)
r Generate a new message
f Tell the model what to change, e.g. f shorter, and mention the CLI flags
q Quit without committing

If the commit fails

When git commit fails, for example because a hook rejects it, git-agent saves the message to .git/GIT_AGENT_MSG. You can reuse it with git commit --file .git/GIT_AGENT_MSG.

Starting a branch

git agent branch names a new branch after what it is for, creates it and switches to it.

git agent branch fix login timeout        # name it after a description
git agent branch 42                       # after GitHub issue #42, looked up with gh
git agent branch                          # after the uncommitted changes
git agent branch -c add dark mode         # create it right away, no review
git agent branch --from origin/main ...   # start it at origin/main instead of HEAD
git agent branch --raw fix login timeout  # print just the name

The name starts with the kind of work, as in fix/login-timeout or feat/42-dark-mode. The model sees your recent branches and follows their pattern, so in a repository with branches like feature/PROJ-12-avatars, the names look like feature/dark-mode. A name has an issue number or ticket key only when the description or the issue has one; git-agent removes any that the model makes up or copies from another branch.

Without a description, git-agent names the branch after your uncommitted changes. That helps when you started on main and want the work on a branch of its own. The changes come along to the new branch.

The review works like the one for commit messages: c creates the branch, e lets you edit the name in place, r asks for a new name and f tells the model what to change. git-agent won't create a branch with the name of an existing local or remote branch.

The new branch starts at HEAD, or where --from says, and has no upstream yet. Its first push sets an upstream of the same name, so it never pushes to the branch it started from. Looking up an issue needs the GitHub CLI, as pull requests do.

Splitting a change into commits

When your changes do several things at once, git agent split turns them into several commits. The model groups the changes by what they are for, and you review its plan and then each commit's message.

git agent split                # plan the commits, then review each one
git agent split --all          # stage everything, including untracked files, first
git agent split -H "why"       # give the model extra context
git agent split --commit       # make the commits without any review
git agent split --push         # push after the last commit
git agent split -- --signoff   # pass extra arguments to each `git commit`

How git-agent plans the commits

git-agent splits the changes a commit would include: the staged ones, or all changes to tracked files if nothing is staged. It cuts them into parts. A part is one hunk of a changed file, or a whole file when the file is new, deleted, renamed or binary, or excluded like a lockfile. The model sees the numbered parts and groups them into commits, ordered so that each builds on those before it. If it leaves a part out, git-agent adds that part to the commit with the rest of its file, or to a last commit. When the hunks are too many to show the model one by one, it groups whole files instead.

Reviewing the plan

── Proposed commits ────────────────────────────────────────────
1. Introduce LRU caching for configuration loading
   Cargo.lock, Cargo.toml, README.md, src/cache.rs (new file), src/config.rs (1 of 3 changes)
2. Refine config parsing logic and documentation
   src/config.rs (2 of 3 changes)
────────────────────────────────────────────────────────────────
[a]ccept  [e]dit  [r]egenerate  [f]eedback  [q]uit  [?] ›

a accepts the plan. e opens it in your git editor, where each part has a numbered line under its commit; move a line to move the part to another commit. r asks for a new plan, and f tells the model what to change, such as f put the feature and its documentation in one commit.

Making the commits

git-agent then makes the commits in order. For each one it writes the message from that commit's own changes, and you review it like any other message, with two differences: c commits, and s skips the commit and leaves its changes as they were. q stops; the commits made so far stay. Changes that aren't committed stay where they were, staged or not.

git-agent doesn't build or test the commits in between, so a commit may not compile on its own if the plan separates code that belongs together. Secrets are checked as for any commit, and git-agent won't split changes while a merge or rebase is in progress.

Tidying commits before you push

git agent tidy cleans up the commits you haven't pushed yet. A commit whose message doesn't say what it does, such as "wip" or "asdf", gets a new message written from its changes. A commit that only fixes an earlier one, such as "fix typo", is folded into the commit it fixes.

git agent tidy                 # plan the tidy-up, then review it and each new message
git agent tidy --base HEAD~5   # tidy the commits after HEAD~5
git agent tidy -H "why"        # give the model extra context
git agent tidy --apply         # tidy without any review

Reviewing the plan

── Proposed tidy-up ────────────────────────────────────────────
keep   a6fbe58 feat: add csv export
reword ed39c0d wip
fold   842f69d fix typo
keep   8e249fb docs: document the export
fold   7f944d8 fixup! docs: document the export
reword 237af0f asdf

6 commits become 4: 2 folded into others, 2 with a new message
────────────────────────────────────────────────────────────────
[a]ccept  [e]dit  [r]egenerate  [f]eedback  [q]uit  [?] ›

A folded commit joins the commit on the line above it and keeps that commit's message. Commits named fixup! <subject> or squash! <subject>, as git commit --fixup makes them, fold into the commit they name unless you tell the model otherwise. e opens the plan in your git editor as lines like those of git rebase -i: change a line's first word to keep, reword or fold, and move a fold line under the commit it fixes. Every commit must stay in the plan.

After you accept the plan, git-agent writes the new messages one by one, and you review each like any commit message, except that u uses the new message and k keeps the old one. q stops before anything has changed.

What git-agent changes

git-agent only tidies commits that no remote branch has, so it never has to force-push. In a repository without a remote, name the commit to tidy after with --base. It doesn't tidy across merges, or more than 40 commits at a time.

The rewrite runs git rebase in a temporary worktree, without hooks, so your work tree and index stay as they are, uncommitted changes included. The branch moves only if the tidied commits end with exactly the same files as before, and git-agent then prints the command that undoes the tidy-up:

✅ Tidied 6 commit(s) into 4:
   a6fbe58 feat: add csv export
   63b8816 feat(export): add optional CSV header support
   7d6754a docs: document the export
   bf40777 test(export): add a test for the CSV header
   To undo: git reset --keep 237af0f

If folding a commit into an earlier one conflicts with the commits between them, git-agent stops and leaves the branch as it was.

Pushing

git-agent pushes with a plain git push, so it works with GitHub or any other remote and uses your existing git credentials. The first time you push a branch that has no upstream, git-agent sets one: it uses remote.pushDefault if you've configured it, otherwise your only remote, otherwise origin. If the repository has no remote yet, create one on GitHub with gh repo create --source . --push.

If the push fails, the commit stays. Fix the problem and run git push.

When the remote has new commits

Before pushing, git-agent fetches from the remote. If the remote has commits you don't have, such as a file you edited on GitHub, it lists them and rebases your commit on top of them. Your history stays linear and the remote commits stay as they are. From the review menu, git-agent asks before it rebases; --commit --push rebases without asking. Any uncommitted changes are set aside during the rebase and restored afterwards.

Conflicts

If your commit conflicts with the remote commits, git-agent cancels the rebase and leaves your branch as it was. Run git pull --rebase, resolve the conflicts, then run git push. git-agent never force-pushes.

Pull requests

git agent pr writes a title and description for the current branch from its commits and its diff, then creates the pull request on GitHub. The pull request merges into the repository's default branch unless you name another with --base. git-agent fetches that branch first, so commits that reached it from elsewhere aren't described as yours.

git agent pr                  # write the pull request, then review it
git agent pr --base develop   # merge into develop instead of the default branch
git agent pr --draft          # create it as a draft
git agent pr -H "fixes #42"   # give the model extra context
git agent pr --create         # create it right away, no review
git agent pr --raw            # print just the title and description
git agent pr -- --label bug   # pass extra arguments to `gh pr create`

Reviewing the pull request

The first line of the proposal is the title, and the rest is the description in Markdown. The review works like the one for commit messages, except for its first two keys: c creates the pull request and d creates it as a draft. If the branch isn't on GitHub yet, or GitHub lacks some of its commits, git-agent pushes it first, the same way it pushes after a commit. Uncommitted changes are not part of the pull request, and git-agent tells you when you have some.

What it needs

git-agent creates pull requests with the GitHub CLI, so it uses your existing GitHub login. Install gh and run gh auth login once. Only --raw together with --base works without it.

If the branch already has an open pull request, git-agent shows its address and stops. If gh pr create fails, git-agent saves the description to .git/GIT_AGENT_PR.md and prints the command that creates the pull request from it.

Explaining code and history

Two commands answer questions about code that is already committed. Neither changes anything in the repository.

Why a line is the way it is

git agent why explains lines of code from their history.

git agent why src/fit.rs:120              # one line
git agent why src/fit.rs:120-140          # a range of lines
git agent why src/fit.rs:share            # a function
git agent why src/fit.rs:120 "why 400?"   # ask something specific

The model gets the lines with some code around them, the commit that last changed each line, and the history of the lines: every commit that changed them, newest first, with its message and its diff of those lines. The history follows the lines through renames, and blame skips commits that only change whitespace or move the lines. When the history is too long for the context, git-agent cuts the diffs short and keeps the newest commits and the oldest one, since the commit that added the lines often says why they are there.

git-agent finds a function the way git log -L :name:file does, by the line that starts it. It turns on git's built-in function detection for common languages, among them Rust, Python, Go, Java, Kotlin, C, C++, C#, Ruby and PHP, so methods inside classes and impl blocks are found too. Your own .gitattributes still takes precedence.

Lines with uncommitted changes are marked, and the rest are explained from the committed version. If none of the lines are committed yet, there is no history to explain, and git-agent says so.

Explaining commits

git agent explain summarizes commits: what they change and why, and what you may need to act on, such as a breaking change or a new setting.

git agent explain                          # the last commit
git agent explain a1b2c3d                  # another commit
git agent explain v0.5.0..v0.6.0           # a range of commits
git agent explain --branch                 # the current branch since it left the default branch
git agent explain --branch feature         # another branch
git agent explain --pull                   # what the last git pull brought in
git agent explain -q "anything breaking?"  # ask something specific

A merge commit is explained through the commits it merged. --branch compares the branch with the remote's default branch, such as origin/main, unless you name another with --base. --pull looks in the current branch's reflog for the last git pull that changed it. A pull with rebase puts new copies of your own commits on top of the ones that arrived; git-agent leaves the copies out and explains only what arrived.

The model gets the commit messages and the diff of everything the commits change together. A long range is shortened the same way as a large commit (see Large changes).

Follow-up questions

After the answer, both commands wait for a follow-up question, and the model answers it with the same code and history at hand. Press Enter on an empty line to finish. git-agent stops asking when the conversation no longer fits in the model's context. When the output goes to a pipe or a file, it prints the answer and exits.

Secrets in the code and the history are replaced with [REDACTED] before anything is sent, as for commits. These commands commit and push nothing, so they only warn about secrets, whatever on_secrets is set to.

Choosing a model

git-agent sends your changes to the model server at base_url. By default that is http://localhost:1234/v1, the address of LM Studio running on your own machine. To use another local server, such as Ollama, or a cloud provider, choose it in git agent config or change base_url in the config file.

Local servers

With a local server, you don't have to configure a model. git-agent asks the server which model is loaded and uses that one. If several models are loaded, or none is and the server has more than one, git-agent asks you to pick one with git agent config or --model. git agent models lists the server's models and marks the one git-agent uses.

Cloud providers

git-agent works with any provider that has an OpenAI-compatible API, and with Anthropic through its own Messages API. Your diffs then leave your machine and go to that provider, without the secrets git-agent finds in them.

To set one up, run git agent config, choose Provider, and pick it from the list: OpenAI, Anthropic, OpenRouter, Google Gemini, Mistral, Groq, DeepSeek, xAI, Together AI, Fireworks AI or Cerebras. "Other" takes the address of any other OpenAI-compatible API. git-agent asks for your API key, then lists the provider's models for you to pick from. You can type to filter the list.

Most providers don't report a model's context window, so the menu asks for it after you pick a model. You'll find it on the provider's model page. Without it, git-agent assumes 4,096 tokens and shortens large diffs much more than it needs to. Anthropic and OpenRouter report it, so the menu doesn't ask for their models.

You can also set the provider in the config file:

base_url = "https://api.example.com/v1"   # the provider's OpenAI-compatible endpoint
model = "model-id"                        # an ID from `git agent models`
context_length = 128000                   # from the provider's model documentation

API keys

git-agent never stores API keys in the config file. On macOS, git agent config saves each key in your keychain, under the name git-agent and the provider's host (such as api.openai.com), and git-agent reads it from there on every run. Each provider keeps its own key, so you can switch between them. After you install a new version of git-agent, macOS may ask whether git-agent can use the saved key; choose Always Allow.

On other systems, set the GIT_AGENT_API_KEY environment variable. It also works on macOS, and like --api-key, it overrides a saved key:

export GIT_AGENT_API_KEY="your-api-key"

Secrets in your changes

Before git-agent sends anything to the model, it looks for secrets in it and replaces each one with [REDACTED]. That covers the diff, the commit messages of pull requests and explained commits, and the code and history that git agent why sends. The model still sees that something was there, but the secret itself never goes into a prompt, whichever server you use.

Rejecting or warning

By default, git-agent also refuses to commit or push secrets. When your changes add one, it stops before it asks the model and lists what it found:

error: the changes add what look like secrets, so git-agent didn't commit them:
   .env:2  DB_PASSWORD value
   src/client.rs:14  GitHub token
Take them out of the changes. If they aren't real secrets, add --on-secrets warn, or choose Warn under Secrets in `git agent config`.

Before every push, git-agent checks each commit the push would send, including its message, because a secret that a later commit removes is still in the history. git agent pr checks every commit on the branch the same way before it writes the pull request. A secret on a line that your change leaves alone or removes doesn't stop anything; git-agent only lists it.

To be warned instead, set on_secrets = "warn" in the config file, choose Warn under Secrets in git agent config, or pass --on-secrets warn for one run. git-agent then lists the secrets and carries on, and the model still gets [REDACTED] in their place.

What git-agent recognizes

  • API keys and tokens in the formats of GitHub, GitLab, Slack, Stripe, AWS, OpenAI, Anthropic, Google, Hugging Face, Groq, xAI, npm and PyPI, and JSON web tokens
  • private keys (-----BEGIN … PRIVATE KEY-----)
  • passwords in URLs, such as postgres://app:s3cret@db.example.com, and bearer tokens
  • values assigned to names such as password, secret, token or API_KEY, in .env files, YAML, JSON or code

It leaves placeholders such as your-api-key and changeme alone, and references such as ${API_KEY} too. A secret in a format git-agent doesn't know can still get through.

Large changes

Each run starts with a line that names the model and shows how much of its context the prompt uses:

🤖 google/gemma-4-e4b (loaded on the server) · prompt about 24,578 of 131,072 context tokens

Context window

git-agent asks the server how large the model's context window is, so it knows how much of your changes it can send. LM Studio, Ollama, llama.cpp, vLLM, Anthropic and OpenRouter report it. For other servers, and for models that aren't loaded yet, git-agent assumes 4,096 tokens and tells you so. Set context_length to give it the real size.

Large prompts take longer to process. To keep them smaller, set context_length below the model's real context.

How git-agent shortens a diff

The prompt always leaves room for the reply. When your changes don't fit, git-agent shortens them in steps and stops as soon as they fit:

  1. It leaves out the unchanged lines that git shows around each change.
  2. It shares the remaining room between the files. Files with small diffs are sent whole and larger ones are cut short, so every file shows at least its first changes.
  3. If there are too many files even for that, the largest ones are sent by name only. They still appear in the list of changed files.

What git-agent tells you

When it shortens your changes, git-agent prints what it left out:

warning: the changes need about 24,656 tokens, more than fit in the context, so git-agent sent part of them:
   46 of 58 files in full, without the unchanged lines around the changes
   12 cut short: src/builder/arg.rs (6 of 23 changed lines), src/builder/command.rs (6 of 93 changed lines), and 10 more

The message may then miss details from the shortened files. For better messages, commit in smaller parts: git agent split does that for you, or you can stage some of the files, run git agent, and repeat. Loading the model with a larger context also helps.

Configuration

Changing settings

Run git agent config to change settings in a menu:

git-agent settings · ~/.config/git-agent/config.toml
? Setting:
> Provider        LM Studio · http://localhost:1234/v1
  Model           automatic: the model loaded on the server
  API key         none (local servers usually need none)
  Context length  automatic: asked from the server
  Output limit    16,000 tokens (sent only to Anthropic)
  Temperature     automatic: 0.4 on local servers
  Timeout         300 seconds
  Secrets         reject: don't commit or push them
  Excluded files  common lockfiles
  System prompt   default: Conventional Commits
  Done

Move with the arrow keys and press Enter to change a setting. git-agent saves each change to the config file right away. Esc goes back one step, and quits from the main menu. Excluded files and the system prompt open in your git editor. The menu writes the same commented file as git agent config init, so comments you added to the file yourself are not kept.

These commands work with the settings too:

git agent config            # change settings in a menu
git agent config init       # write a commented config file with the defaults
git agent config show       # print the effective settings
git agent models            # list models on the server; * marks the one git-agent uses

Where settings come from

Settings come from these sources, and later ones override earlier ones:

  1. Built-in defaults
  2. The config file: ~/.config/git-agent/config.toml (or $XDG_CONFIG_HOME/git-agent/config.toml)
  3. Environment variables
  4. Command-line flags

API keys come from --api-key, then GIT_AGENT_API_KEY, then the macOS keychain.

Settings

Key Flag Environment variable Default
base_url --base-url GIT_AGENT_BASE_URL http://localhost:1234/v1
model -m, --model GIT_AGENT_MODEL the model loaded on the server
(API key) --api-key GIT_AGENT_API_KEY the macOS keychain, never the file
context_length --context-length GIT_AGENT_CONTEXT_LENGTH reported by the server, or 4096
max_output_tokens --max-output-tokens GIT_AGENT_MAX_OUTPUT_TOKENS 16000
temperature -t, --temperature 0.4 on local servers, else the provider's default
on_secrets --on-secrets GIT_AGENT_ON_SECRETS reject (or warn)
timeout_secs 300
exclude common lockfiles
system_prompt Conventional Commits instructions
(file location) --config GIT_AGENT_CONFIG see above

Temperature

On local servers, git-agent uses a temperature of 0.4 unless you set another one, because a low temperature gives more predictable messages. Many cloud models accept only their default temperature and reject requests that set another one, so with a cloud provider git-agent sends a temperature only if you set one. When you switch to a cloud provider, the menu offers to stop sending it. Anthropic's API takes no temperature, so git-agent never sends one there.

Output limit

Anthropic's API needs a limit on how many tokens the model may write in one reply. git-agent asks for at most 16,000, which leaves current models plenty of room to think before they write the message. To change it, use "Output limit" in git agent config, max_output_tokens in the config file, or --max-output-tokens. Much higher limits can make a reply take longer than the timeout. OpenAI-compatible servers use their own limit.

Using another model for one run

Flags and environment variables apply to a single run:

git agent -m qwen/qwen3-8b
GIT_AGENT_BASE_URL=http://localhost:11434/v1 git agent -m llama3.2   # Ollama

Notes

  • Files matching exclude (by default Cargo.lock, package-lock.json and similar) appear in the list of changed files sent to the model, but their diffs don't. Deleted files are sent by name, without their old content.
  • git-agent removes reasoning blocks (<think>…</think>) and code fences around the model's reply.
  • Set NO_COLOR to turn off colored output.

Contributing

Contributions are welcome! Open an issue for bugs and feature ideas, or send a pull request. For larger changes, please open an issue first so we can discuss the approach.

Before you send a pull request

Make sure these pass:

cargo fmt --check
cargo clippy --all-targets
cargo test

The tests create temporary git repositories and don't need a running model server.

License

git-agent is licensed under the MIT License. By contributing, you agree that your contributions are licensed under the same terms.