On this page
Git branching strategy for multiple developers
This documentation needs review. See "Help improve this page" in the sidebar.
While Git manages versions of Drupal code, the active configuration used during Drupal runtime comes from the database. The configuration API, when used with Drush, allows for exporting the active configuration from the database to the filesystem, so that it can be committed to Git to be migrated between environments, where it is imported into the database to become active configuration in that system.
When working with multiple developers however, it can be easy for developers to accidentally overwrite each other's changes, causing issues such as a deleted field that keeps re-appearing, regression of settings, or newly created fields that suddenly disappear. These issues arise as a result of the fact that both configuration imports to the database, and exports to file, wipe overwrite the configuration they are being written to. Without a proper branching and configuration management strategy, developers can inadvertently change configuration with their commits.
Below is a branching strategy that when followed allows for a flexible management of code branches in Git between multiple developers that maintains configuration without regression.
Branching Strategy
- A git branch that is created as a mirror of production, in the format
[MAJOR].x.x, eg1.x.x. Development is not done on this branch. It should only ever be updated after a successful deployment to production, to ensure that it is an exact copy of production (more on this later). 1.x.xis cloned to an active development branch, in the format[MAJOR].[MINOR].x, eggit checkout 1.x.x && git pull && git checkout -b 1.0.x.- When a developer starts work on code, they clone a new branch from the active development branch created in step 2. As
1.x.xis the same as the code on production, this ensures that the starting point for the code changes will work on production. For example1.x.x->feature-1. (Side note - using ticket numbers for the branch keeps code and issues cross-referenceable, ensuring code changes have an accompanying ticket from which to get information about the change) - The developer synchronizes their local environment with the git branch:
composer install && drush cr && drush updb && drush cim - When the developer is finished their work, they export config to the file system, and commit the exported config .yml files to Git. The developer then opens a pull/merge request, to merge the feature branch into the active development branch, eg
feature-1->1.1.x. This pull request is reviewed by whomever does review, and merged if approved. - When a new release is to be deployed, a new Git tag is created in the feature branch, with the full version. Eg.
1.0.0, then1.0.1,1.0.2... and so on - This tag is checked out into testing environments, so it can be tested/reviewed by testers.
- If the testing fails, fixes are made in the feature branch relevant to the bug, eg
feature-1, then go back to step 5. Repeat as necessary. - When testing passes, the git tag is checked out in the production environment, containing all the tested code
- As the code on production and the main
1.x.xbranch has now diverged, the deployed release tag is merged into1.x.x, eg1.0.8 ->1.x.x. This once again makes1.x.xan exact copy of production. - Go back to step 2, and create eg the next active development branch, ie
1.x.x->1.1.x. New pull/merge requests will now be committed to1.1.x
Note 1: In regards to step 3, branches will almost always be created from the production mirror. However, sometimes during development, branches will require code that has been developed in another branch in active development. In this scenario, instead of creating the branch as described in step 3 above from 1.x.x, the branch would be created from the feature branch it depends upon, eg feature-1 -> feature-2
Note 2: If hotfixes are needed, the flow is 1.x.x -> [Hotfix branch] -> [next tag] -> 1.x.x -> 1.1.x. Or, in other words, a copy is made from the current production code into a hotfix branch, the fix is applied, the branch is merged back into the previous active development branch, and a new tag is generated. This tag can go through testing and to production. When it is deployed to production, it is merged back into the production mirror branch, and then into the current active development branch.
Note 3: New major branches are created for major breaking code changes. For example when going from Drupal X to Drupal Y, Git can be branched 1.x.x -> 2.x.x, a development branch is created 2.x.x -> 2.0.x, and the Drupal upgrade to D11 would be handled as 2.x.x -> [UPGRADE BRANCH] -> 2.0.x. Regular non-D10 development would be handled in 1.x.x, to be merged into 2.x.x when ready.
Note 4: The advantage of an active development branch (step 2) can be seen with the following scenario:
- Three feature branches are created,
feature-1,feature-2, andfeature-3 - These branches are merged into the active development branch:
1.3.x - Upon review, the client decides that they do not want
feature-3anymore.
This creates an issue in that it is tricky to extract a feature branch after it has been merged, and in relevance to this particular thread, can cause issues with configuration. Due to the presence of an active development branch, the above issue can be resolved as follows:
- Create the next active development branch from the copy of the production code. For the above example that means
1.x.x->1.4.x. - Merge the branches that ARE to be deployed, into the new development branch. For the example above, that means merging
feature-1andfeature-2into1.4.x. 1.3.xis then abandoned.
That covers the logic behind how git branches are structured. It allows for flexibility as well as graduated releases based on a semantic versioning system.
Managing merge conflicts
Consider a deeper dig into steps 3-5 of the branching strategy outlined above on this page:
- Developer A creates a new feature branch, eg
1.x.x->feature-1 - Developer X creates their own feature branch, writes code, opens a pull request, and it is committed to
1.x.x(now1.x.xhas diverged from when Developer createdfeature-1) - Developer A commits their changes, pushes their branch, and opens a pull/merge request
- The pull/merge request reports that
feature-1cannot be merged into1.0.x, due to some conflict with the code Developer X pushed, and Git cannot sort it out. - Developer A, in their local environment, checks out the the active development branch that cannot be merged into (
1.0.xabove), and pulls the new changes. They then merge the active development branch into their feature branch. For example, if active development branch is1.0.x, this is achieved with:git checkout 1.0.x && git pull && git checkout feature-1 && git merge 1.0.x - Git will report which files have merge conflicts. These files require visual review and manual resolution of all conflicts, sometimes in consultation with other developers to understand what is happening in the conflict to determine how it is resolved. When all conflicts are resolved, the files are saved, and the changes are committed to Git.
- Next, Drupal the Drupal database is synchronized with any new code changes that have come into the branch from
feature-1. This means getting any new code with composer, running any database updates, and importing the changed configuration:composer install && drush cr && drush updb && drush cim - The developer ensures that the code in this branch is working as expected, and was not broken by the incoming code. If anything is broken, it is fixed.
- The active config is exported from the database to the file system.
- The updated code is committed to Git, and pushed
- The original pull/merge request that was blocked is now unblocked and can be completed (unless a fresh conflict has been introduced in the target branch in the meantime).
FAQ
How does the above flow ensure that new/changed/deleted configuration from other developers is properly maintained without regression when branches are merged and configuration is imported or exported?
To prevent configuration regressions, any time developers changes branches, they must bring their local DB in-line with the code in the branch, by running composer install && drush cr && drush updb && drush cim. This synchronization is seen in step 4 of the main branching strategy above, as well as in step 4 of the process of handling conflicts. Running composer install ensures that the Drupal codebase required by the configuration is present when Drupal is instantiated. drush updb will run any database updates that have come from any core/module/theme updates. And drush cim will add/update/remove any configuration changed by other developers. Note that in some cases, drush cim needs to be run before drush updb. If one results in an error, try the other first.
How does the above flow ensure that when a developer has new/changed/deleted configuration that it propagates to other developers without regression?
If the developer has properly imported the configuration in a clean branch before starting development in the branch (or after merging in changes), then exports the configuration before committing, they will not introduce any regressions with their own commit. Other developers will also pull the code and import it into their own systems in the manner outlined in the first question, so they can get the updated configuration changes with drush cim.
What about when two developers have worked on the same configuration item?
Usually Git can sort out changes even when multiple developers have made them on the same file, but sometimes git will not be able to sort it out, resulting in in merge conflicts. The process for handling merge conflicts was outlined above. These can be messy to figure out, so it's best, when possible to not have developers working within the same area of a site, to avoid creating a situation in which they could be concurrently working on the same configuration. It's sometimes not possible to avoid however, in which case, discussion between the developers when resolving merge conflicts is essential, to prevent regressions.
Originally posted as a comment by @jaypan in Forum post Best Practice for syncing configurations among multiple developers.
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.