Git Revision Selection and Ancestry Queries
Read advanced Git revision expressions, merge-parent selectors, object and message selectors, revision-set algebra, and ancestry-focused history queries without confusing ranges with diffs.
Advanced
Learner goal
Revision syntax is a language for selecting existing Git objects and commit sets. This lesson extends the V425 foundation: you will read merge-parent selectors, parent-set shorthands, object and commit-message selectors, revision ranges as set operations, and ancestry-focused history queries. The goal is not memorizing punctuation in isolation; it is being able to predict exactly which object or commits a command receives.
`^` chooses a parent; `^n` chooses a specific parent
For a commit revision R, `R^` means the first parent and `R^n` means the nth parent. On a normal single-parent commit there is only one parent. On a merge commit, parent order matters: `R^` is the first parent, while `R^2` is the second parent. This is different from `R~2`, which follows the first-parent chain twice.
git rev-parse MERGE^
git rev-parse MERGE^2
git rev-parse MERGE~2
git log --oneline --first-parent MERGEQuick check
A merge commit has first parent P1 and second parent P2. Which statement is correct?
A merge commit has two parents: P1 then P2.MERGE^2 = P2; MERGE~1 = P1.
Parent-set shorthands: `^@`, `^!`, and `^-n`
Three suffixes turn a single commit into a revision set. `R^@` names all of R's parents. `R^!` names R while excluding all of its parents, so as a range by itself it selects only R. `R^-n` is shorthand for `R^n..R`: include R and exclude everything reachable from its nth parent. These forms are especially useful around merge commits because they express topology without hard-coding parent object IDs.
git rev-parse MERGE^@
git rev-parse MERGE^!
git rev-list --oneline MERGE^-2
git rev-list --oneline MERGE^2..MERGEQuick check
What does `MERGE^!` denote when used as a revision set?
MERGE is a merge commit with two parents.The selected set contains MERGE, not its parent histories.
Compare `^n` with `~n` on a merge
A useful mental model is: `^` asks “which parent?”, while `~` asks “how many first-parent steps?”. Thus `MERGE^2` moves sideways into the second parent, but `MERGE~2` moves backward along the integration branch's first-parent line. On non-merge commits, `R^` and `R~1` coincide; on merge commits, confusing these operators is a common source of incorrect history queries.
# MERGE has parents P1 and P2; P1 has parent BASE
git rev-parse MERGE^
git rev-parse MERGE^2
git rev-parse MERGE~2Object-type and message selectors
Revision syntax can also constrain the kind of object Git must return. `R^{commit}` peels through tag objects until a commit is found; `R^{tag}` requires a tag object; `R^{object}` verifies an object without requiring tag peeling; and `R^{}` recursively peels annotated tags to a non-tag object. For commit-message selection, `:/text` names the youngest matching commit reachable from any ref, while `R^{/text}` restricts the search to commits reachable from R before applying the same message-match idea.
git rev-parse v1^{tag}
git rev-parse v1^{commit}
git rev-parse v1^{}
git rev-parse ':/fix parser'
git rev-parse 'HEAD^{/fix parser}'Quick check
What does `v1^{commit}` guarantee when `v1` names an annotated tag?
v1 is an annotated tag pointing at a commit.`v1^{commit}` resolves to the target commit object.
Quick check
What does `:/fix parser` select?
Multiple reachable commits have messages matching the expression.Git returns the youngest matching commit.
Select an object inside a commit with `R:path`
A revision can name more than a commit. `R:path` addresses the blob or tree stored at that path in the tree-ish named by R. This is different from a pathspec used to filter a command: `HEAD:README.md` names the object stored at that exact path in the `HEAD` tree. During an unresolved merge, the index also has stage-based forms such as `:1:path`, `:2:path`, and `:3:path` for the merge stages.
git rev-parse HEAD:README.md
git show HEAD:README.md
# During an unresolved merge:
git show :1:config.txt
git show :2:config.txt
git show :3:config.txtQuick check
What kind of thing does `HEAD:README.md` name?
HEAD contains README.md.The expression resolves the stored Git object at that path.
Revision ranges are set operations
For history-walking commands, a single revision means the commits reachable from it. Prefixing a revision with `^` excludes its reachable set. Therefore `A..B` is shorthand for `^A B`, while `A...B` is the symmetric difference: commits reachable from either A or B but not both. The three-dot form is equivalent to taking the two sides and excluding the commits reachable from all merge bases (`git merge-base --all`).
git log --oneline A..B
git log --oneline ^A B
git log --oneline A...B
git merge-base --all A BQuick check
For `git log`, what is the set meaning of `A...B`?
A and B have a common history plus unique commits on each side.The shared history is excluded; unique commits on either side remain.
Quick check
Which expression is the two-dot form of the set operation `^A B`?
A is the excluded side; B is the included side.`A..B` names B's reachable commits after excluding A's reachable commits.
`git diff` gives the range notation a different meaning
Do not transfer `git log` range semantics blindly to `git diff`. The current official `git-diff` documentation says `git diff A B` compares the two endpoint trees, and `git diff A...B` compares the tree at the merge base of A and B against B. In other words, `A...B` in `git diff` is not the symmetric-difference commit set taught for history traversal.
git diff A B
git diff A...B
# Equivalent endpoint comparison:
git diff $(git merge-base A B) BQuick check
What does `git diff A...B` mean according to the current official `git-diff` manual?
A and B have a common ancestor.The diff starts at the merge base and ends at B.
Ancestry-focused history queries
`git log` and `git rev-list` can further constrain a range with `--ancestry-path`. Given a range such as `D..J`, the option keeps commits in that range that are on the ancestry path between the range endpoints: they are descendants of D and ancestors of J (or J itself). This is useful when a broad range contains parallel work that reached the same endpoint but is not actually descended from the starting commit.
git log --oneline D..J
git log --oneline --ancestry-path D..J
git log --oneline --ancestry-path=D D..JQuick check
Why can `--ancestry-path` return fewer commits than `D..J`?
D..J contains parallel commits from another branch that are not descendants of D.`--ancestry-path` filters those parallel commits out while retaining the D-to-J ancestry path.
First-parent traversal is a different ancestry question
`--first-parent` answers a different question: when history traversal reaches a merge, continue only through that merge's first parent. It is useful for viewing the mainline evolution of an integration branch without walking into the individual commits that were brought in by merged topic branches. It does not rewrite the repository or change what the merge commit's second parent is.
git log --oneline MERGE
git log --oneline --first-parent MERGEQuick check
What does `git log --first-parent MERGE` do at a merge commit?
MERGE has first parent P1 and second parent P2.The traversal continues from MERGE through P1 and does not enter P2's history.
Verify complex selectors before scripting with them
Advanced revision expressions are powerful but unforgiving. When a script needs a concrete object ID, use `git rev-parse --verify` and add an object-type assertion when appropriate, such as `^{commit}`. Quote selectors containing punctuation such as `:/...` when your shell could interpret special characters. Treat a failed resolution as a failed precondition rather than silently substituting another revision.
git rev-parse --verify HEAD^{commit}
git rev-parse --verify 'HEAD^{/fix parser}'
git rev-parse --verify HEAD:README.mdPractice what you learned
Use the dedicated Git code-completion exercise for this lesson.
Official-source boundary
This lesson is grounded in the current official Git project documentation: `gitrevisions` for revision names, parent shorthands, object selectors, message selectors, and range algebra; `git-rev-parse` for revision resolution; `git-rev-list` / `git-log` for ancestry traversal such as `--first-parent` and `--ancestry-path`; and `git-diff` for its command-specific two- and three-dot endpoint semantics. The live manuals were rechecked on 2026-10-01 and are current through Git 2.56.0.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.