Git Commits & Branches
The Professional Playbook
Git Commits & Branches
The Professional Playbook
Every developer writes commits. But most developers write commits that nobody — including themselves — can understand three weeks later. Commit messages like fix stuff, asdf, or WIP are noise. They tell a story of someone who didn't care. And in a professional team, that's a problem.
This guide will teach you the exact system used by top engineering teams around the world: Conventional Commits for commit messages, and Git Flow for branch naming and workflow. By the end, you’ll write commits that inform, branches that communicate intent, and you’ll fit naturally into any professional team.

Part I — The Commit Message System
The industry standard is called Conventional Commits. It gives every commit a predictable structure that both humans and tools (like CI/CD pipelines and changelog generators) can understand.
The format looks like this:
<type>[optional scope]: <description>
[optional body - more detail]
[optional footer - references, breaking changes]
A real example in full:
feat(auth): add Google OAuth login
Integrates Google OAuth 2.0 flow using Passport.js.
Users can now sign in with their Google account in addition to email/password.
Closes #204
BREAKING CHANGE: removed legacy /auth/google-old endpoint
Golden Rule: Complete this sentence — “If applied, this commit will…” Your description should finish it perfectly. “If applied, this commit will add Google OAuth login.” ✓
feat — New Feature
Used when you add something new that didn’t exist before. If a user can now do something they couldn’t do before — this is your type.
Rule: “If applied, this commit will add a new capability to the product.”
feat(auth): add JWT token refresh logic
feat(dashboard): add dark mode toggle
feat(cart): allow guest checkout without account
fix — Bug Fix
Used when you fix something that was broken. Something was wrong; now it’s right. Only use this when a defect existed.
Rule: “If applied, this commit will fix a broken behavior.”
fix(login): resolve infinite redirect loop on logout
fix(api): handle null response in /users endpoint
fix(ui): correct button alignment on mobile screens
refactor — Code Restructure
Used when you rewrite or reorganize code but behavior stays exactly the same. No new features, no bug fixes — pure internal improvement.
Rule: “Same input, same output — but the code inside is cleaner.”
refactor(auth): simplify token validation logic
refactor(cart): extract price calculation to utility function
refactor: rename userId → user_id for API consistency
chore — Maintenance Work
Used for behind-the-scenes tasks that don’t affect users at all. Updating dependencies, cleaning up unused files, configuration changes.
Rule: “Nobody notices this change except developers.”
chore: update npm dependencies to latest
chore: add .env.example file for onboarding
chore(deps): bump axios from 1.2.0 to 1.4.0
docs — Documentation
Used when you only change documentation — README files, code comments, wikis, API docs. Not a single line of executable code changed.
Rule: “You only touched .md files, comments, or documentation.”
docs: add API authentication section to README
docs(contributing): update PR guidelines for new hires
docs: add JSDoc comments to utility functions
style — Code Formatting
Used for cosmetic code changes only — spaces, semicolons, indentation, quote styles. Zero logic change. A linter or formatter told you to do it.
Rule: “Prettier or ESLint complained. This is that fix.” ⚠️ This is NOT about CSS styling — it’s about code formatting.
style: fix indentation in auth controller
style: remove trailing whitespace across codebase
style: convert single quotes to double in config files
test — Tests
Used when you add, fix, or update tests only. Production code is untouched.
Rule: “You only touched .test.ts or .spec.js files.”
test(auth): add unit tests for login service
test: fix broken snapshot test in Header component
test(api): add integration test for /users endpoint
perf — Performance
Used when code becomes faster or more efficient without any behavior change. The user gets the same result, just faster.
Rule: “Same result — measurably faster or uses less memory.”
perf(images): lazy load product images on scroll
perf(db): add index on users.email column
perf(api): cache frequent database queries with Redis
ci — CI/CD Pipeline
Used when you change GitHub Actions, Jenkins, CircleCI or any pipeline configuration. The product code is untouched.
Rule: “You touched .github/workflows/ or a pipeline config file.”
ci: add automated deployment to staging on push
ci: fix failing lint check in GitHub Actions
ci: add code coverage reporting step to pipeline
build — Build System
Used when you change the build tooling — webpack, Vite, Rollup, Dockerfile, package.json scripts. Affects how the app is compiled or packaged.
Rule: “You changed how the app is built or bundled.”
build: upgrade webpack from v4 to v5
build: add multi-stage Dockerfile for production
build(deps): bump react from 18.0 to 18.3
revert — Undo a Commit
Used when you roll back a previous commit. Always reference the original commit message so the team knows exactly what was undone.
Rule: “Something broke. Reference the original commit.”
revert: feat(auth): add JWT token refresh logic
This reverts commit a4f82b3 which caused session issues in production.
! — Breaking Change
Add ! after the type when your change breaks backward compatibility. Other developers or API consumers will need to update their code because of your change.
Rule: “Someone else’s code will break because of this commit.”
feat!: remove support for v1 API endpoints
refactor!: rename userId to user_id in all responses
fix(auth)!: change token format from JWT to opaque
The Scope — What Goes in the Parentheses?
The scope narrows down which module of your codebase was touched. Keep it short, lowercase, and consistent as a team.
feat(auth): → authentication module
fix(api): → backend API layer
feat(dashboard): → the dashboard page/feature
chore(deps): → dependencies
ci(github): → GitHub Actions specifically
build(docker): → Docker / containerization
Team Agreement Required: Scope names only work if your whole team uses the same ones. Add them to a CONTRIBUTING.md file in your repo so everyone stays consistent.
Part II — Branch Naming & Strategy
If commits are the sentences in your codebase’s story, branches are the chapters. A well-named branch tells the entire team — at a glance — who is working on what, why, and where it belongs in the larger workflow.
main / master — Production
This is production. Every line of code here is running right now, serving real users. It is sacred and protected.
- ✗ Never commit directly to this branch
- ✗ Never push broken or untested code here
- ✓ Only receives merges from
release/orhotfix/ - ✓ Every merge gets a version tag:
v1.4.0
develop — Staging
This is the staging / integration branch. All finished features collect here before going to production. Think of it as “almost ready.”
- ✓ All feature branches merge back into here
- ✓ This is what QA and testers test on
- ✗ Should never be in a broken state for long
feature/ — New Work
Created for every new piece of work. Branches off develop, merges back into develop via Pull Request.
feature/user-authentication
feature/T-42-shopping-cart-redesign
feature/add-email-notifications
feature/JIRA-204-google-oauth
Best practice: include your ticket number from Jira or GitHub Issues so the branch automatically traces back to a task.
bugfix/ — Non-Urgent Bug Fix
Created to fix a bug found during development or QA testing. This bug has NOT reached production yet. Branches off develop.
bugfix/fix-login-redirect-loop
bugfix/T-310-cart-total-miscalculation
bugfix/resolve-null-pointer-in-checkout
*bugfix/= bug not yet in production → branch fromdevelophotfix/= bug already hurting real users → branch frommain*
hotfix/ — Emergency Production Fix
The emergency branch. Created only for critical production bugs — things that are breaking for real users right now and cannot wait for the normal release cycle.
hotfix/critical-payment-failure
hotfix/security-xss-vulnerability
hotfix/T-99-login-broken-for-all-users
- ✓ Branch from
maindirectly - ✓ Merge into BOTH
mainANDdevelopwhen done - ✗ Never forget the
developmerge — or the fix gets lost in the next release
release/ — Version Preparation
Created when develop is feature-complete and being prepared for a new production version. Only bugfixes, version bumps, and release notes go here — no new features allowed.
release/1.4.0
release/2.0.0-beta
release/2025-Q1-sprint3
- ✓ Merge into
mainwhen ready → tag asv1.4.0 - ✓ Also merge back into
developto carry over any release fixes
Part III — The Full Company Workflow
Here is how all the pieces connect in a real engineering team’s day-to-day:
main ────────────────────────────────────────── (production — live)
↑ ↑
release/1.5.0 hotfix/payment-crash
develop ───────────────────────────────────────── (staging — tested)
↑ ↑ ↑
feature/ feature/ bugfix/
add-login dark-mode cart-total
The day-to-day flow for every developer:
- Pick up a ticket — Grab an issue from Jira, GitHub Issues, or Linear. Note the ticket ID.
- Create your branch —
git checkout -b feature/T-42-user-profile develop - Write code, commit often — Small, focused commits. Each commit does one thing. Use the right type.
- Push and open a Pull Request — Target
develop. Write a clear PR description. - Code review + CI checks — Teammates review. CI runs tests, linting, and type checks automatically.
- Squash merge into develop — All your commits become one clean commit. Delete the feature branch.
- Sprint ends → release branch —
developbecomesrelease/1.5.0. QA does final testing. Version is bumped. - Ship to production — Merge
release/1.5.0→main. Tag asv1.5.0. Deploy. Done.
Part IV — The Quick Reference Cheatsheet
Situation Commit Type Branch Prefix New feature for users feat feature/ Bug found in development/QA fix bugfix/ Bug live in production fix hotfix/ Cleaning up code (no behavior change) refactor feature/ Making something faster perf feature/ Updating docs or comments docs docs/ Updating dependencies chore chore/ Changing CI/CD pipeline ci ci/ Changing build config build chore/ Adding or fixing tests only test test/ Rolling back a bad commit revert hotfix/ Change that breaks other teams' code feat! or fix! feature/ Preparing a new version chore: bump version release/
Part V — Enforcing It Automatically
The best professional teams don’t rely on developers remembering the rules. They encode the rules into the repository so bad commit messages are rejected automatically before they’re ever pushed.
The two tools that do this are commitlint and Husky:
# Install commitlint + Husky
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
# Create commitlint config
echo "module.exports = { extends: ['@commitlint/config-conventional'] }" > commitlint.config.js
# Enable Husky git hooks
npx husky install
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
Now if anyone on the team tries to commit with a message like fix stuff, Git itself will reject it with an error. Only properly formatted commits get through.
Pro Move: Add semantic-release on top of this setup. It reads your conventional commit history and automatically determines the next version number, generates a changelog, and creates a GitHub release — all from your commit messages alone.
Final Thoughts
A clean commit history is an act of respect — for your teammates, for your future self, and for the product you’re building together. When someone bisects a bug at 2am and finds a commit that reads fix(payments): prevent double-charge on retry, that's not just helpful. It's professional.
You don’t need to memorize every rule today. Start with the basics: use feat and fix correctly. Name your branches with feature/ or bugfix/. Include a scope when it's useful. The rest will become muscle memory within a week.
The engineers who stand out on any team are the ones who communicate clearly — and commit messages are one of the most constant, lasting forms of communication in software development.
References: conventionalcommits.org · Git Flow by Vincent Driessen · commitlint.js.org
메타데이터
- post_id
- 0b6791c7fbd7
- slug
- git-commits-branches-0b6791c7fbd7
- url
- https://medium.com/@hossamsoliuman/git-commits-branches-0b6791c7fbd7
- canonical_url
- https://medium.com/@hossamsoliuman/git-commits-branches-0b6791c7fbd7
- author_url
- https://medium.com/@hossamsoliuman
- status
- ok
- fetched_at
- 2026-07-13 06:23:13