3

Imagine we use Gitflow where we have split a release branch from develop that will eventually be merged into both main and develop. On release there are only quality improvements. Most of these require a deployment to an integration stage, so their version in several pom.xml (multi-module) and package.json is updated and tagged on the release branch.

On develop there is regular (unstable) feature development for future releases, and the version has been set accordingly. Occasionally the improvements from release are merged back into develop. There we get merge conflicts, marked with an X in the following illustration.

main     ----------------------o----
                              /
release        o---o-----o-o-o
              /     \     \   \
develop  ----o---o---x--o--x-o-x----
                           ^
               we are here |

Example:

  • On release the version number is 1.0.0-SNAPSHOT.
  • On develop the version number is 1.1.0-SNAPSHOT after branching off.
  • New features go into develop, the version number stays constant there.
  • The version in release is incremented (and tagged) occasionally to 1.0.1, 1.0.2, 1.0.3 and so on.
  • Now of course there is a conflict when I want to merge version 1.0.x into 1.1.0 while the common ancestor is 1.0.0.
    • (We fully understand what happens there, don't need explanation for that.)
$ git checkout develop
$ git merge --no-commit --no-ff release

Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
...
Auto-merging client/package.json
CONFLICT (content): Merge conflict in client/package.json
Automatic merge failed; fix conflicts and then commit the result.

We are looking for ideas to handle this situation. Some research shows that this is not an uncommon problem, so I found several suggestions. Mostly it is said to just manually resolve the conflicts. But I am still eager to find a way that can be automated in a script. Maybe there is some Git magic to help? Maybe we started badly in the first place?

The following discussion brings a better image to it, where we are at "Only bugfixes!":

Approach 0 -- Do not do this?

Since our team started doing this kind of version increments and tagging, I've been unsure if this is a good practice. But it basically works, we defined the details of the workflows together, and our customer and partners and test team require us to deliver release candidates as if it were actual releases. When a version x.y.z has been tested successfully, it goes to productive environment unchanged and then release is merged into main. But the problem stays: As soon as a hotfix is made in main and should be backported to develop, we are going to get version conflicts again.

Approach 1 -- Cherry Picking?

Sorry, I won't do this. Too often I read that cherry picks are evil. And they would be against Gitflow.

Approach 2 -- Accept to resolve manually?

That is what we do now. The process is not automated. Every time the version number in release is changed, the following merge gets a conflict that has to be manually resolved. We accept it but are unhappy with that.

Approach 3 -- Do not merge that often?

I guess that would be bad practice. We want to have the quality improvements merged to all our branches.

Approach 4 -- Use merge option --ours or similar?

The problem is that automated "resolution" of merge conflicts is file-based, from what all I could find out, not line- or block-based. We need to keep the version number from develop, but other changes in those files pom.xml or package.json might be on either side and not to be overridden blindly, so these kind of conflicts we want to see and resolve manually. I am open to any suggestions in this direction though!

Approach 5 -- Move version number to separate file?

This way we would reduce the conflicts to one single location where it can be trivially resolved using --ours. While it seems to be possible with newer Maven versions, I am not aware of an approch for package.json to refer to an externally defined version number. Has anyone made good experience with that and would recommend to go further this way?

Approach 6 -- Prepare and reset version in develop?

I saw such behavior by jgitflow-maven-plugin which is not maintained for over 6 years now. We could make a commit in develop, writing the release version into the files, then merge, and change the version back to the original one.

I dislike that there would be additional commits that have nothing to do with actual development, and I see no possiblity to clean up the Git history.

So this would be an interesting follow-up question: I know I can rebase/squash D into C, but I don't see how I can rebase/squash A or B into C. Does anyone else?

-----B---------
      \
---A---C---D---

Approach 7 -- Prepare version in release?

Similar to the previous approach, we could make a commit in release, write the target version, then merge to develop without conflict. We would then not need a revert commit in release but could just move the branch pointer back with git reset --hard HEAD^ and/or simply not push it, so this preparation commit would be located "between" the two branches.

-----B-----------
      \
       B'
        \
---A-----C---D---

The following article describes a similar thing using an intermediate branch (to fulfill the requirement for a pull request), but it is several manual steps that don't solve my challenge.

Approach 8 -- Prepare version without commit?

My favorite solution would be to just write the target version in local develop without commit, then merge release onto that... but git merge does not allow this. I do not see any switch to override this behavior and ignore unmerged

error: Your local changes to the following files would be overwritten by merge:
        client/package.json
        ...
        pom.xml
Please commit your changes or stash them before you merge.
Aborting

Searching the web tells me to stash the local changes, but that is not an option of course.

Approach 9 -- Write program to resolve conflicts?

I play with the idea that these conflicts are well-structured and can even be fully predicted, so it should be possible to write a small shell script to grep/sed the conflicts in order to automatically resolve and commit the merge. But I hesitate to put large efforts here and hope for enlightenment by other people!

General Grievance
  • 4,259
  • 21
  • 28
  • 43
Lynax
  • 63
  • 2
  • 7

2 Answers2

1

Note: "Best Practice" for something like this is difficult to define, since everyone's situation is likely different.

That being said, one of our projects has a similar situation as yours: we use Git Flow, and our develop branch build numbers are always different than the release branch build numbers. Our potentially ideal solution has not been implemented, but would likely be similar to your suggested Approach 8, where we would inject the version into the build pipeline without it being hard-coded in a commit (i.e. don't even modify the version file at all). The downside of this though is you can't know what version is represented by a specific commit based on code alone. But you could tag the commit with a specific version, which is probably what we would do if we implemented that. We could also bake the commit ID along with version info into the artifact meta data for easy lookup.

The solution we currently use is a combination of Approaches 4, 5, and 7. We separate version files (your Approach 5), and every time we create a release (or hotfix) branch, the first commit only changes the version file to the upcoming release version (your Approach 7). We make sure that release always has the tip of main in it, so that anytime we deploy release to production we can cleanly merge release to main. (Note we still use --no-ff as suggested by Git Flow but the point is we could fast-forward if we wanted to.)

Now, after you complete the release branch into main, Git Flow suggests merging release back to develop, but we find merging main back to develop slightly more efficient so that the tip of main is also on develop, but occasionally we also merge release back into develop before deployment if important bug fixes appear on release. Either way, both of those merges back to develop will always have conflicts with the version files on develop, and we use your Approach 4 to automate choosing the develop version of those files. This enables the merge back to be fully automated, however, sometimes there are still other conflicts that have to be resolved manually, just as a course of normal development happening on develop and release simultaneously. But at least it's usually clean.

Note that a side effect of our approach is that our versions files are always different on develop and main, and that's fine with us.

TTT
  • 14,202
  • 7
  • 53
  • 56
0

What about using an external tool to manage the version? We use GitVersion for this. Now I am not sure if there is a smarter way, but a brute-force one is to have something like this <version>${env.GitVersion_SemVer}</version> in your pom.xml, where env.GitVersion_SemVer is an output from GitVersion.