On this page
- Very simple workflow for small projects
- Simple method to create Drupal GitLab MR
- Common development workflow
- 1. Create a new feature branch from master
- 2. Commands to merge master branch into a feature branch
- How to deal with merge conflicts
- Allow roll-back: Tie SQL dumps to commit hashes
- GIT ignore SQL dumps
- Define common tables to skip the data for (not the structure)
- Make a "smart" dump
- How to perform a rollback?
- Use aliases (functions) instead
- Commit a database (during initial development) and the corresponding config export
- Additional: How to import the remote database to your local database
- Note
Common development workflow
When developing new changes in Git it is recommended to use a development workflow that allows for the changes to be developed, tested, without impacting the primary project. Once everything has been tested and approved those changes can be merged in to the primary branch of the project. For bigger, more complex projects, custom modules and themes will normally be in their own repository, and pulled in via the main project's composer.json.
For smaller simpler projects, you may be able to not use branching, with configuration, custom modules, and themes, etc. in a single repo.
While you can create GitLab MR's via the user interface, if there are many changes, using Git to create an MR for Drupal contrib module or Drupal core can be most practical.
Very simple workflow for small projects
If you are the only one working on a small project, and looking for a simple workflow, you might not need to use branching. Just clone the remote repo. Make your changes. Commit your changes. Push your commits to the remote repo.
To make things even simpler, you could consider housing custom modules and themes in a single unified project repo, and not in separate repos.
An alternate strategy for custom modules is to commit them directly to the repository of the site where they are used. This is commonly done with modules that are specific to just one site.
From .gitignore patch in #3082958: Add gitignore(s) to Composer-ready project templates.
Simple method to create Drupal GitLab MR
It can seem difficult to create a Merge Request (MR) in GitLab with Git for a Drupal core or contrib module, breaking it down into the minimum required steps, using HTTPS token authentication can help. The example is based on #3256623: Add upgrade path for migrations from 7.x-1.x to Drupal 10.
- In the issue, click "Create issue fork", if it doesn't already exist and check it says "✓ You have push access".
- Clone the project and enter the folder:
$ git clone https://git.drupalcode.org/project/login_security $ cd login_security/ - Now set the remote, download it, and switch to that branch. These commands can be found under "Show commands":
$ git remote add login_security-3256623 https://git.drupalcode.org/issue/login_security-3256623.git $ git fetch login_security-3256623 $ git checkout -b '3256623-add-upgrade-path' --track login_security-3256623/'3256623-add-upgrade-path' - Create a GitLab token, see Git Authentication for Drupal.org Projects > Authenticating for pushing over HTTPS.
- Do your code updates, and send them to the branch on GitLab. Use your drupal.org user name (replace
my_username) and the GitLab token as password:
$ git add . $ git commit -m "Description of the changes" $ git push Username for 'https://git.drupalcode.org': my_username Password for 'https://my_username@git.drupalcode.org': Enumerating objects: 28, done. [...]
Congratulations! If all went well, the code is now added in the GitLab branch, and you can create a Merge Request.
Common development workflow
Describes the steps in a Git managed Drupal project workflow, and the commands.
1. Create a new feature branch from master
The first step in this process is to branch off of the primary branch so you have a place to work on the changes about to be developed.
git checkout -b feature/[your-new-branch]
# Make you code changes. Do not forget to export new configuration.
git add -A # Stages new, modified and deleted
git commit -m "Some message"
git push -u origin feature/[your-new-branch] # First push only, next just 'git push'
2. Commands to merge master branch into a feature branch
Now that a new branch has been created work can now be pushed up to the branch. Most features take a while to develop, test, and approve. This often means that the primary branch has been updated be the time the new feature is ready. Before merging the feature into the primary branch you will want to be sure that those new changes are added to your branch to reduce the chance of merge conflicts.
Using the above-mentioned tools Composer, Git and Drush, what terminal commands would you use when we are on an existing feature branch and want to update it with the latest master changed on the remote repository?
# In local development environment:
drush config:export # Export new configuration while on a feature branch
git add -A # Stages new, modified and deleted
git commit -m "Some message"
git checkout master # Go to the master branch
git pull # Get the latest code base
git checkout feature/[my-existing-branch] # Go back to feature branch
git merge master # If you get any merge conflicts, see next paragraph
git push # Include latest master commits in remote feature branch
# On remote staging (or production/live) server:
drush state:set system.maintenance_mode 1 # Switch to maintenance mode
# Pull the code(configuration) from git, if it has not been put on the server automatically
composer install # Get any new dependencies
drush updatedb -y # Run the update scripts
drush config:import # Import new configuration
drush cache:rebuild # Clear all caches
drush state:set system.maintenance_mode 0 # Put the website back online
Note that we merge into our feature branch before we do a configuration import to avoid the loss of new configuration added in the feature branch such as new blocks.
Also, updb should always be run before cim, if you need to update entities after that, create a hook_post_update_NAME and use this alternative workflow:
drush updatedb --no-post-updates -y
drush config:import -y
drush updatedb -ySince 10.3 Drush provides a new deploy command that wrap it up:
drush deploy -y⚠ Beware, it changes the previous workflow order, hook_post_update_NAME is fired after drush updatedb and a new hook HOOK_deploy_NAME() is fired after drush config:import, here's the details:
drush updatedb --no-cache-clear # HOOK_update_N + HOOK_post_update_NAME
drush cache:rebuild
drush config:import
drush cache:rebuild
drush deploy:hook # HOOK_deploy_NAME()How to deal with merge conflicts
Sometimes you may still run into merge conflicts. Use this guide to help with resolving those conflicts.
Allow roll-back: Tie SQL dumps to commit hashes
We've all been there, the database is corrupt or there were errors validating the config synchronization. We want to do a rollback to a certain commit hash but you realize you don't have the database dump that goes along with it. Using an older version would mean you probably lose content.
To avoid this situation regularly make a database dump (locally and/or on your staging environment). Preferably before each merge (or pull) and with the commit hash inside the dump file name to know which state of the codebase it goes with. Now you are able to always restore your site based on a commit hash.
Follow these steps:
GIT ignore SQL dumps
Add to the .gitignore file:
# Ignore the default SQL database dumps
*.sql
*.sql.gz
/db-backups/Define common tables to skip the data for (not the structure)
Taken from https://github.com/drush-ops/drush/blob/master/examples/example.drush.yml.
Add to the drush.yml file (usually at drush/drush.yml):
sql:
# List of tables whose *data* is skipped by the 'sql-dump' and 'sql-sync'
# commands when the "--structure-tables-key=common" option is provided.
# You may add specific tables to the existing array or add a new element.
structure-tables:
common:
- cache
- 'cache_*'
- history
- 'sessions'
- 'watchdog'
Optionally you can apply it to any Drush SQL dump by adding:
# This section is for setting command-specific options.
command:
sql:
dump:
options:
# Omit cache and similar tables (including during a sql:sync).
structure-tables-key: common
Make a "smart" dump
Make a dump in a dedicated dump folder in the project root (must contain a .git folder) with a traceable file name:
mkdir -p `git rev-parse --show-toplevel`/db-backups; drush sql-dump --structure-tables-key=common --result-file=`git rev-parse --show-toplevel`/db-backups/$(date +%Y%m%d\T%H%M%S-)`git branch | grep \* | cut -d ' ' -f2 | sed -e 's/[^A-Za-z0-9._-]/_/g'`-`git rev-parse HEAD | cut -c 1-8`.sql- creates a folder db-backups in the project root (mkdir will be ignored if the folder already exists)
- using a date and timestamp in the name of the dump file so they will get listed in chronological order by default
- appended with the current branch (sanitized to be used within the file name)
- appended with an 8 character commit hash (like used by GitLab) to know what state of the codebase it "belongs" to.
resulting in for example 20180726T112941-develop-07b7ff12.sql .
Before first use, in the project root do a git init to create a Git repository. Running git init in an existing repository is safe. It will not overwrite things that are already there.
Or, if you want to GZIP:
mkdir -p `git rev-parse --show-toplevel`/db-backups; drush sql-dump --structure-tables-key=common --gzip --result-file=`git rev-parse --show-toplevel`/db-backups/$(date +%Y%m%d\T%H%M%S-)`git branch | grep \* | cut -d ' ' -f2 | sed -e 's/[^A-Za-z0-9._-]/_/g'`-`git rev-parse HEAD | cut -c 1-8`.sqlRemember that the date and time in the filename represent the moment that the dump was created, just to keep them in the right order. That is not necessarily the date and time of the commit.
In the next chapter, we are going to automate the generation of the above dump on each git pull from the repo (before we import the configuration into the database).
How to perform a rollback?
Rollback the codebase first:
Look into the filename of your last database dump to get the branch and commit hash of the code base state that "belongs" to it and follow the instructions in the link and subsequent Drush commands mentioned below.
git checkout - How to revert a Git repository to a previous commit - Stack Overflow
If you can not find the commit probably you are on the wrong branch. Finding what branch a git commit came from - Stack Overflow, use for example: git branch --contains 07b7ff12
Then to do a database rollback:
drush sql-drop -y; drush sql-cli < db-backups/[your-filename].sqlOr, if you have a GZIP:
drush sql-drop -y; gunzip db-backups/[your-filename].sql; drush sql-cli < db-backups/[your-filename].sqlUse aliases (functions) instead
If you want to use the shorter aliases dbdump and dbrestore instead of the long lines above, in your terminal do:
nano ~/.bashrc # Open editor.
# Add to the bottom:
dbdump() {
mkdir -p "$(git rev-parse --show-toplevel)"/db-backups
drush sql-dump --structure-tables-key=common --gzip --result-file="$(git rev-parse --show-toplevel)"/db-backups/"$(date +%Y%m%d\T%H%M%S-)""$(git branch | grep \* | cut -d ' ' -f2 | sed -e 's/[^A-Za-z0-9._-]/_/g')"-"$(git rev-parse HEAD | cut -c 1-8)".sql
}
dbrestore() {
drush sql-drop
gunzip -k "$(git rev-parse --show-toplevel)"/db-backups/$1.sql.gz
drush sql-cli < "$(git rev-parse --show-toplevel)"/db-backups/$1.sql
}
^X # Exit and save.
source ~/.bashrc # Make the new functions instantly available.
Note:
- Before first use, in the project root do a git init to create a Git repository. Running git init in an existing repository is safe. It will not overwrite things that are already there.
- Actually, we used functions instead of aliases.
- For dbrestore provide the file name as an argument (without extensions), thus for example
dbrestore 20190608T115648-master-efc2b9e7. - Both "aliases" can be used from inside any folder in your project, except for other nested git folders.
- If on the server you lack permission to create a folder then remove the mkdir and write to /var/tmp instead:
dbdump() { drush sql-dump --structure-tables-key=common --gzip --result-file=/var/tmp/"$(basename `git rev-parse --show-toplevel`)"-"$(date +%Y%m%d\T%H%M%S-)""$(git branch | grep \* | cut -d ' ' -f2 | sed -e 's/[^A-Za-z0-9._-]/_/g')"-"$(git rev-parse HEAD | cut -c 1-8)".sql } dbrestore() { drush sql-drop gunzip -k /var/tmp/$1.sql.gz drush sql-cli < /var/tmp/$1.sql }The repository name has also been added to the filename to distinguish between multiple projects writing their backups to /var/tmp.
Be aware that any backups in /var/tmp remain only for about 30 days. - @TODO: Make that if no argument is supplied with dbrestore, it will take the latest one corresponding to the current branch.
- To check the above script, it is suggested to use ShellCheck – shell script analysis tool.
Commit a database (during initial development) and the corresponding config export
During initial development sometimes you want to include a database in an 'install' folder in the project root. Especially in this case, it is important the database filename contains the commit hash of the codebase it "belongs to". The 'install' directory contains files needed to set up a working environment, either on a local development environment or a server. It usually consists of a compressed database file and, optionally, an archive of files outside of version control that are needed, for example, images like a site logo.
Installation instructions should still be put in the README.md or INSTALL.md file in the project root, including the location of the needed files mentioned above.
The below script can be added to the .bashrc file. The created command dbcommit will take care of the needed operations. Make sure you run this after the other commits.
dbcommit() {
drush config:export -y
git add "$(git rev-parse --show-toplevel)"/config/*
git commit -m "Configuration export"
mkdir -p "$(git rev-parse --show-toplevel)"/install
git rm "$(git rev-parse --show-toplevel)"/install/*.sql.gz
drush sql-dump --structure-tables-key=common --gzip --result-file="$(git rev-parse --show-toplevel)"/install/"$(date +%Y%m%d\T%H%M%S-)""$(git branch | grep \* | cut$ | cut -d ' ' -f2 | sed -e 's/[^A-Za-z0-9._-]/_/g')"-"$(git rev-parse HEAD | cut -c 1-8)".sql
git add "$(git rev-parse --show-toplevel)"/install/*.sql.gz
git commit -m "Updating the corresponding database installation file to the current codebase (check commit hash of the filename)"
}
The commit hash for the database filename is never the last one but the previous one. When rolling back, you can use either one.
An example:
git checkout efc2b9e7
dbrestore 20190608T115648-master-efc2b9e7 #filename without extensions
drush cache:rebuildAdditional: How to import the remote database to your local database
Using Drush sql-dump and sql-sync to sync between multiple environments
Note
The "Common development workflow" chapter was moved from another guide. Here is a placeholder page of the old location.
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion
Still on Drupal 7? Security support for Drupal 7 ended on 5 January 2025. Please visit our Drupal 7 End of Life resources page to review all of your options.