Home DevOps Git Push — The Force Push That Deleted a Colleague's Commits
Beginner 3 min · July 07, 2026
Git Push: Safe Publishing and Force-Push Recovery

Git Push — The Force Push That Deleted a Colleague's Commits

A developer used git push --force on a shared branch, silently deleting a teammate's commits.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Production
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 20 min
  • Git 2.x installed, basic familiarity with Git commands (commit, branch, checkout), access to a Git hosting service (GitHub, GitLab, or Bitbucket), and a terminal with bash. No prior experience with force push required.
 ● Production Incident
Quick Answer

git push origin main pushes the local main branch to origin. git push -u origin feature sets upstream tracking. git push --force-with-lease force-pushes safely (fails if remote has unknown commits). NEVER use --force alone.

✦ Definition~90s read
What is Git Push?

git push uploads local commits to a remote repository and updates remote branches. It is the command that makes your work visible to the team.

Git push is like publishing a book.
Plain-English First

Git push is like publishing a book. You've been writing locally (committing), and now you're sending the finished chapters to the publisher (remote). git push origin main says 'here are my latest chapters for the main book.' If someone else published chapters while you were writing, --force-with-lease says 'only publish if my copy matches what I last saw' — preventing accidental overwrites. Plain --force says 'publish regardless of what anyone else wrote' — which is how chapters get lost.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

Git push is the most dangerous common Git command. A wrong push can overwrite teammates' commits, rewrite published history, and break CI/CD pipelines. The safety mechanisms (--force-with-lease, branch protection, pre-push hooks) are not enabled by default. Every developer has a force-push horror story. This guide covers safe push practices, the anatomy of a force-push disaster, and how to recover when things go wrong.

The Anatomy of a Safe Git Push

Before you type git push, understand what it does: it sends your local commits to a remote repository. A safe push means your changes integrate cleanly without disrupting collaborators. The golden rule: never push to a shared branch without first pulling the latest changes. Use git pull --rebase to replay your commits on top of the remote branch, avoiding merge commits that clutter history. Always verify the remote URL with git remote -v to ensure you're pushing to the correct destination. A common mistake is pushing to the wrong remote (e.g., production instead of staging). Set up remote aliases like origin for your fork and upstream for the main repo. Before pushing, run git log --oneline -5 to review your commits. If you see WIP or debug commits, squash them with git rebase -i. This discipline prevents accidental exposure of half-baked work.

safe-push.shBASH
1
2
3
4
5
git checkout main
git pull --rebase origin main
git log --oneline -5
# Review commits, then push
git push origin main
Output
Successfully rebased and updated refs/heads/main.
<commit-hash> Fix login bug
<commit-hash> Add tests
Everything up-to-date
💡Always Pull Before Push
Running git pull --rebase before push ensures your branch is up to date. If you forget, you risk a non-fast-forward rejection or, worse, overwriting someone else's work.
📊 Production Insight
In production, a push to the wrong remote can deploy broken code. Use pre-push hooks to validate branch names and remotes.
🎯 Key Takeaway
A safe push starts with pulling the latest changes and reviewing your commits.
git-push THECODEFORGE.IO Git Push Protection Layers Defense stack against accidental force push User Action Pre-push hook | Manual reflog check Local Git Force-with-lease | Rebase safety Remote Repository Branch protection rules | Deny non-fast-forward Audit & Recovery Git reflog | Push history log THECODEFORGE.IO
thecodeforge.io
Git Push

Understanding Force Push: When and Why

Force push (git push --force) overwrites the remote branch with your local history. It's destructive: any commits on the remote that you don't have locally are lost. Use it only on private branches or when you've rebased a feature branch and need to update the remote. Never force push to shared branches like main or develop without team agreement. The --force-with-lease option is safer: it checks that your remote-tracking branch matches the remote, preventing accidental overwrite of others' work. For example, if a colleague pushed while you were rebasing, --force-with-lease will reject the push. Always prefer --force-with-lease over --force. In CI/CD pipelines, force push can be used to clean up release branches, but only with strict access controls.

force-push-example.shBASH
1
2
3
git checkout feature/login-fix
git rebase main
git push --force-with-lease origin feature/login-fix
Output
Total 0 (delta 0), reused 0 (delta 0)
To github.com:user/repo.git
+ <old-hash>...<new-hash> feature/login-fix -> feature/login-fix (forced update)
⚠ Force Push Is Dangerous
Using --force without --with-lease can wipe out commits from teammates. Always use --force-with-lease as a safety net.
📊 Production Insight
A force push to main can cause production rollbacks. Protect main with branch rules that require pull requests and disable force push.
🎯 Key Takeaway
Force push is for private branches only; use --force-with-lease to avoid overwriting others' work.

Recovering from a Force Push Disaster

If you accidentally force-pushed and lost commits, act fast. First, check your local reflog: git reflog shows every HEAD movement. Find the commit before the force push. Create a new branch from that commit: git checkout -b recovery-branch <commit-hash>. Then push this branch to the remote: git push origin recovery-branch. If you don't have the commits locally, check if any teammate has them in their reflog. Ask them to push their version. If the remote has a backup (e.g., GitHub's 'Restore' feature for deleted branches), use that. For repositories with protected branches, force push may be blocked, but if it went through, the reflog is your best friend. Remember: reflog is local and expires after 90 days by default.

recover-force-push.shBASH
1
2
3
4
5
git reflog
# Find the commit before force push, e.g., abc1234
git checkout -b recovery abc1234
git push origin recovery
# Notify team to fetch from recovery branch
Output
abc1234 HEAD@{0}: checkout: moving from main to recovery
...
* [new branch] recovery -> recovery
🔥Reflog Is Your Safety Net
Git's reflog records all local changes. Even if you force push, your local history remains until garbage collection. Use it to recover lost commits.
📊 Production Insight
In production, set up automated backups of all branches. GitHub's audit log can also help trace who force pushed and when.
🎯 Key Takeaway
Recover from force push by using git reflog to find lost commits and push them to a new branch.
git-push THECODEFORGE.IO Git Push Protection Layers Defense mechanisms against force push disasters User Awareness Training | Code Review Culture | Push Best Practices Pre-Push Hooks Client-Side Scripts | Commit Validation | Branch Name Checks Branch Protection Rules Admin Enforcement | Required Reviews | No Force Push Audit and Recovery Git Reflog | Push History Logs | Recovery Scripts THECODEFORGE.IO
thecodeforge.io
Git Push

Preventing Force Push with Branch Protection

Branch protection rules are your first line of defense. On GitHub, GitLab, or Bitbucket, configure rules for critical branches like main, develop, and release/*. Disable force push for these branches. Require pull requests with approvals before merging. Enable status checks (CI, linting) to pass. For teams, use 'Require linear history' to prevent merge commits and enforce rebasing. This ensures that only clean, reviewed code enters production. If a developer needs to force push a feature branch, they can, but the protected branches remain safe. Audit branch protection settings regularly; misconfigurations can lead to incidents.

branch-protection.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
branches:
  - name: main
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
      required_status_checks:
        contexts:
          - continuous-integration/jenkins
      enforce_admins: true
      restrictions:
        users: []
        teams: []
      allow_force_pushes: false
Output
Branch protection rules applied to main.
💡Protect Main at All Costs
Enable branch protection on main to prevent direct pushes and force pushes. This is a non-negotiable practice for any production repository.
📊 Production Insight
A misconfigured branch protection can allow a force push to bypass CI. Regularly audit protection rules and use infrastructure-as-code to manage them.
🎯 Key Takeaway
Branch protection rules prevent force pushes to critical branches, enforcing code review and CI checks.

Using Pre-Push Hooks to Catch Mistakes

Git hooks are scripts that run before certain actions. A pre-push hook can validate branch names, check for sensitive data, or ensure commits are signed. For example, reject pushes to main unless it's a pull request merge. Or block force pushes to branches matching release/*. Hooks are local and not shared by default, but you can distribute them via a script in your repository. Use git config core.hooksPath to point to a versioned hooks directory. A common pre-push hook checks for WIP commits or large files. This catches mistakes before they reach the remote.

pre-push-hook.shBASH
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# Prevent force push to main
while read local_ref local_sha remote_ref remote_sha
do
  if [[ "$remote_ref" == "refs/heads/main" ]] && [[ "$local_ref" != "refs/heads/main" ]]; then
    echo "ERROR: Direct push to main is not allowed. Use a pull request."
    exit 1
  fi
done
exit 0
Output
ERROR: Direct push to main is not allowed. Use a pull request.
⚠ Hooks Are Local Only
Pre-push hooks are not synced with the remote. Ensure every team member installs them, or use a CI check as a backup.
📊 Production Insight
In production, combine hooks with server-side checks. Hooks can prevent accidental secrets commit, but server-side scanning is essential for compliance.
🎯 Key Takeaway
Pre-push hooks automate safety checks, blocking dangerous pushes before they happen.

Safe Rebasing and Force Push Workflow

Rebasing is common for feature branches, but it rewrites history. After rebasing on main, you must force push the feature branch. The safe workflow: communicate with your team. Before rebasing, ensure no one else is working on that branch. Use git push --force-with-lease after rebase. If someone else has commits on the branch, coordinate to merge their changes first. Alternatively, use git merge instead of rebase to avoid force push. For long-lived feature branches, consider merging main into your branch periodically instead of rebasing. This avoids force push entirely.

safe-rebase-workflow.shBASH
1
2
3
4
git checkout feature/new-api
git fetch origin
git rebase origin/main
git push --force-with-lease origin feature/new-api
Output
Successfully rebased and updated refs/heads/feature/new-api.
Total 0 (delta 0), reused 0 (delta 0)
To github.com:user/repo.git
+ <old-hash>...<new-hash> feature/new-api -> feature/new-api (forced update)
🔥Coordinate Before Rebasing
Rebasing a shared branch without warning can cause confusion. Announce your intent in team chat and wait for confirmation.
📊 Production Insight
In production, use merge commits for shared branches to avoid force push. Rebasing is acceptable for solo feature branches but risky for collaborative work.
🎯 Key Takeaway
Rebase with caution: communicate with your team and use --force-with-lease to minimize risk.

Recovering from a Non-Fast-Forward Rejection

A non-fast-forward rejection occurs when your local branch is behind the remote. Git refuses to push because it would lose commits. The solution: pull the latest changes first. Use git pull --rebase to replay your commits on top. If you have merge conflicts, resolve them. After rebase, push normally. If you accidentally force push to resolve this, you might overwrite others' work. Instead, always pull first. If you're in a hurry and force push, you risk disaster. The correct response is to update your local branch, not override the remote.

fix-non-fast-forward.shBASH
1
2
3
4
5
git pull --rebase origin main
# Resolve conflicts if any
git add .
git rebase --continue
git push origin main
Output
Successfully rebased and updated refs/heads/main.
Everything up-to-date
💡Never Force Push to Fix Rejection
A non-fast-forward rejection is a signal to pull, not to force push. Force pushing in this scenario can erase teammates' commits.
📊 Production Insight
In production, set up CI to reject non-fast-forward pushes. This enforces that developers pull before pushing, reducing merge conflicts.
🎯 Key Takeaway
Non-fast-forward rejections are fixed by pulling and rebasing, not by force pushing.
Force Push vs Safe Push Comparing risks and best practices in Git push operations Force Push (--force) Safe Push (--force-with-lease) Remote Overwrite Behavior Overwrites remote branch unconditionally Aborts if remote has new commits Risk of Data Loss High: can delete colleague's commits Low: protects against accidental overwri Use Case Only for personal branches or emergencie Standard for rebased feature branches Recovery Difficulty Difficult: requires reflog or backup Easy: push rejected, no damage done Team Safety Unsafe for shared branches Safe for collaborative workflows THECODEFORGE.IO
thecodeforge.io
Git Push

Auditing Push History with Git Log and Reflog

After a push, you may need to audit what was pushed. Use git log --all --oneline --graph to see the full history. For a specific remote, git log origin/main..main shows commits not yet pushed. To see who pushed what, use git reflog show origin/main (if available) or check the remote's audit log (GitHub's 'Push' events). Reflog is local, but you can compare with teammates. If a force push occurred, the reflog shows the old HEAD. Use git fsck --lost-found to find dangling commits. Regularly audit your repository's push history to catch anomalies early.

audit-push.shBASH
1
2
3
4
5
git log --all --oneline --graph --decorate
# Check reflog for recent changes
git reflog --date=iso | head -10
# Find dangling commits
git fsck --lost-found
Output
* abc1234 (HEAD -> main) Fix login bug
* def5678 (origin/main) Add tests
* ...
abc1234 HEAD@{0}: commit: Fix login bug
def5678 HEAD@{1}: commit: Add tests
...
dangling commit 1234abc...
🔥Audit Logs Are Your Friend
Regularly review push history and reflog. In production, integrate with monitoring tools to alert on unexpected force pushes.
📊 Production Insight
In production, enable branch audit logs on your Git server. This provides a trail for compliance and incident response.
🎯 Key Takeaway
Audit push history using git log and git reflog to detect and recover from unwanted changes.
● Production incidentPOST-MORTEMseverity: high

The Force Push That Silently Deleted 3 Commits

Symptom
Two developers were working on the same feature branch. Developer A rebased their local branch and used git push --force to update the remote. Developer B had pushed 3 commits that Developer A didn't know about. The force push silently deleted Developer B's 3 commits from the remote. The team discovered the missing commits 3 days later during QA.
Assumption
Developer A assumed that since they were the only one working on the branch, force push was safe. They did not communicate with Developer B before pushing. They used --force instead of --force-with-lease.
Root cause
1. Developer A and Developer B were both working on feature/payment-v2. 2. Developer A rebased their local branch onto main (legitimate rebase to resolve conflicts). 3. While A was rebasing, Developer B pushed 3 commits to the remote: 'Add validation', 'Fix timeout', 'Add logging'. 4. Developer A ran git push --force origin feature/payment-v2. 5. --force unconditionally overwrote the remote branch with A's local state. 6. Developer B's 3 commits existed only locally on B's machine. 7. Developer B's next git pull produced 'diverged branches' error. 8. B assumed it was a local issue and spent 2 hours debugging before realizing commits were missing from remote. 9. Three days later, QA noticed 'Add validation' was missing from the deployed feature.
Fix
1. Immediate: Developer B recovered their commits from local reflog and re-pushed to a new branch. 2. Cherry-picked B's commits onto A's branch to restore them. 3. Both devs switched to git config --global push.forceWithLease true which makes --force default to --force-with-lease. 4. Team rule: announce before force-pushing to shared branches. 5. Branch protection: added rule to require PR review before merging to feature/* branches. 6. Added CI check: git push --dry-run step that warns if branch has diverged from remote.
Key lesson
  • NEVER use git push --force.
  • Always use --force-with-lease which fails if the remote has commits you don't know about.
  • Communicate before force-pushing shared branches.
  • Configure push.forceWithLease globally.
  • Branch protection rules prevent force-pushes entirely.
Production debug guideUse this when production is on fire5 entries
Symptom · 01
Push a branch to remote for the first time
Fix
Run git push -u origin <branch> — sets upstream tracking
Symptom · 02
Push with upstream already configured
Fix
Run git push — uses upstream branch from -u
Symptom · 03
Re-push after rebasing (force required)
Fix
Run git push --force-with-lease — NEVER --force
Symptom · 04
Delete a remote branch
Fix
Run git push origin --delete <branch>
Symptom · 05
See what would be pushed without sending
Fix
Run git push --dry-run origin main
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
safe-push.shgit checkout mainThe Anatomy of a Safe Git Push
force-push-example.shgit checkout feature/login-fixUnderstanding Force Push
recover-force-push.shgit reflogRecovering from a Force Push Disaster
branch-protection.ymlbranches:Preventing Force Push with Branch Protection
pre-push-hook.shwhile read local_ref local_sha remote_ref remote_shaUsing Pre-Push Hooks to Catch Mistakes
safe-rebase-workflow.shgit checkout feature/new-apiSafe Rebasing and Force Push Workflow
fix-non-fast-forward.shgit pull --rebase origin mainRecovering from a Non-Fast-Forward Rejection
audit-push.shgit log --all --oneline --graph --decorateAuditing Push History with Git Log and Reflog

Key takeaways

1
git push -u origin <branch> sets upstream tracking on first push
2
Always use --force-with-lease, NEVER plain --force
3
Configure push.forceWithLease true globally
4
Branch protection rules prevent force-pushes to protected branches
5
Recover lost commits via reflog on the developer who has them
6
Use git revert for fixing bad commits on shared branches, not reset+force-push
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between `git push --force` and `git push --force-with-lease`?
02
How do I recover commits lost after a force push?
03
Can I prevent force pushes to the main branch?
04
What should I do if I get a non-fast-forward rejection?
05
Is it safe to force push a feature branch that only I work on?
06
How can I audit who force pushed to a branch?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
🔥

That's Git. Mark it forged?

3 min read · try the examples if you haven't

Previous
Git Log: Master Commit History Search and Filtering
44 / 47 · Git
Next
Git Remote: Manage Repository Connections Safely