はじめに
AIを使ったバイブコーディングが普及してきた影響か、最近「コードは書けるけどGitはよくわからない」という人が増えてきた気がします。
AIがコードを生成してくれる時代になっても、Gitの操作は自分でやることが多いです。
そしてGitの概念を理解していないと、ある日突然「履歴がぐちゃぐちゃになった」「チームメンバーの変更を消してしまった」という事故が普通に起きます。
自分が新人のころ、先輩からこんなことを言われました。
「
git pullは使うな。git fetchしてgit rebaseしろ」
正直、最初は意味がわかりませんでした。
当時はGitに関しての知識としては、自分のブランチを最新化するときは git pull で何が悪いんだくらいの感じでした。
でも今なら先輩が言いたかったことがよくわかります。
この記事では、その「意味」をこれからGitを使う人向けに解説していきます。
git pull の正体
まず前提として、git pull が何をしているか正確に理解してますか?
「リモートの変更を取り込む」
それは正しいですが、どうやって取り込んでいるかを理解しておくことが重要です。
git pull の正体はこれ
git fetch origin
+
git merge origin/main
つまり git pull は fetch + merge の合わせ技に過ぎない。
これを知らずに git pull を使い続けると、「なんか変なコミットが増えた」「履歴がぐちゃぐちゃになった」という事態に陥ります。
厳密には「git pull が悪」なのではなく、「merge を理解せず pull すること」が危険です。
fetch と merge について
git fetch:情報を取ってくるだけ
git fetch origin
これはリモートの最新情報をローカルに持ってくるだけで、作業ブランチには何も影響しません。
origin/main というリモート追跡ブランチが更新されるが、自分の main はそのままです。
git merge:取り込む
git merge origin/main
これで origin/main の変更を自分のブランチに取り込みます。
勘のいい方はお分かりだと思いますが、ここで問題が発生します。
merge commit という「ノイズ」
git pull(= fetch + merge)を使うと、feature branch 側で main の変更を取り込むときにこうなります。
① Before:feature/login で作業中に main が進んだ状態
② After git merge main:merge commit F が生まれる
③ After PR merge:merge commit ごと main に取り込まれる
PR マージ時の G は避けられませんが、問題は F です。作業中に git pull するたびにこの F のような merge commit が feature/login に積み重なり、それが PR マージ時に main にそのまま持ち込まれます。
複数人が毎日 git pull をすると、main の履歴がこうなります。
* Merge branch 'main' into feature/login
* Merge branch 'main' into feature/login
* Merge branch 'main' into feature/login
* 実際の変更
* Merge branch 'main' into feature/login
* Merge branch 'main' into feature/login
つまり、意味のない merge commit がノイズになり、本当の変更が埋もれてしまいます。
これが先輩の言う「git pull するな」の理由でした。
rebase という選択肢
一方、git rebase は merge とは別のアプローチで変更を取り込みます。
① Before:feature/login で作業中に main が進んだ状態
② After git rebase origin/main:D・E が C の後ろに付け替えられる
D と E が C の後ろに付け替えられます(D' E' はコミットIDが変わる)。merge commit は生まれません。
③ After PR merge
PR マージ時の戦略はチームによって異なります。
GitHub で Merge commit を使う場合、PR マージ時に merge commit が1つ生まれますが、作業中の F がないぶん履歴はずっときれいです。
なお、Squash merge や Rebase merge を使う場合は merge commit すら生まれず、完全に一直線になります。
* 9d4e5f6 (HEAD -> main) 自分の変更E
* 2c3d4e5 自分の変更D
* 7a8b9c0 チームメンバーの変更C
* 1e2f3a4 チームメンバーの変更B
rebase を使えば作業中に積み重なる無駄な merge commit がなくなるのが最大のメリットです。
pull --rebase という便利な書き方
毎回 fetch して rebase するのが面倒なら、こう書くこともできます。
git pull --rebase
これは内部的に fetch + rebase を実行するコマンドです。
さらに、毎回 --rebase を付けるのが面倒なら、グローバル設定で固定できます。
git config --global pull.rebase true
これを設定すると、以降は git pull が自動的に fetch + rebase として動作するので、新しいマシンをセットアップするときに設定してもいいかもしれません。
rebase の本質:「履歴を書き換える」
ここが rebase を理解するうえで一番重要なポイントです。
rebase はコミットを付け替える操作になります。
D が D' になったように、コミットIDが変わる、つまり、「履歴の書き換え」を意味します。
merge は既存の履歴を保ったまま新しいコミットを追加しますが、rebase は既存のコミットを「なかったこと」にして新しい場所に再配置することが特徴です。
rebase の注意点
共有ブランチに対する rebase は注意が必要です。
rebase は履歴を書き換えるため、push 済みコミットに対して行うと、他メンバーの履歴と衝突する場合があります。
特に git push --force を安易に使うと、他人の変更を巻き込んでしまう事故につながることがあります。
そのため、rebase は「自分しか触っていない branch」で使うのが基本です。
rebase が便利な理由
一方、自分しか触っていない feature branch では rebase は非常に便利です。
# mainが進んでいたら
git fetch origin
git rebase origin/main
# コンフリクトを解消しながら進める
git add .
git rebase --continue
feature branch を main の最新に追従させながら作業できる、つまり、PR を出すときに「このブランチは main の最新から分岐しています」という状態を保てるので、レビュアーにとっても読みやすいです。
merge派 と rebase派 の思想の違い
チームによって「merge派」と「rebase派」に分かれることがありますが、個人的には「履歴は読まれるためにある」という派閥です。
merge派の考え方
「履歴は事実の記録だ。merge commit があることで、いつ何をマージしたかが正確にわかる。書き換えるべきではない」
rebase派の考え方
「履歴は読まれるためにある。merge commit というノイズを排除して、変更の流れを一直線に保つべきだ」
どちらが絶対的に正しいという話ではありません。
GitHub の merge 戦略やチーム文化によって最適解は変わるので、まずはチームルールを確認しましょう。
rebase -i という次のステップ
rebase には git rebase -i(インタラクティブ rebase)という機能もあります。
これを使うと、PR を出す前にローカルコミットの履歴を整理する、つまり、「WIP」「fix typo」「やっぱ違った」みたいなコミットをまとめたり、順番を入れ替えたりできます。
これについては別の記事で詳しく書く予定です。
まとめ
最初に先輩から「git pull するな」と言われたとき、正直ピンとこなかったですが、実際に merge commit まみれの git log を見て、「あ、これは読めない」と思った瞬間に理解することができました。
Git は単なるバックアップツールではなく、履歴管理ツールです。
履歴は未来の自分やチームメンバーが読むものなので、読みやすい履歴を作ることが、良いエンジニアリングの一部だと思っています。
git pull の代わりに git fetch + git rebaseで、履歴の読みやすさが大きく変わるので、ぜひ試してみてください。
この記事は「Git実務シリーズ」の第1弾です。第2弾では git rebase -i を使ったPR前のコミット整理について書く予定です。