For version as git tag: use annotated tag, not 'v' prefix
David Bernard
Posted on March 28, 2021
Tagging commit with meaningful version number like major.minor.patch
(see Semantic Versioning 2.0.0 | Semantic Versioning) is a common way to name a source tree.
Why using 'v' prefix is not useful ?
A common practice is to prefix tags for version with 'v' like v1.0.0
, but I don't understand why?
It is not to easily identify tag that refer to version with the pattern v*
. Because this pattern also grabs non version tag that started by the letter v
. So need to used a more complex pattern like v*.*.*
or to combine a tool that support regexp.
# accept v1.0.0 and 1.0.0
git tag -l '*.*.*'
# combine with grep to have a more selective pattern
git tag -l | grep -P '\d+\.\d+\.\d+'
If you use tag only for version then "why using a prefix ?" and if not then version tag will be potentially mixed with tag starting with a letter (a...
,b...
, ..., v...
, w...
). Without the 'v' prefix, digit will be before or after in alphabetical order, for time based order no change.
An other issue comes if you use the tag to extract the current version of a source tree, you need to remove the prefix.
So without v
prefix you can do something like (github-actions Filter pattern cheat sheet):
on:
push:
tags:
#- 'v*.*.*'
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Test
run: |
echo $RELEASE_VERSION
echo ${{ env.RELEASE_VERSION }}
Why annotated tag ?
Annotated tag are like "heavyweight" tag, meaning that like a commit they also include a message and an author. IMHO, they also are a prefect way to mark the importance of version number and to isolate them from lightweight tag.
# create an annotated tag
git tag -a "0.1.0" -m ":bookmark: 0.1.0"
When using annotated tag only for version, it also means that you can use git describe
to know the "semi-semantic version" of the current working tree (without need to apply filter).
❯ git describe -h
usage: git describe [<options>] [<commit-ish>...]
or: git describe [<options>] --dirty
--contains find the tag that comes after the commit
--debug debug search strategy on stderr
--all use any ref
--tags use any tag, even unannotated
--long always use long format
--first-parent only follow first parent
--abbrev[=<n>] use <n> digits to display object names
--exact-match only output exact matches
--candidates <n> consider <n> most recent tags (default: 10)
--match <pattern> only consider tags matching <pattern>
--exclude <pattern> do not consider tags matching <pattern>
--always show abbreviated commit object as fallback
--dirty[=<mark>] append <mark> on dirty working tree (default: "-dirty")
--broken[=<mark>] append <mark> on broken working tree (default: "-broken")
My favorite arguments are:
git describe --always --dirty
because :
- if on the exact commit with the annotated tag, returns only the tag value (eg
0.1.0
) - if after an annotated tag, returns
<last_annotated_tag>-<number_of_commit>-g<last_commit_short_hash>
(eg0.1.0-2-ge453fae
) - if no annotated tag, then output the short commit-hash (so never empty output, always a (non-semantic) version)
- if uncommitted change in the working tree then append
-dirty
(ignore untracked and ignored files)
❯ git init
❯ touch README.md
❯ git add README.md
❯ git commit -m "add README"
[development (root-commit) 8edcbdc] add README
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
❯ git describe --always --dirty
8edcbdc
❯ git tag -a "0.1.0" -m ":bookmark: 0.1.0"
❯ git describe --always --dirty
0.1.0
❯ echo "toto" > README.md
❯ git describe --always --dirty
0.1.0-dirty
❯ git commit -a -m "modify README"
[development 6cfe247] modify README
1 file changed, 1 insertion(+)
❯ git describe --always --dirty
0.1.0-1-g6cfe247
❯ echo "toto 33" > README.md
❯ git describe --always --dirty
0.1.0-1-g6cfe247-dirty
So you have an unique version number for any state of your working tree, and more human friendly than only the commit hash.
If using a build tool that can compute version number (instead of declarative like in a toml or xml) you can use this command to always have the unique and current version. By example for Gradle's based build, you can define the version as :
# build.gradle
version = "git --no-pager describe --always --dirty".execute().text.trim()
Tips
Create the first annotated tag as soon as possible, maybe after your initial commit you can tag the version 0.0.0
if you don't know when 0.1.0
will be created.
Annotated tags can be push with commits in a single command via
git push --follow-tags
Last words
In fact their is no opposition, you can stop using v
prefix without using annotated tag OR using the v
prefix with annotated tag is possible, but the better combinason (IMHO) is to not use v
prefix and annotated tag for version.
Posted on March 28, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024