Skip to content

Git 2.9 has been released

The open source Git project has just released Git 2.9.0, with a variety of features and bug fixes. Here's our look at some of the most interesting new features: Faster…

Git 2.9 has been released
Author

The open source Git project has just released Git 2.9.0, with a variety of features and bug fixes. Here’s our look at some of the most interesting new features:

Faster and more flexible submodules

In the last release, we showed you how to use the --jobs=<N> option to fetch submodules in parallel, which can be a real time saver.

$ git fetch --recurse-submodules --jobs=4

Now you can also use the --jobs option when cloning or updating submodules:

$ git clone --recurse-submodules --jobs=4 ...
$ git submodule update --jobs=4 ...

If you always want submodules to be processed in parallel, you can set the submodule.fetchJobs config option. [source]

There are also some new conveniences for specifying how you want your submodule clones and fetches to behave. Git will now pass command-line configuration options down to submodules commands. So if you need to set a one-off variable for all of your submodule fetches, you can do so with git -c http.proxy=... clone --recursive. In the last release we mentioned how Git LFS can use -c config to speed up the initial checkout. Now it can apply the same trick to cloning submodules. Similarly, you can pass --shallow-submodules to git clone to have it make shallow clones of all of your submodules. [source, source, source]

Beautiful diffs

If you’ve used Git, you’ve probably looked at a lot of diffs. Most of the time the diff shows exactly what you did: changed some lines, added some new ones, or took some away. But because Git generates the diff only from seeing the “before” and “after” states of your files, sometimes the way it shows the changes can be pretty confusing. For example, look at this diff that adds a new loop to an existing function:

diff --git a/foo.rb b/foo.rb
index 64bb579..a2b5573 100644
--- a/foo.rb
+++ b/foo.rb
@@ -3,6 +3,10 @@ module Foo
   def finalize(values)

     values.each do |v|
+      v.prepare
+    end
+
+    values.each do |v|
       v.finalize
     end
 end

The author probably didn’t write the loop as two out-of-order halves. But because the outer lines of the old and new loops are the same, it’s equally valid to attribute the lines either way (it’s tempting to say that the problem can be solved by always starting the added lines at the top, but that only fixes this case; you’d have the opposite problem if the new loop were added after the old).

In 2.9, Git’s diff engine learned a new heuristic: it tries to keep hunk boundaries at blank lines, shifting the hunk “up” whenever the bottom of the hunk matches the bottom of the preceding context, until we hit a blank line. The result shows what the author actually wrote in this case:

diff --git a/foo.rb b/foo.rb
index 64bb579..a2b5573 100644
--- a/foo.rb
+++ b/foo.rb
@@ -2,6 +2,10 @@ module Foo

   def finalize(values)

+    values.each do |v|
+      v.prepare
+    end
+
     values.each do |v|
       v.finalize
     end

Ah, much better. This new heuristic is still experimental, and may change in the future, or even become the default. For now, you can enable it with the --compaction-heuristic option on the command line, or by setting diff.compactionHeuristic in your git config. [source, source]

But maybe the minutae of diff hunk alignment isn’t your thing (it’s OK, we all have our niche interests). Let’s talk about colors.

You probably already know that Git can colorize its diff output, and you can even customize the colors yourself. But there are also scripts that will filter Git’s output and change it even further. For instance, there’s a diff-highlight script that will put an extra emphasis on the changed part of a line:

diff --git a/foo.rb b/foo.rb
index bff273..9741ad8 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,6 +1,6 @@
 module Foo
   def output
-    puts "hello, world!"
+    puts "goodbye, world!"
   end
 end

There are a few ways to make use of this script, but the simplest is to configure Git to filter your diffs whenever it’s showing them in a pager:

$ git config pager.log 'diff-highlight | less'
$ git config pager.show 'diff-highlight | less'
$ git config pager.diff 'diff-highlight | less'

That covers almost everything, but there’s one spot missing: diffs shown by the interactive patch-staging tool (you are using interactive
staging
, right?). In Git 2.9, you can now ask it to filter the diffs it shows through any script you like:

$ git config interactive.diffFilter diff-highlight

Phew, with that set you can avoid the horror of ever seeing an unhighlighted diff again. [source]

And if you really want to go nuts, check out the diff-so-fancy project, which plugs in in the same way. These diffs are so fancy, you’ll pop your monocle.

Testing all the commits with rebase -x

If you’re a neat freak when it comes to commit history, you may have used Git’s interactive rebase. It’s great for fixing commit messages, or squashing, splitting, or reordering commits. But at it’s heart, interactive rebase is really just a recipe of instructions to follow: pick this commit, then squash that one, now edit the message on this one, and so on.

What many people don’t know is that you can insert any command you like into the instruction sheet, and Git will run it at the appropriate time, stopping the run only if the command fails. This makes it perfect for testing each commit in a series. The rebase stops at the first buggy commit, allowing you to fix it, re-run the tests, and amend the commit before continuing. If your project does commit-by-commit review, polishing your commits like this (rather than lumping on bug fixes at the end) is a great way to be kind to your reviewers. Rather than complain about your bugs in the early commits only to find them fixed later on, they get to see more directly the changes you propose, in a logical flow that builds towards the final goal.

Of course, writing out exec make test for each commit in your instruction
sheet gets pretty tedious. That’s why git rebase supports a -x option to add it for each commit, and as of this release, -x implies -i. And because rebase already defaults to your @{upstream} tracking branch, testing a full branch is as easy as:

$ git rebase -x 'make test'
Executing: make test
  Result: OK
Executing: make test
  Result: FAIL
make: *** [test] Error 2
Execution failed: make test
You can fix the problem, and then run

        git rebase --continue

Oh no, a bug! We can fix it up and continue on our way:

$ hack hack hack
$ make test
  Result: OK
$ git commit -a --amend
$ git rebase --continue
Executing: make test
  Result: OK
Successfully rebased and updated refs/heads/your-branch.

And now our perfect result is ready to be shared. [source]

Git Tidbits (Gitbits? Tidgits?)

  • The git describe command gives a human-readable (but still uniquely identifiable) name to commits. In its --contains mode, it finds a tag that contains a given commit, which makes the names it generates a good shorthand for “about when did this commit get released?”. Older versions of Git looked for the topologically “closest” tag, but this could sometimes produce counter-intuitive results when newer tags had been merged in. Git 2.9 has a new heuristic that is much easier to explain: pick the oldest tag that contains the commit, and describe the path between them in the shortest way possible. [source]
  • If you’re a fan of ASCII art (and let’s be honest; who isn’t?), you’ll be pleased to know that git log will now expand tabs in commit messages to account for its 4-space indentation. That turns messy and broken alignment like this:

    $ git log
    commit 8d7fca1d690ff2ffb678dc00fbca1954df5e5b90
    Author: Mu-An Chiou <muan@users.noreply.github.com>
    Date:   Mon Sep 23 09:21:03 2013 +0900
    
           _____
           ╲    ╲
           │    │
           │    │                      ___________________________
           └─#──┘    ########           /                           |
             ##     ##~ ~ ~ ##       /                           |
           #####    ##########     <   My circuits are frazzled!  |
           #####    ##########                             |
            ###  #######╱╱#######     ___________________________|
             |  / ####╱╱ HUBOT ##               .
          /  ##╱╱###########                    .
              ╱╱############   |                 .
               #############  ###
               #############  ####
                  ######      ####
                   ###     XX
                    ##
                    #

    into this:

    $ git log
    commit 8d7fca1d690ff2ffb678dc00fbca1954df5e5b90
    Author: Mu-An Chiou <muan@users.noreply.github.com>
    Date:   Mon Sep 23 09:21:03 2013 +0900
    
           _____
           ╲    ╲
           │    │
           │    │                      ___________________________
           └─#──┘    ########         /                           |
             ##     ##~ ~ ~ ##       /                            |
           #####    ##########     <   I have expanded your tabs! |
           #####    ##########                                   |
            ###  #######╱╱#######     ___________________________|
             |  / ####╱╱ HUBOT ##                .
              /  ##╱╱###########                .
                  ╱╱############   |              .
                   #############  ###
                   #############  ####
                      ######      ####
                       ###         XX
                        ##
                        #

    Oh, and you can use it for serious things like tables and diagrams, too. [source]

  • Rename detection is now enabled by default for diffs. You may have heard that Git doesn’t record renames. It’s true!
    Git infers on the fly when a file has been renamed by looking for similarities between the contents of the old and new files. This feature, which has existed since the early days of Git, is now enabled by default. [source]
  • You can now specify a custom path for hooks. Git calls hook scripts to allow you to implement custom policy or actions when certain events occur. These hook scripts are found inside the .git directory of each repository, making them a pain to manage if you have a standard set of hooks for all of your repositories. Now you can store the hooks in one place and point all of the repositories at them with the new core.hooksPath config. [source]
  • The git-p4 tool can now map p4 users to Git identities, as well as record p4 job information in the commit messages. Thanks to git-p4 and other tools, you don’t have to forego the joy of using Git as a client, even if the project you’re working on uses Perforce (or SVN, or
    Hg, or CVS, or…). [source, source]

The rest of the iceberg

That’s just a sampling of the changes in Git 2.9. Check out the the full release notes for the complete list.

Explore more from GitHub

Open Source

Open Source

Gaming, Git, new releases, and more.
The ReadME Project

The ReadME Project

Stories and voices from the developer community.
GitHub Copilot

GitHub Copilot

Don't fly solo. Try 30 days for free.
Work at GitHub!

Work at GitHub!

Check out our current job openings.