コードを書いていると、こんな場面に心当たりはないでしょうか。「ちょっとした修正なのにコミットメッセージを考えるのが億劫で、ついつい fix や update で済ませてしまう」。後からgit logを見返したとき、何をしたのかまったくわからなくて困った、という経験です。
Gemini API を使えば、このジレンマをかなり楽に解消できます。差分(git diff)を渡すだけで、変更内容を的確に言語化したコミットメッセージを提案してくれるツールを Python で作れます。今回は、実際に私が個人開発で使っているシンプルなスクリプトをベースに、ゼロから解説します。
完成イメージと使い方
まず完成形を見ておきましょう。このツールを使うと、こんな流れになります。
# ステージングエリアに変更を追加
git add src/auth/login.py
# ツールを実行
python commit_gen.py
# 出力例:
# Suggested commit message:
# feat(auth): add rate limiting to login endpoint to prevent brute force attacksgit diff --staged の出力を Gemini API に渡して、Conventional Commits 形式のメッセージを提案させる、というシンプルな仕組みです。
必要なものと事前準備
- Python 3.9 以上
- Gemini API キー(Google AI Studio で無料取得可能)
google-genaiパッケージ
pip install google-genai環境変数に API キーを設定しておきます。
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"基本実装 — まず動くものを作る
最小限のコードから始めましょう。
import subprocess
import os
from google import genai
def get_staged_diff() -> str:
"""ステージングエリアの差分を取得する"""
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:
"""Gemini API でコミットメッセージを生成する"""
if not diff.strip():
return "No staged changes found."
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
prompt = f"""以下の git diff を読み取り、Conventional Commits 形式のコミットメッセージを1行で提案してください。
形式: <type>(<scope>): <description>
type は feat / fix / refactor / docs / test / chore から選ぶ。
説明は英語で、50文字以内に収める。
余計な説明は不要で、コミットメッセージだけを出力してください。
---
{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}")ポイントが2つあります。
1つ目は diff[:3000] で差分を切り詰めている点です。大量の差分をそのまま渡すと、トークン消費が増えてコストがかさむ上に、モデルの注意が散漫になりがちです。コミットメッセージの生成には最初の数千文字で十分なことがほとんどです。
2つ目は gemini-2.5-flash を選んでいる点です。このタスクは推論の深さより速度とコストが重要なので、Flash モデルで十分です。毎日何十回もコミットする場合でも料金を気にせず使えます。
実用的な改良 1:クリップボードにコピー
提案されたメッセージをそのままコピーできると便利です。
import subprocess
import sys
def copy_to_clipboard(text: str) -> bool:
"""OSに応じてクリップボードにコピーする"""
try:
if sys.platform == "darwin": # macOS
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
# main 部分を更新
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")macOS を使っている私は pbcopy がそのまま使えて重宝しています。Linux では xclip のインストールが必要な場合があります。
実用的な改良 2:確認してからコミット
提案されたメッセージを確認してから実際に git commit まで実行できると、もう一ステップ省けます。
def confirm_and_commit(message: str) -> None:
"""確認後にコミットを実行する"""
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.")Y で確定、e で編集、n でキャンセル、という3択にしました。実際に使ってみると、提案をそのまま使えるケースが8割くらいあります。
エラーハンドリングを忘れずに
本番で使うなら、最低限これだけは入れておきましょう。
import os
from google import genai
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}")GEMINI_API_KEY が未設定のまま実行してしまうと、わかりにくいエラーが出ることがあります。明示的にチェックするだけで、デバッグ時間を大幅に節約できます。
Git フックとして自動実行する
毎回手動で実行するのが面倒なら、Git フックとして組み込む方法もあります。prepare-commit-msg フックを使うと、git commit したときに自動でエディタにメッセージを挿入できます。
#\!/bin/bash
# .git/hooks/prepare-commit-msg
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
# 空のコミット(-m なし)のときだけ発動
if [ -z "$COMMIT_SOURCE" ]; then
GENERATED=$(python /path/to/commit_gen.py --output-only 2>/dev/null)
if [ -n "$GENERATED" ]; then
echo "$GENERATED" > "$COMMIT_MSG_FILE"
fi
fi--output-only フラグをスクリプトに追加して、メッセージだけを標準出力するモードを用意しておくと連携しやすくなります。
フックとして使う場合、スクリプトへのパスを絶対パスにすることを忘れがちなポイントです。相対パスで書いてしまってフックが動かない、という罠には私も一度ハマりました。
全体を振り返って
最後に整理しておくと、このツールの要点は次の通りです。
git diff --stagedの出力を Gemini API に渡すだけの、シンプルなアーキテクチャ- Flash モデルを選ぶことでコスト効率を最大化
- 差分の切り詰め(3,000文字前後)で安定したレスポンス品質を確保
- エラーハンドリングを最初から入れることで実用性を担保
次のステップとしては、差分の量が多いときに自動でチャンクに分割して要約してから渡す、といった処理を加えると、大規模なリファクタリングでも対応できるようになります。ぜひご自身のワークフローに合わせてカスタマイズしてみてください。
Gemini API の structured output 機能を使って、type・scope・description を別々に生成させる方法に興味がある方には、Gemini API の Structured Output 完全ガイドもあわせてご覧いただけると理解が深まるかと思います。