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:

npmERR!Test failed.Execution failed: npm testYou 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:

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:

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:

a pre-commit hook can verify that your tests are passing before allowing a commit to be created

However, up until now, hooks have had a big administrative drawback: you have to copy or symlink them into the .git/hooks directory for every repository you want to apply them to. Git 2.9 introduces a new core.hooksPath config property to override the location of your hooks directory. So:

git config core.hooksPath /etc/git/hooks

will configure your current repository to check for hooks in /etc/git/hooks, or:

git config −−global core.hooksPath /etc/git/hooks

will let you centrally manage hooks for all of your local repositories.

git commit learnt to be permanently verbose

Do you ever invoke git commit and then stare blankly at vim trying to remember all the changes you just made? The verbose flag is for you!

Instead of:

Ah crap, which dependency did I just rev?
# Please enter the commit message for your changes. Lines starting
# with ‘#’ will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with ‘origin/master’.
#
# Changes to be committed:
# new file: package.json
#

you can invoke git commit −−verbose to view an inline diff of your changes. Don’t worry, it won’t be included in your commit message:

The −−verbose flag isn’t new, but as of Git 2.9 you can enable it permanently with git config −−global commit.verbose

git merge will no longer merge unrelated histories

It’s possible to have multiple branches with completely separate root commits — and therefore separate histories — within the same repository. There are a few good reasons for this, for example: in Bitbucket you can store your project’s documentation on its own branch and serve it as a static website using Aerobatic; or you may have rewritten your master branch to use Git LFS in order to reduce the size of your repository. Regardless, if you or one of your colleagues accidentally ran git merge on two unrelated branches in your repository, it would have some bad consequences for your team. In the former case, your documentation would be quietly merged into your code. In the latter, you’d probably just end up with a horrific set of merge conflicts.

There’s plenty more!

These are just six items from a list of over a hundred features, fixes, and performance improvements shipped by the Git wizards in version 2.9. Check out the release notes for the full scoop, and then download the latest version or upgrade using your favorite package manager.

Thanks for reading! If you want to chat Git, Bitbucket, or JIRA arcanery please hit me up on Twitter: I’m @kannonboy.