Chapter 8. Naming Commits

Git has a variety of ways to refer to (or name, or “spell”) Git objects, usually commits, either individually or as a set, by following the commit graph or matching some criteria. You can find further detail on the conventions described next in gitrevisions(7).

The command git rev-parse is useful for checking your understanding: it will take a name in the various syntaxes presented here and translate it to an object ID, so you can make sure it refers to the object you expected. For names that represent sets of commits, git rev-list will show the resulting set.

Naming Individual Commits

Commit ID

The full SHA-1 object ID
For example, 2ee20b94203f22cc432d02cd5adb5ba610e6088f.
An abbreviated object ID
A prefix of an object’s full ID unique to your repository. So 2ee20b94 could name the same object as before, if no other object in your database has an ID beginning with those digits (if there were a conflict, you could just use a few more digits).
git describe
The output of the git describe command, which names commits relative to a tag; for example, v1.7.12-146-g16d26b16 refers to commit 16d26b16, which is 146 commits away from the tag v1.7.12. As output, this might be used as part of a build identifier, where it suggests to the reader the proximity of the build to a tag with a presumably helpful name. As input to Git however, only the trailing hex digits after -g are meaningful, and are used as an abbreviated commit ID.

Ref Name

A simple ref points directly to an object ID. Git follows a symbolic ref such as “master” until it finds a simple ref; for example, HEAD points to master if you are that branch, and master points to the commit at the branch tip. If the object is a tag rather than a commit, then Git follows the tag (possibly through intermediate tags) until it reaches a commit.

There are several rules for expanding ref names, allowing you to use short names in most situations rather than fully qualified names such as refs/heads/master. To find a ref named foo, Git looks for the following in order:

  1. foo: Normally, these are refs used by Git internally, such as HEAD, MERGE_HEAD, FETCH_HEAD, and so on, and are represented as files directly under .git
  2. refs/foo
  3. refs/tags/foo: The namespace for tags
  4. refs/heads/foo: The namespace for local branches
  5. refs/remotes/foo: The namespace for remotes, though this would not ordinarily itself be a ref, but rather a directory containing the remote’s refs
  6. refs/remotes/foo/HEAD: The default branch of the remote “foo”

Briefly, this means that git checkout foo will check out a tag named foo if there is one, otherwise, a branch; if there is neither, but there is a remote named foo, then it will check out the default branch of that remote.

Names Relative to a Given Commit

In the following, rev refers to any “revision”: an object referred to using any of the syntaxes discussed in this chapter. These rules can apply multiple times; e.g., a tag name tigger is a rev, thus tigger^ is also a rev, as is tigger^^ (using the first rule that follows):

rev^n

For example, master^2; this refers to the nth parent of a commit, numbered starting at 1. Recall from The Object Store that a commit contains a list of zero or more parent commits, referred to by their object IDs; commits with more than one parent are produced by merging. Special cases:

  • rev^ = rev^1
  • rev^0 = rev if rev is a commit. If rev is a tag, then rev^0 is the commit to which the tag refers, possibly through a chain of other tags (see rev^{commit} next).

In a linear history, rev^ is the previous commit to rev, and rev^^ the commit two steps back. Remember though that in the presence of merges, there may not be a single “previous commit,” and these expressions may not do what you expect; for example, note carefully that, in Figure 8-1, rev^^ ≠ rev^2.

rev^^ vs rev^2
Figure 8-1. rev^^ vs rev^2
rev~n

For example, HEAD~3; this is the nth ancestor of rev, always following the first parent commit. Special cases:

  • rev~ = rev~1
  • rev~0 = rev

Again, be careful: HEAD~2 = HEAD^1^1 = HEAD^^, but these are not the same as HEAD^2.

Names Relative to the Reflog

Local branch names usually have a reflog: a log of commits that used to be the head of this branch, along with the actions that changed it each time: commit, cherry-pick, reset, and so on. You view the composite log with git log -g, which follows your trail from one branch log to another via checkouts. The syntax refname@{selector} allows you to name a single commit according to various criteria evaluated against your reflog:

refname@{time/date}

The commit named by this ref at the given point in time. The time can be specified in a very flexible format that does not appear to be documented in the man pages, but that includes such expressions as:

  • now
  • yesterday
  • last week
  • 6 months ago
  • two Saturdays past
  • Sat Sep 8 02:09:07 2012 -0400 (or meaningful subsets of this)
  • 1966-12-06 04:33:00

Times after the latest commit return the latest commit, and similarly times previous to the earliest commit return the earliest commit. You can use dots instead of spaces to avoid having to quote or escape spaces to the shell, to ease typing: topic@{last.week} instead of topic@{"last week"} or topic@{last\ week}.

refname@{n}

For nonnegative n, this is the nth prior value of refname (zero refers to the current value and is a synonym for refname). Note that this need not be the same as refname~n, the nth prior commit on the branch! For example, if git pull performs a fast-forward update of a branch, there will be one entry in the reflog, but possibly several intervening commits. This is because Git added those commits in a single action; your branch moved from the previous commit to the last of the new ones in one step, and your branch was never “at” any of the intermediate ones (you never had them checked out).

You can omit refname to refer to the current branch (e.g., @{5}).

@{-n}
With a negative number, this is the current tip of the nth branch checked out before the current one. For example, if you’re on master and switch to foo with git checkout foo, then git checkout @{-1} will take you back to master. Note the very different meanings of @{5} and @{-5}: the first is the fifth prior position of the current branch, while the latter is the fifth prior branch you checked out (and neither of them is HEAD~5 or HEAD^5). Also note the word “current” in this description: if the eighth prior branch you checked out was master, it probably had a different tip commit then, as reflected in the corresponding reflog entry—but this notation refers to the current tip of that branch. (You can’t prefix this form with a ref name, as it is not relative.)

The critical thing to keep in mind about this syntax is that it is relative to your reflog, which is part of your repository and reflects your local work history; commits named this way are not globally meaningful or unique. Your reflog is a history of a particular branch name in your repository and the commits to which it has referred over time as a result of your checkouts, pulls, resets, amends, etc.; this is distinct from the history of the branch itself (a portion of the commit graph). The name master@{yesterday}, for example, may refer to a different commit in your repository than in someone else’s, even if you are working on the same project; it depends on what you were doing yesterday.

The Upstream Branch

The notation foo@{upstream} (or just foo@{u}) names the branch upstream of the branch foo, as defined by the repository configuration. This is usually arranged automatically when checking out a local branch corresponding to a remote one, but may be set explicitly with commands such as git checkout --track, git branch --set-upstream-to, and git push -u. It just gives the object ID of the upstream branch head, though; options to git rev-parse are useful to find out the upstream branch name:

$ git rev-parse HEAD@{upstream}
b801f8bf1a76ea5c6c6ac7addee2bc7161a79c93

$ git rev-parse --abbrev-ref HEAD@{upstream}
origin/master

$ git rev-parse --symbolic-full-name HEAD@{upstream}
refs/remotes/origin/master

The first is more convenient but may have difficulties if the branch name is ambiguous; Git will warn in that case. (See also the strict and loose arguments to --abbrev-parse.)

Matching a Commit Message

rev^{/regexp}
For example, HEAD^{/"fixed pr#1234"}; this selects the youngest commit reachable from rev whose commit message matches the given regular expression. You can omit rev by writing simply :/regexp; this selects the youngest matching commit reachable from any ref (branch or tag). A leading ! is reserved (presumably for some sort of negation, though it does not yet have that meaning), so you have to repeat it as an escape if need be: :/!!bang searches for the string “!bang”.

Notes

  • Watch out for assuming that the commit you get is the one you want, especially if you omit rev; multiple commits might match your regular expression, and “youngest commit” means the one closest to the edge of the commit graph, which may not be the one with the most recent committer or author date. git show -s is useful to check that you have the right commit; omit the -s if you want to see the commit diff as well as the description (author, committer, date, and so on).
  • The match is on the entire commit message, not just the subject, so the matching text itself may not show up if you use git log --oneline together with a match expression.
  • You can’t specify case-insensitive matching; if you want that, use git log -i --grep, which also uses the broader PCRE regular expressions rather than the simpler “regcomp” style used by the :/ syntax.

Following Chains

There are various kinds of pointers or indirection in Git: a tag points to another object (usually a commit); a commit points to the tree representing the content of that commit; a tree points to its subtrees; and so on. The syntax rev^type tells Git to recursively dereference the object named by rev until it reaches an object of the given type. For example:

  • release-4.1^{commit} names the commit tagged by release-4.1, even if there are intermediate tags.
  • master~3^{tree} names the tree associated with the third commit back from the tip of the master branch.

You don’t often have to use these kinds of names, as Git is smart about doing this automatically when appropriate. If you give a tag to git checkout, it knows you mean to check out the tagged commit; similarly, if you want to list the filenames in a commit, git ls-tree -r master~3 would be sufficient. However, sometimes you need to be more precise: git show release-4.1 would show both the tag and the commit; you could use release-4.1^{commit} to show only the commit. Special cases:

  • rev^0 is a synonym for rev^{commit}.
  • rev^{} means to follow the chain to the first nontag object (of whatever type).

Addressing Pathnames

The notation rev:path names a file by pathname in a given commit (e.g., olympus@{last.week}:pantheon/zeus). Actually, it’s more general than that: recall from The Object Store that a pathname foo/bar/baz names an object in some tree, either a blob (the contents of a file baz) or another tree (the entries in a directory baz). So rev can be any tree-like object: a tree (obviously), a commit (which has an associated tree), or the index, and the object selected by path may be a blob (file) or another tree (directory). Special cases:

:path
Addresses an object in the index.
:n:path
Addresses an object in the index, including its stage number (see Details on Merging); :path is actually short for :0:path.

Warning

Outside of Git, a filename such as foo/bar, without a leading slash, is relative to the current directory. In the notation master:foo/bar, however, it is absolute in the sense that it starts at the top of the tree of the named commit (the tip commit of the branch master, in this case). So if you’re in the directory foo and want to see the version of bar two commits back, you might think to type git show HEAD~2:bar—but you’ll get an error, or see the bar in the top level of the repository, if there is one.

To use relative pathnames in this notation, be explicit by using ./ or ../; here, you need git show HEAD~2:./bar instead.

Naming Sets of Commits

The foregoing notation names individual commits. Git also allows you to name sets of commits, using a combination of reachability in the commit graph (containment in a branch or tag), and the usual mathematical operations on sets: union, intersection, complement, and difference. Here, the letters A, B, C, and so on are names for commits using any of the syntaxes introduced earlier. These terms can be used in combination, as a space-separated list of terms, and the definitions read as actions: adding or removing certain commits. Remember that a commit is always considered reachable from itself.

A
Add all commits reachable from A.
^A
Remove all commits reachable from A.
A^@
Add all commits reachable from A, but exclude A itself. This acts like a macro that expands to the list of parents of A, which are then interpreted according to (1).
A^!
Add only the commit A. This acts like a macro that expands A, followed by the list of A’s parents each prefixed with a caret, which are then interpreted according to (1) and (2).

Since cases (3) and (4) can be expressed as combinations of (1) and (2), we can consider just the latter. To get a definition by sets for any expression, say:

A ^X ^Y B C ^Z …

Rearrange to gather the T and ^T terms together:

A B C … ^X ^Y ^Z …

And rewrite as:

(A ∪ B ∪ C ∪ …) ∩ (X ∪ Y ∪ Z ∪ …)′

where each letter is interpreted as in (1), and the “prime” symbol (′) indicates the complement of a set in usual mathematical notation. If either category of term is absent, that union is the empty set; thus, if there are no caret terms, the intersection is with the complement of the empty set, that is, all commits—and so does not affect the result. Also, a term ^A by itself is meaningful and accepted, if not terribly useful: according to our definitions, this is the intersection of the empty set with the set of commits not reachable from A—that is, the empty set.

Here are some useful abbreviations:

--not X Y Z … = ^X ^Y ^Z …
A..B = ^A B
This is all commits reachable from B but not from A. Note that this excludes A itself.
A...B = A B --not $(git merge-base A B)
This is all commits reachable from either A or B, but not from both. It is called the symmetric difference, which is the name for the corresponding set operation: (A ∪ B) − (A ∩ B).

For the .. and ... operators, a missing commit name on either side defaults to HEAD.

Here are some examples using this simple commit graph. See Figure 8-2.

Simple commit graph
Figure 8-2. Simple commit graph
  • master = {A, B, C, X, Y, Z}, the commits on the master branch
  • master..topic = {1, 2, 3}, the commits on topic not yet merged into master
  • master...topic = {1, 2, 3, X, Y, Z}, the commits by which the topic and master branches differ

A useful command for exploring this notation is git rev-list, which expands one of these expressions into the corresponding set of commits. It’s especially helpful to combine it with git name-rev thus:

$ git rev-list rev | git name-rev --stdin --name-only

This will print out the commit set using names relative to local branches and tags.

Warning

The use of this set notation depends on context. git log interprets its arguments just as shown in this section, indicating the set of commits on which it should report. git checkout, however, does not accept it, since it doesn’t make sense to check out more than one commit at a time. And git show treats individual revs as naming just one commit (rather than all commits reachable from it), but accepts compound forms such as A..B.

Note too that git diff also uses the .. and ... syntaxes with pairs of commits—but with entirely different meanings! git diff A..B is just a synonym for git diff A B. Caveat Gittor.

Get Git Pocket Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.