Resolve Revisions and Repository Paths with git rev-parse
git rev-parse turns a revision like HEAD~3 into a real object ID and finds your repository root from any subdirectory. Write enough Git-aware shell scripts and you will need both, plus a way to check that a user-supplied revision is genuine before you act on it. The examples were checked with Git 2.43.0, matching the installed git-man page on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 15 minutes. You need Git and a working repository. These commands only read repository state: none needs elevated privileges and none changes commits, refs or configuration.
1. Confirm the installed command
Check the executable and version before relying on an option. This matters when a script gets copied to an older host:
$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0
Checkpoint
The version output should match the manual you are using. The command is normally called as git rev-parse, not as a separate executable.
2. Find the working tree from a subdirectory
Run these from anywhere below the working tree:
$ git rev-parse --show-toplevel
/home/you/project
$ git rev-parse --show-prefix
src/lib/
$ git rev-parse --show-cdup
../../
--show-toplevel prints the top-level directory, absolute by default. The prefix is the current directory relative to that top level, and the cdup value is the path back up to it. At the top level, --show-prefix and --show-cdup both produce an empty line.
These values solve a common script error: a relative path received as src/lib is not automatically relative to the repository root. A script can preserve the caller's location, then switch to the root deliberately:
$ root=$(git rev-parse --show-toplevel) || exit 1
$ cd "$root" || exit 1
$ printf 'repository root: %s\n' "$PWD"
repository root: /home/you/project
Checkpoint
If this reports that you are not in a repository, stop there. Do not paper over the error with sudo; elevated privileges do not turn an unrelated directory into a Git work tree.
3. Inspect the repository and its paths
Use these read-only checks when a script needs to tell a work tree apart from Git's internal directory:
$ git rev-parse --is-inside-work-tree
true
$ git rev-parse --is-inside-git-dir
false
$ git rev-parse --git-dir
/home/you/project/.git
$ git rev-parse --show-object-format
sha1
The last value is this repository's object format, not a promise that every Git installation uses SHA-1: a repository using another supported hash format reports that format instead. --git-dir can be relative when Git chooses a relative path, so ask for a canonical absolute path when a script needs one:
$ git rev-parse --path-format=absolute --git-dir
/home/you/project/.git
--git-path objects resolves a path below the Git directory while respecting relocation variables such as GIT_OBJECT_DIRECTORY, which is safer for scripts than concatenating $(git rev-parse --git-dir) with a guessed directory name.
4. Validate a revision before using it
--verify takes exactly one argument and prints its object name only when Git can resolve it. Add a type suffix when a commit is required:
$ git rev-parse --verify HEAD^{commit}
8fc4c13fa03a16665b594e35dc991f3ae967062a
$ git rev-parse --verify --short HEAD
8fc4c13
The full length depends on the repository's object format. --short produces a unique abbreviation of at least four characters, using the effective core.abbrev setting when no length is supplied. Do not treat a short ID as a permanent identifier or a security decision.
For a revision supplied by another person, a file or a web request, put --end-of-options before the value. This stops a value beginning with a dash being read as another rev-parse option:
$ REV='HEAD'
$ git rev-parse --verify --end-of-options "$REV^{commit}"
8fc4c13fa03a16665b594e35dc991f3ae967062a
Quote the shell variable. If an invalid value should simply fail a test, add --quiet and inspect the status:
$ git rev-parse --verify --quiet --end-of-options 'not-a-revision' >/dev/null
$ printf 'status: %s\n' "$?"
status: 1
--default FALLBACK supplies a fallback only when no parameter is given; it does not turn an invalid supplied revision into the fallback. Treat a non-zero status as an error before using the output.
5. Read ranges without confusing their direction
Commands such as git log consume sets of reachable commits. Two-dot notation means the right side, excluding commits reachable from the left:
$ git log --oneline origin/main..HEAD
7b31c2a Add report
2e9a8f1 Fix parser
This asks what your current branch has that origin/main does not. The shorthand origin/main.. means origin/main..HEAD. The reverse, ..origin/main, asks what the remote side has that your current HEAD does not.
Three dots select the symmetric difference: commits reachable from either side but not both, useful for seeing both sides of a branch split:
$ git log --oneline --left-right main...feature/report
< 1a2b3c4 Main branch change
> 7b31c2a Add report
Do not put two dotted ranges side by side and assume most Git commands process two independent ranges. For ordinary history commands, the arguments form one combined revision set.
6. Use revision suffixes deliberately
- HEAD~3 means the third first-parent ancestor.
- HEAD^2 selects the second parent of a merge commit.
- HEAD^{tree} peels a tag or commit to a tree object.
- HEAD:README.md names the file as stored in the tree at
HEAD. - @{1} means the previous value of the current branch in its reflog, if that reflog exists.
These are names, not file paths on disk. A missing reflog entry, an absent path, or an object of the wrong type makes verification fail, so test the exact suffix you intend to pass to the next command.
7. Quote arguments for shell scripts
--sq-quote emits one shell-quoted line and only quotes its input; it does not interpret normal rev-parse options:
$ git rev-parse --sq-quote 'a b' "x'y"
'a b' 'x'\''y'
--sq is different: it still interprets rev-parse input, then emits one shell-quoted line. When using either result with eval, quote the command substitution exactly as the manual shows, and never mix untrusted text into a hand-built command string casually. Prefer arrays in Bash when you control the script; they avoid eval altogether.
Done means
- You can locate the repository root and tell it apart from
.git. - You verify a user-supplied revision with a type suffix and
--end-of-options. - You understand A..B selects B minus A, while A...B is symmetric.
- You treat short object IDs as display values, not durable identifiers.
- Your script quotes paths and revision values, checks exit statuses, and makes no unnecessary state changes.