Custom Git Workflow with different environments?

My question is a theoretical/procedural one. I am trying to figure out/build a custom Git Workflow (not to be confused with Git Flow) that works well for our organization.

We’re using GitLab (but this is not that important) and have the following 3 long-living branches:

  • development (latest stable codebase)
  • staging (auto deploys to staging server)
  • master (auto deploys to production server)

The development, staging and master branches are protected, meaning that the developers can only merge to them, but can’t push. Also, during the merging, rebase (only FF) and squash-merge are enforced for a cleaner Git log.

For every feature/bugfix, a new feature-branch gets created, based on development and merged back to the respective branches when ready.

But here is the problem, we want to have good control of what gets merged into the 3 long-living branches. For example, just because some functionality is developed and merged with development, doesn’t mean we want it in master, which turns out to be hard to achieve, at least from my perspective.

Here is an example scenario:

  • A developer creates a feature/store branch, based on development, and completes it over time
  • They rebase it to development and merge it to development and staging
  • After that a new feature development starts named feature/add-footer-menu. This branch is based on development too and once completed, they merge it to development, staging and master, because it needs to go live immediately

The tricky part is that this way the changes from feature/store go live too, and we don’t want that. The only workaround to this is to work exclusively with cherry-picks and cherry-pick only the commits we want to merge with staging and master. This probably will work, but is an overkill in my opinion. Is there an easier flow to achieve this?

The main goal is to have good control over what goes live. Usually development and staging will have the same commits, but only specific features need to go to master.

Also, one more thing. If we do the above, this means that if we want to have a feature branch merged into the 3 long living branches, this means we need to have the respective feature branches, so we could rebase and merge them. 3 long-living branches = 3 short-lived feature branches, just for the merging. Without being a Git expert, this just doesn’t look right.

Please share your thoughts and suggestions. Thank you!

It is not possible to separate merge features while also enforcing a linear history and squashing as you do. Even with cherry picking, in that case you would end up with a duplicate (and mis-matched) commit between the three long lived branches.

If you accept using merge commits you could merge the same feature branch into some long running branches but not others, and when you later merge between the long running branches the commits for each feature will remain the same.

I also recommend not using squash merging and instead keep commits as the smallest logically complete changes, but that’s a separate matter. If you squash feature branches before merging, merging will still work the same.