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 tagv1.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:
-
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 -
refs/foo
-
refs/tags/foo
: The namespace for tags -
refs/heads/foo
: The namespace for local branches -
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 -
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
^0rev
ifrev
is a commit. Ifrev
is a tag, then
is the commit to which the tag refers, possibly through a chain of other tags (seerev
^0
next).rev
^{commit
}
In a linear history,
is the previous commit torev
^rev
, and
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
^^
.rev
^2-
-
rev~n
For example,
HEAD~3
; this is the nth ancestor ofrev
, always following the first parent commit. Special cases:-
=rev
~rev
~1 -
=rev
~0rev
Again, be careful:
HEAD~2
=HEAD^1^1
=HEAD^^
, but these are not the same asHEAD^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 oftopic@{"last week"}
ortopic@{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 forrefname
). Note that this need not be the same as
, the nth prior commit on the branch! For example, ifrefname
~n
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
, thengit 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 isHEAD~5
orHEAD^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 fromrev
whose commit message matches the given regular expression. You can omitrev
by writing simply:/
; this selects the youngest matching commit reachable from any ref (branch or tag). A leadingregexp
!
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
tells Git to recursively dereference the object named by rev
^type
rev
until it reaches an object of the given type. For example:
-
release-4.1^{commit}
names the commit tagged byrelease-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:
-
is a synonym forrev
^0
.rev
^{commit} -
means to follow the chain to the first nontag object (of whatever type).rev
^{}
Addressing Pathnames
The notation
names a file by pathname in a given commit (e.g., rev
:path
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);
:
is actually short forpath
: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 excludeA
itself. This acts like a macro that expands to the list of parents ofA
, which are then interpreted according to (1). -
A^!
-
Add only the commit
A
. This acts like a macro that expandsA
, followed by the list ofA
â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 fromA
. Note that this excludesA
itself. -
A...B
=A B --not $(git merge-base A B)
-
This is all commits reachable from either
A
orB
, 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.
-
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.