Git is the universal version control system. The tool is fixed; how teams use it varies. Some workflows are dramatically more productive than others.
This page covers the practices that work.
Main + short-lived feature branches. The modern default. See TrunkBasedDevelopment.
Develop + feature + release + hotfix branches. Heavy; for software with formal release cycles.
Main + feature branches; PR; merge. Simple variant of trunk-based.
For most modern teams, GitHub Flow or pure trunk-based.
Two ways to integrate changes:
A---B---C---F (main)
\ /
D---E (feature)
Preserves the branch history. Merge commit (F) joins the branches.
A---B---C---D'---E' (main)
Rewrites the feature branch's commits onto main. Linear history; no merge commits.
For team workflows, pick one strategy. Mixing them produces messy history.
The dominant modern preference: squash merge for feature branches. Main has one commit per feature; clean history; PRs map cleanly.
One commit = one logical change. Not "added user feature, fixed unrelated bug, formatted some code."
Easier to review, revert, cherry-pick.
Subject line in imperative mood (50 chars max)
Body explaining why this change was made (72 char wrap).
What does this commit fix? Why is it needed?
Refs #123
The subject says what; the body says why. Future engineers (including future you) will thank you.
Each commit should pass tests. Bisecting (git bisect) requires this.
Within a feature branch, atomic commits help review. Squash on merge to main.
The modern code review unit. Practices:
PRs under 400 lines get reviewed. Larger PRs get rubber-stamped. Break large changes into multiple PRs.
Read your own diff before requesting review. Catches half the "why is this here?" questions.
Why this change? What did you consider and reject? How did you test? The description guides the review.
Don't just commit fixes silently. Reply to comments; explain decisions; mark as addressed.
CI failed for a reason. Fix it. Don't override.
git rebase -iInteractive rebase. Reorder, squash, edit commits in your branch before merging.
git cherry-pickApply a specific commit from one branch to another. Useful for hotfixes.
git revertCreate a new commit that undoes a previous one. Safe for shared branches (doesn't rewrite history).
git resetMove the branch pointer. Powerful and dangerous. Don't use on shared branches.
git stashTemporarily save uncommitted changes. Pop them later.
git bisectFind the commit that introduced a bug. Binary search through commit history. Requires that commits compile and tests run.
git push --force to main rewrites history that others have. Their checkouts break.
--force-with-lease is safer (fails if remote has changed). Still risky on shared branches.
For your own feature branch before merging: force push freely. After merge, never.
Passwords, API keys, certificates. Once committed, they're in history forever (even after deletion).
Use:
.gitignore for files containing secretsgit-secrets or pre-commit hooksAfter a few weeks, branches diverge enough that merging is painful. Keep branches short.
CI exists for a reason. Manual "looks good" doesn't catch what tests catch.
Trying to clean up an old branch by rebasing dozens of commits. Usually produces conflict resolution that's worse than the original.
For large monorepos, specific tools help:
Default git becomes slow at scale; these features help.