Reference: conventionalcommits.org

Conventional Commits

A commit message is more than a note to yourself — it is the primary record of why a change was made. Anyone reading the history of a repository (including future you, six months from now) should be able to understand what happened and why without having to read every line of code that changed.

Conventional Commits is a lightweight specification for writing commit messages in a consistent, machine-readable format. The format is simple enough to type in seconds, but structured enough that tools can parse it automatically to generate changelogs, determine version numbers, and filter history by change type.

The Format

A conventional commit message has three parts:

<type>[optional scope]: <description>

[optional body]

[optional footer]

The first line — <type>[optional scope]: <description> — is the most important part and the one you will write most often. The body and footer are used for longer explanations and metadata such as breaking change notices or issue references.

Type

The type is a short keyword that categorizes what the commit does. The most commonly used types are:

Type When to use
feat Adds a new feature or capability
fix Corrects a bug or incorrect behavior
docs Changes to documentation only
style Formatting changes that do not affect behavior (whitespace, semicolons)
refactor Code restructuring that is neither a new feature nor a bug fix
test Adding or correcting tests
chore Maintenance tasks that don’t touch production code (dependency updates, CI config)
ci Changes to continuous integration configuration

In bioinformatics workflows you will most commonly reach for feat, fix, and docs. refactor is useful when you reorganize a pipeline without changing what it does — for example, extracting a repeated block into a shared subworkflow.

Scope

The scope is an optional word in parentheses that narrows the type to a specific part of the codebase. It is helpful in larger projects where a commit might touch one module among many:

feat(prokka): add --metagenome flag to annotation process
fix(trimmomatic): correct adapter path for paired-end samples
docs(readme): update cluster submission instructions

In a single-module project or a small lab repo, scope is often omitted.

Description

The description is a short, imperative-mood summary of the change. Imperative mood means you write it as a command: “add”, “fix”, “update”, “remove” — not “added”, “fixed”, “updating”, or “removed”.

Think of it as completing the sentence: “If applied, this commit will…”

  • Good: feat: add GC content calculation to genome stats process
  • Good: fix: handle gzipped input in calc_length.py
  • Less useful: updated the script
  • Less useful: fixed bug
  • Less useful: WIP

Keep the description under 72 characters so it displays cleanly in git log.

Examples

A minimal single-line commit:

git commit -m "feat: add jellyfish kmer counting process"

A commit with scope:

git commit -m "fix(samtools): remove duplicate fai index output field"

A commit with a body explaining the reasoning:

git commit -m "refactor: extract QC steps into shared reads_qc subworkflow

FASTQC and Trimmomatic were duplicated across project-2 and project-3.
Moving them to a shared subworkflow removes ~80 lines of repeated code
and ensures both projects stay in sync when parameters change."

A commit that documents a breaking change in the footer:

git commit -m "feat!: replace tuple inputs with typed record outputs

BREAKING CHANGE: process inputs now use record() destructuring.
Callers must update channel construction to supply named fields
instead of positional tuple elements."

The ! after the type is shorthand for a breaking change. The BREAKING CHANGE: footer makes it explicit for changelog tools.

Why It Matters

Readable history

When every commit follows the same format, git log --oneline becomes a structured summary of the project’s evolution rather than a stream of free-text notes:

3f1a2c4 feat: add publishDir to GENOME_STATS process
b8e0d91 fix: correct shebang line in gc_content.py
a12c883 docs: update iteration_3 instructions with nextflow log commands
9d4f701 refactor: move biopython env spec to envs/ directory

You can immediately see what category of change each commit represents without opening the diff.

Filtering history

Because the type is always in the same position, you can search commit history by change category:

git log --oneline --grep "^fix"    # show only bug fixes
git log --oneline --grep "^feat"   # show only new features

Communicating intent to collaborators

In a shared repository — or when submitting work to an instructor — conventional commit messages communicate not just what changed but why the change was necessary. A fix: prefix signals that something was wrong. A refactor: prefix signals that behavior is unchanged and the diff can be reviewed with less scrutiny. A feat: signals new capability that a reviewer should test.

Common Mistakes

Using past tense. Write “add support for gzipped input”, not “added support for gzipped input”. The convention is imperative mood.

Bundling unrelated changes. If you fixed a bug and added a new process in the same commit, the message cannot accurately describe both. Make two commits. Small, focused commits are easier to review, easier to revert if something goes wrong, and produce more useful history.

Skipping the type. A message like "update main.nf" is technically valid git but provides no information about why the file changed. Requiring yourself to choose a type forces you to think about what the change is — and sometimes reveals that you are about to bundle two different kinds of changes into one.

Over-specifying the description. The description should say what the commit does, not list every file touched. File names are visible in the diff; the message should convey intent.

Adopting Conventional Commits

You do not need any tooling to use conventional commits — it is just a writing convention. The full specification at conventionalcommits.org defines additional rules for changelog generation and semantic versioning tooling, but following the basic type: description format immediately improves your commit history without any configuration.

For this course, using feat, fix, docs, refactor, and chore consistently is sufficient. When in doubt, pick the type that best describes the primary intent of the change.

Updated: