Useful commands
The commands in this chapter are the ones I find myself reaching for often enough to want them written down, but not often enough to remember without looking. Every one of them is a direct application of something from earlier in the guide.
Find which commit a reference points at
$ git rev-parse HEADef49a14e3c8c05b89c6a0e639ad30027a3a95aee$ git rev-parse --short HEADef49a14
Find which branches contain a commit
$ git branch --contains main~3baz* conflictingfoomainpart-1part-2wip
Add -r for remote-tracking branches, or -a for both. git tag --contains does the same for tags, which answers "which release was this fix in?".
Find commits that are in one branch but not another
$ git log --oneline main..conflictingef49a14 Add shared file
This is the two-dot range from References: reachable from the right, not from the left. main.. on its own (no right side) means "up to HEAD", and @{u}.. is "what haven't I pushed?".
Exclude commits that were cherry-picked
Sometimes a commit is on both branches, but as two different objects, because it was cherry-picked. Git can detect these by comparing the changes rather than the hashes, and leave them out.
$ git log --oneline --cherry-pick --right-only main...conflictingef49a14 Add shared file
Compare two versions of a branch
After you've rebased or amended a branch, git diff between the old and new tips is useless (everything moved). git range-diff compares the two sequences of commits and shows which commits changed, which were added or dropped, and how each one's diff differs.
$ git range-diff main...wip1: 05af0a2 < -: ------- Add shared file (Spanish)-: ------- > 1: d0d4aaf Add greeting-: ------- > 2: 7a248e1 Add farewell
The most common form is git range-diff @{u}...HEAD on a branch you're about to force-push, to see exactly what the rebase changed. It is also the best way to review a pull request that has been rebased since you last looked at it.
View details of an object
$ git cat-file -p HEADtree b2bac5883a60a40f50f142f9ecf40809911d0af1parent 05af0a27a29222f09de063cd9d243cc1598caa6bauthor Joseph Wynn <[email protected]> 1780003240 +1200committer Joseph Wynn <[email protected]> 1780003420 +1200Add shared file
Show an object's type
$ git cat-file -t HEADcommit
Show an object's size
$ git cat-file -s HEAD246
Print the tree of a given reference
$ git ls-tree -r -t HEAD100644 blob f70f10e4db19068f79bc43844b49f3eece45c4e8 A.txt100644 blob 223b7836fb19fdf64ba2d3cd6173c6a283141f78 B.txt100644 blob 3cc58df83752123644fef39faab2393af643b1d2 C.txt100644 blob 178481050188cf00d7d9cd5a11e43ab8fab9294f D.txt100644 blob 1c507261389e25abfe3620ddd348c73f4eb3b91e E.txt100644 blob cf84443e49e1b366fac938711ddf4be2d4d1d9e9 F.txt100644 blob 2638c45f8e7bc5b56f70e92390153572811782c3 O.txt100644 blob d556eb5858d616a1bda314d82cf8f75a132244c9 shared.txt
Show the contents of a file at a specific commit
$ git show 'HEAD~1:shared.txt'uno
No checkout required. This works with any revision on the left of the colon, including origin/main: and stash@{0}:.
Find the most recent tag
git describe will take any reference or commit hash, and return the name of the most recent tag reachable from it. If the tag points at the commit you gave it, git describe will return only the tag name. Otherwise, it will suffix the tag name with the number of commits since the tag and an abbreviation of the commit hash. This can be helpful for build and release scripts, as well as figuring out which version a change was introduced in.
$ git describe mainv1.0-5-g05af0a2$ git describe --abbrev=0 mainv1.0$ git describe --tags --exact-match v1.0v1.0
--abbrev=0 gives just the tag, and --exact-match fails unless the commit is tagged, which is handy in a release script that should refuse to run from an untagged commit.
Find the commit that added or removed a string
$ git log --oneline --all -S 'Hello, world'd0d4aaf Add greeting
-S (the "pickaxe") finds commits where the number of occurrences of the string changed, which is a more useful question than "which commits mention this string in a diff". -G does the same with a regular expression. Both are dramatically faster than checking out old commits and grepping.
Show the history of a range of lines
$ git log --oneline -L 1,1:greeting.txt wipd0d4aaf Add greetingdiff --git a/greeting.txt b/greeting.txtnew file mode 100644index 0000000..a5c1966--- /dev/null+++ b/greeting.txt@@ -0,0 +1,1 @@+Hello, world
-L <start>,<end>:<file> follows a block of lines back through history, tracking it across edits, and shows each commit that touched it. It's git blame for people who want to know the previous answer too. -L :<function>:<file> does the same for a whole function, if Git can recognise the language.
Blame, ignoring the noise
$ git blame -s wip -- farewell.txt7a248e1f 1) Goodbye
git blame on its own is often dominated by whoever last reformatted the file. -w ignores whitespace changes, -M follows lines that moved within the file, and -C follows lines that were copied from other files. --ignore-rev skips a specific commit (the big reformat), and blame.ignoreRevsFile lets you keep a list of them in the repository so everybody's blame skips them.
Custom log formats
$ git log --oneline --format='%h %as %an: %s' -3ef49a14 2026-05-29 Joseph Wynn: Add shared file05af0a2 2026-05-29 Joseph Wynn: Add shared file (Spanish)eec7c29 2026-05-29 Joseph Wynn: O
--format takes placeholders for every field in a commit object (%H full hash, %an author name, %as author date as YYYY-MM-DD, %s subject, %b body, %d refs, and dozens more in git help log). Two related commands are worth knowing: git log --first-parent shows only the mainline of a branch that's had lots of merges, and git shortlog -sn counts commits per author.
$ git log --oneline --first-parent -3 main05af0a2 Add shared file (Spanish)eec7c29 O3b2d27e D$ git shortlog -sn --all19 Joseph Wynn
Count commits
$ git rev-list --count main8
rev-list is the plumbing behind git log. Anything log accepts as a range, rev-list --count will count, so git rev-list --count @{u}.. is "how many commits am I ahead by?".
List every branch and what it points at
$ git show-ref --heads9643a9ce6db7087d2802b4376f15a3dccf5185be refs/heads/barff3e5001c7ca5761a1a5c8e6114893413812b3b7 refs/heads/bazef49a14e3c8c05b89c6a0e639ad30027a3a95aee refs/heads/conflicting3b2d27ed1af386f275c0a1515baf6f99b022a9d3 refs/heads/foo05af0a27a29222f09de063cd9d243cc1598caa6b refs/heads/main2745d7b9a93d585d9ac32aea7384e672a5d748f4 refs/heads/part-1d08ddfc3fb64c53d842f8b6a84e8113e4127bee3 refs/heads/part-27a248e1fd286af3711359c186a38ba00b71200d7 refs/heads/wip
git for-each-ref is the more flexible version, with --format and --sort (git for-each-ref --sort=-committerdate refs/heads/ lists your branches by most recent commit, which is the fastest way to find what you were working on last week).
Configuration worth setting once
Finally, the handful of settings this guide has recommended along the way, all in one place.
$ git config set --global init.defaultBranch main$ git config set --global merge.conflictStyle zdiff3$ git config set --global pull.ff only$ git config set --global fetch.prune true$ git config set --global push.autoSetupRemote true$ git config set --global push.followTags true$ git config set --global push.useForceIfIncludes true$ git config set --global rebase.autoSquash true$ git config set --global rebase.updateRefs true$ git config set --global rerere.enabled true
None of these change what Git is. They just make its defaults match the way it's actually used, now that you know how it works.
