This is a rough guide to set up and support a "wunderflow" style branching strategy with code sync to various environments with git. It will show how to create a Pantheon-like multidev experience on any server, using git hooks, as well as how to bypass Pantheon's "serial" code workflow and use a more robust "parallel" workflow. See: Parallel Principle of Git Workflow
The Env Setup below illustrates how this might be done on a single server with subdomains, plus additional notes on using MultiDev on https://pantheon.io
ENV SETUP
GIT REPOS
Create a Bare Repo
This will vary for the chosen hosting service. On Pantheon for example, we'd just create "multidev" environments for develop and stage branches, using the main master branch for live.
If you're configuring your own server, set up vhosts for your 3 environments and configure subdomains: dev.[project].tld, stg.[project].tld. Our master branch will deploy code for [project].tld.
vhosts should point to doc roots for each environment something like:
/var/www/dev
= Development Testing/var/www/stage
= Staging for review and approval prior to release./var/www/html
= Live
Now create a bare git repo to dispatch code to your "work trees".
mkdir /var/www/[project].git
cd /var/www/[project].git
git init --bare
So all your project directories will look like this:
/var/www/[project].git
= The Bare git repo./var/www/dev
= Development testing./var/www/stage
= Staging for review and approval prior to release./var/www/html
= Live
Create a post-receive hook that will push to a detached work tree.
/var/www/[project].git/hooks/post-receive
#!/bin/bash
# The path to the bare git repo on the test/prod server.
GIT_DIR="/var/www/[project].git"
# The following checks which branch is received (master, stage, or develop)
# and sets some variables for code syncing to the correct work tree.
while read oldrev newrev ref
do
if [[ $ref = refs/heads/master ]];
then
echo "Ref $ref received. Deploying MASTER branch to production..."
WORK_TREE="/var/www/html"
BRANCH="master"
elif [[ $ref = refs/heads/stage ]];
then
echo "Ref $ref received. Deploying STAGE branch to stage environment..."
WORK_TREE="/var/www/stage"
BRANCH="stage"
elif [[ $ref = refs/heads/develop ]];
then
echo "Ref $ref received. Deploying DEVELOP branch to test environment..."
WORK_TREE="/var/www/dev"
BRANCH="develop"
else
echo "Not a valid environment branch, no code sync."
exit 1
fi
done
echo
echo "Syncing Code to $WORK_TREE"
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout $BRANCH -f
echo
echo "Cleaning up. (git clean -df)"
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR clean -df
# Run any post code sync up tasks.
# Example below for Drupal 8 updates, config sync, and cache clear.
echo
echo "Updating and Importing"
cd $WORK_TREE
# Run Drupal database updates.
drush updb -y
# Import updated configuration.
drush cim -y
# Clear caches.
drush cr
echo
echo
echo "Done deploy and update."
echo
Make the git hook executable.
chmod +x /var/www/[project].git/hooks/post-receive
Set Up Your Git Remotes
While code deployment can, and probably should, be automated, I describe steps below as if they were being done manually, to illustrate the process.
For Manual Deployment: Add remotes for your working repo and host.
_NOTE: for simplicity, I'm focusing only on deploying the whole repo to a host. A more advanced process would be to have a separate "working repo" with tooling for development along with the codebase. The deployment process would create a "build artifact" of production code that would be committed to a separate repo that is deployed to the host. This process would allow for a light weight working repo with dev tools and dependency files, while only committing compiled dependencies and production code to a host.
Because the repo on the host is only meant for managing branches for dispatch to our environments, and the host has limited access, we host a working copy of the repo somewhere else like Github or Bitbucket. This is where developers can checkout code and push changes to be merged.
git clone [your project]
-- working copy from Bitbucket/Githubcd [your project]
git remote add host [host uri]:/var/www/[project].git
Now you can use git commands to sync code from your working repo to your host repo for each environment. Simply git pull
the desired environment branch from remote: origin
(your working repo), and git push
the same environment branch to remote: host
. (See NOTE about simplicity, above).
If new work was merged into develop on Bitbucket, to get it to your development environment:
git pull origin develop
git push host develop
(Or configure an automated task to do this at intervals, or on merge to an environment branch.)
Deploying a Release
During a development cycle, all PRs make their way through Development Testing and then to stage for final review and sign off.
Each release branch should have a version number i.e 8.2.x.0.0 D.d.f.p (Drupal major version, Drupal minor version, Custom feature release, patch/bug).
Code that has final approval for deployment can be merged into the master
branch. Master can be tagged with the version number, and synced to the live environment.
git checkout master
git pull origin master
git tag release-D.d.x.f.p
git push host
git push host release-D.d.x.f.p
Pantheon: A secret trick for parallel git workflow.
We have an extra step when deploying to Pantheon. We need to add an additional tag in the tag step above.
Pantheon's standard workflow is "serial" not "parallel". We must use multidev instances on Pantheon for a parallel workflow. The standard workflow uses only the master branch with specific tags to indicate which commit to checkout for the test and live environments. We only care about pantheon_live_N because we ignore pantheon dev and test, in favour of our own develop
and stage
multisites. Read the linked post to find out why.
The following command will list the tags on the rep, filter them to pantheon_live_ with grep, and show only the highest increment.
git tag -l --sort=v:refname $pantheon_prefix* | tail -1
Once we have the current latest tag, we increment by one and tag our new latest commit on master:
git checkout master
git pull origin master
git tag release-D.d.x.f.p
git tag pantheon_live_N
-- whatever the nextpantheon_live_
value is.git push host
git push host --tags
Pushing the tag pantheon_live_N
will trigger a code sync to Live if N is greater than any other pantheon_live_N
tag.
More info: https://pantheon.io/docs/hotfixes