Proper way to merge dev into main branch
I am working in a company and we decided that we want to use git more properly. I am responsible for integrating dev branch into main branch, and I am a confused as to how to correctly merge in different scenarios.
How we work now: developers would simply rebase their feature branches upon dev
branch. Once finished they'd submit a merge request to dev
branch. Then, I would check the code (We are a small team), and accept merge request. After that they'd simply delete feature branch. When dev
branch was properly tested, I would create a merge request to main branch in GitLab, assign it to myself and approve it. This creates a commit on the main
branch:
Merge branch 'dev' into 'main'
However, now the dev
branch is one commit behind main
. How do I best fix this? Up untill now I would simply merge main
into dev
again but this seems cumbersome and strange as their is now another commit in my dev
branch;
Merge branch 'main' into 'dev'
I could also rebase dev on main but I have learned "thou shallt never rebase public branches"
This SO question tells me the proper way is to first merge main into dev, to make sure main branch stays clean. Would this be the best way of achieving dev --> main
merges?
And is any of this influenced by the notion that git fast-forwards merges by default? I read that a fast-forward merge occurs when there is a linear path from the current branch tip to the target branch. This would imply that, when the dev
branch is simply ahead x
of commits to main
when merging, and no commits were made to main in the mean-time (as is common in our work since we technically only commit to main
from dev
), the merge would automatically be a fast-forward merge? Is this behavior problematic for merging dev
into main
, and should I use the -no-ff
flag when merging?
If I get it correctly, a fast-forward merge integrates the dev
commits into the main
branch, whereas a --no-ff
merge causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward (Source: This SO question). Does this mean that a ff merge does not create a commit, when dev is simply ahead x
commits to main? How would I then push the merge upstream after merging branches locally? Should I even merge dev and main locally or simply keep using gitlab web interface for this?
Needless to say, I am very confused by all this. Would be great if somebody could clear things up.
Solution 1:
Your understanding of what git merge --no-ff
does is nearly correct. The part that perhaps needs tweaking is your use of the word integrates for just fast-forward, whereas it's important to realize that whether you allow fast-forward to occur, or if you use --no-ff
to force creating a merge commit, the resulting file structure is identical. In both cases all of the commits are "integrated" and you can see them in both branches with the same commit IDs. Forcing the merge commit affects the graph of the branch history. Note that after a fast-forward merge, the branches are identical which means that both branch names are pointing to the same commit ID. If fast-foward was possible but you use --no-ff
to create a merge commit instead, one of the branches points to the merge-commit and will be, as you noticed, one commit ahead of the source branch. Which leads us to your question:
However, now the
dev
branch is one commit behindmain
. How do I best fix this? Up until now I would simply mergemain
intodev
again but this seems cumbersome and strange as their is now another commit in mydev
branch:
Two observations here:
- Actually, if you merge
dev
intomain
and thenmain
back intodev
before any new commits appear ondev
, then a fast-forward back intodev
would be possible and you would not have to generate that new merge commit (with message "Merge branch main into dev") if you didn't wish to. If you allow the fast-forward merge then afterwardsdev
would point to the same commit asmain
which would be the merge commit with the message "Merge branch dev into main". If you choose to use--no-ff
for that merge then of course you will create a new merge commit. - Let's assume the merge commit is there, either because new commits snuck into
dev
between the two merges, or because you used--no-ff
and forced it. So what? If you force the merge commits (like the default Git Flow recommends for example), then yes, you will have merge commits, and this meansdev
andmain
will never be identical, and that's totally fine.
Now let's burn through some of your other questions:
This SO question tells me the proper way is to first merge main into dev, to make sure main branch stays clean. Would this be the best way of achieving dev --> main merges?
That's up to you. In a Git Flow model, if new commits appear in main
you normally would want to merge main
back down to dev
ASAP so you aren't testing something different than what you will deploy. (Or worse, if you deploy from dev
or a temporary release
branch, that you don't blow away hotfix changes in Production from main
because they weren't in your release
branch yet.) So, it isn't really the case that you need to merge main
into dev
before merging dev
into main
. Instead, if hotfixes appear on main
you should get those merged down into dev
shortly after. Then dev
will always be ready to go for merging into main
.
And is any of this influenced by the notion that git fast-forwards merges by default? ... when the dev branch is simply ahead x of commits to main ... the merge would automatically be a fast-forward merge?
Yes. Fast-forward merging is the default, when it's possible.
Is this behavior problematic for merging dev into main, and should I use the -no-ff flag when merging?
It is not problematic. Whether you should force the merge commit is up to you. There are some pros and cons. Taken from my answer to another question:
The merge (with --no-ff) forces a merge commit, and this is helpful because each PR contains the list of commits associated with just that PR, enabling you to view the first-parent history which shows all merges into the branch, and easily compare them. Another benefit of forcing the merge commit is that it's easy to revert an entire PR by simply reverting the merge commit, rather than individually reverting every commit that was in the original PR.
Note that GitLab calls a "Pull Request" a "Merge Request", so you can just substitute "MR" where you see "PR" in the above paragraph. The only con of forcing a merge commit is: If you don't care about any of those advantages just mentioned, then it adds unnecessary complexity to the resulting graph.
Lastly, you asked:
How would I then push the merge upstream after merging branches locally? Should I even merge dev and main locally or simply keep using gitlab web interface for this?
It doesn't make a difference if you don't have branch protection enabled. If you turn on branch protection/policies for your dev
and main
branches (in GitLab), then you will need to use the Merge Request functionality to perform those merges. When you complete the Merge Request you can select whether you want fast-forward or to force a merge commit. If you do it locally, then you simply have to push out the branches to your remote server (GitLab), if you have permission to do it. Either way the end result is the same.
Solution 2:
I would like to focus my answer on one part of your question as you are asking multiple questions at once. You could ask yourself whether it is a good idea for that project/team to even have a development branch. Maybe you will be more productive and/or have the same code quality and/or maintenance capabilities without it. See e.g. this SO question about the benefits of having a development branch.
Regarding local vs Gitlab/Github merging: I would alwasy try to merge with Gitlab because it allows to discuss the changes and send links about the merge (request) to other people more easily. Furthermore, it will allow you more easily to rely on the CI before the merge will actually happen, so to merge only when the pipeline succeeds (see e.g. this Gitlab Documentation).