June 2020 by Kristian Lumme

Using Git with WordPress — Part 1

In the first article in our new series on using version control with WordPress, we look at the benefits of keeping our work in version control and walk through a couple of simple scenarios where a plugin or a theme is kept in a Git repository. In upcoming articles, we'll go beyond the basics and expand our repository to include more of the project, look at Git submodules and the Composer package manager, and consider the role of the database in our workflow.

Make sure to check out article #2, #3 and #4— and get notified when the next article in this series is available, simply by signing up for our newsletter or by following us on Twitter.

WordPress and Version Control

There are many reasons you might want to keep a WordPress site — or parts of it — under version control. The potential benefits of version control are much the same here as they are for any other kind of project. You'll be able to experiment fearlessly, knowing that you can always get back to an earlier version of your code. Branches let you develop new features while simultaneously implementing fixes in your production version. Using a hosting service like GitHub, Bitbucket or GitLab, you'll not only have code backups with a full history of development, but collaboration with other developers will also be a lot smoother.

However, WordPress was conceived at a time when the use of version control was less ubiquitous than it is now. For example, the first release of WordPress was in 2003, while the first release of Git took place in 2005. Of course, there were capable version control systems long before Git, but considering the widespread use of Git in the industry today, this may serve to put WordPress in its proper context.

In other words, WordPress probably wasn't made with a version-controlled workflow in mind and, as anyone who tries to apply one to WordPress discovers, this does cause some issues. Unlike with some more modern systems, there is no single best way to do things, no generally accepted best practices. Still, people have given this topic quite a bit of thought, and the WordPress community has come up with some solutions and workarounds! By applying these techniques, you can get many of the benefits of a version-controlled workflow in your WordPress project with a minimum of the headaches.

Coming Up

In this article series, we'll look at some scenarios where you might want to use Git with WordPress, along with ideas on how to approach them. Moving from simple to more intricate workflows, we'll start with a couple of basic examples and then go on to introduce concepts like Git submodules and the Composer package manager. We'll also take a look at a couple of WordPress plugins that can be integrated into your workflow in order to streamline some aspects of it. Hopefully, the series will serve as a good overview of the different options available. Let's dive right in!

Scenario: Developing a Standalone Theme or Plugin

First, we'll quickly touch on the scenario where you're developing a standalone plugin or theme. This differs from the other scenarios which concern themselves with developing and version-controlling a website, and is the simplest of our scenarios. In this case, it probably makes sense to keep just your burgeoning plugin or theme in version control. You'd make the directory in question a Git repository, tracking changes here as you develop your project. While you probably use a WordPress installation to test it, the project is not directly tied to any particular WordPress site, and so the other aspects of the site don't belong in your repository.

Below is a quick look at how that may work. Let's assume we're working on a plugin, but haven't created a repository for it yet. Our plugin is in the usual place in wp-content/plugins inside the WordPress folder, and the contents of our plugin directory look something like this:

assets/

include/

src/

gulpfile.js

my-awesome-plugin.php

We want to add most of these files to the repository. The assets directory, though, contains files that are automatically generated from files in the src directory, using a build tool like gulp. Best practice is to not include files that are automatically generated from other files in your repository, and so we create a .gitignore file with the following contents inside the plugin directory:

/assets

Then, we initialise a repository in the plugin directory and add the relevant files and directories to it:

$ git init

$ git add .

$ git commit -m "Initial commit"

Next, we do some work on our plugin, editing some SCSS for example, resulting in git status reporting the following:

$ git status

On branch master

Changes not staged for commit:

(use "git add <file>..." to update what will be committed)

(use "git restore <file>..." to discard changes [...])

modified:   src/scss/main.scss

no changes added to commit ([...])

We then commit our hypothetical change:

$ git add src/scss/main.scss

$ git commit -m "Prevent image overflow on smaller screens"

Now, git log shows a record of our changes:

$ git log --oneline

8df5539 (HEAD -> master) Prevent image overflow [...]

017159c Initial commit

As we keep working on our plugin, we can always check the development history, compare versions, and work on different features in separate branches. In addition, let's say we've created a remote repository for the plugin on a hosting service like GitHub. We can now push up our local repository, which gives us a code backup and makes collaborating with others easier (remember to substitute your own username and repository name):

$ git remote add origin https://github.com/klumme/my-plugin.git

$ git push -u origin master

To be sure, it's possible to expand on what's included in the repository. For example, we could have automated tests that essentially create a temporary WordPress installation programmatically. Maybe we have one or more Docker images with different WordPress setups used during development and testing, and the configuration of these are included in our repository. There are many options, but in this scenario it seems likely that the repository is focused on the plugin or theme.

What benefits will Git give us in a situation like this?

- For one, it lets us try new things without worrying about losing any work. If we make commits every time we complete a self-contained chunk of work, we'll be able to jump back and forth in our development history, undo changes, compare versions and find out when a particular bug was introduced.

- Using branches, we can try out new ideas without affecting the main line of development, discarding or integrating the ideas as we go along.

- If we are working on our project with others, a Git hosting service such as GitHub, Bitbucket or GitLab will be a great help in integrating the work of all developers and distributing the latest changes to everyone.

Scenario: Developing a Site by Creating a Custom Theme

A scenario that is very similar to the one above is developing a site by creating a custom theme, keeping only the theme in version control. In this case, we might have a WordPress installation running on our computer, another on the site server, and possibly some testing or staging installation. We keep only the theme directory in version control, handling the WordPress installations and their content separately. For a simple site, this might be a good solution, giving us version control without the challenges that come with trying to keep more things in Git. The drawback is that the rest of the site lives its own life outside of Git, and so we don't have all of the development and history of the project in one place.

The steps for this scenario look much the same as the steps for the scenario above. Instead of repeating those, we'll look at how the workflow plays itself out in the Tower Git client. Similarly to the earlier plugin scenario, we have a theme we're working on, but not yet a repository. The theme resides in wp-content/themes/my-awesome-theme inside our WordPress directory, with the following files and directories:

assets/

include/

src/

functions.php

gulpfile.js

index.php

style.css

To initialise a repository here, we just drag the folder into our repositories in Tower and choose "Create Repository":

Again, we don't want the assets directory to be added to the repository, as its files are automatically generated. The same goes for the style.css file. To accomplish this, we just right-click the files inside Tower, choose "Ignore → Ignore This Item", and Tower will generate the .gitignore file for us:

We stage all the files and create our initial commit:

After making some changes and committing again, our development history is available right in Tower, and, as before, we can check the development history, compare versions, and work on different features in separate branches — this time, right in Tower!

The benefits are largely the same as mentioned for the previous scenario: code backups, fearless experimentation, collaboration... In addition, Git may be useful when we want to deploy the changes in our theme to the site. For example, a script on the site server could pull down the latest code from a hosting service and execute some build steps, or we could use an automation tool like Ansible to accomplish the same thing, or a dedicated third-party deployment service. In either of these cases, deployment likely starts with committing the latest updates to our Git repository.

Conclusion

That's it for the first installment in the series. We've looked at keeping a plugin or theme directory in version control using Git, and considered some benefits this brings us. In the next article, we'll go beyond the basics and expand our repository to include WordPress core files as well. We'll consider the cases where this makes sense and the ones where it doesn't, and deal with some of the challenges such a workflow brings with it.

Make sure to get notified when the next article is available — simply by signing up for our newsletter below or by following us on Twitter.

June 2020 by Kristian Lumme

Using Git with WordPress — Part 2

In the second article in our series on using version control with WordPress, we go beyond the basics, introducing an improved directory structure and looking at how we can use WordPress as a Git submodule. In upcoming articles, we'll discuss dependency management using Composer and consider the role of the database in our workflow.

Be sure to check out article #1, #3 and #4 — and get notified when the next post in this series is available, simply by signing up for our newsletter or by following us on Twitter.

Building High-Quality Web Apps

"The Twelve-Factor App" is a document describing a methodology for building high-quality web applications. The methodology consists of twelve recommendations on how best to handle such concerns as configuration, logging and dependency management. To what degree this methodology can and should apply to a WordPress site is debatable. However, the first factor is probably a good recommendation for just about any long-term project involving code today: in short, it states that the project should consist of a single version-controlled codebase. This is what we'll take a look at next.

In the previous article, we considered a site with a custom theme, with only the theme in version control. Yet there are parts of the project outside the theme that contribute to the final site: the WordPress code itself is an example of this. There are also the files uploaded by the authors of content on the site, as well as any plugins and themes installed. Finally, there is the WordPress configuration to consider.

Git and WordPress Issues

At this point, some of the issues related to using Git with WordPress become apparent. You may be thinking that it makes little sense to put user-uploaded files in version control, as the content using these files is going to be saved in the database, not on the file system. You'd be right, and furthermore, the database is not only used for content in WordPress. Many settings are saved here as well and, if you're using a page builder plugin, much of the layout too! Evidently, putting our files into version control is not going to cover all the parts of the site setup. There is simply no clean division between what may be called the site structure — layout, options, code — and content in WordPress. This fact complicates version control, and requires us to make some trade-offs when putting our site in a Git repository.

Another objection worth considering is this: the WordPress project is already version-controlled. The codebase of WordPress is developed using Subversion, with a mirror using Git on GitHub. As a rule of thumb, if something is already version-controlled elsewhere it should not be put into your repository — sticking the WordPress code into a Git repository essentially means duplicating the effort of the WordPress core developers. We'll look at a workaround in this article, and get back to alternative ways of handling the problem later.

Finally, it may be noted that tracking plugins and themes in our repository is subject to the same objection as tracking WordPress core: these are likely already version-controlled somewhere else. Also, consider your development workflow. Maybe you develop the site locally on your own computer, committing changes to your custom theme, to the configuration, and to installed plugins to your Git repository. At some point, you then deploy the changes to a production site... but if a user installs a new plugin through the production site, should it go into version control? Should user-installed plugins live outside version control, along your version-controlled ones? Or maybe users of the site shouldn't be allowed to install plugins at all? You have to decide on which alternative makes sense for your workflow.

What's the Point?

At some point, then, a thought occurs: if we're not tracking plugins, themes and user uploads in Git, and the WordPress core is version-controlled elsewhere, what do we gain by putting more of our codebase in version control compared to the previous scenario where only our custom theme was version-controlled? Good question.

- For one, we'll lock down our WordPress version (that is, the code in our repository will be a specific version of WordPress).

- We'll also put the WordPress configuration (i.e. the wp-config.php file) in version control.

- Finally, we may choose to track the specific plugins we install as we develop the site without tracking user-installed plugins, possibly disallowing users from installing new plugins altogether.

Again, you're going to have to consider the requirements inherent in your own workflow. In the end, if you're only tracking your custom theme and the WordPress core files, maybe the previous scenario of just keeping your theme in version control is going to work better for you!

Our Scenario

In this article, the approach we've settled on is to disable plugin and theme installations from the WordPress admin, as well as automatic updates. We'll assume that we want to put as much of the site under version control as possible. This would work well with a workflow where the site is developed locally and then deployed to a production server, with only content updates done on the server. For this purpose, you could give other site users limited roles, making them editors rather than administrators for example. However, we'll lock down installations and updates using the WordPress configuration file.

It was mentioned earlier that we'd look at a workaround for duplicating the version control efforts of the WordPress developers. That workaround involves Git submodules. Put simply, Git submodules allow you to keep a Git repository as a subdirectory of another repository. The files of both repositories show up on your file system, but the outer Git repository doesn't track the content of the inner one. By setting up a submodule pointing to a GitHub repository, for example, you can include the project of some other developer inside your own project.

Maybe you see where this is going? Using a Git submodule, we can point to the official WordPress GitHub mirror in our project, including that code without checking it into our own repository. We will be able to choose a specific version of WordPress and even update WordPress by changing which version our submodule points at. There's just one caveat...

Submodule Considerations

Let's take a step back for a moment: we use WordPress through a Git submodule, which puts the WordPress code in a directory in our project. We want to track changes to our custom theme, to the WordPress configuration and possibly to installed plugins and themes... all of which currently happen inside this submodule directory. The issue is that all of those changes will be interpreted as happening to the submodule, not to our main project repository. In other words, committing and pushing these changes would mean pushing them to the main WordPress repository. As you probably understand, this is neither a realistic nor a useful scenario. Getting this setup to function is going to require a little more work.

WordPress has a few options that allows it to better accommodate this kind of workflow. Currently, the directory holding WordPress is the root directory of our site (hereafter referred to as the webroot). We can, however, put WordPress in a subdirectory of its own. Combined with another option that lets us move the wp-content directory outside the directory containing the WordPress core files, this will let us make things a lot cleaner.

Implementation

So, let's get going! Here, we're going to work with a fresh install of WordPress, creating a repository directly in our webroot, where the WordPress files are initially located. If you're working with an existing site, adapting these instructions should not be too difficult.

This article follows the official WordPress documentation, which at the time of writing only covers putting WordPress in a subdirectory using Apache. The .htaccess files mentioned in the article allow per-directory configuration of Apache, and are often used to set up rewrite rules, for example. While WordPress does support other servers such as nginx, special-case setups for these are not as thoroughly documented — perhaps, in the case of nginx, because nginx has no feature directly corresponding to .htaccess files. As a consequence, this article covers configuration in Apache, but finding resources on how to accomplish similar setups using other web servers should be possible.

I did have some success with nginx combining "method 2" described here with the "location strategies" described here.

To begin with, we create a directory called wordpress inside of our webroot, and move all the WordPress files from the webroot into this directory. In particular, note that if you've gone through the WordPress install, there is probably an .htaccess file here that is hidden, according to the convention of files with names starting with a dot being hidden. We want to make sure this is moved into the wordpress directory as well!

Then we create a new .htaccess file in the webroot, next to the wordpress folder, with these contents (remember to replace the two instances of my-site.test with the URL of your site):

<IfModule mod_rewrite.c>

RewriteEngine on

RewriteCond %{HTTP_HOST} ^(www.)?my-site.test$

RewriteCond %{REQUEST_URI} !^/wordpress/

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ /wordpress/$1

RewriteCond %{HTTP_HOST} ^(www.)?my-site.test$

RewriteRule ^(/)?$ wordpress/index.php [L]

</IfModule>

This will rewrite requests to our site to hit the WordPress installation in the subfolder correctly.

That's all there is to making the site work with WordPress in a subdirectory!

Next, we'll make it so that plugins, themes and uploads exist outside the WordPress core directory. We copy the wp-content folder out of the wordpress directory to our webroot. Until we configure WordPress to use the new directory, it will still use wp-content in the wordpress directory. To make WordPress use the new directory, we need to add the following lines to the file wordpress/wp-config.php, right before the comment saying "That's all, stop editing!", again replacing the URL with the URL of your site:

define('WP_CONTENT_DIR', dirname(__FILE__) . '/wp-content');

define('WP_CONTENT_URL', 'http://my-site.test/wp-content');

/* That's all, stop editing! Happy publishing. */

Now, our webroot only holds two directories — wp-content, which holds plugins, themes and user uploads, and wordpress, which holds the WordPress core files — and an .htaccess file. But there's another step to take before we're finished. As mentioned earlier, we're currently version-controlling all of the WordPress core files in our main repository. With the way we've set things up now, we're ready to use a Git submodule for WordPress instead.

We'll want to retain our configuration file, so we first move our wp-config.php out of the wordpress directory into our webroot. WordPress will automatically detect a wp-config.php file that is one level above the directory where WordPress is installed. Then we remove or move the wordpress directory — these files will be provided by the Git submodule in the future. Finally, let's initialize our Git repository and add the WordPress submodule in our webroot:

$ git init

$ git submodule add git://github.com/WordPress/WordPress.git wordpress

When this command finishes, we find that the webroot directory again contains a wordpress subfolder, in which the WordPress core files can be found. In addition, the webroot contains a wp-content directory and the wp-config.php file (and the hidden .htaccess file, along with the Git repository files).

At this point, if we go ahead and refresh our site, things should work just fine! However, we're not quite done yet.

By default, Git will initialize the submodule to point to the latest commit on the master branch of the WordPress project. This is probably not what we want, so let's set it up to point to a specific WordPress version instead. At the time of writing, the latest WordPress version is 5.4, which also exists as a tag in the repository. Specifying this version is as easy as entering the submodule directory and checking out that tag:

$ cd wordpress

$ git checkout 5.4

The same thing can be done in the Tower Git client by double-clicking the submodule, showing the tag list in the sidebar, and then simply double-clicking the relevant tag:

A submodule will generally be in "detached HEAD mode". In a normal repository, this might be an indication that something is not right, but in the submodule, this is the normal order of business: we want the submodule to point to a specific commit, ensuring that its contents don't change unexpectedly.

There are a couple of small things to fix. First, note that if WordPress core was to be updated from the admin, or automatically updated, this would show up as a bunch of modified files in our submodule WordPress repository. We're going to be handling updates using Git in the future, so we'll disable other ways of updating. As mentioned, we're also going to lock down plugin and theme installations from the admin. There's an option in WordPress that will accomplish both of these things: we add the following line towards the end of our wp-config.php, after the lines we added previously:

define( 'DISALLOW_FILE_MODS', true );

This will prohibit any file modifications done by WordPress automatically or through the admin. Not only will plugin and theme installations be disabled, but also any automatic updates, and the ability to edit plugins and themes from the admin. User media uploads will still be possible.

Note that there is a security aspect here: if you turn off any automatic updates, you're going to be the one responsible for making sure the site is updated. As updates often fix potential security holes, keeping up-to-date with new versions is important. If you want a bit more control over what to disable, this article is a great resource.

Finally, we do want to track the plugin and theme directories in wp-content, but we don't want to track user uploads. To that end, we'll create a .gitignore file in our repository root, with the following contents:

/wp-content/uploads

Let's commit the project at this point (in the webroot — the directory of the parent repository):

$ git add .

$ git commit -m "Initial commit"

In the future, updating WordPress or installing a specific version is easy: we just enter the submodule directory, run git fetch to get the latest updates from the remote, and check out the tag in question! The parent repository will pick up the changes and we can make a commit there to make it point to the new version.

Workflow

There are a few things worth mentioning about Git submodules and the workflow we've established here. To begin with, when we add the submodule, Git will check out the relevant commit and place its contents in the submodule directory for us. However, when cloning the project, this doesn't happen. There are a few ways to populate the submodule directory with the correct contents. When cloning the project, we can provide the option --recurse-submodules, and Git will initialize and clone the submodules for us. If we've already cloned the project, we can instead run git submodule update --init --recursive, and Git will do the same for us.

When saving permalinks in WordPress, it may save an .htaccess file in the wordpress directory, resulting in Git showing an untracked file in the submodule repository. We can't ignore this file from the parent repository, and any changes in the .gitignore file in the submodule repository would be overwritten when we check out a new commit. However, we can ignore it locally by editing .git/modules/wordpress/info/exclude (in the parent repository) to include the line /.htaccess. These changes will not be included when pushing to a remote, but will be considered locally. We can do the same thing in the Tower Git client by double-clicking the submodule, right-clicking the .htaccess file, and then selecting "Exclude → Exclude This Item", as seen below.

Conclusion

There we go! We now have most of our site under version control, with WordPress as a Git submodule. As we work on the site, install new plugins or perhaps develop our custom theme, everything will be tracked in version control. Collaboration with other developers will be aided by the ability to synchronize changes and to have different branches, perhaps shared through a Git hosting service. In addition, we may have a deployment workflow which entails pulling down the latest version of our code to the server from such a hosting service.

We did mention the fact that, when it comes to plugins and themes, we're essentially duplicating the version control effort made elsewhere. With WordPress, we used a Git submodule as a solution, but were content at this time to just include the plugins directly in our repository. In the next installment, that won't be the case anymore. We'll take the approach of considering the plugins and themes — indeed, even WordPress itself — as dependencies of our project, and look at how we might go about handling such a scenario.

That concludes the second article in the series. We've updated our folder structure and looked at using WordPress as a Git submodule. In the next article, we'll use the Composer package manager to handle our dependencies — "dependencies" in this case meaning WordPress as well as plugins and possibly themes.

Make sure to get notified when the next article is available — simply by signing up for our newsletter below or by following us on Twitter.

uly 2020 by Kristian Lumme

Using Git with WordPress — Part 3

In the third article in our series on using version control with WordPress, we introduce the Composer package manager and use it to handle our dependencies, including plugins, themes and WordPress itself. In an upcoming article, we'll look at the role of the database in our workflow and present a couple of plugins which can be of use here.

Be sure to check out article #1, #2 and #4 of this series as well!

Dependency Management

In our last article, we put the whole site under version control and used a Git submodule to manage WordPress. We referred to "The Twelve-Factor App" and the recommendation of keeping your site in a single codebase under version control. Now, we'll apply another one of the twelve factors to our Git and WordPress projects, namely factor number two. This states that we should "explicitly declare and isolate dependencies". Let's take a look at what that might entail in the context of a WordPress project.

What are our dependencies in such a project? Well, that is not quite as clear-cut as you might think:

- Some would say, for example, that WordPress itself is a dependency. Others might say that WordPress is the application itself (the discussion on this WordPress ticket provides some differing viewpoints on this matter).

- Are our plugins and themes dependencies? It's certainly possible to see things that way.

- How about PHP, its extensions and the web server? Again, it's probably possible to make a case either way.

For the purposes of this article, we're going to draw the line at plugins and themes. We'll look at how we might set up a project where we consider WordPress core and our plugins and themes external dependencies for our project.

Enter Composer

The point about dependencies being explicitly declared is this: the code of the dependencies should not be included in the repository, nor should it be assumed to exist on the system. Instead, dependencies should be specified in a file (or several files) included with the project, and be installed using a package manager.

There exist many package managers and formats for specifying dependencies: the package.json file for the NPM package manager is an example in the JavaScript/Node.js ecosystem and Ruby has Gem and its Gemfile. For PHP, the package manager we'll use is called Composer, and the dependencies will be specified in a file called composer.json.

The workflow we're looking to implement is this: as the site is developed, dependencies will be recorded in the composer.json file. The code of the dependencies themselves won't be included in the repository, but the composer.json file will. When someone else starts working on the project, all they need to do is clone the repository and then use Composer to install the dependencies. This will put the actual code of the dependencies in the right place, using a version that satisfies the constraints specified in the composer.json file.

Conventions and Solution

There's one more thing to address before we move on to a practical example. While systems such as Ruby on Rails, Django and Node.js have been designed with this sort of dependency management in mind, WordPress has not. For the former systems, there are clear best practices and accepted ways to do things. With WordPress, this is not really the case. As noted, some would argue that treating WordPress and its plugins and themes as dependencies the way we do here is the wrong way to go about things. Furthermore, a package manager installing dependencies needs a place to get these dependencies: a source of information about available packages and a place to download their actual contents. For Composer, this source is the package repository Packagist. There are just a couple of problems: there is no official WordPress package here, nor are there packages for every plugin and theme.

Fortunately, the WordPress community has developed workarounds for these problems. For WordPress core, there are unofficial packages on Packagist, allowing us to install WordPress through Composer. When it comes to the plugins and themes, there's a separate package repository called WordPress Packagist, which mirrors the official plugin and theme repositories. With these, we can implement the workflow we want. Finally, there are community efforts wrapping the ideas described here, along with others, into starter projects. That way, we don't have to start from scratch when setting up a modern WordPress workflow. One such starter project is Bedrock, which is what we'll be using in this article.

A side note on the "isolation" part of the factor description above: this refers to the fact that dependencies that exist on the system, outside the application, should not be able to implicitly "leak in" and be used by the application. We'll only note that a WordPress installation or plugins and themes outside our application are unlikely to accidentally be used by our application, and leave it at that.

Getting Started with Bedrock

In the previous article, we started from a standard install of WordPress, changing things around to better fit the workflow we were trying to achieve. In this article, we'll go with a different approach. As mentioned when discussing community solutions above, a team called Roots has created a "WordPress boilerplate" project called Bedrock that helps with many of the things discussed in these articles. In particular, it's set up to allow dependency management using Composer out of the box. It also changes the directory structure in a similar way to what we did in the previous article, although Bedrock takes things one step further.

To get started, we first need to install the package manager Composer on our system. Instructions for doing this vary depending on your system, and can be found on the Composer website. When Composer is installed, starting a new project based on Bedrock is easy:

composer create-project roots/bedrock my-bedrock-site

This command will download the specified package — Bedrock in this case — into the my-bedrock-site directory, and then automatically run composer install there to install the dependencies defined by that package.

In the previous article, we moved the WordPress core files into a subdirectory of their own inside our webroot. We then moved the wp-config.php file outside the WordPress directory, and did the same with the wp-content directory. Bedrock takes a similar approach. However, while we kept everything inside the webroot, Bedrock moves some files that should not be served by the webserver outside the webroot. These include configuration files, the composer.json file specifying our dependencies, and the dependencies themselves (except for the WordPress core files, plugins and themes). So, where, in the last article, the webroot was the project directory, here, the webroot is a directory inside our project directory.

Configuring Our Site

Bedrock adds some flexibility to the configuration process. We won't go into all the possibilities, but here follows a quick overview: configuration that differs from host to host and that we don't want in our repository (this includes sensitive data like database credentials) is handled through environment variables. This is an application of another factor described in "The Twelve-Factor App". An easy way to populate the environment variables automatically is to fill in the .env ("dotenv") file that comes with the project (and which is excluded from version control by default in the included .gitignore file). The .env file is located in the project directory. Here, we set the database credentials, site URL and location of the WordPress files, and some security-related variables. In addition, the WP_ENV variable specifies whether we're running a development, staging or production instance. This variable is used to load an environment-specific configuration file in config/environments, allowing us to do things like enable debugging when developing, but not in production. The reason there are specific files only for development and staging, not production, is that the default settings are for production.

We don't edit the wp-config.php file directly. Instead, this file is set up to load your actual configuration, which resides in the config directory. The application.php file here holds the configuration normally found in wp-config.php, while the config/environments directory holds environment-specific configuration files.

For our local development setup, the file looks something like the example below. Replace the database credentials and the URL to match your setup. Server configuration is outside the scope of this article but examples for nginx and Apache can be found in the Bedrock documentation. On your staging and production servers, you would add different .env files, corresponding to the particular needs of those environments. The options towards the end of the file relate to security and can be generated automatically online.

DB_NAME='my-bedrock-db'

DB_USER='kristian'

DB_PASSWORD='correct-horse-battery-staple'

WP_ENV='development'

WP_HOME='http://my-bedrock-site.test/'

WP_SITEURL="${WP_HOME}/wp"

# Generate your keys here: https://roots.io/salts.html

AUTH_KEY='KOpS7LuHymY8l))w=Lg68V)4b:=Z]*&)6TZ4:d1yw2;%!V`o:u/$Kg)|x+^n?*kS'

SECURE_AUTH_KEY='1gEkmau@kN6B4MAY}aM=Nj8fSP:O5tkkQ52S4v=74IhD,p<?W6<#KU6F2#$6%%U+'

LOGGED_IN_KEY=']a3f<Y_N&?x|21`]V$7jx2w-!AjO:qDztTH[g/04VJt/?C)k0yD%6ZFUIle;r?=1'

NONCE_KEY='Vf<Z.SqCKWsLeq5`|3o%bx]q%j?5C&.QDcs+56p?}D{W`df`Fo*22+qiS0np(w_`'

AUTH_SALT='EVRi)si#hGqi//0!A[*EjjSAt/9(s0=^0MY/;gWuL>{ImF,aAEXLntO<S.n(gNuG'

SECURE_AUTH_SALT='NGC_tj^4@4:oH)8Dtk>Z4%WuEY.)m:7DfjR&CjEj7y08H>6|zkFG_YL9{|}YF1%#'

LOGGED_IN_SALT='B`>j27%R4%Lb.}Tcio=5qAY,}jQy#8t%p*/6fK^WmwU=[Sjnl76x/ySPYigF-6|C'

NONCE_SALT='X><QK1x`9mP1`2=vjC`xLZW;|k_{Y8`3v1Nx:p*3NFS6jlvmU%D!Q>l-QTKOSqq7'

By setting WP_ENV to development, we ensure that the config/environments/development.php configuration file will be loaded. This enables debugging in WordPress, among some other things.

There is one setting we should change for the development environment. In the last article, we disabled file modifications, disallowing plugin and theme installations from the WordPress admin, along with automatic updates. This setting is applied in the production environment in Bedrock, too. However, the development environment re-enables file modifications. We want to handle everything through Composer in the development environment as well, so, in config/environments/development.php, just remove the line that says Config::define('DISALLOW_FILE_MODS', false);.

Up and Running

With the above steps done, provided the server is configured correctly, we can visit the site in our browser and you proceed with the WordPress installation as usual. Next, let's go ahead and initialise a Git repository in the project directory:

$ git init

$ git add .

$ git commit -m "Initial commit"

The .gitignore file that comes with Bedrock excludes files like the .env file with the database credentials from the repository by default. There are some files like README.md that refer to the Bedrock project rather than to the site we're building, but there's no harm in keeping them in the repository. The .gitkeep files you see in some places are a convention — as Git won't track an empty directory, these are used to make sure otherwise empty directories like web/app/plugins are included in the repository. The files themselves are completely empty.

Plugins and Themes

Unlike the previous article, where we included plugins and themes in the repository, in Bedrock, these are handled by Composer. However, if you're developing a custom theme specifically for your site, you'll want to keep that in the repository. Let's take a look at how to manage WordPress updates and plugins through Composer.

As mentioned before, WordPress core, plugins, and potentially themes are specified in the composer.json file. Here, under require, we find roots/wordpress along with some other dependencies. Note that the WordPress version is specified exactly here. That means that, as WordPress is updated, the version used for our site is not going to change, unless we change it explicitly. Note also that we use a custom version of WordPress, provided by the Roots project, as the official version doesn't support Composer. The dependencies also include other PHP libraries used by the project. For example, the composer/installers dependency along with the extra.installer-paths option makes Composer install plugins and themes in the correct place, rather in the default vendor directory.

To change the WordPress version installed is as easy as changing the version in composer.json and running composer update! What's more, we can install plugins in a similar way. As mentioned, plugins and themes are provided through a separate Composer repository called WordPress Packagist, which mirrors the official plugin and theme directories. Let's say we want to install the plugin "Contact Form 7". Searching for it on the WordPress Packagist website confirms its existence. To install it, we could add a line under require in composer.json manually... but we can also run a Composer command that accomplishes the same thing. Let's do just that:

composer require wpackagist-plugin/contact-form-7

After this command, we'll find that the dependencies in composer.json include Contact Form 7:

"require": {

[...]

"wpackagist-plugin/contact-form-7": "^5.1"

},

The caret operator ("^") means that Composer can install newer versions of the plugin in the future, as long as they're compatible with the version we just installed. The composer.lock file, however, will specify exact versions of the packages installed. That means by default, versions used by the project will not change... but when we're ready to update packages to the latest versions that still match the constraints in composer.json, we can run composer update.

That's all it takes to install a plugin! git status will now show the files composer.json and composer.lock as being modified, so let's make a commit:

git commit -am "Install Contact Form 7 plugin"

Conclusion

That wraps it up for this article. We now base our site on a community project with an updated directory structure and more flexible configuration. Furthermore, we handle the dependencies of our site, including plugins, themes, and WordPress itself, through the Composer package manager. As far as keeping a WordPress site in Git goes, we're pretty far along.

However, there is still one unaddressed issue — an elephant in the room so to speak. You may have asked yourself in earlier articles: "what about the database?". The database doesn't just hold the content of a WordPress site, but also to some degrees its options, layout and other aspects that are hard to ignore as we're trying to improve our workflow. In the next and last installment in this article series, we'll take a look at the database, and see how a couple of plugins can make managing it more convenient.

July 2020 by Kristian Lumme

Using Git with WordPress — Part 4

In the last article in our series on using version control with WordPress, we look at the plugins WP Migrate DB Pro and VersionPress and how they help us manage the database in our workflow.

Be sure to check out article #1, #2, and #3 in the series as well!

WordPress and the Database

In the series so far, we've looked at progressively more sophisticated ways of keeping a WordPress site under version control with Git. We've seen how this workflow can give us benefits like easier experimentation, backups, collaboration and deployment. However, there's an aspect that's very much a piece of the puzzle which we haven't discussed much so far. That piece, of course, is the database.

In this final article in the series, we'll widen our scope beyond Git and look at the practical question of how to handle our database in a scenario like those described in earlier articles. For that purpose, we'll consider two plugins and find out what problems they can help us solve.

Shared Responsibilities

Let's begin by looking at the role the database plays in our project. In the previous article, for example, we handled plugins and themes using Composer, and kept most of our files in version control. Among other things, this lets us share the site code with others, and deploy by pulling the latest code from the repository. Still, it's not the complete picture.

For example, if we add a post (or a page) to our local version of the site, commit, push, and then pull down the code on the production site, the post won't appear there. This is because the post exists in the database, not on the file system. To some extent, this makes sense, as it's not a given that we want to have exactly the same content on the local and the production site... but this limitation affects more than just site content. In WordPress, the line between content and the structure of the site itself is quite blurred, as not only posts and pages, but also many options, styles and even layout can be saved in the database. For example, if we install a page builder plugin and create a custom layout for your start page, that layout is likely saved in the database and not on the file system.

What about backups, and conveniently moving back and forth through the history of changes made to our site? Again, Git handles this easily for the code part... but the database won't keep up. If we add a feature to our theme and utilize it on a page, then find out that the feature didn't work as intended and roll back to a previous version of the code, the page using the feature will still remain, possibly resulting in a broken page.

Let's take a look at a couple of plugins that can help us with the issues described above.

WP Migrate DB Pro

(Disclaimer: We reached out to the developers of this plugin, Delicious Brains, who provided us with a license so we could include the plugin in this article series.)

The functionality of the commercial plugin WP Migrate DB Pro is, on the surface, easy to describe. The plugin can take your database on one site and push it to another site, or pull the database from one site to another. In the process, it can perform necessary substitutions in the database to handle things like URLs and paths that may differ between the instances.

It speaks to the quality of this plugin that the practical usage is not much more complicated than that. After installing the plugin on a couple of sites, we can enable pushing and pulling for one site, get a URL which is used to push or pull to that site, enter the URL on the other site, and sync the database between the two sites.

It's worth mentioning at this point that the plugin doesn't merge the database changes, i.e. it can't combine changes from both of the sites. It only lets us overwrite one of the databases with the other.

A common problem when moving a WordPress site is that absolute URLs and paths are saved in the database, serialized in such a way that simply searching and replacing them won't work. WP Migrate DB Pro handles this as well, letting us specify the new path and URL of the site, and even auto-populating the fields.

A Real-World Example

Let's look at an example. We'll assume we have two sites set up based on the Bedrock project, along with Git repositories, as described in the previous article. The second site is based on a clone of the repository of the first, so it has the original site as a Git remote. In addition, WP Migrate DB Pro has been downloaded and installed on both sites, along with its Media Files Addon. The Media Files Addon let's us push and pull the media library between the sites in addition to the database. This is useful because, as you may remember, in the Bedrock project, the uploads folder is added to the .gitignore file so that it will not be tracked using Git. The sites will be referred to as "development" and "production", although in this example, we'll be using two local sites. They could just as well be a local and a remote site.

Note that WP Migrate DB Pro needs to be installed manually, not through Composer, and that installing the plugin by uploading it through the WordPress admin requires the DISALLOW_FILE_MODS option to be set to false. In addition, the project is set up not to include plugins in the Git repository, as these are handled through Composer. To include the WP Migrate DB Pro plugin and its Media Files Addon in our repository, we need to add exceptions for their directories in our .gitignore file.

On the development site, we'll install the page builder plugin Elementor. Remember, in this approach, we handle plugins through Composer, so the plugin is installed by the following command:

$ composer require wpackagist-plugin/elementor

We then commit the change:

$ git commit -am "Install Elementor plugin"

Next, let's do some changes inside WordPress. Through the customizer, we first set the start page to a static page. Next, we activate the Elementor page builder plugin and work on a front page for the site:

To make the Elementor design transfer successfully to the new site, we need to go into its advanced settings and set "CSS Print Method" to "Internal Embedding" instead of "External File". By default, Elementor saves its CSS as an external file in the uploads folder but outside the media library — meaning the file wasn't picked up either by Git (thanks to the .gitignore file) or the Media Files Addon (which only works on the media library).

Great! Now, we obviously want to deploy these changes to the production site. In the real world, we would probably push the changes to a hosting service like GitHub, Bitbucket or GitLab, then pull down the latest changes to the site. For this example, we're running both sites locally, with one of them set up as the remote of the other, so we can pull down the changes directly in the directory of our production site:

$ git pull

Finally, committing the plugin installation in the previous step only added the plugin to the list of dependencies — in order to actually install the plugin on our production site, we need to run the following command:

$ composer install

You can probably guess what happens next. On the production site, even though we've pulled down the latest code, the page we just created doesn't exist, the front page of the site is still the post listing, and the Elementor plugin isn't even activated. This is because all of these changes exist in the database, not on the file system. At this point, we'll turn to WP Migrate DB Pro.

To begin with, let's go to the WP Migration DB Pro settings for the production site and enable "pull" and "push". We also copy the connection info listed here:

On the development site, we go to the WP Migrate DB Pro admin page, choose the push operation and paste the connection info. Note that WP Migrate DB Pro has filled in the find-and-replace fields for us with a URL and a path. We also enable "media files" to make sure the images on the page are included when we push (this is where the Media Files Addon comes into play).

We then click "Push" and wait for the operation to finish.

That's it! Our changes are now pushed to the production site! (In reality, the push changed the name of the site as well. I went in and changed it after the push to make it clear which of the sites we're on at the moment. With some coding, you can get around this).

Plugin Strengths

We've now looked at one way to synchronize not just code changes, but also database changes, between sites. Let's consider which problems this plugin solves for us, and which it doesn't. WP Migrate DB Pro has export and import operations, which could allow us to take backups of the database along with the code backups provided by using Git, and to export the database before implementing a tricky feature, allowing us to "roll back" the database along with the code if needed. However, these are manual operations and do not provide the flexibility that Git does. Rather, the strength of this plugin lies in its push and pull functionality, allowing us to apply the changes on one site to another. This is very useful for deployment and collaboration, and so WP Migrate DB Pro proves a very useful companion to Git in such scenarios.

The plugin we'll take a look at next takes a different approach to the database. Not only does it adhere more closely to the Git way of doing things — it is, in fact, an attempt at putting the WordPress database under version control using Git, along with the files!

VersionPress

VersionPress is an interesting and very ambitious plugin, aiming to integrate WordPress directly with Git. In particular, database updates are now version-controlled, meaning things like changing an option or creating a new post will result in Git commits. Behind the scenes, VersionPress relies on pure Git, letting us use standard Git commands and workflows to work on our site.

According to the official site, VersionPress is currently a developer preview and should not be used in production, although some people are certainly doing this. Making all or most WordPress operations version-controlled is a complicated undertaking, and many things can go wrong. Plugin compatibility, or lack thereof, is a factor in how well VersionPress works, and it is not clear how actively it is being developed at the moment. On the other hand, the solutions it offers are enticing, and if VersionPress does prove to work in your specific scenario, you could get a lot of benefit from it. At the very least, you should probably make sure to have backups that are independent from VersionPress.

The benefit of the plugin are probably best shown with a few examples. For this article, we're using the latest release of the plugin found on GitHub at the time of writing, which is version 4.0-beta2.

In Practice

This time, let's start from a completely vanilla installation of WordPress — no subdirectory install, no submodules — everything standard. After downloading VersionPress from GitHub, installing and activating it, we get a VersionPress page in the WordPress admin, which lists the history of changes on the site. Currently, there's just the change where we activated VersionPress.

Each of the changes here correspond to a commit in Git. Demonstrating that this is indeed the case, here's what's shown when we run git log --oneline in the project directory:

e2f8f2b (HEAD -> master) [VP] Activated VersionPress 4.0-beta2

Let's create a new post in the WordPress admin the usual way. After this, the VersionPress page in the admin lists our new change. Notice also the options to undo or roll back to each change:

As before, our change shows up in the Git log as well:

af55763 (HEAD -> master) [VP] Created post 'My New Post'

e2f8f2b [VP] Activated VersionPress 4.0-beta2

In an earlier article, we talked about how reverting a commit in Git isn't enough to roll back a change if we've made changes in the database as well as on the file system. Let's take a look at how VersionPress lets us handle such a scenario. This is also a good opportunity to illustrate how we can use normal Git commands on our repository as well. First, we'll make a change to our theme (in this case, the default Twenty Twenty theme of WordPress). We'll edit the functions.php file in the theme to add a new widget area ("sidebar", in WordPress terms) called "Header".

Now, if we take a look at the VersionPress page, VersionPress notes that we have uncommitted changes in our repository, but it doesn't create a commit by itself. We will create the commit ourselves in Git. Let's use the Tower Git client for this one:

... and the commit shows up on the VersionPress admin page, along with the changes made by VersionPress:

We then make a change in the database that depends on the code change in the previous step. Let's go to the "Widgets" admin page and move all widgets from the other sidebars into the "Header" sidebar.

Again, VersionPress lists our changes:

Now we're in the scenario described before: we've made a change (moving widgets to a new sidebar) that touches both the code and the database, and just rolling back the code change does not restore the state before it was introduced. However, with VersionPress, we can roll back the change! Just click "Roll back to this" on the change before we added the new sidebar in functions.php, and things will be back as they were before!

This is one example of the magic of VersionPress. Note here that the rollback command doesn't just revert some old commits, it also needs to update the database. Therefore, we still can't jump around in history using purely the Git command-line interface, but need to use the VersionPress interface (though we could accomplish the same thing using pure Git and the WP-CLI apply-changes command, which we'll get back to later).

Collaboration

VersionPress also has operations to clone a site and push and pull changes between sites, but these work only locally — they don't directly allow us to synchronize work between a local site and one on a server, for example. However, using Git and the WP-CLI apply-changes command of VersionPress, such a workflow can still be accomplished! Let's take a look at how.

The VersionPress clone and merge operations, as well as the apply-changes and restore-site commands used here require WP-CLI. WP-CLI is a command-line interface to WordPress, allowing us to accomplish many operations in WordPress through the command-line or through scripts. An introduction to WP-CLI is outside the scope of this article, but installation and usage instructions can be found on the project homepage.

One awesome feature of VersionPress is that it can restore a site, including the database, from just a Git repository. To set up the next example, I've done the following: I've taken my example site using VersionPress and cloned the site to another location:

$ git clone my-versionpress-site my-versionpress-clone

To go from cloned repository to working site, I've used the WP-CLI restore-site command. This requires some extra steps, which are listed in the official documentation for VersionPress. Let's appreciate how useful this is for a moment: we get a complete, working copy of the previous site, including the database, based on nothing but a Git repository! In this case, both sites are running on the same machine, but if we were to use a service like GitHub to push and pull commits between the projects, they could just as easily be running on different machines.

Now let's look at something else that's possible because the change tracking is based on Git. When looking at the WP Migrate DB Pro plugin, we noted that pushing or pulling a database in that plugin always overwrote the changes on the receiving site — we couldn't merge changes between databases. As every change in VersionPress is represented by a commit, merging changes between sites is, indeed, possible!

Suppose we create a new post on the original site. At the same time, someone else creates a new post on the cloned site. Now, on both of these sites, VersionPress has introduced a commit for the change, and both sites have changes we want to keep — we want to retain the new posts on both sites.

The Git repository of the clone has the original site as a remote, so let's pull down the latest changes on the clone:

$ git pull

Git asks us for a merge commit message as the changes from the remote are merged into the current branch, but this runs without problems, as we don't have any conflicting changes. Pushing locally is a little tricky, so let's instead add the clone as a remote of the original site (so both sites have the other one as a remote — this is where you would probably just push to and pull from a service like GitHub in a real-world scenario). Then, to get the changes into the original site, let's pull to that site from the clone. In the directory of the original site:

$ git remote add clone ../my-versionpress-clone

$ git pull clone master

Now we have the commits for both posts in the repositories of both sites, but not in the database. We've only pushed around the commits, not actually made the changes apply in the database. That happens next. In both projects, we run the WP-CLI command apply-changes:

$ wp apply-changes

This will take what's in the commits and apply it to the database. After the above command has been run for both sites, both sites have the latest changes — the post created on the original site as well as the post created on the clone!

Should I Use It?

The apply-changes and restore-site commands, along with everything being accessible in Git, has quite some potential! Our Git repository gives us a history of development, providing backups and possibility for rolling back changes, not only to our code but to the database as well. Collaboration and deployment can benefit greatly from these features. On the flip side, VersionPress tries to solve a very complicated problem and, as mentioned, the developers don't recommend this plugin for production use. You're going to have to decide for yourself whether this plugin can have a place in your workflow or not.

Final Words

This concludes our article series on using Git with WordPress. We've covered a lot of ground, and there are many considerations, challenges and questions we didn't bring up here. Hopefully, though, you've found this series useful, and found some new ideas you can bring into your workflow.

Table of Contents

Using Git with WordPress — Part 1

Using Git with WordPress — Part 2

Using Git with WordPress — Part 3

Using Git with WordPress — Part 4