简体   繁体   中英

How to revert pushing local branch to remote master


1) I have pushed my local branch to the origin master instead of remote branch.
2) Then I pushed my local branch to the remote branch as I it should be
BUT:
How to delete the first action without making a mess ?

You cannot fix this without making a mess, because the mess is already made. The last failsafe before you have a mess is the push to a shared remote.

So the question is, how to best clean up the mess. There are two options.

One option is to do a history rewrite. This will have the "cleanest" end result, but it requires coordination with every user of the repo. IF you can't coordinate with everyone who might have a clone of the repo, then you can't safely do a history rewrite (and if you try to do one, it might end up being undone anyway).

The other option is to add new commits to get to the desired state, leaving a "messy" history behind. That's not great, but for shared repos it does tend to "sort itself out" without a major coordination effort, in a way that history rewrites don't.


History Rewrite

A "history rewrite' is any operation that removes commits from a branch's history. In your case, you had

O -- x -- x <--(master)(origin/master)
           \
            A -- B -- C <--(branch)

and you accidentally pushed branch to origin/master , giving you

O -- x -- x <--(master)
           \
            A -- B -- C <--(branch)(origin/master)

As far as the remote is concerned, A , B , and C are now part of master , so to undo that you could remove A , B , and C from origin's master - but that's a history rewrite.

A rewrite produces the cleanest result, but it's really only a suitable solution if (a) you alone use the remote, or (b) the remote is shared by a small (or at most moderate-sized) team such that you can reasonably coordinate with all team members.

The problem with history rewriting is, it causes a branch to move in an unexpected way; and if git treated this kind of movement as routine, it would cause concurrent changes to routinely be lost - the opposite of what branching and merging is meant for.

You could do a history rewrite locally in a number of ways - but in this case, you don't have to, because your local branches are already in the correct state. But to get the remote to rewrite the history of its branch, you have to do a "forced push'. You may or may not be allowed to do that, depending on how the origin repo is set up.

If you are allowed to do it, then the first step is to communicate to all users of the repo that a history rewrite is going to happen (preferably with an explanation of what happened, why a rewrite is needed, and when it's going to happen). This is because once you push the rewrite, anyone who had pulled any of A , B , and C into their copy of master will be in a "broken" state and will have to perform a local clean-up procedure.

You can read more about the problem and how to clean it up in the git rebase docs under "recovering from upstream rebase". (This is documented under rebase but applies to any remote history rewrite.) Note that any user of the repo could perform their cleanup incorrectly, perpetuating the problem and potentially undoing the rewrite.

Once you have everyone on board, you would

git checkout master
git push --force-with-lease origin master

As long as nobody has pushed other changes to origin/master on top of C , this should move origin/master back where it belongs, and other users can begin cleaning up their local state as needed.

Note that --force-with-lease replaces the older --force (or -f ) option, which is less safe in that it will silently clobber any commits that were pushed on top of C . --force-with-lease will abort in this case, and that would be further reason not to do a history rewrite (or extra work to be done if you do proceed with a rewrite).


Without History Rewrite

If for any reason a history rewrite isn't suitable, or if you just prefer a less disruptive solution and can live with the "messy" history, then you would do something like this. Again you have

O -- x -- x <--(master)
           \
            A -- B -- C <--(branch)(origin/master)(origin/branch)

We need a new commit that will bring origin/master 's content back into alignment with master . For that we can use git revert

git checkout origin/master
git checkout -b temp
git revert -n master..branch
git commit

This gives you

O -- x -- x <-(master)
           \
            A -- B -- C <--(branch)(origin/master)(origin/branch)
                       \
                        ~CBA <--(temp)

and you can verify with git diff that temp matches master . Ultimately we'll move master to temp , but before we do we should solve a new problem:

At this point, A , B , and C are integrated into what will be master 's history, but the changes are not reflected in the final state (because they've been reverted); this means branch can't be merged back in. Fixing this is slightly more complicated because you've pushed branch to origin/branch , and we don't want to create another history rewrite scenario.

Still, to fix this, we need new copies of A , B , and C .

git rebase --onto temp master branch

Will create the copies

O -- x -- x <-(master)
           \
            A -- B -- C <--(origin/master)(origin/branch)
                       \
                        ~CBA <--(temp)
                            \
                             A' -- B' -- C' <--(branch)

Now everything can be resolved with fast-forward merges and fast-forward pushes.

git checkout master
git merge temp
git branch -D temp
git push

yields

O -- x -- x -- A -- B -- C <--(origin/branch)
                          \
                           ~CBA <--(master)(origin/master)
                            \
                             A' -- B' -- C' <--(branch)

and then

git checkout branch
git push

yields

O -- x -- x -- A -- B -- C -- ~CBA <--(master)(origin/master)
                                  \
                                   A' -- B' -- C' <--(branch)(origin/branch)

Note that in these steps, I used the default refspecs for the pushes. You could say

git push origin master

and

git push origin branch

but IMO being too much in the habit of specifying that makes it too easy to push to the wrong remote branch. Instead I recommend having your config set up so that you can rely on defaults most of the time.

AFAIK there is only one way, you need to force push to origin master with the correct branch pointer.

So, locally you would reset the master branch to the correct origin/master state and then

git push -f origin HEAD:master

This will reset the master pointer on origin to the correct state. Anyone that has pulled/fetched between you made the mistake and you fixed it should pull/fetch again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM