もし図の表示がおかしかったら、このページの SVGでないバージョンを試して下さい。
SVG の画像処理を中止しています。 (SVG の画像処理を再開)
このページのオリジナルは、Mark Lodato さんが執筆した A Visual Git Referenceです。
このページでは、よく使われる git のコマンドを簡潔に図を用いて説明します。 git について少し知識があるなら、このページはその知識を整理するのに役立つかもしれません。このページがどのようにして作られたのか興味があるなら、私のGitHub リポジトリを見て下さい。(日本語訳の GitHub リポジトリ)
上記4つのコマンドは、作業ディレクトリ、ステージ(インデックスとも呼ばれる)、および履歴(一連のコミット)間でファイルをコピーします。
git add files
は files (の現在の状態)をステージにコピーします。git commit
は、ステージの内容をコミットとして保存します。git reset -- files
は、files のステージングを取りやめます。つまり、filesを最新のコミットからステージへコピーします。これは、git add files
を取り消すのに使えます。また、git reset
として、すべてのステージングを取り消すこともできます。git checkout -- files
は、filesをステージから作業ディレクトリへコピーします。ローカルの変更をすべて破棄するには、このコマンドを使って下さい。ファイルを特定しないで(あるいはファイルに加えて)、git reset -p
、git checkout -p
、あるいは
git add -p
を使えば、対話的にどのハンクを対象にするのか選べます。
これ以降、以下のような図を使っていきます。(訳注:図中の英語を日本語にする方法が分かりません。orz)
緑色はコミットで、5文字の識別子が付いており、自分の親を指しています。ブランチはオレンジ色で示してあり、ある特定のコミットを指しています。現在のブランチは、特殊な参照である HEAD によって識別され、その情報はブランチに*くっついて*います。この図では、最新の5つのコミットが表示されており、一番新しいコミットには ed489 という識別子が付いています。現在のブランチである master は、このコミットを指しています。一方、もう一つのブランチ maint は、master が指すコミットの祖先を指しています。
差分を取る方法は、たくさんあります。以下によく使われるコマンドの例を示します。すべてのコマンドには、ファイル名を指定することもでき、その場合は差分の計算をそのファイルに限定します。
コミットを実行すると、git はステージングされたファイルから、新しいコミットオブジェクトを作り、その親として最新のコミットを設定します。そして、現在のブランチをこの新しいコミットへ移動させます。以下の図では、現在のブランチはmasterです。このコマンドを実行する前は、masterは、ed489を指していました。実行後は、新しいコミット f0cec が作られ、親が ed489となり、masterがこの新しいコミットへ移動します。
たとえ、現在のブランチが中間のコミット(あるコミットの親であるコミット)であっても、同じことが起きます。以下の図では、masterの祖先であるmaintでコミットが発生し、その結果が1800bとなりました。これ以降、maintは、masterの祖先ではなくなります。この二つの履歴を統合するには、merge (あるいは rebase) を実行する必要があります。
ときどき、間違ったコミットを実行してしまうこともあるでしょう。git commit --amend
を使えば、簡単に訂正できます。このコマンドを使うと、git は最新のコミットと同じ親を持つ新しいコミットを作ります。(古いコミットは、何からも参照されていなければ破棄されます。)
4番目の使い方は 分離HEADへのコミットですが、これについては後述します。
checkout コマンドは、履歴(またはステージ)から作業ディレクトリへファイルをコピーするために使います。条件によっては、ブランチを切り替えます。
ファイル名(と加えて-p
と)が与えられると、git は指定されたファイルを指定されたコミットからステージと作業ディレクトリへコピーします。たとえば、git checkout HEAD~ foo.c
はファイル foo.c
を HEAD~ というコミット(最新のコミットの親)から作業ディレクトリへコピーし、同時にステージングします(もしコミット名が指定されないと、ファイルはステージからコピーされます)。現在のブランチには、何の変更もないことに注意しましょう。
ファイル名が指定*されず*、参照がローカルのブランチである場合、HEADがそのブランチに移動し(つまり、ブランチが切り替わり)、ステージと作業ディレクトリがそのコミットの内容になるよう設定されます。(下の図では a47c3 という名前の)現在コミットに存在するファイルすべてがコピーされます。(ed489 という名前の)以前のコミットに存在するが、現在のコミットには存在しないファイルは、削除されます。どちらにも存在しないファイルは、そのまま残されます。
ファイル名が指定されず、参照がローカルのブランチでない場合、すなわちタグやリモートブランチ、SHA-1 の名前、あるいは master~3 のように指定された場合は、無名のブランチができます。これは、分離HEADと呼ばれます。この機能を使えば、履歴の中を飛び回れます。たとえば、git バージョン 1.6.6.1 をコンパイルしたいなら、git checkout v1.6.6.1
(v1.6.6.1 はブランチではなくタグ)とした後に、コンパイルしてインストールし、git checkout master
のようにブランチに戻ってくればよいのです。ただし、分離HEADへのコミットは、通常とは若干ことなる動作をします。これについては、以下で説明します。
HEAD が分離されている場合、名前のついたブランチは何も更新されないという点を除いて、commit は通常と同じ動作をします。(これを無名ブランチだと考えてもよいでしょう。)
なんらかのコマンド、たとえば master を checkout すると、このコミットは他の何からも参照されなくなるので、削除されます。以下の図で、このコマンドを実行した後、2eecb は何からも参照されていないことに注意して下さい。
逆にもし、この状態を保存したいなら、git checkout -b name
を使って、名前付きのブランチを新しく作ればよいのです。
reset コマンドは、現在のブランチの位置を他へ移動します。条件によっては、ステージと作業ディレクトリの内容を更新します。このコマンドは、作業ディレクトリを変更せずに、履歴からステージへファイルをコピーする目的でも使われます。
ファイル名なしでコミットが指定されると、現在のブランチがそのコミットに移動し、ステージの内容はそのコミットと同じになります。--hard
が指定されると、作業ディレクトリも更新されます。--soft
が指定されると、どちらも更新されません。
もしコミットが指定されないと、HEAD が指定されたことになります。この場合、ブランチは移動しませんが、ステージの内容が、最新のコミットの内容にリセットされます。(もし --hard
が指定されると、作業ディレクトリもリセットされます。)
もしファイル名(および -p
)が指定されると、ファイル名を指定した checkout のように動作しますが、更新されるのは作業ディレクトリではなく、ステージのみです。(HEAD以外のコミットからファイルをコピーするように、コミットを指定することもできます。)
merge は、他のコミットから変更を受け入れるための新しいコミットを作成します。merge する前は、ステージが最新のコミットと一致していないといけません。つまらない例としては、他のコミットが現在のコミットの祖先である場合が挙げられます。この場合は、何も起きません。次に簡単な例としては、現在のコミットが他のコミットの祖先である場合です。これは、fast-forward マージとなります。単に参照が移動して、移動先のコミットが checkout されます。
それ以外の場合では、本当のマージが起こります。マージの戦略は選択可能ですが、通常は再帰マージが選ばれます。この戦略では基本的に、現在のコミット(ed489 below) と指定されたコミット(33104、other) の共通の先祖であるコミット(b325c) が特定され、three-way マージが実行されます。結果は、作業ディレクトリとステージに反映され、commit が実行されます。このコミットでは、二人目の親として (33104)が指定されます。
cherry-pick コマンドは、あるコミットからメッセージとパッチをコピーし、現在のブランチに新しいコミットを作ります。
rebase は、merge の代替コマンドあり、複数のブランチを接ぎ木します。merge は 2 つの親を持つ 1 つのコミットを作成し、履歴を一直線に保ちませんが、rebase は現在のブランチにあるコミットを他のブランチで再現することで履歴を一直線に保ちます。本質的に、rebase は自動的に連続で cherry-pick をすることに他なりません。
上記のコマンドは、topicブランチに存在するがmasterには存在しないすべてのコミット(すなわち169a6と 2c33a)をmasterブランチ上で再現します。そして、ブランチを新しい先端に移動させます。古いコミットは、他から参照されてないので、ゴミとして回収されます。
履歴をどれだけたどるかを制限するためには、--onto
オプションを使います。次のコマンドは、現在のブランチの 169a6 より後 (それ自体は含まない)のコミット(つまり2c33a)をmasterブランチで再現します。
コミットを再現するだけでなく、もっと複雑なことを指定できるコマンド git rebase --interactive
もあります。このコマンドでは、コミットを削除したり、変更したり、コミット同士を統合したりできます。これを表現する適切な図は存在しません。詳細は、git-rebase(1) を参照して下さい。
ファイルの内容は実際には、インデックスファイル(.git/index)やコミットオブジェクトの中には格納されません。ファイルはオブジェクトデーダベース(.git/objects)にblobとして格納され、SHA-1 ハッシュで識別されます。インデックスファイルは、ファイル名、対応するblobの識別子、そして他のデータの一覧です。他にもtreeというデータ型があって、これもハッシュで識別されます。treeは作業ディレクトリに含まれるディレクトリに対応し、treeやそのディレクトリに含まれるファイルに対応するblobを格納しています。それぞれのコミットは、最上位のtreeの識別子を格納しており、そのtreeは commit されるすべての blob と他のtreeを格納しています。
分離HEADを使って commit すると、最後のコミットは HEAD の reflog によって参照されます。しかし、これはしばらくするとなくなり、コミットはゴミとして回収されます。これは、git commit --amend
や git rebase
で破棄されたコミットと同様です。
Copyright © 2010, Mark Lodato. This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
訳の著作権は山本和彦に属します。