Skip to content

Instantly share code, notes, and snippets.

@Pierstoval
Last active September 9, 2021 07:46
Show Gist options
  • Save Pierstoval/27e8f309034fa0ababa1 to your computer and use it in GitHub Desktop.
Save Pierstoval/27e8f309034fa0ababa1 to your computer and use it in GitHub Desktop.
Git pre-receive example to use a bare repo to deploy an ap

Example of a pre-receive Git hook

What does it do?

  1. First, it fetches the Git repository of your project.
  2. If you push a tag, it creates and checkouts a new branch based on the tag name, and executes a script you can customize.
  3. Else, if you push something else than a tag, it just executes a script you can customize too.

And that's all folks!

It's just much more beautiful than Capistrano and all other deployers, because, you know, it's in Bash, not in Ruby.

And totally customizable.

And can use ssh keys.

⚠️ It will execute the script for each ref you send. Means that git push prod v0.1.0 v0.2.0 v0.3.0 will create three branches, sequentially, and execute your script after each branch is created!

Install

  1. Create a bare repo on your server, anywhere you want:
git clone --bare git@github/Me/MyProject /var/repos/my-project

This repo will receive the push you will send to it.

Note: If you want you can create an empty repo the same way with git init --bare but it's not the very same. As you wish, actually.

  1. On your local machine, add a new remote to deploy on this freshly new repository:
git remote add prod ssh://me@my_server:port/var/repos/my-project

This allows you to run git push prod and send Git references to this repository.

  1. Go back to your server, inside the /var/repos/my-project directory and run this:
wget https://gist.githubusercontent.com/Pierstoval/27e8f309034fa0ababa1/raw/pre-receive.bash -O hooks/pre-receive

Note: Be sure to run this at the root of your bare repository, because it will create a hooks/pre-receive file.

  1. Edit the hooks/pre-receive file.
  2. READ the comments above the first vars (Note: If you're clever, read the code under "Script start" too, you'll know how it works).
  3. Update the workingtree, success_tag_script and/or success_nontag_script variables according to what you need.
  • a. Use git push prod my_tag to push a tag and execute the tag script.
  • b. Use git push prod to push the local branch and execute the nontag script.

If you have any question, just ask here.

The licence is MIT, written at the bottom of the pre-receive.bash file on this repo.

#!/bin/bash
########################################################################
################################ README ################################
########################################################################
#
# This script is here to allow the use of "git push prod v1.2.3" commands or similar.
#
# Push a tag to a bare repository having this file as pre-receive hook,
# and you'll be able to deploy directly from command line in your local environment,
# as long as you create a git remote pointing to this bare repo via ssh.
#
# For more info, full readme can be found here: https://gist.github.com/Pierstoval/27e8f309034fa0ababa1
#
# Enjoy! :)
# Working Tree
# This var corresponds to the project you are working on.
# This is where the fetch & checkout will occur for deployment.
workingtree="/var/www/project"
# Success Script for a git TAG reference
# Example of script executed after a successful deploy
# Change it accordingly if you want to use proper commands for your projects
# This script is executed after a "cd" to the $workingtree directory.
# Here is an example for a Symfony project
read -d '' success_tag_script << SCRIPT
composer install && \
php bin/console cache:clear --env=prod && \
(phpunit -c app || echo "[ERROR] Tests failed!!" && exit 128)
SCRIPT
# Success Script for a git NON-TAG reference
# This script is executed if you push something else than a tag.
# Actually, if you don't push a tag, we cannot know exactly the branch you're pushing.
# (Note: I may not have discovered it yet, actually, feel free to enlighten me if you know!)
# So it's your job to specify another script to be executed!
read -d '' success_nontag_script << SCRIPT
echo "This is not a tag, so I will not do anything.'
exit 1
SCRIPT
####
#### Script start
####
# You should not modify anything starting here.
# But still, if you want to read the comments... ;)
echo "[INFO] Receiving the current push as `whoami`."
gitcmd="git --git-dir=${workingtree}/.git --work-tree=${workingtree}"
# References are sent in stdin for the pre-receive script.
# We then get all refs manually and we can do something on each ref.
echo "[INFO] Reading stdin..."
while read line
do
echo "[INFO] Line to read: $line"
# Only if line is not empty.
if [[ -n "${line// }" ]]; then
# Split the line into array.
IFS=' ' read -r -a array <<< "$line"
# This is the standard Git behavior for pre-receive:
parentsha=${array[0]}
currentsha=${array[1]}
ref=${array[2]}
echo "[INFO] "
echo "[INFO] Current line:"
echo "[INFO] > Parent sha: $parentsha"
echo "[INFO] > Current sha: $currentsha"
echo "[INFO] > Ref: $ref"
if [[ "$ref" =~ ^refs\/tags\/ ]]; then
# Here, we have a tag, so we may create a special branch and deploy!
# Regex replace the tag to get only its name.
tag=${ref/refs\/tags\//}
# This var will be used to check the return code of each command.
cmdcode=0
echo "[INFO] "
echo "[INFO] Received a tag: $tag."
# Execute fetch and check that the command succeeds.
echo "[INFO] "
echo "[INFO] Fetching repository..."
$gitcmd fetch --all --prune --tags
cmdcode=$?
if [ $cmdcode -ne 0 ]; then
echo "[INFO] Could not fetch repository."
exit 100
else
# If we could fetch, we create a branch based on $tag.
# And we check that it succeeds.
echo "[INFO] "
echo "[INFO] Creating a new branch based on the tag."
currentbranch=`$gitcmd name-rev --name-only HEAD`
$gitcmd checkout -b "release_${tag}" "tags/${tag}"
cmdcode=$?
if [ $cmdcode -ne 0 ]; then
echo "[INFO] Could not create branch."
exit 110
else
echo "[SUCCESS] Successfully checked out the \"release_${tag}\" branch!"
echo "[INFO] If you need to rollback, just checkout the previous branch with this command:"
echo "[INFO] \$ $gitcmd checkout $currentbranch"
fi
fi # end if fetch failed/succeeded
if [ $cmdcode -ne 0 ]; then
# We add a message here to stop the process with a proper error message.
# Every other message sent by Git is sent to stderr so we can check on it.
echo "[ERROR] Could not deploy to $workingtree."
exit 120
else
# SUCCESS!
# Here, no command failed so we can deploy and do what we want!
echo "[SUCCESS] Deployed! Now, executing scripts..."
# Move to the directory to execute scripts
cd $workingtree
# And execute the script!
eval $success_tag_script
cmdcode=$?
if [ $cmdcode -ne 0 ]; then
echo "[INFO] Post-deploy script failed and returned exit code ${cmdcode}."
exit 130
else
echo "[SUCCESS] Deployed!"
fi
fi # end if commands failed/succeeded
# End of "tag" deployment
else
# Here it means the pushed ref is not a tag, so it's certainly a branch.
# You can do something else if you want, like checkout the branch and deploy it...
# By default, we do nothing, it's your role to check everything.
# If you want a hint, many users may simply do a "git pull origin master" in
# their distant repository...
echo "[INFO] Not a tag, executing the plain configured script."
# Move to the directory to execute scripts
cd $workingtree
# And execute the script!
eval $success_nontag_script
cmdcode=$?
if [ $cmdcode -ne 0 ]; then
echo "[INFO] Post-deploy script failed and returned exit code ${cmdcode}."
exit 140
else
echo "[SUCCESS] Deployed!"
fi
fi
fi # endif line is not empty
done < "${ST:-/dev/stdin}"
echo "[INFO] End of pre-receive script."
# Copyright (c) 2016 Alex Rock Ancelet <[email protected]>
# MIT license - https://opensource.org/licenses/MIT
# Source: https://gist.github.com/Pierstoval/27e8f309034fa0ababa1
@c0debreaker
Copy link

Thank you for sharing your example publicly. I'm trying to figure out a step from this blog - https://tech.gogoair.com/jenkins-jobs-as-code-with-groovy-dsl-c8143837593a

I want to create jenkins job automatically by a presence of runway/dsl.groovy file.

He says -

3. Developers push their changes and a pre-receive git hook determines that the dsl.groovy file was changed

I've been shaking my heading trying to figure out where pre-receive should be saved.

Any help would be great appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment