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.
- ✓Basic programming fundamentals (variables, functions, control flow)
- ✓No prior devops experience needed — we start from first principles
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.
Push Basics: Upstream, Tracking, and Safe Force Push
git push origin main pushes local main to remote origin's main. git push -u origin feature pushes and sets upstream tracking — after this, simply git push works. git remote -v shows configured remotes.
git push --force-with-lease is the ONLY safe way to force-push. It checks that the remote branch is at the commit you expect before overwriting. If another developer pushed, --force-with-lease fails with a warning. git config --global push.forceWithLease true makes even --force behave like --force-with-lease.
git push --dry-run shows what would happen without sending. git push --delete origin <branch> deletes a remote branch. git push --tags pushes tags that git push doesn't send by default.
git push --force unconditionally overwrites the remote branch. It does not check if the remote has advanced since your last fetch. --force-with-lease checks the remote's expected state and fails if unknown commits exist. Configure push.forceWithLease true globally as a safety net. Branch protection rules on GitHub/GitLab can prevent force-pushes entirely for specific branches.push.forceWithLease globally on every developer machine. On CI, require git push --force-with-lease for any deploy workflow that modifies release branches. Use GitHub branch protection rules: set 'Allow force pushes' to 'Everyone' with 'Force push bypasses pull request requirement' unchecked — this prevents force-pushes to protected branches entirely.-u on first push. Use --force-with-lease not --force. Configure push.forceWithLease true globally. Branch protection rules prevent force-pushes to main/release.Recovering from a Bad Push
If you force-pushed and lost commits: 1. Anyone who has the commits locally can re-push them. 2. Check their local reflog: git reflog | grep <branch-name>. 3. Recreate the branch from the found SHA: git branch recovered <sha>. 4. Push the recovered branch: git push origin recovered.
If the bad push was a force-push that deleted a remote branch: use GitHub's 'Restore branch' button (Settings → Branches → 'View branches' → find deleted branch → Restore). For GitLab, use the 'Restore branch' button in Repository → Branches.
If you pushed sensitive data (API keys, passwords): assume it's compromised. Rotate the key immediately. Use git filter-branch or git filter-repo to remove the data from history, then force-push with lease. Notify your security team.
git reflog on every developer's machine to find the SHA. 2. git push --delete origin <branch> to remove the corrupted remote branch. 3. git push origin <branch> from the developer who has the good copy. 4. Notify the team that the branch was force-recovered. For CI/CD, always use git push --force-with-lease in deploy scripts — never plain --force.git revert for bad commits (not force-push). For sensitive data, use git filter-repo. Tag recovery checkpoints before risky operations.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 |
|---|---|---|
| 01_push_basics.sh | git push -u origin feature/payment-v2 | Push Basics |
| 02_push_recovery.sh | git reflog | head -10 | Recovering from a Bad Push |
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-push20+ 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