git log

The git log command is how you read a repository’s history — every commit, in order, with who made it, when, and why they made it. It is the most-used read-only command in Git: whenever you need to understand what happened before you arrived, track down which commit introduced a bug, or preview what a merge will bring in, git log is where you start. Because every commit is a permanent, content-addressed snapshot, the log is not just a list of messages — it is a navigable map of your project’s entire history.

Overview / How it works

To understand git log, you first need to understand what it is walking through. Every time you run git commit, Git creates a commit object. That object is a small piece of data containing: a pointer to a tree object (a snapshot of every file and directory at that point, itself made of blobs — the raw file contents — and nested trees for subdirectories), the author name and email, the committer name and email (usually the same person, but not always — think of a rebase, where the author stays the same but the committer changes), a timestamp, a commit message, and a pointer to one or more parent commits. A regular commit has one parent. The very first commit in a repository has none. A merge commit has two (or more).

Because every commit points backward to its parent, the commits form a chain — technically a directed acyclic graph, since merges create branching and rejoining paths. A branch (like main) is nothing more than a small file containing the SHA-1 (or SHA-256, on newer repos) hash of the latest commit on that line of work — a movable pointer, updated automatically every time you commit. HEAD is, in the normal case, a pointer to a branch, which in turn points to a commit; this is why committing on a branch “just works” — Git updates the branch pointer, and HEAD follows along because it points at the branch, not the commit directly.

git log starts at whatever ref you give it (HEAD by default, meaning “the tip of the branch I currently have checked out”) and walks backward through parent pointers, printing each commit it visits. This is why git log only shows the history of your current branch by default — it has no way to see commits that aren’t reachable by following parent links from HEAD, unless you tell it to look elsewhere with a different ref, a range, or --all.

By default, Git pipes long output through a pager (usually less) so it doesn’t scroll off your screen. If your terminal appears to “freeze” after running git log, you’re not stuck — you’re in the pager. Press q to quit, arrow keys or space to scroll.

Syntax

git log [<options>] [<revision-range>] [[--] <path>...]
Option What it does
--oneline One commit per line: short hash + subject line.
--graph Draws an ASCII graph of branch/merge structure alongside the log.
--decorate Shows branch and tag names next to the commits they point to.
--all Shows commits reachable from any branch or tag, not just the current one.
-n <number> / -<number> Limits output to the last N commits (e.g. -5).
--author=<pattern> Filters to commits whose author name/email matches the pattern (regex).
--since=<date> / --until=<date> Filters commits by date range (accepts "2 weeks ago", ISO dates, etc.).
--grep=<pattern> Filters to commits whose message matches the pattern.
-p / --patch Shows the full diff introduced by each commit.
--stat Shows a summary of files changed and line counts per commit, without the full diff.
--pretty=format:"..." Fully customizes the output format using placeholders like %h, %an, %ad, %s.
--follow Follows a file’s history across renames (used with a path).
-S<string> Shows commits that added or removed an occurrence of the given string (the “pickaxe”).
--no-pager Prints output directly instead of piping through the pager.

Examples

1. Plain git log

git log
commit 8f2c9a1e4b6d3f0a7c5e9d2b1a4f6c8e0d3b5a7f (HEAD -> main)
Author: Priya Sharma <priya@example.com>
Date:   Fri Jul 31 10:14:22 2026 +0530

    fix: correct off-by-one error in pagination

commit 3e7b1d9c2f5a8e0b4d6c1a3f9e2b5d7c0a4f6e8b
Author: Priya Sharma <priya@example.com>
Date:   Thu Jul 30 16:02:41 2026 +0530

    feat: add login page component

commit 0a4c6e8b2d5f7a9c1e3b6d8f0a2c4e6b8d0f2a4c
Author: Priya Sharma <priya@example.com>
Date:   Wed Jul 29 09:45:03 2026 +0530

    chore: initial project scaffold

Each entry shows the full commit hash, author, date, and message, newest first. (HEAD -> main) tells you that HEAD and the main branch pointer both currently point to that top commit.

2. A compact, visual overview

git log --oneline --graph --decorate --all
* 8f2c9a1 (HEAD -> main, origin/main) fix: correct off-by-one error in pagination
* 3e7b1d9 feat: add login page component
| * 5d1f8b3 (origin/feature/nav-redesign) wip: sidebar collapse animation
|/
* 0a4c6e8 chore: initial project scaffold

This is one of the most useful invocations in daily use. --oneline condenses each commit to a hash and subject, --graph draws the branch topology with * and | characters, --decorate labels commits with their branch/tag names, and --all pulls in every branch (here you can see a diverging feature/nav-redesign branch), not just the checked-out one.

3. Filtering by author, date, and message

git log --author="Priya Sharma" --since="2026-06-01" --grep="fix" --oneline
8f2c9a1 fix: correct off-by-one error in pagination
c4e1a09 fix: prevent duplicate toast notifications

The three filters combine: only commits by Priya Sharma, made since June 1st 2026, whose message contains “fix”, are shown. This kind of query is exactly how you’d track down when a specific bug fix landed, or audit one contributor’s recent work. Filters like --author and --grep match as regular expressions, so partial matches work too.

4. Viewing the actual diff for a file’s history

git log -p -1 -- src/auth/login.js
commit 8f2c9a1e4b6d3f0a7c5e9d2b1a4f6c8e0d3b5a7f (HEAD -> main)
Author: Priya Sharma <priya@example.com>
Date:   Fri Jul 31 10:14:22 2026 +0530

    fix: correct off-by-one error in pagination

diff --git a/src/auth/login.js b/src/auth/login.js
index 1a2b3c4..5d6e7f8 100644
--- a/src/auth/login.js
+++ b/src/auth/login.js
@@ -12,7 +12,7 @@ function getPage(items, pageSize, page) {
-  return items.slice(page * pageSize, (page + 1) * pageSize + 1);
+  return items.slice(page * pageSize, (page + 1) * pageSize);
 }

-p (or --patch) shows the full unified diff for each matching commit, and -1 limits it to the single most recent one. The -- before the path tells Git “everything after this is a path, not a revision” — important when a name could be ambiguous (see Common Mistakes below).

How it works step by step

  1. Git resolves the starting ref — by default HEAD — to a commit hash. HEAD points at a branch file (e.g. .git/refs/heads/main), which contains that hash.
  2. Git reads the commit object for that hash from the object database (.git/objects), extracting its metadata and its parent pointer(s).
  3. The commit is printed (or queued, if you’re filtering/formatting), then Git moves to the parent commit and repeats — walking backward one link at a time.
  4. If a commit has two parents (a merge commit), Git follows both lines, which is what --graph visualizes as diverging and reconverging paths.
  5. The walk stops when it runs out of parents (the root commit), hits a limit like -n, or exhausts the given revision range.
  6. Filters like --author, --since, and --grep are applied per-commit during the walk — they don’t change which commits Git visits, only which ones it prints.

Common Mistakes

Mistake 1: Thinking the terminal froze

You run git log, the prompt doesn’t come back, and it looks stuck. It hasn’t — git log opened your pager (less by default). Press q to exit.

git config --global core.pager cat

This disables paging globally by routing output straight through cat instead — useful if you find the pager more confusing than helpful, though most people keep it for long histories and just remember q.

Mistake 2: Assuming git log shows every branch

A new user expects git log to show commits made on other branches too. It doesn’t — it only walks backward from HEAD, so commits on branches that haven’t been merged into your current one are invisible to a plain git log. Use git log --all (every ref) or git log branch-a..branch-b (commits on branch-b not yet on branch-a) to see across branches.

Mistake 3: Ambiguous name errors

git log feature-login

If a branch and a file or directory happen to share the name feature-login, Git can’t tell whether you mean the revision or the path, and errors out with something like fatal: ambiguous argument 'feature-login': unknown revision or path not in the working tree. Disambiguate with --:

git log -- feature-login

Everything after -- is treated strictly as a path. This habit is worth keeping even when there’s no current ambiguity, since a future branch or file name could collide.

Best Practices

  • Learn git log --oneline --graph --decorate --all by heart (or alias it, e.g. git config --global alias.lg "log --oneline --graph --decorate --all") — it’s the fastest way to orient yourself in any repo.
  • Write Conventional Commits-style messages (fix:, feat:, chore:, docs:) so --grep filtering by type actually works well later.
  • Use --stat instead of -p when you just want to know which files changed, not the full diff — it’s faster to skim.
  • Before merging, preview incoming work with a range like git log main..feature/login-page so you know what you’re about to bring in.
  • Use --follow -- <path> when investigating a file’s full history, since a plain path filter stops tracking a file at the commit where it was renamed.
  • In scripts or CI, pass --no-pager (or set GIT_PAGER=cat) so git log doesn’t hang waiting for pager input.
  • Reach for -S<string> (the “pickaxe”) when you need to find the commit that introduced or removed a specific line of code, not just a message keyword.

Practice Exercises

  1. In any local Git repository with a few commits, run git log --oneline -5 to see the last five commits, then run git log --stat -1 on the most recent one to see which files it touched.
  2. Create two branches from main, add a commit on each, then run git log main..<other-branch> --oneline to list only the commits unique to the other branch. Confirm the output matches what you expect from the commits you made.
  3. Pick a file that has been modified more than once in your repo’s history and use git log -p -- <path> to read through its full diff history, oldest change last. If the file was ever renamed, redo it with --follow and compare how much further back the history goes.

Summary

  • git log walks backward from a starting ref (HEAD by default) through each commit’s parent pointer(s), printing what it visits.
  • It only shows the current branch’s ancestry unless you pass --all, a different ref, or a revision range.
  • --oneline --graph --decorate --all is the single most useful combination for getting your bearings in a repo.
  • Filter with --author, --since/--until, and --grep to narrow down history by who, when, and what.
  • Use -p/--patch for full diffs, --stat for a lighter summary of changed files.
  • Long output opens a pager by default — press q to exit, or use --no-pager to skip it.
  • Put -- before a path argument when its name could be confused with a branch or tag.