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
- Git resolves the starting ref — by default
HEAD— to a commit hash.HEADpoints at a branch file (e.g..git/refs/heads/main), which contains that hash. - Git reads the commit object for that hash from the object database (
.git/objects), extracting its metadata and its parent pointer(s). - 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.
- If a commit has two parents (a merge commit), Git follows both lines, which is what
--graphvisualizes as diverging and reconverging paths. - The walk stops when it runs out of parents (the root commit), hits a limit like
-n, or exhausts the given revision range. - Filters like
--author,--since, and--grepare 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 --allby 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--grepfiltering by type actually works well later. - Use
--statinstead of-pwhen 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-pageso 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 setGIT_PAGER=cat) sogit logdoesn’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
- In any local Git repository with a few commits, run
git log --oneline -5to see the last five commits, then rungit log --stat -1on the most recent one to see which files it touched. - Create two branches from
main, add a commit on each, then rungit log main..<other-branch> --onelineto list only the commits unique to the other branch. Confirm the output matches what you expect from the commits you made. - 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--followand compare how much further back the history goes.
Summary
git logwalks backward from a starting ref (HEADby 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 --allis the single most useful combination for getting your bearings in a repo.- Filter with
--author,--since/--until, and--grepto narrow down history by who, when, and what. - Use
-p/--patchfor full diffs,--statfor a lighter summary of changed files. - Long output opens a pager by default — press
qto exit, or use--no-pagerto skip it. - Put
--before a path argument when its name could be confused with a branch or tag.
