Gitブランチ:直感と現実
(jvns.ca)- 多くの人は Git branch の動作方式を直感的ではないと感じている
- Git ブランチに関する一般的な直感的モデルと、実際に Git の内部でブランチがどのように表現されているかの違いを説明している
- 直感的モデルと実際の Git の動作方式が、実は非常に密接に関係していることを示している
- 直感的モデルの限界と、それが問題を引き起こしうる理由を議論している
直感的なブランチモデル
- 多くの人はブランチを「リンゴの木の枝」にたとえて考える。
- Git ではブランチに「親」という概念はなく、
mainから分岐したものとして考えるのとは異なる。
Git におけるブランチは履歴全体
- Git でのブランチは単に分岐したコミットではなく、それ以前のすべてのコミットを含む履歴全体を持つ。
- 例のリポジトリを通じて、
mainとmybranchのどちらも 4 つのコミットを持っていることを示している。
ブランチはコミット ID として保存される
- Git の内部では、ブランチはコミット ID を含む小さなテキストファイルとして保存される。
- 各ブランチの最新コミットがそのファイルに記録されている。
- コミット間に親子関係があるわけではないため、Git はブランチ間の関係を知ることができない。
人々の直感はたいていそこまで間違っていない
- Git に対する人々の直感が「間違っている」と言うのは、ややばかげている。
- 「間違った」モデルでも、実際には有用でありうる。
リベースは「直感的な」ブランチ概念を使う
- リベースは「直感的な」ブランチのコミットだけを
mainに再適用する。 - リベースの結果は直感的モデルと一致する。
マージも「直感的な」ブランチ概念を使う
- マージはコミットをコピーしないが、共有された基底コミットを必要とする。
- マージベースは、直感的モデルに基づいてブランチが分岐したコミットを見つけてくれる。
GitHub のプルリクエストも直感的な考え方を使う
- GitHub で
mybranchをmainにマージしようとするプルリクエストを作成すると、直感的なブランチのコミットだけが表示される。
直感は良いが限界もある
- 直感的なブランチ定義は実際の Git の作業とよく一致するが、Git は
mainとそこから分岐したブランチを別物として認識できない。
トランクと分岐ブランチ
- 人は
mainとmybranchを異なるものとして認識し、それが Git の使い方に影響する。 - Git は、あるブランチが別のブランチからの「分岐」かどうかを区別しない。
Git はリベースを「逆方向」にもできる
- Git は、あるブランチが別のブランチからの「分岐」かどうかを教えてくれないため、いつどのブランチをリベースすべきかはユーザーが理解しておく必要がある。
git rebase mainと、その逆方向のリベースであるgit rebase mybranchの両方が可能。マージも同様。
Git ブランチ間に階層構造がないのはやや奇妙
mainブランチが特別ではないと言われるのは、Git がブランチ間の関係を認識できないためである。- ブランチ同士には関係があるが、git は何も知らない。
Git ブランチ UI も奇妙
- 「分岐した」コミットだけを見たいとき、
git logとgit diffの使い方は異なる。
GitHub ではデフォルトブランチが特別
- GitHub には「デフォルトブランチ」があり、これは特別な役割を持つ。
GN⁺の意見
この記事で最も重要なのは、Git ブランチについての人々の直感的な理解と、実際の Git の動作方式との違いを理解することだ。この記事は、初級ソフトウェアエンジニアが Git ブランチの概念をよりよく理解し、効果的に使えるよう助けてくれるだろう。Git ブランチの直感的モデルが実際の作業とどう一致するのか、そして Git がブランチ間の関係をどのように扱わないのかを知るのは、興味深く有益だ。
1件のコメント
Hacker Newsのコメント
git reset --hardやgit stashを使って変更やブランチポインタを操作しているのを友人に見られると、よく怒られる。誤ったマージを取り消すにはgit reset --hard <マージ前の最後のコミット>を使い、ローカルブランチのちょっとした修正をメインブランチに適用したいときは、git stashを使ってからメインブランチにチェックアウトし、git stash applyを使う。git addとgit commitの使い方を知っている人向けの動的チュートリアルがある。このチュートリアルはブランチを可視化し、読みながら理解できるよう助けてくれる。git merge my-branchは現在のブランチに my-branch をマージし、git rebase my-branchは現在のブランチを my-branch の上にリベースする。mainに属していると教えてくれれば、もっと便利だろう。git range-diffを使うときも基底を意識する必要がある。このツールはmain..previousとmain..currentのような2つの範囲を比較する。