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.
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
- ✓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.
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.
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.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
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.
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.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 without --with-lease can wipe out commits from teammates. Always use --force-with-lease as a safety net.main can cause production rollbacks. Protect main with branch rules that require pull requests and disable force push.--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.
git reflog to find lost commits and push them to a new branch.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.
main to prevent direct pushes and force pushes. This is a non-negotiable practice for any production repository.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.
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.
--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.
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.
git log and git reflog to detect and recover from unwanted changes.The Force Push That Silently Deleted 3 Commits
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.--force instead of --force-with-lease.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.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.- NEVER use
git push --force. - Always use
--force-with-leasewhich fails if the remote has commits you don't know about. - Communicate before force-pushing shared branches.
- Configure
push.forceWithLeaseglobally. - Branch protection rules prevent force-pushes entirely.
git push -u origin <branch> — sets upstream trackinggit push — uses upstream branch from -ugit push --force-with-lease — NEVER --forcegit push origin --delete <branch>git push --dry-run origin main| File | Command / Code | Purpose |
|---|---|---|
| safe-push.sh | git checkout main | The Anatomy of a Safe Git Push |
| force-push-example.sh | git checkout feature/login-fix | Understanding Force Push |
| recover-force-push.sh | git reflog | Recovering from a Force Push Disaster |
| branch-protection.yml | branches: | Preventing Force Push with Branch Protection |
| pre-push-hook.sh | while read local_ref local_sha remote_ref remote_sha | Using Pre-Push Hooks to Catch Mistakes |
| safe-rebase-workflow.sh | git checkout feature/new-api | Safe Rebasing and Force Push Workflow |
| fix-non-fast-forward.sh | git pull --rebase origin main | Recovering from a Non-Fast-Forward Rejection |
| audit-push.sh | git log --all --oneline --graph --decorate | Auditing Push History with Git Log and Reflog |
Key takeaways
git push -u origin <branch> sets upstream tracking on first push--force-with-lease, NEVER plain --forcepush.forceWithLease true globallygit revert for fixing bad commits on shared branches, not reset+force-pushFrequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's Git. Mark it forged?
3 min read · try the examples if you haven't