Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Advanced ~50 min read

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.

bash
git rev-parse MERGE^
git rev-parse MERGE^2
git rev-parse MERGE~2
git log --oneline --first-parent MERGE

Quick 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.

bash
git rev-parse MERGE^@
git rev-parse MERGE^!
git rev-list --oneline MERGE^-2
git rev-list --oneline MERGE^2..MERGE

Quick 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.

bash
# MERGE has parents P1 and P2; P1 has parent BASE
git rev-parse MERGE^
git rev-parse MERGE^2
git rev-parse MERGE~2

Object-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.

bash
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.

bash
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.txt

Quick 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`).

bash
git log --oneline A..B
git log --oneline ^A B
git log --oneline A...B
git merge-base --all A B

Quick 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.

bash
git diff A B
git diff A...B
# Equivalent endpoint comparison:
git diff $(git merge-base A B) B

Quick 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.

bash
git log --oneline D..J
git log --oneline --ancestry-path D..J
git log --oneline --ancestry-path=D D..J

Quick 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.

bash
git log --oneline MERGE
git log --oneline --first-parent MERGE

Quick 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.

bash
git rev-parse --verify HEAD^{commit}
git rev-parse --verify 'HEAD^{/fix parser}'
git rev-parse --verify HEAD:README.md

Practice 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.

Was this page helpful?

Mark this learning resource complete to track your learning progress.