Branches are symbolic names for lines of development. In Git, each branch is realized as a named pointer (reference) to some commit in the DAG of revisions, so it is called branch head.
The representation of branches in Git
Git uses currently two different on-disk representations of branches: the loose format and the packed format.
Take, for example, the master
branch (which is the default name of a branch in Git; you start on this branch in the newly-created repository). In loose format (which takes precedence), it is represented as the one-line file, .git/refs/heads/master
with textual hexadecimal representation of SHA-1 tip of the branch. In the packed format, it is represented as a line in the .git/packed-refs
file, connecting SHA-1 identifier of top commit with the fully qualified branch name.
The (named) line of development is the set of all revisions that are reachable from the branch head. It is not necessarily a straight line of revisions, it can fork and join.
You can create a new branch with the git branch
command; for example, to create a new branch testing
starting from the current branch (see the top right part of Fig 7), run:
$ git branch testing
What happens here? Well, this command creates a new pointer (a new reference) for you to move around. You can give an optional parameter to this command if you want to create the new branch pointing to some other commit.
Note, however, that the git branch
command would not change the position of the HEAD
(the symbolic reference pointing to current branch), and would not change the contents of the working directory.
If you want to create a new branch and switch to it (to start working on new branch immediately), you can use the following shortcut:
$ git checkout -b testing
If we create a new branch at the current state of repository, the checkout -b
command differs only in that it also moves the HEAD
pointer; see transition from left-hand side to the bottom-right in Fig 7.
Sometimes you might want to create a new unconnected orphan branch in your repository. Perhaps you want to store the generated documentation for each release to make it easy for users to get readable documentation (for example, as man pages or HTML help) without requiring to install conversion tools or renderers (for example, AsciiDoc parser). Or, you might want to store web pages for a project in the same repository as project; that is what GitHub project pages use. Perhaps you want to open source your code, but you need to clean up the code first (for example, because of copyrights and licensing).
One solution is to create a separate repository for the contents of an orphan branch, and fetch from it into some remote-tracking branch. You can then create a local branch based on it.
You can also do this with:
$ git checkout --orphan gh-pages Switched to a new branch 'gh-pages'
This reproduces somewhat the state just after git init
: the HEAD
symref points to the gh-pages
branch, which does not exist yet; it will be created on the first commit.
If you want to start with clean state, like with GitHub Pages, you would also need to remove the contents of the start point of branch (which defaults to HEAD
, that is, to current branch and to the current state of the working directory), for example with:
$ git rm -rf .
In the case of open sourcing code with proprietary parts to be excluded (orphan branch is not to bring this proprietary code accidentally to the open source version on merging), you would want to carefully edit the working directory instead.
To switch to an existing local branch, you need to run the git checkout
command. For example, after creating the testing
branch, you can switch to it with the following command:
$ git checkout testing
It is shown in Fig 7 as the vertical transition from the top-right to bottom-right state.
When switching to a branch, Git also checks out its contents into the working directory. What happens then if you have uncommitted changes (which are not considered by Git to be on any branch)?
If the difference between the current branch and the branch you want to switch to does not touch the changed files, the uncommitted changes are moved to the new branch. This is very useful if you started working on something, and only later realized that it would be better to do this work in a separate feature branch.
If uncommitted changes conflict with changes on the given branch, Git will refuse to switch to the said branch, to prevent you from losing your work:
$ git checkout other-branch error: Your local changes to the following files would be overwritten by checkout: file-with-local-changes Please, commit your changes or stash them before you can switch branches.
In such situation you have a few possible different solutions:
git branch --merge
(which would do the three-way merge between the current branch, the contents of your working directory with unsaved changes, and the new branch), or by stashing away your changes before checkout and then unstashing them after a switch.git checkout --force
.What happens if you try to check out something that is not a local branch: for example an arbitrary revision (like HEAD^
), or a tag (like v0.9
), or a remote-tracking branch (for example, origin/master
)? Git assumes that you need to be able create commits on top of the current state of the working directory.
Older Git refused to switch to non-branch. Nowadays, Git will create an anonymous branch by detaching HEAD pointer and making it refer directly to a commit, rather than being a symbolic reference to a branch, see Fig 8 for an example. To create an anonymous branch at the current position explicitly, you can use the --detach
option to the checkout command. The detached HEAD state is shown in branch listing as (no branch) in older versions of Git, or (detached from HEAD) or (HEAD detached at ...) in newer.
If you did detach HEAD by mistake, you can always go back to the previous branch with (here "-" means the name of previous branch):
$ git checkout -
As Git informs you when creating a detached branch, you can always give a name to the anonymous branch with git checkout -b <new-name>
.
There is a special case of checking out something that is not a branch. If you check out remote-tracking branch (for example, origin/next
) by its short name (in this case, next
), as if it was a local branch, Git would assume that you meant to create new contents on top of the remote-tracking branch state, and will do what it thinks you need. Do What I Mean (DWIM) will create a new local branch, tracking the remote-tracking branch.
This means that:
$ git checkout next
Is equivalent to:
$ git checkout -b next --track origin/next
Git will do it only if there are no ambiguities: the local branch must not exist (otherwise the command would simply switch to local branch given), and there can be only one remote-tracking branch that matches. This can be checked by running git show-ref next
(using the short name) and verifying that it returns only one line, with remote-tracking branch info (the last can be recognized by the refs/remotes/
prefix in ref name).
If you use the git branch
commands without any other arguments, it would list all the branches, marking the current branch with asterisk, that is, *
.
This command is intended for the end user; its output may change in the future version of Git. To find out programmatically, in a shell script:
git symbolic-ref HEAD
.git rev-parse HEAD
.git show-ref
or git for-each-ref
.They are all plumbing, that is, commands intended for use in scripts.
You can request more information with -v
( --verbose
) or -vv
. You can also limit branches shown to only those matching given shell wildcard with git branch --list <pattern>
(quoting pattern to prevent its expansion by shell, if necessary).
Querying information about remotes, which includes the list of remote branches, by using git remote show
, is described in Chapter 6, Advanced Branching Techniques.
What to do if you want to abandon the last commit, and rewind (reset) the current branch to its previous position? For this, you need to use the reset
command. It would change where the current branch points to. Note that unlike the checkout
command, the reset
command does not change the working directory by default; you need to use instead git reset --keep
(to try to keep the uncommitted changes) or git reset --hard
(to drop them).
The reset command, and its effects on the working area, will be explained in more detail in Chapter 4, Managing Your Worktree.
Fig 9 shows the differences between the checkout
and reset
commands, when given the branch and non-branch argument. In short, reset
always changes where the current branch points to (moves the ref), while checkout
either switches branch, or detaches HEAD
at a given revision if it is given non-branch:
In the preceding figure, for example, the left-top graph of the revision shows the result of running of the Git checkout
maint
command, starting from the state given by the graphs in the centre.
As in Git, a branch is just a pointer, and an external reference to the node in the DAG of revisions, deleting a branch is just deleting a pointer:
You can do this with git branch -d
. There is, however, one issue to consider—what happens if you delete a branch, and there is no other reference to the part of project history it pointed to? Those revisions will become unreachable and Git would delete them after the HEAD
reflog expires (which, with default configuration, is after 30 days).
That is why Git would allow you to delete only the completely merged-in branch, whose all commits are reachable from HEAD
as in Fig 10 (or is reachable from its upstream branch, if it exists).
To delete a branch that was not merged in, risking parts of the DAG becoming unreachable, you need a stronger command, namely, git branch -D
(Git will suggest this operation when refusing to delete a branch); see Fig 11:
You can check if the branch was merged in into any other branch, by checking whether git branch --contains <branch>
shows anything. You cannot delete the current branch.
Sometimes the name chosen for a branch needs to be changed. This can happen, for example, if the scope of the feature branch changed during the development.
You can rename a branch with git branch -m
(use -M
if target name exists and you want to overwrite it); it will rename a branch and move the corresponding reflog (and add rename operation to the reflog), and change the branch in all of its configuration (its description, its upstream, and so on).