Git 2.9 is out!

By on June 13, 2016

Just a couple of months after version 2.8, Git 2.9 has shipped with a huge collection of usability improvements and new command options. Here’s the new features that we’re particularly excited about on the Bitbucket team.

git rebase can now exec without going interactive

Many developers use a rebasing workflow to keep a clean commit history. At Atlassian, we usually perform an explicit merge to get our feature branches on to master, to keep a record of where each feature was developed. However, we do avoid “spurious” merge commits by rebasing when pulling changes to our current branch from the upstream server (git pull −−rebase) and occasionally to bring our feature branches up to date with master (git rebase master). If you’re into rebasing, you’re probably aware that each time you rebase, you’re essentially rewriting history by applying each of your new commits on top of the specified base. Depending on the nature of the changes from the upstream branch, you may encounter test failures or even compilation problems for certain commits in your newly created history. If these changes cause merge conflicts, the rebase process will pause and allow you to resolve them. But changes that merge cleanly may still break compilation or tests, leaving broken commits littering your history.

However, you can instruct Git to run your project’s test suite for each rewritten commit. Prior to Git 2.9 you could do this with a combination of git rebase −−interactive and the exec command. For example:

git rebase master −−interactive −−exec=”npm test”

would generate an interactive rebase plan which invokes npm test after rewriting each commit, ensuring that your tests still pass:

pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
exec npm test
pick ed93626 ACE-1294: removed pull request service from test
exec npm test
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
exec npm test
pick e68f710 ACE-1294: added testing data to batch email file
exec npm test

# Rebase f32fa9d..0ddde5f onto f32fa9d (20 command(s))

In the event that a test fails, rebase will pause to let you fix the tests (and apply your changes to that commit):

291 passing
1 failing

1) Host request “after all” hook:
Uncaught Error: connect ECONNRESET

npm ERR! Test failed.
Execution failed: npm test
You can fix the problem, and then run

        git rebase −−continue

This is handy, but needing to do an interactive rebase is a bit clunky. As of Git 2.9, you can perform a non-interactive rebase exec, with:

git rebase master -x “npm test”

Just replace npm test with make, rake, mvn clean install, or whatever you use to build and test your project.

git diff and git log now detect renamed files by default

Git doesn’t explicitly store the fact that files have been renamed. For example, if I renamed index.js to app.js and then ran a simple git diff, I’d get back what looks like a file deletion and addition:

diff −−git a/app.js b/app.js
new file mode 100644
index 0000000..144ec7f
−−− /dev/null
+++ b/app.js
@@ -0,0 +1 @@
+module.exports = require(‘./lib/index’);
diff –git a/index.js b/index.js
deleted file mode 100644
index 144ec7f..0000000
−−− a/index.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require(‘./lib/index’);

A move is technically just a move and a delete, but this isn’t the most human-friendly way to show it. Instead, you can use the -M flag to instruct Git to attempt to detect renamed files on the fly when computing a diff. For the above example, git diff -M gives us:

diff −−git a/app.js b/app.js
similarity index 100%
rename from index.js
rename to app.js

What does -M stand for? renaMes? Who cares! As of Git 2.9, the git diff and git log commands will both detect renames by default, unless you explicitly pass the −−no-renames flag.

git clone learned −−shallow-submodules

If you’re using submodules, you’re in luck – for once! 🙂 Git 2.9 introduces the −−shallow-submodules flag that allows you to grab a full clone of your repository, and then recursively shallow clone any referenced submodules to a depth of one commit. This is useful if you don’t need the full history of your project’s dependencies. For example, if you have a large monorepo with each project stored as a submodule, you may want to clone with shallow submodules initially, and then selectively deepen the few projects you want to work with. Another scenario would be configuring a CI or CD job that needs to perform a merge: Git needs the primary repository’s history in order to perform its recursive merge algorithm, and you’ll likely also need the latest commit from each of your submodules in order to actually perform the build. However you probably don’t need the full history for every submodule, so retrieving just the latest commit will save you both time and bandwidth.

Overriding .git/hooks with core.hooksPath

Git’s comprehensive hook system is a powerful way to tap into the lifecycle of various Git commands. If you haven’t played with hooks yet, take a quick look at the .git/hooks directory in any Git repository. Git automatically populates it with a set of sample hook scripts:

$ ls .git/hooks

applypatch-msg.sample pre-applypatch.sample pre-rebase.sample
commit-msg.sample pre-commit.sample prepare-commit-msg.sample
post-update.sample pre-push.sample update.sample

Git hooks are useful for all sorts of things, for example: