Working with branches

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.

Tip

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.

Working with branches

Fig 7. Creating a new testing branch and switching to it, or creating a new branch and switching to it at once (with one command)

Creating a new branch

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.

Creating orphan branches

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.

Selecting and switching to a branch

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.

Obstacles to switching to a branch

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)?

Note

It is a good practice to switch branch in a clean state, stashing away changes or creating a commit, if necessary. Checking out a branch with uncommitted changes is useful only in a few rare cases, some of which are described in the following section.

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:

  • You can stash away your changes, and restore them when you come back to the branch you were on (this is usually the preferred solution). Or you can simply create a temporary commit of the work in progress with those changes, and then either amend the commit or rewind the branch when you get back to it.
  • You can try to move your changes to the new branch by merging, either with 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.
  • You can also throw away your changes with git checkout --force.

Anonymous branches

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.

Anonymous branches

Fig 8. The result of checking out non-branch, the state after Git checkout HEAD command, detached HEAD, or anonymous branch

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

Git checkout DWIM-mery

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

Listing branches

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, *.

Note

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:

  • To get the name of the current branch, use git symbolic-ref HEAD.
  • To find SHA-1 of the current commit, use git rev-parse HEAD.
  • To list all the branches, use 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.

Rewinding or resetting a branch

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:

Rewinding or resetting a branch

Fig 9. A table comparing the checkout and reset commands with either branch (for example, maint) and non-branch revision (for example, HEAD^) as arguments.

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.

Deleting a branch

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:

Note

Actually deleting a branch also removes, irretrievably, (at least, in the current Git version) the reflog for the branch being deleted, that is, the log of its local history.

Deleting a branch

Fig 10. Deleting just merged in base-doc branch with git branch -d base-doc, when we are on a branch (master here) that includes it

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:

Deleting a branch

Fig 11. Deleting the unmerged osx-port branch with git branch -D osx-port

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.

Changing the branch name

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

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

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