GitRelease Workflow

Release Workflow

A release is a deliberate, versioned snapshot of your codebase that you ship to users. Unlike the continuous stream of commits on main, a release is a named moment in time — v2.0.0, v3.1.4 — with release notes, a version tag in the repository, and (for many projects) artifacts published to npm, PyPI, Docker Hub, or a CDN.

This guide walks through the full release lifecycle: feature freeze, testing, changelog, version bumping, tagging, publishing a GitHub Release, automating with semantic-release, and post-release cleanup.

Preparing a Release

Before cutting any tags or bumping any versions, the codebase needs to be ready. This preparation phase applies to both GitHub Flow and Gitflow.

Release preparation checklist:

  • Feature freeze: agree on a cutoff date — no new features after this point, only bug fixes and release-related changes.

  • Full test suite green: run npm test / pytest / go test ./... locally and confirm CI is clean.

  • No known blocking bugs: all P0/P1 issues are resolved or deferred to the next release.

  • Changelog drafted: every user-visible change is documented (see below).

  • Dependencies reviewed: lock files committed, no known security vulnerabilities (npm audit, pip-audit).

  • Migration guide written (if breaking changes exist): users need to know what to do before upgrading.

  • Documentation updated: README, API docs, and inline comments reflect the new version.

Drafting the Changelog

A changelog is the human-readable record of what changed between versions. Follow the Keep a Changelog format (keepachangelog.com):

CHANGELOG.md

Text
# Changelog

All notable changes to this project will be documented in this file.
Format is based on Keep a Changelog, versioning on Semantic Versioning.

## [Unreleased]

## [2.0.0] - 2026-05-20

### Breaking Changes
- **api**: `/users` endpoint renamed to `/accounts` — update all clients
- **config**: `DATABASE_URL` env var renamed to `DB_CONNECTION_STRING`

### Added
- feat(auth): Google OAuth login support (#142)
- feat(ui): dark mode toggle (#189)
- feat(api): bulk import endpoint for CSV uploads (#201)

### Changed
- perf(query): optimised user search — 4x faster on large datasets (#195)
- refactor(auth): migrate from JWT to session tokens (#197)

### Fixed
- fix(cart): prevent negative quantity on decrement (#209)
- fix(email): correctly handle unicode in subject lines (#215)

### Deprecated
- `getUserById` — use `getAccountById` instead (removed in v3.0.0)

## [1.9.4] - 2026-04-10

### Fixed
- fix(payment): add null check before charge processing (#501)
Tip
Generate a first-draft changelog from commit history: `git log v1.9.4..HEAD --oneline`. If you have been using Conventional Commits, tools like `conventional-changelog` or `git-cliff` can automate this almost entirely.
Gitflow Release Branch Approach

In Gitflow, a release/ branch is created from develop when you are ready to prepare the release. Only bug fixes, version bumps, and release docs go on this branch. Feature development continues on develop uninterrupted.

1. Create the release branch from develop

Bash
git checkout develop
git pull origin develop
git checkout -b release/2.0.0
# Switched to a new branch 'release/2.0.0'

2. Bump the version

Bash
npm version major --no-git-tag-version
# v2.0.0

git add package.json package-lock.json
git commit -m "chore(release): bump version to 2.0.0"

3. Fix any last-minute release bugs on this branch

Bash
# (Only bug fixes — no new features)
git add src/api/accounts.ts
git commit -m "fix(api): correct 404 message for missing account"

# Update CHANGELOG.md
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG for 2.0.0"

4. Merge into main and tag

Bash
git checkout main
git pull origin main
git merge --no-ff release/2.0.0 -m "Release v2.0.0"
git tag -a v2.0.0 -m "Release v2.0.0"
git push origin main --follow-tags

5. Merge back into develop

Bash
git checkout develop
git merge --no-ff release/2.0.0 -m "Merge release/2.0.0 back into develop"
git push origin develop

6. Delete the release branch

Bash
git branch -d release/2.0.0
git push origin --delete release/2.0.0
Warning
Always merge the release branch back into `develop` before deleting it. Any bug fixes applied on the release branch must make it into `develop` or they will be lost when the next release cycle starts.
GitHub Flow Release (Tag main Directly)

In GitHub Flow there is no separate release branch — you simply tag a specific commit on main. This is simpler and works well for continuous delivery projects.

Tag a release on main

Bash
git checkout main
git pull origin main

# Confirm you are at the right commit
git log --oneline -5
# a3f9c21 (HEAD -> main, origin/main) fix(api): correct 404 message
# b1d8e34 feat(auth): add Google OAuth login
# ...

# Create the annotated tag
git tag -a v2.0.0 -m "Release v2.0.0

## Breaking Changes
- /users endpoint renamed to /accounts
- DATABASE_URL renamed to DB_CONNECTION_STRING

## What's new
- Google OAuth login
- Dark mode toggle
- Bulk CSV import

Full changelog: CHANGELOG.md"

git push origin v2.0.0
Bumping the Version

Ecosystem

File(s) to update

Command

Node.js

package.json, package-lock.json

npm version major|minor|patch --no-git-tag-version

Python (PEP 621)

pyproject.toml

bump2version major|minor|patch

Python (setup.py)

setup.py, version.py

bump2version major|minor|patch

Go

version.go or go.mod

Manual edit + git tag with v prefix

Rust

Cargo.toml

cargo set-version 2.0.0

Java/Maven

pom.xml

mvn versions:set -DnewVersion=2.0.0

Ruby

lib/project/version.rb, *.gemspec

gem bump --version major

Node.js version bump example

Bash
# Current: 1.9.4

# Patch: 1.9.4 -> 1.9.5
npm version patch --no-git-tag-version

# Minor: 1.9.4 -> 1.10.0
npm version minor --no-git-tag-version

# Major: 1.9.4 -> 2.0.0
npm version major --no-git-tag-version

# Specific version
npm version 2.0.0 --no-git-tag-version

# Then commit
git add package.json package-lock.json
git commit -m "chore(release): bump version to 2.0.0"
Note
The `--no-git-tag-version` flag tells npm not to create a Git commit and tag automatically. You will do this manually so you can craft a proper annotated tag with full release notes.
Creating a Git Tag

Annotated tag — the right way for releases

Bash
git tag -a v2.0.0 -m "Release v2.0.0

Breaking changes, new features, and bug fixes.
See CHANGELOG.md for full details."

# List all tags
git tag
# v1.9.0
# v1.9.1
# ...
# v2.0.0

# Show tag details
git show v2.0.0
# tag v2.0.0
# Tagger: Alex Developer <alex@example.com>
# Date:   Wed May 20 10:00:00 2026 +0000
#
# Release v2.0.0 ...

# Push the tag
git push origin v2.0.0

# Push all tags at once (use carefully)
git push origin --tags
Warning
Never delete and re-create a published tag that has been pushed and others may have fetched. If you need to fix a tag, create a new release (`v2.0.1`). Changing a published tag breaks every clone that has already fetched it.
Creating a GitHub Release

A GitHub Release is a wrapper around a tag that adds a title, release notes in Markdown, and optional binary file attachments. It lives on the GitHub Releases page and can trigger deployment workflows.

Option A: GitHub web UI — go to your repository → Releases → Draft a new release → choose the tag → fill in the title and notes → Publish.

Option B: GitHub CLI (gh):

Create a GitHub Release with gh CLI

Bash
# Create from existing tag
gh release create v2.0.0 \
  --title "v2.0.0 — Dark Mode and Google OAuth" \
  --notes-file RELEASE_NOTES.md

# Create release and tag at the same time
gh release create v2.0.0 \
  --title "v2.0.0" \
  --generate-notes \
  --target main

# Attach a build artifact
gh release create v2.0.0 \
  --title "v2.0.0" \
  --notes "See CHANGELOG.md" \
  ./dist/myapp-linux-amd64 \
  ./dist/myapp-darwin-arm64
Automated Releases with semantic-release

semantic-release automates the entire release workflow: it reads your Conventional Commits, determines the next version number, generates a changelog, creates a git tag, publishes to npm/GitHub Packages, and creates a GitHub Release — all triggered by a CI push to main.

Install semantic-release

Bash
npm install --save-dev semantic-release \
  @semantic-release/changelog \
  @semantic-release/git \
  @semantic-release/github

.releaserc.json

JSON
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    ["@semantic-release/changelog", {
      "changelogFile": "CHANGELOG.md"
    }],
    "@semantic-release/npm",
    ["@semantic-release/git", {
      "assets": ["package.json", "CHANGELOG.md"],
      "message": "chore(release): ${nextRelease.version} [skip ci]"
    }],
    "@semantic-release/github"
  ]
}

.github/workflows/release.yml

YAML
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      issues: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test
      - run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Commit type

Version bump triggered

fix:

Patch (1.0.0 → 1.0.1)

feat:

Minor (1.0.0 → 1.1.0)

feat! or BREAKING CHANGE:

Major (1.0.0 → 2.0.0)

chore:, docs:, test:

No release

Post-Release Steps

After the release is live, there are a few housekeeping tasks:

  1. Merge release branch back to develop (Gitflow): as shown above — critical to avoid losing release fixes.

  2. Announce the release: post in your team channel, update your status page, send release emails if you have subscribers.

  3. Publish to registries: npm publish, push Docker image, deploy to CDN — if not already automated.

  4. Monitor the release: watch error rates, latency, and logs for 30–60 minutes after deployment.

  5. Create the next Unreleased section in CHANGELOG.md: git commit -m "chore: open changelog for next release" — makes it easy to add entries as work proceeds.

  6. Update documentation site: if you host versioned docs (like Docusaurus or MkDocs), publish the new version.

  7. Milestone: close the GitHub Milestone for this version and open one for the next.

Tip
Set up automatic deployment on tag push so that `git push origin v2.0.0` immediately kicks off production deployment. This eliminates the manual "trigger deploy" step and ties every deployment to a specific, auditable version.