- Git
- 技術 💻
事故を防ぐGitコマンド8選|reflog・rerere・force-with-lease
Gitって、日常で使うのは10個くらいのコマンドなんですよね。でもその外側に、知っていると事故が減るものがけっこう眠っています。
今回は、実務で「ほぉー」となったものを8つ集めました。消したコミットの復活から、二度と同じコンフリクトを解かない設定まで。1つでも知らなかったら儲けものです。異論・追加も大歓迎です。
① 消したコミットは、だいたい戻せます
git reset --hard で作業が消えた。このとき、まだ諦めなくて大丈夫なことが多いんです。Gitは、HEADが動いたすべての履歴を記録しています。
git reflog
a3f9c21 HEAD@{0}: reset: moving to HEAD~3
7d2b8e4 HEAD@{1}: commit: 認証処理を追加
...
消えたと思ったコミットが、ここに残っているんですよね。
git reset --hard 7d2b8e4
これで戻ります。保持期間は既定で、到達可能なものが90日、到達不可能なものが30日です。その間なら救出できるということですね。「消してしまった」と言われたら、まずreflogを見る。これだけで救われる場面は多いです。
② バグが入ったコミットを、自動で特定できます
「先月は動いていたのに、いまは壊れている」。コミットが200個ある中から、原因を探す。手作業だと絶望的ですよね。
git bisect は、二分探索で犯人を特定してくれます。200個なら8回程度の確認で済む計算です。ただ、本当の「ほぉー」はここからで。判定を自動化できるんです。
git bisect start HEAD v1.2.0
git bisect run ./check.sh
check.sh が終了コード0を返せば正常、1〜127(125を除く)なら異常。Gitがその判定を使って、自動で探索を進めてくれます。テストコマンドをそのまま渡すこともできますね。
git bisect run npm test -- auth.test.js
放っておくと、犯人のコミットハッシュが表示されて終わります。判定条件さえ書ければ、探索は完全に任せられるんですよね。
③ 「あのコード、いつ消えたのか」を検索できます
昔あったはずの処理が、見当たらない。こういうとき、コミットメッセージを検索しても見つからないんですよね。消したときのメッセージに、その単語が入っているとは限らないので。変更内容そのものを検索する方法があります。
git log -S "calculateTax" --oneline
これは「calculateTax という文字列の出現回数が変わったコミット」を探します。つまり追加されたコミットと、削除されたコミットが出てくるんですよね。正規表現で探したいときは、こちら。
git log -G "calc.*Tax" --oneline
さらに、特定の行の変遷だけを追うこともできます。
git log -L 45,60:src/payment.js
その範囲が、いつ誰にどう変えられてきたかが順に出てきます。「なぜこの実装になっているのか」を調べるとき、これが速いんです。
④ 同じコンフリクトを、二度解かなくて済みます
長期のブランチをrebaseすると、同じ箇所のコンフリクトを何度も解くことになりますよね。これ、記録して再利用できるんです。
git config --global rerere.enabled true
有効にすると、コンフリクトの解決内容が記録されます。次に同じ形のコンフリクトが起きたとき、Gitが自動で同じ解決を適用してくれるんですよね。rerereは Reuse Recorded Resolution の略。「記録した解決の再利用」そのままの名前です。
注意点もあります。過去の解決が自動で当てられるので、状況が変わっていても同じ解決をしてしまうことがあるんですよね。自動適用されたときは、内容を確認してから進める。ここは意識しておきたいところです。
⑤ フォーマット一括変更で、blameを汚さない方法があります
フォーマッタを導入して、全ファイルを整形した。その後 git blame を見ると、全部の行が「フォーマット適用」のコミットになってしまう。誰が何を書いたのか、まったく追えなくなりますよね。
これ、特定のコミットをblameから除外できます。リポジトリのルートにファイルを置いて、除外したいコミットハッシュを並べます。
# .git-blame-ignore-revs
# 全ファイルにPrettierを適用
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
そして設定します。
git config blame.ignoreRevsFile .git-blame-ignore-revs
これで、blameがそのコミットを飛ばしてくれます。本来の作者が表示されるんですよね。GitHubもこのファイルを認識するので、Web上のblame表示にも反映されます。フォーマッタやリンタを後から導入するときは、同時にこのファイルを作るのが定石かなと思います。
⑥ force pushには、安全な形があります
rebase後のpushで、--force を使っていませんか。これ、その間に誰かがpushしていたら、そのコミットが消えます。安全な代替があるんですよね。
git push --force-with-lease
「自分が最後に取得した状態から、リモートが変わっていなければ上書きする」という条件付きの強制pushです。誰かが先にpushしていたら、失敗して止まってくれます。
さらに厳密にするなら。--force-with-lease にも穴があって、バックグラウンドで自動fetchが走っていると、条件をすり抜けることがあるんです。取得だけして中身を見ていない状態でも「知っている」と判定されてしまうので。これを塞ぐオプションがあります。
git push --force-with-lease --force-if-includes
「リモートの変更を、実際に自分の作業に取り込んでいるか」まで確認してくれます。チームで運用するなら、--force を禁止してこちらを標準にする価値があります。
⑦ stashしないで、別ブランチの作業ができます
作業中に「急ぎで別の修正を」と言われる。stashして、ブランチを切り替えて、直して、戻して、stash popして……。この往復、地味に事故りますよね。作業ディレクトリごと増やせます。
git worktree add ../hotfix main
同じリポジトリを共有したまま、別の場所に独立した作業ディレクトリができます。エディタで両方を同時に開けますし、ビルド成果物も干渉しません。stashも不要です。
git worktree list # 一覧
git worktree remove ../hotfix # 削除
複数のAIエージェントを並列で動かすときにも、この分離が効いてくるんですよね。
⑧ レビュー指摘の修正を、正しい位置に自動で入れる
レビューで「3つ前のコミットのここ、直して」と言われた。新しく「レビュー指摘対応」というコミットを積んでいませんか。履歴が汚れていくやつですね。修正先を指定できます。
git commit --fixup=7d2b8e4
すると fixup! 認証処理を追加 というコミットが作られます。そのあと、こう実行すると。
git rebase -i --autosquash 7d2b8e4^
fixupコミットが、自動で対象コミットの直後に並び替えられて、統合されます。手作業で順番を入れ替える必要がないんですよね。既定で有効にしておくこともできます。
git config --global rebase.autoSquash true
レビュー指摘が複数あっても、それぞれ対象を指定して積んでおけば、最後に一度で綺麗になります。
番外編:リポジトリが重いときに
最後に、大規模リポジトリ向けのものを2つだけ。
必要な分だけ取得する
git clone --filter=blob:none <url>
ファイルの中身を最初にすべて取得せず、必要になった時点で取りに行く方式です。履歴が巨大なリポジトリだと、cloneの時間がまったく変わります。特定のディレクトリだけ作業対象にすることもできますね。
git sparse-checkout set src/frontend
定期メンテナンスを任せる
git maintenance start
コミットグラフの更新や不要オブジェクトの整理を、バックグラウンドで定期実行してくれます。git status や git log が遅くなってきたら、試す価値があります。
よくいただくご質問
Q. どれから覚えるといいですか?
A. ①のreflogと⑥のforce-with-leaseかなと思います。どちらも事故を防ぐ側なので、優先度が高いんですよね。
Q. rerereは常に有効にしていいですか?
A. 便利ですが、自動で適用されたときの確認は必要です。特に長期ブランチだと、状況が変わっていることがあるので。
Q. worktreeとブランチ切り替え、どう使い分けますか?
A. 中断したくない作業があるときはworktree、そうでなければ普通の切り替えで十分かなと思います。増やしすぎると管理が煩雑になるので。
Q. 未経験からでも、ここまで使えるようになりますか?
A. なります。全部が必要なわけではないので、困った場面で1つずつ覚えていく形で十分です。
事故を防ぐものから、覚えると得です
今日の8つを並べてみて思ったのが、これでした。Gitのコマンドは、便利にするものと事故を防ぐものに分かれるんですよね。そして後者の方が、知らなかったときの損失が大きい。reflogを知らずに諦めた作業、force pushで消えた誰かのコミット。取り返しがつかないものもあります。
だから優先順位をつけるなら、守りの方からかなと思います。
みなさんの「これで助かった」も、ぜひ教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。