Git 🌙
Chapters ▾ 2nd Edition

7.1 Git のさまざまなツール - リビジョンの選択

Git を使ったソースコード管理のためのリポジトリの管理や保守について、日々使用するコマンドやワークフローの大半を身につけました。 ファイルの追跡やコミットといった基本的なタスクをこなせるようになっただけではなくステージングエリアの威力もいかせるようになりました。また気軽にトピックブランチを切ってマージする方法も知りました。

では、Git の非常に強力な機能の数々をさらに探っていきましょう。日々の作業でこれらを使うことはあまりありませんが、いつかは必要になるかもしれません。

リビジョンの選択

Git で特定のコミットやコミットの範囲を指定するにはいくつかの方法があります。 明白なものばかりではありませんが、知っておくと役立つでしょう。

単一のリビジョン

SHA-1 ハッシュを指定すれば、コミットを明確に参照することができます。しかしそれ以外にも、より人間にやさしい方式でコミットを参照することもできます。 このセクションでは単一のコミットを参照するためのさまざまな方法の概要を説明します。

SHA の短縮形

Git は、最初の数文字をタイプしただけであなたがどのコミットを指定したいのかを汲み取ってくれます。条件は、SHA-1 の最初の 4 文字以上を入力していることと、それでひとつのコミットが特定できる (現在のリポジトリに、入力した文字ではじまる SHA-1 のコミットがひとつしかない) ことです。

あるコミットを指定するために git log コマンドを実行し、とある機能を追加したコミットを見つけました。

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

探していたのは、1c002dd.... で始まるコミットです。git show でこのコミットを見るときは、次のどのコマンドでも同じ結果になります (短いバージョンで、重複するコミットはないものとします)。

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

一意に特定できる範囲での SHA-1 の短縮形を Git に見つけさせることもできます。 git log コマンドで --abbrev-commit を指定すると、コミットを一意に特定できる範囲の省略形で出力します。デフォルトでは 7 文字ぶん表示しますが、それだけで SHA-1 を特定できない場合はさらに長くなります。

$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

ひとつのプロジェクト内での一意性を確保するには、普通は 8 文字から 10 文字もあれば十分すぎることでしょう。

参考までに数字を挙げておきます。Linux カーネルはコミット数45万、オブジェクト数360万という巨大プロジェクトですが、SHA-1 の最初の12桁が同じになるオブジェクトは存在しません。

注記
SHA-1 に関するちょっとしたメモ

「リポジトリ内のふたつのオブジェクトがたまたま同じ SHA-1 ハッシュ値を持ってしまったらどうするの?」と心配する人も多いでしょう。 実際、どうなるのでしょう?

すでにリポジトリに存在するオブジェクトと同じ SHA-1 値を持つオブジェクトをコミットしてした場合、Git はすでにそのオブジェクトがデータベースに格納されているものと判断します。 そのオブジェクトを後からどこかで取得しようとすると、常に最初のオブジェクトのデータが手元にやってきます (訳注: つまり、後からコミットした内容は存在しないことになってしまう)。

しかし、そんなことはまず起こりえないということを知っておくべきでしょう。SHA-1 ダイジェストの大きさは 20 バイト (160 ビット) です。ランダムなハッシュ値がつけられた中で、たった一つの衝突が 50% の確率で発生するために必要なオブジェクトの数は約 2^80 となります (衝突の可能性の計算式は p = (n(n-1)/2) * (1/2^160) です)。 2^80 は、ほぼ 1.2 x 10^24 、つまり一兆二千億のそのまた一兆倍です。 これは、地球上にあるすべての砂粒の数の千二百倍にあたります。

SHA-1 の衝突を見るにはどうしたらいいのか、ひとつの例をごらんに入れましょう。 地球上の人類 65 億人が全員プログラムを書いていたとします。そしてその全員が、Linux カーネルのこれまでの開発履歴 (360 万の Git オブジェクト) と同等のコードを一秒で書き上げ、馬鹿でかい単一の Git リポジトリにプッシュしていくとします。これを2年ほど続けると、SHA-1 オブジェクトの衝突がひとつでも発生する可能性がやっと 50% になります。 それよりも「あなたの所属する開発チームの全メンバーが、同じ夜にそれぞれまったく無関係の事件で全員オオカミに殺されてしまう」可能性のほうがよっぽど高いことでしょう。

ブランチの参照

特定のコミットを参照するのに一番直感的なのは、そのコミットを指すブランチがある場合です。 コミットオブジェクトや SHA-1 値を指定する場面ではどこでも、その代わりにブランチ名を指定することができます。 たとえば、あるブランチ上の最新のコミットを表示したい場合は次のふたつのコマンドが同じ意味となります (topic1 ブランチが ca82a6d を指しているものとします)。

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

あるブランチがいったいどの SHA を指しているのか、あるいはその他の例の内容が結局のところどの SHA に行き着くのかといったことを知るには、Git の調査用ツールである rev-parse を使います。 こういった調査用ツールのより詳しい情報は [ch10-git-internals] で説明します。rev-parse は低レベルでの操作用のコマンドであり、日々の操作で使うためのものではありません。 しかし、今実際に何が起こっているのかを知る必要があるときなどには便利です。 ブランチ上で rev-parse を実行すると、このようになります。

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

参照ログの短縮形

あなたがせっせと働いている間に Git が裏でこっそり行っていることのひとつが、“参照ログ” (reflog) の管理です。これは、HEAD とブランチの参照が過去数ヶ月間どのように動いてきたかをあらわすものです。

参照ログを見るには git reflog を使います。

$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD

何らかの理由でブランチの先端が更新されるたびに、Git はその情報をこの一時履歴に格納します。そして、このデータを使って過去のコミットを指定することもできます。リポジトリの HEAD の五つ前の状態を知りたい場合は、先ほど見た reflog の出力のように @{n} 形式で参照することができます。

$ git show HEAD@{5}

この構文を使うと、指定した期間だけさかのぼったときに特定のブランチがどこを指していたかを知ることもできます。 たとえば master ブランチの昨日の状態を知るには、このようにします。

$ git show master@{yesterday}

こうすると、そのブランチの先端が昨日どこを指していたかを表示します。 この技が使えるのは参照ログにデータが残っている間だけなので、直近数ヶ月よりも前のコミットについては使うことができません。

参照ログの情報を git log の出力風の表記で見るには git log -g を実行します。

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

参照ログの情報は、完全にローカルなものであることに気をつけましょう。これは、あなた自身が自分のリポジトリで何をしたのかを示す記録です。 つまり、同じリポジトリをコピーした別の人の参照ログとは異なる内容になります。また、最初にリポジトリをクローンした直後の参照ログは空となります。まだリポジトリ上であなたが何もしていないからです。 git show HEAD@{2.months.ago} が動作するのは、少なくとも二ヶ月以上前にそのリポジトリをクローンした場合のみで、もしつい 5 分前にクローンしたばかりなら何も結果を返しません。

家系の参照

コミットを特定する方法として他によく使われるのが、その家系をたどっていく方法です。 参照の最後に ^ をつけると、Git はそれを「指定したコミットの親」と解釈します。 あなたのプロジェクトの歴史がこのようになっていたとしましょう。

$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list

直前のコミットを見るには HEAD^ を指定します。これは “HEAD の親” という意味になります。

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

^ の後に数字を指定することもできます。たとえば d921970^2 は “d921970 の二番目の親” という意味になります。 これが役立つのはマージコミット (親が複数存在する) のときくらいでしょう。 最初の親はマージを実行したときにいたブランチとなり、二番目の親は取り込んだブランチ上のコミットとなります。

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes

家系の指定方法としてもうひとつよく使うのが ~ です。 これも最初の親を指します。つまり HEAD~HEAD^ は同じ意味になります。 違いが出るのは、数字を指定したときです。 HEAD~2 は「最初の親の最初の親」、 つまり「祖父母」という意味になります。指定した数だけ、順に最初の親をさかのぼっていくことになります。 たとえば、先ほど示したような歴史上では HEAD~3 は次のようになります。

$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

これは HEAD^^^ のようにあらわすこともできます。これは「最初の親の最初の親の最初の親」という意味になります。

$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

これらふたつの構文を組み合わせることもできます。直近の参照 (マージコミットだったとします) の二番目の親を取得するには HEAD~3^2 などとすればいいのです。

コミットの範囲指定

個々のコミットを指定できるようになったので、次はコミットの範囲を指定する方法を覚えていきましょう。 これは、ブランチをマージするときに便利です。たくさんのブランチがある場合など、「で、このブランチの作業のなかでまだメインブランチにマージしていないのはどれだったっけ?」といった疑問を解決するために範囲指定を使えます。

ダブルドット

範囲指定の方法としてもっとも一般的なのが、ダブルドット構文です。 これは、ひとつのコミットからはたどれるけれどもうひとつのコミットからはたどれないというコミットの範囲を Git に調べさせるものです。 範囲指定選択用の歴史の例 のようなコミット履歴を例に考えましょう。

範囲指定選択用の歴史の例
図 136. 範囲指定選択用の歴史の例

experiment ブランチの内容のうち、まだ master ブランチにマージされていないものを調べることになりました。 対象となるコミットのログを見るには、Git に master..experiment と指示します。これは「experiment からはたどれるけれど、master からはたどれないすべてのコミット」という意味です。 説明を短く簡潔にするため、実際のログの出力のかわりに上の図の中でコミットオブジェクトをあらわす文字を使うことにします。

$ git log master..experiment
D
C

もし逆に、master には存在するけれども experiment には存在しないすべてのコミットが知りたいのなら、ブランチ名を逆にすればいいのです。 experiment..master とすれば、master のすべてのコミットのうち experiment からたどれないものを取得できます。

$ git log experiment..master
F
E

これは、experiment ブランチを最新の状態に保つために何をマージしなければならないのかを知るのに便利です。 もうひとつ、この構文をよく使う例としてあげられるのが、これからリモートにプッシュしようとしている内容を知りたいときです。

$ git log origin/master..HEAD

このコマンドは、現在のブランチ上でのコミットのうち、リモート originmaster ブランチに存在しないものをすべて表示します。 現在のブランチが origin/master を追跡しているときに git push を実行すると、git log origin/master..HEAD で表示されたコミットがサーバーに転送されます。 この構文で、どちらか片方を省略することもできます。その場合、Git は省略したほうを HEAD とみなします。 たとえば、git log origin/master.. と入力すると先ほどの例と同じ結果が得られます。Git は、省略した側を HEAD に置き換えて処理を進めるのです。

複数のポイント

ダブルドット構文は、とりあえず使うぶんには便利です。しかし、二つよりもっと多くのブランチを指定してリビジョンを特定したいこともあるでしょう。複数のブランチの中から現在いるブランチには存在しないコミットを見つける場合などです。 Git でこれを行うには ^ 文字を使うか、あるいはそこからたどりつけるコミットが不要な参照の前に --not をつけます。 これら三つのコマンドは、同じ意味となります。

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

これらの構文が便利なのは、二つよりも多くの参照を使って指定できるというところです。ダブルドット構文では二つの参照しか指定できませんでした。 たとえば、refArefB のどちらかからはたどれるけれども refC からはたどれないコミットを取得したい場合は、次のいずれかを実行します。

$ git log refA refB ^refC
$ git log refA refB --not refC

この非常に強力なリビジョン問い合わせシステムを使えば、今あなたのブランチに何があるのかを知るのに非常に役立つことでしょう。

トリプルドット

範囲指定選択の主な構文であとひとつ残っているのがトリプルドット構文です。これは、ふたつの参照のうちどちらか一方からのみたどれるコミット (つまり、両方からたどれるコミットは含まない) を指定します。 範囲指定選択用の歴史の例 で示したコミット履歴の例を振り返ってみましょう。 master あるいは experiment に存在するコミットのうち、両方に存在するものを除いたコミットを知りたい場合は次のようにします。

$ git log master...experiment
F
E
D
C

これは通常の log の出力と同じですが、これら四つのコミットについての情報しか表示しません。表示順は、従来どおりコミット日時順となります。

この場合に log コマンドでよく使用するスイッチが --left-right です。このスイッチは、それぞれのコミットがどちら側に存在するのかを表示します。 これを使うとデータをより活用しやすくなるでしょう。

$ git log --left-right master...experiment
< F
< E
> D
> C

これらのツールを使えば、より簡単に「どれを調べたいのか」を Git に伝えられるようになります。

scroll-to-top