[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-git_revert_merge":3,"$f2nvwdfp85xvjv":21},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":12,"permalink":13,"excerpt_src":14,"media_type":14,"media_title":14,"media_author":14,"media_url":14,"rating":14,"layout":14,"pv":15,"admin_only":15,"created_at":16,"updated_at":16,"deleted_at":14,"status":17,"html":18,"excerpt":19,"cover":20},119,"article","git_revert_merge","[译]Git回滚合并","2015-05-26 12:50","tech",[11],"Git","\n如果工作流严重依赖分支合并的话，也难免会碰到需要回滚合并的情况。而且回滚还分为两种情况：永久回滚，或者回滚后在稍后重新合并。\n\n假设有如下分支图：\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge1.png)\n\n> 注：Git分支图中的箭头表示依赖关系，并不是分支发展路线。发展路线和箭头是相反的。也就是图中是从C1开始一直发展到C12的。\n\n假设要回滚C10。\n\n第一种解决方案是将`master`回退到C8，然后将两个特性分支`jk\u002Fpost-checkout`和`db\u002Fpush-cleanup`合并过来。\n\n```sh\ngit checkout master\ngit reset --hard [sha_of_C8]\ngit merge jk\u002Fpost-checkout\ngit merge db\u002Fpush-cleanup\n```\n\n完成之后，分支图如下：\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge2.png)\n\n接下来就可以继续在新的`master`上工作，然后在适当的时候将`tv\u002Frebase-stat`合并回来。\n\n\u003C!-- more -->\n\n## 回滚合并\n\n如果在很久之后才发现要回滚，或者其它人已经在合并之后提交了代码，分支图会是这样：\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge3.png)\n\n这种情况下要么回退一次合并，要么退回去，再重新合并，然后将新的变更（C9和C10）cherry-pick过来。后者容易让人迷惑，做起来也不容易，尤其是在合并之后提交很多的情况下。\n\n`git revert`能很好地处理合并的回退。你需要指定需要回退的那次合并提交记录，并指定保留合并中的哪一条线（parent）。假设我们需要回退合并`jk\u002Fpost-checkout`的记录，则这样做：\n\n```sh\ngit revert -m 1 [sha_of_C8]\nFinished one revert.\n[master 88edd6d] Revert \"Merge branch 'jk\u002Fpost-checkout'\"\n 1 files changed, 0 insertions(+), 2 deletions(-)\n```\n\n完成后会产生一个新提交，这个提交回滚了合并过来的内容。其结果和一个包含被合并过来分支改动的cherry-pick（反向操作，即回滚）差不多。\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge4.png)\n\n## 回滚“回滚”\n\n假设在回滚之后，我们需要再次合并这个分支。如果你直接合并的话，什么都不会发生。\n\n```sh\ngit merge jk\u002Fpost-checkout\nAlready up-to-date.\n```\n\n更令人困惑的是，如果你回到分支，再做一些修改后再合并，则只有新产生的修改会被合并过来。\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge5.png)\n\n这种状态是一种非常奇怪的状态，有可能导致冲突或者难以理解的错误。此时你真正想做的事情应该是回滚“上一次对合并的回滚操作”。\n\n```sh\ngit revert 88edd6d\nFinished one revert.\n[master 268e243] Revert \"Revert \"Merge branch 'jk\u002Fpost-checkout'\"\"\n 1 files changed, 2 insertions(+), 0 deletions(-)\n```\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge6.png)\n\n现在我们将分支恢复到了合并之后的情况，如果分支上有新的改动，就可以直接合并了。\n\n```sh\ngit merge jk\u002Fpost-checkout\nAuto-merging test.txt\nMerge made by recursive.\n test.txt |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n ```\n\n![分支图](\u002Fassets\u002Fgit_revert_merge\u002Funmerge7.png)\n\n最后，建议使用`git merge --no-ff`合并分支，这样可以保持 分支不是快进的，使它可以回滚。\n\n> 原文地址\u003Chttps:\u002F\u002Fgit-scm.com\u002Fblog\u002F2010\u002F03\u002F02\u002Fundoing-merges.html>，本文并未逐字逐句翻译，仅根据理解摘录了重要部分。\n","\u002Farticle\u002Fgit_revert_merge.html",null,0,"2026-08-28 04:37:17","published","\u003Cp>如果工作流严重依赖分支合并的话，也难免会碰到需要回滚合并的情况。而且回滚还分为两种情况：永久回滚，或者回滚后在稍后重新合并。\u003C\u002Fp>\n\u003Cp>假设有如下分支图：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge1.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>注：Git分支图中的箭头表示依赖关系，并不是分支发展路线。发展路线和箭头是相反的。也就是图中是从C1开始一直发展到C12的。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>假设要回滚C10。\u003C\u002Fp>\n\u003Cp>第一种解决方案是将\u003Ccode>master\u003C\u002Fcode>回退到C8，然后将两个特性分支\u003Ccode>jk\u002Fpost-checkout\u003C\u002Fcode>和\u003Ccode>db\u002Fpush-cleanup\u003C\u002Fcode>合并过来。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git checkout master\ngit reset --hard [sha_of_C8]\ngit merge jk\u002Fpost-checkout\ngit merge db\u002Fpush-cleanup\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>完成之后，分支图如下：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge2.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>接下来就可以继续在新的\u003Ccode>master\u003C\u002Fcode>上工作，然后在适当的时候将\u003Ccode>tv\u002Frebase-stat\u003C\u002Fcode>合并回来。\u003C\u002Fp>\n\u003Ch2>回滚合并\u003C\u002Fh2>\n\u003Cp>如果在很久之后才发现要回滚，或者其它人已经在合并之后提交了代码，分支图会是这样：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge3.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>这种情况下要么回退一次合并，要么退回去，再重新合并，然后将新的变更（C9和C10）cherry-pick过来。后者容易让人迷惑，做起来也不容易，尤其是在合并之后提交很多的情况下。\u003C\u002Fp>\n\u003Cp>\u003Ccode>git revert\u003C\u002Fcode>能很好地处理合并的回退。你需要指定需要回退的那次合并提交记录，并指定保留合并中的哪一条线（parent）。假设我们需要回退合并\u003Ccode>jk\u002Fpost-checkout\u003C\u002Fcode>的记录，则这样做：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git revert -m 1 [sha_of_C8]\nFinished one revert.\n[master 88edd6d] Revert &quot;Merge branch 'jk\u002Fpost-checkout'&quot;\n 1 files changed, 0 insertions(+), 2 deletions(-)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>完成后会产生一个新提交，这个提交回滚了合并过来的内容。其结果和一个包含被合并过来分支改动的cherry-pick（反向操作，即回滚）差不多。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge4.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Ch2>回滚“回滚”\u003C\u002Fh2>\n\u003Cp>假设在回滚之后，我们需要再次合并这个分支。如果你直接合并的话，什么都不会发生。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git merge jk\u002Fpost-checkout\nAlready up-to-date.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>更令人困惑的是，如果你回到分支，再做一些修改后再合并，则只有新产生的修改会被合并过来。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge5.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>这种状态是一种非常奇怪的状态，有可能导致冲突或者难以理解的错误。此时你真正想做的事情应该是回滚“上一次对合并的回滚操作”。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git revert 88edd6d\nFinished one revert.\n[master 268e243] Revert &quot;Revert &quot;Merge branch 'jk\u002Fpost-checkout'&quot;&quot;\n 1 files changed, 2 insertions(+), 0 deletions(-)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge6.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>现在我们将分支恢复到了合并之后的情况，如果分支上有新的改动，就可以直接合并了。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git merge jk\u002Fpost-checkout\nAuto-merging test.txt\nMerge made by recursive.\n test.txt |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n \u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge7.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>最后，建议使用\u003Ccode>git merge --no-ff\u003C\u002Fcode>合并分支，这样可以保持 分支不是快进的，使它可以回滚。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>原文地址\u003Ca href=\"https:\u002F\u002Fgit-scm.com\u002Fblog\u002F2010\u002F03\u002F02\u002Fundoing-merges.html\">https:\u002F\u002Fgit-scm.com\u002Fblog\u002F2010\u002F03\u002F02\u002Fundoing-merges.html\u003C\u002Fa>，本文并未逐字逐句翻译，仅根据理解摘录了重要部分。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n","\u003Cp>如果工作流严重依赖分支合并的话，也难免会碰到需要回滚合并的情况。而且回滚还分为两种情况：永久回滚，或者回滚后在稍后重新合并。\u003C\u002Fp>\n\u003Cp>假设有如下分支图：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge1.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>注：Git分支图中的箭头表示依赖关系，并不是分支发展路线。发展路线和箭头是相反的。也就是图中是从C1开始一直发展到C12的。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>假设要回滚C10。\u003C\u002Fp>\n\u003Cp>第一种解决方案是将\u003Ccode>master\u003C\u002Fcode>回退到C8，然后将两个特性分支\u003Ccode>jk\u002Fpost-checkout\u003C\u002Fcode>和\u003Ccode>db\u002Fpush-cleanup\u003C\u002Fcode>合并过来。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>git checkout master\ngit reset --hard [sha_of_C8]\ngit merge jk\u002Fpost-checkout\ngit merge db\u002Fpush-cleanup\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>完成之后，分支图如下：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge2.png\" alt=\"分支图\">\u003C\u002Fp>\n\u003Cp>接下来就可以继续在新的\u003Ccode>master\u003C\u002Fcode>上工作，然后在适当的时候将\u003Ccode>tv\u002Frebase-stat\u003C\u002Fcode>合并回来。\u003C\u002Fp>\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fgit_revert_merge\u002Funmerge1.png",{"total":22,"totalRoots":23,"comments":24,"pv":15},3,2,[25,33,39],{"id":26,"content":27,"user_id":28,"nick":29,"link":30,"date":31,"rid":15,"vote_up":15,"vote_down":15,"visible":32},137,"\u003Cp>merge真是大坑……\u003C\u002Fp>",65,"Leo Deng","","2015-10-08 10:36:52",true,{"id":34,"content":35,"user_id":36,"nick":37,"link":30,"date":38,"rid":15,"vote_up":15,"vote_down":15,"visible":32},138,"\u003Cp>请问这图怎么在git里查看的？\u003C\u002Fp>",66,"earlybirdme","2016-07-19 09:11:15",{"id":40,"content":41,"user_id":23,"nick":42,"link":30,"date":43,"rid":34,"vote_up":15,"vote_down":15,"visible":32},139,"\u003Cp>Git GUI工具中都能看到类似的图。CLI中git log加参数应该也有图，没具体试过。\u003C\u002Fp>","TooBug","2016-07-19 11:22:40"]