Gitの良さがいまだに分からないという人がいるようなので、Git派の一人としてSubversion(以下SVN)と比較してのGitの良さ(メリット)について語りたい。 (GitとSVNの違いについては他の人の記事に詳しいのであまり書いていない一方、勢い余ってGitのデメリットも書いた。)

本題に入る前に、冒頭にリンクを貼った記事についてひとつだけつっこんでおく。 つっこみどころは他にも沢山あるけど。

※話の前提としてgitとSVNを採用している現場に下記のような割と違いがあるとする。

git イシューごとにブランチを切り、ローカルでコミットして、リモートブランチにpushして、GitHub・GitLab・Bitbucket経由でマージリクエスト。コードレビューの後にマージ。

SVN リモートのtrunkに個々人が直接コミット。コードレビューはあまりない。ブランチを切ることもない。

このような違いが出る背景には次のものがある。

gitを採用する現場は、猫も杓子もgit-flowというプラクティスに従う傾向がある
gitを採用する現場は、コードの品質もある程度管理する傾向がある
SVNは集中型でありブランチ機能などが非常に使いにくい
SVNを採用する現場はコードの品質よりも「リリースに含めるならさっさとコミット」と考える傾向がある

この前提には無理がある。

Gitのところに書いてあるのが、Gitというツールの枠を大きくはみだしたGitHub Flowというブランチ戦略+開発プロセスに当たるものであり、 それでGitを批判するのはお門違いであろうという点については、Gitの流行がGitHubの人気によるところが大きく、GitHubを使えることがGitの大きなメリットであるので、目をつむることにする。(マージリクエストを使う羽目になるデメリットなんて言いがかりでしかないとだけ言っておく。)

看過できないのは、SVNを使った開発がコードレビューもブランチもないという点。

どこの世界の話をしているんだろうか。 Gitが世に出る前は世間にコードレビューもブランチもあまりなかったかのような前提だが、もちろんそんなことは全くない。 60万個以上のOSSプロジェクト情報を統括するOpen HUBによれば、OSSプロジェクトの46%がSVNを使っている。この中にはGitの誕生以降にSVNを使い始めたプロジェクトも多くある。270000余りのプロジェクトの大部分がブランチすら使っていないとでも?

GitHub Flowと対比するために無理やりこじつけたんだろうけど、その無理のせいで議論のスタート地点からめちゃくちゃだ。

まともな開発にはコードレビューもブランチも必要だ。 品質管理もリリース管理もしないなら要らないのかもしれないが、そんないい加減な開発現場を前提にSVNかGitかなんて議論しても意味がない。 高品質なソフトウェアを効率よく開発するために則りたい素晴らしい開発フローがあるとして、そのフローをSVNやGitやその他のツールないしひょっとしたらアナクロな日付フォルダの内どれがもっとも上手く実現してくれるか、というのがあるべき議論だ。 この「素晴らしい開発フロー」には一般的に品質管理と並行開発が含まれていて、それらにはコードレビューとブランチの利用が含まれている。 Git(+GitHub)がこんなにも急速にSVNに取って代わって流行ったのは、分散リポジトリの仕組みとブランチの軽量な実装によって効率的な並行開発が実現でき、またプルリクエストなどの機能によりコードレビューを含む快適なソーシャルコーディングが実現できるからだ。 逆に言えば、Gitが流行ったことが、人々が効率的な並行開発やコードレビューを開発フローに取り入れたかった証拠と言えるかもしれない。

Gitのメリット

前置きが長くなったが、少なくともブランチとコードレビューを活用した高品質で高効率なソフトウェア開発をしたいという前提で、SVNに対するGitのメリットを挙げてみたい。

1. リポジトリ構造がシンプル

Gitリポジトリはすごくシンプルに作られているそうな。 確かに、その構造を見ると、addcommitlogresetくらいは自前ですぐに実装できそうだ。

このシンプルな構造のおかげで、Gitリポジトリは壊れにくい。ここで壊れにくいとは、リポジトリ内部で不整合が起こりにくいということで、コマンドミスでコミット履歴が一部消えたりとかいうトラブルは壊れるに入らない。

実のところSVNリポジトリの構造を知らないので経験的なことしか言えないが、SVNリポジトリ(というより作業ディレクトリの管理情報?)はちょくちょく変な状態になり、クリーンアップしたり、酷い時には.svn内のファイルを手動でいじったりしなければならなかった。

因みに、シンプルというのはリポジトリサイズがすごく小さいということにはならず、同等の履歴を含むGitリポジトリとSVNリポジトリはだいたい同サイズなんだそうな。

2. ブランチが軽い

Gitのブランチは単一のコミットを指す参照で、リポジトリ内ではSHA-1ハッシュ値が書かれただけのたった一つのファイルに過ぎない。 その為ブランチは一瞬で作成できるし、ディスクも圧迫しないので、じゃんじゃん作ってじゃんじゃん消せる。 さらに、ローカルリポジトリに過去の全ファイルの全バージョンが入っているという分散リポジトリの特長のおかげで、ブランチの切り替えも軽快にできる。 ローカルから必要なファイルを作業ディレクトリに展開するだけなので。

一方SVNはそもそもブランチをサポートする直接的な機能がないため、ブランチはリビジョンのコピーという形で実装されている。 コピーと言ってもハードリンクみたいなものでディスク上に物理的なコピーが作られるわけではなく、軽量という点ではGitと大差ないが、集中リポジトリなせいでブランチの切り替えには差が出る。 svn switchにしろsvn checkoutにしろネットワークの向こうのサーバとの通信が必要なので、それなりの時間がかかるし、通信が途切れると切り替えられなくなる。

冒頭に貼った記事にはGitはブランチを切り替える際にstashとかしないといけなくて面倒とあったが、そんなのSVNだって同じだし、stashすればいいだけだし、stashという機能があるだけSVNよりまし。Gitならコミットはあとから書き変えられるので、stashの代わりに一時的にコミットしちゃってもいい。

それも嫌ならworktree使えばよろしい。

3. バージョン間の差分取得が速い

Gitは全てのファイルについて全てのバージョンのコンテンツをまるまるリポジトリに持っている。 一方SVNのリポジトリにはバージョン間の変更が記録されている。 このため、あるファイルについて任意のバージョン間の差分を取るのに、Gitはシンプルにそれぞれのバージョンのファイルを取り出して比較するだけでよいが、SVNは隣り合ったバージョンでなければバージョン間の変更を足し合わせて差分を計算しなければいけない。

さらに、Gitは比較するファイルをローカルリポジトリから取り出すだけでよいが、SVNはサーバへのアクセスが必要なので、差分取得はGitの方が大分速い。

4. ログ取得が速い

Gitのコミットは常にプロジェクトの全ファイルに対するものだ。 これは変更したファイルの一部だけを対象とするコミット操作ができないという意味ではない。 Gitがひとつのコミット操作をコミットオブジェクトと呼ばれる単一のファイルに記録し、そのファイルが常にプロジェクトの全ファイルの特定のバージョンを参照しているという意味だ。(正確に言うとこのファイル自身に全ての参照が記録されているわけではないが。)

このためGitのコミット履歴は実にシンプルで、ログ一覧を取得するには単にコミットをたどりながらコミットオブジェクトに書かれたログを集めればいい。

一方SVNはファイル毎にバージョンを管理するので、もう少しややこしい。

さらに、Gitはコミットオブジェクトをローカルリポジトリから持ってこれるがSVNは(以下略)。

5. オフラインでだいたいなんでもできる

と、ここまで書いて、Gitのいいところはオフライン作業が捗るところではないかと思い立った。

実際Gitは、clonefetchpullpushといったあからさまな操作以外はオフラインでできる。 多くの操作にネットワーク通信コストを払わなくていい上、リモートリポジトリサーバが落ちたりネットワークが落ちたり山に籠ったりしていても作業が続けられる。

ノマドに最適。

一方SVNがネットワーク通信なしでできることは、…ベースバージョンとのdiffくらい?

6. コミット履歴を汚さずにコードレビューできる

私の職場はSVNを使っていて、コードを書いたら一旦コミットして、リビジョンを偉い人に通知してレビューしてもらっている。 偉い人は遠い異国にいたりするが、こちらがコミットしてしまえばSVNの機能で変更内容の取得も確認もできるという寸法だ。 リポジトリ外で変更内容をやりとりする方法とは違って、レビュー後のコミットミスや漏れが起こる余地がないのがいいが、レビューで受けた指摘は別のコミットを加えて反映したり、酷い時はリバースコミットで変更を取り消す必要がある。 こういうコミット履歴は大抵単なるノイズで、そうでなくてもリポジトリにある必要はない情報だ。

一方GitならP2Pで偉い人にコミットを送れるし、レビュー後にコミットの作り直しもできるので、コミット履歴をきれいに保てる。 履歴がきれいだと変更のトレーサビリティが高まる。 変更のトレーサビリティが高いと、保守性が高くなり、低メンテナンスコストで高品質なプロダクトの開発につながる。

7. ソーシャルコーディングできる

SaaSならGitHubBitbucket、オンプレミスならGitHub EnterpriseGitLabを利用して、ソーシャルコーディングを実現できるのはやはりGitの大きな強みだ。

ソーシャルコーディングはアジャイルの先にあるDevOpsに必須とも言える要素で、今後これを実現できないIT企業やユーザ企業は開発力で他企業に差を付けられ、苦しい競争を強いられるであろう。

バージョン管理ツール単体だけでなく、その上に乗っかるものまで見た場合、GitはSVNに大きく差を付けている感がある。

Git対SVNの迷信

調べているうちに、Git対SVNで広く信じられている迷信があることを知ったので、ついでに書き残しておく。

1. SVNのマージはクソ

例えば「SVNからGitに移行して分かった、今すぐSVNを捨てるべき3つの理由」という記事の3つめの理由にSVNのマージ機能がクソと書いてあるが、これは最近では迷信とされている。

SVNは確かにかつてブランチとマージに対するサポートが貧弱で、ブランチがどこを起点に作られたか、どのコミットをマージしたかといった情報を記憶しなかったため、ユーザがコマンドに教えてあげたり、コミットログを工夫して記録してやらなければならなかった。 しかし、バージョン1.5からこの状況が改善され始め、バージョン1.8で成熟したオートマージ機能により、SVNのマージも十分強力なものになった。

Gitはオクトパスマージとかマージ戦略オプションとかあってさらに強力そうではあるけど、そんな高機能を必要とする場面があまりなさそう。

2. .svnフォルダが各フォルダにあってうっとうしくてほんとクソ

これも今では迷信。バージョン1.7から.svnはルートフォルダだけに作られるようになった。

Gitのデメリット

GitはSVNより全ての点で優れているというわけでもない。 以下、SVNに対するGitのデメリットを挙げてみたい。

1. cloneに時間がかかる

Gitでの開発は基本的にリポジトリ全体をcloneすることから始まる。 上記の「オフラインでだいたいなんでもできる」というのは、最初に全部ローカルに持ってきてしまうことで活きてくる利点だ。

けどリポジトリが大きいとやっぱりcloneは時間がかかる操作になる。 例えば、LinuxカーネルをGitHubからcloneしてみたら 45 分程かかった。 そんなに気軽にできる操作ではない。

このデメリットに対処する方法はいくつかあるが、それをするとオフライン作業の幅を狭めることになる。

SVNにはcloneの概念がないのでこの悩みはない。

2. 部分cloneのサポートが貧弱

上でも書いたが、GitはSVNのように履歴をディレクトリやファイル毎に管理しているわけではなく、コミットはプロジェクトの全ファイルを参照(i.e. 依存)しているので、特定のディレクトリ以下だけのcloneといった部分cloneの完全な実装は技術的に困難だ。

Gitはリリース当初、部分cloneのサポートを全く提供せず、バージョン1.7になってそれっぽいsparse checkoutが実装されたが、あまり使い勝手が良くない。 Gitの開発陣は当初から部分cloneの実装に乗り気ではないし、上記の技術的な壁もあるので、今後この状況が大きく改善されることは恐らくないであろう。

妥協になるだろうが、ソースをモジュール毎に分割して別々のリポジトリに突っ込み、必要に応じてsubmodulesubtreeでつなげるのが実用的な解ではないだろうか。 それにしたって面倒だが。

SVNではリポジトリの一部をcheckoutする操作は第一級市民であり、何の制限もなく快適にできる。 この点においてはSVNパイセンの圧勝だ。

3. コマンドが分かり辛い

Gitはもともと低レベルなバージョン管理ツールとして開発されたためか、の思考パターンが凡人のそれとはかけ離れているためか、Gitのコマンド体系は分かり辛く使いにくいというのは世界共通の認識のようだ。 このためGitの導入に当たってはどうしても高い学習コストを払わなければいけない。

これは、分散バージョン管理システムというアーキテクチャが複雑だから、という理由からくるものではない。 同じ分散バージョン管理システムでも、Mercurial一貫したきれいな使いやすいコマンド体系をもっているらしい。

好みの問題もあるだろうが、この点についてもSVNに分があるというのが一般的な認識だ。

まあGitのGUIツールもバンドルされてるやつとかTortoiseGitとかSourceTreeとかイカとか色々あるので、それで大分カバーできるだろうが。

4. バイナリファイルの扱いが下手

Gitは基本的にテキストファイルを扱うよう作られていて、バイナリファイルの扱いは下手だ。 これはSVNも同じだけど、SVNの方がましらしい。

例えば、バイナリファイルの同等の履歴を管理するのに、GitはSVNより少しだけ多くリポジトリ容量を食う。

また、Gitはファイルのコンテンツに注目して管理するツールであるが、バイナリファイルは人間から見ると少しの変更(e.g. 画像の明度の変更)でもコンテンツが大きく変わるため、Gitが変更前のファイルと変更後のファイルを別のファイルとして扱ってしまうことがある。(最近のバージョンでは修正されているかも。)

SVNはファイルそのものに注目しているので、その内容がどんなに劇的に変わっても見失うことはない。

Gitでバイナリファイル、特にサイズが大きかったり頻繁に修正されるものを扱う必要があるときは、git-annexGit Large File Storage (LFS)の利用を検討すべし。

5. アクセスコントロール機能がない

Git自身にはアクセスコントロール機能が全く実装されていない(多分)。

cloneするときなんかは、HTTPやSSHやTLSの力を借りてリポジトリ単位でのユーザ認証ができたり、pushするときにはファイルシステムのアクセスコントロールの力を借りて特定のファイルの変更を防いだりはできるが、もっと細かい制御をしたい場合はGitoliteの力を借りる必要がある。

借りてばっかだ。

一方SVNは自前でPath-Based Authorizationという機能を持っていて、ユーザ認証とディレクトリまたはファイル単位での読み書き制限ができる。

6. ファイル単位の履歴を保持しない

上にも書いたが、GitはSVNのようにファイル単位でバージョン管理をしているわけではないし、また、ファイルそのものではなくそのコンテンツに注目してバージョン管理する。この特徴のせいで、Gitはたまにファイルの行方を見失うことがある。

上記バイナリファイルの問題もそうだし、テキストファイルでもリネームとコンテンツ変更を同時にやるとgit log --followファイルの履歴が追えなくなる

SVNはリネームにちゃんとsvn mvを使っている限りファイルを見失うことはない。

ただこれは実際、Gitのデメリットと言うよりは、GitとSVNの思想の違いと言った方がいいかもしれない。 git log --followは単にSVNに慣れ親しんだGit初心者のための機能で、真のGit使いは特定のファイルの履歴を追うということを必要としない。

ファイルの履歴を見たい煩悩に駆られたら、心を静め、に祈りを捧げ、Gitのソースコードを写経し、Gitコマンドを108回たたいて悟りを開くべし。

まとめ

Git派としてGit押しの記事を書こうと思っていたが、意外とデメリットもたくさん見えてきてしまった。 結局、GitとSVNどちらが単純に優れているということはないので、プロジェクトの構成やワークフローなどの要件を鑑みて使い分ければよしということか。

参考資料