Git Course 0%

Tags, releases, semantic versioning

Intermediate ≈ 9 min

Before this lesson

What you will learn

  • What major, minor and patch actually promise
  • How to keep a changelog that costs nothing at release time
  • The full tag-to-release sequence

After this lesson you can

  • I can read a version number and write the notes for one

Why this matters

Version numbers are a promise to whoever depends on your project, and release notes are the one document users actually read. Both are frequently written by the person who writes the documentation, which may well be you.

Semantic versioning

The convention is MAJOR.MINOR.PATCH, and each part is a specific promise:

Part Increment when Promise
MAJOR Something that used to work no longer does Upgrading may require changes to how people use it
MINOR New behaviour is added, nothing existing breaks Safe to upgrade; there is something new
PATCH A bug is fixed, nothing else changes Safe to upgrade; nothing new to learn

So 1.2.0 → 1.2.1 is a fix, 1.2.1 → 1.3.0 adds something, and 1.3.0 → 2.0.0 warns you to read the notes before upgrading.

Two conventions travel with it: 0.x.y means "not stable yet, anything may change", and suffixes like 1.3.0-rc.1 or -beta.2 mark pre-releases, which tools that follow "latest" are supposed to skip.

The changelog

A changelog is a file, usually CHANGELOG.md, listing what changed in each version in the user's language. The way to make it cost nothing is to write each entry when the change is merged, not at release time:

Markdown
# Changelog

## Unreleased

### Added
- `--difficulty` filter on the trail list (#21).

### Fixed
- The list no longer crashes when a trail has no distance (#15).

## 1.2.0 — 2026-08-14

### Added
- Windows installation instructions (#12).

At release time, the "Unreleased" heading becomes the version and the date, and a new empty one is added. That takes two minutes; reconstructing a month of changes from commit messages takes an afternoon.

Categories worth using: Added, Changed, Deprecated, Removed, Fixed, Security. They map onto what a reader wants to know, and "Changed" and "Removed" are the ones that decide the major number.

The release sequence

  1. Check main is green and contains everything the version should have.
  2. Update the changelog: rename "Unreleased" to the version with today's date.
  3. Decide the number from the changelog's categories: anything under Removed or a breaking Changed means a major bump.
  4. Commit the changelog: docs: prepare 1.3.0.
  5. Tag: git tag -a v1.3.0 -m "Release 1.3.0" and git push origin v1.3.0.
  6. Create the release page with the notes (lesson 9.9, lesson 10.9).
  7. Announce it where users are, with a link to the release page.
Terminal
$ git tag -a v1.3.0 -m "Release 1.3.0: Windows FAQ and licence wording"
$ git push origin v1.3.0
To https://gitlab.com/northwind-trails/trailguide.git
 * [new tag]         v1.3.0 -> v1.3.0

Remember that a plain git push does not send tags (command page).

What goes in the notes

Users want outcomes, not commits:

Instead of Write
"Refactor trail parsing" Nothing; it is invisible to users
"Fix #15" "The list no longer crashes when a trail has no distance"
"Add --difficulty" "Filter the list by difficulty: --difficulty hard"
A list of merged merge requests Three sentences, plus the list underneath for the curious

And always include an upgrading note when anything changed name or behaviour, even a small one. That paragraph is the difference between a smooth upgrade and a support queue.

Working out what shipped

Question How
What is in this release? git log --oneline v1.2.0..v1.3.0
Is my fix in 1.3? git tag --contains <hash>
What changed for users? The changelog, if it was kept
Which issues? The milestone attached to the release

How to do it

Terminal
$ git switch main && git pull
$ git log --oneline v1.2.0..main
$ git tag -a v1.3.0 -m "Release 1.3.0"
$ git push origin v1.3.0

The second command is where the notes start: it is the honest list of what has accumulated.

Common mistakes

  • Bumping the minor number for a breaking change, which surprises everyone who upgrades.
  • Writing the changelog at release time from commit messages.
  • Notes written in developer language. "Refactored the parser" tells a user nothing.
  • Moving a published tag. Ship a new patch version instead (command page).
  • Forgetting git push origin <tag>, so the release page cannot find the tag.
  • No upgrading section when something changed name or default.

Try it yourself

Goal: release a version properly, changelog and all.

  1. Add a CHANGELOG.md to your practice project with an "Unreleased" section and two entries, one under Added and one under Fixed.
  2. Decide the version number from those entries and say why.
  3. Rename the heading to that version with today's date, commit it as docs: prepare 1.3.0.
  4. Tag and push the tag, then create the release page with notes based on the changelog.
  5. Run git log --oneline <previous tag>..v1.3.0 and check every user-visible entry appears in your notes.

Expected result: a release page whose notes a user could act on, and a changelog that made writing them a five-minute job.

Show solution

Step 2 is the judgement this lesson is really about. "Added" and "Fixed" alone mean a minor bump; anything under "Changed" or "Removed" that alters existing behaviour means a major one. Getting that wrong is the version-number mistake that actually costs people time.

Check yourself

1. A release renames a command-line option that people already use in scripts. What version bump?
2. When should changelog entries be written?
3. What does git tag --contains <hash> answer?

Key terms

Version Tag Release Milestone