| |

Git 5 🧩 Git Best Practices 🧩 Troubleshooting

Good Git habits aren’t about following rules for their own sake — they’re about making history readable, reviewable, and reversible. A clean commit log tells a story. A messy one forces everyone to read diffs. This chapter covers the habits that separate a Git user from a Git practitioner.

Key point: The goal of good Git practice is clear history. If someone reads your log six months from now, they should understand what happened and why — without opening a single diff.


Writing good commit messages

A commit message is the single most-read piece of documentation in a repository. It’s also the one most often written carelessly.

The structure:

  • Subject line — 50 characters max, imperative mood, no period
  • Blank line separating subject from body
  • Body — wrapped at 72 characters, explaining what and why
Fix login validation error

The previous implementation failed to validate email format properly,
causing authentication failures for valid users. This commit adds
proper regex validation for email addresses.

What makes a good subject line:

RuleExample
Imperative moodAdd login form (not “Added” or “Adds”)
Under 50 charactersFix typo in API docs
No periodFix typo in API docs (not Fix typo in API docs.)
SpecificFix null pointer in user lookup

Why imperative mood: Git itself uses imperative in auto-generated messages (“Merge branch…”, “Revert…”). Consistency matters.

Why 50/72: Traditional terminal width. Short subject lines fit in one line; wrapped bodies read cleanly.

What goes in the body:

  • What changed — briefly
  • Why it changed — the important part
  • Context — links to issues, references to discussions
Add rate limiting to public API

Users were hitting the endpoints too frequently, causing
degraded performance for everyone. This adds a token bucket
limiter at 100 req/min per IP.

Closes #452

Why this matters: The subject answers “what happened?” The body answers “why?”. If only one is present, half the story is missing.


Atomic commits

An atomic commit contains exactly one logical change. Not two. Not a mix of features and fixes.

The rule: Each commit should be independently meaningful and reversible. If you reverted it, exactly one thing would change.

Good:

Fix typo in README
Add user login endpoint
Refactor auth service for testability
Update Node version to 20

Bad:

Fix typo, add login, update Node
Misc changes
WIP
stuff

The bad ones mix unrelated changes, making review and revert painful.

Why atomic commits matter:

  • Reviewable — a reviewer sees one concern, not five
  • Revertable — if the login change broke something, only that commit needs reverting
  • Bisectablegit bisect works only when each commit is a single logical unit
  • Clean history — the log reads as a sequence of decisions, not a soup

How to keep commits atomic:

  • Commit early and often — small steps, not one big dump
  • Use git add -p to stage only specific hunks
  • Split a large change into multiple commits before pushing
# Stage only some hunks of a file
git add -p file.js

# Choose which hunks to include
# y = yes, n = no, s = split, q = quit

Why this discipline pays off: A clean history is easier to bisect, review, and revert. A tangled one makes every future change harder.


Regular pushing and pulling

The more often you sync, the smaller the pain.

Push frequently. Local work is fragile — hardware fails, disks corrupt. Pushing to a remote is a backup. It’s also how your team sees what you’re doing.

Pull frequently. The longer you go without pulling, the more your branch diverges. When you finally pull, you get a large merge with many conflicts.

Sync at the start of every session:

git checkout main
git pull
git checkout feature/your-branch
git rebase main

Or on a shared branch:

git pull --rebase

Why --rebase on pull: It replays your local commits on top of the remote’s, keeping a linear history. Without it, git pull creates a merge commit every time.

Sync before pushing:

git fetch origin
git status        # are you behind?
git pull --rebase # if so
git push

Why small, frequent syncs beat big, rare ones: A merge of 5 commits is easy to resolve. A merge of 500 commits is a week of work. Frequent pulling keeps divergence small.


Additional best practices

Use branches for everything. Never commit directly to main. Every feature, every fix, every experiment gets a branch.

Review your own changes before committing.

git diff --staged

See what you’re about to commit. Typos and stray debug lines get caught here.

Keep branches short-lived. A branch that lives for weeks will conflict with everything. Merge or rebase within days.

Write branch names that say what they do.

feature/user-login
fix/null-pointer-check
hotfix/security-patch

Never commit secrets. Use .gitignore, environment files, or a secrets manager. If you do commit one, rotate the credential immediately — Git history is forever.

Don’t commit generated files. Build output, node_modules/, compiled assets — they belong in .gitignore, not the repo.

Keep .gitignore current. When you add a tool, add its artifacts to ignore.

Use tags for releases. v1.0.0, v1.0.1 — not “released” or “final”.

Prefer git switch and git restore over git checkout. Clearer intent, fewer surprises.

Why these habits compound: Each one is small. Together they make a repository that’s easy to work in, easy to onboard to, and hard to break.


Complete Example Session

# ============================================
# PART 1: WRITE A GOOD COMMIT
# ============================================

git add auth.js
git commit
# (opens editor)

# Editor content:
# Fix null pointer in user lookup
#
# The user lookup assumed a session always exists. When
# the session expires mid-request, the code crashed.
# This adds a null check and returns a 401 instead.

# ============================================
# PART 2: ATOMIC COMMITS
# ============================================

# Instead of one big commit:
git add .
git commit -m "Add login, fix bug, update docs"  # ❌

# Split into focused commits:
git add auth.js
git commit -m "Add login endpoint"

git add auth.test.js
git commit -m "Add login endpoint tests"

git add README.md
git commit -m "Document login endpoint"

# ============================================
# PART 3: STAGE HUNKS SELECTIVELY
# ============================================

git add -p file.js
# [ y/n/s/q prompts for each hunk ]

# ============================================
# PART 4: SYNC BEFORE WORKING
# ============================================

git checkout main
git pull
git checkout feature/login
git rebase main

# ============================================
# PART 5: REVIEW BEFORE COMMITTING
# ============================================

git diff --staged
# (see exactly what will be committed)

# ============================================
# PART 6: PUSH FREQUENTLY
# ============================================

git push origin feature/login
# [ Pushed to remote ]

# ============================================
# PART 7: PULL WITH REBASE
# ============================================

git pull --rebase origin main

# ============================================
# PART 8: SHORT-LIVED BRANCH
# ============================================

git checkout -b fix/typo
# (edit, commit, push, PR)
# (merge within hours, not weeks)

Quick Reference

Commit Message Structure

PartLengthPurpose
Subject50What changed
Blank line1Separator
Body72 per lineWhy it changed

Atomic Commits

GoodBad
One logical changeMixed changes
RevertibleIntertwined
BisectableAmbiguous
Clear subjectVague message

Sync Commands

CommandPurpose
git pullFetch + merge
git pull --rebaseFetch + rebase
git fetchFetch only
git pushPush to remote
git push -u origin NAMEPush + set upstream

Before Committing

CommandPurpose
git statusSee what changed
git diffSee unstaged changes
git diff --stagedSee staged changes
git add -pStage hunks

Branch Naming

PrefixPurpose
feature/New feature
fix/Bug fix
hotfix/Urgent fix
experiment/Experimental
release/Release prep

Sync Frequency

SituationCadence
PushAfter every commit
PullStart of every session
RebaseBefore pushing
Merge branchAfter review

Never Commit

CategoryExamples
Secrets.env, API keys
Generateddist/, build/
Dependenciesnode_modules/
Editor.vscode/, .idea/
OS.DS_Store, Thumbs.db

Best Practices

Do This:

# Write imperative subject lines
git commit -m "Fix login validation"               # ✅

# Use a body for non-obvious changes
git commit                                          # ✅ (editor opens)

# Commit one logical change at a time
git add auth.js && git commit -m "Add auth"         # ✅

# Stage hunks selectively
git add -p file.js                                  # ✅

# Sync before starting work
git pull --rebase                                   # ✅

# Review before committing
git diff --staged                                   # ✅

# Keep branches short-lived
# Merge within days, not weeks                      # ✅

# Use descriptive branch names
git checkout -b fix/user-lookup-null                # ✅

Don’t Do This:

# Don't write vague commit messages
git commit -m "fix"                                 # ❌

# Don't mix unrelated changes
git commit -m "Add login, fix typo, update deps"    # ❌

# Don't commit directly to main
git checkout main && git commit                     # ❌

# Don't accumulate long-lived branches
# Weeks of divergence create huge conflicts          # ❌

# Don't commit secrets
git add .env                                        # ❌

# Don't commit generated files
git add dist/                                       # ❌

# Don't forget to push
# Local-only work isn't backed up                    # ⚠️

# Don't force-push shared branches
git push --force                                    # ❌

Common Pitfalls

PitfallProblemSolution
Vague subjectHistory is unreadableBe specific
No bodyContext lostExplain why
Mixed commitsHard to reviewUse add -p
Long-lived branchConflicts pile upMerge quickly
No sync before workDivergencegit pull first
Forgot to pushWork not backed upPush often
Committed secretSecurity leakRotate + untrack
--force on sharedHistory rewritten--force-with-lease

Real-World Examples

1. Good commit message

Add password reset endpoint

Users couldn't recover accounts without contacting support.
This adds a token-based reset flow with email delivery.

2. Atomic commits

git commit -m "Add user model"
git commit -m "Add user repository"
git commit -m "Add user service"

3. Split a commit

git reset HEAD~1
git add file1.js
git commit -m "First change"
git add file2.js
git commit -m "Second change"

4. Stage hunks

git add -p

5. Sync session start

git checkout main
git pull

6. Pull with rebase

git pull --rebase origin main

7. Review before commit

git diff --staged

8. Push often

git push origin feature/login

9. Short-lived branch

git checkout -b fix/typo
# commit, push, PR, merge — all within hours

10. Delete merged branch

git branch -d fix/typo

11. Descriptive branch names

git checkout -b feature/user-authentication

12. Keep .gitignore current

echo "coverage/" >> .gitignore

13. Tag releases

git tag -a v1.0.0 -m "Release 1.0.0"

14. Switch over checkout

git switch main

15. Restore instead of reset

git restore file.js

Visual: The Commit Message

┌──────────────────────────────────────────────┐
│  Fix login validation error                  │  ← 50 chars max
│  ─────────────────────────                   │  ← blank line
│  The previous implementation failed to       │  ← 72 chars
│  validate email format properly, causing     │     per line
│  authentication failures for valid users.    │
│  This commit adds proper regex validation.   │
│                                              │
│  Closes #452                                 │
│                                              │
└──────────────────────────────────────────────┘

Visual: Atomic vs Mixed Commits

┌──────────────────────────────────────────────┐
│  Mixed (bad)                                 │
│                                              │
│  ● Add login, fix typo, update deps          │
│                                              │
│  Hard to review, hard to revert              │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Atomic (good)                               │
│                                              │
│  ● Add login endpoint                        │
│  ● Fix typo in README                        │
│  ● Update Node to 20                         │
│                                              │
│  Each commit is one change                   │
│  Each commit is revertible                   │
│                                              │
└──────────────────────────────────────────────┘

Visual: Sync Frequency

┌──────────────────────────────────────────────┐
│  Frequent syncs                              │
│                                              │
│  ○───○───○───○───○                           │
│  small merges, small conflicts               │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Rare syncs                                  │
│                                              │
│  ○───────────○                               │
│  big merge, big conflicts                    │
│                                              │
└──────────────────────────────────────────────┘

Visual: Before Every Push

┌──────────────────────────────────────────────┐
│  1. git status                               │
│  2. git diff --staged                        │
│  3. git fetch origin                         │
│  4. git pull --rebase (if behind)            │
│  5. git push                                 │
│                                              │
└──────────────────────────────────────────────┘

Summary

PracticeWhy
Clear commit messagesReadable history
Atomic commitsRevertible, bisectable
Push oftenBackup, visibility
Pull oftenSmall conflicts
Short-lived branchesEasy merges
Descriptive namesSelf-documenting
Never commit secretsSecurity
Use .gitignoreClean repo
Tag releasesTraceable versions

Key takeaways:

  • Commit messages have a subject (50 chars), blank line, and body (72 chars)
  • Write in imperative mood — “Fix” not “Fixed”
  • Atomic commits contain one logical change each
  • Use git add -p to stage specific hunks
  • Push after every commit — local work isn’t backed up
  • Pull at the start of every session — small syncs prevent big conflicts
  • Use git pull --rebase for linear history
  • Review staged changes with git diff --staged before committing
  • Keep branches short-lived — merge within days
  • Never commit secrets, generated files, or dependencies
  • Use descriptive branch names with prefixes
  • Never force-push shared branches
  • Tag releases with semantic versions

Remember: Good Git habits are about making history readable, changes reviewable, and mistakes reversible. Write clear messages, commit atomically, sync often, and keep branches short. The habits feel small in the moment but compound over time — a repository with good history is easier to work in for years.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!