Single revision selection

During development, many times you want to select a single revision in the history of a project, to examine it, or to compare with the current version. The ability to a select revision is also the basis for selecting a revision range, for example a subsection of history to examine.

Many Git commands take revision parameters as arguments, which is typically denoted by <rev> in Git reference documentation. Git allows you to specify specific commits or a range of commits in several ways.

HEAD – the implicit revision

Most, but not all, Git commands that require the revision parameter, default to using HEAD. For example, git log and git log HEAD will show the same information.

The HEAD denotes the current branch, or in other words the commit that was checked out into the working directory, and forms a base of a current work.

There are a few other references which are similar to HEAD:

  • FETCH_HEAD: This records the information about the remote branches that were fetched from a remote repository with your last git fetch or git pull invocation. It is very useful for one-off fetch, with the repository to fetch from given by a URL, unlike when fetching from a named repository such as origin, where we can use the remote-tracking branch instead, for example, origin/master. Moreover, with named repositories, we can use the reflog for remote-tracking branch, for example, origin/master@{1}, to get the position before the fetch. Note that FETCH_HEAD is overwritten by each fetch from any repository.
  • ORIG_HEAD: This records the previous position of the current branch; this reference is created by commands that move the current branch in a drastic way (creating a new commit doesn't set ORIG_HEAD) to record the position of the HEAD command before the operation. It is very useful if you want to undo or abort such operations; though nowadays the same can be done using reflogs, which store additional information that can be examined in their use.

You can also stumble upon the short-lived temporary references used during specific operations:

  • During a merge, before creating a merge commit, the MERGE_HEAD records the commit(s) that you are merging into your branch. It vanishes after creating a merge commit.
  • During a cherry-pick, before creating a commit that copies picked changes into another branch, the CHERRY_PICK_HEAD records the commit that you have selected for cherry-picking.

Branch and tag references

The most straightforward and commonly used way to specify a revision is to use symbolic names: branches, naming the line of development, pointing to the tip of said line, and tags, naming specific revision. This way of specifying revisions can be used to view the history of a line of development, examine the most current revision (current work) on a given branch, or compare the branch or tag with the current work.

You can use any refs (external references to the DAG of revisions) to select a commit. You can use a branch name, tag name, and remote-tracking branch in any Git command that requires revision as a parameter.

Usually, it is enough to give a short name to a branch or tag, for example, git log master, to view the history of a master branch, or git log v1.3-rc3 to see how version v1.3-rc1 came about. It can, however, happen that there are different types of refs with the same name, for example, both branch and tag named dev (though it is recommended to avoid such situations). Or, you could have created (usually by accident) the local branch, origin/master, when there was a remote-tracking branch with the short name, origin/master, tracking where the master branch was in the remote repository, origin.

In such a situation, when ref name is ambiguous, it is disambiguated by taking the first match in the following rules (this is a shortened and simplified version; for the full list, see the gitrevisions(7) manpage):

  1. The top level symbolic name, for example, HEAD.
  2. Otherwise, the name of the tag (refs/tags/ namespace).
  3. Otherwise, the name of the local branch (refs/heads/ namespace).
  4. Otherwise, the name of the remote-tracking branch (refs/remotes/ namespace).
  5. Otherwise, the name of the remote if there exists a default branch for it; the revision is said default branch (example refs/remotes/origin/HEAD for origin as a parameter).

SHA-1 and the shortened SHA-1 identifier

In Git, each revision is given a unique identifier (object name), which is a SHA-1 hash function, based on the contents of the revision. You can select a commit by using its SHA-1 identifier as a 40-character long hexadecimal number (160 bits). Git shows full SHA-1 identifiers in many places, for example, you can find them in the full git log output:

$ git log
commit 50f84e34a1b0bb893327043cb0c491e02ced9ff5
Author: Junio C Hamano <[email protected]>
Date:   Mon Jun 9 11:39:43 2014 -0700

    Update draft release notes to 2.1

    Signed-off-by: Junio C Hamano <[email protected]>

commit 07768e03b5a5efc9d768d6afc6246d2ec345cace
Merge: 251cb96 eb07774
Author: Junio C Hamano <[email protected]>
Date:   Mon Jun 9 11:30:12 2014 -0700

    Merge branch 'jc/shortlog-ref-exclude'

It is not necessary to give a full 40 characters of the SHA-1 identifier. Git is smart enough to figure out what you meant if you provide it with the first few characters of the SHA-1 revision identifier, as long as the partial SHA-1 is at least four characters long. To be able to use a shortened SHA-1 to select revision, it must be long enough to be unambiguous, that is, there is one and only one commit object which SHA-1 identifier begins with given characters.

For example, both dae86e1950b1277e545cee180551750029cfe735 and dae86e name the same commit object, assuming, of course, that there is no other object in your repository whose object name starts with dae86e.

In many places, Git shows unambiguous shortened SHA-1 identifiers in its command output. For example, in the preceding example of the git log output, we can see the shortened SHA-1 identifiers in the Merge: line.

You can also request that Git use the shortened SHA-1 in place of the full SHA-1 revision identifiers with the --abbrev-commit option. By default, Git will use at least seven characters for the shortened SHA-1; you can change it with the optional parameter, for example, --abbrev-commit=12.

Note that Git would use as many characters as required for the shortened SHA-1 to be unique at the time the command was issued. The parameter --abbrev-commit (and the similar --abbrev option) is the minimal length.

Tip

HA short note about the shortened SHA-1:

Generally, 8 to 10 characters are more than enough to be unique within a project. One of the largest Git projects, the Linux kernel, is beginning to need 12 characters out of the possible 40 to stay unique. While a hash collision, which means having two revisions (two objects) that have the same full SHA-1 identifier, is extremely unlikely (with 1/2^80 ≈ 1/1.2×10^24 probability), it is possible that formerly unique shortened SHA-1 identifier will stop to be unique due to the repository growth.

The SHA-1 and the shortened SHA-1 are most often copied from the command output and pasted as a revision parameter in another command. They can also be used to communicate between developers in case of doubt or ambiguity, as SHA-1 identifiers are the same in any clone of the repository. Fig 2 uses a five-character shortened SHA-1 to identify revisions in the DAG.

Ancestry references

The other main way to specify a revision is via its ancestry. One can specify a commit by starting from some child of it (for example from the current commit i.e. HEAD, a branch head, or a tag), and then follow through parent relationships to the commit in question. There is a special suffix syntax to specify such ancestry paths.

If you place ^ at the end of a revision name, Git resolves it to mean a (first) parent of that revision. For example, HEAD^ means the parent of the HEAD, that is, the previous commit.

This is actually a shortcut syntax. For merge commits, which have more than one parent, you might want to follow any of the parents. To select a parent, put its number after the ^ character; using the ^<n> suffix means the nth parent of a revision. We can see that ^ is actually a short version of ^1.

As a special case, ^0 means the commit itself; it is important only when a command behaves differently when using the branch name as a parameter and when using other revision specifier. It can be also used to get the commit an annotated (or a signed) tag points to; compare git show v0.9 and git show v0.9^0.

This suffix syntax is composable. You can use HEAD^^ to mean grandparent of HEAD, and parent of HEAD^.

There is another shortcut syntax for specifying a chain of first parents. Instead of writing n times the ^ suffix, that is, ^^…^ or ^1^1…^1, you can simply use ~<n>. As a special case, ~ is equivalent to ~1, so, for example, HEAD~ and HEAD^ are equivalent. And, HEAD~2 means the first parent of the first parent, or the grandparent, and is equivalent to HEAD^^.

You can also combine it all together, for example, you can get the second parent of the great grandparent of HEAD (assuming it was a merge commit) by using HEAD~3^2 and so on. You can use git name-rev or git describe --contains to find out how a revision is related to local refs, for example, via:

$ git log | git name-rev --stdin

Reverse ancestry references: the git describe output

The ancestry reference describes how a historic version relates to the current branches and tags. It depends on the position of the starting revision. For example, HEAD^ would usually mean completely different commit next month.

Sometimes, we want to describe how the current version relates to prior named version. For example, we might want to have a human-readable name of the current version to store in the generated binary application. And, we want this name to refer to the same revision for everybody. This is the task of git describe.

The git describe finds the most recent tag that is reachable from a given revision (by default, HEAD) and uses it to describe that version. If the found tag points to the given commit, then (by default) only the tag is shown. Otherwise, git describe suffixes the tag name with the number of additional commits on top of the tagged object, and the abbreviated SHA-1 identifier of the given revision. For example, v1.0.4-14-g2414721 means that the current commit was based on named (tagged) version v1.0.4, which was 14 commits ago, and that it has 2414721 as a shortened SHA-1.

Git understands this output format as a revision specifier.

Reflog shortnames

To help you recover from some of types of mistakes, and to be able to undo changes (to go back to the state before the change), Git keeps a reflog—a temporary log of where your HEAD and branch references have been for the last few months, and how they got there. The default is to keep reflog entries up to 90 days, 30 days for revisions which are reachable only through reflog (for example, amended commits). This can be, of course, configured, even on a ref-by-ref basis.

You can examine and manipulate your reflog with the git reflog command and its subcommands. You can also display reflog like a history with git log -g (or git log --walk-reflog):

$ git reflog
ba5807e HEAD@{0}: pull: Merge made by the 'recursive' strategy.
3b16f17 HEAD@{1}: reset: moving to HEAD@{2}
2b953b4 HEAD@{2}: reset: moving to HEAD^
69e0d3d HEAD@{3}: reset: moving to HEAD^^
3b16f17 HEAD@{4}: commit: random.c was too long to type

Every time your HEAD and your branch head are updated for any reason, Git stores that information for you in this local temporary log of ref history. The data from reflog can be used to specify references (and therefore, to specify revisions):

  • To specify the nth prior value of HEAD in your local repository, you can use the HEAD@{n} notation that you see in the git reflog output. It's same with the nth prior value of the given branch, for example, master@{n}. The special syntax, @{n}, means the nth prior value of the current branch, which can be different from HEAD@{n}.
  • You can also use this syntax to see where a branch was some specific amount of time ago. For instance, to denote where your master branch was yesterday in your local repository, you can use master@{yesterday}.
  • You can use the @{-n} syntax to refer to the nth branch checked out (used) before the current one. In some places, you can use - in place of @{-1}, for example, git checkout - will switch to the previous branch.

Upstream of remote-tracking branches

The local repository which you use to work on a project does not usually live in the isolation. It interacts with other repositories, usually at least with the origin repository it was cloned from. For these remote repositories with which you interact often, Git will track where their branches were at the time of last contact.

To follow the movement of branches in the remote repository, Git uses remote-tracking branches. You cannot create new commits on remote-tracking branches as they would be overwritten on the next contact with remote. If you want to create your own work based on some branch in remote repository, you need to create a local branch based on the respective remote-tracking branch.

For example, when working on a line of development that is to be ultimately published to the next branch in the origin repository, which is tracked by the remote-tracking branch, origin/next, one would create a local next branch. We say that origin/next is upstream of the next branch and we can refer to it as next@{upstream}.

The suffix, @{upstream} (short form <refname>@{u}), which can be applied only to a local branch name, selects the branch that the ref is set to build on top of. A missing ref defaults to the current branch, that is, @{u} is upstream for the current branch.

You can find more about remote repositories, the concept of the upstream, and remote tracking branches in Chapter 5, Collaborative Development with Git and Chapter 6, Advanced Branching Techniques.

Selecting revision by the commit message

You can specify the revision a by matching its commit message with a regular expression. The :/<pattern> notation (for example, :/^Bugfix) specifies the youngest matching commit, which is reachable from any ref, while <rev>^{/<pattern>} (for example, next^{/fix bug}) specifies the youngest matching commit which is reachable from <rev>:

$ git log 'origin/pu^{/^Merge branch .rs/ref-transactions}'

This revision specifier gives similar results to the --grep=<pattern> option to git log, but is composable. On the other hand, it returns the first (youngest) matching revision, while the --grep option returns all matching revisions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset