Last active
August 12, 2021 15:59
-
-
Save alexclst/04cc21f0cd48f144bc2fcc180dcdb449 to your computer and use it in GitHub Desktop.
Git hook to deploy a WP plugin.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /bin/bash | |
# | |
# A script meant to run on the `post-recieve` Git hook to deploy to WordPress.org whenever a new version tag is pushed in. | |
# include common post-recieve functions | |
# from https://github.com/stephenh/git-central/blob/master/server/functions | |
root_dir=$(dirname $0) | |
. $root_dir/post-recieve-functions.sh | |
# cleans up before exiting | |
clean_and_exit() { | |
cd $root_dir | |
rm -rf "$repo_name" | |
cd $repo_remote_path | |
exit $1 | |
} | |
# process the pushed ref | |
# based on https://stackoverflow.com/a/5810488 | |
# based on https://github.com/GaryJones/wordpress-plugin-svn-deploy | |
process_ref() { | |
# get stdin parts | |
oldrev=$(git rev-parse $1) | |
newrev=$(git rev-parse $2) | |
refname="$3" | |
# setup with common functions | |
set_change_type | |
set_rev_types | |
set_describe_tags | |
# only process tags | |
case "$refname","$rev_type" in | |
refs/tags/*,commit) | |
# get repo details | |
# from https://stackoverflow.com/a/5584084 | |
repo_name=$(basename "$PWD") | |
repo_name=${repo_name%.git} | |
repo_remote_path=$PWD | |
# collect the tag name | |
version=${refname##refs/tags/} | |
# ensure that the tag is the correct format | |
version_checked=$(echo $version | sed -e '/^[0-9]*\.[0-9]*\.[0-9]*$/d') | |
if ! [[ -z $version_checked ]]; then | |
clean_and_exit 0 | |
fi | |
# log process start | |
echo "Preparing version $version of $repo_name for WordPress.org" | |
# generate temp directory | |
cd $root_dir | |
mkdir -p "$repo_name" | |
# checkout the intended tag of our project from Git | |
git_dir="$root_dir/$repo_name/git" | |
/usr/local/bin/git -c advice.detachedHead=false clone -b $version $repo_remote_path $git_dir | |
# Check version in readme.txt is the same as the main plugin file | |
plugin_version=$(grep -i "Version:" $git_dir/$repo_name.php | awk -F' ' '{print $NF}' | tr -d '\r') | |
readme_version=$(grep -i "Stable tag:" $git_dir/readme.txt | awk -F' ' '{print $NF}' | tr -d '\r') | |
if [ "$readme_version" = "trunk" ]; then | |
echo "Version in readme.txt & $repo_name.php don't match, but Stable tag is trunk. Let's continue..." | |
elif [ "$plugin_version" != "$readme_version" ]; then | |
echo "Version in readme.txt & $repo_name.php don't match. Aborting." | |
clean_and_exit 1 | |
elif [ "$plugin_version" = "$readme_version" ]; then | |
echo "Versions match in readme.txt and $repo_name.php. Let's continue." | |
fi | |
# get svn details | |
# TODO: support options for these, how? | |
# maybe as arguments on this script | |
svn_url="https://plugins.svn.wordpress.org/$repo_name" | |
svn_user="__USERNAME__" | |
svn_pass="__PASSWORD__" # since WP.org svn doesn't work with ssh keys | |
# make the svn path | |
svn_dir="$root_dir/$repo_name/svn" | |
mkdir -p "$svn_dir" | |
echo "Creating local copy of svn repo trunk." | |
/usr/local/bin/svn checkout $svn_url $svn_dir --depth immediates | |
if ! [ -d "$svn_dir/trunk" ]; then | |
echo "SVN repo not found. Aborting. If this is a new plugin please first submit manually to WordPress.org and wait for them to approve the plugin and create the SVN repository." | |
clean_and_exit 1 | |
fi | |
/usr/local/bin/svn update --quiet $svn_dir/trunk --set-depth infinity | |
/usr/local/bin/svn update --quiet $svn_dir/tags/$plugin_version --set-depth infinity | |
echo "Ignoring GitHub specific files" | |
svn_ignore="README.md | |
readme.md | |
Thumbs.db | |
.github | |
.git | |
.gitattributes | |
.gitignore | |
.gitmodules | |
.wordpress-org | |
composer.lock" | |
/usr/local/bin/svn propset svn:ignore \""$svn_ignore"\" "$svn_dir/trunk/" | |
echo ".........................................." | |
echo | |
echo "Deploying WordPress plugin" | |
echo | |
echo ".........................................." | |
echo | |
echo "Copy $version from git to the trunk of svn" | |
rm -rf $svn_dir/trunk/* | |
cp -r $git_dir/* $svn_dir/trunk | |
# If submodules exist, recursively check out their indexes | |
if [ -f ".gitmodules" ]; then | |
echo "Exporting each submodule from git to the trunk of svn" | |
git submodule init | |
git submodule update | |
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | | |
while read path_key path; do | |
#url_key=$(echo $path_key | sed 's/\.path/.url/') | |
#url=$(git config -f .gitmodules --get "$url_key") | |
#git submodule add $url $path | |
echo "This is the submodule path: $path" | |
echo "The following line is the command to checkout the submodule." | |
echo "git submodule foreach --recursive 'git checkout-index -a -f --prefix=$SVNPATH/trunk/$path/'" | |
git submodule foreach --recursive 'git checkout-index -a -f --prefix=$svn_dir/trunk/$path/' | |
done | |
fi | |
# Support for the /assets folder on the .org repo, in Git this will be /.wordpress-org | |
if [ -d $git_dir/.wordpress-org/ ]; then | |
echo "Moving assets." | |
# Make the directory if it doesn't already exist | |
mkdir -p $svn_dir/assets/ | |
rm -rf $svn_dir/assets/* | |
cp $git_dir/.wordpress-org/* $svn_dir/assets/ | |
svn add --force $svn_dir/assets/ | |
fi | |
echo "Changing directory to SVN trunk and committing." | |
cd $svn_dir/trunk/ | |
# Delete all files that should not now be added. | |
# Use $evn_ignore for `rm -rf`. Setting propset svn:ignore seems flaky. | |
echo "$svn_ignore" | awk '{print $0}' | xargs rm -rf | |
/usr/local/bin/svn status | grep -v "^.[ \t]*\..*" | grep "^\!" | awk '{print $2"@"}' | xargs /usr/local/bin/svn del | |
# Add all new files that are not set to be ignored | |
/usr/local/bin/svn status | grep -v "^.[ \t]*\..*" | grep "^?" | awk '{print $2"@"}' | xargs svn add | |
/usr/local/bin/svn commit --username=$svn_user --password=$svn_pass -m "Deploying $plugin_version release" | |
if [ -d $git_dir/.wordpress-org/ ]; then | |
echo "Changing directory to SVN assets and committing." | |
cd $svn_dir/assets/ | |
# Delete all new files that are not set to be ignored | |
/usr/local/bin/svn status | grep -v "^.[ \t]*\..*" | grep "^\!" | awk '{print $2"@"}' | xargs svn del | |
# Add all new files that are not set to be ignored | |
/usr/local/bin/svn status | grep -v "^.[ \t]*\..*" | grep "^?" | awk '{print $2"@"}' | xargs svn add | |
/usr/local/bin/svn update --quiet --accept working $svn_dir/assets/* | |
/usr/local/bin/svn resolve --accept working $svn_dir/assets/* | |
/usr/local/bin/svn commit --username=$svn_user --password=$svn_pass -m "Updating assets" | |
fi | |
echo "Creating new svn tag and committing it." | |
cd $svn_dir/ | |
# If current tag not empty then update readme.txt | |
if [ -n "$(ls -A tags/$plugin_version 2>/dev/null)" ]; then | |
echo "Updating readme.txt to tag $plugin_version" | |
/usr/local/bin/svn delete --force tags/$plugin_version/readme.txt | |
/usr/local/bin/svn copy trunk/readme.txt tags/$plugin_version | |
fi | |
/usr/local/bin/svn copy --quiet trunk/ tags/$plugin_version/ | |
# Remove trunk directories from tag directory | |
/usr/local/bin/svn delete --force --quiet $svn_dir/tags/$plugin_version/trunk | |
/usr/local/bin/svn update --quiet --accept working $svn_dir/tags/$plugin_version | |
cd $svn_dir/tags/$plugin_version | |
/usr/local/bin/svn commit --username=$svn_user --password=$svn_pass -m "Tagging version $plugin_version" | |
# clean up after ourselves | |
echo ".........................................." | |
echo | |
echo "Update deployed to WordPress.org" | |
echo | |
echo ".........................................." | |
echo | |
clean_and_exit 0 | |
;; | |
esac | |
} | |
# process all received refs | |
while read REF; do process_ref $REF; done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Sets: new_commits | |
# Assumes: $oldrev $newrev $refname | |
# | |
# This is for use in post receive hooks, as it assumes the refname has moved and | |
# is now newrev, we need to discard it. This is down with bash string replace, | |
# as it will replace only the first match, compared to the canonical "grep -v" | |
# approach which will throw out multiple matches if the same commit is referred | |
# to by multiple branches. | |
# | |
# Excellent, excellent docs from Andy Parkin's email script | |
# | |
################################################## | |
# | |
# Consider this: | |
# 1 --- 2 --- O --- X --- 3 --- 4 --- N | |
# | |
# O is $oldrev for $refname | |
# N is $newrev for $refname | |
# X is a revision pointed to by some other ref, for which we may | |
# assume that an email has already been generated. | |
# In this case we want to issue an email containing only revisions | |
# 3, 4, and N. Given (almost) by | |
# | |
# git rev-list N ^O --not --all | |
# | |
# The reason for the "almost", is that the "--not --all" will take | |
# precedence over the "N", and effectively will translate to | |
# | |
# git rev-list N ^O ^X ^N | |
# | |
# So, we need to build up the list more carefully. git rev-parse | |
# will generate a list of revs that may be fed into git rev-list. | |
# We can get it to make the "--not --all" part and then filter out | |
# the "^N" with: | |
# | |
# git rev-parse --not --all | grep -v N | |
# | |
# Then, using the --stdin switch to git rev-list we have effectively | |
# manufactured | |
# | |
# git rev-list N ^O ^X | |
# | |
# This leaves a problem when someone else updates the repository | |
# while this script is running. Their new value of the ref we're | |
# working on would be included in the "--not --all" output; and as | |
# our $newrev would be an ancestor of that commit, it would exclude | |
# all of our commits. What we really want is to exclude the current | |
# value of $refname from the --not list, rather than N itself. So: | |
# | |
# git rev-parse --not --all | grep -v $(git rev-parse $refname) | |
# | |
# Get's us to something pretty safe (apart from the small time | |
# between refname being read, and git rev-parse running - for that, | |
# I give up) | |
# | |
# | |
# Next problem, consider this: | |
# * --- B --- * --- O ($oldrev) | |
# \ | |
# * --- X --- * --- N ($newrev) | |
# | |
# That is to say, there is no guarantee that oldrev is a strict | |
# subset of newrev (it would have required a --force, but that's | |
# allowed). So, we can't simply say rev-list $oldrev..$newrev. | |
# Instead we find the common base of the two revs and list from | |
# there. | |
# | |
# As above, we need to take into account the presence of X; if | |
# another branch is already in the repository and points at some of | |
# the revisions that we are about to output - we don't want them. | |
# The solution is as before: git rev-parse output filtered. | |
# | |
# Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N | |
# | |
# Tags pushed into the repository generate nice shortlog emails that | |
# summarise the commits between them and the previous tag. However, | |
# those emails don't include the full commit messages that we output | |
# for a branch update. Therefore we still want to output revisions | |
# that have been output on a tag email. | |
# | |
# Luckily, git rev-parse includes just the tool. Instead of using | |
# "--all" we use "--branches"; this has the added benefit that | |
# "remotes/" will be ignored as well. | |
# | |
################################################## | |
set_new_commits() { | |
nl=$'\n' | |
# Get all the current branches, not'd as we want only new ones | |
new_commits=$(git rev-parse --not --branches) | |
# Strip off the not current branch | |
new_hash=$(git rev-parse $refname) | |
new_commits=${new_commits/^$new_hash/} | |
# Put back newrev without the not | |
new_commits=${new_commits}${nl}${newrev} | |
# Put in ^oldrev if it's not a new branch | |
if [ "$oldrev" != "0000000000000000000000000000000000000000" ] ; then | |
new_commits=${new_commits}${nl}^${oldrev} | |
fi | |
new_commits=${new_commits/$nl$nl/$nl} | |
new_commits=${new_commits/#$nl/} | |
} | |
# Sets: $change_type | |
# Assumes: $oldrev $newrev | |
# | |
# --- Interpret | |
# 0000->1234 (create) | |
# 1234->2345 (update) | |
# 2345->0000 (delete) | |
set_change_type() { | |
if [ "$oldrev" == "0000000000000000000000000000000000000000" ] ; then | |
change_type="create" | |
else | |
if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then | |
change_type="delete" | |
else | |
change_type="update" | |
fi | |
fi | |
} | |
# Sets: $newrev_type $oldrev_type $rev $rev_type | |
# Assumes: $newrev $oldrev | |
# --- Get the revision types | |
set_rev_types() { | |
newrev_type=$(git cat-file -t "$newrev" 2> /dev/null) | |
oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null) | |
if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then | |
rev_type="$oldrev_type" | |
rev="$oldrev" | |
else | |
rev_type="$newrev_type" | |
rev="$newrev" | |
fi | |
} | |
# Sets: $describe | |
# Assumes: $rev | |
# | |
# The email subject will contain the best description of the ref that we can build from the parameters | |
set_describe() { | |
rev_to_describe="$rev" | |
if [ "$1" != "" ] ; then | |
rev_to_describe="$1" | |
fi | |
describe=$(git describe $rev_to_describe 2>/dev/null) | |
if [ -z "$describe" ]; then | |
describe=$rev_to_describe | |
fi | |
} | |
# Sets: $describe_tags | |
# Assumes: $rev | |
# | |
# The email subject will contain the best description of the ref that we can build from the parameters | |
set_describe_tags() { | |
rev_to_describe="$rev" | |
if [ "$1" != "" ] ; then | |
rev_to_describe="$1" | |
fi | |
describe_tags=$(git describe --tags $rev_to_describe 2>/dev/null) | |
if [ -z "$describe_tags" ]; then | |
describe_tags=$rev_to_describe | |
fi | |
} | |
# Takes a lockfile path and command to execute once the lock is acquired. | |
# | |
# Retries every second for 5 minutes. | |
# | |
# with_lock "foo.lock" "echo a" # Works | |
# with_lock "foo.lock" "echo b1 ; echo b2" # Work | |
# several() { | |
# echo several1 ; echo several2 | |
# } | |
# with_lock "foo.lock" several # Works | |
# | |
with_lock() { | |
lockfile="$1" | |
block="$2" | |
with_lock_has_lock=1 | |
trap with_lock_unlock_if_held INT TERM EXIT | |
# used to use lockfile to try multiple times but it's not always available | |
mkdir "$lockfile" | |
with_lock_has_lock=$? | |
if [ $with_lock_has_lock -ne 0 ] ; then | |
exit $? | |
fi | |
eval "$block" | |
rmdir "$lockfile" | |
with_lock_has_lock=1 | |
} | |
with_lock_unlock_if_held() { | |
if [[ "$with_lock_has_lock" -eq 0 ]] ; then | |
rm -f "$lockfile" | |
fi | |
} | |
display_error_message() { | |
echo "----------------------------------------------------" >&2 | |
echo "" >&2 | |
echo "" >&2 | |
for ((i=1;i<=$#;i+=1)); do | |
eval message="$"$i | |
echo "$message" >&2 | |
done | |
echo "" >&2 | |
echo "" >&2 | |
echo "----------------------------------------------------" >&2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment