GitBlit Pre Hook to Prevent Pushing Certain Commit

Git in practice.

There are many different Git workflows and it is very likely that the one you are using relies heavily on Git’s outstanding branch management.

If you have many branches, some of which may be release branches, you surely don’t want anyone to merge a feature branch branched off of develop into it (which already probably is dozens of commits ahead).

This is extremely important in teams formed by many developers – if one of them makes the mistake, and the others start branching off of the release branch, all of them will have the “corrupted” version of the branch, and then it takes a lot of effort to undo all of the damage.

Git doesn’t provide a way to deal with this problem out of the box – a pre hook is required, or some third-party software.

If you are using GitBlit, however, writing a pre hook which guards a branch from being flooded with commits from a branch which should never be merged into it, is quite easy.

Below simple groovy script achieves just that:

import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.transport.ReceiveCommand
import org.eclipse.jgit.transport.ReceiveCommand.Result
import com.gitblit.utils.JGitUtils

// 1. Configuration
def targetRepositoryName = 'CheckBranchPreHookTest.git'
def targetBranch         = 'refs/heads/feature'

// SHA of a commit which should never be pushed to the targetBranch
def masterCommitSHA = 'a568c93dc9f0fa371037353030f2f0ee413b8aa2'

// the "repository" and "commands" objects are supplied by GitBlit
if (repository.name == targetRepositoryName) {
  def Repository repo = gitblit.getRepository(repository.name)

  // 2. Iterating through the commands
  for (ReceiveCommand command : commands) {
    if (command.refName != targetBranch
        || command.type.toString() != 'UPDATE')
      continue;

    logger.info("Checking if the master branch is not pushed into the release branch.")

    // 3. Getting all of the pushed commits
    def commits = JGitUtils.getRevLog(repo,
                                      command.oldId.name,
                                      command.newId.name).reverse()

    // 4. Checking if the specific commit is on the list
    if (commits.find { it.id.name.equalsIgnoreCase(masterCommitSHA) } != null) {
      command.setResult(Result.REJECTED_OTHER_REASON,
                        "\n!!! You are trying to merge the master branch into the release branch!\n")
      return false
    }
  }
}

Let’s go through the code:

  1. Simple configuration of our pre hook consists of:
    • the name of the repository for which the hook should be executed,
    • the branch which should be safe-guarded,
    • SHA of a commit which indicates that the pushed commits originated from a branch (or a version of it) that shouldn’t be merged into our feature branch.
  2. GitBlit binds for us variables like the commands array. We iterate over them, and if the command is an UPDATE of our configured branch, we go to the next point.
  3. We use GitBlit’s API to retrieve a list of pushed commits.
  4. We check if the commit which should never appear in our branch is on the list of commits with which it is about to be updated.
  5. If the commit is on the list, we set the rejected reason and return false, which causes the update to fail and the branch to stay just as it was. The developer who was pushing the commits will receive the message we set using the setResult method.

The idea behind the check is very simple, but it does the trick.

To use the pre hook:

  1. Put it in the GitBlit’s data/groovy directory (in my case, the path is g:\git\gitblit-1.6.2\data\groovy\).
  2. Configure the script with the right name of your repository, branch, and the commit SHA
  3. Associate the script with your repository – edit the config file in GitBlit’s directory with your repository (in my case: g:\git\gitblit-1.6.2\data\git\CheckBranchPreHookTest.git\) and add the following line: preReceiveScript = check_branch.groovy

Let’s try the pre hook. I have a repository named CheckBranchPreHookTest and I configured the pre hook for it. The master branch has the following commits:

$ git log --graph --pretty=oneline

* a568c93dc9f0fa371037353030f2f0ee413b8aa2 third commit
* 7a951674237e14f0324dda430554e4bf5c482005 second commit
* 01150a51c8c81e7dadeb14ca4f2d07ed76e8b8e6 initial commit

I also have a feature branch, which was branched off of master from the second commit, and one commit was added to it:

$ git checkout feature
Switched to branch 'feature'

$ git log --graph --pretty=oneline

* 23337b3851b4a39767530805d5f188ea3b8b3c7c feature commit
* 7a951674237e14f0324dda430554e4bf5c482005 second commit
* 01150a51c8c81e7dadeb14ca4f2d07ed76e8b8e6 initial commit

As you may see, the feature branch does not have the third commit from the master branch. I put the associated SHA of that commit in the pre hook.

Now, if the hook works, when I try to merge a branch branched off of the master branch into the feature branch, I should get the error that I’m trying to update feature with illegal commit. Let’s try that.

Firstly, we’ll create a new branch based on master and add a commit to it:

$ git checkout master

Switched to branch 'master'

$ git checkout -b branch_from_master

Switched to a new branch 'branch_from_master'

$ echo test >> a.txt

$ git commit -a -m "another commit"

[f267284] another commit
 1 file changed, 1 insertion(+)

Secondly, we’ll merge it into feature branch and try pushing it:

$ git checkout feature

Switched to branch 'feature'

$ git merge branch_from_master

Merge made by the 'recursive' strategy.
 a.txt | 5 +++++
 1 file changed, 5 insertions(+)

$ git push origin

Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 532 bytes | 0 bytes/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To http://user:pass@localhost/r/CheckBranchPreHookTest.git
 ! [remote rejected] feature -> feature (
!!! You are trying to merge the master branch into the release branch!
)
error: failed to push some refs to 'http://user:pass@localhost/r/CheckBranchPreHookTest.git'

The pre hook worked as expected! The push failed and the reason was printed out. In GitBlit’s log, we can also see the information:

2016-01-10 23:41:37 [INFO ] ARF: CheckBranchPreHookTest.git/info/refs?service=git-receive-pack (100) authenticated
2016-01-10 23:41:37 [INFO ] ARF: CheckBranchPreHookTest.git/git-receive-pack (100) authenticated
2016-01-10 23:41:37 [INFO ] Checking if the master branch is not pushed into the release branch.
2016-01-10 23:41:37 [ERROR] Groovy script check_branch.groovy has failed!  Hook scripts aborted.
2016-01-10 23:41:37 [WARN ] 334da165afad24be43b331f3c0b875c2b74db149 REJECTED_OTHER_REASON because "
!!! You are trying to merge the master branch into the release branch!"

Hope you’ll find this script useful. If you encounter any problems with it, please let me know! And remember to heavily test whatever pre hooks you write for your Git server to be sure you will not incorrectly reject pushes from your fellow developers!

Leave a Reply

Your email address will not be published.