Search code examples
gitgit-branchgit-pushgit-resetgit-bare

How can I remove a commit on a branch of a remote bare repository?


Problem Description

I have gotten myself into a gitpickle. This image illustrates the situation I've put myself in:

Dev commits

Unsurprisingly, I now can't push the green commits, because the non-fast-forward updates are rejected. On the remote, I tried to checkout the dev branch so I could do the git reset --hard HEAD~1 command to obliterate the commit on the remote, but it is a bare repo so I can't checkout the dev branch. The current branch is always "master." My goal is simply to remove the red commit on the remote so I can push the green commits.

Question

How can I remove the red commit on the remote dev branch without having to check out the dev branch on the remote?

Notes

  • I don't want to force the push because other repos pull from the remote and I don't want to break them. However, I know that nobody has pulled from the remote yet, so I'm okay with just removing the red commit.
  • I now see that in the future, I should use the git revert command instead when a commit that needs to be undone has already been pushed.
  • I also have a master branch, release branches, and various feature branches that I've left out to simplify the image. Let me know if they're relevant and I'll add them.

Related Questions

I've looked at these questions (and many more), but I don't think they address my current question because either I don't see how I can apply their solutions to remove a commit on a remote, or they don't work for bare repos.

What does "Git push non-fast-forward updates were rejected" mean?
How to undo last commit(s) in Git?
How can I fast-forward a branch without checking it out
Rebase a branch without checking it out


Solution

  • I don't want to force the push because other repos pull from the remote and I don't want to break them. However, I know that nobody has pulled from the remote yet, so I'm okay with just removing the red commit.

    Force pushing is still exactly what you want to do here. Force pushing does not magically break the remote repository for all involved parties; instead, when pushing with the force flag, you are simply telling Git to update the branch pointer even if that means that non-fast-forward changes need to be made.

    Imagine this is your situation on your (bare) remote:

    A -- B -- C -- D
                   ↑
                 master
    

    Keep in mind that branches are just pointers to commits in the repository’s history. Now you want to push a commit E that has C as its parent, so in the first step, you just transfer the commit:

                /-- E
               /
    A -- B -- C -- D
                   ↑
                 master
    

    Now, you also tell the remote to update its master branch so it points to E. In the normal situation, Git will refuse to do that because master cannot be fast-forwarded to E. So, instead, you will have to force push it, telling Git to ignore that fact. So the remote repository will look like this:

                  master
                    ↓
                /-- E
               /
    A -- B -- C -- D
    

    So unless some other branch points to D (directly or indirectly), that commit is now “lost” and will eventually be garbage-collected.

    Usually, you want to avoid doing this because everyone that knows the commit D (and has a local branch pointing to it) will have problems with the new remote. E.g. if my old local master pointed to D, I would have to manually reset my branch so that master points to E instead. So usually, the rule is to never delete anything (we’re technically deleting D from the history) that we once pushed.

    However, if you are certain that nobody ever fetched D, then there is nothing wrong with this. Those who never had D will not realize that D disappeared and think that master always was like this. And all the other commits that existed before the force-push and still exist later don’t change anything about the situation. What counts is only the commits that are “removed” with your force-push.