<!--order=10-->
<h1>Git: Searching and Debugging</h1>
<p>Git's history is a searchable record of every change ever made. <code>git grep</code> searches file content across commits. <code>git blame</code> and <code>git annotate</code> show who last changed each line. <code>git bisect</code> performs a binary search through history to find the exact commit that introduced a bug — invaluable when tracking down regressions.</p>

<h2>Commands Covered</h2>
<p><code>git grep</code> · <code>git blame</code> · <code>git annotate</code> · <code>git bisect</code> · <code>git log</code> (advanced search)</p>

<h2>git grep</h2>
<p>Searches working tree files (or any tree object) for a pattern. Faster than system <code>grep</code> because it respects <code>.gitignore</code>, only searches tracked files, and can search historical commits.</p>
<pre>
# Search working tree for a string
$ git grep "functionName"
$ git grep "TODO"

# Case-insensitive search
$ git grep -i "error"

# Show line numbers
$ git grep -n "session_start"

# Show only filenames (not the matching lines)
$ git grep -l "deprecated"

# Count matches per file
$ git grep -c "import"

# Search with a regex
$ git grep -E "def [a-z_]+"       # extended regex
$ git grep -P "\d{3}-\d{4}"       # Perl-compatible regex

# Search within a specific commit or branch
$ git grep "functionName" HEAD
$ git grep "functionName" main
$ git grep "functionName" v1.0.0

# Search across all commits (slow on large repos)
$ git grep "secret" $(git rev-list --all)

# Limit search to specific files
$ git grep "TODO" -- "*.php"
$ git grep "error" -- src/

# Show surrounding context lines
$ git grep -A 3 "findUser"     # 3 lines after
$ git grep -B 2 "findUser"     # 2 lines before
$ git grep -C 2 "findUser"     # 2 lines before and after

# Search for a string that spans word boundaries
$ git grep -w "log"     # matches "log" but not "logger" or "catalog"
</pre>

<h2>git blame</h2>
<p>Shows the commit and author that last modified each line of a file. Essential for understanding why a line of code exists and who to ask about it.</p>
<pre>
# Show blame for a file
$ git blame src/auth.c

# Output format:
# abc12345 (Jason Smith 2024-03-15 14:22:31 -0500 42) if (user == null) {
# ^SHA     ^author      ^date/time               ^line ^content

# Show only specific lines (useful for large files)
$ git blame -L 40,60 src/auth.c      # lines 40 to 60
$ git blame -L 40,+20 src/auth.c     # 20 lines starting at line 40

# Blame a file at a specific commit (how it looked historically)
$ git blame v1.0.0 -- src/auth.c
$ git blame abc1234 -- src/auth.c

# Ignore whitespace changes (blame the last meaningful change)
$ git blame -w src/auth.c

# Follow line movement between files (detect when code was moved/copied)
$ git blame -M src/auth.c    # lines moved within the same commit
$ git blame -C src/auth.c    # lines copied from other files in the same commit
$ git blame -CCC src/auth.c  # very aggressive copy detection

# Short output (abbreviated SHA)
$ git blame --abbrev=8 src/auth.c

# Show the email address instead of name
$ git blame -e src/auth.c

# Ignore specific commits (e.g. a bulk reformatting commit)
# Create a file listing commits to ignore:
$ echo "abc1234" &gt; .git-blame-ignore-revs
$ git blame --ignore-revs-file .git-blame-ignore-revs src/auth.c
# Or set it globally in config:
$ git config blame.ignoreRevsFile .git-blame-ignore-revs
</pre>

<h2>git annotate</h2>
<p>Similar to <code>git blame</code> but uses a slightly different output format. Mostly a legacy command — <code>git blame</code> is preferred.</p>
<pre>
$ git annotate src/auth.c

# Output format (different from blame):
# abc12345        (Jason Smith      2024-03-15 14:22:31 -0500 42)if (user == null) {

# The data is the same; the column layout differs.
# git blame -p gives more structured output for scripting.
</pre>

<h2>git bisect</h2>
<p>Binary search through commit history to find the commit that introduced a bug. You mark commits as "good" or "bad" and Git checks out the midpoint — in O(log N) steps you find the exact culprit out of thousands of commits.</p>
<pre>
# Start a bisect session
$ git bisect start

# Mark the current commit as bad (has the bug)
$ git bisect bad

# Mark a known-good commit (before the bug existed)
$ git bisect good v1.0.0
$ git bisect good abc1234

# Git checks out the midpoint commit.
# Test whether the bug exists, then mark it:
$ git bisect bad    # this commit has the bug
$ git bisect good   # this commit is fine

# Repeat until Git reports:
# abc1234 is the first bad commit
# commit abc1234
# Author: ...
# Date: ...
#     The commit message of the culprit

# End the bisect session (returns to original branch)
$ git bisect reset

# Bisect a specific range from the start:
$ git bisect start HEAD v1.0.0   # shorthand: bad then good
</pre>

<h3>Automated Bisect</h3>
<pre>
# If you have a test script that exits 0 on good, non-zero on bad:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good v1.0.0
$ git bisect run ./test-script.sh

# Git runs the script at each midpoint automatically.
# Much faster than manual bisect for large ranges.

# Example test script:
#!/bin/sh
make &gt;/dev/null 2&gt;&amp;1 || exit 125  # exit 125 = skip this commit (can't build)
./run-tests 2&gt;/dev/null | grep -q "FAIL" &amp;&amp; exit 1  # bad
exit 0  # good

# Exit codes for git bisect run:
# 0     = good (no bug)
# 1-124 = bad (bug present)
# 125   = skip this commit (e.g. doesn't build)
# 126+  = bisect error (abort)
</pre>

<h2>Advanced Log Searching</h2>
<pre>
# Find commits that changed a specific string (the "pickaxe")
$ git log -S "password_hash"        # commits that added or removed this string
$ git log -G "session.*token"       # commits where diff matches this regex

# Search commit messages
$ git log --grep="closes #"
$ git log --grep="fix" --grep="bug" --all-match   # both patterns must match
$ git log --grep="fix" -i            # case-insensitive

# Find who last changed a specific function
$ git log -L :functionName:src/auth.c    # history of a function
$ git log -L 40,60:src/auth.c            # history of lines 40-60

# Find commits that touched a specific file
$ git log -- src/auth.c
$ git log --follow -- src/old-name.c    # follow through renames

# Find commits by author in a date range
$ git log --author="Jason" --since="2024-01-01" --until="2024-06-01"

# Find the commit that deleted a line containing a string
$ git log -S "the deleted string" -p
</pre>

<h2>difftool</h2>
<pre>
# Open diffs in a visual tool instead of the terminal
$ git difftool
$ git difftool HEAD~3 HEAD

# Configure a diff tool
$ git config --global diff.tool vimdiff
$ git config --global diff.tool meld
$ git config --global diff.tool code   # VS Code

# Don't prompt before opening each file
$ git config --global difftool.prompt false

# List available diff tools
$ git difftool --tool-help
</pre>

<h2>References</h2>
<ul>
  <li><a href="https://git-scm.com/docs/git-grep" target="_blank" rel="noopener">git-grep documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-blame" target="_blank" rel="noopener">git-blame documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-bisect" target="_blank" rel="noopener">git-bisect documentation ↗</a></li>
</ul>
