NC Logo UseToolSuite
Developer Tools

Git Best Practices Every Developer Should Know

Practical Git best practices for commit messages, branching, .gitignore, and common mistakes. Learn the habits that separate junior developers from senior ones.

Necmeddin Cunedioglu Necmeddin Cunedioglu

Practice what you learn

.gitignore Generator

Try it free →

I once worked on a team where someone force-pushed to main on a Friday afternoon, wiping out two weeks of commits. The disaster recovery took the entire weekend. That experience taught me something: Git isn’t just a version control tool — it’s a collaboration protocol, and breaking the protocol has real consequences.

The difference between a developer who “uses Git” and one who uses it well isn’t talent. It’s habits. Here are the habits I’ve built over years of working with Git on teams of 2 to 200.

Commit Messages That Actually Help

The Format That Works

After trying various formats, I’ve settled on this structure that works for both solo projects and large teams:

type(scope): short description

Longer explanation if needed. Explain WHY, not WHAT —
the diff already shows what changed.

Fixes #123

Types that cover 95% of commits:

TypePurposeExample
featNew featurefeat(auth): add Google OAuth login
fixBug fixfix(cart): prevent negative quantities
refactorCode restructuringrefactor(api): extract validation middleware
docsDocumentationdocs: update API endpoint descriptions
choreMaintenancechore: upgrade dependencies
styleFormattingstyle: fix indentation in UserService
testTeststest(auth): add login edge case tests

The Messages I Hate Seeing

These commit messages actively make the project harder to maintain:

❌ "fix"
❌ "update"
❌ "WIP"
❌ "changes"
❌ "stuff"
❌ "asdfasdf"

When you need to git bisect to find which commit introduced a bug, these messages force you to read every diff instead of scanning the log. A good commit message saves future-you hours of archaeology.

The One Rule That Matters Most

Write commit messages as if someone will read git log --oneline six months from now and need to understand what changed. Because they will — and that someone is usually you.

Branching Strategies

The Simple Model (Small Teams)

For teams of 2–5 developers, I recommend a minimal branching strategy:

main ──────────────────────────────────────────────→
       \                    /
        feature/add-search ─── (PR → merge to main)
  • main is always deployable
  • Feature branches are short-lived (1–3 days maximum)
  • Merge via pull request with at least one review
  • Delete branches after merging

The GitFlow Model (Larger Teams)

For teams shipping on a release schedule:

main ──────────────────────────────────────────────→
       \          \              /
        develop ───────────────────────────────────→
             \           /
              feature/xyz
  • main = production
  • develop = integration branch
  • Feature branches branch from and merge into develop
  • Release branches prepare releases from develop to main

I’ve used both. For most web applications with continuous deployment, the simple model is less overhead and works better. GitFlow makes sense when you need to support multiple release versions simultaneously.

Branch Naming Conventions

Consistent branch names make the repository navigable:

feature/user-authentication
bugfix/cart-negative-quantity
hotfix/payment-timeout
chore/upgrade-node-18
docs/api-endpoint-documentation

The pattern is type/short-description. Keep descriptions under 5 words and use kebab-case. Some teams prefix with ticket numbers: feature/JIRA-1234-user-auth.

.gitignore: Your First Line of Defense

A proper .gitignore prevents the most common Git disasters: committed node_modules, leaked .env files, and bloated repositories full of build artifacts.

The Files You Must Always Ignore

# Dependencies
node_modules/
vendor/
.venv/

# Environment secrets
.env
.env.local
.env.production

# Build output
dist/
build/
.next/
out/

# OS and IDE files
.DS_Store
Thumbs.db
.idea/
.vscode/settings.json
*.swp

# Logs
*.log
npm-debug.log*

The Most Expensive Mistake

Once a file is committed, removing it from .gitignore doesn’t remove it from the repository history. I’ve seen repositories ballooned to 2GB because someone accidentally committed a database dump. Removing it from the working tree is easy — removing it from the entire git history requires git filter-branch or BFG Repo-Cleaner, and it forces every collaborator to re-clone.

Always set up .gitignore before your first commit. Our .gitignore Generator creates comprehensive ignore files for your specific tech stack — Node.js, Python, Java, Go, Rust, Docker, Terraform, and more — in about 10 seconds.

Start every project right: The .gitignore Generator covers all the edge cases for your stack. I use it at the start of every new repository.

Rebase vs. Merge

This is the most debated topic in Git, and I’ve been on both sides.

Merge (Default)

git checkout main
git merge feature/xyz

Creates a merge commit that preserves the complete branch history. The log shows exactly when branches diverged and merged. This is safer and more transparent.

Rebase (Clean History)

git checkout feature/xyz
git rebase main

Replays your commits on top of the latest main, creating a linear history. The log looks cleaner, but you lose the branching context.

My Recommendation

  • Rebase your own unshared branches before creating a PR. This keeps the git history clean and makes review easier.
  • Never rebase shared branches. Rebasing rewrites commit hashes, which breaks other people’s local copies.
  • Use merge for integrating PRs. Most teams use merge or squash-merge for pull requests.
# Safe: rebase your own feature branch on latest main
git checkout feature/my-work
git rebase main

# Dangerous: never rebase main or shared branches
git checkout main
git rebase feature/xyz  # DON'T DO THIS

Common Mistakes and How to Fix Them

Mistake 1: Committed to the Wrong Branch

# Undo the last commit but keep the changes
git reset --soft HEAD~1

# Switch to the correct branch
git checkout correct-branch

# Commit there instead
git add . && git commit -m "feat: add search functionality"

Mistake 2: Committed a Secret (.env, API key)

If you just committed it and haven’t pushed:

# Remove from staging
git rm --cached .env

# Add to .gitignore
echo ".env" >> .gitignore

# Amend the commit
git commit --amend

If you’ve already pushed, the secret is compromised regardless of what you do in Git. Rotate the credentials immediately. Then use BFG Repo-Cleaner to scrub it from the history.

Mistake 3: Merge Conflicts

Conflicts feel scary, but they’re usually straightforward:

<<<<<<< HEAD (your changes)
const timeout = 5000;
=======
const timeout = 10000;
>>>>>>> feature/update-timeout (incoming changes)

Read both versions, decide which is correct (or combine them), and remove the conflict markers. Use the Diff Checker to compare the two versions side by side if the conflict spans multiple lines.

Mistake 4: Accidentally Deleted a Branch

If you deleted a branch with unmerged work:

# Find the last commit hash from reflog
git reflog

# Recreate the branch from that commit
git checkout -b recovered-branch abc1234

Git reflog keeps a record of all branch tip changes for about 90 days. If you can find the commit hash, you can recover anything.

Git Habits That Compound

These small habits seem trivial individually, but they compound into a significantly better development experience over time:

  1. Commit often, push daily. Small, focused commits are easier to review, revert, and bisect.
  2. Pull before you push. git pull --rebase before pushing keeps the history clean.
  3. Write meaningful commit messages. Your teammates will thank you. Future-you will thank you even more.
  4. Never commit generated files. Build artifacts, compiled code, and dependency directories belong in .gitignore.
  5. Review your own diff before committing. git diff --staged catches debug statements, console.logs, and temporary code before they make it into the history.

Further Reading


Starting a new project? Generate a comprehensive .gitignore for your tech stack with the .gitignore Generator — it takes 10 seconds and prevents the most common Git disasters.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.