Preface

Goal: Deploy SSG in Git Hooks

You should know that you can do CI/CD locally, just pure git and bash, without any third party service. Doing deployment from local, instead of server side, sometimes needed if you have your own VPS, or maybe just writing to /var/www/html for use in intranet.

There are so many potential use of git hooks. This article is only an example. git hooks can be utiliized for many thing else.

Official Documentation


1: Refspec: Jekyll Case

I know it sound silly. Why would I need to build Jekyll manually, while github already doing it for us? As I already said, it is only for curiosity reason, not foir daily use.

Create Master Branch

Let’s get it started.

❯ git clone git@github.com:epsi-rns/manual-jekyll.git
Cloning into 'manual-jekyll'...
warning: You appear to have cloned an empty repository.
❯ cd manual-jekyll

Copy Jekyll Source Files

As usual, I already have a ready to use Jeykll example.

❯ touch .nojekyll
❯ git add .
❯ git commit -m "First Commit" --quiet
[master (root-commit) e6ac8ca] First Commit
 209 files changed, 19053 insertions(+)
❯ git push -u origin master --quiet
Branch 'master' set up to track remote branch 'master' from 'origin'.

Preparing Script

git hooks just a script. You can use bash, perl, python, ruby or else. There are many event that can utilized as a hooked. In this case, we use pre push hook.

First thing to do is to make it executable.

❯ chmod +x .git/hooks/pre-push

To get the root directory of my repository I use this command

❯ git rev-parse --show-toplevel
/media/Works/repository/manual/manual-jekyll

And we can build Jekyll directly to any directory, instead of the default _site.

❯ jekyll build -d "$builddir"

And last, we need a new directory, that would be pushed refs/heads/gh-pages branch.

git commit -m "Update at $(date)" --quiet
git push origin master:refs/heads/gh-pages --force

Pre Push Hook Script

I copy paste my steps from my refspec article. After trial and error, I got this script:

#!/bin/sh

remote="$1"
url="$2"

sourcedir=$(git rev-parse --show-toplevel)
builddir="${sourcedir}/../build-jekyll"
mkdir $builddir

jekyll build -d "$builddir"
cd "$builddir"

git init
git remote add origin git@github.com:epsi-rns/manual-jekyll.git
git add .
git commit -m "Update at $(date)" --quiet
git push origin master:refs/heads/gh-pages --force

cd "$sourcedir"
rm -rf "$builddir"

exit 0

When you push master branch. The screen is showing push to gh-pages instead.

Git Hooks: Pre Push Jekyll

It is very simple really.

Preview

Please enjoy the render at


2: Refspec: Hexo Case

What if, we should face an SSG, that do not have feature to generate to specific directory, such as Hexo?

Use the bash, luke.

Preparing Repository

As usual we need to preapre repository.

From creating master branch

❯ git clone git@github.com:epsi-rns/manual-hexo.git
Cloning into 'manual-hexo'...
warning: You appear to have cloned an empty repository.
❯ cd manual-hexo

And then copy Hexo’s source files

❯ git add .
❯ git commit -m "First Commit" --quiet
[master (root-commit) e6ac8ca] First Commit
 209 files changed, 19053 insertions(+)
❯ git push -u origin master --quiet
Branch 'master' set up to track remote branch 'master' from 'origin'.

Git Hooks: Prepare Hexo

Preparing Script

All we need is to move the generated file, to a new directory that would be pushed refs/heads/gh-pages branch.

hexo generate  --silent
cd "$builddir"
rm -rf *
mv ${sourcedir}/public/* .

Pre Push Hook Script

I copy paste my steps from my Jekyll configuration above, with very few adjustment.

#!/bin/sh

remote="$1"
url="$2"

sourcedir=$(git rev-parse --show-toplevel)
builddir="${sourcedir}/../build-hexo"
mkdir $builddir

hexo generate  --silent
cd "$builddir"
rm -rf *
mv ${sourcedir}/public/* .

git init
git remote add origin git@github.com:epsi-rns/manual-hexo.git
git add .
git commit -m "Update at $(date)" --quiet
git push origin master:refs/heads/gh-pages --force

cd "$sourcedir"
rm -rf "$builddir"

exit 0

Git Hooks: Pre Push Hexo

When you push master branch. The screen is showing push to gh-pages instead.

Preview

Please enjoy the render at


3: Worktree: Hugo Case

Can we do it using worktree instead of refpec?

Recursive Issue

Be aware of recursive hooks calls.

My previous worktree example contain something like this:

git push --set-upstream origin gh-pages --force

When you push to gh-pages, this will trigger another pre-push event hook.

The solution is to check, whether the branch is gh-pages or not, buy using this git command:

❯ git rev-parse --abbrev-ref HEAD
master

The conditional skeleton is as below.

current=$(git rev-parse --abbrev-ref HEAD)

if [ "$current" != "gh-pages" ]; then
  ...
  git push --set-upstream origin gh-pages --force
  ...
fi

Pre Push Hook Script

After trial and error, I finally got it working. The complete script is a little bit long, as shown below:

#!/bin/sh

remote="$1"
url="$2"

current=$(git rev-parse --abbrev-ref HEAD)

if [ "$current" != "gh-pages" ]; then

  # generate
  hugo='/media/Works/bin/hugo-62'
  $hugo --quiet
  
  # gh-pages branch

  sourcedir=$(git rev-parse --show-toplevel)
  builddir="${sourcedir}/../build-hugo"

  echo "$builddir"

  git worktree add $builddir gh-pages

  cd "$builddir"
  pwd
  find . -maxdepth 1 ! -name '.git' -exec rm -rf {} \;
  mv ${sourcedir}/public/* .

  git add .
  git commit --allow-empty -m "$(git log master -1 --pretty=%B)"
  git push --set-upstream origin gh-pages --force

  cd "$sourcedir"
  git worktree remove $builddir --force
fi

exit 0

Do not forget to clean up with git worktree remove.

Git Hooks: Pre Push Hugo

Preview

Please enjoy the render at


Conclusion

You can use either refspec method or worktree method.

Talking about build locally. We are going to have fun with different situation using Jenkins.

Have fun with configuring.