Skip to content

6.3. Releases

What is a project release?

A project release is a formal, versioned package of your project's code and its related artifacts, such as compiled binaries, container images, and documentation. It marks a stable, specific point in the project's history, documenting a curated list of new features, bug fixes, and performance improvements. Each release serves as a reliable checkpoint that other developers and users can depend on.

Why are project releases essential?

  • Communicate Progress: Releases provide a clear, structured way to inform users about new features, bug fixes, and other improvements, managing expectations and demonstrating the project's evolution.
  • Establish Milestones: They divide the development process into manageable milestones, helping teams track progress against a timeline and adjust goals as needed.
  • Guarantee Stability: Each release is a quality gate, ensuring the software meets defined standards for stability and consistency. This builds trust and provides a reliable user experience.

Ultimately, a release is a contract between you and your users. Referencing a specific version, like v1.2.3, guarantees a consistent set of features and behaviors, which is critical for building and maintaining trust as your project grows.

How often should you create releases?

The ideal release frequency depends on your project's nature and goals. Rapidly evolving projects may benefit from short cycles (e.g., every few weeks), while others might adopt longer intervals (e.g., quarterly). The key is to establish a predictable schedule that balances the need for new features with the assurance of stability. A clear versioning scheme is crucial to avoid compatibility issues.

Which Git workflow is right for your project?

Choosing the right Git workflow is critical for effective collaboration and efficient project management. Here are three popular options:

GitHub Flow

A simple and streamlined workflow, ideal for projects that practice continuous delivery.

  • Core Principle: The main branch is always stable and deployable.
  • Process: New work is done on descriptive feature branches, which are then merged into main via a Pull Request (PR) after review.
  • Best For: Small to medium-sized teams and web applications that require frequent releases.

Git Flow

A more structured and robust workflow designed for projects with scheduled release cycles.

  • Core Principle: Uses two long-lived branches: main for stable release history and develop for integrating new features.
  • Process: Feature branches are created from develop. When a release is planned, a release branch is created from develop, stabilized, and then merged into both main and develop. hotfix branches are used for urgent production fixes.
  • Best For: Large, complex projects with multiple versions in production and a formal release schedule.

Forking Workflow

A distributed workflow that is the standard for public open-source projects.

  • Core Principle: Every contributor works on their own server-side copy (a fork) of the repository.
  • Process: Contributors push changes to their personal fork and submit a PR to the main repository. A central maintainer reviews and merges the PR.
  • Best For: Open-source projects or any project with a large, distributed team of contributors who may not have direct push access.

Comparison

Workflow Best For Key Advantage Potential Drawback
GitHub Flow Continuous delivery, small teams Simplicity and speed Less suitable for managing multiple versions
Git Flow Scheduled releases, large projects Structure and parallel development Can be overly complex for simple projects
Forking Workflow Open-source, distributed teams Clean project history, clear contribution path Higher barrier to entry for new contributors

Which versioning scheme should you use?

Choosing a consistent versioning scheme is vital for communicating the nature of changes between releases.

  • SemVer (Semantic Versioning): This is the most widely adopted standard. It uses a MAJOR.MINOR.PATCH format (e.g., 2.1.4).
    • MAJOR: Incremented for incompatible API changes (breaking changes).
    • MINOR: Incremented for new, backward-compatible functionality.
    • PATCH: Incremented for backward-compatible bug fixes.
  • CalVer (Calendar Versioning): This scheme uses the release date as part of the version (e.g., YYYY.MM.MICRO). It's useful for projects where the release date is the most important piece of information, such as time-sensitive applications or services.

Python projects often follow PEP 440, which defines a comprehensive scheme that accommodates pre-releases, post-releases, and developmental versions.

For most libraries and tools, SemVer is highly recommended because it clearly communicates the impact of updates to users.

How do you create a release on GitHub?

Creating a release on GitHub turns a Git tag into a formal, downloadable package with published release notes. You can do this from the web UI or, more reproducibly, from the command line with the GitHub CLI.

From the web UI:

  1. Navigate to your repository's main page and click Releases in the right-hand sidebar.
  2. Click Draft a new release.
  3. Choose an existing Git tag or create a new one. The tag should follow your versioning scheme with a v prefix (e.g., v1.2.3).
  4. Write a clear release title and description. You can auto-generate notes from merged PRs, or paste the changelog section produced by git-cliff (see below).
  5. (Optional) Attach binary files, such as compiled executables or installers.
  6. Publish the release. It now appears on your repository's releases page.

From the command line with gh:

# Publish a release for an existing tag, with notes read from a file.
gh release create v1.2.3 --title "v1.2.3" --notes-file release-notes.md

Publishing a release is also a useful automation trigger: the MLOps Python Package runs its continuous delivery workflow on: release: published to deploy the documentation and build the container image.

How do you coordinate a release with a team?

Effective team collaboration relies on GitHub's project management tools:

  • Issues: Track bugs, feature requests, and other tasks.
  • Labels: Categorize issues by type (bug, feature), priority (high, low), or status (in-progress).
  • Milestones: Group issues into a single release target. This provides a clear overview of progress and helps ensure all planned work is completed.

Using these tools keeps the team aligned on release goals and timelines.

How do you review and merge contributions for a release?

A structured review process ensures code quality and consistency.

  1. Branching: Contributors create a new branch from the main development line (main or develop) for each distinct feature or fix.
  2. Pull Request (PR): Once work is complete, the contributor opens a PR to merge their changes into the target branch. The PR description should clearly explain the "what" and "why" of the change.
  3. Code Review: Other team members review the code, providing feedback and suggesting improvements. Automated checks (like tests and linters) should also run at this stage.
  4. Merge: After approval, the PR is merged, integrating the new code into the main development line. The feature branch can then be deleted.

What are the final steps before creating a release?

Before tagging a release, complete this pre-flight checklist:

  • Testing: Run the full test suite to confirm that all features work as expected and no regressions have been introduced.
  • Documentation: Update all relevant documentation, including READMEs, user guides, and API references.
  • Changelog: Finalize the changelog. Tools like git-cliff automate this by generating a versioned summary of changes from your Conventional Commits history.
  • Dependencies: Review and update project dependencies to address any known vulnerabilities.

How do you generate a changelog with git-cliff?

A changelog is only as good as the discipline behind it, so derive it from your commit history rather than writing it by hand. This course follows Conventional Commits โ€” every commit is prefixed with a type such as feat:, fix:, refactor:, docs:, or chore: โ€” and uses git-cliff to turn those messages into a clean, grouped CHANGELOG.md. git-cliff is a standalone CLI, so you can pin it alongside your other tools in mise.toml.

git-cliff reads a cliff.toml file at the root of your repository. It maps commit types to changelog sections, skips housekeeping commits (like chore(release) and chore(deps)), and computes the next semantic version for you:

# cliff.toml โ€” https://git-cliff.org
[git]
conventional_commits = true
filter_unconventional = true
tag_pattern = "v[0-9].*"
commit_parsers = [
  { message = "^feat", group = "๐Ÿš€ Features" },
  { message = "^fix", group = "๐Ÿ› Bug Fixes" },
  { message = "^perf", group = "โšก Performance" },
  { message = "^refactor", group = "โ™ป๏ธ Refactor" },
  { message = "^docs", group = "๐Ÿ“š Documentation" },
  { message = "^test", group = "๐Ÿงช Testing" },
  { message = "^(build|ci)", group = "โš™๏ธ Build & CI" },
  { message = "^chore\\(release\\)", skip = true },
  { message = "^chore\\(deps\\)", skip = true },
  { message = "^(chore|style)", group = "๐Ÿงน Miscellaneous" },
]

[bump]
# Before v1.0.0, keep breaking changes in the MINOR and features/fixes in the PATCH.
features_always_bump_minor = false
breaking_always_bump_major = false
initial_tag = "v0.1.0"

With this configuration, git-cliff can both tell you the next version and write the changelog for it:

# Print the next semantic version inferred from the commits since the last tag.
git-cliff --bumped-version

# Prepend the new version's section to CHANGELOG.md.
git-cliff --bump -o CHANGELOG.md

Because Conventional Commits carry the semantics of each change (feat bumps the MINOR, fix bumps the PATCH, and a ! or BREAKING CHANGE bumps the MAJOR), git-cliff keeps your version numbers honest without any manual bookkeeping.

How do you cut and publish a release?

Once your main branch is green, releasing is a short, repeatable sequence. The MLOps Python Package follows these steps:

  1. Validate: Start from a clean working tree on main and run the full suite with mise run check and mise run test.
  2. Compute the version: Run git-cliff --bumped-version to get the next tag (e.g., v1.2.3).
  3. Bump the manifest: Update version in pyproject.toml to match (skip this if you use dynamic, tag-based versioning).
  4. Generate the changelog: Run git-cliff --bump -o CHANGELOG.md.
  5. Commit: Stage the changelog and manifest, then commit with git commit -m "chore(release): v1.2.3" (this commit is excluded from the changelog by design).
  6. Tag and push: Create an annotated tag and push it with git tag -a v1.2.3 -m "v1.2.3" followed by git push --follow-tags.
  7. Publish: Create the GitHub release with notes taken from the latest changelog section:
git-cliff --latest --strip all > release-notes.md
gh release create v1.2.3 --title "v1.2.3" --notes-file release-notes.md

Keep the v prefix consistent everywhere โ€” git-cliff, gh, and Git tags all expect the vX.Y.Z form. Never move a tag once it is published; if you need to fix something, cut a new patch release.

How can you automate the release process?

Automating the release with CI/CD saves time and removes human error. With GitHub Actions, a continuous delivery workflow can trigger on a published release and reuse the very same mise run tasks you run locally, so behavior stays identical between your machine and CI:

# .github/workflows/cd.yml
name: CD
on:
  release:
    types: [published]
jobs:
  pages:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: jdx/mise-action@v4
      - run: mise run docs
      # ... then upload-pages-artifact + deploy-pages publish the documentation

A typical automated release pipeline covers these steps:

  1. Version and changelog: Compute the next version and regenerate CHANGELOG.md with git-cliff from the Conventional Commits history.
  2. Tag and release: Create the annotated vX.Y.Z tag and publish the GitHub release with the generated notes.
  3. Build artifacts: Build the Python distribution (mise run build) and, if relevant, the container image.
  4. Publish and deploy: Push packages to registries like PyPI or the GitHub Container Registry, and deploy the documentation to GitHub Pages with the official Pages Actions.

How should you communicate release changes?

Clear communication is key to user adoption and trust.

  • GitHub Release Page: This is the primary source of truth, with detailed release notes and downloadable assets.
  • CHANGELOG.md: A file in your repository that provides a cumulative, version-by-version history of changes, often following the Keep a Changelog format.
  • GitHub Pages: Host version-specific documentation, allowing users to access the docs relevant to their version of the software.

How long should you support previous releases?

The support window for past releases depends on your project's resources and user base. Critical projects, like the Python language, offer Long-Term Support (LTS) for specific versions, providing security patches and critical bug fixes for years.

Establish a clear support policy and communicate it to your users. This helps them plan upgrades and builds confidence in your project's reliability.

Additional Resources