Friendly commits

23/12/2025

Cherry-pick friendly commits

Lesson I re-learn today: Commits are not just a track of what you did - they should be independently mergeable units.

What is my model?

I think of commits like of React components:

// Bad: God Component (does everything, hard to reuse)
const EverythingComponent = () => {
  // 500 lines of mixed concerns
}

// Good: Single Responsibility (easy to compose, reuse, test)
const Button = () => { /* one job */ }
const Input = () => { /* one job */ }
const Form = () => <><Input /><Button /></>

Same applies to commits, here are the one I created <ups>

# Bad: God Commit (hard to cherry-pick, review, revert)
"Add Carousel, refactor ImageCarousel, update imports, fix bug"

# Good: Atomic Commits (easy to cherry-pick, review, revert)
"Add Carousel component"
"Refactor ImageCarousel to use Carousel"
"Fix scroll state initialization"

So what are the rules i find for cherry-pickable commits

Quick the "and" rule

When you think to create a commit message and in your had you see that you would want to add word "and" it's a sign the commit should be split further.

# Bad: Contains "and"
"Add Carousel and update imports"
"Fix bug and refactor helper"

# Good: Split them
"Add Carousel component"
"Update imports to use Carousel"

First self-contained (like pure components)

Each commit should:

  • Build successfully on its own
  • Not break existing functionality
  • Include ALL files needed for that change
// React: Component includes its dependencies
import { useState } from 'react'
const Counter = () => { /* works standalone */ }

// Git: Commit includes its dependencies
+ helpers/calculateDays.js     // new function
+ components/Card.jsx          // uses the function

Next correct dependency order (like import order)

// React: Can't use before define
import { helper } from './helper'  // must exist first
const Component = () => helper()

// Git: Can't cherry-pick usage before creation
Commit 1: "Add helper function"        // creates it
Commit 2: "Use helper in Component"    // uses it

This is important lesson for me: parallel paths

When refactoring, keep old code working:

// React: Gradual migration
const NewCarousel = () => { /* new implementation */ }
const OldCarousel = () => { /* still works */ }

// Usage can switch gradually
<NewCarousel /> // or <OldCarousel />
# Git: Same pattern
Commit 1: "Add NewCarousel component"      # old still works
Commit 2: "Switch Media to NewCarousel"    # migration
Commit 3: "Remove OldCarousel"             # cleanup

Then avoid extract + delete + use in one commit

# Bad: All in one (breaks cherry-picking)
- Delete OldComponent.jsx
+ Add NewComponent.jsx
+ Update imports in 5 files

# Good: Separated (each cherry-pickable)
Commit 1: + Add NewComponent.jsx           # safe addition
Commit 2: + Update Consumer to use New     # safe switch
Commit 3: - Delete OldComponent.jsx        # safe removal

Now before committing I ask myself: "can someone cherry-pick just this commit onto master?"

If no - split it further. Ofc sometimes it will needed to pick a few commits in the row for feature to be fully movable but still it makes it easier the having one commit with many changes.

Here is my example, what I did and was problematic:

5d9fb929 "Rename ImageCarousel to ImageCarouselWithDialog"
  - Deletes ImageCarousel.jsx (233 lines)
  - Creates ImageCarouselWithDialog.jsx (uses Carousel)
  - Updates MarkerData import
  # Problem: Depends on Carousel.jsx from previous commit

# What would be better, from my perspective is to split it:
xxxxxxxx "Add ImageCarouselWithDialog component"     # standalone
xxxxxxxx "Switch MarkerData to ImageCarouselWithDialog"
xxxxxxxx "Remove deprecated ImageCarousel"