はじめに
git 2.54 がリリースされました
個人的に気になったコマンドを紹介していきます!
git history(実験的コマンド)
背景:これまでの問題
コミットメッセージにタイポがあったとき、従来は git rebase -i を使っていました。
# 3つ前のコミットのメッセージを直したいだけなのに…
$ git rebase -i HEAD~3
するとエディタが開いて、こういう to-do リストが出てきます:
pick a1b2c3 Add login feature
pick d4e5f6 Fix bug in authorizaton # ← typo を直したい
pick g7h8i9 Update README
d4e5f6 を reword に変えて保存 → 別のエディタが開いてメッセージを編集 → rebase 完了、という手順が必要でした。さらに rebase 中はワーキングツリーの状態が変わるため、別作業と混在するとリスクがあります。
git history reword:メッセージだけ直す
$ git history reword d4e5f6
エディタが開くので、メッセージを修正して保存するだけ。それだけです。
ワーキングツリーにもインデックスにも一切触れません。
ポイント: そのコミットより後の全コミット(子孫)のハッシュも自動で書き換わります。
Before:
A --- B(typo) --- C --- D ← main
After:
A --- B'(fixed) --- C' --- D' ← main
git history split:1コミットを2つに分割する
たとえば、こういう「まとめすぎた」コミットがあるとします:
$ git log --oneline
a1b2c3 Add user model and fix unrelated CSS bug # ← 2つの変更が混在
これを分割したい場合:
$ git history split a1b2c3
git add -p と同じ UI が出てきます:
diff --git a/user.rb b/user.rb
new file mode 100644
+class User ...
(1/2) Stage this hunk? [y,n,q,?] y # ← User model の変更を選択
diff --git a/style.css b/style.css
+.button { color: red; }
(2/2) Stage this hunk? [y,n,q,?] n # ← CSS の変更は選択しない
結果として:
Before:
A --- [user model + CSS fix] --- B
After:
A --- [user model] --- [CSS fix] --- B
選択したハンクが「新しい親コミット」、残りが「元のコミット」になります。
制限事項(重要)
# ❌ マージコミットを含む履歴には使えない
$ git history reword abc123
error: history rewrites across merge commits are not supported
# ❌ コンフリクトが発生するケースも拒否される
# → そういう複雑なケースは従来通り git rebase -i を使う
設定ファイルベースの Hooks
背景:これまでの問題
従来の hook は リポジトリごとに .git/hooks/ にスクリプトを置く必要がありました。
my-project/.git/hooks/pre-commit # このリポジトリにしか効かない
other-project/.git/hooks/pre-commit # 同じスクリプトをまたコピー…
複数リポジトリで同じ hook を使いたいとき、コピーするか core.hooksPath で共有ディレクトリを指定するかしかなく、「リポジトリAにはlinter、リポジトリBにはlinter+secrets検出」という柔軟な組み合わせができませんでした。
新しい書き方
~/.gitconfig(全リポジトリに適用)に書く:
[hook "linter"]
event = pre-commit
command = ~/bin/linter --cpp20
[hook "no-leaks"]
event = pre-commit
command = ~/bin/leak-detector
これだけで、git commit するたびに どのリポジトリでも 2つのコマンドが順番に実行されます。
スコープの使い分け
| 設定ファイル | 適用範囲 |
|---|---|
/etc/gitconfig |
全ユーザー・全リポジトリ |
~/.gitconfig |
自分の全リポジトリ |
.git/config |
そのリポジトリのみ |
たとえば、linter はグローバルに、secrets検出はあるリポジトリだけに適用する、という構成が可能になります。
確認・無効化
# 現在有効な hook の一覧を確認
$ git hook list pre-commit
global linter ~/bin/linter --cpp20
local no-leaks ~/bin/leak-detector
# 特定リポジトリだけ無効化(設定を削除せずに)
# .git/config に追記
[hook "linter"]
enabled = false
enabled = false にするだけで、~/bin/linter の設定は残しつつそのリポジトリだけ skip できます。
従来の .git/hooks/pre-commit スクリプトとの関係
新旧は 共存できます。実行順は以下の通りです:
1. 設定ファイルで定義した hook(上から順に)
2. .git/hooks/pre-commit スクリプト(最後)
既存の hook は何も変更しなくてもそのまま動き続けます。
最後に
CUI はいいぞ