章 ▾ 第2版

7.1 Gitツール - リビジョン選択

これまで、Gitリポジトリをソースコード管理のために日常的に管理または保守するために必要なほとんどのコマンドとワークフローを学習してきました。ファイルの追跡とコミットという基本的なタスクをこなし、ステージングエリアと軽量なトピックブランチおよびマージの力を活用してきました。

ここでは、日常的には必ずしも使用しないものの、いずれかの時点で必要になる可能性のある、Gitができる非常に強力な機能のいくつかを詳しく見ていきます。

リビジョン選択

Gitでは、単一のコミット、コミットのセット、またはコミットの範囲をさまざまな方法で参照できます。それらは必ずしも自明ではありませんが、知っておくと役立ちます。

単一のリビジョン

もちろん、コミットをその40文字の完全なSHA-1ハッシュで参照することもできますが、より人間が判読しやすい方法もあります。このセクションでは、任意のコミットを参照できるさまざまな方法を概説します。

短いSHA-1

SHA-1ハッシュの最初の数文字を指定した場合でも、その部分的なハッシュが少なくとも4文字長であり、曖昧でない限り(つまり、オブジェクトデータベース内の他のオブジェクトが同じプレフィックスで始まるハッシュを持たない限り)、Gitはあなたが参照しているコミットを十分に理解できます。

たとえば、特定の機能を追加したことがわかっている特定のコミットを調べるには、まずgit logコマンドを実行してそのコミットを特定します。

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

    Fix refs handling, add gc auto, update 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

    Add some blame and merge stuff

この場合、ハッシュが1c002dd…​で始まるコミットに興味があるとします。そのコミットは、git showの以下のバリエーションのいずれか(短いバージョンが曖昧でないと仮定して)で調べることができます。

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

GitはSHA-1値の短い一意な省略形を特定できます。--abbrev-commitgit logコマンドに渡すと、出力は短い値を使用しますが、一意性を保ちます。デフォルトでは7文字を使用しますが、SHA-1の曖昧さを避けるために必要に応じて長くします。

$ git log --abbrev-commit --pretty=oneline
ca82a6d Change the version number
085bb3b Remove unnecessary test code
a11bef0 Initial commit

通常、8〜10文字あれば、プロジェクト内で一意性を保つのには十分です。たとえば、2019年2月現在、Linuxカーネル(かなり大規模なプロジェクト)には875,000を超えるコミットと、オブジェクトデータベースに約700万個のオブジェクトがあり、最初の12文字でSHA-1が同一のオブジェクトは2つとして存在しません。

SHA-1に関する簡単な注意点

多くの人が、いずれかの時点で、偶然にもリポジトリ内に同じSHA-1値にハッシュされる2つの異なるオブジェクトが存在することに懸念を抱きます。その場合どうなるのでしょうか?

もし、以前の異なるオブジェクトと同じSHA-1値にハッシュされるオブジェクトをコミットしてしまった場合、Gitは既存のGitデータベースに以前のオブジェクトがすでにあることを認識し、それが既に書き込まれていると仮定して単にそれを再利用します。もし後でそのオブジェクトを再度チェックアウトしようとすると、常に最初のオブジェクトのデータが取得されます。

しかし、このシナリオがどれほど馬鹿げているほど起こりにくいかを知っておくべきです。SHA-1ダイジェストは20バイトまたは160ビットです。単一の衝突が50%の確率で発生するために必要なランダムにハッシュされたオブジェクトの数は約280です(衝突確率を決定する式はp = (n(n-1)/2) * (1/2^160))。280は1.2 x 1024、つまり100万兆兆です。これは地球上の砂粒の数の1,200倍です。

SHA-1衝突が起こるのにどれくらいの時間がかかるか、例を挙げて説明しましょう。もし地球上の65億人全員がプログラミングをしていて、毎秒、それぞれがLinuxカーネル全体の歴史(650万個のGitオブジェクト)に相当するコードを生成し、それを一つの巨大なGitリポジトリにプッシュし続けたとします。そのリポジトリに単一のSHA-1オブジェクト衝突が50%の確率で発生するのに十分なオブジェクトが含まれるまで、およそ2年かかります。したがって、自然発生的なSHA-1衝突は、プログラミングチームの全員が同じ夜に別々の事件でオオカミに襲われて死亡するよりも起こりにくいことです。

数千ドル相当の計算能力を投入すれば、2017年2月にhttps://shattered.io/で証明されたように、同じハッシュを持つ2つのファイルを合成することは可能です。GitはデフォルトのハッシュアルゴリズムとしてSHA256への移行を進めており、これは衝突攻撃に対してはるかに堅牢であり、この攻撃を軽減するためのコードが導入されています(ただし、完全に排除することはできません)。

ブランチ参照

特定のコミットを参照する簡単な方法の1つは、それがブランチの先端にあるコミットである場合です。この場合、コミットへの参照を期待する任意のGitコマンドでブランチ名を単純に使用できます。たとえば、ブランチ上の最後のコミットオブジェクトを調べたい場合、topic1ブランチがコミットca82a6d...を指していると仮定すると、次のコマンドは同等です。

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

ブランチがどのSHA-1を指しているかを具体的に確認したい場合、またはこれらの例がいずれもSHA-1でどのように要約されるかを確認したい場合は、rev-parseと呼ばれるGitプラミングツールを使用できます。プラミングツールについてはGitの内部で詳細を確認できますが、基本的にrev-parseは低レベルの操作のために存在し、日常的な操作で使用されるようには設計されていません。ただし、何が実際に起こっているのかを確認する必要がある場合には役立つことがあります。ここでは、ブランチでrev-parseを実行できます。

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

RefLogショートネーム

Gitがバックグラウンドで作業中に実行することの1つは、「reflog」— 過去数ヶ月間のHEADとブランチ参照がどこにあったかのログ — を保持することです。

git reflogを使用すると、reflogを表示できます。

$ git reflog
734713b HEAD@{0}: commit: Fix refs handling, add gc auto, update tests
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: Add 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はその情報をこの一時的な履歴に保存します。reflogデータを使用して古いコミットも参照できます。たとえば、リポジトリのHEADの5つ前の値を確認したい場合、reflog出力に表示される@{5}参照を使用できます。

$ git show HEAD@{5}

この構文を使用して、ブランチが特定の時間前にどこにあったかを確認することもできます。たとえば、masterブランチが昨日どこにあったかを確認するには、次のように入力します。

$ git show master@{yesterday}

これにより、masterブランチの先端が昨日どこにあったかが表示されます。この手法は、まだreflogに残っているデータにのみ機能するため、数ヶ月以上前のコミットを検索するには使用できません。

git log出力のように整形されたreflog情報を表示するには、git log -gを実行します。

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

    Fix refs handling, add gc auto, update 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'

reflog情報は厳密にローカルであることに注意することが重要です。つまり、それはあなたがあなたのリポジトリで行ったことのみのログです。他の人のリポジトリのコピーでは参照が同じになることはありません。また、リポジトリを最初にクローンした直後は、リポジトリでまだアクティビティが発生していないため、reflogは空になります。git show HEAD@{2.months.ago}を実行しても、プロジェクトを少なくとも2か月前にクローンした場合にのみ一致するコミットが表示されます。それよりも最近にクローンした場合は、最初のローカルコミットのみが表示されます。

ヒント
reflogをGit版のシェル履歴だと考えてみましょう。

UNIXまたはLinuxのバックグラウンドがある場合、reflogはGit版のシェル履歴と考えることができます。これは、そこにあるものが明らかにあなた自身とあなたの「セッション」にのみ関連しており、同じマシンで作業している他の誰とも関係がないことを強調しています。

PowerShellでの中括弧のエスケープ

PowerShellを使用する場合、{}のような中括弧は特殊文字であるため、エスケープする必要があります。バッククォート「`」でエスケープするか、コミット参照を引用符で囲むことができます。

$ git show HEAD@{0}     # will NOT work
$ git show HEAD@`{0`}   # OK
$ git show "HEAD@{0}"   # OK

祖先参照

コミットを指定するもう1つの主な方法は、その祖先を介して行うことです。参照の末尾に^(キャレット)を置くと、Gitはそのコミットの親を意味するように解決します。プロジェクトの履歴を見たと仮定します。

$ git log --pretty=format:'%h %s' --graph
* 734713b Fix refs handling, add gc auto, update tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd Add 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'
Windowsでのキャレットのエスケープ

Windowsのcmd.exeでは、^は特殊文字であり、異なる扱いが必要です。二重にするか、コミット参照を引用符で囲むことができます。

$ git show HEAD^     # will NOT work on Windows
$ git show HEAD^^    # OK
$ git show "HEAD^"   # OK

^の後に数字を指定して、どの親が欲しいかを示すこともできます。たとえば、d921970^2は「d921970の2番目の親」を意味します。この構文は、複数の親を持つマージコミットにのみ有用です。マージコミットの最初の親は、マージしたときにいたブランチ(通常はmaster)からのもので、マージコミットの2番目の親は、マージされたブランチ(たとえば、topic)からのものです。

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

    Add 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

もう1つの主要な祖先指定は、~(チルダ)です。これも最初の親を参照するため、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などを使用して、前の参照の2番目の親(マージコミットであると仮定して)を取得できます。

コミット範囲

個々のコミットを指定できるようになったので、コミットの範囲を指定する方法を見てみましょう。これは特にブランチの管理に役立ちます。多数のブランチがある場合、「このブランチには、まだメインブランチにマージされていないどの作業がありますか?」といった質問に答えるために、範囲指定を使用できます。

ドット2つ

最も一般的な範囲指定は、ドット2つ構文です。これは基本的に、あるコミットからは到達可能だが、別のコミットからは到達可能でないコミットの範囲をGitに解決させるものです。たとえば、範囲選択の履歴例のようなコミット履歴があるとします。

Example history for range selection
図136. 範囲選択の履歴例

masterブランチにまだマージされていないexperimentブランチにあるものを見たいとします。master..experimentでそれらのコミットのみのログを表示するようにGitに要求できます。これは「experimentから到達可能だが、masterから到達可能でないすべてのコミット」を意味します。これらの例では、簡潔さと明確さのために、図のコミットオブジェクトの文字が、実際に表示されるログ出力の代わりに、その順序で使用されています。

$ git log master..experiment
D
C

逆に、反対を見たい場合、つまりexperimentにないmaster内のすべてのコミットを見たい場合は、ブランチ名を逆にするだけです。experiment..masterは、experimentから到達可能でないmaster内のすべてを表示します。

$ git log experiment..master
F
E

これは、experimentブランチを最新の状態に保ち、マージしようとしているものをプレビューしたい場合に役立ちます。この構文のもう1つの一般的な使用法は、リモートにプッシュしようとしているものを見ることです。

$ git log origin/master..HEAD

このコマンドは、現在のブランチ内にあり、originリモートのmasterブランチにはないコミットを表示します。git pushを実行し、現在のブランチがorigin/masterを追跡している場合、git log origin/master..HEADによってリストされるコミットは、サーバーに転送されるコミットです。また、構文の片側を省略して、GitにHEADを仮定させることもできます。たとえば、git log origin/master..と入力することで、前の例と同じ結果が得られます。片側が欠けている場合、GitはHEADに置き換えます。

複数地点

ドット2つ構文は省略形として便利ですが、リビジョンを示すために2つ以上のブランチを指定したい場合もあります。例えば、現在いるブランチにない複数のブランチのいずれかにあるコミットを見たい場合などです。Gitでは、到達可能なコミットを表示したくない参照の前に^文字または--notのいずれかを使用することで、これを行うことができます。したがって、次の3つのコマンドは同等です。

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

この構文を使用すると、ドット2つ構文ではできない2つ以上の参照をクエリで指定できるため、これは優れています。たとえば、refAまたはrefBから到達可能だが、refCからは到達可能でないすべてのコミットを見たい場合は、次のいずれかを使用できます。

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

これにより、ブランチに何が含まれているかを特定するのに役立つ、非常に強力なリビジョンクエリシステムが実現されます。

ドット3つ

最後の主要な範囲選択構文は、ドット3つ構文で、2つの参照のいずれかから到達可能だが、両方からは到達可能でないすべてのコミットを指定します。範囲選択の履歴例のコミット履歴をもう一度見てください。masterまたはexperimentにあるが、共通の参照にはないものを見たい場合は、次のように実行できます。

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

これもまた通常のlog出力ですが、伝統的なコミット日付順で表示されるのはこれら4つのコミットの情報のみです。

この場合、logコマンドと一緒によく使うスイッチは--left-rightで、これは各コミットが範囲のどちら側にあるかを示します。これにより、出力がより役立ちます。

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

これらのツールを使用すると、どのコミットを調査したいかをGitにはるかに簡単に伝えることができます。

scroll-to-top