Git workflow best practices including branching strategies, commit conventions, merge strategies, pull requests, and common operations. Use when working with Git version control, making commits, creating branches, or managing code collaboration.
Install
npx skillscat add paucasanellas/skills/git-workflow Install via the SkillsCat registry.
This skill covers Git workflow patterns, conventions, and best practices for effective version control and team collaboration.
Git is the foundation of modern software collaboration. Following consistent workflows, commit conventions, and branching strategies reduces friction, improves traceability, and keeps repositories clean. This skill provides practical guidance for day-to-day Git usage.
Commit Conventions
Conventional Commits
Follow the Conventional Commits specification for structured, machine-readable commit messages:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]Types:
| Type | When to use |
|---|---|
feat |
New feature or functionality |
fix |
Bug fix |
docs |
Documentation only changes |
style |
Formatting, whitespace, semicolons (no logic change) |
refactor |
Code restructuring (no feature or fix) |
perf |
Performance improvement |
test |
Adding or correcting tests |
build |
Build system or external dependency changes |
ci |
CI/CD configuration changes |
chore |
Maintenance tasks, tooling, configs |
revert |
Reverting a previous commit |
Examples:
feat(auth): add JWT refresh token rotation
fix(api): handle null response from payment gateway
refactor(users): extract validation logic into shared module
docs(readme): update setup instructions for Docker
feat(orders)!: change order status enum values
BREAKING CHANGE: order status values have been renamed.
"in-progress" is now "processing".Commit Message Best Practices
Key Points:
- Use imperative mood: "add feature" not "added feature" or "adds feature"
- First line ≤ 72 characters
- Separate subject from body with a blank line
- Body explains what and why, not how
- Reference issue/ticket numbers in footer:
Closes #123
Good vs Bad:
# ❌ Bad
fixed stuff
update
WIP
changes
# ✅ Good
fix(cart): prevent duplicate items when adding same product twice
feat(search): add fuzzy matching for product name queries
refactor(db): migrate from raw SQL to query builder patternAtomic Commits
Each commit should represent a single logical change.
Key Points:
- One commit = one purpose
- Should be independently revertable
- Don't mix refactoring with feature changes
- Don't mix formatting with logic changes
- Each commit should leave the codebase in a working state
Branching Strategies
GitHub Flow (Recommended for most teams)
Simple, lightweight strategy based on short-lived feature branches.
Workflow:
mainis always deployable- Create feature branch from
main - Commit and push to feature branch
- Open Pull Request
- Review, discuss, iterate
- Merge to
main - Deploy from
main
Key Points:
- Simple and easy to adopt
- Works well with CI/CD
- Short-lived branches reduce merge conflicts
- Best for continuous deployment
Trunk-Based Development
All developers commit to a single branch (main/trunk) with very short-lived branches (< 1 day).
Key Points:
- Minimal branching, maximum integration
- Requires strong CI/CD and test coverage
- Feature flags to hide incomplete work
- Reduces merge conflicts dramatically
- Best for experienced teams with good test discipline
Branch Naming Conventions
Use consistent, descriptive branch names:
<type>/<ticket-id>-<short-description>Examples:
feat/AUTH-123-jwt-refresh-tokens
fix/BUG-456-null-payment-response
refactor/TECH-789-extract-validation
hotfix/PROD-012-memory-leak-websocket
docs/DOC-345-api-endpoint-reference
chore/TECH-678-upgrade-node-20Key Points:
- Use lowercase and hyphens (kebab-case)
- Include ticket/issue ID when available
- Keep descriptions short but meaningful
- Use the same type prefixes as commit conventions
- Delete branches after merging
Merge Strategies
Merge Commit (--no-ff)
Creates a merge commit preserving the full branch history.
git merge --no-ff feature/my-featureWhen to use:
- You want to preserve the complete branch history
- Feature branches have meaningful intermediate commits
- You need to see when branches were integrated
Squash and Merge
Combines all branch commits into a single commit on the target branch.
git merge --squash feature/my-feature
git commit -m "feat(auth): add JWT refresh token rotation"When to use:
- Feature branch has messy or WIP commits
- You want a clean, linear history on
main - Each PR = one logical commit on
main - Recommended default for most teams
Rebase and Merge
Replays branch commits on top of the target branch, then fast-forwards.
git checkout feature/my-feature
git rebase main
git checkout main
git merge --ff-only feature/my-featureWhen to use:
- You want linear history AND individual commits preserved
- Commits are already clean and atomic
- Be careful: rewrites history — never rebase shared/public branches
Choosing a Strategy
| Strategy | History | Traceability | Cleanliness |
|---|---|---|---|
| Merge commit | Full branch history | High (merge commits) | Can be noisy |
| Squash and merge | One commit per PR | Medium | Very clean |
| Rebase and merge | Linear, all commits | Low (no merge points) | Clean |
Recommendation: Squash and merge as default. Use merge commits for long-running branches where intermediate history matters.
Pull Requests (PRs)
Creating Good PRs
Key Points:
- Keep PRs small and focused (< 400 lines ideal)
- One PR = one logical change
- Write a clear title following commit conventions
- Include description with context, motivation, and approach
- Link related issues or tickets
- Add screenshots for UI changes
- Self-review before requesting reviews
PR Description Template:
## What
Brief description of the change.
## Why
Motivation and context. Link to issue/ticket.
## How
Technical approach and key decisions.
## Testing
How this was tested. Steps to reproduce.
## Screenshots (if applicable)
Before/after for UI changes.Code Review Best Practices
As a reviewer:
- Review promptly (within a few hours, not days)
- Focus on logic, architecture, and correctness — not style (use linters)
- Ask questions rather than making demands
- Approve when "good enough" — don't block on perfection
- Use conventional comments:
nit:,suggestion:,question:,issue:
As an author:
- Don't take feedback personally
- Respond to all comments
- Push fixes as new commits during review (squash on merge)
- Request re-review after significant changes
Interactive Rebase
Clean up commit history before merging:
# Rebase last N commits interactively
git rebase -i HEAD~5
# Rebase onto main
git rebase -i mainCommon operations in interactive rebase:
| Command | What it does |
|---|---|
pick |
Keep the commit as-is |
reword |
Change the commit message |
edit |
Pause to amend the commit |
squash |
Merge into previous commit, combine messages |
fixup |
Merge into previous commit, discard this message |
drop |
Remove the commit entirely |
reorder |
Move lines to change commit order |
Key Points:
- Only rebase commits that haven't been pushed/shared
- Use
fixupto clean up "fix typo" type commits - Reorder commits to group related changes
- Force push with
--force-with-lease(safer than--force)
Common Git Operations
Undoing Changes
# Discard all uncommitted changes in working directory
git checkout -- .
# or modern equivalent:
git restore .
# Unstage files (keep changes in working directory)
git reset HEAD <file>
# or modern equivalent:
git restore --staged <file>
# Undo last commit but keep changes staged
git reset --soft HEAD~1
# Undo last commit and unstage changes
git reset --mixed HEAD~1
# Undo last commit and discard changes (DESTRUCTIVE)
git reset --hard HEAD~1
# Create a new commit that reverses a previous commit (safe for shared branches)
git revert <commit-sha>Key Points:
- Use
reverton shared/public branches — it's safe and traceable - Use
resetonly on local/unpushed commits --hardis destructive — use with caution--force-with-leaseis safer than--forcewhen pushing rebased commits
Stashing
Temporarily save uncommitted changes:
# Stash current changes
git stash
# Stash with a description
git stash push -m "WIP: refactoring auth module"
# List stashes
git stash list
# Apply most recent stash (keep in stash list)
git stash apply
# Apply and remove most recent stash
git stash pop
# Apply a specific stash
git stash apply stash@{2}
# Drop a specific stash
git stash drop stash@{0}Cherry-Pick
Apply a specific commit from another branch:
git cherry-pick <commit-sha>
# Cherry-pick without committing (stage changes only)
git cherry-pick --no-commit <commit-sha>
# Cherry-pick a range of commits
git cherry-pick <start-sha>..<end-sha>When to use:
- Applying a hotfix from
mainto a release branch - Pulling a specific commit from a feature branch
- Avoid using as primary workflow — prefer merging
Tagging
Mark specific points in history for releases:
# Create annotated tag (recommended)
git tag -a v1.2.0 -m "Release version 1.2.0"
# Create lightweight tag
git tag v1.2.0
# Push tags to remote
git push origin v1.2.0
git push origin --tags
# List tags
git tag -l "v1.*"
# Delete a tag
git tag -d v1.2.0
git push origin --delete v1.2.0Key Points:
- Use annotated tags for releases (includes metadata)
- Follow semantic versioning:
vMAJOR.MINOR.PATCH - Tag after merging to
main, not on feature branches
Conflict Resolution
Preventing Conflicts
Key Points:
- Keep branches short-lived
- Pull/rebase from
mainfrequently - Communicate with team about overlapping work
- Small, focused PRs reduce conflict surface area
Resolving Conflicts
# During merge or rebase, conflicts are marked:
<<<<<<< HEAD
const timeout = 5000
=======
const timeout = 10000
>>>>>>> feature/update-timeout
# After resolving all conflicts:
git add <resolved-files>
git rebase --continue # if rebasing
# or
git merge --continue # if merging
# Abort if needed:
git rebase --abort
git merge --abortKey Points:
- Understand both sides before resolving
- Don't just accept "theirs" or "ours" blindly
- Test after resolving conflicts
- Use visual merge tools when conflicts are complex
Git Hooks
Automate checks and enforce standards with Git hooks.
Common Hooks
| Hook | When it runs | Common use |
|---|---|---|
pre-commit |
Before commit is created | Lint, format, type-check |
commit-msg |
After commit message is written | Validate commit message format |
pre-push |
Before push to remote | Run tests, check branch name |
prepare-commit-msg |
Before editor opens for message | Auto-populate templates |
Tools for Git Hooks
Husky (Node.js projects):
# Install
npx husky init
# Add pre-commit hook
echo "npx lint-staged" > .husky/pre-commitlint-staged (run linters on staged files only):
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}commitlint (enforce conventional commits):
# .husky/commit-msg
npx --no -- commitlint --edit "$1".gitignore Best Practices
Structure
# Dependencies
node_modules/
vendor/
# Build output
dist/
build/
.next/
.nuxt/
# Environment files
.env
.env.local
.env.*.local
# IDE
.vscode/settings.json
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Test coverage
coverage/
# Temporary files
tmp/
.cache/Key Points:
- Never commit secrets, API keys, or credentials
- Use
.env.examplewith placeholder values for documentation - Add
.gitignoreearly — before the first commit - Use global gitignore for OS/IDE files:
git config --global core.excludesfile ~/.gitignore_global - Use
git rm --cached <file>to remove already-tracked files that should be ignored
Semantic Versioning
Tag releases following semver (MAJOR.MINOR.PATCH):
| Increment | When |
|---|---|
MAJOR (1.0.0 → 2.0.0) |
Breaking changes to public API |
MINOR (1.0.0 → 1.1.0) |
New features, backward compatible |
PATCH (1.0.0 → 1.0.1) |
Bug fixes, backward compatible |
Pre-release: 1.0.0-alpha.1, 1.0.0-beta.2, 1.0.0-rc.1
Key Points:
- Start at
0.1.0for initial development 0.x.xsignals unstable API — breaking changes can happen in minor versions1.0.0signals first stable public API- Document breaking changes in changelogs
Git Best Practices Summary
Commits:
- Write meaningful commit messages following conventional commits
- Make atomic commits — one logical change per commit
- Don't commit generated files, secrets, or build artifacts
Branches:
- Keep branches short-lived (days, not weeks)
- Delete branches after merging
- Pull/rebase from
mainregularly - Use consistent naming conventions
Collaboration:
- Keep PRs small and focused
- Review PRs promptly
- Use squash and merge for clean history
- Communicate about overlapping work
Safety:
- Never force-push to shared branches (
main,develop) - Use
--force-with-leaseinstead of--force - Use
reverton public branches,reseton local branches - Tag releases with annotated tags