Blog The life and ramblings of just another data scientist.

Writing High Quality, Well Scoped, Commits

In modern software projects, a clean Git history is more than just an aesthetic choice – it is a cornerstone of maintainable, collaborative development. Practitioners across the industry (from Linux kernel maintainers to Google’s Angular team) advocate for atomic commits: each commit being a focused, self-contained change that can stand on its own.

This article outlines widely accepted conventions and best practices for writing such commits in a language-agnostic context, drawing on official guidelines and community consensus. We discuss:

  • Keeping commits small
  • Ensuring each commit builds and passes tests
  • Making commits easy to revert or reorder
  • Including related updates (like documentation and tests) in the same commit

Relevant examples and scenarios are provided to illustrate these principles.

Keep Commits Focused

Make each commit an atomic unit of change. Commits should encapsulate a single logical change or fix. As the official Git documentation advises, you should “split your changes into small logical steps, and commit each of them”1. Each commit ought to be justifiable on its own merits and easily understood in isolation2. In practice, this means you should avoid bundling unrelated modifications into one commit. For example, if you are fixing two separate bugs or adding two independent features, handle them in separate commits (or even separate branches) rather than a single combined commit3. Small, focused commits make it easier for others to review your work and to trace through the project history later (e.g. using git blame or git log)1.

Commit related changes together, and nothing more. A commit should be a wrapper for related changes only3. This is a common theme in many guides, from the Linux kernel’s Submitting Patches guide to internal team wikis. The Linux kernel documentation explicitly states: “Separate each logical change into a separate patch.” For instance, if a change includes a bug fix and a performance improvement, split those into two patches (commits)2. Conversely, if one logical change requires edits across multiple files, those can be grouped in one commit2. The rule of thumb is that each commit addresses one idea or issue. This helps reviewers verify the change and ensures the commit is self-explanatory.

Example: If you are refactoring a module and adding a new feature in that module, do it in two commits. The first commit might be titled “refactor(auth): simplify token validation logic” containing only the cleanup/refactoring, and the second commit “feat(auth): add support for multi-factor login” containing the new feature implementation. This separation makes it clear what each change does and allows one to be reverted or adjusted without affecting the other.

Commit often to keep changes small. Embracing a workflow of frequent commits helps achieve this granularity. Rather than coding an entire feature over several days and committing at the end, break the work into smaller sub-tasks or milestones that can be committed independently. Commit each meaningful step – this might be every few hours or even more frequently. Regular commits prevent huge “omnimbus” changes and reduce the chance of merge conflicts4. It’s often said that it’s easier to squash multiple small commits later than to split a gigantic commit. Git’s interactive rebase (git rebase -i) makes it possible to combine or edit commits after the fact, but splitting one large, tangled commit is very difficult. The Git project’s own workflow notes underscore this: don’t fear making too many small commits; you can always clean up history before merging. “It is always easier to squash a few commits together than to split one big commit into several”, and you can polish commit series with rebase before publishing1.

Avoid mixing concerns in one commit. Any changes that are not directly related to each other should live in separate commits. For instance, do not bundle cosmetic changes (like reformatting code or fixing typos) with functional changes. If you happen to re-indent a code block or rename variables while fixing a bug, consider committing the refactoring first (or separately) and then the bug fix. This way, the bug fix commit’s diff is focused only on the substantive change, unclouded by whitespace or renaming noise. In large projects, maintainers appreciate this separation. The Linux kernel guidelines even give a specific example: if you need to move code from one file to another, do not modify that code in the same commit as the move – perform a pure move in one commit, and then make changes in a subsequent commit 2.

Ensure Commits Pass Tests

A hallmark of a well-scoped commit is that the project remains in a working, buildable state after that commit. In other words, if someone checks out any given commit from the history, the code should compile (or run) and ideally all tests should pass at that point. Keeping the repository in a buildable state at each commit is crucial for tools like git bisect, which relies on testing a range of commits to pinpoint regressions 2. If some intermediate commit breaks the build or test suite, it can frustrate developers trying to bisect, not to mention teammates who might check out that commit. The Linux kernel process documentation explicitly urges developers: “take special care to ensure that the kernel builds and runs properly after each patch in the series”, because someone may split your series at any point in a bisection2. Similarly, the official Git workflows guide says each commit should “pass the test suite, etc.” 1, underlining that each step in your commit history should maintain project integrity.

Don’t commit half-done work. You should only commit code when a logical chunk of functionality is completed and integrated. If your work is still in progress (e.g. a feature is only partially implemented, or tests are still failing), avoid committing that to the main branch. Instead, use a feature branch or even Git’s stash to save your work until it’s ready4. An oft-cited guideline is: “Don’t commit broken code”. Committing something that doesn’t even compile or that causes major parts of the application to fail will “make no sense” to others and may impede colleagues working on the project5. In a collaborative repository, pushing a commit that breaks the build or fails tests can block integration pipelines and annoy team members. Before committing, run the test suite. Ensure that all tests pass (or at least those not intentionally expected to fail). This discipline might require running a quick unit test command or a full CI pipeline locally. As one set of best practices puts it: “Resist the temptation to commit something that you think is completed – test it thoroughly to make sure it really is completed and has no side effects.” 4. By testing your code before you commit, you validate that the code in that commit is in a good state.

Maintain bisect-friendly history. If every commit builds and tests green, git bisect can be a powerful ally for tracking down issues. When you introduce a bug down the line, git bisect will check out commits one-by-one to find where things went wrong. If your commits are small and each is stable, bisect will cleanly pinpoint the first bad commit. However, if some commits are unstable (say, an intermediate commit that “half-implements” a feature and causes a test to fail), git bisect could be led astray. Unintentional test failures act like false signals during bisection6. For example, a commit that only adds a failing test (for a bug not yet fixed) introduces a red test in history; later the test passes when the fix is committed. From the perspective of bisect, the test going red in that commit might be mistaken for a regression, even though it was intentional. Because bisect assumes at most one transition from “good” to “bad”, a deliberately introduced failure can confuse it6. The safest practice is to avoid committing new failing tests or broken code in the main history altogether.

Intentional breaking commits (rarely) and how to handle them. In general, every commit to a shared branch should keep the build green. There are rare cases, often in test-driven development (TDD) or complex feature rollouts, where one might commit a known failing test or temporarily break something with the intention to fix it in the next commit. For example, when fixing a bug, a developer might first add a test that exposes the bug (which fails), and then in the next commit implement the fix so the test passes. This two-step approach can make the reasoning clear: the first commit shows the problem, the second shows the solution. Important: Such patterns should be used with care. Many experts advise not to push the failing test commit to the main branch until it’s fixed5. In a team setting or on the main integration branch, it’s better to combine the test and fix in one atomic commit, or at least ensure the failing test is flagged (skipped) so it doesn’t break the build. If you do use a separate commit for a failing test (on a topic branch, for example), ensure it’s clearly intentional. Some teams mark the commit message with a tag like “[WIP]” or use a convention to indicate that the commit is part of an incomplete sequence. In summary, breaking the build or tests should never be an accident – only do it when absolutely necessary, and even then, communicate that intent clearly (e.g. via commit message or branch strategy).

Independent and Revertible Commits

Another key characteristic of a well-scoped commit is that it can be cleanly reverted or cherry-picked without entangling unrelated code. This comes naturally when commits are atomic (one change at a time) and each commit stands on its own. An independent commit means it does not secretly rely on subsequent commits to function. In other words, it works in the context of the codebase as of that commit. This independence also implies a degree of reorderability: if you have two or three separate atomic commits, the project state should not fundamentally break if their order is swapped or if one is omitted – assuming they don’t have direct dependencies on each other. (Of course, some sequences do build on each other; the goal is to minimize unnecessary coupling.)

Ensure commits can be undone singly. A good test of a commit’s isolation is to ask: if this commit were reverted later, would the codebase still make sense and build correctly? An atomic commit “should be able to be reverted (via git revert) and not cause any side effects or conflicts in other parts of the system”7. When a commit contains only one self-contained change, reverting it will cleanly remove that change. On the other hand, if a commit mixes multiple concerns or partial work, a revert might remove some needed pieces or conflict with later changes. Community wisdom emphasizes this: “An 'atomic' commit is easier to handle in case you want to revert, cherry-pick or merge it. The changes of the commit are clear and understandable.”8. For example, consider a commit that both renames a database column and changes the logic that uses that column. If a problem is later found with the logic change, you cannot revert that commit without also undoing the rename – they’re entangled. Two independent commits (one for the rename, one for the logic change) would have been revertible in isolation. Keeping changes orthogonal in this way provides a safety net: any single commit can be undone with minimal impact on the rest of the code.

Cherry-pick and reorder with confidence. Independent commits also allow you to reuse changes easily in other contexts. For instance, if you develop a feature on a branch that consists of five small commits, and later you realize one of those commits is actually a useful bugfix that should go into the release branch, you can cherry-pick that one commit onto the release branch. This will be painless if that commit doesn’t depend on the other four commits from the feature. Teams often find themselves needing to rearrange the order of commits (during rebase) or drop a commit from a series; atomic commits make this process straightforward. The Git workflows documentation notes that commits should work independently of later commits 1. That implies that if you stop the history at any given commit, everything up to that point works – and likewise, you could potentially reorder some commits without breaking things. While not every sequence of commits is reorderable (sometimes A must come before B), following the practice of one-per-change and keeping them buildable maximizes flexibility. In patch-driven projects like the Linux kernel, it’s acceptable for one patch to depend on another, but it should be noted in the description and each patch still must be justifiable on its own2. The takeaway is to minimize hidden interdependencies between commits. If commit X is meant to prepare for commit Y, ensure X by itself doesn’t put the system in a faulty state; it should lay groundwork harmlessly, and then Y builds on it. This way, maintainers could reorder or drop commits during integration if needed (for example, if one commit in a series isn’t ready, they might defer it and take the others).

Small commits simplify merges. When everyone commits in small, self-contained chunks, integrating changes via merges becomes easier as well. If a merge conflict occurs, it’s often easier to resolve when the commits involved are narrow in scope. Moreover, the chances of two developers editing the exact same lines or files in the same commit are lower if each commit is focused. In case of conflict, understanding what each side was doing (from the commit messages and diffs) is clearer with atomic commits. Finally, debugging issues is easier: if a bug is introduced, you can often pinpoint it to a single small commit. If needed, you can revert that one commit to fix the bug, rather than backing out a large grab-bag commit that also contained unrelated changes.

Tested and Documented Commits

A commit doesn’t just represent a code change; it represents a change in the state of the project. Therefore, any ancillary updates required by that change should be included in the same commit whenever practical. This ensures that the repository remains consistent and that anyone looking at that commit sees the full context of the change.

Update tests along with code. If your change is supposed to be covered by tests – for example, you fix a bug or add a new feature – consider adding or updating the tests in the same commit. An oft-quoted rule for atomic commits is that all necessary pieces go together. “If you add a new feature, the same commit should ideally also add automatic tests for the feature to ensure it won’t regress”9. The logic here is straightforward: the feature and its tests are one logical unit of work. They either both go in, or neither does. Shipping code without its tests in the same commit could mean that, for a brief period in history, the code is untested (or incorrectly tested), or that tests in a later commit might be testing behavior introduced in an earlier commit (making that earlier commit not fully verifiable on its own). By including tests in the commit that introduces the functionality, you guarantee that anyone checking out that commit can run the test suite and see tests passing, including the new ones. It also documents the intended behavior of the code at the moment it’s introduced. For example, if you fix a bug in function parseData(), add a test case in that commit demonstrating the bug is fixed – so future readers of the history can clearly see the before/after effect via that test.

Keep documentation in sync. Documentation and configuration files should likewise be kept up-to-date with the code changes. If your commit changes how an API works, update the README, user guide, or code comments in that same commit to reflect the new behavior. If you’re adding a new feature that users should know about, it might warrant an entry in the CHANGELOG or release notes – don’t postpone that to some future commit. By updating docs as part of the change, you reduce the chance of forgetting to do it later. In the spirit of atomic commits: everything related to that change goes together. One common practice in many projects (including Angular and others) is to have a commit type for documentation changes (often docs:). This makes it clear that the commit affects documentation. For instance, a commit message might start with docs: update README with new configuration options. But importantly, if the documentation update is tied to a code change (e.g., adding a new config option), it can be part of the same commit that introduces that option, or a directly subsequent commit labeled as docs. In either case, it should be done before the change is considered “complete.” A good guideline is: if someone checked out your commit, would they have all they need to understand and use the change? If not, consider what’s missing (tests, docs, example config, etc.) and include it.

Ancillary files and metadata. Don’t overlook other repository files that might need updates. For example, if you add a new contributor in a project that tracks authors in a CONTRIBUTORS file, update that in the commit where appropriate. If you make a change that affects the build or deployment, update any configuration or build scripts as needed. In a documentation-driven project, if you change a function signature, update the relevant docs or even generated docs if they are version-controlled. All these practices ensure consistency. The project should not be in a state where the code says one thing but the README says another (at least not within a single commit’s snapshot of the repo). By keeping commits self-contained in this way, you again make it easier to revert or cherry-pick changes: you won’t accidentally revert code and leave stale documentation behind, or cherry-pick a feature without its necessary docs.

Example: Suppose you remove a deprecated command-line option from a CLI tool in a commit. A well-scoped commit for this would delete the code handling that option, update the help text or README to no longer mention it, and perhaps adjust any reference config files or tests that used it – all in one commit titled something like feat(cli): remove deprecated "verbose" option (BREAKING CHANGE). This commit fully encapsulates the removal of the feature. If later someone needs to revert this removal, the revert commit will bring back not just the code but also the relevant documentation and tests, keeping everything consistent.

Include CHANGELOG entries when relevant. Some repositories maintain a CHANGELOG.md manually. If your project does this, consider adding an entry in the changelog as part of the commit that introduces a notable change (especially for user-facing features or fixes). This way, you tie the changelog update to the change itself atomically. However, many modern workflows use automated generation of changelogs from commit messages (e.g., Conventional Commits with tools that aggregate feat and fix commits). If that’s the case, ensuring your commit message is descriptive and follows the convention is how you “update” the changelog. For example, Angular’s contributing guide and the Conventional Commits specification define commit message types like feat (feature), fix, docs, etc., and a special marker for breaking changes 10. Following such a convention helps downstream tools pick up your commit for release notes. For instance, if your commit introduces a breaking API change, including BREAKING CHANGE: in the commit message footer is an established practice10 – it signals to maintainers and automated tools that this commit is meant to introduce a deliberate breaking change in functionality.

Clear Commit Messages

(While this article focuses on commit content, it’s worth noting that good commit practices go hand-in-hand with good commit messages. A few guidelines are mentioned here for completeness.)

Always accompany your well-scoped commit with a clear, descriptive commit message. Many organizations have style guides for this. For example, the Angular project’s commit message format (which influenced the Conventional Commits standard) requires a structured message like <type>(<scope>): <short summary> 11. Even if you don’t follow a specific template, follow general best practices for messages: use a short summary line (50 characters is a common recommendation) describing what the commit does, and a longer body if needed to explain why and how. Use the imperative mood (“Fix bug” not “Fixed bug”), as if giving an order to the codebase3. This is consistent with messages generated by Git for merges or reverts and is widely adopted. For example, instead of writing “I added a check for null inputs”, write “Add check for null inputs”.

If your commit is one in a series, it can be helpful to mention relationships (e.g., “Part 1 of 3” or “Prerequisite for X feature”) in the body. And if the commit introduces a breaking change or deprecates something, explicitly call that out – some conventions use BREAKING CHANGE: in the message body to flag this10. This practice ensures that when scanning history, such commits stand out. It also ties back to the idea of intentional breaks in the test or API: by noting it in the message, you signal to everyone that the breakage is acknowledged, not accidental.

Example of a well-formatted commit message:

feat(api)!: remove deprecated endpoints

Remove the deprecated v1 API endpoints `GET /users` and `POST /submit`.
This commit deletes the code handling these endpoints and updates the API documentation and tests accordingly.

BREAKING CHANGE: Clients using the removed endpoints will receive HTTP 404 errors. They must migrate to the v2 endpoints introduced in version 2.0.

In this example, the commit title follows a convention (feat type with a ! to indicate a breaking change). It clearly states what the commit does. The body explains details – what was changed and why. It also notes that documentation and tests were updated (showing the commit is comprehensive), and explicitly calls out the breaking change for downstream users. Anyone reviewing the history can immediately grasp the impact of this commit.

Conclusion

Writing small, self-contained commits is a discipline that pays dividends in team productivity, code quality, and project longevity. By adhering to the conventions outlined above – one logical change per commit, always leaving the code in a working state, and including all relevant updates – you create a Git history that tells a coherent story. Such a history is easy to navigate, making debugging and code archaeology far less painful. It also facilitates smoother code reviews and collaboration, since each commit is focused and can be discussed in isolation.

These best practices are reflected in the guidelines of some of the most respected software communities. The Linux kernel, for instance, requires patches to be logically separated and bisect-friendly2, and projects like Angular enforce structured commit messages for clarity and automated tooling11. Tools and specs (Git itself, Conventional Commits, etc.) have evolved to encourage this style of work because it leads to more maintainable software. As a developer, making a habit of crafting atomic commits with clear messages is a mark of professionalism and care for your craft.

In summary, commit often, commit intentionally, and commit completely. Each commit should be an island of functionality: small but whole. By following these conventions, you ensure that any commit in your project’s history can be understood, built, tested, and if necessary, reverted or reused with confidence. This fosters a robust development workflow where changes are tracked and communicated effectively through version control. As the proverb goes, “take care of the pennies and the pounds will take care of themselves” – in Git terms, take care of the commits, and the codebase will take care of itself.

TL;DR

  • Make incremental changes in each commit, keeping them small: Write the smallest amount of code necessary to implement a single change or fix. This approach makes each update easy to test and roll back without affecting unrelated functionality12. Small commits also reduce the likelihood of merge conflicts by minimizing overlap with others’ work 12.
  • Limit each commit to one logical change or issue: Do not bundle unrelated fixes or features in the same commit. For example, if you fix a bug and improve performance, use separate commits for each; a commit should serve as a wrapper for related changes only 2,4. Keeping commits focused ensures they are easier to understand, review, and revert if something goes wrong4.
  • Keep commits atomic and self-contained: Ensure every commit is a single, self-sufficient unit of work (one task or fix) that can be applied or reverted in isolation without side effects12. An atomic commit encapsulates a complete thought – for instance, a code refactoring and a new feature should be in separate commits – which makes code reviews faster and revert operations safer12. Each commit should be justifiable on its own merits and not require later commits to be understandable2.
  • Avoid committing half-done work: Only commit when a piece of functionality or bug fix is fully implemented and tested. Incomplete code (such as a partial feature that doesn’t yet work) does not belong in the main commit history 4. If you need to checkpoint work or switch context, use a draft/WIP branch or Git’s stash feature rather than committing unfinished changes4. This ensures that every commit in the shared history represents a coherent, finished change.
  • Commit early and commit often: Frequent commits prevent individual commits from growing too large. By breaking development into quick, logical chunks, you make it easier for teammates to integrate changes regularly and avoid large merges 4. Short-lived branches with regular small commits minimize divergence from the main branch, reducing integration problems and easing collaboration12.
  • Ensure each commit builds and passes tests: Treat the repository as if it should be in a working state at every commit. Compile the code and run the test suite for the project after each commit to verify nothing is broken2. This guarantees that any commit can be checked out on its own and will function correctly, which is crucial for tools like git bisect and for safely reverting commits in isolation2.
  • Test and review before committing: Resist the urge to commit code you think is complete—run the code and tests to be sure. Verifying the change in isolation (including checking for side effects) is important for quality 4. Additionally, self-review the diff before you commit to confirm that only the intended changes are included and that you haven’t introduced debugging statements or unrelated edits. This extra scrutiny helps maintain focus and catch mistakes early13.
  • Use interactive staging to craft focused commits: Leverage Git’s staging tools (for example, git add -p or git add --interactive) to stage changes selectively. This allows you to split a mixed set of edits into separate commits for each concern13. By carefully choosing hunks of code to include, you ensure each commit contains only relevant changes, which reinforces a clean separation of ideas and makes the history easier to understand.
  • Separate formatting or whitespace changes from functional changes: If you need to reformat code, fix indentation, or make other non-functional style tweaks, do so in a dedicated commit without mixing in code logic changes14. Isolating purely cosmetic changes prevents noise in the diff and lets reviewers concentrate on the substantive modifications. In practice, such a commit should contain no semantic code changes, making it clear that its purpose is only to improve readability or adhere to style guidelines14.
  • Isolate large-scale code movements or renames in their own commits: When moving code between files or renaming classes/functions, perform the move in one commit and make no other changes in that same commit2. This clear separation means that the move/rename commit purely relocates code. The subsequent commit can then modify that code if needed. Keeping moves separate greatly aids reviewers (who can verify no logic changed during the move) and preserves file history for tools like git blame2.
  • Use topic branches to organize development: Develop new features or fixes on separate branches rather than on the main branch. Branching isolates your work until it’s ready, which avoids polluting the main history with WIP commits and allows for code review before integration12. Once the work is complete and each commit is polished, merge the branch back (or rebase onto main) so that the main line of development remains stable and linear. This practice also makes it easier to revert or cherry-pick a set of changes if they were developed in an isolated branch.
  • Establish a consistent team workflow and commit policy: As a team, agree on how you use Git – for example, whether you squash commits on merge, if you prefer rebase over merge commits, how you name branches, and the expected commit message format. A defined workflow (such as GitHub Flow or Git Flow) and shared conventions ensure that everyone works in a compatible way4. Consistency in commit practices across the team promotes clarity and makes the project history more predictable and maintainable.
  • Adopt a standard commit message format: Use an established convention for commit messages to provide structure and meaning. For instance, many projects follow the Conventional Commits or Angular format, where each message begins with a type like feat:, fix:, docs:, etc., optionally followed by a scope, and a brief description10. Having a consistent format makes the history easy to parse and can enable automated changelog generation or semantic versioning tools.
  • Begin each commit message with a concise, descriptive summary: The first line of the commit message (the “subject”) should quickly convey what the commit does. Write it in imperative mood (as if giving an order, e.g. “Add user login audit log” not “Added” or “Adding”) and in the present tense12. Keep this summary line short (around 50 characters or less) and capitalize the first word. A good summary allows others scanning the commit log to immediately grasp the intent of the change.
  • Provide explanatory detail in the commit message body when necessary: If the reason or context for the change isn’t obvious from the code diff and summary alone, include a body in the commit message after a blank line below the summary. In this body, explain why the change was made and any background info or implications, not just what was done4. Wrap the text at roughly 72 characters per line for readability4. A well-written body can describe the problem being solved or the decision behind the implementation, helping future maintainers understand the commit without needing external references12.
  • Reference relevant issues, tickets, or external links in the footer: When a commit relates to a discussion, bug report, or feature request, mention that in the commit message. For example, include “Fixes #123” or “Refs: issue-456” in the footer to automatically link the commit to issue trackers10. This practice provides traceability – anyone reading the commit can discover the broader context or see that an issue was resolved by that commit. It also helps project management by closing issues when commits are merged, if the repository host supports it.
  • Maintain consistency and clarity in commit messages: Ensure each commit message adheres to the agreed style and contains enough information to stand on its own. Describe your changes in a way that other developers (or your future self) can understand the history without guesswork. For example, rather than just saying “Update code” or having an empty message, always supply meaningful detail. Consistent, well-structured messages across the project make the version history a valuable narrative of the project’s evolution 12.
  • Leverage tooling to enforce commit quality: Use automated checks and hooks to uphold your commit standards. A commit-msg hook can verify that commit messages meet your formatting rules (for example, rejecting messages that don’t follow the Conventional Commits pattern), and a pre-commit hook can run linters or tests to prevent code that fails checks from being committed. Many teams integrate these into their workflow so that commits that don’t meet the project’s guidelines are flagged or rejected before they reach the main repository. Automation in this way helps sustain high quality and consistency in a collaborative environment.
  • Strive for a clean, bisectable history: Each commit in the history should be meaningful and not merely a “fixup” for the previous one. Try to correct small mistakes (typos, minor bugs introduced in the last commit, etc.) by amending the prior commit or via an interactive rebase before pushing, rather than adding a new “fix typo” commit. The goal is a tidy commit log where any commit can be understood on its own and, if needed, reverted without having to also revert subsequent “patch” commits. This discipline makes tools like git bisect more effective and the project history more professional and maintainable.

References


  1.  ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
  2.  ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
  3.  ↩︎ ↩︎ ↩︎
  4.  ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
  5.  ↩︎ ↩︎
  6. Hacker News – community discussion about using failing tests for git bisect (When fixing a bug, add a failing test first, as a separate commit | Hacker News↩︎ ↩︎

  7.  ↩︎
  8. Stack Overflow – discussion on why small, atomic Git commits are easier to understand, revert, and cherry-pick (Why I need small Git commit every time? - Stack Overflow↩︎

    • Optimized by Otto Blog – “Five requirements for a good git commit” (atomic, with tests and docs included) (How to make a good git commit)
     ↩︎
    • Conventional Commits v1.0.0 – specification for structured commit messages (types like feat/fix/docs, and marking breaking changes) (Conventional Commits)
     ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
  9. Angular Project – Contributing Guidelines (commit message formatting rules that inspired Conventional Commits) (angular/CONTRIBUTING.md at main · angular/angular · GitHub↩︎ ↩︎

  10. GitLab – “What are Git version control best practices?” (advice on making incremental, small changes) (GitLab Blog↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  11. DEV Community – “Make Small Commits” by oculus42 (tips on using IDE tools to manage focused commits) (Make Small Commits - DEV Community↩︎ ↩︎

  12. Stack Overflow – Committing when changing source formatting? (discussion on separating cosmetic from functional changes) (version control - Committing when changing source formatting? - Stack Overflow↩︎ ↩︎

When the Earth Held its Breath

When the earth held its breath,
and the trees stopped speaking in green,
I found your shadow pressed into the silence—
not like absence,
but like the memory of warmth
left in a chair
after someone has risen.

I did not call your name.
It was already there,
inscribed beneath the skin of rain,
folded into the hush of wheat fields
bowing under the weight of their gold.
Even the wind carried you—
not as sound,
but as the echo of longing
before the voice has formed.

There were no angels.
Only the dust rising
from the soles of tired workers
who knew love by its weight,
not its wings.
And still, the sun leaned low,
willing to touch the dirt
just to reach us.

You were the breath I took
before understanding what it meant
to be hollow
and still full.
You were the salt in my wound
that sang.

Oh, what a terrible, beautiful thing—
to be stitched into another’s silence.
To be the ache
someone calls home.
To carry within you
the whole cathedral of their absence,
lit by nothing
but the soft, persistent flame
of remembering.

And still—
I would carry it.
The ache, the salt,
the tender ruins of your voice
crumbling somewhere
between my ribs.

I would carry it
into the next life
and the next,
and the next—
not because I must,
but because
even grief
was more beautiful
with you in it.

-- Jeffrey Freeman

A poem I just wrote as I sit here missing Noi Noi. She is so far away its hard, but I hope to see her soon.

Eugen Rochko, CEO of Mastodon, Caves to Nazi's Agenda

UPDATE: Eugen Rochko responded publicly about this and indirectly confirmed what was said here, see update at the end.

I know, clickbait title right? Sadly no. When we say Nazi here we dont just mean your garden variety racist, as bad as that is, but your swastika wielding, genocide wanting, literal Nazi. But lets take a step back for a moment and start from the begining. Its been a long road with many twists and turns, its a very damning accusation, and its important we lay out all the details, show the proof, and give you all the information for you to decide for yourself.

I am the founder of one of the oldest instances (servers) on the Mastodon network, QOTO. It is almost 5 years old and we have been building this network along side Eugen Rochko since the begining. There has been a fair bit of drama and while Eugen Rochko has been more or less consistent in stamping out hate speech and prejudice this seems to be limited to when its popular rather than when its right. But this isnt the story of Eugen, in many ways its the story of QOTO and the impact Eugen has played in the network and towards our little corner of it.

The begining, the exodus

QOTO was founded in July of 2018 out of a need to create a STEM oriented instance that respected and protected the safety of its userbase, in particular that of the LGBTQ+ community. At its founding the network was much simpler than it was today, only a handful of instances and moderation was simplistic.

QOTO only had a dozen or so users at the time and one morning in July of 2019 when I woke up I noticed we were getting hundreds of new users a day. I had no idea what had changed, excited I started asking around to see why we were so popular. As it turns out there was a fracturing throughout the network underway and QOTO was slowly becoming a focal point. To my shock it turned out that GAB, a social media network filled with hatred, racism, sexism, and all forms of prejudice, had decided to enter the Mastodon network. When this happened instance owners around the network took a hardline approach that divided the network down the middle. On one side instance owners decided to start forcing its users to not be able to see Gab content and blocked them from even seeing instances that did not likewise follow suit, users couldnt follow anyone, even from well behaved instances, if those users were allowed to see Gab at all. On the other side many servers allowed the content unrestricted. To further compound the issue the instances that allowed the content generally had little to no moderation policies themselves, so hate speech ran rampant on those servers. Meanwhile the servers that did block the content tended to be very draconian and were blocking users for very trivial reasons and "dog whistles" and it became a witch hunt. The reason QOTO became a focal point at this time is because we were one of the only servers that allowed our users to choose what content they wanted to block, and made our blocklist optional, but at the same time had a zero tolerance stance for hate speech and prejudice. It became a haven for the LGBTQ+ community that wanted the freedom to choose for themselves what instances make the cut or not.

I think it's important I back up as much as I can with the evidence here, therefore, you can view our strong protections for the LGBTQ+ in QOTO's Terms of Service here: https://qoto.org/about/more. Below are some excerpts from that link. For the sake of transparency it is important for me to point out in the early days when we were smaller these protections weren't as clearly stated and were added as we grew and the issue needed to be more clearly addressed; however, we have always had a strong emphesis on engaging in respectful discourse and have had a heavy hand when it comes to banning those who engaged in hate speech.

All cultures welcome

Hate speech and harassment strictly forbidden.

hate-based speech such as sexist, racist, or homophobic speech will not be tolerated, be kind to each other.

QOTO aims to provide a community where our users do not fear being punished for their personal opinions. We do not allow people to disseminate ideologies that are abusive or violent towards others. Demonstrating support for or defending ideologies known to be violent or hateful is a bannable offense. This includes, but is not limited to: racial supremacy, anti-LGBTQ or anti-cis-gender/anti-straight, pro-genocide, child abuse or child pornography, etc. While we recognize questions and conversation regarding these topics are essential for a STEM community, in general, doing so in bad faith will result in immediate expulsion.

What will get you banned: ... Hate-based racism, sexism, and other hateful speech...

As our numbers quickly grew into the thousands the issue of what to do about Gab and our stance on censorship came under scrutiny. We had to decide officially where we stand and what rules we wanted to make. Since our community at the time was largely made up of the very victims affected by everything going on we felt it best to hold a private discussion to decide how we could best protect the interests of our LGBTQ+ community. We had weeks of discussion and debate around the issue, but there was a strong consensus in the end. You see, there were some very bad actors on the Gab network, people like Milo Yiannopoulos, who had a habit of doxxing and making calls to violence against people in the trans community. Him and people like him were at the center of the debate. As it turns out the overwhelming majority of the LGBTQ+ that had migrated to our server did so because it was one of the few places left they could monitor these bad-actor accounts. They would keep a close eye for attempts at doxxing or calls to violence and then quickly disseminate that to their communities and warn the parties at risk. This was critical for ensuring the safety of the lives of many in our community. We still weren't sure what to do so we discussed alternative ways to monitor these accounts, each of which were shot down for various reasons. I will summarize some of the alternatives we considered and why the community ultimately rejected it below.

Open an account on Gab for monitoring - This was immediately rejected as it would expose the victims emails, ip addresses, and usage patterns to the admins of Gab who were not trusted.

Use RSS feeds - This wouldn't give access to follower-only posts and were not always enabled on many accounts

Use a VPN - VPNs only work when you remember to use it. If a user forgot, even once, to turn on their VPN then their IP would get exposed, a risk they weren't willing to take.

Needless to say we didn't have any good solutions that were in the best interest of our community. In the end we made a very controversial decision. We decided to let people have access to view these accounts but with very strict policies against anyone who would show any support for the hateful content. If anyone was shown to encourage or support the hate coming from these sites they would be immediately met with a ban and we encouraged all new users to import a block list that would block these servers outright, at their discretion.

Unfortunately this caused a lot of backlash. Many instance admins were bitter over losing such a huge chunk of their user base to us, particularly when they perceived their own actions as justice rather than oppressive. Many of the instance admins started misinformation and hate campaigns against QOTO and there was a lot of backlash and QOTO even got on a few block lists as a result. But since the safety of the lives of our LGBTQ+ community were a point we refused to compromise on our stance remained absolute.

Later we even took it one step further. You see in Mastodon when you follow an account this alerts the account that you are following them. This was a big concern since it might expose our LGBTQ+ members who were monitoring accounts and fear for their privacy was at stake. As a result we implemented a unique feature only present at QOTO called subscriptions. It effectively allowed our users to follow an account without alerting the account they were being followed. It was privacy-respecting, however, specifically designed so it only allowed you to see posts a user made as a public post. Any posts that were restricted to follower-only or private could not be seen with a subscribe, you still needed to do a follow for that. Despite the privacy-respecting aspect of the feature the hate and disinformation campaign against QOTO continued by a handful of bitter instance admins. They lied and made announcements about our feature as privacy-violating and claimed, erroneously, that it bypassed security and let users view follower-only content, which of course it didn't. The divide grew even larger, and as it did our following grew even larger too.

Acceptance on the joinmastodon directory

Despite the divide across the network we still have a huge LGBTQ+ community and a large following in the thousands by this point. We therefore applied to join the joinmastodon.org directory, the official directory of Mastodon maintained by Eugen himself. The criteria for getting on this directory are listed clearly and since we qualified, and because of the applause of our supporters, we eventually made it to the list.

As a specialty instance that caters specifically to the STEM community this was a crucial milestone for us. It increased our influx of new users substantially and allowed us to reach a level of healthy growth. Due to our popularity Eugen generally defended the attacks against us from instance admins still bitter of the earlier exodus. This was looking good for QOTO and the fediverse.

Enter snow, the Nazi

By this point in the story QOTO had grown to tens of thousands in size and was in fact one of the largest specialty instances in the entire network. Aside from a handful of bitter instance owners our reputation was excellent and we were highly recommended by our peers. We were consistently seeing hundreds of new users a day and we were growing steadily.

In stepped a new user, a software engineer who went by the moniker Snow. At first he seemed unsuspecting and we had no reason to worry about him, but things quickly went downhill. Snow started posting content largely targeted at transgendered individuals criticising the choice to be trans as unhealthy. He quickly was met with opposition from our community and shortly after his hate speech began he found himself blocked from our community. He tried opening alt accounts and to protest the ban but we weren't having any of it, he wasn't welcome back.

Shortly after Snow was booted he tried to join a few other communities to use as a platform to discredit QOTO, some of which were somewhat accepting of hate speech but ultimately he was banned from all of them. Furious and determined Snow started his own instance called wintermute, his profile littered with actual swastikas and Nazi insignia, he called for genocide against trans people and other horrific acts. Sadly he even managed to gain a following at his server of similar minded Nazis.

Snow ultimately used this to his advantage and began coordinating an attack on QOTO whose effects linger to this day. He publicly posted from his main account a call to arms for his followers to flood fake accounts on QOTO and the fediverse at large. Their tactic was to post hate speech on QOTO, then immediately report it across several of the progressive instances around the fediverse in an attempt to make us seem like a pro-nazi forum. While the original post is no longer online as the server has since been taken down you can see a screenshot of this and a related post below.

Of course QOTO quickly deleted and suspended any accounts posting such content. But because their alts reported it immediately those reports still showed up on hundreds of other servers coming to the attention of their admins. Not only did he coordinate this attack by reporting the content across many servers around the fediverse but he also reported it to several block lists for further visibility. Below is a screenshot of him attempting to report it to the Fediblock block list. The link in the below screenshots originally went to the posts I showed above. Luckily he didn't manage to succeed with Fediblock but he did manage to get us on quite a few block lists as a result.

Needless to say the previously small number of instance admins that still had an issue with QOTO used this as fuel for the fire. They spread the news of the reports in an attempt to defame QOTO and because the reports were so widespread it worked. QOTO quickly started showing up on numerous block lists around the fediverse. Needless to say it is hard to fight against a misinformation campaign, lies spread faster than truth.

Picking up the pieces

As the admin of QOTO I was left trying to figure out some way to counter the disinformation that had spread. Like most disinformation campaigns it took on a life of its own. People saw the lies, bought them, and their gut reaction was to hate us without ever bothering to check the facts. Once someone hates you it is very hard to reason with them, the lies compound, gossip ripples, and soon everyone swears they have a cousin who was the victim of an attack. Of course anyone who spent even a moment looking through QOTOs timeline would see we are a place where acceptance and concern for the LGBTQ+ community remains our top priority. We retained a loyal band of supporters, but our enemies were growing no matter how much goodwill we tried to spread.

In an attempt to remedy the situation I decided to reach out to key members in the community and explain the situation. I found a seemingly innocuous website on the internet that listed all the instances that blocked QOTO. I went down the list and started reading the about pages looking to see what administrators had contact information and welcomed people reaching out to them. Of the servers that blocked us about 20% of them explicitly had contact information and welcomed others reaching out about administrative matters. So I started emailing them one by one and tried my best to politely and respectfully explain what had happened in the hopes we could reconcile.

Unfortunately this backfired, many of these administrators had made up their mind before ever hearing our side of it. They used my friendly efforts to reach out and mend things as an attack, going so far as to call it harassment simply for contacting them at all. To add insult to injury it just so happened that the tool used to generate the list that ultimately made its way to the website was written by a member of KiwiFarms, a vile and hateful community. The website itself didn't have any indication of this in the domain name and at the time I was unaware of this fact. Nonetheless this information was used to further defame us, as if we are somehow responsible for the tool a website author happened to use. Regardless of this a new campaign of misinformation and lies spread to try to attack us for emailing them and now the claim is centered around harassment.

For the sake of openness and providing all the evidence the full content of the letter I sent to the administrators mentioned above is included below:

Hi, I noticed you had QOTO blocked, I figured this might be a mistake as there has, for some time, been attempts to lie about our policies by some users we blocked for hate speech and sadly some of the community followed along.

Snow, some years back, was blocked for hate speech and he began a campaign against us spoofing various users claiming we accept hate speech on our instance and other outright lies. At one point he even attempted to create accounts on QOTO that posted hate speech and despite getting suspended shortly after, by doing so in the middle of the night, some of it was noticed for the short time it was up.

My guess is some of this misinformation made its way to you and you, understandably, may have blocked our instance.

We also had some similar trouble many years ago where the LGBTQ community was being attacked and they fled, en masse to our server (which is how our server became so popular early on). This caused several people to attack us in a similar manner spreading quite a few lies as well.

For more information detailing that incident please see this post, it also explains our subscribe feature, which is privacy-respecting and provides the same level of access as using an RSS reader to follow an account: https://qoto.org/@freemo/109319817943835261

I urge you to actually review our timeline and see for yourself, hate speech, harassment, misinformation, etc has never been tolerated in our instance and still is not.

To recap some of the misinformation:

  1. We have very strict and aggressively enforced policies against hate speech in all forms, you wont find any on our timeline and if you do and report it you will find it is usually acted on with a suspension very quickly.

  2. Our subscribe feature is privacy respecting and was specifically implemented to protect the physical safety of our LGBTQ community during their mass exodus to QOTO in the early years. Similarly, for their protection and at their request, is why we only silence servers and do not suspend them (see the post I linked above for the details on this).

  3. harassment and other issues are also strictly enforced on our server.

If you wish to discuss this further please feel free to reach out to me either by email or by reaching out to me at @freemo@qoto.org

Eugen caves to Nazi's campaign

This leads us to Eugen Rochko's role in all this. Up until this point while QOTO was still popular with the majority of the instances he had defended his position to keep us on the joinmastodon official directory of instances. However with the misinformation spread by Snow and the hate this ultimately generated directly and indirectly from this incident he decided to delist us from the official directory over at joinmastodon.org. He did this knowing full well what happened with Snow, he knew Snow's  efforts were to get us suspended, delisted, and blocked and knowing this was all the agenda of a Nazi and his cohorts he still decided to cave to popular pressure rather than to do what was right and set the record straight and stand by the LGBTQ+ communities safety above popularity. This is a shameful and outright disgusting act on his part. I believe it is fueled largely by the increased visibility Mastodon has had lately as people are migrating en masse from twitter to the network. He cared more about appearances than the actual safety of the lives of the LGBTQ+ community and this is disheartening to say the least.

I had reached out to Eugen and explained the situation in detail. There has also been a huge outpouring of people on both Twitter and Mastodon trying to contact him and get him to see reason. Other than a short email response he has completely refused to even engage in the conversation at all.

There are several concerning consequences of this decision, particularly the silence that accompanies it.

  1. It sends the message that as long as a Nazi or other bad actor can generate enough support his demands will be appeased.

  2. It lends credibility to the Nazi's claims, giving the impression to others QOTO was delisted because the claims against it were true.

  3. It significantly reduces our influx of new users. As a specialty instance it is not easy to find new users as it would b e for a generalist instance.

  4. it no longer shows up when trying to connect using the official mastodon app which relies on that list as part of its search. This causes people to have a hard time connecting even if they are already here.

He was aware of the incident with the group of Nazis, he knew their agenda, and in the end he supported the Nazi's agenda to get us banned and delisted because it was the popular thing to do. Nothing more, nothing less. In his own words in the one reply he managed to muster about this incident "I will take a listing down if it risks endangering the reputation of Mastodon". A man who cares more about Mastodon's "reputation" than he does about the physical safety of the LGBTQ+ community has no business being in charge.

Update: Eugen indirectly verifies content of this article

See Eugen's response here, which I will comment on: https://mastodon.social/@Gargron/109402824022874888

Here is a link to all the changes that occured to our ToC over the last 6 months. As can clearly be seen the only changes made were stronger protections for the LGBTQ and replacing the phrase "free speech" with "Academic Freedom".

https://web.archive.org/web/diff/20220525221306/20221122160257/qoto.org/about/more

Which means the only perceived changes he could mean are either greater protections to LGBTQ groups or he is caving to the misinformation spread by Snow.

I already mentioned in the article the emails he refers to, which were sent only to admins with email addresses that welcomed administrative contact. They servered the purpose of alerting them as to Snow's disinformation and an attempt to set the record straight. They were sent, by hand, to a very limited number of admins.

Seems quite clear, even by Eugen's own admission that what I said in this post is completely factual afterall. He did, cave to the misinformation spread by a Nazi.

In case the link to Eugen's response is taken down here is a screenshot:

Spacemacs Ultimate Cheatsheet

A cheatsheet I wrote for all the Spacemacs hotkeys worth knowing.

    / [pdf]

Download Cheatsheet