14

I'm working on a PHP app, using three main branches: 'master' (current stable release), 'bugfix' (for patches) and 'next' (for next minor version).

We store the current version number in the code - it's used for a few things including checking for updates to the app, and also simple cache-busting for CSS/JS files (like styles.css?v=1.2.3) which helps when updating.

My problem is regarding merge conflicts. Say I have version 1.2.3, and 'next' is branched off there to start work on version 1.3. I change the version in that branch to 1.3.0 or 1.3.0-dev. Then I need to fix a bug in the 1.2 line, so the 'dev' branch is patched and version 1.2.4 is released.

But now when I merge the changes into the 'next' branch I get merge conflicts, because the same line was edited in both branches. Are there any strategies to avoid this? Or should I just resolve the merge conflict and move on?

I've seen a few answers across SE that talk about using git tags (which we do use for releases) but I don't think that really helps my situation since we need the version number in the code.

1
  • 2
    There might be branching strategies that prevent these conflicts on the version number, but on the other hand it is a very easy conflict to resolve. Commented Mar 12, 2017 at 7:33

6 Answers 6

6

Read the version from metadata generated at build time

One thing you can do is to read the version from your Git tags at build time. Generate the version from your git tags after commiting, as a part of the release process. After determining the version at the beginning of the build process, store it as metadata in a text or key/value pair file, in a location with other build artifacts or metadata that won't get stored in your repo (it should be generated as a part of the build, after a commit has been made, to the local working directory only).

The version is therefore metadata that is a build artifact regenerated every build rather than stored in the repo. It is still available to your code because the file is generated at build time, before final linking or storage of external resources into your DLL or executable. Therefore, the final executable has access to this version information at runtime.

  • The advantages of this approach:
    • You have a single source of truth - the git tags
    • You are still able to access the version in your code
    • You do not need to store the version directly in the source code, so there are not merge conflicts
  • The disadvantages are:
    • You still need to resolve the version conflicts - just it happens during the tagging process rather than as a merge conflict
    • During development, you must ensure that development builds have tags in the repo or a means of generating them in the metadata, and that metadata must be regenerated for each build (or in-between official builds for development, if necessary).

To accomplish this, you will likely need to create a script to generate the version file or a value/pair entry file (JSON, etc.). As a part of your build process, run the script, then run the rest of the build.

After a build, you will have the metadata file available, so if the source is run in a script environment, it can access the version, but it will always be the old version from the last build.

For example, in the base Python Packaging module, setuptools, there is an extension available called PBR that uses this method. It autogenerates the version info from git tags, including automatically incrementing based on a rule for every commit that doesn't have a tag. It then writes the version info into an xxx.egg-info/PKG-INFO file that is a standard metadata file for Python packages. There are a functions to pull the version and other metadata info if needed in the live scripts. These files are typically included in .gitignore, so they must be regenerated as a part of a package release or running tools that generate an executable.

3

The first thing is to read up and understand Semantic Versioning.

The next trick is to look into your build process and your version control system so that your build process automatically adds the appropriate tag(s) to the version based on which "branch" you are building from.

If you are happy using a little python you can generate the version value, as either an environment variable or as a generated include file by or whatever the equivalent is for the languages in use using the bindings available for most version control systems. You can also do similar tricks with bash scripts, etc., but I find the python solution a lot cleaner and more maintainable.

Personally I also set a flag if a build includes any uncommitted changes.

3

Version numbers are not quite part of the code. I'd rather say it is meta information and ideally shouldn't be in the repository with the source code. It should be added only in the build phase. But the world isn't an ideal place, so sometimes you need to have the version hard-coded.

For these cases, it is expected to have version conflicts when merging different versions. This is fine and shouldn't make you uncomfortable.

In these cases, I would minimize the number of places where the version is stored. Ideally, it is only one specific file that contains only the version and nothing else.

You can solve these conflicts manually, but the better decision would be to write some custom merge driver to solve this type of conflicts automatically. Example of such a driver is in this answer.

2

What does a version number represent?

A release is the output generated by following a defined process using build tools on source code.

So, if the release process or build tools change, even when used on the same source code, then the release should be considered a different version. It is because of this, that the version number should be managed by the CI/CD pipeline, and not within the source code, and should be generated as a unique label each time the the release process is performed.

Who / What cares about the version number?

Which version of the Google search engine did you use to find this question on Stack Overflow?

Okay, you probably don't know (or care), but I'd bet my house that Google knows.

The key thing to think about is the audience of the version numbers. For an internal release, branch.build_number.commit_hash contains the key information. From the branch name you know the maturity of the release, the build number allows you to determine relative age between two releases, identify build parameters and tooling used from the CI/CD pipeline logs, and the git commit hash allows you to identify the source code used. If your product is your website, this format can also be used on your production releases too.

However, if you are releasing a series of discrete version of the application over time for other organisations to use as a component of their product, then your customers should be version aware. Upgrading your component version in their product should be something they consider as a change to their product that needs to go through their test and release process. Using version numbering to provide information about the significance of the changes is a common approach. For example, major.minor.patch[.build] is a common format for public releases.

  • Major version number changes, customers should expect significant changes to the product that have the potential to break existing behaviour.
  • Minor version number changes signal that there are feature enhancements, but not breaking changes.
  • Patch version number changes should signal that only internal implementations have changed. Changes that are visible to customers should be limited to fixing product defects.

The key message here is make sure that your release labels are relevant to their audience and infer useful information about their content.

What about if my code needs to know its version number?

Code should know its version number, and be able to report it, but it should not care about what its version number is.

As part of the CI/CD build process, I would expect that the version number is embedded into the output artifact, allowing full reporting and traceability of defects and functionality. However, code deriving information from its own version label, and changing its behaviour as a consequence is a risky design pattern.

How should i store the current release number in Git, and not get merge conflicts?

If the reason for storing the release number in Git is because you release from the Developers local environment, then you have bigger problems than version numbering. Set up CI/CD first.

In Git, define the release label format, and the script that populates the version string and embeds the value into the build output. Ideally this should be part of your build scripts repository and not your source code repository.

e.g.

release_label.yaml

---
internal_release_label_format: "{branch}.{build}.{hash}"
release_label_format: "{major}.{minor}.{patch}.{build}"
1

Put the version number in its own file and don't ever merge that file.

1
  • You can even merge that file - merge conflicts will be very easy to handle, because there is just a single line of code.
    – gnasher729
    Commented Jan 26, 2020 at 12:51
0

I have that problem quite a lot, and it takes ten seconds to resolve the conflict. So it's not something I worry about.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.