リーナストーバルス氏がGitで本当に目指した大事なものの勘所(5/6)


その4から続き

好きな状態に戻せること:ブランチと差分管理とスピード

好きな状態に戻せることとは、どういうこと?かを考えます。

前にコミットした状況に、素早く移動や指定ができることです。

レポジトリには永遠とコミットが連なっていきます(正確にはブランチに沿って)。またブランチやフォークのように枝分かれしていきます。

そういう状況を考えると、その時点時点でのスナップショットが確保できること、前の状態(後ろは無理だし、一旦なくても良い)の関係性をコミット単位で保持できる必要があります。

コミットの筋をブランチ(枝)という形にし、コミットごとにIDをつけそれをコミットごとに前のIDとして自分のIDと別に保存します。ノンリニアなこのブランチ機能は gitの主目的でもあります。ブランチの数に制限されないスケールできるデータ構造が必要です。

実現方法は

普通に考えれば差分方式

普通に考えれば、前のコミットの差分だけ保存します。差分は過去をベースにするよりも、編集等は現在のものをベースとして、過去のものをたどるときに逆差分で過去のものを作るというのがファイル単位の管理イメージです。これにはファイル容量が節約できるというメリットも有りますが、欠点があります。

過去のものをたどるときに、処理に時間が掛かるということです。これが問題1です。前の状態を確認したかったり、一旦手戻って分岐したい時全てに時間がかかります。

ブランチするときにも問題が置きます。ブランチするたびに、コピーが必要です。なぜならトランクとなる主要な枝(幹)が変わった場合、ブランチした元となるものが一旦消えてしまうので、トランクをベースに遡るか、コピーすることが必要になるからです。さらに問題はブランチしてコピーしてしまうと、元のデータがたどりにくくなることがsubversionでは良くありました。(最近はSubversionでもトラッキングできるようにするという情報もあります)

何を元にブランチしたか、マージしたかの情報を残し、いつでも戻れるようにする。しかもスピードを持って。

gitは参照方式のスナップショットでまるまる保持

スピードと両立させるときに、差分による方法がネックになります。そこでファイルサイズが大きくなるのは一旦諦めて、ファイル単位ではなくプロジェクト全体のスナップショットとしてみます。実はプロジェクトとは一つのファイルではなく複数のファイル郡で1プロジェクトが正しく動くことになるので、そちらのほうが全体を整合させるという考え方であれば自然ですし、差分方式より良いんですね。例えば、時間軸で T1の時に修正して、T2、T3で修正しなかった時。T2の修正状況をたどるのは、ファイルを軸に、コミット時期からT1とT4の差分と日付をチェックすることになります。これは重い処理です。一方スナップショットではコミット単位ですので、どのコミットがわかれば、その内容もすぐ引けるようになります。

Git誕生10周年を記念した開発者のリーナス・トーバルズ氏のインタビュー - GIGAZINE
http://gigazine.net/news/20150407-10-years-git-linus-torvalds/
GitはLinux開発のような大きなプロジェクトに向けて作られているので、他のシステムだと何時間もかかるようなマージの処理をわずか数秒~数分で完了させることができるようになったとしています。

動作確認は必須です。苦痛だけれどもやらなければいけない内容です。それについて、今まで気になって考えていた部分がありました。

本の虫: gitの10周年を記念したLinus Torvalsへのインタビューの翻訳
http://cpplover.blogspot.jp/2015/04/git10linus-torvals.html
例えばだ。「マージ」という概念は、大抵のソース管理ツールではとても頭が痛くて難しい作業だ。マージのための計画を練る必要がある。とても重要だからだ。それは俺にとって許容できないことだ。なぜならば、俺はマージ期間中に一日あたり何十ものマージを普通に行うからだ。それも、マージ自体が面倒なのではなく、結果をテストすることのほうが難しくあるべきだ。gitでは、マージはたったの数秒のことだ、マージよりも、マージの説明メッセージを書く方に時間がかかるべきなのだ。

 

取り込む作業はよくある。だから数秒で終わらせるんだ。という気持ちがわかります。

ソーシャル化するOSS開発者たち - @IT
http://www.atmarkit.co.jp/news/analysis/200904/14/git.html
Gitのソースコードは読みづらい、そもそもC++じゃないし、文字列処理を自前でやるのはムダなばかりかエラーが入りやすいのではないか、という苦情をメーリングリストで述べた人に対して、リーナスがこてんぱんにやりこめている議論を見れば、どれほどパフォーマンスにこだわっているかということが、よく分かる

 

BitKeeper紛争を受け、トーバルズ氏が新プロジェクト - ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/0504/20/news075.html
「gitはある程度『日常的に行う作業に1秒

 

ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/0504/20/news075.html
既に多数のオープンソースコード管理製品が出回っているが、Linuxカーネル級の規模のプロジェクトではパフォーマンスが遅くなる傾向にあるとトーバルズ氏は述べている。「ソース基盤が大きなためにパッチを当てるのに数十秒かかる。これは受け入れられない」もかかってはいけない』という原則を基に設計されている」

ファイルサイズのデメリットを抑える

またファイルサイズのデメリットについては、2つの対策がされています。ひとつは前のバージョンなどと変更がなかったら、実体はそのままに参照ポインタだけ保存されています。またソースコードなどであれば圧縮した時に、文字種が少ないため、5割ぐらいは余裕で減ると思いますし、1,2割ぐらいのサイズになることもできます。差分に比べたら軽い処理です。

マージする環境やコミットした環境が本当に同じファイルだろうか?

何かのミスにより、管理していたものが変わってしまうことはたまに起こりえます。そういう時のために、ファイルを比較するのですが、効率のよい比較方法があります。それはハッシュを利用した一意性の確認です。完全ではありませんがかなりのパターンに対応でき、しかも高速で文字も固定であるため扱い易いです。

コミット単位のIDをコミットハッシュとし、ファイルやディレクトリの単位はツリーハッシュで管理します。プログラマーであればハッシュのアルゴリズムはある程度把握していると思いますが、ユニークな識別子により、複数のデータから素早く特定するアルゴリズムです。  セキュリティ界ではSHA1から2へシフトしていますが、2^160で管理されています。 およそ10^48。載という単位です。 億は10^8、京は10^16 、無量大数は 10^68。ユニーク性は2^60(10^18)ぐらいと見積もってもかなり大きいです。。1行40文字数千万行でも300億bitのファイルが数千万ファイルあっても全然余裕ですね。

ソーシャル化するOSS開発者たち - @IT
http://www.atmarkit.co.jp/news/analysis/200904/14/git.html
リーナスによれば、多くのソースコード管理ツールにはチェックサム程度しかないのだという。一方、Gitはソースコードやコミットのログを対象としたハッシュ(SHA1)を取ることによって、ディスクエラーなどでコードのどこかが壊れてしまっていないかや、余計なコードや変更が混入していないこと、これまでの変更履歴が自分が思っているものと確実に同一であることを保証してくれるという。リーナスはバックアップは取らない(なぜなら他人がどんどんミラーしてくれるから)と公言しているが、この160ビットのSHA1の値だけを管理していれば、1000万行を超えるソースコード全体に目を光らせていなくても、夜よく眠ることができるのだという。

マージの時も合わせて考える

ブランチの話をしましたが、マージとセットで考える必要があります。

マージについても、コミット単位の参照方式であるため、差分とは違い共通の過去に遡りチェックできます。

コミットオブジェクトの中に、コミットハッシュがある。git show。
そのスナップ内のディレクトリ構成は ツリーハッシュで連携されている。

 

共通の過去があれば、ファイルの更新有無をチェックし、バッティングしていなければそのままファイルコピーというマージをしても基本は問題ありません。バッティングしていれば競合を通知します。これについてもファイルの中を精査するわけではないため、軽い処理となります(ファイルの移動・削除がるため多少処理が必要ですが)。

 

このように、コミット単位の参照方式であるため、branchやタグは一瞬でできます(参照ポインタを保存するだけ)。また、過去のブランチや ひとつ前の HEAD^なども参照ポインタをたどるだけですのですぐ戻せるとともに、現在のレポジトリ上にあるファイル等は全く破壊せず保存されているので、コミットされていれば好きにブランチを変えることができます。つまり―ワーキングディレクトリは、すぐ消えやすく儚いので新規というわけではなく、コミットしている今の対象ブランチのワーキングディレクトリという意識でいたほうが良いと思います。

以下のコマンドの目的語は今の作業対象のブランチが省略されていることをイメージすると混乱は少ないと思います。

(英文)set target branch master -> (コマンド)git checkout master    ・・・チェックアウトの前にワーキングディレクトリが途中であればコミットしないと消える。

(英文)make branch test from master -> (コマンド)git branch test master

(英文)merge develop into this branch -> (コマンド) git merge develop

一方 pull / pushなど レポジトリ間のやり取りになる場合は、コミット単位のブランチではなく、レポジトリ単位となるため、コマンドも変わります。

(コマンド)git push targetrepository localbranch

 

 その6へ続く

Delicious にシェア
Digg にシェア
reddit にシェア
LinkedIn にシェア
LINEで送る
email this
Pocket

479 views.



コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です