mergeq
Have you ever broken the build? Have you ever had to wait for a teammate to fix the build so that you can deploy your change that won't break the build?
mergeq can help by only allowing branches that pass CI to get merged into
master
or develop
or whatever. If your build doesn't pass, the branch you
are trying to merge into doesn't change and no one else is affected.
mergeq is an implementation of the "pre-tested commit" pattern that is:
- Robust--two people can merge at the same time and, as long as their branches do not conflict with one another, they both get a chance to get merged in safely.
- Continuous Integration server independent--as long as your server has permission to push to your repo and can support running only one build at a time, it should work.
- Flexible--there are hooks to support safety checks or post build notifications.
- Easy to setup--no additional intermediary repository, no additional infratructure
- Battle tested--we have been using it for over 3 years with great success.
If you want strict pre-reviewed, as well as pre-tested commits, and you don't mind additional infrastructure, you can check out Gerrit.
How it works
Say you have a branch, feature
that you want to merge into master
. Instead
of merging directly to master
, you run mergeq master
. mergeq
will fetch
the latest master
from origin
and merge your branch into it. You can
resolve any merge conflicts and then mergeq
will push the merged branch to a
special branch called merge/master
. Your CI server will pick up changes from
that branch, run the build, and if it passes, push the merge to master
. If it
fails, it will do nothing else and your failing build won't get in anyone's
way. Merge away!
Remote Installation (quick)
$ cd your_project
$ bash <(curl -s https://raw.githubusercontent.com/aaronjensen/mergeq/master/bin/mergeq_remote_install)
You can always re-run this script to upgrade your project's copy of mergeq.
If you don't trust curl
, which is totally understandable, just do this:
$ curl -o mergeq_remote_install https://raw.githubusercontent.com/aaronjensen/mergeq/master/bin/mergeq_remote_install
$ chmod +x mergeq_remote_install
# open the install script and audit it for security
$ ./mergeq_remote_install
Running mergeq_remote_install
will add a few files, so be sure to commit them to your repo:
$ git status
$ git add .
$ git commit -m "Add mergeq to the project"
Next, create branches that you want to make queueable. This example creates a branch
called staging
for queuing builds.
$ git push origin master:staging
$ git push origin master:merge/staging
Configuring CI
There are a few things to take into account when using mergeq on your CI server.
-
Only one build per branch can run at once. With TeamCity, you can "Limit the number of simultaneously running builds" to 1.
-
You need to run a build for every push rather than just the most recent. With TeamCity, we do this by disabling VCS build triggering and starting the build automatically via a webhook like this:
curl --insecure "https://user:pass@teamcity.server.com/httpAuth/action.html?add2Queue=$build_id&name=GIT_REF&value=$git_ref"
You'll need to add two steps to your CI for mergeq.
-
First step will merge before testing.
%GIT_REF%
is the sha of the queue commit to merge. This will be the tip of themerge/staging
branch and look like:cf629af - Queuing merge: feature/mergeq-check-acceptance into integration (8 weeks ago) <Somebody>
%BRANCH%
is the name of the target branch. If the queue branch ismerge/staging
, the target branch isstaging
.Your CI script should look something like:
$ git reset --hard %GIT_REF% $ bin/mergeq_ci merge %BRANCH%
-
Then your CI should run build/tests.
-
If successful, it should:
$ bin/mergeq_ci push %BRANCH%
Hooking mergeq
You'll probably want to hook parts of mergeq as part of your build process. Examples:
- pausing zeus for the duration of mergeq to avoid churn
- cleaning up branch metadata after a CI build
All the hooks you write live in $project_dir/.mergeq/hooks
, and therefore are on
a per-repo basis.
mergeq
hooks:
-
before_merge
- this runs before a merge happens -
after_push
- this runs after your branch is pushed tomerge/$target
-
after_cleanup
- this is the last thing that runs beforemergeq
exits
mergeq_ci
hooks:
-
before_ci_startup
- this runs at the very beginning of themergeq_ci
script -
after_ci_merge
- this runs after CI merges into the target branch -
before_ci_push
- this runs before CI pushes the target branch back to origin -
after_ci_push
- this runs after CI pushes the target branch back to origin
All hooks live in $project_dir/.mergeq/hooks/$hook_name
, and need to have chmod +x
to be executable.
mergeq
hook
Example Arguments:
- $1 - the target branch (
integration
,master
, etc) - $2 - the name of the merge branch (
merge/integration
,merge/master
, etc)
Rails projects often run something like Zeus to handle fast code loading. Since mergeq
does a bunch of git checkouts, we want to pause Zeus for the duration of a mergeq
so
it doesn't freak out.
# .mergeq/hooks/before_merge
#!/bin/bash
target_branch=$1
merge_branch=$2
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
function zeus_pid {
cat $DIR/../../tmp/zeus.pid 2> /dev/null
}
function stop_zeus {
pid=$(zeus_pid)
if [[ "$pid" ]]; then
kill -USR1 $pid
fi
}
stop_zeus
# .mergeq/hooks/after_cleanup
#!/bin/bash
target_branch=$1
merge_branch=$2
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
function zeus_pid {
cat $DIR/../../tmp/zeus.pid 2> /dev/null
}
function start_zeus {
pid=$(zeus_pid)
if [[ "$pid" ]]; then
kill -USR2 $pid
fi
}
mergeq_ci
hook
Example Arguments:
- $1 - the target branch (
integration
,master
, etc)
Say we want to delete our feature branch from GitHub after a successful merge+push to
origin/master
. We can hook after_ci_push
to achieve this.
# .mergeq/hooks/after_ci_push
#!/bin/bash
target_branch=$1
function delete_feature_branch {
if [ "$target_branch" = "master" ]
then
git ls-remote --heads origin | grep `git rev-parse HEAD^2` | cut -f2 -s | xargs -I {} git push origin :{}; true
fi
}
delete_feature_branch
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request