GitHub 14 ๐ GitHub Releases, Tags, and Versioning
A tag is a Git concept: a fixed pointer to a specific commit that never moves. A release is a GitHub concept layered on top of a tag: a page with release notes and downloadable binary artifacts. Confusing the two is common. You can tag a commit without creating a release, but you cannot create a release without a tag. The tag is the anchor; the release is the package that users actually download and read about.
Key point: Releases are built on annotated tags, not lightweight tags. An annotated tag records the tagger, the date, and a message, and can be signed. A lightweight tag is just a movable bookmark. For anything that will be published, use git tag -a.
Why tags and releases exist
The mutable branch problem. A branch pointer advances with every commit. If you tell someone “use version 1.0,” and main has moved 40 commits since, the reference is meaningless. A tag solves this: v1.0.0 points to one commit forever. git describe --tags uses the nearest annotated tag to produce a human-readable name like v1.2.0-14-g2414bc4โ14 commits after v1.2.0, at commit 2414bc4 . This is what build scripts bake into --version output.
The source-versus-artifact problem. Git stores source code, not compiled binaries. A user who wants to run your Rust binary should not have to install a toolchain, clone the repository, and compile. A GitHub Release attaches the compiled binaries (Linux, macOS, Windows) directly to the release page. The tag identifies the exact source commit; the assets are the built artifacts from that commit .
The versioning semantics problem. Semantic Versioning (SemVer) encodes meaning into the version number: MAJOR.MINOR.PATCH. A patch increment means backward-compatible bug fixes. A minor increment means backward-compatible new features. A major increment means breaking changes . When a user sees v2.0.0, they know the upgrade requires attention. When they see v1.2.4, they can upgrade without reading migration guides.
The release-notes problem. A commit message explains a single change. A release note explains the entire delta since the last release: new features, bug fixes, breaking changes, and upgrade instructions. GitHub can generate release notes automatically from merged pull requests and commit history, or you can write them manually in a CHANGELOG.md and extract the relevant section .
The automation problem. A release should not require manual clicking. A Git tag push can trigger a GitHub Actions workflow that builds binaries, creates the release, and attaches the assets. The workflow runs only when the tag matches a pattern like v*, ensuring that releases are deliberate and reproducible .
a. Creating annotated tags and pushing them
A tag is created locally with git tag. For any tag that will be published, use the -a flag and provide a message. The message becomes part of the tag object and can be read by tools.
# Tag the current commit with an annotated tag
git tag -a v1.0.0 -m "First stable release"
# Tag a specific commit (e.g., if you forgot to tag at the time)
git tag -a v0.9.0 9fceb02 -m "Beta release"
# Verify the tag
git show v1.0.0
git push does not push tags. Tags are refs outside refs/heads/, and a normal push ignores them. You must push them explicitly. The safest approach is to push a single tag, or use --follow-tags to push annotated tags reachable from the commits being pushed. A blanket git push --tags can publish local experimental tags you never intended to release .
# Push a specific tag
git push origin v1.0.0
# Push commits and their reachable annotated tags
git push --follow-tags
To delete a tag before it is widely used, delete it locally and on the remote. Deleting a published tag after users have downloaded it is antisocial and breaks their references .
git tag -d v1.0.0
git push origin --delete v1.0.0
b. Creating a release from a tag
A release is created from the GitHub web interface or the gh CLI. The web interface: navigate to the repository’s “Releases” page, click “Draft a new release,” select the existing tag (or create a new one), enter a title and description, optionally attach binary assets, and click “Publish release” . If the repository has immutable releases enabled, create the release as a draft first, attach all assets, then publishโafter publishing, assets cannot be added or replaced .
The gh CLI is faster for automation and scripting. The gh release create command accepts the tag as the first argument and supports flags for notes, assets, and release type.
# Create a release with explicit notes
gh release create v1.0.0 --title "v1.0.0" --notes "Initial stable release"
# Create a release with auto-generated notes from merged PRs
gh release create v1.0.0 --generate-notes
# Read notes from a file (e.g., extracted from CHANGELOG)
gh release create v1.0.0 --notes-file release-notes.md
# Mark as pre-release
gh release create v1.0.0-rc.1 --prerelease --notes "Release candidate"
# Attach binary assets with display labels
gh release create v1.0.0 ./dist/app-linux ./dist/app-macos '#MacOS Bundle'
The --generate-notes flag uses GitHub’s Release Notes API to produce a changelog from merged pull requests since the last release. The --notes-start-tag flag pins the comparison base when the default range is incorrect . If the repository uses a CHANGELOG.md, a workflow can extract the section for the current version and pass it via --notes-file .
c. Automating releases with GitHub Actions
A release workflow triggers on tag pushes matching v*. The workflow checks out the code, builds the artifacts, and creates the release with gh release create or the softprops/action-gh-release action. The critical permission is contents: writeโwithout it, the release creation step fails .
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build artifacts
run: cargo build --release
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
target/release/my-binary
The softprops/action-gh-release action automatically creates a release from the tag, uses the tag name as the release title, and uploads the listed files as assets . The files input accepts newline-separated globs and supports display labels with the # syntax.
A subtle pitfall: if a workflow creates and pushes a tag using the default GITHUB_TOKEN, that tag push does not trigger another workflow with on: push: tags. The token’s push events are suppressed to prevent recursive workflows. The fix is to use a personal access token for the tag creation, or to dispatch the release workflow explicitly .
Complete Example Session
# ============================================
# PART 1: ENSURE THE CODE IS READY
# ============================================
git checkout main
git pull origin main
cargo test
# All tests pass.
# ============================================
# PART 2: UPDATE THE VERSION
# ============================================
# Edit Cargo.toml
# version = "1.2.0"
git add Cargo.toml Cargo.lock
git commit -m "Bump version to 1.2.0"
git push origin main
# ============================================
# PART 3: CREATE AND PUSH AN ANNOTATED TAG
# ============================================
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0
# The tag is now on GitHub.
# ============================================
# PART 4: CREATE THE RELEASE WITH GH CLI
# ============================================
gh release create v1.2.0 \
--title "v1.2.0" \
--generate-notes \
./target/release/my-binary
# Release published with auto-generated notes and binary asset.
# ============================================
# PART 5: VERIFY THE RELEASE
# ============================================
gh release view v1.2.0
# Shows title, notes, and assets.
# ============================================
# PART 6: MARK AS PRE-RELEASE (IF NEEDED)
# ============================================
gh release edit v1.2.0 --prerelease
# Users see the pre-release warning on the releases page.
# ============================================
# PART 7: DELETE A MISTAKEN RELEASE
# ============================================
gh release delete v1.2.0 --yes
git tag -d v1.2.0
git push origin --delete v1.2.0
# Fix the issue, re-tag, and re-release.
# ============================================
# PART 8: AUTOMATE VIA GITHUB ACTIONS
# ============================================
# .github/workflows/release.yml triggers on 'v*' tags.
# The workflow builds artifacts and creates the release.
git tag -a v1.2.1 -m "Release v1.2.1"
git push origin v1.2.1
# Actions runs, release is created automatically.
The eight parts trace the manual release lifecycle: verifying the code, bumping the version, creating the tag, creating the release, verifying, marking pre-release, deleting a mistake, and automating via Actions. The manual path is for one-off releases; the automated path is for repeatability.
Quick Reference
Tag Commands
| Command | Purpose |
|---|---|
git tag -a v1.0.0 -m "msg" | Create annotated tag |
git tag -l "v1.*" | List tags matching pattern |
git show v1.0.0 | Inspect tag metadata |
git push origin v1.0.0 | Push specific tag |
git push --follow-tags | Push commits + reachable annotated tags |
git tag -d v1.0.0 | Delete local tag |
git push origin --delete v1.0.0 | Delete remote tag |
Release Commands (gh CLI)
| Command | Purpose |
|---|---|
gh release create v1.0.0 | Interactive release creation |
gh release create v1.0.0 --generate-notes | Auto-generate notes |
gh release create v1.0.0 --notes-file notes.md | Notes from file |
gh release create v1.0.0 --prerelease | Mark as pre-release |
gh release create v1.0.0 ./dist/app | Attach asset |
gh release view v1.0.0 | View release details |
gh release edit v1.0.0 --prerelease | Edit release |
gh release delete v1.0.0 | Delete release |
Semantic Versioning
| Increment | When | Example |
|---|---|---|
| MAJOR | Backward-incompatible changes | 1.0.0 โ 2.0.0 |
| MINOR | Backward-compatible new features | 1.0.0 โ 1.1.0 |
| PATCH | Backward-compatible bug fixes | 1.0.0 โ 1.0.1 |
Best Practices
โ Do This:
git tag -a v1.0.0 -m "Release v1.0.0"
<!-- Always use annotated tags for releases --> # โ
gh release create v1.0.0 --generate-notes
<!-- Let GitHub generate notes from merged PRs --> # โ
permissions:
contents: write
<!-- Required for Actions to create releases --> # โ
โ Don’t Do This:
git tag v1.0.0
<!-- Lightweight tag: no metadata, no signature --> # โ
git push --tags
<!-- Pushes all local tags, including experimental --> # โ
# Create a release without a tag
<!-- Releases require a tag as the anchor --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Tag not on GitHub | Forgot git push origin <tag> | Push the tag explicitly |
| Release shows no notes | No --generate-notes or --notes | Add notes flag or edit post-publish |
| Actions release fails | Missing contents: write permission | Add permissions block |
| Tag push does not trigger workflow | Tag created by GITHUB_TOKEN | Use PAT or dispatch workflow explicitly |
| Cannot edit published release assets | Immutable releases enabled | Create as draft, attach assets, then publish |
Real-World Examples
1. Annotated Tag
git tag -a v1.0.0 -m "Release v1.0.0"
2. Push Tag
git push origin v1.0.0
3. Create Release
gh release create v1.0.0 --title "v1.0.0" --notes "First release"
4. Auto-Generated Notes
gh release create v1.0.0 --generate-notes
5. Pre-Release
gh release create v2.0.0-rc.1 --prerelease
6. Attach Binary
gh release create v1.0.0 ./target/release/app
7. View Release
gh release view v1.0.0
8. Delete Release and Tag
gh release delete v1.0.0 --yes
git push origin --delete v1.0.0
9. Actions Release Workflow
on:
push:
tags: ['v*']
10. Softprops Action
uses: softprops/action-gh-release@v2
with:
files: target/release/app
Visual
Tag vs Release
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GIT TAG (repository concept) โ
โ โ
โ v1.0.0 โ commit abc123 โ
โ Fixed pointer, never moves. โ
โ Metadata: tagger, date, message. โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ GitHub Release (layered on top)
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GITHUB RELEASE (GitHub concept) โ
โ โ
โ Tag: v1.0.0 โ
โ Notes: "New feature X, bug fix Y" โ
โ Assets: app-linux, app-macos, checksums.txt โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The tag is the source anchor. The release adds prose and binaries.
SemVer Decision
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ What changed? โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ BREAKING โ NEW FEATURE (compatible)
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ MAJOR increment โ โ MINOR increment โ
โ v1.2.3 โ v2.0.0 โ โ v1.2.3 โ v1.3.0 โ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ BUG FIX (compatible)
โผ
โโโโโโโโโโโโโโโโโโโโโโโโ
โ PATCH increment โ
โ v1.2.3 โ v1.2.4 โ
โโโโโโโโโโโโโโโโโโโโโโโโ
The version number communicates the upgrade cost.
Summary
| Item | Value |
|---|---|
| Tag type for releases | Annotated (git tag -a) |
| Tag push | git push origin <tag> |
| Release creation (web) | Releases โ Draft a new release |
| Release creation (CLI) | gh release create <tag> |
| Auto notes | --generate-notes |
| Attach assets | gh release create <tag> <file> |
| Pre-release flag | --prerelease |
| Actions permission | contents: write |
| SemVer format | MAJOR.MINOR.PATCH |
Key takeaways:
- A tag is a fixed pointer; a release is a page built on that pointer. The tag lives in Git; the release lives on GitHub and carries notes and binaries .
- Use annotated tags for releases. They record who tagged, when, and why. Lightweight tags are bookmarks, not release anchors .
git pushdoes not push tags. Push them explicitly withgit push origin <tag>or--follow-tags.- SemVer communicates upgrade cost. MAJOR for breaking changes, MINOR for features, PATCH for fixes .
- Automation requires
contents: write. A GitHub Actions workflow that creates releases needs this permission explicitly set . - Tag pushes by
GITHUB_TOKENdo not trigger tag workflows. This is a deliberate suppression to prevent recursive runs. Use a PAT or explicit dispatch .
Remember: A release is not a tag. The tag is the immutable source anchor; the release is the package users see. Tag the commit, push the tag, then create the release with notes and binaries. Or automate the whole chain with a tag-triggered workflow. Either way, the tag comes first.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!