git push --force and how to deal with it

Ever been in a situation where the wrong Git command has had a chaotic impact on your project’s repo? People make mistakes! …but sometimes, these mistakes can cost hours of your team’s time. So, in this post, we’ll show you how to quickly recover from an unfortunate git push --force.

Editor’s note: This post was originally published in 2017, but it’s been overhauled for 2024!

Now, if you’re thinking, “this doesn’t sound like something I’d ever do”, well, let’s not get overly confident. Sooner or later, this is going to happen. When working with several remotes in the same Git repository, eventually, you will git push --force into main (or another important branch that should’ve never been messed with).

This might happen, for instance, when deploying with Heroku, which uses separate Git remotes to build and deploy an application. Following a long day of work, it could be incredibly easy to execute git push --force instead of the usual git push --force heroku main.

…and oops! In the blink of an eye, your teammates have lost all their latest work. Time to face their rage.

However, as an excellent guide from a separate field famously reminds us, “DON’T PANIC”! After all, the upside is that you’re using Git, and that means everything can be fixed.

Let’s start with the best case scenario: someone else working on the same code pulled a recent version of the main branch just before you broke it. In this case, all you have to do is to go into your team chat and ask this person to force push their recent changes.

Schedule call

Bring our backend experts to your project

If you’re lucky, their local repository will have the full history of commits, your mistake will be overwritten with fresh code (and probably forgotten by all), and nothing will be lost.

But what if you’re not so lucky? Then, read on!

Good news! You already have everything you need to undo your mistake. But do not close or clear your terminal.

Background for Easy multi-language, multi-version documentation with Docsify, Git, and GitHub Actions

Keep your team (and everyone else) happy by maintaining project docs with Docsify.

With this in mind, first, go into your team’s chat and confess your sins. Ask people not to mess with the repo for the next minute or so while you’re fixing things. Inside your shell, look at the output of git push --force and try to locate a line that resembles this one:

 + deadbeef...f00f00ba main -> main (forced update)

The first group of symbols (which looks like a commit’s SHA prefix) is the key to pulling off this rescue operation. In this case, deadbeef is your last good commit to the main just before you inflicted this unfortunate damage.

So all you need is to… force push (we’re fighting fire with fire!) this commit back to the main branch, on top of the bad one.

$ git push --force origin deadbeef:main

And congratulations! You’ve saved the day. Just be sure to learn from your mistakes!

So, next, let’s say that just before you performed git push --force, someone had closed a bunch of pull requests, and so main now looks nothing like your local copy.

In this case, you can no longer do git push --force sha1:main because you do not have the recent commits locally (and you can’t get them with git fetch because they no longer belong to any branch).

Nevertheless, keep calm and ask your teammates to stay off the remote for a while.

GitHub does not immediately remove unreachable commits, and this will be of benefit to us here. Of course, you can’t fetch them either, but there is a workaround.

Still, let’s note that this appraoch will only work if you are “watching” the repository (meaning that everything happening in it appears in a feed displayed on your GitHub front page).

If so, open this feed and look for something like this:

an hour ago
Username pushed to main at org/repo
 - deadbeef Implement foo
 - deadf00d Fix bar

Now you can:

  • Compose a URL https://github.com/org/repo/tree/deadbeef, where deadbeef is the hash of the last good commit to the damaged branch
  • Open the branches/tags switcher in GitHub’s web UI

GitHub branch/tag switcher

GitHub branch/tag switcher

  • Create a name for a new temporary branch (e.g., main-before-force-push)
  • Click “Create branch”

Now you can fetch all missing commits:

$ git fetch
From github.com:org/repo * [new branch] main-before-force-push -> origin/main-before-force-push

With this, your problem has been reduced to the one described in the previous case:

$ git push --force origin origin/main-before-force-push:main

If you still need your work to be in the main, just rebase on top of it:

  1. GitHub and GitLab have a feature called “protected branches”. So, we can mark main, develop, stable, or any other crucial branches, as “protected” and no one will be allowed to force push into them. It’s always possible to temporarily “unprotect” a branch if you really need to overwrite history.

    Read the GitHub docs for more details.

  2. Instead of the --force option, use --force-with-lease. It will halt the push operation if someone has pushed to the same branch while you were working on it (and haven’t pulled any changes).

    See this article from Atlassian (it hasn’t been updated in a while, but the content is still relevant).

  3. Create aliases for commands that use git push --force to prevent destructive actions by accident:

    
    [alias] deploy = "!git push --force heroku \"$(git rev-parse --abbrev-ref HEAD):main\""
    

    Read the ProGit chapter about aliases.

  4. Never do experiments in the main branch of a repository! git switch -c experiments is your best friend.

Pro Tip: git push has many available options. --force and --all work together especially well. You can use this combo when returning to the project after several months of inactivity. Give it a try if you’re feeling extra adventurous!

  • Refresh links, overhaul text, and change commands to be up-to-date.

ホーム - Wiki
Copyright © 2011-2024 iteam. Current version is 2.132.0. UTC+08:00, 2024-09-21 19:30
浙ICP备14020137号-1 $お客様$