Reverting a series of pushed merges and commits in Git (without rewriting history)

前端 未结 4 2016
时光说笑
时光说笑 2021-02-01 16:36

Context

One of my teammates mistakenly pushed some commits to our main development branch. We\'re a small, collocated team. Our remote repository is hosted on an inter

相关标签:
4条回答
  • 2021-02-01 17:26

    You have a small co-located team, so communication isn't a problem. Make the commit history look as it should have looked:

    git branch -f develop dab4896
    git branch newfeature 8c29252
    git push -f origin develop newfeature
    

    and have everyone refetch. You're done.

    This kind of mistake is one of the reasons to rewrite.

    0 讨论(0)
  • 2021-02-01 17:32

    Even though your history has changed, you can create branches that let you go back and experiment. Git means never having to say, “you should have.” If you converge on a reality you like better, then go with it. Otherwise, throw it away.

    The examples below will create new branches that leave everything else in your repository alone.

    Alternative 1: git revert

    First create a scratch branch at the point where you started your adventure.

    $ git checkout -b tmp-revert faada93

    By specifying a commit range, git revert will undo multiple commits.

    $ git revert da8b496..faada93

    Alternative 2: git commit-tree

    Consider the diagram below from Git Internals — Git Objects, section 10.2 in the second edition of Pro Git by Scott Chacon and Ben Straub. The topmost commit (“third commit”) has a SHA1 hash that begins 1a410e. In the context of this history, 1a410e^{tree} would resolve to 3c4e9c, that is, the tree object immediately to the third commit’s right.

    Figure 151 from Pro Git, 2nd ed.

    Study this model to understand how git tracks content. Creating a new fourth commit whose tree is identical to the second commit’s (that is, 0155eb) would add a new commit object that would share or “point to” the existing tree and blobs rather than adding new duplicate objects.

    Read on to learn how to perform this low-level stitching with git commit-tree.

    Start by creating another temporary branch to work on.

    $ git checkout -b tmp-ctree faada93

    At this point, you want to create a new commit where its tree (that is, the committed code) is identical to that of da8b496, the last commit you wanted to keep. This tree is directly addressable in git: da8b496^{tree}.

    git commit-tree is “plumbing,” a low-level command in git—as opposed to “porcelain.” It may feel awkward or unfamiliar to use, but in this case it gives precise control of the result you want.

    Create a new unattached commit whose tree is the same as da8b496’s and whose parent (-p) is the tip of the current branch, faada93 in your case. Note that git commit-tree reads the commit message of the new commit on the standard input, which the command below supplies with the echo command.

    $ echo Revert back to da8b496 | \
        git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree)
    new-commit-sha1

    The italicized portion above is not part of the command. It indicates that git commit-tree outputs the SHA1 hash of the newly created commit. Knowing the new commit’s SHA1, you can move the branch to that point, e.g.,

    $ git merge new-commit-sha1

    In the command above, replace new-commit-sha1 with the output from git commit-tree. (You could do the same git reset --hard new-commit-sha1, but hard reset is a sharp tool where casual use is best avoided.)

    You could roll all of the above into a single compound command.

    $ git merge --ff-only $(echo Revert back to da8b496 | \
        git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree))

    The --ff-only switch to git merge is meant to prevent surprises. Your intent is for the new commit to be a fast-forward or a descendant of the current branch head—its immediate child, in fact!

    Cleanup

    To delete the temporary branches above, switch to another and fire away, Mr. McManus. Your other branches will be just as you left them.

    $ git checkout develop
    $ git branch -D tmp-revert tmp-ctree

    The two should be identical, as you can verify with

    $ git diff tmp-revert tmp-ctree

    To keep one, merge it into your develop branch.

    $ git checkout develop
    $ git merge --ff-only tmp-ctree
    $ git push origin develop
    0 讨论(0)
  • 2021-02-01 17:34

    What you are trying to do is very risky.

    indeed you can revert and delete the commits that you have already pushed to the repo but if someone has already pulled your changes and he has the commitId that you are going to delete, the repo can become "unstable" and git will not be able to handle the pull and pushes since you deleted commit that is now removed from the history.

    Do this (revert and delete commit ) only and only if no one has pull this commit yet.

    0 讨论(0)
  • 2021-02-01 17:35

    May I suggest that this could be considered a duplicate of this answer: Make the current git branch a master branch

    Jefromi's excellent solution was:

    [git branch better_branch <last good commit>]
    git checkout better_branch
    git merge --strategy=ours master    # keep the content of this branch, but record a merge
    git checkout master
    git merge better_branch             # fast-forward master up to the merge
    
    0 讨论(0)
提交回复
热议问题