GEMINI LABJP
SIRI — WWDC 2026 confirms the revamped Siri runs on a Google Gemini model, though it won't ship in the EU at iOS 27 due to the DMAFLASH3.5 — Gemini 3.5 Flash is now GA, the top Flash model for sustained frontier performance on agentic and coding tasksIMAGE-GA — Gemini 3.1 Flash Image and 3.1 Pro Image are GA as native visual models; the preview versions shut down Jun 25MANAGED-AGENTS — Managed Agents launch in public preview in the Gemini API, running autonomous agents in Google-hosted isolated Linux sandboxesFILE-SEARCH — File Search now supports multimodal search, with native image embedding and retrieval via gemini-embedding-2DEPRECATION — gemini-3.1-flash-image-preview and gemini-3-pro-image-preview shut down Jun 25 — migrate to the GA models soonSIRI — WWDC 2026 confirms the revamped Siri runs on a Google Gemini model, though it won't ship in the EU at iOS 27 due to the DMAFLASH3.5 — Gemini 3.5 Flash is now GA, the top Flash model for sustained frontier performance on agentic and coding tasksIMAGE-GA — Gemini 3.1 Flash Image and 3.1 Pro Image are GA as native visual models; the preview versions shut down Jun 25MANAGED-AGENTS — Managed Agents launch in public preview in the Gemini API, running autonomous agents in Google-hosted isolated Linux sandboxesFILE-SEARCH — File Search now supports multimodal search, with native image embedding and retrieval via gemini-embedding-2DEPRECATION — gemini-3.1-flash-image-preview and gemini-3-pro-image-preview shut down Jun 25 — migrate to the GA models soon
Articles/API / SDK
API / SDK/2026-04-20Intermediate

Building a Git Commit Message Generator with Gemini API — A Python Developer's Guide

Build a Python tool that reads git diffs and generates meaningful commit messages automatically using the Gemini API. Includes working code, clipboard integration, and Git hook setup.

gemini-api285python132git2developer-tools9automation57commit-message

We've all been there: you just made a small but meaningful change, and instead of writing a proper commit message, you type fix and move on. Later, reading through git log feels like deciphering ancient hieroglyphs.

Building a commit message generator with the Gemini API turns out to be a surprisingly satisfying solution to this problem. The core idea is simple — pass git diff --staged output to Gemini and ask for a Conventional Commits-style message. In this guide, I'll walk through the implementation from a minimal working script to a practical daily-use tool.

What the Finished Tool Looks Like

Here's the user experience once everything is set up:

# Stage your changes
git add src/auth/login.py
 
# Run the generator
python commit_gen.py
 
# Output:
# Suggested commit message:
# feat(auth): add rate limiting to login endpoint to prevent brute force attacks

Gemini reads the diff, understands the intent of the change, and proposes a descriptive message in the Conventional Commits format. The accuracy is good enough that I accept the suggestion as-is roughly 80% of the time.

Prerequisites

  • Python 3.9 or higher
  • A Gemini API key (free from Google AI Studio)
  • The google-genai package
pip install google-genai

Set your API key as an environment variable:

export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"

The Minimal Implementation

Start with something that works before adding polish:

import subprocess
import os
from google import genai
 
def get_staged_diff() -> str:
    """Retrieve the current staged diff from Git."""
    result = subprocess.run(
        ["git", "diff", "--staged"],
        capture_output=True,
        text=True,
        encoding="utf-8"
    )
    if result.returncode \!= 0:
        raise RuntimeError(f"git diff failed: {result.stderr}")
    return result.stdout
 
def generate_commit_message(diff: str) -> str:
    """Send the diff to Gemini API and return a commit message."""
    if not diff.strip():
        return "No staged changes found."
 
    client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
 
    prompt = f"""Read the following git diff and suggest a single commit message in Conventional Commits format.
 
Format: <type>(<scope>): <description>
Choose type from: feat / fix / refactor / docs / test / chore
Keep the description in English, under 50 characters.
Output the commit message only — no additional explanation.
 
---
{diff[:3000]}
---"""
 
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt
    )
    return response.text.strip()
 
if __name__ == "__main__":
    diff = get_staged_diff()
    message = generate_commit_message(diff)
    print(f"Suggested commit message:\n{message}")

Two design decisions worth explaining here.

First, the diff[:3000] truncation. Feeding an enormous diff doesn't improve message quality and drives up token costs. For the purpose of summarizing what changed, the first few thousand characters capture the essential information in almost every case.

Second, the choice of gemini-2.5-flash. This task values speed and cost-efficiency over deep reasoning. Flash handles commit message generation perfectly well, and since you might run this dozens of times a day, keeping costs low matters.

Practical Improvement 1: Copy to Clipboard

It saves a few seconds to have the suggested message land directly in your clipboard:

import sys
 
def copy_to_clipboard(text: str) -> bool:
    """Copy text to the system clipboard, handling macOS/Linux/Windows."""
    try:
        if sys.platform == "darwin":
            subprocess.run(["pbcopy"], input=text.encode(), check=True)
        elif sys.platform == "linux":
            subprocess.run(["xclip", "-selection", "clipboard"],
                           input=text.encode(), check=True)
        elif sys.platform == "win32":
            subprocess.run(["clip"], input=text.encode(), check=True)
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False
 
if __name__ == "__main__":
    diff = get_staged_diff()
    message = generate_commit_message(diff)
    print(f"Suggested commit message:\n{message}")
 
    if copy_to_clipboard(message):
        print("\n✓ Copied to clipboard")

On Linux you may need to install xclip separately. On macOS, pbcopy is available out of the box.

Practical Improvement 2: Confirm Before Committing

Going one step further, you can confirm the message and run git commit directly:

def confirm_and_commit(message: str) -> None:
    """Prompt the user to accept, edit, or reject the suggested message."""
    print(f"\nProposed message: {message}")
    answer = input("Use this message? [Y/n/e(dit)]: ").strip().lower()
 
    if answer in ("", "y"):
        result = subprocess.run(
            ["git", "commit", "-m", message],
            capture_output=True,
            text=True
        )
        if result.returncode == 0:
            print("✓ Committed successfully")
        else:
            print(f"✗ Commit failed: {result.stderr}")
    elif answer == "e":
        edited = input(f"Edit message [{message}]: ").strip()
        if edited:
            confirm_and_commit(edited)
    else:
        print("Cancelled.")

The three-option flow (Y to accept, e to edit, n to cancel) keeps things quick without removing human oversight.

Error Handling Essentials

For a tool you'll rely on daily, handle the failure cases upfront:

from google.genai import errors as genai_errors
 
def generate_commit_message(diff: str) -> str:
    if not diff.strip():
        return ""
 
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise ValueError("GEMINI_API_KEY environment variable is not set")
 
    client = genai.Client(api_key=api_key)
 
    try:
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=build_prompt(diff)
        )
        return response.text.strip()
    except genai_errors.APIError as e:
        raise RuntimeError(f"Gemini API error: {e}")

The explicit API key check avoids cryptic error messages when the environment variable isn't set — a small thing that saves real debugging time.

Setting Up as a Git Hook

For maximum convenience, wire this into Git's prepare-commit-msg hook so it triggers automatically on every git commit:

#\!/bin/bash
# .git/hooks/prepare-commit-msg
 
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
 
# Only fire for empty commits (not -m, -c, merge, etc.)
if [ -z "$COMMIT_SOURCE" ]; then
    GENERATED=$(python /absolute/path/to/commit_gen.py --output-only 2>/dev/null)
    if [ -n "$GENERATED" ]; then
        echo "$GENERATED" > "$COMMIT_MSG_FILE"
    fi
fi

Make it executable:

chmod +x .git/hooks/prepare-commit-msg

One common pitfall: the hook runs in a different working context than your shell, so use an absolute path to the Python script. Relative paths are a silent failure waiting to happen.

Add an --output-only flag to your script to support this integration:

import argparse
 
parser = argparse.ArgumentParser()
parser.add_argument("--output-only", action="store_true")
args = parser.parse_args()
 
diff = get_staged_diff()
message = generate_commit_message(diff)
 
if args.output_only:
    print(message)
else:
    confirm_and_commit(message)

Where to Go From Here

The tool as described handles the vast majority of everyday commits. If you want to extend it, two natural next steps come to mind.

For large refactors where the diff runs into thousands of lines, chunking the diff and summarizing each chunk before the final message generation keeps quality consistent.

For team use, you might want to encode project-specific conventions into the prompt — custom scopes, issue tracker prefixes like JIRA-1234, or stricter length limits for automated changelog generation.

If you find yourself wanting more structure — like generating type, scope, and description as separate fields — the Gemini API's structured output capability makes that straightforward. The Gemini Structured Output Production Guide is a good next read once you have this baseline tool working.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-05-17
Auto-generating Japanese and English Release Notes from git log with Gemini API — A Real Implementation from Beautiful HD Wallpapers v2.1.0
I realized I was spending 1–2 hours per release writing notes in multiple languages. Here's how I automated that with Gemini API and git log — tested on Beautiful HD Wallpapers v2.1.0, with code you can run today.
API / SDK2026-04-17
Auto-Generate Code Documentation with Gemini API: README, JSDoc, and OpenAPI Specs in Python
Learn how to use Gemini API to automatically generate README files, JSDoc comments, and OpenAPI specs from your codebase. Python scripts included — eliminate the documentation backlog with AI.
API / SDK2026-05-03
Automate Contact Form Handling with Gemini API — Classification, Priority Scoring & Slack Alerts
Build a Python system that automatically classifies incoming contact form submissions using Gemini API, scores their priority, and sends structured Slack notifications — ready to deploy today.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →