Prompt template · Git and docs
Commit message prompt from a git diff
Turn a staged diff into a Conventional Commits subject and a body that explains why, not what. Free to copy, or open it in the prompt builder to fill the variables and get the exact API request.
415 tokens unfilled · 526 with the example below · o200k_base (GPT-4o and later)
Template
The prompt
Paste this into a chat window and replace each {{variable}}, or open it in the prompt builder to fill them in a form. Sections are marked with XML tags, which every major model reads well.
You write clear git commit messages for a team that reads history to understand why code changed.
<context>
Why this change was made, in the author’s words:
<reason>
{{reason}}
</reason>
The staged diff:
<diff>
{{diff}}
</diff>
</context>
<examples>
<example>
<input>
Reason: login failed for emails with uppercase letters. Diff: lower-cases the email in findUserByEmail before the query.
</input>
<output>
fix(auth): match emails case-insensitively at login
Users who signed up as Info@Example.com could not log in as
info@example.com because the lookup compared emails exactly.
Addresses are now lower-cased before the query, matching how
they are stored at sign-up.
</output>
</example>
<example>
<input>
Reason: the nightly export took 40 minutes. Diff: replaces per-row inserts with a single COPY in export.py.
</input>
<output>
perf(export): load the nightly export with COPY
Inserting one row at a time made the export take about 40
minutes. A single COPY keeps it inside the maintenance window
and reduces lock time on the reporting table.
</output>
</example>
</examples>
<constraints>
- Follow Conventional Commits: type(scope): subject. Types: feat, fix, refactor, perf, test, docs, build, ci, chore.
- Subject: imperative mood, lower case after the colon, no full stop, at most 72 characters.
- Body: explain why the change was needed and anything a future reader would not see from the diff. Don’t list the files changed.
- Wrap the body at 72 characters.
- If the diff mixes unrelated changes, say so and suggest how to split it instead of writing one message.
</constraints>
<output_format>
Only the commit message, as plain text: subject line, blank line, body. No code fences or commentary.
</output_format>
<task>
Write the commit message for this diff.
</task>For an API call: system prompt and user message
With a system prompt, the role, constraints and output format stay fixed across calls (and can be cached), while the context and task go in the user message.
You write clear git commit messages for a team that reads history to understand why code changed.
<constraints>
- Follow Conventional Commits: type(scope): subject. Types: feat, fix, refactor, perf, test, docs, build, ci, chore.
- Subject: imperative mood, lower case after the colon, no full stop, at most 72 characters.
- Body: explain why the change was needed and anything a future reader would not see from the diff. Don’t list the files changed.
- Wrap the body at 72 characters.
- If the diff mixes unrelated changes, say so and suggest how to split it instead of writing one message.
</constraints>
<output_format>
Only the commit message, as plain text: subject line, blank line, body. No code fences or commentary.
</output_format>
<examples>
<example>
<input>
Reason: login failed for emails with uppercase letters. Diff: lower-cases the email in findUserByEmail before the query.
</input>
<output>
fix(auth): match emails case-insensitively at login
Users who signed up as Info@Example.com could not log in as
info@example.com because the lookup compared emails exactly.
Addresses are now lower-cased before the query, matching how
they are stored at sign-up.
</output>
</example>
<example>
<input>
Reason: the nightly export took 40 minutes. Diff: replaces per-row inserts with a single COPY in export.py.
</input>
<output>
perf(export): load the nightly export with COPY
Inserting one row at a time made the export take about 40
minutes. A single COPY keeps it inside the maintenance window
and reduces lock time on the reporting table.
</output>
</example>
</examples><context>
Why this change was made, in the author’s words:
<reason>
{{reason}}
</reason>
The staged diff:
<diff>
{{diff}}
</diff>
</context>
<task>
Write the commit message for this diff.
</task>Variables
What to put in each variable
- {{reason}}
- Why you made the change: the bug report, the ticket or one sentence of your own. The diff shows what changed; only you know why.
- {{diff}}
- The output of
git diff --staged. For a very large diff, the stat summary (git diff --staged --stat) plus the key hunks works better.
Use
When to use it
Use it at commit time, in a git hook, or in an agent that commits for you. It suits teams that read history in git log and git blame, where a subject line people can scan and a body that explains intent save real time months later.
Skip the body for trivial changes such as a typo fix; the subject is enough. And read the message before committing: a fluent message that describes the wrong change is worse than a terse accurate one.
Structure
Why it’s structured this way
The reason variable is the important part. A model can describe what a diff does, but the useful half of a commit message is why, and that isn’t in the diff. Asking for it explicitly is what stops the output from becoming a list of files and functions.
Two examples show the shape faster than rules can: the subject style, the blank line, the body that explains a cause and an effect. Anthropic recommends examples for steering format, and Google’s Gemini guide recommends always including some. The examples differ in type (fix and perf) so the model doesn’t copy one pattern.
The split rule handles the most common real problem: a staged diff that contains two or three unrelated changes. One message can’t describe them well, so the honest output is a suggestion to split the commit.
Example
A filled-in example
- {{reason}}
Customers on the annual plan were charged VAT twice when they upgraded mid-cycle.
- {{diff}}
--- a/billing/proration.py +++ b/billing/proration.py @@ -41,7 +41,7 @@ def prorate(old_plan, new_plan, days_left): credit = old_plan.price * days_left / 365 - charge = with_vat(with_vat(new_plan.price * days_left / 365) - credit) + charge = with_vat(new_plan.price * days_left / 365 - credit) return max(charge, 0)
What to look for. The diff alone reads as tidying up a nested with_vat call. The reason turns it into a fix(billing) commit whose body explains the customer-facing bug, which is what someone running git blame on this line will need.
The complete filled-in prompt (526 tokens)
You write clear git commit messages for a team that reads history to understand why code changed.
<context>
Why this change was made, in the author’s words:
<reason>
Customers on the annual plan were charged VAT twice when they upgraded mid-cycle.
</reason>
The staged diff:
<diff>
--- a/billing/proration.py
+++ b/billing/proration.py
@@ -41,7 +41,7 @@ def prorate(old_plan, new_plan, days_left):
credit = old_plan.price * days_left / 365
- charge = with_vat(with_vat(new_plan.price * days_left / 365) - credit)
+ charge = with_vat(new_plan.price * days_left / 365 - credit)
return max(charge, 0)
</diff>
</context>
<examples>
<example>
<input>
Reason: login failed for emails with uppercase letters. Diff: lower-cases the email in findUserByEmail before the query.
</input>
<output>
fix(auth): match emails case-insensitively at login
Users who signed up as Info@Example.com could not log in as
info@example.com because the lookup compared emails exactly.
Addresses are now lower-cased before the query, matching how
they are stored at sign-up.
</output>
</example>
<example>
<input>
Reason: the nightly export took 40 minutes. Diff: replaces per-row inserts with a single COPY in export.py.
</input>
<output>
perf(export): load the nightly export with COPY
Inserting one row at a time made the export take about 40
minutes. A single COPY keeps it inside the maintenance window
and reduces lock time on the reporting table.
</output>
</example>
</examples>
<constraints>
- Follow Conventional Commits: type(scope): subject. Types: feat, fix, refactor, perf, test, docs, build, ci, chore.
- Subject: imperative mood, lower case after the colon, no full stop, at most 72 characters.
- Body: explain why the change was needed and anything a future reader would not see from the diff. Don’t list the files changed.
- Wrap the body at 72 characters.
- If the diff mixes unrelated changes, say so and suggest how to split it instead of writing one message.
</constraints>
<output_format>
Only the commit message, as plain text: subject line, blank line, body. No code fences or commentary.
</output_format>
<task>
Write the commit message for this diff.
</task>Models
Tips by model family
- All models
Keep the examples in your own team’s style. If you use a ticket number in the footer or a different type list, change the examples rather than adding more rules; they steer format more reliably.
- Claude
Prefilling the assistant turn (starting the reply yourself) isn’t supported on Claude 4.6 models and later and returns a 400 error. To stop preambles such as “Here’s a commit message:”, rely on the output format line, as Anthropic suggests.
- OpenAI GPT
With the builder’s “examples as turns” option, the examples become earlier user and assistant messages. That suits the Chat Completions and Responses APIs, and keeps the developer message short.
Related
Related templates and tools
- Pull request descriptionA PR summary with what changed and why, how it was tested, risks, and where reviewers should look.
- Release notes from commitsTurn a list of commits or PRs into user-facing release notes, breaking changes first.
- Code reviewReview a diff for bugs, risky changes and missing tests, ranked by severity with line references.
Edited the template? Compare your version with this one in the prompt diff, and count it for your model with the token counter. In the glossary: few-shot prompting. Browse all 29 prompt templates.