As a developer, you‘ll often find yourself needing to overwrite files in a remote Git repository with local changes. By default, Git prevents you from force pushing to avoid accidentally overwriting commits. However, there may be cases where you intentionally want to overwrite the remote history. Here‘s how to force Git push to overwrite remote files.

Understanding Git‘s Refusal to Overwrite

First, it‘s important to understand why Git doesn‘t allow forced overwrites by default.

When you clone a Git repository, you get the entire commit history going back to the first commit. Each commit is like a snapshot in time, with a unique SHA-1 hash ID that identifies it. These commits form a directed acyclic graph (DAG) that represents the evolution of the project over time.

If Git allowed you to forcibly overwrite the remote history, you could end up with duplicated commits or a corrupted commit DAG. Other developers pulling from that repository would get incorrect or confusing results.

So Git disallows forced push by default as a safety measure. When you try to push local commits that conflict with the remote history, you‘ll get an error like this:

! [rejected]        main -> main (fetch first)
error: failed to push some refs to ‘https://github.com/example/repo.git‘
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., ‘git pull ...‘) before pushing again.  
hint: See the ‘Note about fast-forwards‘ in ‘git push --help‘ for details.

This prevents accidental corruption of the shared commit history. But sometimes, overwriting the remote files is intended.

When Forced Push May Be Necessary

There are a few cases where you may need to forcibly overwrite the remote repository:

  • Reverting recent changes: If some bad commits got pushed recently, you can overwrite them with a forced push. This rewrites history to undo those changes.

  • Mirroring an upstream repo: Open source projects often have many mirrors on GitHub. These mirrors periodically force push to overwrite themselves with the canonical upstream repository.

  • Splitting/squashing commits: You may rewrite commits locally with rebase -i, split/squash/alter them, then force push to overwrite the remote branch.

  • Backup/migration: When backing up or migrating a repository to a new server, a forced push can overwrite the destination with the latest commit data.

  • Syncing feature branches: Teams often develop features in isolated branches then force push those branches when ready to publish the changes upstream.

  • Resolving merge conflicts: If a remote merge caused integration issues, engineers may fetch, revert locally, then force push to resolve, overwriting the bad remote merge.

  • Repository backups: Scripted backup jobs often force push copies of repositories to remote backup servers to archive the current state.

In these cases, a forced push is destructive but intentional. As long as all collaborators know to expect it, it won‘t cause issues. But uncontrolled force pushing, especially of shared branches like main, can still cause confusion.

Dangers of Uncontrolled Force Pushing

While force pushing has valid applications, uncontrolled use on public branches puts your collaborators at risk:

  • Lost work/commits: If another developer has new commits not merged locally, a forced push can cause remote-tracking branches to lose those commits entirely.

  • Repeated conflict resolution: Engineers may waste significant time resolving merge conflicts due to their existing local work being repeatedly overwritten by force pushed remote changes.

  • Broken tools/automation: Repository analysis tools, CI pipelines, etc may break or behave unexpectedly when the previously analyzed commit history changes via force push.

  • Limited accountability: Rewriting commit history can be used to erase poorly thought out commits or hide unintended information leaks, rather than fixing at the source.

One Git source code analysis found 17% of active repositories on GitHub use force push, primarily on feature branches. But even rare force pushes of main branches caused significant developer frustration and disruption. Integrating force pushed changes wasted 14% of total project coordination time in some analysed projects..

So while force push is a powerful tool, it must be handled with care to avoid productivity loss and confusion.

How to Force "git push"

With the background above, let‘s see how to actually force a git push to overwrite remote files when needed.

1. Check What Needs to Be Overwritten

Before blindly force pushing, check what commits/files Git wants to overwrite. Compare the local and remote commit histories:

git fetch --all
git log --graph --decorate --oneline origin/main..main

This compares main (your local branch) to the remote origin/main and shows a commit graph diff. Review this diff to ensure the overwrite makes sense.

2. Force Push with -f

Once you‘ve verified the intended changes, force push them with the -f (or --force) flag:

git push -f origin main

The -f option disables Git‘s default safety checks and allows overwriting history on the main branch of the origin remote.

You can also combine -f with other options:

  • Force push a specific commit: git push -f <remote> <commit>:<branch>
  • Force push all branches: git push -f --all <remote>
  • Force mirror a repository: git push -f --mirror <remote>

But in most cases, force pushing a branch is sufficient.

3. Remember to Warn Other Developers

When you force push shared branches, it poses a risk of disrupting other developers with conflicting work. So it‘s good practice to warn them a force push is coming.

For example, add a file called FORCE_PUSH.txt to the root of the repository before pushing:

echo "Force pushing to revert recent changes - please run \"git pull\" after fetch" > FORCE_PUSH.txt
git add FORCE_PUSH.txt
git commit -m "Warn force push coming" 
git push -f

This clearly signals a forced update is coming so no one gets blindsided by their commits suddenly being rewritten.

Configuring Force Push Settings

If you need to force push often or want to enforce limitations around it, Git provides some configuration options:

Disabling Non-Fast-Forward Pushes

To completely disable force pushing and rewriting history for a repository, enable the receive.denyNonFastForwards config:

# In remote repository:   

git config receive.denyNonFastForwards true

This protects published history by rejecting any push that would cause non fast-forward merges.

Whitelisting Specific Users

You can whitelist specific users/groups who are allowed to force push with receive.allowNonFastForwards:

git config receive.allowNonFastForwards group-developers

This allows the group-developers group to force push while denying all other users.

Fine-grained access control with Git hooks helps ensure force pushes don‘t happen accidentally.

Requiring Force Push Warnings

It‘s also possible to enforce usage of the FORCE_PUSH.txt convention above by configuring server-side hooks like pre-receive. The hook can check pushed commits for the force push warning and reject the push if it‘s missing. This would ensure developers always warn when force pushing.

Branch Specific Policies

More advanced Git hosts like GitHub and GitLab also allow setting per-branch force push policies, either blocking force push entirely or allowing it for specific user roles:

# GitLab example protecting main
main:
  push_rules:  
    # Deny force pushes
    reject_force_pushes: true 

Setting branch-specific rules provides targeted protection against force push risk only where needed.

Alternatives to Force Push

While useful in some cases, force pushing should not be overused as a shortcut to avoid other Git workflows. Here are some alternatives:

git reset vs force push

The git reset command serves a similar purpose to force push – undoing commits to revert to an earlier state:

# Soft reset
git reset --soft HEAD~5  

# Hard reset
git reset --hard HEAD~5

However, reset only changes local history – additional steps would be needed to force push the rewritten history upstream. Force pushing skips this.

Still, favor local git reset over force push where possible. This avoids broadcasting rewritten history across the entire team. Only force push after resetting locally if upstream synchronization is absolutely required.

git revert vs force push

When specific commits go bad, git revert is another option creating a new "inverse" commit undoing changes rather than resetting history:

git revert 72856ea

This may be preferred over force pushing to remove buggy changes, as it does not destroy historical evidence of what went wrong.

git rebase vs force push

Both interactive git rebase and force push rewrite history – but rebase does so only locally by default.

git rebase -i HEAD~5
# Squash, edit commits

git push -f   # Force push rewritten commits 

So combining local rebase with force push aligns with the best practice of rewriting history locally before overwriting the remote branch when necessary.

Overall, reach for force push only when important – other Git workflows can avoid the downstream disruption of overwriting shared history in many cases.

Recovering from Dangerous Force Pushes

Even with cautions around unsafe force pushing, mistakes still happen. When a damaging force push overwrites critical shared branches, how do we recover?

Finding the Damage

Start by using git reflog across engineers and CI servers search for evidence of the original references before being overwritten:

git reflog show
# c5f5671 HEAD@{0}: clone: from https://github.com/project/repo.git

Any references in reflog to the missing commits not on the main branch may provide a path to restore those commits.

Inverting the Force Push

If you have access and knowledge to perform the dangerous force push originally, the same engineer can often inverse the push to put contents back:

# Fetch missing old commits from reflog 
git fetch <remote> +refs/original/main:refs/remotes/origin/main

# Inverse force push  
git push -f origin refs/remotes/origin/main:main

This leverages the original capability for harm to undo it. But lacking that access, additional remediation may be needed.

Pull Request Reverts

If manual recovery isn‘t feasible, creating pull requests with commits or patches reversing the damage may be the next option. This leverages the project‘s code review process to apply fixes.

Review and merge these with caution and coordination though – as misapplication risks compounding the issues!

Upstream Source Recovery

As a last resort, restoring missing commits from upstream sources like git mirrors may serve as a source of truth:

# Fetch from GitHub mirror
git fetch github-mirror main 

# Force push mirror contents upstream  
git push -f origin github-mirror/main:main

This allows an upstream read-only copy like GitHub to provide read-only recovery of the missing pieces of history.

Having robust backup plans for when the worst case force push scenarios occur lets you recover from even very destructive events.

Guidelines for Safe Force Pushing

Between configuration policies, access control, and disaster recovery tools, how do engineering teams allow flexible force pushing while preventing damage? Here are some best practice guidelines:

1. Protect default branches

Reject force pushes for high risk branches like main via receive.denyNonFastForwards. Allow only for feature/task branches.

2. Require peer reviews

Mandate an approved pull request before force pushing shared branches. This adds oversight to prevent unnecessary rewriting.

3. Limit contributors

Scope force push abilities via branch rules or access control. Ensure only expert engineers have rights to destructive operations.

4. Notify stakeholders

Require adding warnings in commit messages before force push explaining why rewriting history is necessary.

5. Build recoverability

Configure upstream mirrors and robust CI logs/pipelines to remediate issues from force pushes gone wrong.

Adopting these force push hygiene practices makes it possible to enable the flexibility benefits safely.

Conclusion

By default, Git protects the shared commit history – but sometimes overwriting the remote files is needed to undo changes, mirror repositories, rebase commits or migrate data.

The git push -f command forcefully updates remote branches, allowing non-fast forward history rewriting. Used judiciously this power enables workflows like isolated feature development otherwise prevented by distributed sharing constraints.

However, accidentally or maliciously overwriting critical shared branches risks lost work, repeated conflicts and automation failures for your team. So combine force push capabilities with controls like branch protections, peer reviews, and backup plans when enabling.

With the right precautions, force push remains a useful tool – albeit one that should still be handled with care!

Similar Posts