今回のブログ コンテンツの大部分は Steve Losh 氏との共同執筆によるものです。Steve 氏はプログラマー、写真家、ブルース ダンサー、そしてミュージシャンです。Steve 氏の プロジェクト を参照し、彼のクールな取り組みをご覧ください。また、彼の Bitbucket アカウント 経由でソース コードを確認してみましょう。
分散型バージョン管理への切り替え
多くの個人、チーム、そして組織は Git や Mercurial (Hg) などの 分散型バージョン管理システムへの切り替え を検討しています。今後数週間にわたって DVCS の利用と理解に焦点を当てた一連のブログ投稿を公開しますが、今回はその最初の記事です。
バージョン管理全般に関する基本的な事項から始め、一般的な内容について確認していきましょう。
簡単な例
コードの編集について議論する際、具体例があるとわかりやすくなります。そこで、簡単なパーソナル Web ページを使用してみます。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<h1>John Doe</h1>
A Java programmer from Chicago, IL. <header> </header> John is experienced in many areas of Java programming. <footer>Copyright John Smith, 2010</footer> |
このシンプルな HTML ページを今回のエントリの中で例として使用します。
コードは頻繁に書き換えられる
我々がプログラマーとして記述するコードは頻繁に書き換えられます。修正すべきバグ、追加すべき機能、そして変更すべきコンテンツ。
コードの多くはお馴染みのプレーン テキスト ファイルとして保存され、これらのファイルを編集することでコードを書き換えます。変更を保存するたびに、古いバージョンのファイルは新しいバージョンのもので上書きされます。
残念なことに、完璧なプログラマーはいません。ときどき間違いを犯します。ファイルに変更を加えて保存し、コンパイルして何かがうまくいかなかった場合、古いバージョンに戻れたり、実際の変更箇所に関するレポートを取得できると便利です。これにより、どこで間違ったか確認できます。
ここで例を挙げてみましょう。架空の人物である John は自分の "Contact Information (連絡先情報)" ヘッダーを "John's Contact Information (John の連絡先情報)" と更新したいと考えています。彼はファイルを編集し、該当セクションを以下のようにします。
1
2 3 4 5 6 7 |
<section>
<h1>John’s Contact Information</h1> <ul> <li>Email: john@example.com</li> <li>Phone Number: (555) 555-1024</li> </ul> </section> |
ファイルを保存してページを再読み込みしたところで、間違いに気付きます。John は問題をどのように見つけられるでしょう?
今回のシンプルな例では、素直にファイル全体を読んで問題を特定することは比較的簡単です。しかし、巨大なファイルで相互に関連しあっている多くの部分を編集している場合は、非常に困難になる可能性が高いことは明白です。
ファイルのバージョン間の比較の際に、現在でも利用されているもっとも簡単な方法は、"diff (差分)" および "patch (パッチ)" と呼ばれるユーティリティの組み合わせです。近年のバージョン管理システムは、これらのツールの概念 (そしてファイル形式までも) を使用しています。それでは、これらがどのように動作するのかを見てみましょう。
Diff
diff は元々は 1970 年代初期に考案されました。その目的は、2 つのファイルのバージョンを入力内容として取り込み、最初のファイルと 2 つ目のファイルの変更箇所を示すテキストの塊を出力することです。
diff にはさまざまな形式が存在しますが、ここでは "ユニファイド形式 diff" として知られるもっとも一般的な形式を使用します。自宅で Linux や OS X 上で作業している場合、diff を実行してこの形式を取得するには以下のオプションを渡す必要があります。
1
|
-U3
|
架空のユーザー John は diff を使用して自分の変更内容を視覚的に表示しようとしています。しかし、その前にもう少ししなければならないことがあります。Web ページの元のバージョンをとって置く必要があるからです。John が自分の Web ページを編集したい場合、以下の手順を実行することになります。
- index.html ファイルを index-old.html にコピーして、ページのバックアップ コピーを作成する。
- 変更を加え、それを保存してプレビューを行う。
- diff を使用して index-old.html と index.html とを比較する。
diff を使用してこれらのファイルを実行すると、以下のような出力結果が表示されます。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
— index-old.html 2010-10-06 19:33:31.000000000 -0400
+++ index.html 2010-10-06 19:33:50.000000000 -0400 @@ -13,7 +13,7 @@ John is experienced in many areas of Java programming. – <h2>Contact Information</h2> + |
修正した行が 2 行出力されていることに注目してください。最初の行の前には '-' があります。これは、"この行は削除された" ことを表します。2 つ目の行の前には '+' があります。これは、"この行は追加された" ことを表します。
この例は diff に関する重要な点を表しています。つまり、John が行の数か所のみを変更した場合でも、diff は行全体を追加された、もしくは削除されたものとして処理します。標準の diff は行全体のみを処理します。
John は変更箇所を正確に把握できるようになったため、問題の特定は簡単です。彼は第 2 レベルのヘッダーを第 1 レベルのヘッダーに変更していました。John は問題を修正して変更を保存し、問題がなければ index-old.html ファイルを削除できます。
ファイル内の変更を容易に確認できる機能は diff を使用する最大のメリットの 1 つです。しかし、それ以外にも patch ユーティリティを使用してファイルを変換する際にも使用できます。
Patch
patch は diff が生成したテキストの塊を読み取り、ファイルに適用するのに使用されるユーティリティです。これにより、古いファイルは新しいバージョンに変換されます。
たとえば、John が友人の Mary に Web ページに関するアドバイスを求めるとしましょう。Mary はコードに目を通し、重要なセクションが目立つように少し変更を加えます。それから diff を実行し、自分の変更に関する以下のようなテキストの塊を生成します。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
— index-old.html 2010-10-06 19:33:50.000000000 -0400
+++ index.html 2010-10-06 19:50:08.000000000 -0400 @@ -1,7 +1,7 @@ – @@ -15,10 +15,10 @@ + + <section> </section>– <footer>Copyright John Smith, 2010</footer> + <footer>Copyright John Smith, 2010, All Rights Reserved</footer> <header> </header> |
彼女はこのテキストをファイルとして保存し、John にメールで送ります。
John はファイルを受け取ったら、Mary が追加したすべての変更を再度入力してファイルを保存することもできます。しかし、それは手間が掛かる上に入力を間違えるかもしれません。そうではなく、patch ユーティリティを使用して Mary の diff の出力結果を自分のファイルのコピーに "適用" することができます。
John 自身のファイルと Mary の diff を patch index.html index-from-mary.patch を使用して patch ユーティリティに読み込ませます。結果として、John のファイルのコピーは Mary のファイルのようになります。そして、他に何もすることなくそのファイルを Web サーバーにアップロードできます。
diff と patch の中心となるコンセプト
diff の目的を要約すると、「ファイルへの変更をテキストの塊として表現する」となります。このテキストの塊は人間が読むことで変更箇所を特定でき、ファイルとして保存して他のユーザーへメールで送信することも可能です。
patch の目的を要約すると、「diff が生成したテキストの塊を取得し、古いバージョンのファイルに適用することでそのファイルを新しいバージョンに変換する」となります。
これらのユーティリティは変更を共有するのに効果的です。たとえば、10,000 行あるファイルの 1 行を変更した場合、そのファイルの diff はたったの数バイトです。もしファイル全体を変換した場合、ファイルは 10 KB になるかもしれません。複数のファイルを処理するとしたら、この差は広がっていきます。
バージョン管理ツールを使用しないファイルのバージョン管理
diff および patch ユーティリティの大きなデメリットは、"古いファイル" と "新しいファイル" のたった 2 つのバージョンのファイルのみ取り扱うということです。実際のところ、期間中に更新が 1 回だけで済むプログラミング プロジェクトはまれです。
時間の経過とともにコード ファイルがどのように変化して行ったのかを確認することは有益です。そのためにはファイルの "バージョン" を保存する必要があります。これにより、後で比較することができます。1 つのやり方として、ファイルのコピーをたくさん作成し、番号を振って保存します。たとえば、以下のようになります。
1
2 3 4 5 6 7 |
index.html
index-2009-04-08.html index-2009-06-06.html index-2009-08-10.html index-2009-11-04.html index-2010-01-23.html index-2010-09-21.html |
この方法のデメリットは多くあります。
- 変更が 1 行の場合でも、ファイルのコピー全体を保存しておく必要があります。
- 同じ日に 2 つの異なるバージョンを保存する必要がある場合、番号付けはより複雑になります。
- 2 人のユーザーが同じ日にファイルを編集する場合が考えられます。
- 多くのバージョンのファイルが保存されるため、プロジェクト フォルダーがいっぱいになります。
- ハード ドライブが故障した場合、ファイルの履歴全体が消えてしまいます。
- 同僚に「X と Y との間の変更を確認して」と伝える場合、これらのバージョンをそのユーザーに送る必要があります。
バージョン管理ツールを使用しない場合のファイル グループのバージョンの管理
diff と patch のその他のデメリットは、一度に扱えるファイルは 1 つだけということです。
現実世界における多くのプログラミング プロジェクトでは、多くの ファイルを扱います。つまり、変更を適用する各 ファイル のコピーを保存しなければなりません。
さらに面倒なことに、あるファイルの変更が他のファイルに影響を及ぼす場合があります。C ヘッダー ファイルを変更した場合、ほぼ間違いなく関連する .c ファイルを変更する必要があります。つまり、これらのバージョンはグループとして一緒に処理しなければなりません。
ファイルのコピーを作成して自分でバージョンを管理することは、すぐに悪夢に変わります。バージョン管理システムは人間に代わってこれらを処理するために設計されたプログラムです。私たちはファイルのコピーに煩わされることなくコーディング作業に戻れます。
集中型バージョン管理 vs. 分散型バージョン管理
次回のエントリ では集中型バージョン管理のメリットとデメリットについてご紹介します。Subversion から Git への移行を検討中ですか? DVCS の鍵となるコンセプトを知りたいですか? 今後の "DVCS への切り替え" に関するブログ シリーズをどうぞお楽しみに。
(翻訳: ゴーツーグループ株式会社)
*本ブログは Atlassian Blogs を翻訳したものです。本文中の日時などは投稿当時のものですのでご了承ください。
*原文 : 2012 年 2 月 9 日投稿 "What is Version Control: Diffs and Patches"