【Git】どのコミットでバグが生まれたのか？git log -Sで探したいコミットを爆速で見つける！

# はじめに

こんにちは、ken です！

開発中に次のような疑問を抱くことってありませんか？

- 「このコード、いつ追加されたんだろう？」
- 「この関数が削除されたのはなぜ？」

Git にはこうした疑問を解消する便利なコマンドがあります、それが`git log -S`です！
本記事では、このコマンドの基本から具体的な使い方までを解説してみようと思います。

# `git log -S`とは

`git log -S`とはどういったコマンドなのでしょうか。以下に[Git の公式ドキュメント](https://git-scm.com/docs/git-log#Documentation/git-log.txt--Sltstringgt)の説明を引用します。

> Look for differences that change the number of occurrences of the specified string (i.e. addition/deletion) in a file. Intended for the scripter’s use.
>
> It is useful when you’re looking for an exact block of code (like a struct), and want to know the history of that block since it first came into being: use the feature iteratively to feed the interesting block in the preimage back into -S, and keep going until you get the very first version of the block.
>
> Binary files are searched as well.

> (筆者による日本語訳)
> ファイル中の指定された文字列の出現回数（追加/削除など）を変更する差分を検索する。スクリプト作成者向けに設計されています。
>
> コードの正確なブロック（構造体など）を探し、そのブロックが最初に作成されてからの履歴を知りたい場合に便利です。この機能を繰り返し使用して、プリイメージ内の関連あるブロックにフィードバックします。-S やロックの最初のバージョンを取得するまで続けます。
> 構造体のような特定のコードブロックを正確に検索し、そのブロックが初めて作成された時点からの履歴を確認したい場合に便利です。この機能を繰り返し使用し、興味のあるブロックを前の状態から抽出して -S オプションに入力し続けることで、そのブロックが最初に現れたバージョンまで遡ることができます。
>
> バイナリファイルも検索対象に含まれます。

ファイル内の指定された文字列の出現回数の変更を探し出す、というのが少し聞き慣れない表現ですが、要するに**特定の文字列が「追加」または「削除」されたコミット**を探すためのコマンドということです。
言葉だけでは分かりにくいと思うので、実際に具体例を見てみましょう！

# 簡単な具体例

```go: math.go
package math

func Add(a int, b int) int {
    return a + b
}

func Subtract(a int, b int) int {
    return a - b
}
```

例えば上のようなコードがあったとします。
「`Subtract`関数はいつ、どのコミットで追加されたのか？」と思ったときに、`git log -S`が活躍します。

## コマンドの実行

次のようにコマンドを実行します。

```bash
git log -S "func Subtract" -- math.go
```

各オプションの意味は以下のとおりです。

- `-S "func Subtract"`で、検索する文字列を指定
- `-- math.go`：検索対象を特定のファイルに絞る（省略時はリポジトリ全体を対象にして検索します）

## 実行結果

以下のようなログが出力されます。

```bash
commit 89517ecb54e3e13a54e67a26dbccc546d0f37d35 (HEAD -> master)
Author: Ken <qiita_ken@example.com>
Date:   Sat Nov 30 19:52:42 2024 +0900

    add Subtract function to math.go
```

この結果から、「`Subtract`関数が追加されたのは`89517ecb`というコミットである」とわかりました！

## もっと詳しく表示する

コミットの詳細を確認したい場合は、以下のコマンドを実行します。

```bash
git show 89517ecb
```

皆さんおなじみの`git show`ですね！
実行後出力されるこのコミットメッセージと差分から、`Subtract`関数がいつどのように実装されたかがわかります。

```diff
commit 89517ecb54e3e13a54e67a26dbccc546d0f37d35 (HEAD -> master)
Author: Ken <qiita_ken@example.com>
Date:   Sat Nov 30 19:52:42 2024 +0900

    add Subtract function to math.go

diff --git a/math/math.go b/math/math.go
index f486ea3..bbe266a 100644
--- a/math/math.go
+++ b/math/math.go
@@ -3,3 +3,7 @@ package math
 func Add(a int, b int) int {
        return a + b
 }
+
+func Subtract(a int, b int) int {
+       return a - b
+}
```

また、冒頭で述べたとおり、`git log -S`は、「特定の文字列が『追加』または『削除』されたコミットを探すためのコマンド」なので、**削除されたコードを追跡したいとき**も同じコマンドを使います。つまりSubtract 関数が削除されていた場合も、先ほどと同じコマンドで履歴を追うことができるというわけです。

```bash
git log -S "func Subtract" -- math.go
```

結果には、追加時と削除時の両方のコミットが表示されます。

```bash
commit 4f82fdbd6eb0885cacc10001568a17545f284a04 (HEAD -> master)
Author: Ken <qiita_ken@example.com>
Date:   Sun Dec 1 00:52:06 2024 +0900

    remove Subtract function from math.go

commit 89517ecb54e3e13a54e67a26dbccc546d0f37d35
Author: Ken <qiita_ken@example.com>
Date:   Sat Nov 30 19:52:42 2024 +0900

    add Subtract function to math.go
```

# より実用的な具体例

ここまではひとまず`git log -S`を理解するために簡単な具体例を扱いましたが、ここからはより実用的な使い方を 2 つ紹介します。

## 削除された機能の履歴を追跡する

「昔あったはずの API エンドポイント（例: `/v1/legacy-endpoint`）がなぜ消えたのか」を調査したいとします。
`git log -S`は特定の文字列が削除されたコミットも探すことができるので、下のコマンドを叩いて出てきた最新のコミットを見れば削除された理由がわかるはずです。

```bash
git log -S "/v1/legacy-endpoint" -- app/api/*
```

## バグを引き起こしたコミットを探す

「特定のバグが発生した原因となった変更を探りたい」とします。
例えば`if user.IsAdmin()`という条件が追加されていたことで、予期せぬ挙動が発生した場合、この条件分岐が追加されたコミットを確認することで、バグが生まれてしまった原因や、いつ頃からバグが発生していたのかを調べることができます。

```bash
git log -S "if user.IsAdmin()" -- app/domain/user.go
```

# `git blame`との違い

「`git blame`でも同じことができるのでは？」と思う方もいるかもしれませんが、`git blame`には次の制約があります：

- **リポジトリ全体を検索できない**
  - `git blame`は 1 ファイル単位でしか履歴を追えませんが、`git log -S`は複数ファイルやリポジトリ全体を対象に検索可能です
- **差分そのものを追いづらい**
  - `git blame`で**行**の変更履歴は確認できますが、差分そのものや具体的な追加・削除の内容を調べることはできません

# おまけ

ちなみに`git log -S`に似たコマンドとして、`git log -G`もあります。（[公式ドキュメント](https://git-scm.com/docs/git-log#Documentation/git-log.txt--Gltregexgt)）
`-S`では文字列の**増減**を追跡していたのに対して、`-G`では文字列を含む**差分**を追跡できます。
例えば、`user.Role`を含む条件式の差分を追跡したいときは次のようなコマンドを叩けばよいです。`git log -G`を使うときは正規表現で検索をかけます。[^1]

```bash
git log -G "if .*user\.Role.*" -- app/domain/user.go
```

追加や削除だけでなく、より広く特定のパターンやロジックの変更を検索したいときはこちらの`git log -G`を使うと良さそうですね。
[^1]: 一応`git log -S`でも`--pickaxe-regex`オプションを併用することで正規表現を使うことは可能です。`git log -S "<正規表現>" --pickaxe-regex`の形で記述します。

# 最後に

この記事では`git log -S`の有用性について書いてみました。
海外の方の動画を見てこのコマンドを知ったのですが、記事を書くにあたって`git log`のオプションを確認したら他にもたくさんあって驚きました...。
<iframe width="560" height="315" src="https://www.youtube.com/embed/mnmYwRoSisg?si=ON6G-IGkgrY5aqun" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

間違いなどありましたらコメント欄でご指摘いただければ幸いです。最後までお読みいただきありがとうございました。

# PR
株式会社HRBrainでは、一緒に働く仲間を募集しています！
興味を持っていただけた方はぜひ弊社の採用ページをご確認ください！

https://www.hrbrain.co.jp/recruit/