|$ curl https://forge-ai.dev/api/markdown?path=docs/git/best-practices
$cat docs/git-best-practices.md
updated Today·25-35 min read·published

Git Best Practices

◆Git◆Best Practices◆Workflow◆Intermediate🎯Free Tools
Introduction

Git best practices turn a powerful tool into a reliable team workflow. This guide covers commit hygiene, branching, pull requests, rebase vs merge, secrets, gitignore, signed commits, and team conventions.

ℹ

info

Pair with How to Master Git and Commits.
Commit Hygiene

Every commit should be a coherent, reviewable unit. Prefer many small commits over one mega-commit that mixes formatting, features, and refactors.

  • One logical change per commit — feature, fix, or chore, not all three
  • Subject at most 72 chars; use body for why, not what
  • Prefer conventional commits: feat:, fix:, docs:, chore:
  • Stage with git add -p when a file contains unrelated hunks
  • Do not commit generated artifacts, secrets, or editor junk
good-commits.sh
Bash
1# Bad: everything mixed
2git add -A && git commit -m "updates"
3
4# Good: split by intent
5git add src/auth/session.ts
6git commit -m "feat(auth): refresh tokens before expiry"
7
8git add src/auth/session.test.ts
9git commit -m "test(auth): cover refresh edge cases"
10
11git add README.md
12git commit -m "docs(auth): document refresh flow"
✓

best practice

Use amend & fixup to clean local history before opening a PR.
Commit Message Anatomy

A good subject summarizes intent; the body explains motivation and trade-offs.

message.txt
TEXT
1feat(checkout): accept percentage coupons
2
3Why: marketing needs time-boxed discounts without redeploying prices.
4Coupons are validated server-side; expired codes return 422.
5
6Breaking: CouponService.validate now requires currency code.

Conventional Commits cheat sheet

TypeUse for
featNew user-facing capability
fixBug fix
docsDocumentation only
styleFormatting; no logic change
refactorInternal restructure
perfPerformance improvement
testTests only
choreTooling, deps, chores
ciCI config
buildBuild system
Branching Strategy

Short-lived feature branches reduce merge pain. Agree as a team on trunk-based vs Git Flow — inconsistency is worse than either choice.

PatternWhenRisk
Trunk-based + short PRsMost product teamsNeeds good CI
Git FlowVersioned releases / hotfixesLong-lived branches drift
Release branchesSupport multiple versionsCherry-pick overhead
ForkingOpen source contributorsSync discipline
branching.sh
Bash
1# Start from fresh main
2git fetch origin
3git switch -c feature/checkout-coupon origin/main
4
5# Keep up to date frequently
6git fetch origin
7git rebase origin/main
8
9# Delete after merge
10git switch main
11git pull --ff-only
12git branch -d feature/checkout-coupon
13git push origin --delete feature/checkout-coupon
⚠

warning

Never commit directly to main in shared repos. Protect the branch in GitHub/GitLab settings.
Pull Request Practices

PRs are a communication tool. Optimize for reviewer time: small diffs, clear description, linked issues, and green checks.

  • Target under 400 changed lines when possible; split large features
  • Description: problem, approach, test plan, screenshots if UI
  • Self-review the diff before requesting review
  • Respond to comments with commits or clear reasons
  • Use gh pr create / templates for consistency
pr.sh
Bash
1gh pr create --title "feat(checkout): coupon codes" --body "## Summary
2Adds coupon validation at checkout.
3
4## Test plan
5- [ ] Unit tests for CouponService
6- [ ] Manual: apply valid/invalid/expired codes
7- [ ] CI green"
8gh pr checks
9gh pr diff | head
Rebase vs Merge

Both integrate histories. Rebase rewrites local commits for linearity; merge preserves exact branch topology.

SituationPreferWhy
Update feature onto main (local)rebaseClean PR diff
Integrate finished feature to mainmerge --no-ff or squashPolicy choice
Shared long-lived branchmergeAvoid rewriting others commits
Public tag/release historynever rewriteUse revert
rebase-vs-merge.sh
Bash
1# Local feature update (safe)
2git switch feature/x
3git fetch origin
4git rebase origin/main
5git push --force-with-lease
6
7# Integrate on main (example: merge commit)
8git switch main
9git merge --no-ff feature/x
✕

danger

Golden rule: do not rebase commits that other people have based work on. Details in Rebasing.
Secrets & Credential Safety

Once a secret is pushed, treat it as compromised — rotate it even if you rewrite history.

  • Ignore .env, key files, and local config via .gitignore
  • Use secret scanning (gitleaks, trufflehog) in pre-commit and CI
  • Store secrets in vaults / CI secret stores — never in the repo
  • If leaked: rotate credentials first, then history cleanup if needed
secrets.sh
Bash
1# Pre-commit mindset
2git status -sb
3git diff --staged | grep -iE 'api[_-]?key|secret|password|BEGIN RSA' && echo "ABORT"
4
5# Remove accidentally staged secret before commit
6git restore --staged .env
7echo ".env" >> .gitignore
8git add .gitignore
9git commit -m "chore: ignore env files"
✕

danger

History rewrite (filter-repo / BFG) does not un-leak a secret that was cloned. Rotate first.
.gitignore Hygiene

Ignore build outputs, dependencies, IDE files, and secrets. Keep .gitignore reviewed like code.

gitignore-example
GITIGNORE
1# dependencies
2node_modules/
3.venv/
4vendor/
5
6# build
7dist/
8build/
9.next/
10coverage/
11
12# env & secrets
13.env
14.env.*
15!.env.example
16*.pem
17*.p12
18
19# OS / IDE
20.DS_Store
21.idea/
22.vscode/*
23!.vscode/extensions.json
ℹ

info

Full pattern language: .gitignore guide.
Signed Commits

Signing proves authorship for protected branches and audit trails. Prefer SSH signing for simplicity or GPG for classic setups. See Signing.

signing-quick.sh
Bash
1git config --global gpg.format ssh
2git config --global user.signingkey ~/.ssh/id_ed25519.pub
3git config --global commit.gpgsign true
4git commit -S -m "feat: signed"
5git log --show-signature -1
Team Conventions Checklist

Write conventions down. Ambiguity causes force-push incidents.

ConventionExample
Default branchmain
Branch namesfeature/*, fix/*, chore/*
Commit styleConventional Commits
PR sizePrefer under 400 LOC
Update strategyrebase onto main before merge
Merge to mainSquash merge via GitHub
Protected branchrequire reviews + signed commits + CI
Hotfixbranch from main; cherry-pick to release if needed
Tagsannotated semver vMAJOR.MINOR.PATCH
CODEOWNERS
TEXT
1# .github/CODEOWNERS
2* @org/platform
3/docs/ @org/docs
4/src/auth/ @org/security
🔥

pro tip

Automate conventions with husky/lefthook + commitlint + CI required checks.
Hooks & CI as Guardrails

Local hooks catch mistakes early; CI is the source of truth for mergeability.

pre-commit-sample.sh
Bash
1#!/usr/bin/env bash
2# .git/hooks/pre-commit (or lefthook)
3set -euo pipefail
4if git diff --cached --name-only | grep -E '\.env$|id_rsa|\.pem$'; then
5 echo "Refusing to commit secret-like paths" >&2
6 exit 1
7fi
ℹ

info

Deep dive: Hooks.
Anti-Patterns
  • git add . blindly before reading git status
  • Force-pushing to shared branches
  • Rewriting history to hide secrets instead of rotating
  • Long-lived feature branches without rebasing
  • Commit messages like fix, wip, asdf
  • Committing node_modules or build artifacts
  • Using reset --hard as a daily undo without understanding data loss
⚠

warning

If you catch an anti-pattern mid-flight, stop and choose the safe undo: restore, revert, or reflog.
Pre-Push Checklist
  • git status -sb is clean of secrets and junk
  • Diff matches the PR description
  • Tests pass locally for touched areas
  • Rebased onto latest main without leftover conflict markers
  • No force push to protected branches
pre-push.sh
Bash
1git status -sb
2git diff --check
3git log --oneline origin/main..HEAD
4git push -u origin HEAD

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.