My goal is that for every project we start we copy this base project and just add the features required by the client. Some of this feature may be interesting to bring back to our base project.
The best solution depends on the coupling between the base project and the added features.
If your project is designed with loose coupling in mind and the added features are independent modules that can be safely added or removed without affecting the rest of the project then you can place each feature in a Git sub-module or you can even create them as separate components (independent projects) and load them into the main project using Composer.
For both approaches, each feature will stay in its own Git repository.
Each customer will get the base project, a customized composer.json
(and its corresponding composer.lock
) file and some customized configuration files.
The files specific to each customer can be stored on different branches in the main project or they can be stored in sub-directories of a dedicated directory (no customer branches). They need to be copied from there (an the configuration for other customers removed) by the deployment script (or a script that is able to prepare the package for a customer).
The pros and cons of this approach:
- (+) there is one copy of the code of each feature; changes, fixes and improvements performed on one feature are easily transferred to all customers by running
composer update
on the directories of all customers that use the feature;
- (-) it doesn't allow a feature to evolve independently for two different customers; in order to do this the feature project must be duplicated.
On the other hand, if the coupling is tighter and the features cannot be extracted as independent sub-projects (they all depend on the main project nonetheless) then I would place each feature in its own sub-directory (all of them located in a features/
directory).
The main branch (master
or develop
or whatever name convention you use) contains the full-featured project (the main project and all the features). For each customer create a new branch starting from the main branch. All the changes for this customer happens on this branch. Start by removing from features/
the features that are not provided to the customer.
This strategy works well as long as you operate all the changes in the main project in one direction: apply them on the main branch then merge the main branch into the customer branches.
The changes on the features are usually applied on one customer branch first and, after they are validated, they can be ported to the main branch and from there to the branches of the other customers.
Porting a change in a feature from a customer branch to the main branch cannot be done by merging because usually the features are customized for each customer in a different way. You probably need to apply to the main branch only a couple of commits (that contain a fix or a functionality improvement) and you can do this using cherry-pick. Porting the changes in a feature from the main branch to the customer branches usually can be done using merging because the main branch should contain only generic code, no customization.