Pro Git

Git公式で公開されているPro Git

1.1 Getting Started - About Version Control

Local Version Control Systems

ディレクトリで整理するアプローチは一般的だが、問題も多い。

  • RCS

多くの人々が使っているバージョン管理手法は、他のディレクトリ(気の利いた人であれば、日時のついたディレクトリ)にファイルをコピーするというものです。 このアプローチはとても単純なので非常に一般的ですが、信じられないほど間違いが起こりやすいものです。 どのディレクトリにいるのか忘れやすく、うっかり間違ったファイルに書き込んだり、上書きするつもりのないファイルを上書きしてしまったりします。

Centralized Version Control Systems (CVCs)

中央管理型のバージョン管理システム。

  • CVS、Subversion、Perforce

もし中央データベースのあるハードディスクが破損し、適切なバックアップが保持されていなければ、完全に全てを失ってしまいます。プロジェクトの全ての履歴は失われ、残るのは個人のローカル・マシンにたまたまあった幾らかの単一スナップショット(訳者注:ある時点のファイル、ディレクトリなどの編集対象の状態)ぐらいです。 ローカルVCSシステムも、これと同じ問題があります。つまり、一つの場所にプロジェクトの全体の履歴を持っていると、全てを失うリスクが常にあります。

Distributed Version Control Systems (DVCs)

分散管理型のバージョン管理システム。

  • Git、Mercurial、Bazaar、Darcs

あるサーバーが故障して、DVCSがそのサーバーを介して連携していたとしても、どれでもいいのでクライアント・リポジトリの一つをサーバーにコピーすれば修復できます。 クローンは全て、実際は全データの完全バックアップなのです。

1.3 Getting Started - What is Git

Snapshots, Not Differences

従来のバージョン管理システムはファイルを基本とした変更差分を扱う。

概念的には、他のシステムのほとんどは、情報をファイルを基本とした変更のリストとして格納します。
Git width=640

Gitはコミットの状態をスナップショットとして扱う。このデータ構造の違いがブランチの扱いの違い大きく影響している。

Gitはデータをミニ・ファイルシステムのスナップショットの集合のように考えます。 Gitで全てのコミット(訳注:commitとは変更を記録・保存するGitの操作。詳細は後の章を参照)をするとき、もしくはプロジェクトの状態を保存するとき、Gitは基本的に、その時の全てのファイルの状態のスナップショットを撮り(訳者注:意訳)、そのスナップショットへの参照を格納するのです。 効率化のため、ファイルに変更が無い場合は、Gitはファイルを再格納せず、既に格納してある、以前の同一のファイルへのリンクを格納します。 Gitは、むしろデータを一連のスナップショットのように考えます。
Git width=640

Git Has Integrity

SHAで照合されるのでファイル破損を心配しなくていい。

Gitの全てのものは、格納される前にチェックサムが取られ、その後、そのチェックサムで照合されます。 これは、Gitがそれに関して感知することなしに、あらゆるファイルの内容を変更することが不可能であることを意味します。 この機能は、Gitの最下層に組み込まれ、またGitの哲学に不可欠です。 Gitがそれを感知できない状態で、転送中に情報を失う、もしくは壊れたファイルを取得することはありません。

ハッシュ値ベースの管理

Gitはファイル名ではなく、ファイル内容のハッシュ値によってGitデータベースの中に全てを格納しています。

Git Generally Only Adds Data

コミットした内容を変更するのはめんどくさい。

Gitで行動するとき、ほとんど全てはGitデータベースにデータを追加するだけです。 システムにいかなる方法でも、UNDO不可能なこと、もしくはデータを消させることをさせるのは困難です。 あらゆるVCSと同様に、まだコミットしていない変更は失ったり、台無しにできたりします。

The Three States

ローカル操作する上でこのthe Three Statesのイメージを理解する必要がある。

Gitは、ファイルが帰属する、コミット済、修正済、ステージ済の、三つの主要な状態を持ちます。 コミット済は、ローカル・データベースにデータが安全に格納されていることを意味します。 修正済は、ファイルに変更を加えていますが、データベースにそれがまだコミットされていないことを意味します。 ステージ済は、次のスナップショットのコミットに加えるために、現在のバージョンの修正されたファイルに印をつけている状態を意味します。
このことは、Gitプロジェクト(訳者注:ディレクトリ内)の、Gitディレクトリ、作業ディレクトリ、ステージング・エリアの三つの主要な部分(訳者注:の理解)に導きます。
Git width=640

2.2 Git Basics - Recording Changes to the Repository

Recording Changes to the Repository

Tracked/Untrackedも意識しつつ状態のライフサイクルを理解する。

作業コピー内の各ファイルには追跡されている(tracked)ものと追跡されてない(untracked)ものの二通りがあることを知っておきましょう。 追跡されているファイルとは、直近のスナップショットに存在したファイルのことです。これらのファイルについては変更されていない(unmodified)」「変更されている(modified)」「ステージされている(staged)」の三つの状態があります。 追跡されていないファイルは、そのどれでもありません。直近のスナップショットには存在せず、ステージングエリアにも存在しないファイルのことです。 最初にプロジェクトをクローンした時点では、すべてのファイルは「追跡されている」かつ「変更されていない」状態となります。チェックアウトしただけで何も編集していない状態だからです。
ファイルを編集すると、Git はそれを「変更された」とみなします。直近のコミットの後で変更が加えられたからです。変更されたファイルをステージし、それをコミットする。この繰り返しです。
Git width=640

3.1 Git Branching - Branches in a Nutshell

Branches in a Nutshell

コミットを繰返すとポインターが自動的に進んでいく。

最初にコミットした時点で、直近のコミットを指す master ブランチが作られます。その後コミットを繰り返すたびに、このポインタは自動的に進んでいきます。
Git width=640

masterブランチも他と変わらない。慣習的に意味を持つだけ。

Git の “master” ブランチは、特別なブランチというわけではありません。 その他のブランチと、何ら変わるところのないものです。 ほぼすべてのリポジトリが “master” ブランチを持っているたったひとつの理由は、 git init コマンドがデフォルトで作るブランチが “master” である (そして、ほとんどの人はわざわざそれを変更しようとは思わない) というだけのことです。

HEADは現在の作業ブランチへのポインターを意味している。

Git は、あなたが今どのブランチで作業しているのかをどうやって知るのでしょうか? それを保持する特別なポインタが HEAD と呼ばれるものです。 これは、Subversion や CVS といった他の VCS における HEAD の概念とはかなり違うものであることに注意しましょう。 Git では、HEAD はあなたが作業しているローカルブランチへのポインタとなります。 今回の場合は、あなたはまだ master ブランチにいます。 git branch コマンドは新たにブランチを作成するだけであり、 そのブランチに切り替えるわけではありません。

ブランチの切替で作業ディレクトリのファイルも切り替わる。

気をつけておくべき重要なこととして、Git でブランチを切り替えると、作業ディレクトリのファイルが変更されることを知っておきましょう。 古いブランチに切り替えると、作業ディレクトリ内のファイルは、最後にそのブランチ上でコミットした時点の状態まで戻ってしまいます。 Git がこの処理をうまくできない場合は、ブランチの切り替えができません。

3.2 Git Branching - Basic Branching and Merging

Basic Branching and Merging

マージコミットによってブランチを統合する。

単にブランチのポインタを先に進めるのではなく、Git はこの三方向のマージ結果から新たなスナップショットを作成し、それを指す新しいコミットを自動作成します。 これはマージコミットと呼ばれ、複数の親を持つ特別なコミットとなります。
マージの基点として使用する共通の先祖を Git が自動的に判別するというのが特筆すべき点です。 CVS や Subversion (バージョン 1.5 より前のもの) は、マージの基点となるポイントを自分で見つける必要があります。 これにより、他のシステムに比べて Git のマージが非常に簡単なものとなっているのです。

Git width=640

Git width=640

Basic Merge Conflicts

同じ部分を変更している場合はうまくマージできず、コンフリクトになる。

同じファイルの同じ部分をふたつのブランチで別々に変更してそれをマージしようとすると、Git はそれをうまくマージする方法を見つけられないでしょう。
Git は、標準的なコンフリクトマーカーをファイルに追加するので、ファイルを開いてそれを解決することにします。 コンフリクトが発生したファイルの中には、このような部分が含まれています。

1
2
3
4
5
6
7
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

これは、HEAD (merge コマンドを実行したときにチェックアウトしていたブランチなので、ここでは master となります) の内容が上の部分 (======= の上にある内容)、そして iss53 ブランチの内容が下の部分であるということです。 コンフリクトを解決するには、どちらを採用するかをあなたが判断することになります。 たとえば、ひとつの解決法としてブロック全体を次のように書き換えます。

1
2
3
<div id="footer">
please contact us at email.support@github.com
</div>

このような解決を各部分に対して行い、>>>>> の行をすべて除去します。 そしてすべてのコンフリクトを解決したら、各ファイルに対して git add を実行して解決済みであることを通知します。 ファイルをステージすると、Git はコンフリクトが解決されたと見なします。

補足(Visual Studio Codeのコンフリクト処理)

Visual Studio Codeではコンフリクト状態をどうするか選択の処理が自動挿入される。

VSCode Conflict width=640

3.4 Git Branching - Branching Workflows

Long-Running Branches

慣習的によく使われるdevelopブランチなどのこと。

Git では簡単に三方向のマージができるので、あるブランチから別のブランチへのマージを長期間にわたって繰り返すのも簡単なことです。 つまり、複数のブランチを常にオープンさせておいて、それぞれ開発サイクルにおける別の場面用に使うということもできます。 定期的にブランチ間でのマージを行うことが可能です。

Topic Branches

個別の改修で作成するトピックブランチのこと。

一方、トピックブランチはプロジェクトの規模にかかわらず便利なものです。 トピックブランチとは、短期間だけ使うブランチのことで、何か特定の機能やそれに関連する作業を行うために作成します。 これは、今までの VCS では実現不可能に等しいことでした。 ブランチを作成したりマージしたりという作業が非常に手間のかかることだったからです。 Git では、ブランチを作成して作業をし、マージしてからブランチを削除するという流れを一日に何度も繰り返すことも珍しくありません。

3.5 Git Branching - Remote Branches

Remote Branches

クローンすると自動で origin/master が作成される。master同様 origin に機能的な特別な意味はない。慣習的な物としてはフォークしたプロジェクトのフォーク元として upstream などがある。

リモート参照は、リモートリポジトリにある参照(ポインタ)です。具体的には、ブランチやタグなどを指します。 リモート参照をすべて取得するには、git ls-remote [remote] を実行してみてください。また、git remote show [remote] を実行すれば、リモート参照に加えてその他の情報も取得できます。 とはいえ、リモート参照の用途としてよく知られているのは、やはりリモート追跡ブランチを活用することでしょう。
リモート追跡ブランチは、リモートブランチの状態を保持する参照です。 ローカルに作成される参照ですが、自分で移動することはできません。ネットワーク越しの操作をしたときに自動的に移動します。 リモート追跡ブランチは、前回リモートリポジトリに接続したときにブランチがどの場所を指していたかを示すブックマークのようなものです。
ブランチ名は (remote)/(branch) のようになります。 たとえば、origin サーバーに最後に接続したときの master ブランチの状態を知りたければ origin/master ブランチをチェックします。 誰かほかの人と共同で問題に対応しており、相手が iss53 ブランチにプッシュしたとしましょう。 あなたの手元にはローカルの iss53 ブランチがあります。しかし、サーバー側のブランチは origin/iss53 のコミットを指しています。
Git の “master” ブランチがその他のブランチと何ら変わらないものであるのと同様に、 “origin” もその他のサーバーと何ら変わりはありません。 “master” ブランチがよく使われている理由は、ただ単に git init がデフォルトで作るブランチ名がそうだからというだけのことでした。 同様に “origin” も、git clone を実行するときのデフォルトのリモート名です。 たとえば git clone -o booyah などと実行すると、デフォルトのリモートブランチは booyah/master になります。
手元での作業を同期させるには、git fetch origin コマンドを実行します。 このコマンドは、まず “origin” が指すサーバー (今回の場合は git.ourcompany.com) を探し、まだ手元にないデータをすべて取得し、ローカルデータベースを更新し、origin/master が指す先を最新の位置に変更します。

Pushing

ローカルで作成したブランチはPushでリモートに同期する。

ブランチの内容をみんなと共有したくなったら、書き込み権限を持つどこかのリモートにそれをプッシュしなければなりません。 ローカルブランチの内容が自動的にリモートと同期されることはありません。 共有したいブランチは、明示的にプッシュする必要があります。

Tracking Branches

リモートに紐づいたブランチのこと。

リモート追跡ブランチからローカルブランチにチェックアウトすると、“追跡ブランチ” というブランチが自動的に作成されます(そしてそれが追跡するブランチを`‘上流ブランチ’’といいます)。 追跡ブランチとは、リモートブランチと直接のつながりを持つローカルブランチのことです。 追跡ブランチ上で git pull を実行すると、Git は自動的に取得元のサーバーとブランチを判断します。

Pulling

リモート側の変更を取り込む。

git fetch コマンドは、サーバー上の変更のうち、まだ取得していないものをすべて取り込みます。 しかし、ローカルの作業ディレクトリは書き換えません。 データを取得するだけで、その後のマージは自分でしなければいけません。 git pull コマンドは基本的に、git fetch の実行直後に git merge を実行するのと同じ動きになります。 先ほどのセクションのとおりに追跡ブランチを設定した場合、git pull は、 現在のブランチが追跡しているサーバーとブランチを調べ、そのサーバーからフェッチしたうえで、リモートブランチのマージを試みます。
一般的には、シンプルに fetch と merge を明示したほうがよいでしょう。 git pull は、時に予期せぬ動きをすることがあります。

3.6 Git Branching - Rebasing

Rebasing

リベースでコミットメッセージをきれいにする。

別の方法もあります。 C3 で行った変更のパッチを取得し、それを C4 の先端に適用するのです。 Git では、この作業のことを リベース (rebasing) と呼んでいます。 rebase コマンドを使用すると、一方のブランチにコミットされたすべての変更をもう一方のブランチで再現することができます。
最終的な統合結果には差がありませんが、リベースのほうがよりすっきりした歴史になります。 リベース後のブランチのログを見ると、まるで一直線の歴史のように見えます。 元々平行稼働していたにもかかわらず、それが一連の作業として見えるようになるのです。
リモートブランチ上での自分のコミットをすっきりさせるために、よくこの作業を行います。 たとえば、自分がメンテナンスしているのではないプロジェクトに対して貢献したいと考えている場合などです。 この場合、あるブランチ上で自分の作業を行い、プロジェクトに対してパッチを送る準備ができたらそれを origin/master にリベースすることになります。 そうすれば、メンテナは特に統合作業をしなくても単に fast-forward するだけで済ませられるのです。

The Perils of Rebasing

公開リポジトリにプッシュしたコミットをリベースしてはいけない

Rebase When You Rebase

改変不可派

あなたのリポジトリにおけるコミットの歴史は、実際に発生したできごとの記録 だと見ることもできます。 これは歴史文書であり、それ自体に意味がある。従って、改ざんなど許されないという観点です。 この観点に沿って考えると、コミットの歴史を変更することなどあり得ないでしょう。 実際に起こってしまったことには、ただ黙って 従う べきです。 マージコミットのせいで乱雑になってしまったら? 実際そうなってしまったのだからしょうがない。 その記録は、後世の人々に向けてそのまま残しておくべきでしょう。

履歴整理派

コミットの歴史は、そのプロジェクトがどのように作られてきたのかを表す物語である という考えかたです。 最初の草稿の段階で本を出版したりはしないでしょう。また、自作ソフトウェア用の管理マニュアルであれば、しっかり推敲する必要があります。 この立場に立つと、リベースやブランチフィルタリングを使って、将来の読者にとってわかりやすいように、物語を再編しようという考えに至ります。

中庸派

一般論として、両者のいいとこどりをしたければ、まだプッシュしていないローカルの変更だけをリベースするようにして、 歴史をきれいに保っておきましょう。プッシュ済みの変更は決してリベースしないようにすれば、問題はおきません。

5.1 Distributed Git - Distributed Workflows

Centralized Workflow

リポジトリを共有し、開発者が自分のブランチを作成して平行開発するスタイル。

Git width=640

中央管理型のシステムでは共同作業の方式は一つだけです。それが中央集権型のワークフローです。 これは、中央にある一つのハブ (リポジトリ) がコードを受け入れ、他のメンバー全員がそこに作業内容を同期させるという流れです。 多数の開発者がハブにつながるノードとなり、作業を一か所に集約します。
二人の開発者がハブからのクローンを作成して個々に変更をした場合、最初の開発者がそれをプッシュするのは特に問題なくできます。 もう一人の開発者は、まず最初の開発者の変更をマージしてからサーバーへのプッシュを行い、最初の開発者の変更を消してしまわないようにします。 この考え方は、Git 上でも Subversion (あるいはその他の CVCS) と同様に生かせます。そしてこの方式は Git でも完全に機能します。
また、この例は小規模なチームに限った話ではありません。Git のブランチモデルを用いてひとつのプロジェクト上にたくさんのブランチを作れば、何百人もの開発者が同時並行で作業を進めることだってできるのです。

Integration-Manager Workflow

各開発者がリポジトリを持ち平行開発するスタイル。

Git width=640

Git では複数のリモートリポジトリを持つことができるので、書き込み権限を持つ公開リポジトリを各自が持ち、他のメンバーからは読み込みのみのアクセスを許可するという方式をとることもできます。 この方式には、「公式」プロジェクトを表す公式なリポジトリも含みます。 このプロジェクトの開発に参加するには、まずプロジェクトのクローンを自分用に作成し、変更はそこにプッシュします。 次に、メインプロジェクトのメンテナーに「変更を取り込んでほしい」とお願いします。 メンテナーはあなたのリポジトリをリモートに追加し、変更を取り込んでマージします。そしてその結果をリポジトリにプッシュするのです。

Dictator and Lieutenants Workflow

各開発者がリポジトリを持ち平行開発するスタイルの大規模パターン。

Git width=640

複数リポジトリ型のワークフローのひとつです。 何百人もの開発者が参加するような巨大なプロジェクトで採用されています。有名どころでは Linux カーネルがこの方式です。 統合マネージャーを何人も用意し、それぞれにリポジトリの特定の部分を担当させます。彼らは副官 (lieutenant) と呼ばれます。 そしてすべての副官をまとめる統合マネージャーが「慈悲深い独裁者 (benevalent dictator)」です。 独裁者のリポジトリが基準リポジトリとなり、すべてのメンバーはこれをプルします。

5.2 Distributed Git - Contributing to a Project

Commit Guidelines

コミットメッセージをきれいにする。

コミットメッセージについてのちょっとした注意点をお話しておきましょう。 コミットに関する指針をきちんと定めてそれを守るようにすると、Git での共同作業がよりうまく進むようになります。 Git プロジェクトでは、パッチの投稿用のコミットを作成するときのヒントをまとめたドキュメントを用意しています。Git のソースの中にある Documentation/SubmittingPatches をごらんください。
余計な空白文字を含めてしまわないように注意が必要です。 Git には、余計な空白文字をチェックするための簡単な仕組みがあります。コミットする前に git diff –check を実行してみましょう。おそらく意図したものではないと思われる空白文字を探し、それを教えてくれます。
コミットの単位が論理的に独立した変更となるようにしましょう。 つまり、個々の変更内容を把握しやすくするということです。
すべての変更を同時に追加しさえすれば、一度にコミットしようが五つのコミットに分割しようがブランチの先端は同じ状態になります。あとから変更内容をレビューする他のメンバーのことも考えて、できるだけレビューしやすい状態でコミットするようにしましょう。 こうしておけば、あとからその変更の一部だけを取り消したりするのにも便利です。
最後に注意しておきたいのが、コミットメッセージです。 よりよいコミットメッセージを書く習慣を身に着けておくと、Git を使った共同作業をより簡単に行えるようになります。 一般的な規則として、メッセージの最初には変更の概要を一行 (50 文字以内) にまとめた説明をつけるようにします。その後に空行をひとつ置いてからより詳しい説明を続けます。 Git プロジェクトでは、その変更の動機やこれまでの実装との違いなどのできるだけ詳しい説明をつけることを推奨しています。
メッセージでは命令形、現在形を使うようにしています。 つまり “私は○○のテストを追加しました (I added tests for)” とか “○○のテストを追加します (Adding tests for,)” ではなく “○○のテストを追加 (Add tests for.)” 形式にするということです。 Tim Pope が書いたテンプレート (の日本語訳) を以下に示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
短い (50 文字以下での) 変更内容のまとめ

必要に応じた、より詳細な説明。72文字程度で折り返します。最初の
行がメールの件名、残りの部分がメールの本文だと考えてもよいでしょ
う。最初の行と詳細な説明の間には、必ず空行を入れなければなりま
せん (詳細説明がまったくない場合は空行は不要です)。空行がないと、
rebase などがうまく動作しません。

空行を置いて、さらに段落を続けることもできます。

- 箇条書きも可能

- 箇条書きの記号としては、主にハイフンやアスタリスクを使います。
箇条書き記号の前にはひとつ空白を入れ、各項目の間には空行を入
れます。しかし、これ以外の流儀もいろいろあります。

Forked Public Project

フォークの作法。

まずはメインリポジトリをクローンしましょう。そしてパッチ用のトピックブランチを作り、そこで作業を進めます。 このような流れになります。
rebase -i を使ってすべての作業をひとつのコミットにまとめたり、メンテナがレビューしやすいようにコミット内容を整理したりといったことも行うかもしれません。
ブランチでの作業を終えてメンテナに渡せる状態になったら、プロジェクトのページに行って “Fork” ボタンを押し、自分用に書き込み可能なフォークを作成します。 このリポジトリの URL を追加のリモートとして設定しなければなりません。ここでは myfork という名前にしました。
今後、自分の作業内容はここにプッシュすることになります。 変更を master ブランチにマージしてからそれをプッシュするよりも、今作業中の内容をそのままトピックブランチにプッシュするほうが簡単でしょう。 もしその変更が受け入れられなかったり一部だけが取り込まれたりした場合に、master ブランチを巻き戻す必要がなくなるからです。メンテナがあなたの作業をマージするかリベースするかあるいは一部だけ取り込むか、いずれにせよあなたはその結果をリポジトリから再度取り込むことになります。
自分用のフォークに作業内容をプッシュし終えたら、それをメンテナに伝えましょう。 これは、よく「プルリクエスト」と呼ばれるもので、ウェブサイトから実行する (GutHub には Pull request を行う独自の仕組みがあります。詳しくは GitHub で説明します) こともできれば、 git request-pull コマンドの出力をプロジェクトのメンテナにメールで送ることもできます。
自分がメンテナになっていないプロジェクトで作業をする場合は、master ブランチでは常に origin/master を追いかけるようにし、自分の作業はトピックブランチで進めていくほうが楽です。そうすれば、パッチが拒否されたときも簡単にそれを捨てることができます。 また、作業内容ごとにトピックブランチを分離しておけば、本流のリポジトリが更新されてパッチがうまく適用できなくなったとしても簡単にリベースできるようになります。