git logのオプション(後編)
今回もgit logのオプションについての紹介ですが、前編中編とは少し趣旨が違います。
これまでは、いわば、ある一つのブランチの中で完結するようなオプションを紹介しました。 複数のブランチが切られている時(というかそれが当たり前ですが)、git logと打ってどの範囲のログが出力されるか、正確に分かっていますか? 「あのブランチだけのログが見たいのに、何故か違うブランチのログも出力される。。。」といったことはありませんか?「雰囲気でHEAD~とか打ってるけど詳しい意味は分かってない。。。」といったことはありませんか? たまーに出てくるチルダ(~)やキャレット(^)ですが、どのような意味を持っているか分かっていますか?
私もわかっていなかったので改めて確認したいと思います。
2つのブランチを対象にログを出力する
まずは、ダブルドット構文(..)とトリプルドット構文(...)の使用方法を説明します。
これらの構文は2つの引数(便宜上、引数と呼びます)をとります。ブランチA..ブランチBといった形ですね。
..は、どちらか一方からのみ辿れるコミットを出力します。結果は引数の渡し順によって変わります。...は、どちらか一方から辿れるコミットを出力します。こちらは引数の渡し順によって変わりません。
また、どちらも、2つのブランチ間で重複しているコミットを除く動きをします。
前提として、リポジトリは以下の状態であるとします。
master、develop、fixという3つのブランチがあります。
青い丸の中の数字はコミットのハッシュIDです。8はdevelopにfixをマージしたマージコミットです。現在HEADはdevelopを指しています。
このリポジトリを再現するには、以下のスクリプトを実行してください。
スクリプトを見る
git init
# masterブランチで作業
git commit -m "1" --allow-empty
git commit -m "2" --allow-empty
git commit -m "3" --allow-empty
# developブランチを作成
git checkout -b develop HEAD~~
# developブランチで作業
git commit -m "4" --allow-empty
git commit -m "5" --allow-empty
# fixブランチを作成
git checkout -b fix HEAD~~
# fixブランチで作業
git commit -m "6" --allow-empty
git commit -m "7" --allow-empty
# developブランチに移動
git checkout develop
# fixブランチをマージ
git merge --no-ff fix -m "8"
まずは、git log ブランチ名といった風に、単体でブランチ名を入力した場合の出力を確認しておきます。
git log master
masterである3から矢印で辿れる、3,2,1が対象です。
git log develop(HEAD)
developである8から辿れる、8,7,6,5,4,1が対象です。
現在HEADはdevelopを指していることから、git log HEADとしても同じです。
git log fix
fixである7から辿れる、7,6,1が対象です。
git log develop..master
では、..の動作を確認します。
言語化するとしたら「developになくて、masterにだけあるもの」 です 。3,2が出力されます。
masterから3,2,1が辿れますが、1はdevelopからも辿れるので対象外です。
masterから辿れる3,2,1から、developからも辿れて重複している1が除かれているのが分かると思います。冒頭で「2つのブランチ間で重複しているコミットを除く動きをします。」と説明したのはこのことです。
git log master..develop
上記の逆です。読み方は「masterになくて、developにだけあるもの」です。
developから8,7,6,5,4,1が辿れますが、1はmasterからも辿ることができ、重複しているので対象外です。
git log master..fix
読み方は「masterになくて、fixにだけあるもの」です。
fixから7,6,1が辿れますが、1はmasterからも辿れるので対象外です。
git log fix..develop
読み方は「fixになくて、developにだけあるもの」です。
developから8,7,6,5,4,1が辿れますが、7,6,1はfixからも辿れることができ、重複しているので対象外です。
git log develop..fix
読み方は「developになくて、fixにだけあるもの」です。
fixから7,6,1が辿れますが、これらは全てdevelopからも辿れることができ、重複しているのでコミットは出力されません。
リモートリポジトリとの差分を確認できる
これまで示した例のように、ローカルでブランチ間の差分を見るのにも使用できますが、リモートリポジトリー(GitHubなど)との差分を見るのにこのコマンドは役に立ちます。
例えばgit log origin/featureAAA..featureAAAとすると、リモートになくてローカルにあるコミットだけを出力できます。つまり、どのコミットをpushすべきかという事が分かります。
git log master…develop (git log develop…master)
ここからは...の使用方法です。
...は、どちらか一方から辿れるものを出力します。どちらからも辿れるもの(つまり重複しているもの)は対象外です。
上記のコマンドでいうと、読み方は「masterかdevelopのどちらか一方にあるもの」です。1はmasterからもdevelopからも辿ることができ、重複しているので対象外です。
なお、...を使用する場合、どのような順番でブランチを指定しても結果は同じです。
git log develop…fix (git log fix…develop)
読み方は「developとfixのどちらか一方にあるもの」です。7,6,1はdevelopからもfixからも辿れることができ、重複しているので対象外です。

チルダとキャッレット
チルダ~とキャレット^はgitの中でも理解しにくいものの一つです。私も何となく分からないので何となく放置していました。
あと、ググラビリティも低いですね。
~で親のコミットを表す
チルダを付与することで、ひとつ親のコミットを表すことができます。この場合、git log develop~はgit log 5と同義と言え、5,4,1が出力されます。
同様に、~~、~~~といった風に~を連ねればその分だけ親をさかのぼることができます。また、~~は~2、~~~は~3と表すこともできます。
^で親を選択する
キャレット^は複数の親がある場合(マージコミットなど)に有効です。git log develop^とすることでひとつめの親である5を指すことになります。同様に、develop^2とすると、ふたつ目の親である7を指すことになります。
git log develop^2とすると、2つ目の親である7を指すことになり、7,6,1が出力されます。
しかし、develop^^とdevelop^2は同義ではありません。develop^^は、develop^の^という風に「一つ目の親の一つ目の親」という意味になります。
develop^2^は「2つめの親の一つ目の親」であり、develop^2~と同義です。
複雑ではないコミット履歴をこうやって図示しているから分かりやすいものの、これをコマンドラインでやろうと思うと直感的に理解しにくいと思います。この辺りはGUIツールの出番ですね。^と~はgit log以外にも色んなところで出てくるので、これらの持つ意味さえ覚えておけばOKです。
--notでブランチを除外する
--not ブランチ名とすることで、そのブランチから辿れるコミットを除くことができます。
..や...と同じオプションにも見えますが、これらは引数を2つしかとることができません。しかし--notなら、例えば、git log master develop --not fixとすることで、「masterとdevelopから辿れるコミットから、fixからも辿れるコミットは除外する」といった風に、3つ以上のブランチ名を記述することができます。
また、--not ブランチA ブランチBといった風に、2つ以上のブランチを除外することもできます。下図はgit log develop --not master fixのイメージです。
これもややこしいですね。頭の中でこういったイメージを持ちながらコミットを辿るのは限界があります。GUIツールを頼りましょう。
また、ブランチ名の前にキャレット^をつけることで、--notと同じく、ブランチを除外することができます。git log develop ^master ^fixといった具合です。このように、^はブランチ名の前につけるか後ろに付けるかで全く意味が変わってきます。
参考
[git] チルダ(~)とキャレット(^)の違い | Tech控え帳