Stop using a series of commits in git

Here's what happened:

  • Wrote code
  • Did commit (commit: a)
  • Code worked. There was much rejoicing.
  • Decided to add features.
  • Wrote more code, and created commits b, c, d, e, and f. These were pushed to the repo in master.
  • Realized that was not the best way to do it, and we need to revert back to commit a, so, we did git checkout a and made changes, which work.
  • Now, we need to commit the code as-is to master, effectively deleting, ignoring, or jumping over commits bf.

    How do I tell git: "Yeah, I know I went back to commit a, and then made changes. I don't want anything in commits b, c, d, e, and f anymore. Just make commit g the code exactly as I have it now."?


    If you're OK with screwing up everyone who has used your pushed master, then use Mureinik's advice and just to git push --force origin .

    However, if you have other people who use the master branch and you'd like them not to murder you in your sleep, then:

    First, create a new branch. Let's call it "temp_work":

    git checkout -b temp_work
    

    This will keep your current commit.

    Then, checkout master:

    git checkout master
    

    Revert commits b, c, d, e, and f:

    git revert f e d c b
    

    (It's in reverse order to avoid potential conflicts. Reverting the newest commits first should give you the lowest possibility of conflicts.)

    You will get one editor screen for each commit to revert.

    Finally, merge temp_work into master:

    git merge temp_work
    

    You can now delete the temp_work branch:

    git branch -d temp_work
    

    However, next time, avoid checking out an old commit and doing changes there. Revert the commits you don't want first, and then continue as normal from there, as it saves you a bit of work. But otherwise, still no big issue.


    There are multiple ways to do it. Which you should use depends on (a) what you want the history to look like, and possibly (b) whether the costs of a "history rewrite" make sense and (c) whether your answer to (a) or (b) is more important to you. So let's look at some options...

    No matter what else you do, you may want to start by committing your new changes. This ensures that no amount of fiddling around will cause you to lose them.

    git checkout -b temp
    git commit
    

    Now those new changes become G , and you have

    A -- B -- C -- D -- E -- F <--(master)(origin/master)
     
      G <--(temp)
    

    When you're done, you'll delete temp , and if G isn't part of the actual solution state then it will get cleaned up eventually, so no harm in taking this first step as a safety net.

    Now if you know you'll never care about B through F again, then the simplest solution with the cleanest result is to simply move master to G . This is a history rewrite, in that it removes commits from the history of a ref. When doing history rewrites, you have to coordinate with everyone else who uses the repo / might have fetched the ref when the "removed" commits were in its history. Things that would make a history rewrite more difficult to execute:

  • Other work has been done "on top of" F
  • Lots of developers use the repo
  • Some developers who use the repo are in limited communication and might not be immediately aware of what's happening
  • If you have any of those conditions, you might want to settle for a different solution. The ideal case for a history rewrite would be when you can coordinate with all repo users to

  • push all changes
  • discard all local repos
  • perform the rewrite
  • everyone re-clones
  • If you decide rewriting the history is the right thing to do:


    First move any ref(s) pointed at F (eg master in the above example) to G

    git checkout temp
    git branch -f master
    

    If other branches have been based on F , they need to be rebased to G . For example if you have

    A -- B -- C -- D -- E -- F <--(origin/master)
                             
      G <--(temp)(master)      H <--(feature_b)
    

    then you could say

    git rebase --onto temp origin/master feature_b
    

    yielding

    A -- B -- C -- D -- E -- F <--(origin/master)
     
      G <--(temp)(master)
       
        H' <--(feature_b)
    

    If you have work based on B , D , or E (but not F ), that could be more challenging, though rebasing that may also be ok.

    Finally, you "force push" any branches you moved or rebased. eg

    git checkout master
    git push -f
    

    At this point, all other users need to get back in sync. If, as I suggested above, everyone threw away their local repos, they can just re-clone and they're back in sync. Otherwise, any local changes they have, based on any commit you've replaced, need to be rebased; and their refs have to be updated. See "Recover from upstream rebase" in the git rebase docs for more detail.

    Note that if all of this sounds good, except you want to keep the original commits B through F "off to the side" for future reference, then before moving master in the above procedure you can tag F .

    git checkout master
    git tag old_master
    

    (In truth you can do it after moving master , but at that point it's a little harder to find F .)


    If a history rewrite isn't good for your situation, then B through F have to remain in the history. You can then add one or more commits to "undo" B through F and apply G . This is still easiest if there aren't a bunch of branches all already based on F . In the simplest case, you can

    git checkout master
    git revert HEAD~4..HEAD
    git rebase master temp
    git checkout master
    git merge temp
    

    giving you something like

    A -- B -- C -- D -- E -- F -- ~F -- ~E -- ~D -- ~C -- ~B -- G' <--(master)
    

    If you want to jump straight from F to G' (without explicit commit(s) to revert changes), you could instead just re-parent G onto F (see git filter-branch docs). Or another way to do the same thing

    git checkout temp
    git reset --soft master
    git commit
    git checkout master
    git merge temp
    

    With any of these solutions, B through F still appear in the history (eg git log ). On a repo-by-repo basis, if a developer wants to hide this they can supply A as a "replacement" for the commit before G' (ie G'^ ). See the git replace docs for details.

    One additional option would be to merge G into master (presumably after a revert commit). This yields something like

    A -- B -- C -- D -- E -- F -- ~FEDCB -- M <--(master)
                                          /
      ---------------- G ------------------
    

    which is ok, but makes it harder to "paper over" the history with replacements. Note that in this case you should not combine the revert commit(S) with the merge commit, as this would produce an "evil merge" and could cause trouble down the line.

    链接地址: http://www.djcxy.com/p/49844.html

    上一篇: 如何更改推送提交的消息

    下一篇: 停止在git中使用一系列提交