チャプター ▾ 第2版

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

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

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

リビジョン選択

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

単一のリビジョン

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

短い SHA-1

Git は、SHA-1 ハッシュの最初の数文字を提供すれば、それが少なくとも 4 文字長で曖昧さがない限り、どのコミットを参照しているかを理解できるほど賢いです。つまり、オブジェクトデータベース内の他のオブジェクトが同じプレフィックスで始まるハッシュを持つことはありません。

たとえば、特定の機能を追加したことがわかっている特定のコミットを調べたい場合、まず 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 値の短く一意な省略形を生成できます。git log コマンドに --abbrev-commit を渡すと、出力はより短い値を使用しますが、それらを一意に保ちます。デフォルトでは 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 京の 1200 倍です。これは地球上の砂粒の数の 1,200 倍です。

SHA-1 の衝突が発生するのにどれくらいの時間がかかるかを示す例を挙げます。地球上の 65 億人全員がプログラミングしていて、毎秒、それぞれが Linux カーネル全体の履歴 (650 万個の Git オブジェクト) に相当するコードを生成し、それを 1 つの巨大な 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 Internals」を参照してください。基本的に、rev-parse は低レベルの操作のために存在し、日常的な操作で使用されるようには設計されていません。ただし、何が実際に起こっているのかを確認する必要がある場合に役立つことがあります。ここで、ブランチに対して rev-parse を実行できます。

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

リファレンスログのショートネーム

Git が作業中にバックグラウンドで行うことの 1 つに「リファレンスログ」の保持があります。これは、HEAD とブランチの参照が過去数ヶ月間どこにあったかのログです。

git 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 はその情報をこの一時的な履歴に保存します。このリファレンスログデータを使用して、古いコミットも参照できます。たとえば、リポジトリの HEAD の 5 つ前の値を確認したい場合は、リファレンスログ出力に表示される @{5} 参照を使用できます。

$ git show HEAD@{5}

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

$ git show master@{yesterday}

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

git log の出力のようにフォーマットされたリファレンスログ情報を表示するには、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'

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

ヒント
リファレンスログはGit版のシェル履歴と考えてください。

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

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

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

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

祖先参照

コミットを指定するもう一つの主な方法は、その祖先を介することです。参照の最後に ^ (キャレット) を置くと、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

もう一つの主な祖先指定は ~ (チルダ) です。これも最初の親を指すため、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 番目の親 (それがマージコミットであったと仮定して) を取得するなどです。

コミット範囲

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

二点

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

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

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

$ git log master..experiment
D
C

一方、反対を見たい場合、つまり experiment にない master のすべてのコミットを見たい場合は、ブランチ名を逆転させることができます。experiment..master は、experiment から到達できない master 内のすべてを表示します。

$ git log experiment..master
F
E

これは、experiment ブランチを最新の状態に保ち、マージしようとしているものをプレビューしたい場合に便利です。この構文のもう一つのよくある使用法は、リモートにプッシュしようとしているものを確認することです。

$ 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

これにより、ブランチの内容を把握するのに役立つ非常に強力なリビジョンクエリシステムが実現します。

トリプルドット

最後の主要な範囲選択構文は、トリプルドット構文で、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