GitBlit – Handling a File Rename in Pre Hooks

Git in practice

I’ve been recently writing a pre hook for Git to check if any of the committed files violate any policies. I’m using GitBlit and it’s using JGit (an implementation of Git written in Java). You can’t use normal Git pre hooks with GitBlit, but you can write them in Groovy.

GitBlit’s author created an API to ease the use of JGit in pre hooks. I needed to detect if a file was renamed, and have access to its old path, as well as the new one.

Unfortunately, the documentation of PathChangeModel (which corresponds to a file in a commit) doesn’t state how to obtain the old path – the renamed file object’s path property contains the new path.

Luckily, it’s open source! I’ve had a look into the source code of the mentioned class and it turned out that, in case of a rename, the old path is stored in the name property. Take a look at the following example.

Let’s:

  1. Create a new repository and:
    • add a file and commit,
    • push it to the GitBlit server,
    • rename the file and commit (but don’t push it yet).
  2. Define a new pre hook for Git Blit which will print information about committed files and configure the pre hook for our repository.
  3. Push the commit and check out what our pre hook will tell us.

New repository:

$ mkdir RenamePreHookTest

$ cd RenamePreHookTest/

$ git init

Initialized empty Git repository in g:/git/RenamePreHookTest/.git/

Adding a file, committing it and pushing the new repository to origin:

$ echo test > a.txt

$ git add a.txt

warning: LF will be replaced by CRLF in a.txt.
The file will have its original line endings in your working directory.

$ git commit -m "Initial commit."

[master (root-commit) 55add25] Initial commit.
warning: LF will be replaced by CRLF in a.txt.
The file will have its original line endings in your working directory.
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt

$ git remote add origin http://user:pass@localhost/r/RenamePreHookTest.git

$ git push origin master

Counting objects: 3, done.
Writing objects: 100% (3/3), 216 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Updating references: 100% (1/1)
To http://user:pass@localhost/r/RenamePreHookTest.git
 * [new branch]      master -> master

Rename the file:

$ git mv a.txt b.txt

$ git commit -m "Renaming the file"

[master 253a337] Renaming the file
warning: LF will be replaced by CRLF in b.txt.
The file will have its original line endings in your working directory.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename a.txt => b.txt (100%)

Now, we create a new script with the pre hook called rename_test.groovyit should be put in the GitBlit’s data/groovy directory:

import com.gitblit.utils.JGitUtils

repository = gitblit.getRepository(repository.name)

for (command in commands) {
  // fetch all pushed commits
  def commits = JGitUtils.getRevLog(repository,
                                    command.oldId.name,
                                    command.newId.name).reverse()

  commits.each { commit ->
    // fetch all changed files in the commit
    def files = JGitUtils.getFilesInCommit(repository, commit)

    for (file in files) {
      println "Change type: ${file.changeType}\n" +
              "File's path property: ${file.path}\n" +
              "File's name property: ${file.name}"
    }
  }
}

The code is fairly simple – we obtain the repository object, loop through each command and then through each commit, and, finally, we print the change type along with the path and name properties of each file.

Now we have to configure the pre hook for our repository – to do that, we add the following line to the file named config placed in GitBlit’s bare copy of our repository stored in data\git\RenamePreHookTest.git (just to be clear – not in your directory with the repository, but in the GitBlit’s one):

preReceiveScript = rename_test.groovy

Finally, we push the commit which renames the file:

git push origin master

This is the output produced by our pre hook:

2016-01-06 22:39:27 [INFO ] ARF: RenamePreHookTest.git/info/refs?service=git-receive-pack (100) authenticated
2016-01-06 22:39:27 [INFO ] ARF: RenamePreHookTest.git/git-receive-pack (100) authenticated
Change type: RENAME
File's path property: b.txt
File's name property: a.txt
2016-01-06 22:39:27 [INFO ] admin UPDATED refs/heads/master in RenamePreHookTest.git (from a7e951b58c2168b2d826b562818024bd3ae6997e to e33486ddfaa65661c692fb19a8dbc65ae9c2130e)

As you may see, the new path is stored in the path property, and the old one in the name property.

Have fun writing GitBlit’s pre hooks!

Leave a Reply

Your email address will not be published.