I have gotten myself into a gitpickle. This image illustrates the situation I've put myself in:
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.
How can I remove the red commit on the remote dev branch without having to check out the dev branch on the remote?
git revert
command instead when a commit that needs to be undone has already been pushed. 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
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.