We have three branches:
- master
- staging
- dev
We branch per issue and each issue needs to go through testing and staging before being merged to master.
We branch feature branches from master and work on that feature.
- When ready for testing, we merge the feature branch into dev branch and TeamCity deploys to test environment.
- When ready for staging, we merge the feature branch into staging branch and TeamCity deploys to staging environment.
This involves a lot of merge commits on dev/staging branches, but very few on master, because the feature branches are always branched from master.
It’s always a merge commit merged with a feature branch, so this is a merge from a merge, from a merge, from a merge, etc… So at any time TeamCity has 100+ pending changes, even though there is only 1 changed file when doing:
git diff dev~1...dev
Is there some way to minimize these merge commits?
Are we doing Git wrong?
Your workflow seems similar to the “Git Flow”, except that you start feature branches from master
, whereas the Git Flow recommends branching from dev
.
The idea is that dev
closely reflects the current development state. Feature branches are supposed to be very short-lived (at most a couple of days of development). So when a feature is branched from dev
and merged back into dev
a day later, there will be very few conflicts. The dev
branch would be used for Continuous Integration.
Git Flow then suggest branching a release branch (your staging
) from the dev
branch with multiple new features for QA and release preparation. On release, the code is merged into master
so that master
only contains fully tested published versions.
If we were to branch new features off master
(i.e. the last published version), this would ignore all other features that have been merged into dev
in the meanwhile. Although the diff between dev^
and dev
might be rather clean, there would be vast differences in the history:
Am B..Zm
master ----x------------------------------------x
/ /
staging --x---|------------------|-------------x B..Zs
/As | B C..Y | Z /
dev x-----|---------x--x--x--|-----------x
A / / / | /
featureB x--x--x ..... | |
B1 B2 B3 | |
... | |
/
featureZ x--x--x
Z1 Z2 Z3
At merge B
into dev (A)
from featureB (A As Am B1 B2 B3)
, the merge is rather clean. We then add a couple of other features C
through Y
into dev
. After that, the history of dev
looks like A As Am B1 B2 B3 C1 C2 C3 ... Y1 Y2 Y3
. That still looks reasonable. Finally, we have the featureZ
branch with the history A As Am Z1 Z2 Z3
– note that Am
is the nearest common ancestor commit of dev
and featureZ
.
The result is that at merge Z
, we do not only test the effect of the commits Z1 Z2 Z3
, but the effect of combining all the features B1 B2 B3 C1 C2 ... Y2 Y3 Z1 Z2 Z3
.
The impact of this problem is reduced by frequent releases, so that the difference between dev
and master
is not that pronounced. However, this only makes the problem smaller, and does not solve it. TeamCity seems to realise that it’s actually testing all the changes you made since you last merged into master, although these are not obvious in the branch history (since dev
only contains merge commits).
I recreated the above branching example as an actual git repo, which is available as latk/Git-Branching-Madness on GitHub. You can look at the branch graph online, or clone the repo and inspect it yourself, e.g.:
# tip: use "git log" with "--pretty=oneline"
$ git log featureZ # history of featureZ
$ git log dev^ # complete history of dev before featureZ merge
$ git log dev^..dev # what commits were added during the featureZ merge
$ git log master --graph # display complete branching history
1
Merging multiple times should not be a problem, and that’s actually a normal way of working with Git.
Your team agreed to have a specific workflow. For small fixes, that may induce some overhead, compared to, e.g., merging directly on master. But this is okay, at least you have a process to perform continuous integration.
Now, I don’t know TeamCity, but maybe you should focus on why it has 100+ pending changes when the diff is actually small.
1
One option is to frequently git fetch
and git merge
/ git rebase
master during development and qa in order to keep up with the changes and resolve conflicts as they happen. I prefer git rebase which replays your changes on top of the lastest master