28 ポイント 投稿者 GN⁺ 2023-11-24 | 1件のコメント | WhatsAppで共有
  • 多くの人は Git branch の動作方式を直感的ではないと感じている
  • Git ブランチに関する一般的な直感的モデルと、実際に Git の内部でブランチがどのように表現されているかの違いを説明している
  • 直感的モデルと実際の Git の動作方式が、実は非常に密接に関係していることを示している
  • 直感的モデルの限界と、それが問題を引き起こしうる理由を議論している

直感的なブランチモデル

  • 多くの人はブランチを「リンゴの木の枝」にたとえて考える。
  • Git ではブランチに「親」という概念はなく、main から分岐したものとして考えるのとは異なる。

Git におけるブランチは履歴全体

  • Git でのブランチは単に分岐したコミットではなく、それ以前のすべてのコミットを含む履歴全体を持つ。
  • 例のリポジトリを通じて、mainmybranch のどちらも 4 つのコミットを持っていることを示している。

ブランチはコミット ID として保存される

  • Git の内部では、ブランチはコミット ID を含む小さなテキストファイルとして保存される。
  • 各ブランチの最新コミットがそのファイルに記録されている。
  • コミット間に親子関係があるわけではないため、Git はブランチ間の関係を知ることができない。

人々の直感はたいていそこまで間違っていない

  • Git に対する人々の直感が「間違っている」と言うのは、ややばかげている。
  • 「間違った」モデルでも、実際には有用でありうる。

リベースは「直感的な」ブランチ概念を使う

  • リベースは「直感的な」ブランチのコミットだけを main に再適用する。
  • リベースの結果は直感的モデルと一致する。

マージも「直感的な」ブランチ概念を使う

  • マージはコミットをコピーしないが、共有された基底コミットを必要とする。
  • マージベースは、直感的モデルに基づいてブランチが分岐したコミットを見つけてくれる。

GitHub のプルリクエストも直感的な考え方を使う

  • GitHub で mybranchmain にマージしようとするプルリクエストを作成すると、直感的なブランチのコミットだけが表示される。

直感は良いが限界もある

  • 直感的なブランチ定義は実際の Git の作業とよく一致するが、Git は main とそこから分岐したブランチを別物として認識できない。

トランクと分岐ブランチ

  • 人は mainmybranch を異なるものとして認識し、それが Git の使い方に影響する。
  • Git は、あるブランチが別のブランチからの「分岐」かどうかを区別しない。

Git はリベースを「逆方向」にもできる

  • Git は、あるブランチが別のブランチからの「分岐」かどうかを教えてくれないため、いつどのブランチをリベースすべきかはユーザーが理解しておく必要がある。
  • git rebase main と、その逆方向のリベースである git rebase mybranch の両方が可能。マージも同様。

Git ブランチ間に階層構造がないのはやや奇妙

  • main ブランチが特別ではないと言われるのは、Git がブランチ間の関係を認識できないためである。
  • ブランチ同士には関係があるが、git は何も知らない。

Git ブランチ UI も奇妙

  • 「分岐した」コミットだけを見たいとき、git loggit diff の使い方は異なる。

GitHub ではデフォルトブランチが特別

  • GitHub には「デフォルトブランチ」があり、これは特別な役割を持つ。

GN⁺の意見

この記事で最も重要なのは、Git ブランチについての人々の直感的な理解と、実際の Git の動作方式との違いを理解することだ。この記事は、初級ソフトウェアエンジニアが Git ブランチの概念をよりよく理解し、効果的に使えるよう助けてくれるだろう。Git ブランチの直感的モデルが実際の作業とどう一致するのか、そして Git がブランチ間の関係をどのように扱わないのかを知るのは、興味深く有益だ。

1件のコメント

 
GN⁺ 2023-11-24
Hacker Newsのコメント
  • ブランチはコミットを指すポインタであり、新しいコミットが作成されるたびにこのポインタが更新される。ブランチはタグのように漂う名前と見なすこともできる。コミット自体が親コミットを指しているため、ブランチとは関連するコミットの連鎖であり、名前付きの入口を持つものだ。ブランチを削除すると名前付きラベルがなくなり、単に関連コミットの連鎖としてだけ残る。
  • コミットの系譜は、「前へ」ではなく「後ろへ」向いているポインタとして考えると理解しやすい。ブランチはコミットIDなので、親リンクをたどればそのブランチの全履歴を見つけられる。「ブランチポイント」は2つのコミットチェーンが交わる地点であり、マージコミットは特別で、2つの履歴が1つに統合されたことを示す。
  • 個人的なプロジェクトで git reset --hardgit stash を使って変更やブランチポインタを操作しているのを友人に見られると、よく怒られる。誤ったマージを取り消すには git reset --hard <マージ前の最後のコミット> を使い、ローカルブランチのちょっとした修正をメインブランチに適用したいときは、git stash を使ってからメインブランチにチェックアウトし、git stash apply を使う。
  • Git には「mainが特別だ」という概念はないが、GitLab のようなツールは保護ブランチ機能を提供しており、ミスを減らせる。「親」と「子」のブランチという概念は実際には興味深いかもしれず、長期サポートブランチのためには複数の「親」ブランチをサポートする必要があるだろう。
  • マージ、rebase、プルリクエストを行うときは、別のブランチを明示的に指定しなければならない。Git は、ユーザーがどのブランチをベースだと考えているかを知らないからだ。機能ブランチを別の機能ブランチにマージしたい場合もあるので、どのブランチをどのブランチにマージするのかを明確に指定する必要がある。
  • 人々が持っている直感が技術的には部分的に間違っているとしても、そのような直感を持つのにはもっともな理由がある。
  • git addgit commit の使い方を知っている人向けの動的チュートリアルがある。このチュートリアルはブランチを可視化し、読みながら理解できるよう助けてくれる。
  • Git コマンドを実行するときは、「常に」現在のブランチを変更していることを覚えておけば、Git の文法を「簡単に」理解できる。たとえば、git merge my-branch は現在のブランチに my-branch をマージし、git rebase my-branch は現在のブランチを my-branch の上にリベースする。
  • ブランチ(head)には、そのブランチが始まった基底コミットを指す「尾」があるとよい気がする。ブランチは頻繁にリベースされるため、どこから始まったのかを考える必要がある場合がある。Git が、その基底コミットが main に属していると教えてくれれば、もっと便利だろう。
  • メーリングリストに「パッチ」を送るときは、基底コミットを任意で含めることができる。これは、変更が最新リリース、メイン開発ブランチ、あるいは統合ブランチのどれをベースにしているのか明確でないことがあるためだ。git range-diff を使うときも基底を意識する必要がある。このツールは main..previousmain..current のような2つの範囲を比較する。
  • ブランチについての個人的な見解を読み返して、忘れていたいくつかのことを改めて学んだ。