Skip to content

DispatchesΒΆ

Rebase. Squash. Merge. Repeat.

You open a pull request. The CI checks pass. A reviewer leaves a comment:

"Please squash your commits and rebase onto the latest main."

If you are new to contributing to open source or working on a team with a structured workflow, that request can feel like an obstacle between you and getting your work merged. It is not. It is a signal that the project cares about its history, and that caring about history is worth your time too.

This post covers the contributor side: what to do to a branch before opening a pull request (or merge request, as GitLab calls it), covering rebase, squash, and sign.

The maintainer side, covering the three GitHub merge strategies and why squash and merge is the right default, is covered in Squash and Merge: A Better Default.

A lot of what follows is inspired by Marc Gasch's Git rebase, squash...oh my!. This post builds on that foundation and connects these practices to the structured commit workflow covered in Conventional Commits: How to Write a Better Git Commit Message and the contributor expectations described in CONTRIBUTING.md: Writing Practical Contribution Guidelines for GitHub Repositories.

Squash and Merge: A Better Default

Every GitHub repository shows three options when a pull request is ready to be merged:

  1. Create a Merge Commit
  2. Squash and Merge
  3. Rebase and Merge

The default is usually Create a Merge Commit, which produces history that becomes harder to read and reason about over time. And most teams tend to leave this default as-is.

This post covers what each strategy does, why Squash and Merge is a better default, and how to configure GitHub to enforce it.

Signing Your Git Commits: From Zero to Verified

Anyone can commit code to a repository pretending to be you. Git's author fields (user.name and user.email) are free-form text that any client can set to anything. Cryptographic commit signing closes that gap by mathematically binding a commit to a key pair that only you control. Once you add a verified badge to your commits on GitHub or GitLab, every reviewer can be confident that the code actually came from you.

This post walks through the full picture: why signing matters, how to configure your git client correctly, how to generate and publish a GPG key, how to use your platform's no-reply address so you never expose your real email, and how to automate Signed-off-by trailers with git hooks, complete with copy-paste examples for every step.

Conventional Commits: How to Write a Better Git Commit Message

If you browse the commit history of a long-running project, you will find one of two things. Either a history that reads like a record, something you can actually use to understand why the code is the way it is, or something like this:

fix stuff
wip
update
changes
more fixes
actually fix it this time

I have written commits like that. Most developers have. It happens when you treat the commit message as a formality, when the only audience you are writing for is the CI system that just needs to see something in the message field.

Compare that to this:

feat(datastore): add support for datastore clusters
fix(ssh): prevent IPv6 addresses from being double-wrapped in brackets
refactor(firmware): set firmware configuration during create
chore(deps): bump govmomi from 0.51.0 to 0.52.0
docs: update README with plugin installation instructions
feat(datasource): add virtual machine datasource

Which would you rather read?

The second log follows Conventional Commits, a lightweight specification that gives every commit a predictable, parseable shape. The difference is not talent or effort. It is convention.

This post is my attempt to explain how Conventional Commits work, why each rule exists, and how I use them in practice. A lot of this builds on Chris Beams's foundational piece, How to Write a Git Commit Message. If you have not read it, do that first. What follows extends the principles he outlines into the structured format I have settled on.

Inside the Stack: How This Blog Is Built

Every once in a while, someone asks me how this site works. The short answer: it is a static site generated by MkDocs, themed with MkDocs Material, deployed to GitHub Pages via GitHub Actions, with Cloudflare Pages serving pull request build previews and Cloudflare handling DNS for the custom domain. No CMS, no database, no runtime server. Every page is a Markdown file in a Git repository. Everything runs automatically on every push to main.

This post walks through how all those pieces fit together, with the actual configuration files that run this blog today.