When a PR is created against a pipeline environment, Gearset will sometimes perform a reverse merge to pull changes from the target environment branch into the promotion branch. (For information on the pipelines branching model and what a promotion branch is see this article.) This is similar to the standard git practice of keeping your pull requests in sync. In the context of Gearset, it ensures conflicts are resolved early and allows us to address things that the standard git merge gets wrong. (See the why reverse merge at all section below for more detail on this.)
However, due to the way git works and the way features are promoted through the pipeline, this reverse merge commit can occasionally cause confusion.
Example
Consider the following simple pipeline. On the VCS side, we have 2 long lived branches, uat
and main
.
New feature branches are created from main
.
uat
is precisely aligned with main
. If we create a feature branch feature
and commit a change to it then our git graph will look like this.
For the sake of this example, we'll say that the feature just adds a new field. We'll also perform reverse merges all the time. In reality, Gearset will only do this some of the time.
When a PR is created to move these changes to uat
, Gearset creates a promotion branch feature_-_uat
. and merges uat
into that promotion branch.
Later, when the feature is promoted, Gearset merges the promotion branch into uat
, leaving us with this git graph:
Lets say that we now decide to start a new feature before this one is promoted through to main
or to just abandon this feature completely. The change to add the new field is still committed against uat
but the new feature branch we create will not have that change in its history.
When we start work on this new feature, we go through the same process:
This time, the reverse merge from uat
into the promotion branch will include the change to add the new field from the first feature. If the first feature had been promoted all the way through to main
before feature2
was created then feature2
would already have that change, but because it didn't get any further than uat
, it will be added to future promotion branches by the reverse merge.
When do reverse merges happen?
Gearset will only add the reverse merge commit if at least one file has been modified on both the source and the target branch of the PR.
For example, if there are 2 feature branches that both add new fields to the same layout. The first is promoted all the way through the pipeline so that change is now in all environments. Now, when the second is promoted through, Gearset will add the reverse merge commits (or possibly flag up a merge conflict that needs to be resolved, depending on the precise details of the changes)
Why reverse merge at all?
When we complete a PR/MR, the source branch (the *_-_uat
branches in the example above) get merged into the target branch (uat
in the example above).
This merge happens on the VCS provider's servers using a standard git merge process.
Git merge is a fantastic tool. It is totally generic and can be used to merge any type of data. However, this means that it can make sub-optimal choices when merging specific types of files.
This is where Gearset's Semantic merge driver comes in. It is written with the knowledge that it will only be used for merging XML files containing Salesforce meta data. (Semantic merge will pass Apex code and other file types through to the standard git merge.)
Unfortunately, it is not possible to insert this merge driver into the workflow for when a PR is merged as that happens on the VCS provider's servers as described above.
Instead, we need to do the merge locally on Gearset's servers. We don't want to be pushing commits to the long-lived branch, so the only option here is to perform the reverse merge onto the promotion branch and then push that change to the VCS to become part of the PR/MR.
Why do my long lived branches have these changes in them?
There are 2 reasons that the long lived branch might have changes that are not in the feature branch:
Other features are flowing through the pipeline normally, but:
Those features were not in
main
when your feature branch was created andThey were already in, or have since been added to, intermediate environments.
Features are abandoned after being promoted part way through the pipeline.
The first case above should be considered normal operation. In this situation, the reverse merge is making sure that the changes you've made will not cause conflicts with the changes that others have made to those intermediate environments since you took your feature branch from main.
The fact that these changes then appear as part of the changes in the promotion branch is annoying in some cases, but it is necessary to allow us to use Gearset semantic merge, which is in turn necessary to avoid validation errors following standard git merges as described in What is Semantic merge.
The second case is also expected, but will add noise that will increase over time as more and more changes are abandoned part way through. We'd strongly recommend avoiding this if at all possible.
If you must abandon a feature part way through then we would recommend updating the feature branch so that it makes no changes and then promoting that through from the start of the pipeline. See this article for more guidance about making changes to a feature that is already partially propagated.
How can I fix it?
If you are ever working on more than one feature at once, then this noise from the reverse merge is inevitable, as described above.
The reverse commit is simply making sure that your promotion branch is up to date with the target environment, as there may be some drift between the target environment and the main branch in your pipeline.
I.e. The reverse merge commit will not normally change the target branch (unless there are merge conflicts) and will not affect subsequent pipeline environments. As such, it would not be unreasonable to ignore the specific contents of that commit as long as there are no conflicts.
However, if you have had a lot of abandoned features in the past and want to tidy up your branches, then there are a couple of routes to do so:
Delete and recreate
Delete the affected environment, refresh the Salesforce org and delete the VCS branch. Now, create a new environment branch from main
and reconnect to your pipeline.
This is a significantly simpler and less risky solution than the one below. However, it is potentially more disruptive.
⚠️ Any features that are currently in the pipeline between the affected environment and the main/production environment will be removed from the affected environment following this operation. Once those features have reached main/prod, the sync environments operation within Gearset can be used to restore them.
Push changes forward to update git history
This method aims to ensure that main
and future feature branches have these changes represented correctly in their git history, therefore avoiding future cases:
Try to ensure that all changes in the affected environment are either:
Changes that have been discarded in the past and are unimportant.
Changes that you are happy to promote to through to main/production in the very near future.
Create a new 'sync' branch from the affected branch. In the example above, we would create a
uat-sync
branch fromuat
Create a second sync brach from main (of whatever your base branch for features is. e.g.
main-sync
)Use the VCS to create a PR/MR merging
uat-sync
intomain-sync
Modify the
uat-sync
branch until the PR/MR is only making the changes that you would want to see inmain
.⚠️ This may involve reviewing a very large amount of changes. Care is required at this point to ensure that the changes will impact downstream environments in the correct way. If you are unsure about this, we would recommend the delete/recreate mechanism in order to avoid potential damage to features currently in-flight.
Complete the PR/MR
Create a new PR/MR merging
main-sync
intouat
Complete it as normal and promote the change through the pipeline
Once it's in
main
, any new feature branches should not suffer from this problem