Git
目次 ▾ 第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値の短く、一意な省略形を自動的に生成できます。`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カーネル(かなり大規模なプロジェクトです)には、87万5千以上のコミットと約700万個のオブジェクトがオブジェクトデータベースに存在しますが、最初の12文字が同一のオブジェクトは存在しません。

注記
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倍です。

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

数千ドル相当のコンピューティングパワーを投入すれば、2017年2月にhttps://shattered.io/で証明されているように、同じハッシュを持つ2つのファイルを合成することは可能です。Gitは、衝突攻撃に対する耐性がはるかに高いSHA256をデフォルトのハッシュアルゴリズムとして使用する方向に進んでおり、この攻撃を軽減するコードも実装されています(ただし、完全に排除することはできません)。

ブランチ参照

特定のコミットを参照する簡単な方法は、それがブランチの先端にあるコミットである場合です。その場合、コミットへの参照を期待する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は、作業中にバックグラウンドで、HEADとブランチ参照が過去数ヶ月間どこに存在していたかのログである「reflog」を維持します。

`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 -g`を実行すると、`git log`出力のような形式でreflog情報が表示されます。

$ 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

祖先参照

コミットを指定するもう一つの主な方法は、その祖先によるものです。参照の最後に`^`(キャレット)を配置すると、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`では、`^`は特殊文字であり、異なる方法で処理する必要があります。それを2回繰り返すか、コミット参照を引用符で囲むことができます。

$ 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

これらの構文を組み合わせることもできます。前の参照(マージコミットであると仮定)の2番目の親を取得するには、`HEAD~3^2`などを使用します。

コミット範囲

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

ダブルドット

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

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

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

$ git log master..experiment
D
C

一方、逆の状況、つまり`master`にあるが`experiment`にはないすべてのコミットを確認したい場合は、ブランチ名を逆にします。`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つ以上のブランチを指定したい場合があります。例えば、現在いるブランチには含まれていないいくつかのブランチにあるコミットを確認したい場合などです。Gitでは、到達可能なコミットを表示したくない参照の前に`^`文字または`--not`を使用してこれを行うことができます。そのため、次の3つのコマンドは同等です。

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

この構文では、クエリに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