axiac@web

Atlassian SourceTree and Git hooks

This article explains how I made Sismo work with Atlassian SourceTree on macOS. First I thought it doesn’t work out-of-the-box because of a $PATH problem but soon it turned out that the biggest issue comes from the command line used for integration. Keep reading to learn how I found it out and how easy is to fix it.

The actors

Atlassian SourceTree is a GUI for Git and Mercurial that runs on Windows and OSX. It covers most of the basic VCS workflow; it provides a handy button that opens a command line console in the work tree directory, allowing the user to type the advanced commands it doesn’t provide. All in all, it is a good tool for the daily needs of any developer regarding the source code versioning.

Git hooks are a nice way to enhance the behaviour of Git. The pre-commit hooks can be used to verify the shape of the code before committing the changes (can run the tests, verify the formatting etc). The post-commit hook can be used to send notification emails, to trigger project builds on the continuous integration server and so on.

Sismo is a continuous testing server published by Fabien Potencier (the creator of Symfony). It is small (everything is packed in a single PHP file that can run both as CLI and as web page), it is fast and dumb-easy to configure.

The play

I’m working with Git and SourceTree for years. Sismo joined the group last week and making it work standalone was as easy as taking candy from a baby. I created a project and put composer; phpunit in the list of commands to be run by Sismo when I ask it to build the project.

The next step was to install it as a post-commit hook, in order to run it every time I create a new commit. The Sismo project page explains how to integrate it into the Git workflow by launching it1 from the post-commit git hook:

.git/hooks/post-commit
1
2
3
# !/bin/sh

php /path/to/sismo.php --quiet build symfony-local `git log -1 HEAD --pretty="%H"` &

1 At the time of this writing, the command displayed on the project’s page incorrectly escapes the backticks, making Sismo reject it because the incorrect number of arguments. I posted it here in the correct form (that runs). It’s possible that it was fixed in the meantime in the official documentation too.

The intrigue

It works well when I use Git from the command line. However, it fails with command not found errors when I commit using SourceTree. Putting echo $PATH as the first command for the project in ~/.sismo/config.php revealed that it cannot find composer because it is not in the path known by SourceTree. This happens because SourceTree uses the system environment while the environment of my bash command line (and of the programs launched by it) is enhanced with paths and aliases in ~/.bashrc.

One (partial) resolution

In order to debug the problem easier and get a solution that can be applied to all my projects easily, I moved the code from .git/hooks/post-commit into a separate script (I named it sismo-git-post-commit). This way, the Git post-commit hook contains a simple call of this external script that receives the Sismo project name as argument. All the setup needed to make Sismo work when it is invoked through SourceTree (and all future corrections and improvements) require changing a single file (and not every project).

Since ~/.bashrc is read only by bash I had to explicitly call Sismo through bash. This can be done by prependind the above Sismo command line with bash. It still doesn’t work this way because, as man bash states:

When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute.

I modified ~/bin/sismo-git-post-commit to launch Sismo using bash and provide it the correct path to .bashrc in BASH_ENV:

~/bin/sismo-git-post-commit
9
BASH_ENV=~/.bashrc bash ~/bin/sismo --quiet build $PROJECT $(git log -1 HEAD --pretty="%H") &

(If you have noticed that $PROJECT is undefined then please also notice that this is line #9 of the script. I have chosen to show only the relevant part here. Keep reading for the complete script.)

It was still working fine using the command line git and it started working when I committed the code using SourceTree. It seemed I solved the problem and it was the time to move on.

But something wasn’t working as it used to work before. Committing using SourceTree used to complete in a snap. Now it seemed sluggish. And it felt like this because it was sluggish.

Further investigation

I suspected that, even if Sismo runs in the background, for some reason SourceTree waits until it completes. It’s not Git the one that produces the delay; it worked fine when I used git commit from the command line.

The execution of sismo build takes some time, depending on the size of the project and the commands used to build it. Mine were pretty light, that’s why I added sleep 5 as the first build command for the project. The next two commits, one from the command line, the other using SourceTree revealed the truth: SourceTree indeed waits for the sismo command to complete before it closes the “Committing” console. The command line git commit still completes in a snap, without any additional delay.

I scratched my head and suddenly it dawned to me: when it launches Git in the background to process the commit, SourceTree connects to Git’s standard output through a pipe in order to get and display in the “Committing” console whatever output Git might produce. Git launches the hook, the hook launches sismo in background but none of them close their stdout (they don’t have any reason to do it) and because of inheritance, all their standard output streams remain connected with SourceTree through the pipe.

Even if sismo-git-post-commit doesn’t produce any output (the --quiet option of sismo takes care of that), the pipe remains open until all the processes attached to it on the other end complete. SourceTree remains connected to it, dutifully waiting for some git output to present to the user.

To verify this assumption I replaced --quiet with --verbose (to force sismo produce a lot of output) and I configured SourceTree to always open the console when it runs the Git commands in background and voilà! All the output produced by sismo is there, after the 5 seconds delay.

The epilogue

At this point, everything was clear and the fix was straight forward. After I removed all the debug code all I had to do was to disconnect from the pipe the stdout and stderr of the sismo instance launched in background. This can be easily accomplished by redirecting them to /dev/null.

The commit hook and the script it uses now look like this:

.git/hooks/post-commit
1
2
3
# !/bin/sh

~/bin/sismo-git-post-commit project-name &
~/bin/sismo-git-post-commit
1
2
3
4
5
6
7
8
9
# !/bin/bash

PROJECT=$1
if [ -z "$PROJECT" ]; then
echo "Missing project name."
exit 1
fi

BASH_ENV=~/.bashrc bash ~/bin/sismo --quiet build $PROJECT $(git log -1 HEAD --pretty="%H") >/dev/null 2>&1 &

Remarks

A very helpful tool for debugging the problem was the shell redirection of standard output (and error) streams of the sismo command to a file. Together with the --verbose option it allowed me to see what happened under the hood when the things doesn’t work as expected (in the first part).

Comments