Learning Objectives

By the end of this module, you will be able to:

  1. Format git log output with --oneline, --graph, --all, --decorate, and custom --pretty=format: strings
  2. Filter history by author, date, commit message, and content changes using --author, --since, --until, --grep, -S, and -G
  3. Scope log output to specific files and directories with path filtering
  4. Use range notation (main..feature, main...feature) to explore commit reachability
  5. Navigate merge-heavy histories with --first-parent and --merges/--no-merges
  6. Search code within any commit using git grep and git log -L (line-range history)
  7. Create reusable log aliases for daily workflows

1. git log Basics and Display Formats

git log is your primary tool for reading commit history. Out of the box, it shows each commit's hash, author, date, and message — but its real power comes from the dozens of formatting and filtering options.

Default Output

git log
commit abc1234567890abcdef1234567890abcdef123456
Author: Alice Smith <alice@example.com>
Date:   Mon Mar 15 14:22:30 2024 +0100

    feat: add user authentication

commit def5678901234567890abcdef1234567890abcde
Author: Bob Jones <bob@example.com>
Date:   Fri Mar 12 09:15:00 2024 -0500

    fix: resolve login redirect loop

--oneline — Compact Single-Line Format

git log --oneline
abc1234 feat: add user authentication
def5678 fix: resolve login redirect loop
ghi9012 refactor: extract validation helpers

Shows abbreviated SHA + first line of commit message. This is the most commonly used format for scanning history.

--graph — Visualize Branch Topology

git log --oneline --graph
*   f5a3b2c Merge branch 'feature/auth'
|\
| * abc1234 feat: add user authentication
| * def5678 feat: add login form
|/
* ghi9012 refactor: extract validation helpers
* jkl3456 initial commit

The ASCII art shows branch/merge structure. Essential for understanding how branches were integrated.

--all — Show All Branches

git log --oneline --graph --all

Without --all, log only shows commits reachable from HEAD (your current branch). With --all, it shows every branch, tag, and ref — giving you the complete picture of the repository.

--decorate — Show Branch and Tag Labels

git log --oneline --graph --all --decorate
* f5a3b2c (HEAD -> main, origin/main) Merge branch 'feature/auth'
|\
| * abc1234 (origin/feature/auth) feat: add user authentication
| * def5678 feat: add login form
|/
* ghi9012 (tag: v1.0) refactor: extract validation helpers

--decorate adds branch names, tags, and HEAD indicators. Modern Git enables this by default.

The Classic Combo

git log --oneline --graph --all --decorate

This is so common that most developers alias it. You'll see it abbreviated as glog, gloga, or similar in shell configurations.

git show — Inspect a Single Commit

While git log lists commits, git show displays a single commit with its diff:

# Show the latest commit with its patch
git show
 
# Show a specific commit
git show abc1234
 
# Show without the diff (metadata only)
git show --no-patch abc1234
git show -s abc1234
 
# Show with stat summary instead of full diff
git show --stat abc1234

2. Built-In Format Presets

Git provides several preset formats via --format= (or --pretty=):

FormatWhat It Shows
onelineSHA message (single line)
shortSHA, author, message (no date)
mediumSHA, author, date, message (default)
fullSHA, author, committer, message
fullerSHA, author + date, committer + date, message
rawInternal Git object format
git log --format=short -3
git log --format=full -3
git log --format=fuller -3

Author vs. Committer

The full and fuller formats reveal something important: Git tracks both author and committer separately.

  • Author — the person who originally wrote the change
  • Committer — the person who applied the change to the repository

They're usually the same person. They differ when:

  • Someone cherry-picks or rebases your commit (you're the author, they're the committer)
  • Someone applies a patch you emailed (historical workflow)
  • CI systems or maintainers merge contributions from external sources
commit abc1234
Author:     Alice Smith <alice@example.com>       ← wrote the code
AuthorDate: Mon Mar 15 14:22:30 2024 +0100
Commit:     Bob Jones <bob@example.com>           ← applied via cherry-pick
CommitDate: Tue Mar 16 09:00:00 2024 -0500

3. Custom Formats with --pretty=format:

For complete control over log output, use format placeholders:

git log --pretty=format:"%h %s (%an, %ar)"
abc1234 feat: add user authentication (Alice Smith, 2 days ago)
def5678 fix: resolve login redirect loop (Bob Jones, 5 days ago)

Essential Format Placeholders

PlaceholderMeaningExample
%HFull commit hashabc1234567890abcdef...
%hAbbreviated hashabc1234
%TTree hashdef5678901234...
%PParent hash(es)ghi9012... jkl3456...
%anAuthor nameAlice Smith
%aeAuthor emailalice@example.com
%adAuthor dateMon Mar 15 14:22:30 2024
%arAuthor date (relative)2 days ago
%aiAuthor date (ISO format)2024-03-15 14:22:30 +0100
%cnCommitter nameBob Jones
%ceCommitter emailbob@example.com
%cdCommitter dateTue Mar 16 09:00:00 2024
%crCommitter date (relative)1 day ago
%sSubject (first line of message)feat: add user auth
%bBody (rest of commit message)Multi-line text
%dRef names (branches, tags)(HEAD -> main, tag: v1.0)
%DRef names (no wrapping parens)HEAD -> main, tag: v1.0
%nNewline

Color in Custom Formats

git log --pretty=format:"%C(yellow)%h%C(reset) %C(blue)%an%C(reset) %s %C(green)(%ar)%C(reset)%C(red)%d%C(reset)"

Available colors: red, green, blue, yellow, cyan, magenta, white, reset (back to default), bold, dim, ul (underline).

Practical Custom Format Examples

Release changelog style:

git log --pretty=format:"- %s (%an)" v1.0..v2.0
- feat: add user authentication (Alice Smith)
- fix: resolve login redirect loop (Bob Jones)
- docs: update API reference (Carol Lee)

Commit audit trail:

git log --pretty=format:"%h | %ai | %-20an | %s"
abc1234 | 2024-03-15 14:22:30 +0100 | Alice Smith          | feat: add user auth
def5678 | 2024-03-12 09:15:00 -0500 | Bob Jones            | fix: login redirect

JSON-ish output for scripting:

git log --pretty=format:'{"hash":"%H","author":"%an","date":"%ai","message":"%s"},'

4. Filtering by Author and Date

Author Filter

# Commits by a specific author (substring match, case-insensitive)
git log --author="Alice"
 
# Combine with oneline for scanning
git log --oneline --author="alice@example.com"
 
# Multiple authors (OR logic)
git log --oneline --author="Alice\|Bob"

The --author flag matches against both name and email using a regex pattern.

Date Filters: --since / --until

Git accepts remarkably natural date expressions:

# Relative dates
git log --since="2 weeks ago"
git log --since="3 days"
git log --until="1 month ago"
 
# Absolute dates
git log --since="2024-01-01"
git log --until="2024-06-30"
 
# Combined range
git log --since="2024-01-01" --until="2024-03-31"
 
# Natural language
git log --since="last Monday"
git log --since="yesterday"

--after is a synonym for --since. --before is a synonym for --until.

Combining Filters

Filters are AND-combined by default:

# Alice's commits from the last month
git log --oneline --author="Alice" --since="1 month ago"
 
# Bob's commits between two dates
git log --oneline --author="Bob" --since="2024-01-01" --until="2024-03-31"

5. Filtering by Commit Message and Content

--grep — Search Commit Messages

# Find commits mentioning "login"
git log --oneline --grep="login"
 
# Case-insensitive search
git log --oneline --grep="login" -i
 
# Regex patterns
git log --oneline --grep="fix\|bug\|patch"
 
# All branches
git log --oneline --grep="login" --all
 
# Invert match (commits NOT mentioning "WIP")
git log --oneline --grep="WIP" --invert-grep

--grep with Multiple Patterns

By default, multiple --grep flags use OR logic. Use --all-match for AND:

# Commits mentioning "login" OR "auth"
git log --grep="login" --grep="auth"
 
# Commits mentioning "login" AND "auth" (both in the message)
git log --grep="login" --grep="auth" --all-match

-S (Pickaxe) — Find When a String Was Added/Removed

# Find commits where "validateEmail" was added or removed
git log -S "validateEmail" --oneline
 
# With the actual diff
git log -S "validateEmail" -p
 
# Restrict to specific file types
git log -S "validateEmail" --oneline -- "*.js"

Pickaxe counts the occurrences of the string in the old and new versions. If the count changes, the commit is included. This is how you find when a function/variable was introduced or deleted.

-G — Regex Search in Diffs

# Find commits where lines matching a regex were changed
git log -G "function\s+validate" --oneline
 
# Find TODO additions/removals
git log -G "TODO|FIXME" --oneline -p

-G is broader than -S: it includes commits where any diff line matches the regex, even if the count didn't change (e.g., a line containing the term was modified).


6. Path Filtering

Restrict log output to commits that touched specific files or directories:

# Commits that modified a specific file
git log --oneline -- src/auth.js
 
# Commits that touched anything in a directory
git log --oneline -- src/components/
 
# Multiple paths
git log --oneline -- src/auth.js src/config.js
 
# Glob patterns
git log --oneline -- "*.test.js"
 
# Follow renames (for a single file)
git log --oneline --follow -- src/auth.js

The -- separator ensures Git doesn't confuse file paths with branch names.

--follow — Track File Renames

Without --follow, if a file was renamed at some point, git log only shows history from the current name onward. --follow detects renames and continues showing history under the old name:

git log --oneline --follow -- src/utils/helpers.js
# Shows commits even from when it was called src/helpers.js

Limitation: --follow only works for a single file, not directories or glob patterns.

-L — Line-Range History

Track the history of specific lines in a file:

# History of lines 10-20 in auth.js
git log -L 10,20:src/auth.js
 
# History of a function (Git auto-detects function boundaries)
git log -L :validateEmail:src/auth.js
 
# From a line to end of function
git log -L 15,+10:src/auth.js

The -L flag shows the evolution of those specific lines across commits — like git blame but showing every change, not just the latest.


7. Range Notation in git log

Range notation controls which commits to display based on branch reachability.

Two-Dot: main..feature

git log --oneline main..feature

Shows commits reachable from feature but NOT reachable from main — in other words, what feature has that main doesn't.

A --- B --- C            (main)
       \
        D --- E --- F    (feature)

git log main..feature → shows D, E, F
git log feature..main → shows C

This is the most common range: "What commits are on this branch that haven't been merged yet?"

Three-Dot: main...feature

git log --oneline main...feature

Shows commits reachable from either main OR feature, but NOT from both — the symmetric difference. These are the commits on both sides since they diverged.

A --- B --- C            (main)
       \
        D --- E --- F    (feature)

git log main...feature → shows C, D, E, F

Add --left-right to see which side each commit is on:

git log --oneline --left-right main...feature
< abc1234 C (commit on main)
> def5678 F (commit on feature)
> ghi9012 E (commit on feature)
> jkl3456 D (commit on feature)

< = left side (main), > = right side (feature).

Important: .. and ... in git log vs. git diff

Syntaxgit loggit diff
A..BCommits reachable from B but not ACompare snapshots at A and B
A...BCommits reachable from A or B but not bothDiff from merge-base to B

They have completely different semantics between the two commands. This is one of Git's most confusing asymmetries.


8. Navigating Merge-Heavy Histories

Real-world repositories with many contributors and frequent merging can have complex, tangled graphs. These options help you cut through the noise.

--first-parent — Follow Only the Main Line

git log --oneline --first-parent

In a merge commit, the first parent is always the branch you were on when you ran git merge. --first-parent follows only these, skipping the merged-in branches. The result is a linear history of merges into the main branch:

Without --first-parent:
*   f5a3b2c Merge feature/auth
|\
| * abc1234 feat: add login
| * def5678 feat: add form
|/
*   ghi9012 Merge feature/api
|\
| * jkl3456 feat: add endpoint
|/
* mno7890 initial commit

With --first-parent:
* f5a3b2c Merge feature/auth
* ghi9012 Merge feature/api
* mno7890 initial commit

This is invaluable for repos using merge-based workflows (GitHub PRs, GitLab MRs) where you want to see what was merged and when, without the details of every feature branch.

--merges / --no-merges

# Show only merge commits
git log --oneline --merges
 
# Show only non-merge commits (skip merge commits)
git log --oneline --no-merges

--ancestry-path

When using a range, --ancestry-path limits output to commits that are descendants of the first commit AND ancestors of the second:

# Commits directly on the path between v1.0 and v2.0
git log --oneline --ancestry-path v1.0..v2.0

This filters out side branches that were merged but weren't on the direct line between the two points.

Limiting Output

# Show only the last N commits
git log -5
git log -n 5
 
# Skip the first N commits
git log --skip=10 -5
 
# Stop after N commits total
git log --max-count=20

9. git grep — Search Files at Any Point in History

While git log -S searches commit diffs for string changes, git grep searches the actual file contents of a specific snapshot.

Basic Usage

# Search current working tree
git grep "validateEmail"
 
# Search with line numbers
git grep -n "validateEmail"
 
# Case-insensitive
git grep -i "validateemail"
 
# Count matches per file
git grep -c "validateEmail"

Why git grep Over Regular grep?

  1. Faster — it only searches tracked files (ignores node_modules, build outputs, etc.)
  2. History-aware — you can search any commit, not just the working tree
  3. Scope detection — it can show the enclosing function/scope

Searching a Specific Commit

# Search in the v1.0 tag
git grep "validateEmail" v1.0
 
# Search in a specific commit
git grep "validateEmail" abc1234
 
# Compare: current vs. old
git grep -c "TODO" HEAD
git grep -c "TODO" v1.0

Advanced Options

# Show enclosing function/scope
git grep -p "validateEmail"
 
# Group results with headings (file name as header)
git grep --heading --break "validateEmail"
 
# Search with regex
git grep -e "validate.*Email" --and -e "return"
 
# Boolean combinations
git grep -e "error" --and -e "handler" -- "*.js"
 
# Invert match (lines NOT containing)
git grep -v "console.log" -- "*.js"

Combining git grep with git log

# Find the commit that first introduced "validateEmail" in any file
git log --oneline --all -S "validateEmail"
 
# Then see how it was used in that commit
git grep "validateEmail" abc1234

10. Creating Useful Log Aliases

The most productive Git users have a set of well-crafted log aliases. Here are battle-tested examples.

Essential Aliases

# Compact graph log (the everyday workhorse)
git config --global alias.lg "log --oneline --graph --decorate"
 
# Full graph with all branches
git config --global alias.lga "log --oneline --graph --decorate --all"
 
# Log with author and relative date
git config --global alias.ll "log --pretty=format:'%C(yellow)%h%C(reset) %s %C(cyan)(%ar)%C(reset) %C(blue)<%an>%C(reset)%C(red)%d%C(reset)'"
 
# Log with full date
git config --global alias.lf "log --pretty=format:'%C(yellow)%h%C(reset) %C(green)%ai%C(reset) %s %C(blue)<%an>%C(reset)%C(red)%d%C(reset)'"
 
# What did I do today?
git config --global alias.today "log --oneline --since='midnight' --author='$(git config user.name)'"
 
# What changed between this branch and main?
git config --global alias.ahead "log --oneline main..HEAD"
git config --global alias.behind "log --oneline HEAD..main"

Oh My Zsh Git Plugin Aliases

If you use Oh My Zsh, the git plugin provides these log aliases out of the box:

AliasCommandDescription
gloggit log --oneline --graph --decorateCompact graph
glogagit log --oneline --graph --decorate --allFull graph
glolgit log --graph --pretty='...' Graph with author + date
glolagit log --graph --pretty='...' --allFull graph with author + date
glodgit log --graph --pretty='...' --date=shortGraph with short date
glodsgit log --graph --pretty='...' --date=isoGraph with ISO date

You can override these in your .zshrc to customize the format (e.g., remove parentheses around dates, angle brackets around author names).

Creating Git Config Aliases

Aliases defined in .gitconfig use the [alias] section:

[alias]
    lg = log --oneline --graph --decorate
    lga = log --oneline --graph --decorate --all
    ll = log --pretty=format:'%C(yellow)%h%C(reset) %s %C(cyan)(%ar)%C(reset) %C(blue)<%an>%C(reset)%C(red)%d%C(reset)'
    # Count commits by author
    who = shortlog -sn --no-merges
    # Show files changed in last commit
    last = diff-tree --no-commit-id --name-only -r HEAD

Command Reference

CommandDescription
git log --onelineOne commit per line (short SHA + subject)
git log --graphASCII art branch topology
git log --allInclude all branches and tags
git log --decorateShow branch/tag labels on commits
git log -n 5Limit to last 5 commits
git log --pretty=format:"..."Custom output format
git log --author="name"Filter by author (regex match)
git log --since="2 weeks"Commits after a date
git log --until="2024-01-01"Commits before a date
git log --grep="pattern"Search commit messages
git log -S "string"Commits that add/remove a string (pickaxe)
git log -G "regex"Commits with diff lines matching regex
git log -- path/to/fileCommits touching a specific path
git log --follow -- fileFollow renames for a single file
git log -L 10,20:fileHistory of specific lines in a file
git log main..featureCommits on feature not on main
git log main...featureCommits on either but not both
git log --left-right A...BShow which side each commit is on
git log --first-parentFollow only first parent (main line)
git log --mergesShow only merge commits
git log --no-mergesExclude merge commits
git show <commit>Display a single commit with its diff
git show -s <commit>Commit metadata only (no diff)
git grep "pattern"Search file contents in current tree
git grep "pattern" <commit>Search file contents at a specific commit
git grep -n -p "pattern"Search with line numbers and scope
git shortlog -snCommit count by author, sorted

Hands-On Lab

Setup

We'll build a repository with a rich enough history to make exploration meaningful.

mkdir log-lab && cd log-lab
git init
 
# Simulate a multi-author project by configuring author per commit
# (using --author flag)
 
# Initial commit
cat > app.js << 'EOF'
function main() {
  console.log("App starting");
  initialize();
}
 
function initialize() {
  console.log("Initializing...");
}
 
module.exports = { main, initialize };
EOF
git add app.js
git commit --author="Alice Smith <alice@example.com>" -m "feat: initial application setup"
 
# Tag v1.0
git tag -a v1.0 -m "Version 1.0"
 
# Add config
cat > config.js << 'EOF'
const config = {
  port: 3000,
  host: "localhost",
  debug: false,
  logLevel: "info"
};
module.exports = config;
EOF
git add config.js
git commit --author="Bob Jones <bob@example.com>" -m "feat: add configuration module"
 
# Add utils
cat > utils.js << 'EOF'
function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
 
function formatDate(date) {
  return date.toISOString().split('T')[0];
}
 
module.exports = { validateEmail, formatDate };
EOF
git add utils.js
git commit --author="Alice Smith <alice@example.com>" -m "feat: add utility functions
 
Includes email validation and date formatting helpers.
These will be used across multiple modules."
 
# Create a feature branch
git checkout -b feature/auth
 
cat > auth.js << 'EOF'
const { validateEmail } = require('./utils');
 
function login(email, password) {
  if (!validateEmail(email)) {
    throw new Error("Invalid email");
  }
  // TODO: implement actual auth
  return { token: "abc123", user: email };
}
 
function logout(token) {
  // TODO: invalidate token
  return true;
}
 
module.exports = { login, logout };
EOF
git add auth.js
git commit --author="Carol Lee <carol@example.com>" -m "feat: add authentication module
 
Implements login and logout functions.
Uses email validation from utils."
 
echo '// Session management coming soon' >> auth.js
git add auth.js
git commit --author="Carol Lee <carol@example.com>" -m "chore: add session management placeholder"
 
# Switch back to main and make a parallel change
git checkout main
 
cat > logger.js << 'EOF'
const config = require('./config');
 
function log(level, message) {
  const levels = ['debug', 'info', 'warn', 'error'];
  if (levels.indexOf(level) >= levels.indexOf(config.logLevel)) {
    console.log(`[${level.toUpperCase()}] ${new Date().toISOString()}: ${message}`);
  }
}
 
module.exports = { log };
EOF
git add logger.js
git commit --author="Bob Jones <bob@example.com>" -m "feat: add logging module
 
Respects logLevel from config.
Supports debug, info, warn, error levels."
 
# Merge feature branch
git merge --no-ff feature/auth -m "Merge branch 'feature/auth' into main"
 
# More commits after merge
cat > config.js << 'EOF'
const config = {
  port: 3000,
  host: "localhost",
  debug: true,
  logLevel: "debug",
  sessionTimeout: 3600
};
module.exports = config;
EOF
git add config.js
git commit --author="Bob Jones <bob@example.com>" -m "fix: enable debug mode and add session timeout
 
BREAKING: debug is now true by default.
Added sessionTimeout config option."
 
# Tag v2.0
git tag -a v2.0 -m "Version 2.0 - Authentication release"
 
# A few more commits
echo "// Performance optimization" >> app.js
git add app.js
git commit --author="Alice Smith <alice@example.com>" -m "perf: optimize main startup sequence"
 
echo "// Error boundary" >> app.js
git add app.js
git commit --author="Alice Smith <alice@example.com>" -m "fix: add error boundary to main
 
Prevents unhandled exceptions from crashing the app.
Related to issue #42."
 
echo "// Metrics" >> logger.js
git add logger.js
git commit --author="Dave Wilson <dave@example.com>" -m "feat: add metrics collection to logger"
 
echo "# Project README" > README.md
git add README.md
git commit --author="Carol Lee <carol@example.com>" -m "docs: add project README"
 
echo ""
echo "Repository ready! Commits:"
git log --oneline --all

Part 1: Display Formats

# 1. Default full format
git log -3
 
# 2. Compact oneline
git log --oneline
 
# 3. Graph with all branches
git log --oneline --graph --all --decorate
 
# 4. Full format (author + committer)
git log --format=full -1
 
# 5. Custom format — hash, subject, author, relative date
git log --pretty=format:"%C(yellow)%h%C(reset) %s %C(blue)<%an>%C(reset) %C(green)(%ar)%C(reset)"

Checkpoint: The graph view should show the merge commit and the feature/auth branch topology. The custom format should produce a clean, colored single-line output.

Part 2: Filtering by Author and Date

# All commits by Alice
git log --oneline --author="Alice"
 
# All commits by Bob
git log --oneline --author="Bob"
 
# Commits by Alice OR Carol
git log --oneline --author="Alice\|Carol"
 
# Count commits per author
git shortlog -sn
 
# Commits in the last 30 minutes (should show your recent work)
git log --oneline --since="30 minutes ago"

Exercise: How many commits did each author make? Who made the most?

Checkpoint: git shortlog -sn should show Alice with the most commits, followed by Bob, Carol, and Dave.

# Find commits mentioning "auth" in the message
git log --oneline --grep="auth"
 
# Find commits mentioning "feat:" (conventional commit prefix)
git log --oneline --grep="feat:"
 
# Find commits mentioning "fix:"
git log --oneline --grep="fix:"
 
# Pickaxe: when was "validateEmail" introduced?
git log --oneline -S "validateEmail"
 
# Pickaxe: when was "debug: true" introduced?
git log --oneline -S "debug: true"
 
# Regex search: find commits that touched any TODO
git log --oneline -G "TODO"
 
# Invert grep: commits NOT mentioning "feat" or "fix" or "chore"
git log --oneline --grep="feat:\|fix:\|chore:" --invert-grep

Checkpoint: The -S "validateEmail" search should return the commit that added utils.js. The -S "debug: true" should return Bob's config change.

Part 4: Path Filtering and Line History

# Commits that touched config.js
git log --oneline -- config.js
 
# Commits that touched anything in the project root
git log --oneline -- "*.js"
 
# History of the config object (line range)
git log -L 1,7:config.js
 
# What changed in auth.js across all commits?
git log -p -- auth.js

Checkpoint: git log -- config.js should show Bob's two commits (initial add and the debug change). git log -L 1,7:config.js should show the evolution of the config object.

Part 5: Range Notation

# What's on feature/auth that isn't on main?
git log --oneline main..feature/auth
# (Should be empty — it was merged)
 
# What was on feature/auth before it was merged?
# Use the merge commit's parents:
git log --oneline HEAD~4^2 --not HEAD~4^1
# Or more simply, look at the merge commit:
git log --oneline --first-parent
 
# What's between v1.0 and v2.0?
git log --oneline v1.0..v2.0
 
# What's happened since v2.0?
git log --oneline v2.0..HEAD
 
# Symmetric difference between v1.0 and HEAD
git log --oneline --left-right v1.0...HEAD

Checkpoint: git log --oneline v1.0..v2.0 should show all commits between the two tags. git log --oneline v2.0..HEAD should show commits after the v2.0 tag.

Part 6: Navigating Merge History

# Full history (with merge details)
git log --oneline --graph
 
# First-parent only (main line)
git log --oneline --first-parent
 
# Only merge commits
git log --oneline --merges
 
# Only non-merge commits
git log --oneline --no-merges

Checkpoint: --first-parent should produce a clean linear history without the feature branch commits. --merges should show only the one merge commit.

Part 7: git grep — Searching Code

# Search for "validateEmail" in current code
git grep "validateEmail"
 
# With line numbers
git grep -n "validateEmail"
 
# Count occurrences per file
git grep -c "TODO"
 
# Search with function scope
git grep -p "validateEmail"
 
# Search at the v1.0 tag
git grep "validateEmail" v1.0
# (Should find nothing — utils.js didn't exist at v1.0)
 
# Search at v2.0
git grep "validateEmail" v2.0
# (Should find it in utils.js and auth.js)

Checkpoint: git grep "validateEmail" v1.0 should return nothing. git grep "validateEmail" v2.0 should return matches in both utils.js and auth.js.

Challenge

  1. Create an alias called git summary that shows the last 10 commits with: abbreviated hash, relative date, author name, and subject — all nicely formatted with colors
  2. Find all commits that mention an issue number (search for # followed by digits in commit messages)
  3. Use git log -L :login:auth.js to trace the history of the login function
  4. Generate a changelog for the v1.0 to v2.0 range using git shortlog
  5. Count how many feat: vs fix: vs other commit types exist in the repo

Common Pitfalls

PitfallWhat HappensPrevention
Using --all unnecessarilyCluttered output with irrelevant remote branchesDefault to no --all; add it only when you need the full picture
Confusing .. in log vs. difflog A..B = reachability; diff A..B = snapshot comparisonRemember: log thinks in terms of commit sets, diff thinks in terms of snapshots
Forgetting -- before file pathsGit may confuse a filename with a branch nameAlways use -- separator: git log -- path/to/file
--follow with multiple filesSilently ignores all but the first file--follow works with a single file only; for multiple files, omit it
Searching with -S when -G is needed-S misses commits that modify (but don't add/remove) a stringUse -G for regex matching within diffs, -S for introduction/removal
Not using --first-parent on busy reposOverwhelming graph with hundreds of interleaved branch commitsUse --first-parent to see just the merge points on the main line
Custom format without colorsOutput is hard to parse visuallyAdd %C(color) and %C(reset) around key fields

Pro Tips

  1. --first-parent is your best friend on active repositories. When a repo has dozens of merged feature branches, --first-parent gives you a clean, linear narrative of what was merged and when.

  2. Pipe to wc -l for quick counts: git log --oneline v1.0..v2.0 | wc -l instantly tells you how many commits are in a release.

  3. git log -1 --format=%H gives you just the SHA of the latest commit — useful in shell scripts, CI pipelines, and build systems.

  4. Combine --grep with --all to search for a commit message across all branches, not just the current one. A commit might be on a branch you forgot about.

  5. git log -p -S "function_name" is the ultimate archeology tool. It shows you the exact diff of every commit that added or removed that function name.

  6. Save your favorite format as a Git alias, not a shell alias. Git aliases in .gitconfig are portable across machines and shell environments. Shell aliases are tied to your .zshrc or .bashrc.

  7. git grep over grep -r: For any tracked repository, git grep is faster because it skips untracked files, build artifacts, and node_modules. It's also history-aware.


Quiz / Self-Assessment

  1. What does git log --oneline --graph --all --decorate show, and why is it useful?
Answer

It shows every commit in the repository (from all branches and tags) in a compact single-line format, with an ASCII art graph showing branch and merge topology, and labels showing which branches/tags point to which commits. It's useful for getting a complete picture of the repository's branching structure. This is the most commonly aliased log command.

  1. How do you find all commits by a specific author in the last month?
Answer
git log --oneline --author="Author Name" --since="1 month ago"

--author accepts a regex that matches against both name and email. --since (synonym: --after) accepts natural language date expressions. The filters are AND-combined.

  1. What is the difference between git log main..feature and git log main...feature?
Answer

main..feature (two dots) shows commits reachable from feature but NOT from main — the commits that feature has that main doesn't. main...feature (three dots) shows commits reachable from EITHER branch but NOT from both — the symmetric difference, showing divergent commits on both sides. Add --left-right with three dots to see which side each commit belongs to.

  1. What's the difference between --grep, -S, and -G in git log?
Answer
  • --grep searches commit messages for a pattern
  • -S (pickaxe) searches diffs for commits where the number of occurrences of a string changed (introduction/removal)
  • -G searches diffs for commits where any changed line matches a regex pattern (broader than -S)

Use --grep for "what commits mentioned X?", -S for "when was X introduced or removed?", and -G for "when was code matching X touched?"

  1. What does --first-parent do, and when should you use it?
Answer

--first-parent follows only the first parent of each merge commit, effectively showing the "main line" history without diving into the details of merged feature branches. In a merge commit, the first parent is always the branch you were on when you ran git merge. Use it on merge-heavy repositories (typical in GitHub PR workflows) to see a clean, linear sequence of what was merged and when, without the noise of individual feature branch commits.

  1. How do you view the history of a specific function across all commits?
Answer
git log -L :functionName:path/to/file.js

The -L flag with the :funcname:file syntax uses Git's built-in function detection to track the evolution of that function across commits. It shows the diff of the function in each commit that changed it. For specific line ranges, use git log -L 10,20:file.js.

  1. What format placeholder gives you the author name in a custom --pretty=format: string? How about relative date?
Answer

%an gives the author name, %ar gives the author date in relative format (e.g., "2 days ago"). Other useful placeholders: %h (short hash), %s (subject), %ae (author email), %ai (ISO date), %d (ref names/decorations), %cn (committer name).

  1. How does git grep differ from git log -S?
Answer

git grep searches the contents of files at a specific point in time (defaulting to HEAD). It tells you "where does this string appear right now?" git log -S searches through commit diffs across history. It tells you "in which commits was this string added or removed?" Use git grep to find current occurrences; use git log -S to find when something was introduced.

  1. You want to generate a list of all "feat:" commits between v1.0 and v2.0 with just the commit message (no hash). What command do you use?
Answer
git log --pretty=format:"%s" --grep="^feat:" v1.0..v2.0

Or for a simpler approach:

git log --oneline --grep="feat:" v1.0..v2.0

--pretty=format:"%s" outputs only the subject line. --grep="^feat:" matches commit messages starting with "feat:". v1.0..v2.0 limits to the range between the two tags.

  1. How do you follow a file's history across renames?
Answer
git log --oneline --follow -- path/to/file.js

The --follow flag tells Git to detect renames and continue showing history under the file's previous name. Without --follow, git log only shows history from the current filename onward. Limitation: --follow only works with a single file path, not directories or patterns.