Commits are history. Branches are parallel timelines. But sometimes you need landmarks—signposts that say this is version 1.0, or this is the stable release shipped to users. That’s what tags are for. Tags are immutable labels you attach to commits. They don’t move like branches; they stay anchored forever, marking milestones in the project’s journey. In this chapter, we’ll dive into tags, release workflows, and semantic versioning (SemVer).


The Essence of a Tag

A tag is just a pointer to a commit, like a branch. But unlike a branch, it never moves. It’s a sticky note you slap onto a commit saying “this matters.”

Visualization:

gitGraph
  commit id: "A"
  commit id: "B"
  commit id: "C" tag: "v1.0.0"
  commit id: "D"

Here, v1.0.0 forever marks commit C.


Creating Tags

Lightweight tag:

git tag v1.0.0

Annotated tag (recommended):

git tag -a v1.0.0 -m "Release version 1.0.0"

Annotated tags store author, date, and message—making them part of history.


Listing and Inspecting Tags

List all tags:

git tag

Show details of a tag:

git show v1.0.0

Pushing Tags to Remote

Tags aren’t pushed automatically. Share them explicitly:

git push origin v1.0.0

Or push all tags:

git push origin --tags

Visualization:

flowchart LR
  LocalTag[Local Tag v1.0.0] -->|push| RemoteTag[Remote Repo Tag v1.0.0]

Deleting Tags

Delete locally:

git tag -d v1.0.0

Delete on remote:

git push --delete origin v1.0.0

Lightweight vs Annotated Tags

  • Lightweight: simple name → commit.
  • Annotated: includes metadata (author, date, message).
  • Use annotated for releases, lightweight for temporary marks.

Tags in Release Workflows

Tags are the backbone of release processes. CI/CD pipelines often trigger builds when new tags are pushed. For example, tagging v2.0.0 may automatically build binaries, upload to package registries, and publish changelogs.

Example workflow:

git tag -a v2.0.0 -m "Release version 2.0.0"
git push origin v2.0.0

CI/CD system detects the tag and runs deployment.


Semantic Versioning (SemVer)

Semantic Versioning is a convention: MAJOR.MINOR.PATCH.

  • MAJOR: incompatible changes.
  • MINOR: backwards-compatible new features.
  • PATCH: backwards-compatible bug fixes.

Examples:

  • 1.0.0 → first stable release.
  • 1.1.0 → added features.
  • 1.1.1 → bug fix.
  • 2.0.0 → breaking changes.

Visualization:

flowchart LR
  V100[v1.0.0] --> V110[v1.1.0] --> V111[v1.1.1] --> V200[v2.0.0]

Solo Workflow Example

As a solo developer, you tag stable milestones so you can roll back if needed. For instance, tagging v1.0.0 before a big refactor lets you return easily if things break.

git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0

Team Workflow Example

On a team, tags synchronize releases. Everyone knows exactly which commit was shipped. CI/CD pipelines run builds and tests automatically when a tag appears.

Visualization of release pipeline:

flowchart TD
  Dev[Developer tags v3.0.0] --> Remote[Remote Repo]
  Remote --> CI[CI/CD Pipeline]
  CI --> Build[Build Artifacts]
  CI --> Deploy[Deployment]

Tags become triggers for automation, not just labels.


Think Different Mindset

Tags are more than labels—they’re promises. When you tag v1.0.0, you promise stability. When you follow SemVer, you promise predictability. Git teaches that history is not just continuous—it has milestones. Tags are how we transform history from endless logs into meaningful landmarks.


Tags anchor history. Annotated tags preserve meaning. SemVer brings discipline. Together they create clarity in release processes. With tags, history becomes navigable and releases become reliable. In the next chapter, we’ll explore cherry-pick, revert, and reset—surgical tools for rewriting and correcting history.