You will read history far more often than you write it — understanding an unfamiliar codebase, hunting a regression, working out why a line exists. These are the tools that make history searchable rather than merely present.
git log, usefully
Bare git log is a wall of text. The options are the point:
# The one to memorise git log --oneline --graph --all --decorate # Bound the search git log --since="2 weeks ago" --until="yesterday" git log --author="priya" git log -20 # What changed git log -p # with patches git log --stat # files and line counts git log --name-only # just file names # Narrow to a path (-- separates paths from revisions) git log -- src/auth/ git log -p -- src/auth/session.js # A range of commits git log main..feature # in feature, not in main git log main...feature # in either, but not both
main..feature means "commits reachable from feature but not main" — what your branch adds, and the right question before opening a pull request. main...feature is the symmetric difference, showing where the two have diverged from each other. In git diff the three-dot form means something subtly different again: diff against the merge base.
The pickaxe: searching history by content
This is the most underused tool in Git, and the one that feels like a superpower the first time it lands.
# Commits where the number of occurrences of this string changed git log -S "processPayment" # Commits whose diff contains a line matching this regex git log -G "TODO.*urgent" # Combine with a path and show the patches git log -S "API_KEY" -p -- config/
-S answers "when was this introduced, and when was it removed?". Note that git grep searches only the current tree — it cannot find something that was added and later deleted, which is exactly the case when hunting a leaked secret or a removed feature.
git show
git show # HEAD, with its diff git show a3f9c2e # a specific commit git show v1.2.0 # what a tag points at git show HEAD~3:src/auth.js # a file as it was, three commits ago
That last form is quietly excellent: it prints an old version of a file to stdout without touching your working directory, so you can diff it, pipe it, or just look.
git blame
git blame src/auth.js git blame -L 40,60 src/auth.js # only lines 40-60 git blame -w src/auth.js # ignore whitespace-only changes git blame -C src/auth.js # follow code moved between files
A repository-wide Prettier or gofmt run makes blame attribute every line to whoever ran the formatter. The fix is .git-blame-ignore-revs: list the formatting commit hashes in that file, then
git config blame.ignoreRevsFile .git-blame-ignore-revs
GitHub honours the same file automatically. Worth setting up before you do a big reformat, not after.
git bisect
A binary search for the commit that introduced a behaviour. Over 200 commits it takes about eight tests instead of up to 200.
git bisect start git bisect bad # current commit is broken git bisect good v1.4.0 # this tag was fine # Git checks out a midpoint. Test it, then: git bisect good # or: git bisect bad # repeat until Git names the first bad commit git bisect reset # return to where you started
If you can express "is it broken?" as a script exiting non-zero on failure, automate the whole thing:
git bisect start HEAD v1.4.0 git bisect run ./scripts/reproduce.sh
Bisect is precisely why "one commit, one idea, always builds" matters. If half your commits are broken intermediate states, bisect keeps landing on them and you have to mark them git bisect skip, which blunts the search. A clean history makes bisect nearly magic; a messy one makes it frustrating.
git diff in its useful forms
git diff # working dir vs index git diff --staged # index vs HEAD — review before committing git diff HEAD # everything uncommitted git diff main feature # between two branches git diff main...feature # feature's changes since it left main git diff --stat # summary only git diff --word-diff # word-level, good for prose git diff --check # flag whitespace errors
Aliases worth having
git config --global alias.lg "log --oneline --graph --all --decorate" git config --global alias.st "status --short --branch" git config --global alias.last "log -1 --stat" git config --global alias.unstage "restore --staged"
git lg in particular you will use dozens of times a day.