Update 2018-11-24: I’ve put together a maven plugin that can also publish git tags in Bitbucket Cloud, plus it checks there are no gaps in a semver sequence. It’s available here.

I wrote a small script today that I wanted to share. It runs for Maven builds in Bitbucket Pipelines and it creates a new git tag based on the version in the pom.xml. I only run this in the master branch for projects that need versioning (like parent poms and maven libraries).

First of all I need to read the version from the pom file. It would be great if we had standard CLI tools, like grep and sed, which work with XML, JSON and YAML files. Trying to extract the version of the pom file using bash kung fu is probably not a good idea (although if you’re determined enough you’ll manage). Instead, since this is a maven project anyway, I used a maven plugin. Here’s the bash function, filtering out some maven noise:

function getPomVersion {
    # Uses a maven plugin to print the version of the pom.
    # Filters out Download messages of maven and [INFO] log messages.
    mvn org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version | grep -v '\[' | grep -v 'Download'

Next, I’d like to check if the git tag already exists. The obvious solution is to use the git CLI and run some commands. First of all, I’m running these scripts inside Bitbucket Pipelines, which means I’m running them inside a Docker image, which doesn’t have the git program installed. But even if it did, I have had some bad experience in the past trying to run git commands within a CI server. The CI server (depends on the product of course) might not use git the same default way a developer does. I have run into surprises in that area, running commands only to find that they fail with weird error messages.

So, the git CLI is out of the question. Instead, I found that the Bitbucket Pipelines REST API is easy to use (and curl is most of the time included in Docker images).

Here’s the code that checks if a tag exists:

function ensureGitTagDoesNotExist {
    # Ensures that the given tag does not exist in Bitbucket.
    OUTPUT=$(curl -s -u ${SUPER_SECRET} https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/refs/tags?q=name+%3D+%22${TAG}%22 | tr -d '\r\n ')
    if [[ "$OUTPUT" == *"$TAG"* ]]; then
        echo "Tag $TAG already exists"
        exit 1
        echo "Tag $TAG does not exist"

The variable SUPER_SECRET needs to contain the username and password (separated by a colon) of a user who has access to create git tags in Bitbucket. The BITBUCKET_REPO_OWNER and BITBUCKET_REPO_SLUG are default variables in Bitbucket Pipelines.

The function gets all tags by the given name. If that name is present in the JSON response, then the tag exists. If it’s not there, we got back probably some JSON like values: [], so we conclude the tag is missing.

Combined, we have this tiny script that will break the build if the pom version already exists as a git tag:

ensureGitTagDoesNotExist v$POM_VERSION

Following the common convention, I prefix the git tags with the letter v. So if the pom’s version is 1.2.3, the git tag will be v1.2.3.

The last part is to actually create the tag. That’s done with another API call:

curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/refs/tags \
    -s -X POST -H "Content-Type: application/json" -u ${SUPER_SECRET} \
    -d "{ \"name\" : \"v${POM_VERSION}\", \"target\" : { \"hash\" : \"${BITBUCKET_COMMIT}\" } }"

In addition to the variables mentioned above, the BITBUCKET_COMMIT is a built-in variable which contains the git commit id.

Action Branches When
Ensure the git tag does not exist All branches First step in the build
Publish a new git tag Only master branch Last step in the build

Perhaps it’s worth noting again that (at least for now) I only version things that are dependencies of other projects: libraries and parent poms. I don’t version services, as nobody uses them as a version dependency.