Beyond the Basics
Most developers know how to commit, push, and pull. But Git's real power lies in its branch model, history manipulation tools, and workflow strategies that keep large codebases manageable as teams grow.
Branching Strategies
Git Flow
Uses long-lived branches (develop, release, hotfix) and is suited for projects with scheduled, versioned releases. Adds structure at the cost of overhead.
Trunk-Based Development
Short-lived feature branches (under 2 days) merged frequently into main. Enables continuous integration and requires feature flags for incomplete work. This is the approach recommended by the DORA research and adopted by high-performing teams.
# A typical trunk-based workflow
git checkout -b feat/add-search # short-lived branch
# ... make focused changes ...
git push origin feat/add-search
# Open PR → review → merge to main within a day or two
git branch -d feat/add-search # delete branch after merge
Interactive Rebase
Interactive rebase lets you clean up your work before sharing it. Use it to squash WIP commits, reorder changes, or rewrite messages:
# Rebase the last 5 commits interactively
git rebase -i HEAD~5
In the editor that opens, you can change the command for each commit:
pick 8a1b2c3 Add user model
squash 4d5e6f7 wip
squash 9a0b1c2 fix typo
reword 3d4e5f6 add login endpoint
pick 7a8b9c0 add tests
pick— keep as-isreword— change message onlysquash— merge into previous commit, combine messagesfixup— like squash but discard the messagedrop— remove the commit entirely
Warning: Only rebase commits that haven't been pushed to a shared branch. Rewriting shared history causes chaos for your teammates.
Conventional Commits
A standardised commit format enables automated changelogs, semantic versioning, and clear history at a glance:
feat: add user authentication endpoint
fix: resolve token expiration edge case
docs: update API reference for /users
style: apply prettier formatting
refactor: extract email validation to utility
perf: cache user lookup query results
test: add integration tests for auth service
chore: upgrade dependencies to latest minor
The format is: <type>(<optional scope>): <description>. Combine with commitlint and husky to enforce this automatically on every commit.
Git Hooks with Husky
Run linting and commit message checks automatically before code is committed:
npm install -D husky lint-staged commitlint @commitlint/config-conventional
npx husky init
# .husky/pre-commit
npx lint-staged
# .husky/commit-msg
npx commitlint --edit "$1"
// lint-staged.config.mjs
export default {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
};
Power-User Commands
# Binary search for the commit that introduced a bug
git bisect start
git bisect bad HEAD # current state is broken
git bisect good v1.4.0 # last known good state
# Git checks out commits; mark each as good/bad until found
git bisect reset # clean up when done
# Stash with a descriptive message
git stash push -m "wip: partial search implementation"
git stash list
git stash pop
# Apply a specific commit from another branch
git cherry-pick abc123
# Undo last commit but keep changes staged (safe!)
git reset --soft HEAD~1
# Find which commit deleted a function
git log -S "functionName" --source --all
# Compact log for branch visualisation
git log --oneline --graph --decorate --all
# Clean up remote-deleted branches
git fetch --prune
# Delete all local merged branches
git branch --merged main | grep -v "^\* main$" | xargs git branch -d
Useful Git Configuration
# Set up a better diff tool
git config --global diff.tool vscode
git config --global difftool.vscode.cmd "code --diff $LOCAL $REMOTE --wait"
# Auto-setup remote tracking on push
git config --global push.autoSetupRemote true
# Rebase instead of merge on pull (cleaner history)
git config --global pull.rebase true
# Helpful aliases
git config --global alias.lg "log --oneline --graph --decorate"
git config --global alias.st "status -sb"
git config --global alias.undo "reset --soft HEAD~1"
Best Practices
- Commit small, logical, atomic changes — one concept per commit
- Write in imperative mood — "Add feature" not "Added feature"
- Never force-push shared branches — coordinate first, or use
--force-with-lease - Review your own diff before pushing —
git diff HEADis your last chance - Keep feature branches under 2 days — the longer a branch lives, the harder the merge
- Write a good PR description — future you and your teammates will be grateful
Conclusion
Git mastery is one of the highest-leverage skills in a developer's toolkit. The commands and workflows above are used daily by senior engineers at the world's top engineering teams. Practise them deliberately and they'll become second nature.