Skip to content

Beginner · Git in depth · topic 14 of 53 · 30 min

Semantic versioning, tags and releases

Explanation

Semantic versioning gives every release a number MAJOR.MINOR.PATCH that tells users what changed: PATCH for bug fixes, MINOR for new features that do not break anything, MAJOR for breaking changes. Pre-releases like 2.0.0-rc.1 come before the final release. In Git you mark a release with a tag, and from Conventional Commits a tool can work out the next version and the changelog.

Why version numbers need rules

Your app depends on dozens of libraries, and other teams depend on your API. When a new version comes out, they need to know: is it safe to upgrade? Semantic versioning (SemVer) answers that with the number itself. A version has three parts, MAJOR.MINOR.PATCH, such as 2.4.1.

You changed...BumpExample
Only bug fixes, nothing new, nothing brokenPATCH2.4.1 → 2.4.2
Added features in a backward-compatible wayMINOR (reset PATCH to 0)2.4.1 → 2.5.0
Something incompatible: removed or changed behaviour users rely onMAJOR (reset MINOR and PATCH to 0)2.4.1 → 3.0.0

Version 0.y.z is for early development: anything may change at any time. Releasing 1.0.0 is a promise that you now follow these rules for your public API.

Think of it like this: Think of a train timetable. A PATCH is fixing a typo in the timetable. A MINOR change adds a new train; old trains still run as before. A MAJOR change cancels a train or changes its route, so passengers must replan their journey.

Pre-releases and build metadata

A pre-release adds a hyphen and labels after the patch number: 2.0.0-alpha, 2.0.0-beta.2, 2.0.0-rc.1 (release candidate). A pre-release is LOWER than the normal version: 2.0.0-rc.1 comes before 2.0.0. Labels are compared part by part: numbers as numbers, words alphabetically, so 2.0.0-alpha < 2.0.0-alpha.1 < 2.0.0-beta < 2.0.0-rc.1 < 2.0.0.

Build metadata adds a plus sign: 2.0.0+build.417 or 2.0.0+20261019.3f9c2ab. It records how the artefact was built and is ignored when comparing versions, so 2.0.0+a and 2.0.0+b count as the same version.

Careful: Compare versions as numbers, not as text. As strings, "1.10.0" < "1.9.0" because the character "1" comes before "9". As versions, 1.10.0 is newer. This bug appears in many home-made deploy scripts.

Version ranges in package managers

npm (and many other package managers) let you accept a range of versions in package.json. The two common symbols are the caret and the tilde.

RangeMeansAccepts
^1.4.2Same MAJOR, at least 1.4.21.4.3, 1.9.0, not 2.0.0
~1.4.2Same MAJOR.MINOR, at least 1.4.21.4.9, not 1.5.0
1.4.2Exactly this versionOnly 1.4.2
^0.4.2Below 1.0.0, the caret is stricter: same 0.40.4.5, not 0.5.0

npm install saves dependencies with ^ by default. The lock file (package-lock.json) then records the exact version installed, so every machine and every CI run installs the same thing. Commit the lock file.

Git tags

A tag is a name that permanently points at one commit, usually a release. Unlike a branch, it does not move. A lightweight tag is just a name. An annotated tag is a full object with the tagger, date and a message, and can be signed. Use annotated tags for releases.

bash
git tag v1.2.0                          # lightweight tag on the current commit
git tag -a v1.2.0 -m "Release 1.2.0"    # annotated tag (preferred for releases)
git tag                                 # list tags
git show v1.2.0                         # tag details and the commit
git push origin v1.2.0                  # tags are NOT pushed by default: push one...
git push --tags                         # ...or push all local tags
The v prefix is a common tag convention; the version itself is 1.2.0.

Releases and changelogs

On GitHub or GitLab, a release is a page attached to a tag, with release notes and downloadable files. A changelog (CHANGELOG.md) lists notable changes per version under headings such as Added, Changed, Fixed and Removed, newest version first, so users do not have to read commits.

The next version from Conventional Commits

If commit messages follow Conventional Commits, the next version can be computed. Look at all commits since the last tag. Any breaking change means MAJOR. Otherwise, any feat means MINOR. Otherwise, any fix (teams often count perf too) means PATCH. If there are only docs, chore or test commits, there is nothing to release. Tools such as semantic-release and release-please do this in CI, then tag, publish and write the changelog.

output
Last tag: v2.4.1
  fix(cart): keep coupon after login        → patch
  feat(orders): add "Order again" button    → minor
  docs: update README                       → no release
Highest wins: MINOR → next version v2.5.0

Common mistakes and how to fix them

  • Releasing a breaking change as a MINOR version: users with ^ ranges get it automatically and their builds break. If unsure, check whether old code calling your API still works unchanged.
  • Moving or reusing a tag after it was published. Never change what v1.2.0 points to; release v1.2.1 instead.
  • Forgetting to push tags, so CI never sees the release. Push the tag explicitly.
  • Forgetting to reset lower parts: 1.4.2 with a new feature is 1.5.0, not 1.5.2.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Semantic versioning, tags and releases · 1/7

Semantic versioning

Semantic versioning tells users from the number alone whether an upgrade is safe.

Prefer a video by a teacher? Find free videos on “semantic versioning explained git tags releases conventional commits” on YouTube ↗

In real life: A shared UI library at an Indian e-commerce company

Ten frontend teams use an internal button and form library published to the company's private npm registry. Each team depends on "^3.2.0". The library team uses Conventional Commits and release-please. When someone adds a date picker (feat), CI releases 3.3.0, and every team gets it on their next install. When the Button prop "type" is renamed (marked with !), the release becomes 4.0.0, the changelog lists the migration steps, and no team is surprised, because ^3.2.0 never installs 4.x.

bash
git tag -a v3.3.0 -m "Release 3.3.0: add DatePicker"
git push origin v3.3.0

Practice (0/4 done)

Task 1 · Question

A library is at 4.2.7. You add a new optional function and change nothing else. What is the next version?

Task 2 · Code · Javascript

Write bump(version, part) for versions like "1.4.2" (no pre-release). part is "major", "minor" or "patch". Increase that part and reset every part to its right to 0. Return the new version string. Throw an Error for any other part.

Task 3 · Code · Javascript

Write compareVersions(a, b) returning -1 if a is lower, 1 if a is higher and 0 if equal, following SemVer: 1. Ignore a leading "v" and anything after "+" (build metadata). 2. Compare MAJOR, MINOR, PATCH as numbers. 3. If equal, a version WITHOUT a pre-release is higher than one with a pre-release. 4. If both have pre-releases (the part after "-"), compare them dot-separated part by part: two numbers compare as numbers; two words compare alphabetically; a number is lower than a word. If all shared parts are equal, the one with fewer parts is lower.

Task 4 · Code · Javascript

Write nextVersion(current, commits). current is like "2.4.1"; commits is a list of commit messages since the last tag. Use the first line of each message: a type followed by an optional (scope), an optional "!", then ":". If any commit has "!" or a line starting with "BREAKING CHANGE:", bump MAJOR. Otherwise any "feat" bumps MINOR. Otherwise any "fix" or "perf" bumps PATCH. Otherwise return null (nothing to release). Reset lower parts to 0.

Quick quiz

Question 1

1.Which version is the highest?

Question 2

2.package.json has "express": "~4.18.2". Which version may npm install?

Question 3

3.What is the difference between a lightweight and an annotated tag?

Question 4

4.Since the last release v3.1.4 there are commits: "fix: login timeout" and "feat: export to PDF". What is the next version?

Question 5

5.How do 1.0.0+build.5 and 1.0.0+build.9 compare under SemVer?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which version is the highest?

Answer: 1.10.0

1.10.0 has the highest MINOR, and the release is higher than its own rc.1 pre-release.

package.json has "express": "~4.18.2". Which version may npm install?

Answer: 4.18.5

The tilde allows newer patches of the same MAJOR.MINOR only: at least 4.18.2 and below 4.19.0.

What is the difference between a lightweight and an annotated tag?

Answer: An annotated tag stores a tagger, date and message (and can be signed); a lightweight tag is just a name

Annotated tags are full Git objects, which is why they are preferred for releases.

Since the last release v3.1.4 there are commits: "fix: login timeout" and "feat: export to PDF". What is the next version?

Answer: 3.2.0

The feat is the highest change, so MINOR is bumped and PATCH reset: 3.2.0.

How do 1.0.0+build.5 and 1.0.0+build.9 compare under SemVer?

Answer: They have the same precedence

Build metadata after + is ignored when comparing versions.

Which file should be committed to Git?Related

Answer: package-lock.json

The lock file guarantees the same versions everywhere; node_modules and secrets are not committed.

From Node.js basics

Which commit message marks a breaking change in Conventional Commits?Related

Answer: feat(api)!: remove v1 endpoints

A ! before the colon (or a BREAKING CHANGE: footer) marks a breaking change.

From Collaboration: remotes, pull requests and branching strategies

Under semantic versioning, a release adds a new feature without breaking anything. Version 2.3.1 becomes:Related

Answer: 2.4.0

A new backward-compatible feature bumps MINOR and resets PATCH to 0.

From Testing, quality gates and artefacts

package.json has "dayjs": "^1.11.13". Which version can npm install?Related

Answer: 1.12.0

^ allows newer versions with the same major number: 1.12.0 yes, 2.0.0 no, and older ones like 1.10.0 no.

From Modules, npm and Vite

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Git in depth”. The module exam comes next.
  2. Module 3 exam: Git in depth · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Docker. 53 topics left in the course before the final exam.

← Previous: Collaboration: remotes, pull requests and branching strategies