Nov
29

Increment project version from Travis

posted on 29 November 2016 in programming with 0 Comments

Can you effectively use GitHub and Travis for continuous delivery? You sure can, but managing the version number can be difficult, here’s a way to automatically increment the patch number on every deployment from Travis.

We use a shared repository model for our JavaScript project on GitHub, the workflow goes something like this:

  • The master branch is always the currently deployed production code
  • When someone wants to add a new feature or fix a bug, they create a branch (both locally and on GitHub)
  • Travis builds all commits to all branches and runs the continuous integration test suite
  • Once development is complete and tests pass they open a Pull Request
  • Once someone has approved the Pull Request (we love the new functionality - thanks GitHub!), they then merge and squash onto master
  • Travis builds all commits onto the master branch, if tests pass it deploys the package to a private npm repository and deploys the artefacts to S3

The trick is that the version number in the package.json file must be updated on every commit to master otherwise npm will reject it (rightly so). We used to manually update this before merging the Pull Request by doing an npm version patch, but people forget to do this or they do it too early and get merge conflicts.

We now automate incrementing the version by using git tags. Before Travis builds the project, it pulls the latest git tag, then compares the major and minor version (x.x.0) to what’s in package.json. If they match it increments the patch version (0.0.x) and creates a new git tag and updates the package.json file, if they don’t match we assume the developer has incremented either the minor or major version and so it resets the patch number to 0. At the end of a successful build on the master branch, the tag is pushed back to GitHub. This ensures a nice history of every commit on master having a git tag of the version number, which supports our continuous delivery workflow.

Here’s the relevant .travis.yaml entries to make this work:

# This is needed to avoid building all the tags pushed by travis
branches:
  except:
- /^v?\d+\.\d+\.\d+$/

before_install:
  # Create a git tag of the new version to use
  # If package.json major and minor versions match last tag, then increment last tag. Else use package.json major.minor.0.
- "{ sed -nE 's/^[ \\t]*\"version\": \"([0-9]{1,}\\.[0-9]{1,}\\.)[0-9x]{1,}\",$/\\1/p' package.json; git describe --abbrev=0 | sed -E 's/^v([0-9]{1,}\\.[0-9]{1,}\\.)([0-9]{1,})$/\\1 \\2/g'; } | tr \"\\n\" \" \" | awk '{printf($1==$2?\"v\"$2$3+1:\"v\"$1\"0\")}' | xargs -I {} git tag -a {} -m \"{}\"\n"
  # Update package.json based on the git tag we just created
- npm --no-git-tag-version version from-git

# Push the new tag back to GitHub but only if on the master branch
deploy:
- provider: script
    skip_cleanup: true
    script: git push --tags
    on:
      branch: master

Let’s decompose that bash one-liner:

  1. Read the current major and minor version from package.json (e.g. 1.2)

     sed -nE 's/^[ \\t]*"version": "([0-9]{1,}\\.[0-9]{1,}\\.)[0-9x]{1,}",$/\\1/p' package.json;
    
  2. Get the latest git tag (e.g. v1.2.43)

     git describe --abbrev=0
    
  3. Split out the major and minor version and the patch version into separate parts (e.g. 1.2 43)

     sed -E 's/^v([0-9]{1,}\\.[0-9]{1,}\\.)([0-9]{1,})$/\\1 \\2/g';
    
  4. Take the output of 1. and 2. and create 3 space separated parts which are (in order) <major.minor from package.json> <major.minor from git tag> <patch from git tag>

     tr "\\n" " "
    
  5. If part 1 and 2 match, just increment the patch version, otherwise use major.minor from package.json and reset patch to 0.

     awk '{printf($1==$2?"v"$2$3+1:"v"$1"0")}'
    
  6. Pipe the resulting version number into a git tag (e.g. v1.2.44)

     xargs -I {} git tag -a {} -m "{}"