Git 2.5, including multiple worktrees and triangular workflows

The open source Git project has just released Git 2.5. Here’s our take on its most useful new features. git worktree: one Git repository with multiple working trees It’s not…

|
| 6 minutes

The open source Git project has just released Git 2.5. Here’s our take on its most useful new features.

git worktree: one Git repository with multiple working trees

It’s not very difficult to switch a single Git repository between multiple branches, or to create a second local clone of a repository. This lets you work on two branches simultaneously, or start long-running tests in one clone while continuing development in the other. However, maintaining multiple clones of a repository means extra work to keep the clones in sync with each other and with any remote repositories.

The new Git subcommand git worktree creates additional working trees connected to an existing Git repository [1]. Each linked working tree is a pseudo-repository with its own checked-out working copy. Its .git is actually a file that refers to the history and references from the main repository.

Please note that the worktree feature is experimental. It may have some bugs, and its interface may change based on user feedback. It’s not recommended to use git worktree with a repository that contains submodules.

Suppose you’re working in a Git repository on a branch called feature, when a user reports a high-urgency bug in master. First you create a linked working tree with a new branch, hotfix, checked out relative to master, and switch to that directory:

$ git worktree add -b hotfix ../hotfix origin/master
Enter ../hotfix (identifier hotfix)
Branch hotfix set up to track remote branch master from origin.
Switched to a new branch 'hotfix'
$ cd ../hotfix

Now you’ve got a new working tree, with branch hotfix checked out and ready to go. You can fix the bug, push hotfix, and create a pull request. After you’ve committed the changes to the hotfix branch, you can delete the hotfix directory whenever you like, because the commits are stored in your main repository:

$ cd ../main
$ rm -rf ../hotfix

The main repository and any associated linked working trees all know about each other, which keeps them from getting in each other’s way. For example, it’s not allowed to have the same branch checked out in two linked working trees at the same time, because that would allow changes committed in one working tree to bring the other one out of sync. So suppose that you want to run long-running tests on the current commit. Create a detached HEAD state, using the --detach option, to check out the current commit independent of the branch:

$ git worktree add --detach ../tests HEAD
Enter ../tests (identifier tests)
HEAD is now at 977212e... add file
$ cd ../tests
$ make super-long-test
[...]

While the test is running, you can continue working in your main repository.

[1] You may have heard of an older script called contrib/workdir/git-new-workdir that does something similar. That script never made it to mainstream because it was non-portable and somewhat fiddly.

Improved support for triangular workflows

When contributing to open source projects, it’s common to use what’s called a “triangular workflow”:

  • You fetch from a canonical “upstream” repository to keep your local repository up-to-date.
  • When you want to share your own modifications with other people, you push them to your own fork and open a pull request.
  • If your changes are accepted, the project maintainer merges them into the upstream repository.

Triangular workflow

Git has many features that support triangular workflows, but it’s sometimes hard to see how to use them together in real life. Let’s take a closer look at triangular workflows, including the new command line shorthand <reference>@{push} that was added in Git 2.5.

Preparation

Suppose you want to contribute to the Atom editor. Although you likely don’t have push permission to the main Atom repository, you can fork that repo and push your changes to your own fork. Here’s how you set that up:

  1. Go to the main Atom repository and click “Fork”. This creates a new fork under your account with the URL https://github.com/YOUR-USERNAME/atom.
  2. Create a local clone of your fork on your computer:
    $ git clone https://github.com/YOUR-USERNAME/atom
    $ cd atom
    $ git config remote.pushdefault origin
    $ git config push.default current

    After this step, the remote called origin refers to your fork of Atom. It also sets the default remote for pushes to be origin and the default push behavior to current. Together this means that if you just type git push, the current branch is pushed to the origin remote.

  3. Create a second remote called upstream that points at the main atom repository and fetch from it:
    $ git remote add upstream https://github.com/atom/atom
    $ git fetch upstream

Hacking

You only have to follow the above steps once. From then on, whenever you want to work on a new feature, you can more easily interact with the remote repositories:

  • Make sure that your local repository is up-to-date with the upstream repository:
    $ git fetch upstream
  • Create a branch whizbang off of upstream master to work on a new feature, and check out the branch:
    $ git checkout -b whizbang upstream/master

    This automatically sets up your local whizbang branch to track the upstream master branch. This means that if more commits are added to master upstream, you can merge those commits into your whizbang branch by typing

    $ git pull

    or rebase your branch on top of the new master by typing

    $ git pull --rebase

    If you’re ever unsure of the branch that would be pulled from, you can type

    $ git rev-parse --abbrev-ref '@{u}'
  • Hack, commit, hack, commit.
  • Push your whizbang branch to your fork:
    $ git push

    Because of the above configuration, and because whizbang is the current branch, the above command doesn’t need any arguments.

  • Continue to hack, commit, hack, commit.
  • See what commits you’ve added to your current branch since the last push:
    $ git log @{push}..

    This uses the new @{push} notation, which denotes the current value of the remote-tracking branch that the current branch would be pushed to by git push, namely origin/whizbang. You can also refer to the push destination of an arbitrary branch using the notation whizbang@{push}.

  • Push the new commits to the whizbang branch on your GitHub fork:
    $ git push

    If you’re unsure of which branch would be pushed to, you can type

    $ git push --dry-run

    or

    $ git rev-parse --abbrev-ref '@{push}'

Performance improvements

Git 2.5 includes performance improvements aimed at large working trees and working trees stored on networked filesystems (e.g., NFS):

  • You can run git update-index --untracked-cache to enable an experimental feature that tells Git to examine only the modification times of directories when looking for new files. This can speed up git status on many filesystems.
  • git index-pack (run, for example, by git gc) now makes far fewer scans of the packed-refs directory. This can make a huge difference if the repository is on a networked filesystem.
  • Git now calls utime far less often when it’s about to reuse existing packed objects. This also mostly benefits repositories on networked filesystems.

Also, clean/smudge filters are no longer required to read all of their input. This can help speed up filters that don’t need to read the whole file contents to do their work (e.g., Git Large File Storage).

Go forth and collaborate

Of course there are many, many other changes in this release; for the full list, check out the 2.5.0 release notes.

We hope that you find something in Git 2.5 that makes your day just a little bit nicer. And the best part is that, through the magic of open source, Git just keeps getting better and better! If you’d like to get more involved in the Git open source community, come check out the community page.

Related posts

Game Off 2024 theme announcement

GitHub’s annual month-long game jam, where creativity knows no limits! Throughout November, dive into your favorite game engines, libraries, and programming languages to bring your wildest game ideas to life. Whether you’re a seasoned dev or just getting started, it’s all about having fun and making something awesome!