## 概要
[[Lazygit]]でコミットメッセージを書くときに[[GitHub Copilot]]の補完機能で楽ができるかを試してみたくなったところ、以下の記事を見つけた。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://static.zenn.studio/images/logo-transparent.png" />
<span class="link-card-v2-site-name">Zenn</span>
</div>
<div class="link-card-v2-title">
Neovim × lazygit でコミットメッセージを楽に書く
</div>
<img class="link-card-v2-image" src="https://res.cloudinary.com/zenn/image/upload/s--9jgmZQYw--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:Neovim%2520%25C3%2597%2520%2520lazygit%2520%25E3%2581%25A7%25E3%2582%25B3%25E3%2583%259F%25E3%2583%2583%25E3%2583%2588%25E3%2583%25A1%25E3%2583%2583%25E3%2582%25BB%25E3%2583%25BC%25E3%2582%25B8%25E3%2582%2592%25E6%25A5%25BD%25E3%2581%25AB%25E6%259B%25B8%25E3%2581%258F%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:nabekou29%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2pMeDlwcWpWUGtnS2lVckVmYlVncWUyc2F6VmEwbVdVQ3B1X0U9czI1MC1j%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png?_a=BACAGSGT" />
<a href="https://zenn.dev/nabekou29/articles/neovim-lazygit-commit-message"></a>
</div>
AIを使ったコミットメッセージ生成に興味はなかったが、[[コミットテンプレート (Git)|コミットテンプレート]]を[[GitHub Copilot]]のプロンプトとして利用するという発想がなかったので、ちょっと試してみたくなった。
> [!hint]
> 動作イメージを知りたければ [[#動作の様子]] を最初に参照。
## 戦略
### 基本
[[Lazygit]]でコミットメッセージを編集する方法は大きく2種類ある。
1. [[TUI]]上で操作する
2. 外部エディタを立ち上げて操作する
1の方法ではできることが限られているため、2の方法を利用する。外部エディタには[[Neovim]]を使う。
### 詳細
`Shift+c` で[[Neovim]]が起動する[[バッファ (Vim)|バッファ]]にはルールがある。
- ファイルパスは `.git/COMMIT_EDITMSG`
- [[ファイルタイプ (Vim)|ファイルタイプ]]は `gitcommit`
これを利用して、以下2つの設定を行う。
- [[Neovim]]の[[copilot.lua]]を有効にする
- コミットメッセージバッファに最後に[[GitHub Copilot]]が必要なコンテキストをメッセージとして追記する
## [[Neovim]]の[[copilot.lua]]を有効にする
[[copilot.lua]]の設定で `opts.filetypes.gitcommit` に `true` を設定する。
```lua
{
opts = {
filetypes = {
gitcommit = true,
},
},
}
```
これで `gitcommit` の[[ファイルタイプ (Vim)|ファイルタイプ]]バッファに対しても[[copilot.lua]]が有効になる。
## コミットメッセージバッファにコンテキストを追加
`after/ftplugin/gitcommit.lua` を追加する。
```lua
-- COMMIT_EDITMSG を開いたときにステージ済み差分をコメントとして末尾へ追記する
local function is_commit_editmsg()
return vim.fn.expand("%:t") == "COMMIT_EDITMSG"
end
local function git_command(git_dir, repo_root, args)
local command = { "git", "--git-dir=" .. git_dir, "--work-tree=" .. repo_root }
vim.list_extend(command, args)
return vim.fn.systemlist(command)
end
local function resolve_git_paths()
local commit_path = vim.fn.expand("%:p")
local git_dir = vim.fn.fnamemodify(commit_path, ":h")
local repo_root = vim.fn.fnamemodify(git_dir, ":h")
if git_dir == "" or repo_root == "" then
return nil, nil
end
return git_dir, repo_root
end
local function fetch_recent_logs(git_dir, repo_root)
local logs = git_command(git_dir, repo_root, {
"log",
"-10",
"--pretty=%s",
})
if vim.v.shell_error ~= 0 then
return {}
end
return logs
end
local function should_prefer_japanese(log_lines)
if #log_lines == 0 then
return true
end
local re = vim.regex("\\v[ぁ-んァ-ン一-龠々〆ヵヶー]+")
for _, line in ipairs(log_lines) do
if re:match_str(line) then
return true
end
end
return false
end
local function fetch_staged_diff(git_dir, repo_root)
local diff = git_command(git_dir, repo_root, {
"diff",
"--cached",
})
if vim.v.shell_error ~= 0 or #diff == 0 then
return nil
end
return diff
end
local function build_header_lines(log_lines, use_japanese)
local header = {}
local has_logs = #log_lines > 0
if use_japanese then
table.insert(header, "# 以下の差分を参考にコミットメッセージを書いて。")
table.insert(header, "# コミットメッセージは日本語で書いてください。英語は禁止!")
table.insert(
header,
"# Conventional Commitを採用しています。直近のコミットメッセージを参考にしてください。"
)
table.insert(header, "# ")
if has_logs then
table.insert(header, "# 直近のコミットメッセージ(最新10件):")
end
else
table.insert(header, "# Write the commit message referring to the staged diff.")
table.insert(header, "# Must write the commit message in English.")
table.insert(header, "# We use Conventional Commit. Please refer to recent commit messages.")
table.insert(header, "# ")
if has_logs then
table.insert(header, "# Recent commit messages (latest 10):")
end
end
return header
end
local function build_log_lines(log_lines)
local lines = {}
for _, msg in ipairs(log_lines) do
if msg ~= "" then
table.insert(lines, "# - " .. msg)
end
end
if #lines > 0 then
table.insert(lines, "# ")
end
return lines
end
local function build_diff_lines(diff_lines)
local lines = { "# --- Staged Diff (for Copilot) ---" }
for _, line in ipairs(diff_lines) do
table.insert(lines, "# " .. line)
end
return lines
end
local function build_injected_lines(log_lines, diff_lines, use_japanese)
local lines = { "" }
vim.list_extend(lines, build_header_lines(log_lines, use_japanese))
vim.list_extend(lines, build_log_lines(log_lines))
vim.list_extend(lines, build_diff_lines(diff_lines))
return lines
end
if not is_commit_editmsg() then
return
end
if vim.b._commit_diff_injected then
return
end
-- 同一バッファでの重複挿入を防ぐ
vim.b._commit_diff_injected = true
local git_dir, repo_root = resolve_git_paths()
if git_dir == nil or repo_root == nil then
return
end
local log_lines = fetch_recent_logs(git_dir, repo_root)
local use_japanese = should_prefer_japanese(log_lines)
local diff_lines = fetch_staged_diff(git_dir, repo_root)
if diff_lines == nil then
return
end
vim.api.nvim_buf_set_lines(0, -1, -1, false, build_injected_lines(log_lines, diff_lines, use_japanese))
```
コードはすべて[[Codex CLI]]に委ねたので無駄な記述や謎の記述はあるかもしれない。『自己レビュー+修正』を3周させているので、そこそこはリファクタリングされている。はず。
## 動作の様子
[[🦉Toki]]に加えてみた変更で試してみる。以下が差分。
```diff
diff --git a/mnt/toki/toki.sh b/mnt/toki/toki.sh
index f5160d9..0a66824 100755
--- a/mnt/toki/toki.sh
+++ b/mnt/toki/toki.sh
@@ -46,6 +46,7 @@ Available targets
| go-sqlx | Go | - | Go | sqlx + mysql + air | golangci-lint | - |
| rust | Rust | - | Cargo | - | - | - |
| python | Python | Virtualenv | Pip | - | ruff | ruff |
+| uv | Python | uv | uv | - | ruff | ruff |
| nvim | Lua | Lua | | nvim | - | - |
| nvimapp | Lua | Neovim | lazy | - | - | - |
| bash | Bash | Bash | | - | - | - |
@@ -502,6 +503,30 @@ $ mise watch dev
exit 0
fi
+# -------------------------------------------
+# uv
+# -------------------------------------------
+if [[ $command == "uv" ]]; then
+ path="${1:?'pathは必須です'}"
+
+ mkdir -p "$path"
+ cd "$path"
+
+ git init
+ uv init --bare
+ uv add ruff
+
+ cp -r "${TEMPLATE_DIR}"/uv/* .
+
+ echo "
+🚀 Try
+
+$ cd ${path}
+$ mise watch dev
+"
+ exit 0
+fi
+
# -------------------------------------------
# nvim
# -------------------------------------------
```
[[Lazygit]]ですべて[[ステージング (Git)|ステージング]]してから、`Shift+c` を押してみる。
![[2025-12-06-20-51-42.avif|frame]]
*赤枠が今回追加された内容*
[[Conventional Commits]]のtypeを入力すると、あとは補完される。
![[2025-12-06-20-53-23.avif]]
自分の設定の問題かもしれないが、typeを入力しないと補完が出なかったり、精度が下がったりする。typeくらいは意識してもいいのかもしれない。
なお、最終的なコミットメッセージは以下のように修正したので、精度としては **『無いよりはマシ』** くらいのもの。
```
feat(toki): toki uvコマンド追加
```
過去のコミットメッセージ数を増やしたり、ルールをしっかり伝えれば精度は上がるかもしれないが、そこまでやる価値は感じていない。
## 思ったこと
正直、今のところは恩恵は感じない... がまずは1週間試してみる。
日本語のコミットメッセージはそこまで苦ではないため、コメント行に書いた日本語を英語に翻訳するような[[コミットテンプレート (Git)|コミットテンプレート]]の方がいいのかもしれない。