なぜGitの履歴は重要なのか?
15人の開発者が参加する大規模なプロジェクトを想像してみてください。毎日、数十ものPull Requestがマージされます。全員がデフォルトのMergeコマンドを使用すると、Gitのグラフ(Network Graph)はすぐに複雑に絡み合った「スパゲッティ状態」になってしまいます。こうなると、バグの追跡や特定の変更箇所の特定は、まるでもみ殻の中から針を探すような作業になってしまいます。
mainブランチの最新コードを自分のfeatureブランチに取り込む必要があるとき、通常は2つの選択肢に直面します:Merge または Rebase。どちらも同じ問題を解決しますが、履歴に残る形は全く異なります。
- Merge: 2つのブランチを統合し、新しい「マージコミット」を作成します。この方法は、失敗したマージや不要なコードも含め、履歴を完全にそのまま保持します。
- Rebase: 自分のコミットをすべてターゲットブランチの先端に移動させることで、履歴を「書き換え」ます。その結果、履歴は一本の直線になり、非常に読みやすくなります。
私は以前、非常に厳格なテックリードと一緒に仕事をしたことがあります。彼は、プロダクションに反映する前にすべてのコミットを整理することを要求しました。最初は面倒に感じましたが、プロジェクトが1,000コミットを超えたとき、ようやく「綺麗な」履歴の価値を理解しました。おかげで git bisect コマンドを使い、バグの原因となったコミットを数時間ではなく数分で見つけることができたのです。
操作前の環境準備
ローカルのベースコードが古い状態でRebaseを開始してはいけません。まずはサーバーと同期し、不要な衝突を避けることが第一歩です。
# サーバーから最新情報を取得
git fetch origin
# 作業中の機能ブランチに切り替え
git checkout feature-xyz
違いを観察するためのちょっとしたコツとして、エイリアス git adog を設定することをお勧めします。このコマンドを使うと、ターミナル上でコミットグラフをツリー形式で表示でき、デフォルトのログコマンドよりもはるかに直感的です。
git config --global alias.adog "log --all --decorate --oneline --graph"
詳細比較:安全なMergeと洗練されたRebase
1. Git Merge:「現場をそのまま残す」選択
Mergeを使って main を feature に統合すると、Gitは3方向マージ(3-way merge)を実行してマージコミットを作成します。
git checkout feature-xyz
git merge main
この方法は、古いコミットを変更しないため非常に安全です。しかし、毎日コードを更新するために頻繁にマージを繰り返すと、ブランチが “Merge branch ‘main’ into…” といった内容のコミットで溢れ、プロジェクトの履歴にノイズが発生します。
2. Git Rebase:履歴を語る芸術
Rebaseは異なる仕組みで動作します。一時的に自分のコミットを取り出し、feature ブランチを main の最新状態に更新してから、その上に自分のコミットを積み直します。
git checkout feature-xyz
git rebase main
この時、履歴はまるで最新の土台の上でコーディングを始めたかのように見えます。すべてが完璧な時系列に従って並びます。
Interactive Rebaseの威力:
コーディング中には、「タイポ修正」や「細かな更新」といった断片的なコミットが溜まりがちです。プッシュする前に、対話モード(Interactive mode)を使って、10個の不要なコミットを1つの意味のあるコミットにまとめましょう。
git rebase -i HEAD~5 # 直近の5つのコミットを編集する
表示されたリストの中で、サブのコミットに対して pick を squash(または s)に変更します。Gitはそれらすべてを直前のメインコミットに統合してくれます。
黄金律:いつどちらを使うべきか?
プロフェッショナルなワークフローのために、以下のルールを適用してみてください:
- Rebaseを使う場合: 個人のブランチ(ローカルブランチ)に対して。世界に公開する前に、自分のコミットを綺麗に整理しましょう。
- Mergeを使う場合: 完成した機能を共有ブランチ(
mainなど)に統合するとき。これにより、「この時点で機能Xが統合された」という明確な節目を残すことができます。
重要な注意点: 複数の人が共同で作業している公開ブランチでは、絶対にRebaseを行わないでください。コミットのハッシュ値が変わってしまうため、同僚がコードをプルした際に深刻な履歴の衝突が発生してしまいます。
コンフリクト解消と安全なコードプッシュ
衝突(Conflict)は避けられないものです。Rebaseの場合、Mergeのように一度だけでなく、コミットごとに衝突が発生する可能性があります。パニックにならず、コードを修正してから以下を実行してください:
git add .
git rebase --continue
もし状況が混乱しすぎた場合は、いつでも git rebase --abort で中止できます。このコマンドは、ブランチをRebase開始前の元の状態に戻してくれます。
ローカルでのRebase成功後、通常のプッシュはできなくなります。以下の安全なコマンドを使用してください:
git push --force-with-lease
なぜ --force-with-lease なのでしょうか? 盲目的な --force とは異なり、このコマンドは、自分がまだ取得していない新しいコードを誰かが既にプッシュしていた場合に、プッシュを拒否してくれます。これは、同僚のコードを誤って上書きして消してしまわないための最後の砦です。
結論として、歴史の事実を尊重するためにMergeを使い、そのストーリーを最も明快に語るためにRebaseを使いましょう。

