
Git Best Practices for Clean Commits and Team Collaboration
Git can preserve every meaningful step in a project, but a repository does not become useful simply because developers create commits. A healthy Git history depends on deliberate decisions: what belongs in a commit, how branches are named, when work is reviewed, how secrets are protected, which merge method is used, and how recovery is handled when something goes wrong.
Good Git practices reduce friction for everyone who touches the project. They make reviews faster, debugging easier, releases safer, onboarding clearer, and automation more reliable. Poor practices produce the opposite: oversized commits, confusing branches, accidental secrets, unstable main branches, noisy history, and risky force pushes.
This guide brings together practical Git habits for individual developers and teams. It focuses on clean commits, branch discipline, collaboration, repository security, code review, CI/CD, recovery, maintenance, and long-term history quality.
What You Will Learn
How to design focused and reviewable commits.
How to write useful commit messages.
How to stage changes intentionally.
How to choose sensible branch names and lifetimes.
How to keep the default branch healthy.
How to review and merge changes safely.
When to use merge, squash, rebase, revert, or cherry-pick.
How to protect secrets and sensitive configuration.
How to use
.gitignoreand.gitattributescorrectly.How to avoid destructive history mistakes.
How to use hooks, CI, branch protection, and automation effectively.
How to maintain large repositories and binary files.
How to build a team Git policy that remains understandable.
Best Practice 1: Understand the Repository State Before Acting
Many Git mistakes begin with running a command without understanding the current branch, staged changes, or untracked files.
Start important operations with:
git status
git branch --show-current
git log -1 --oneline
Before a commit, merge, rebase, pull, reset, or branch deletion, verify:
The current branch is correct.
The working tree contains only expected changes.
The staging area contains the intended snapshot.
No unfinished work will be overwritten.
The repository is synchronized according to the team workflow.
git status is not a beginner-only command. Experienced developers use it constantly because it provides a safe summary before state-changing operations.
Best Practice 2: Keep Commits Focused
A commit should represent one logical change.
Good examples include:
Add email validation to registration.
Fix duplicate invoice generation.
Rename payment service interfaces.
Document local environment setup.
Add tests for expired password reset tokens.
A poor commit may combine:
A bug fix.
A formatting pass.
A dependency update.
Documentation changes.
Temporary debugging code.
Focused commits are easier to:
Review.
Test.
Revert.
Cherry-pick.
Understand months later.
Use during debugging and bisect operations.
Commit size should be judged by logical purpose, not by a fixed number of changed lines. A coherent database migration may touch several files. A one-line change may still be risky and deserve its own commit.
Best Practice 3: Stage Changes Intentionally
A professional commit workflow separates editing from commit design.
Review unstaged work:
git diff
Stage selected paths:
git add src/OrderService.php
git add tests/OrderServiceTest.php
Or stage selected hunks:
git add -p
Review the proposed commit:
git diff --staged
Then commit:
git commit -m "Prevent duplicate order creation"
Avoid treating git add . as an automatic first step. It is convenient, but it may include unrelated edits, local files, credentials, generated output, or debugging changes.
A safer pattern is:
git status
git diff
git add -p
git diff --staged
git commit
Best Practice 4: Write Useful Commit Messages
A commit message should explain the purpose of the change.
Weak subjects:
update
fix
changes
final
new code
Stronger subjects:
Reject expired password reset tokens
Prevent duplicate webhook processing
Add search index for task titles
Document queue worker configuration
A practical message format is:
Concise subject
Optional body explaining why the change was needed,
important constraints, trade-offs, and testing notes.
Good message habits include:
Use a clear action-oriented subject.
Describe the purpose, not only the file name.
Explain the reason when the diff cannot.
Record important compatibility or migration details.
Reference issues according to team conventions.
Avoid sensitive information in commit messages.
The diff shows what changed. The message should preserve why it changed.
Best Practice 5: Use a Consistent Commit Convention
Teams benefit from consistent message style.
A simple convention may use:
Add ...
Fix ...
Remove ...
Refactor ...
Document ...
Test ...
Some projects use Conventional Commits:
feat: add task search filters
fix: prevent duplicate invoice generation
docs: explain local queue setup
refactor: extract payment retry policy
test: cover expired reset tokens
This can support automated changelog generation and release tooling, but it is not a Git requirement.
Use a structured convention only when:
The team understands it.
Tooling benefits from it.
Rules are documented.
Validation does not become unnecessarily rigid.
Best Practice 6: Do Not Commit Broken or Incomplete Logical States
When practical, every commit should leave the project in a testable state.
A commit should not intentionally contain:
Syntax errors.
Unresolved conflict markers.
Known failing tests without explanation.
Half-completed migrations that make the application unusable.
References to files added only in a later commit.
This matters because individual commits may be:
Checked out during debugging.
Used by
git bisect.Cherry-picked into another branch.
Reviewed independently.
Released accidentally by automation.
Not every intermediate commit must be production-ready, but it should be internally understandable and should not break basic repository assumptions without a clear reason.
Best Practice 7: Separate Refactoring from Behavior Changes
A refactor should ideally preserve behavior. A feature or bug fix changes behavior.
Combining both in one large commit makes review difficult because reviewers cannot easily distinguish structural movement from functional logic.
Prefer:
Commit 1: Extract invoice validation service
Commit 2: Reject duplicate invoice identifiers
This allows reviewers to first verify that the refactor preserves behavior, then review the new logic separately.
When separation is impossible, explain the relationship clearly in the commit body or pull request description.
Best Practice 8: Use Descriptive Branch Names
A branch name should communicate purpose.
Good examples:
feature/task-search
fix/payment-timeout
hotfix/duplicate-invoice
docs/local-setup
refactor/order-service
release/3.2.0
Poor examples:
new
test
final
branch2
adnan-work
changes
A branch naming policy may include:
A type prefix.
A short description.
An issue identifier when useful.
Lowercase words separated consistently.
Example:
feature/APP-142-task-search
Avoid personal names as the main branch identity. Branches should describe work, not ownership.
Best Practice 9: Keep Branches Short-Lived
The longer a branch remains open, the more it diverges from the target branch.
Long-lived branches create:
Larger merge conflicts.
Late architectural surprises.
Long review cycles.
Duplicate work.
Risky integration events.
Difficulty knowing whether the branch is still active.
Prefer small, reviewable increments that merge within hours or a few days when the project allows it.
For large features:
Use feature flags.
Use branch by abstraction.
Split the work into independently safe increments.
Integrate infrastructure before activation.
Best Practice 10: Start Branches from the Correct Base
Before creating a branch:
git switch main
git fetch origin
git merge --ff-only origin/main
git switch -c feature/task-search
The exact update command depends on the workflow, but the principle is stable: start from the intended current base.
Creating a branch from an outdated or incorrect branch can introduce unrelated commits, missing fixes, and confusing pull-request diffs.
Best Practice 11: Keep the Default Branch Healthy
The default branch should have a clearly defined state.
Common expectations include:
Always deployable.
Always releasable.
Always passing required tests.
Always the latest integrated development state.
Protect the default branch by:
Requiring pull requests or merge requests.
Blocking direct pushes.
Requiring automated checks.
Requiring approvals.
Preventing force pushes.
Restricting deletion.
When the default branch breaks, prioritize repair. A branch that remains broken teaches the team to ignore CI and reduces confidence in every later merge.
Best Practice 12: Review the Branch Diff Before Opening a Pull Request
Developers should review their own work before requesting review.
Useful commands:
git log --oneline main..HEAD
git diff --stat main...HEAD
git diff main...HEAD
Check for:
Unrelated files.
Debug statements.
Secrets.
Generated files.
Formatting noise.
Missing tests.
Accidental deletions.
Database changes without migration handling.
A self-reviewed pull request saves reviewer time and improves collaboration.
Best Practice 13: Keep Pull Requests Small
Large pull requests reduce review quality.
A reviewer facing thousands of changed lines is more likely to:
Miss defects.
Focus on style rather than design.
Delay the review.
Approve without full understanding.
Request a complete rewrite late in the process.
Small pull requests:
Receive faster feedback.
Produce fewer conflicts.
Are easier to test.
Are easier to revert.
Encourage incremental design.
When a large change is unavoidable, separate mechanical changes, schema preparation, feature-flag infrastructure, and behavior activation into stages.
Best Practice 14: Use Draft Reviews Early
A draft pull request or merge request is useful when:
The architecture needs early feedback.
The change affects several teams.
The developer wants CI validation before completion.
The branch may otherwise remain isolated too long.
Early review is more valuable than late review after every design decision has become expensive to change.
Draft status should not become permanent. Continue splitting large work into mergeable increments.
Best Practice 15: Choose One Clear Merge Policy
Teams should define which integration methods are allowed.
Merge Commit
Preserves branch topology and individual commits.
Squash Merge
Combines the pull request into one new commit on the target branch.
Rebase and Merge
Replays commits onto the target history without a merge commit.
The correct choice depends on whether the team values:
Explicit merge boundaries.
One commit per pull request.
Linear history.
Preservation of feature-branch commits.
Inconsistent merge methods make history harder to interpret. Document the default and the exceptions.
Best Practice 16: Rebase Only When It Is Safe
Rebase rewrites commits by creating replacement commits on a new base.
It is useful for:
Cleaning private feature-branch history.
Updating a local branch before review.
Combining fixup commits.
Maintaining a linear history when the team requires it.
Avoid rebasing commits that other developers already depend on unless the team coordinates the rewrite.
A practical rule:
Private local history can be rewritten freely.
Shared public history should be rewritten only by explicit agreement.
Best Practice 17: Prefer force-with-lease Over Blind Force Push
When a rewritten branch must be updated remotely, avoid:
git push --force
Prefer:
git push --force-with-lease
--force-with-lease adds a safety check. It refuses to overwrite the remote branch when the remote state differs from what your local repository expects.
This does not make force pushing harmless. It only reduces the risk of deleting another developer’s newer work.
Never force push protected shared branches such as main unless a documented emergency process requires it.
Best Practice 18: Use Revert for Shared History
When a bad commit is already shared, create a new commit that reverses it:
git revert <commit>
This preserves the historical record.
Use reset mainly for local or private history:
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
Reset modes have different effects on the branch, index, and working tree. --hard can destroy uncommitted work.
General rule:
Use revert to undo published commits.
Use reset to reorganize private history when you understand the consequences.
Best Practice 19: Use Cherry-Pick Intentionally
git cherry-pick copies the change introduced by selected commits onto the current branch.
Useful cases include:
Backporting a bug fix.
Applying one approved change to a release branch.
Recovering a useful commit from an abandoned branch.
Example:
git switch release/2.5
git cherry-pick -x abc1234
The -x option records the original commit identifier in the new message when appropriate, improving traceability.
Do not use cherry-pick as the default replacement for normal branch integration. It duplicates changes without recording a branch merge relationship.
Best Practice 20: Protect Secrets Before They Enter History
Never commit:
Passwords.
API keys.
Private tokens.
Cloud credentials.
Private SSH keys.
Production environment files.
Database dumps containing real customer data.
Private certificates.
Use:
Environment variables.
Secret managers.
CI/CD secret stores.
Local configuration files excluded from Git.
Example files with placeholder values.
Example:
.env
.env.*
!.env.example
If a secret is committed:
Revoke or rotate it immediately.
Remove it from current files.
Assess whether history rewriting is required.
Coordinate with all contributors and deployment systems.
Update monitoring and incident records.
Deleting the secret in a later commit does not remove it from earlier history.
Best Practice 21: Use .gitignore Correctly
A repository-level .gitignore should contain shared project rules.
Common entries include:
# Environment files
.env
.env.*
# Dependencies
vendor/
node_modules/
# Build output
dist/
build/
# Logs
*.log
storage/logs/
# Editor and operating-system files
.DS_Store
Thumbs.db
Important principles:
.gitignoreaffects untracked files.It does not stop tracking a file already committed.
Project-required ignore rules belong in the repository.
Personal editor patterns may belong in a global ignore file.
Stop tracking a file while keeping it locally:
git rm --cached .env
Then commit the removal and rotate any exposed secrets.
Best Practice 22: Use .gitattributes for Repository-Wide File Rules
.gitattributes defines behavior for paths across every clone.
Example:
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.png binary
*.jpg binary
*.pdf binary
Use it for:
Line-ending normalization.
Binary-file declaration.
Custom diff drivers.
Merge behavior.
Export settings.
Git LFS path rules.
Repository-level attributes are more reliable than assuming every developer has identical global settings.
Best Practice 23: Avoid Unnecessary Binary Files
Git works best with text files because it can compare and merge them efficiently.
Large binary files create problems:
Repository clones become large.
Every changed version may remain in history.
Diffs are limited or unavailable.
Merging is difficult.
CI and network operations become slower.
For necessary large binaries, consider:
Git LFS.
Artifact repositories.
Object storage.
Package registries.
Build pipelines that generate artifacts from source.
Do not commit build output merely because it is needed for deployment. Prefer reproducible builds and immutable artifacts stored in systems designed for them.
Best Practice 24: Do Not Commit Generated Dependencies Unless Required
Most ecosystems track dependency definition and lock files rather than installed dependency directories.
Examples:
Track
composer.jsonand usuallycomposer.lock; ignorevendor/.Track
package.jsonand the selected lock file; ignorenode_modules/.Track dependency manifests; regenerate installed packages in CI.
Exceptions exist for deployment models, vendored libraries, offline builds, and embedded environments. Document the reason when generated dependencies are intentionally committed.
Best Practice 25: Keep Lock Files Consistent
Applications usually benefit from committing the ecosystem’s lock file because it records tested dependency versions.
Common problems include:
Updating the manifest without updating the lock file.
Combining unrelated dependency changes.
Resolving lock-file conflicts manually without reinstalling dependencies.
Using several competing lock-file formats.
After dependency changes:
Run the package manager’s normal update command.
Review both manifest and lock changes.
Run tests and builds.
Keep the dependency update focused.
Best Practice 26: Use Git Hooks Carefully
Git hooks can run checks at important points.
Useful local hooks include:
pre-commit: Formatting, linting, secret scans.commit-msg: Message validation.pre-push: Tests before publishing.
Benefits:
Fast feedback before CI.
Automatic formatting.
Reduced accidental mistakes.
Consistent local validation.
Limitations:
Hooks are local by default.
Developers can bypass them.
Heavy hooks slow every commit or push.
Different environments may behave differently.
Critical rules should also run in CI or server-side controls.
Best Practice 27: Keep Automated Checks Fast and Relevant
Required CI should provide trustworthy feedback quickly.
A practical pipeline may include:
Formatting checks.
Linting.
Static analysis.
Unit tests.
Integration tests.
Security scanning.
Build verification.
Migration validation.
Poor CI practices include:
Flaky mandatory tests.
Running every expensive test for every tiny documentation change.
Ignoring failed checks.
Keeping broken pipelines for long periods.
Allowing merges before critical checks finish.
Use path-based or layered validation when appropriate, but do not weaken checks simply to make pipelines appear faster.
Best Practice 28: Sign Important Commits and Tags When Required
Cryptographic signing can help verify that a commit or tag was created by a holder of an approved key.
Sign a commit:
git commit -S -m "Release payment callback fix"
Sign a tag:
git tag -s v2.0.1 -m "Release version 2.0.1"
Verify a tag:
git tag -v v2.0.1
Signing proves something about the Git object and key relationship. It does not prove that:
The code is secure.
The review was correct.
The build artifact matches the source.
The deployment was authorized.
Combine signing with trusted build, release, and artifact processes.
Best Practice 29: Tag Releases Explicitly
Use tags to identify important released commits.
Prefer annotated or signed tags for formal releases:
git tag -a v3.1.0 -m "Release version 3.1.0"
git push origin v3.1.0
Before tagging:
Verify the branch.
Verify the commit.
Ensure the working tree is clean.
Run release tests.
Confirm the version number.
Inspect the tag before publishing.
Treat published release tags as immutable. Moving a release tag creates inconsistent identities across clones, caches, build systems, and deployments.
Best Practice 30: Use Reflog as a Recovery Tool
When a branch moves unexpectedly or a commit disappears from normal history, inspect:
git reflog
Useful recovery situations include:
An accidental reset.
An incorrect amend.
A rebase that removed expected commits.
A deleted local branch.
A detached-HEAD commit.
Inspect a candidate:
git show HEAD@{2}
Preserve it:
git branch recovery HEAD@{2}
Reflogs are local and temporary. They are not a replacement for normal commits, remote copies, or backups.
Best Practice 31: Avoid Destructive Commands Until You Understand the Layers
Commands such as these can remove work:
git reset --hard
git clean -fd
git restore .
git checkout -- .
git branch -D
git push --force
Before using them:
Run
git status.Review the target revision.
Inspect untracked files.
Create a temporary branch or stash when appropriate.
Confirm whether the work exists anywhere else.
Use preview modes when available:
git clean -nd
This shows what git clean would delete without deleting it.
Best Practice 32: Do Not Use Stash as Permanent Storage
Stash is useful for temporary local work, not long-term project management.
Problems with old stashes include:
Unclear purpose.
Conflicts with newer code.
No normal branch visibility.
No remote collaboration.
Risk of accidental clearing.
Convert important work into a branch:
git stash branch feature/recovered-work stash@{0}
Then create focused commits.
Best Practice 33: Delete Completed Branches
After integration, remove short-lived branches.
Delete a safely merged local branch:
git branch -d feature/task-search
Delete a remote branch:
git push origin --delete feature/task-search
Stale branches make it difficult to understand what is active.
Before force deletion:
git log --oneline main..feature/task-search
git branch --contains feature/task-search
Confirm that unique work is preserved elsewhere.
Best Practice 34: Prune Stale Remote-Tracking References
Remote branches deleted by others can leave stale remote-tracking references locally.
Fetch and prune:
git fetch --prune
Or prune one remote:
git remote prune origin
Configure automatic pruning if it matches the team’s preference:
git config --global fetch.prune true
Pruning removes stale remote-tracking references. It does not delete active remote branches.
Best Practice 35: Maintain Repository Performance
Git performs maintenance automatically in many environments, but large or long-lived repositories may require attention.
Useful commands include:
git count-objects -vH
git gc
git maintenance run
git fsck
Use cases:
git count-objects: Inspect loose-object and pack usage.git gc: Optimize and clean repository data.git maintenance: Run maintenance tasks.git fsck: Check object connectivity and validity.
Do not run aggressive cleanup while trying to recover unreachable objects. Maintenance may remove data that reflog or object recovery could otherwise find.
Best Practice 36: Use Sparse Checkout or Partial Clone for Very Large Repositories
Large monorepositories can be expensive to clone and check out.
Options include:
Partial clone.
Sparse checkout.
Shallow clone for limited CI scenarios.
Repository decomposition when architecture supports it.
Example sparse-checkout setup:
git sparse-checkout init --cone
git sparse-checkout set services/billing shared
These techniques have trade-offs. Some commands and tools behave differently with shallow or partial history. Use them deliberately and document team expectations.
Best Practice 37: Use .mailmap for Identity Consistency
Developers may appear under several names or email addresses.
A .mailmap file can normalize identities in commands such as git shortlog.
Example:
Jane Developer <jane@company.example> <jane.old@example.com>
This improves contributor summaries without rewriting existing commits.
Best Practice 38: Document Repository-Specific Git Rules
Do not rely on unwritten team habits.
Useful files include:
CONTRIBUTING.md
docs/git-workflow.md
docs/release-process.md
CODEOWNERS
Document:
Branch naming.
Default branch expectations.
Commit-message style.
Required checks.
Review requirements.
Merge methods.
Release tagging.
Hotfixes and backports.
Force-push policy.
Secret-handling rules.
A concise, enforced policy is better than a long document nobody follows.
Best Practice 39: Use CODEOWNERS and Review Responsibility
Large repositories benefit from clear review ownership.
A CODEOWNERS file can associate paths with responsible reviewers on supported hosting platforms.
Example:
/src/Payments/ @payments-team
/database/migrations/ @backend-team
/docs/ @documentation-team
Ownership should not create silos. It should ensure that sensitive areas receive knowledgeable review while still sharing architectural knowledge.
Best Practice 40: Review for More Than Syntax
A useful code review checks:
Correctness.
Security.
Error handling.
Tests.
Performance.
Data migration safety.
Backward compatibility.
Observability.
Maintainability.
Deployment impact.
Git shows the diff, but reviewers must understand the surrounding system.
A small syntactic change can create a large operational risk. A large generated diff may contain little meaningful logic.
Best Practice 41: Avoid Mixing Formatting with Functional Changes
Formatting entire files inside a feature branch creates noisy diffs.
Prefer:
Automated formatting before feature work.
A separate formatting commit.
Repository-wide formatter configuration.
CI checks that prevent style drift.
This keeps functional changes visible during review and improves blame history.
Best Practice 42: Make Database Changes Deployable
Database migrations deserve special Git discipline.
Good practices include:
Keep schema and application changes compatible during rollout.
Avoid destructive changes in the same deployment that removes old code paths.
Test forward and rollback procedures where rollback is supported.
Do not edit migrations already applied broadly unless the project explicitly allows it.
Separate data backfills from schema definitions when necessary.
Document operational sequencing.
A clean Git commit is not enough if deployment order makes the application fail.
Best Practice 43: Record Deployment Provenance
Teams should know exactly which commit and artifact reached each environment.
Useful identifiers include:
Commit hash.
Release tag.
Build number.
Container digest.
Artifact checksum.
A branch name alone is not sufficient because branches move.
Deployment systems should record immutable identifiers.
Best Practice 44: Use Automation for Repetitive Repository Tasks
Automation can handle:
Deleting merged branches.
Generating release notes.
Creating version tags.
Updating changelogs.
Running dependency updates.
Checking stale branches.
Validating commit or pull-request conventions.
Automation should reduce manual errors, not hide important decisions. Release tags, migrations, and destructive cleanup still require clear ownership and auditability.
Best Practice 45: Keep History Useful, Not Artificially Perfect
A useful history should be:
Understandable.
Traceable.
Reviewable.
Recoverable.
Consistent with the team workflow.
It does not need to look mathematically perfect.
Over-optimizing history can create problems:
Excessive rebasing.
Loss of collaboration context.
Force-push risk.
Time spent polishing minor commit details after review.
Clean private history before sharing when useful. Preserve stable shared history after integration.
A Practical Daily Git Workflow
1. Update the Base
git switch main
git fetch origin
git merge --ff-only origin/main
2. Create a Focused Branch
git switch -c fix/payment-timeout
3. Work in Small Steps
git status
git diff
git add -p
git diff --staged
git commit -m "Increase payment provider timeout"
4. Run Tests
# Use the real project commands
php vendor/bin/phpunit
npm test
5. Review the Branch
git log --oneline main..HEAD
git diff --stat main...HEAD
git diff main...HEAD
6. Push and Open Review
git push -u origin fix/payment-timeout
7. Respond to Review
Create focused follow-up commits or amend private commits according to team policy.
8. Merge and Clean Up
git switch main
git pull --ff-only
git branch -d fix/payment-timeout
Manual Run: Clean Up a Mixed Working Tree
Assume the working tree contains:
A payment bug fix.
A README improvement.
Temporary debug output.
Inspect:
git status
git diff
Stage only the bug fix:
git add -p src/PaymentService.php
git add tests/PaymentServiceTest.php
Review:
git diff --staged
Commit:
git commit -m "Prevent duplicate payment retries"
Remove unwanted debug output from the working tree or leave it unstaged while preparing the documentation commit.
Stage documentation separately:
git add README.md
git commit -m "Document payment retry configuration"
The result is two focused commits instead of one mixed snapshot.
Manual Run: Recover from an Accidental Reset
Suppose a branch was reset and the latest commit disappeared.
Inspect the reflog:
git reflog
Find the previous branch position:
git show HEAD@{1}
Preserve it:
git branch recovery/payment-fix HEAD@{1}
Verify:
git log --oneline --graph --decorate --all
Create a recovery branch before running more history-changing commands.
Manual Run: Safely Update a Rewritten Feature Branch
Assume a private branch was rebased:
git rebase main
Review the new history:
git log --oneline --graph --decorate main..HEAD
Update the remote with a lease:
git push --force-with-lease
If another developer pushed new commits to the remote branch, the lease should reject the overwrite, allowing the team to reconcile the work.
Common Git Mistakes
Committing Everything at Once
This creates mixed history and makes review and recovery difficult.
Using Vague Commit Messages
Messages such as update provide no useful historical context.
Working Directly on Main Without Policy
This can bypass review and required checks.
Leaving Feature Branches Open for Weeks
Divergence and integration risk increase over time.
Force Pushing Shared Branches
This can erase other developers’ visible history.
Committing Secrets
A later deletion does not remove them from earlier commits.
Ignoring CI Failures
Required checks lose value when failures are accepted routinely.
Using Reset on Published History
This rewrites branch position and can disrupt collaborators.
Deleting Branches Without Checking Unique Commits
Important work may become difficult to find.
Committing Generated Files Without Need
This increases repository size and creates noisy diffs.
Using Several Merge Styles Randomly
History becomes inconsistent and difficult to interpret.
Treating Reflog as a Backup
Reflog is local and temporary.
Team Git Policy Example
Default branch:
- main
Branch names:
- feature/<description>
- fix/<description>
- docs/<description>
- hotfix/<description>
Commit rules:
- One logical change per commit
- Clear action-oriented subject
- No secrets or generated dependencies
Pull request rules:
- Direct pushes to main are blocked
- At least one approval is required
- CI, static analysis, and tests must pass
- Pull requests should remain focused and small
Merge policy:
- Squash merge for normal feature branches
- Merge commits for release and long-lived maintenance branches
- Rebase only private feature history
Release policy:
- Annotated or signed tags
- Tags identify tested commits
- Published tags are immutable
Safety:
- Use revert for shared history
- Use force-with-lease for approved rewritten feature branches
- No force push to main
Git Best Practices Checklist
Before committing:
The current branch is correct.
The commit contains one logical change.
The staged diff has been reviewed.
No secrets or generated files are staged.
Relevant tests pass.
The commit message explains the purpose.
Before opening a pull request:
The branch starts from the correct base.
The pull request is focused.
The branch diff has been self-reviewed.
CI passes.
Tests and migration concerns are documented.
No temporary debugging code remains.
Before merging:
Required approvals are complete.
Required checks pass.
The merge method matches team policy.
The target branch is correct.
Release and deployment impact is understood.
Before rewriting or deleting history:
The commits are private or the team has coordinated.
Important work is preserved elsewhere.
The remote state is understood.
--force-with-leaseis used instead of blind force when appropriate.A recovery branch or reflog reference is available.
Frequently Asked Questions
How Often Should I Commit?
Commit whenever a logical change is complete and understandable. Avoid both one giant end-of-day commit and meaningless commits after every saved line.
Should Every Commit Pass All Tests?
Every commit should ideally preserve a usable and testable state. At minimum, it should not knowingly break basic repository assumptions without a documented reason.
Should I Use git add .?
It is acceptable when every current change belongs together, but always review git status and git diff --staged afterward.
Should I Squash All Commits?
No. Squash noisy or temporary branch history when one logical mainline commit is desired. Preserve meaningful independent commits when they improve history.
Is Rebase Better Than Merge?
Neither is universally better. Rebase creates replacement commits and can produce linear history. Merge preserves existing history and records integration. Use the team’s documented policy.
When Is Force Push Acceptable?
Usually only on a private or explicitly rewriteable feature branch. Prefer --force-with-lease. Do not force push protected shared branches.
What Should I Do After Committing a Secret?
Rotate or revoke it immediately. Then remove it from current files, assess history cleanup, and coordinate with the team and affected systems.
Should I Commit .env.example?
Yes, when it contains safe placeholder keys and documents required configuration. Do not place real secret values inside it.
Should I Commit Lock Files?
Applications generally should. Libraries may follow ecosystem-specific policies. The team should document the decision.
Should I Delete Merged Branches?
Yes, for short-lived work. This keeps the repository easier to understand. Preserve long-lived release or maintenance branches only when they serve an active purpose.
Is a Clean Linear History Always Better?
No. A readable history matters more than a perfectly straight graph. Merge commits can preserve valuable integration context.
Can Git Replace a Backup System?
No. Git protects committed repository history, not databases, uploaded files, external services, or every uncommitted local change.
How Can I Make Git Easier for New Team Members?
Document the workflow, automate setup and validation, use consistent branch and commit conventions, and keep the repository history understandable.
Conclusion
Git best practices are not about memorizing the largest number of commands. They are about reducing ambiguity.
A clean commit explains one logical change. A good branch describes one purpose. A useful pull request is small enough to review. A healthy default branch passes required checks. A safe repository keeps secrets out of history, uses clear release tags, and avoids rewriting shared commits without coordination.
The most reliable daily pattern is simple: inspect the repository, review the working diff, stage intentionally, review the staged diff, test the change, commit with a meaningful message, and publish through the team’s review process.
For teams, the highest-value improvements are consistency and enforcement. Document the workflow, protect important branches, automate repeatable checks, define merge and release policies, and keep the process no more complex than the product requires.
The final article in this Git foundation series will provide a complete Git glossary, connecting the essential terms used across repositories, commits, branches, remotes, merges, rebases, tags, stashes, recovery, and collaboration.
Official References and Further Reading

