将以前的提交点恢复到Git顶部的最简单方法

时间:2018-01-04 03:07:30

标签: git revert git-revert

好的,这就是我想要的,非常像Going back to certain previous commit and not modifying git history

假设我的git日志是这样的:

detour C
detour B
detour A
Last good point

我想恢复到"最后的好点",同时仍然在历史中保持弯路,但与Going back to certain previous commit and not modifying git history不同,我想再次使它成为顶峰。所以之后我的git日志会想:

Revert to last good point
detour C
detour B
detour A
Last good point

我知道官方的方式是

git revert HEAD~3

然而,我得到了

error: could not revert f755e55... Last good point
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'

即,我需要解决那些非常混乱的冲突,这是我想尽可能避免的。我知道

git checkout HEAD~3

会马上把我带到那里,但是我读了git将处于一个独立的阶段或者其他什么,我不知道如何将这个阶段重新复制到顶部。请帮忙。谢谢。

4 个答案:

答案 0 :(得分:3)

TL; DR

有三种相当直接的方法可以达到你想要的效果:

  1. git revert --no-commit HEAD~3..HEAD && git commit
  2. git read-tree --reset -u HEAD~3 && git commit
  3. git rm -rf -- . && git checkout HEAD~3 -- . && git commit
  4. 这三个都要求您输入新的提交消息;您可以将-C HEAD~3 --edit添加到git commit命令,以便您可以从HEAD~3中的消息开始进行编辑。这三个中的最后一个要求您在(cd - ed)存储库的顶层。如果你还没有,你应该先使用:

    cd $(git rev-parse --show-toplevel)
    

    或:

    git rev-parse --show-toplevel
    

    然后将输出剪切并粘贴到cd命令中以进入顶层。

    更长:为什么以上是正确的,从#3

    开始

    关键词是:

      

    我想将 还原为 &#34;最后的好处&#34;

    (强调我的:还原为 ,而不仅仅是还原,这是一个执行稍微不同的Git命令。)

    您也应该警惕 stage 这个词,它在Git中具有技术定义的含义(指复制到临时区域,是Git调用的另一个短语,不同的是,索引缓存,当然还有暂存区域。) [编辑:删除自从标题现在调整]

    执行此操作的低级命令是git read-tree,与PetSerAl's answer中一样。我建议使用git read-tree --reset -u,因为-m表示执行合并,并且您希望重置索引。但有一种方法可以做到这一点,虽然有点笨拙,但可能会使用git checkout对人类更有意义。那个命令集3,我们先来看看。

    正如您所注意到的,git checkout HEAD~3将使所需的提交成为当前提交 - 但它通过&#34;分离HEAD&#34;这是一个可怕的短语,这意味着您不再在一个命名的分支上。 (你&#34;通过运行git checkout branchname来重新附加&#34;你的HEAD,它通过检查该分支的提示提交再次设置以便你在该分支上,这当然意味着你不再使用所需的提交。)这是因为所有提交或多或少是永久性的, 1 并且完全只读:你不能更改过去,您只能重新访问

    git checkout命令可以做的不仅仅是重新访问过去(通过检查特定的过去提交)或切换到其他分支(通过签出任何命名分支)。可能这些操作中的许多或大部分应该具有不同的前端命令,因为将它们全部归入git checkout只会使Git更加混乱;但这就是我们所拥有的:git checkout commit-specifier -- paths告诉git checkout将给定的 paths (文件或目录名称)提取到索引中,然后再进入工作树,覆盖当前索引和工作树中的任何内容,不用更改提交。

    因此:

    git checkout HEAD~3 -- .
    

    告诉Git从提交HEAD~3(从现在的位置返回三步)提取目录.。如果您位于Git存储库的顶层,.将命名存储库中的每个文件。

    更确切地说,.为存储库中特定提交中的每个文件命名。这就是你应该首先运行的原因:

    git rm -rf -- .
    

    告诉Git从索引和工作树中删除每个文件(Git知道,即,现在在索引中的那个)。这一点是......好吧,假设在三次绕道提交期间,你添加了一个 new 文件newfile.ext。该新文件至少在提交detour C中,如果不在所有这三个文件中。但是HEAD~3中的不是,它将提交22769c2命名为您想要恢复的最后一个。所以当你告诉git git checkout 22769c2 -- .或同等的时候,Git会查看22769c2,找到提交的所有文件 - 它不包括newfile.txt - 并使用良好提交中的文件替换当前文件,但在索引和工作树中留下newfile.ext

    首先删除Git在detour C提交中知道的一切,你可以为git checkout ... -- .命令提供一个清晰的提取内容。

    因此,命令集3意味着:

    • 删除Git知道的所有内容,以生成一个干净的索引和工作树。 (Git 没有知道的文件,例如编译器构建的.o文件,或Python的.pyc字节代码文件,或者其他什么,通过.gitignore忽略,请勿删除。)

    • 将好的提交中的所有内容提取到索引和工作树中:用好东西填充干净的平板。

    • 提交:进行提交,而不是22769c2但是其他一些哈希ID,其父级是detour C提交,但其内容是其中的任何内容现在是索引,这是我们刚从22769c2中提取的内容。

    1 &#34;或多或少&#34;部分原因是您可以通过更改各种名称来放弃提交,以便 name 不再查找这些提交。没有找到它们的名字,提交就会丢失并被遗弃。一旦它们被放弃了足够长的时间 - 通常至少30天,因为隐藏的 reflog条目名称仍然可以找到提交,但这些reflog条目最终会到期,通常在30天内进行此类提交-Git&# 39; s Grim Reaper 收集器,也称为垃圾收集器git gc,实际上将删除它们。

    git read-tree方法

    git read-tree --reset所做的是,尽可能简单地将git rm -r --cached .步骤与大多数git checkout HEAD~3 -- .步骤结合起来。当然,这些并不完全是#3中的内容:此表单--cached仅删除 index 条目。此外,git checkout步骤填充工作树。这是命令-u添加的内容:它更新工作树以匹配对索引所做的更改。删除某些条目(如果有的话)删除,会导致删除相应的工作树文件;更新其余条目,包括从正在读取的提交中添加新条目,会导致更新或创建相应的工作树文件。因此,git read-tree --reset -u HEAD~3与我们的移除和签出序列相同,但效率更高。

    (你可能不记得了:git read-tree不是经常使用的命令。另外,使用-m告诉Git 目标树合并到当前索引中,这不是你想要的,尽管它几乎肯定会在这里做正确的事。)

    或者您可以使用git revert -n

    上面的第一个命令使用git revert --no-commit。这是拼写-n的漫长道路,这意味着在不提交结果的情况下执行每个恢复。通常,git revert执行的操作是将每个提交转为待恢复进入变更集,然后&#34;反向应用&#34;变化。给定一系列提交,例如HEAD~3..HEAD,Git首先收集所涉及的所有哈希ID的列表 - 在这种情况下它们是:

    7a6c2cc detour C
    dc99368 detour B
    1cf4eb4 detour A
    

    Git然后以向后的顺序遍历它们,从最亲孩子到最父母,即首先查看detour C,然后是detour B,然后是detour A

    每个提交本身都是一个快照,但每个提交都有一个,也是一个快照。从detour B中的detour C中的内容中减去README.md快照中的内容,告诉Git实际上是已更改以便从B转到C.然后Git可以&#34;不改变&#34;确切地说是那些变化:如果从B到C添加一行到README.md从<{1}}删除该行。如果它从a.txt删除了一行,该行添加回a.txt。如果它删除了整个文件,则将该文件放回去;如果添加了新文件,请将其删除。

    一旦所有更改都被撤消(结果与绕行B快照中的内容相匹配),git revert - 显然应该被称为git backout - 通常会结果的新提交;但是对于-n,它并没有。相反,它将结果保留在索引和工作树中,准备提交。然后它继续进入列表中的下一个提交,这是绕行B. Git将其与其父进行比较以查看更改的内容,并撤消这些更改。结果是,在这种情况下 ,与绕道A中的相同快照。

    如果我们从绕道C快照以外的其他地方开始,那么退出绕道C的变化将不会与绕道B相匹配,然后退出绕道B的变化将与绕道A中的变化不匹配。但我们确实是从绕道C快照中的内容开始的。所以现在Git支持绕道A改变的任何东西,留下 - 那是对的! - 无论在最后一次好的提交中是什么。

    此状态现在位于索引和工作树中,准备提交。所以现在我们只是将它作为新提交提交。这个命令序列1:以相反的顺序还原(退出)三个不好的想法,这保证会有效,因为我们从最后一个快照开始。不要提交任何中间结果。然后,一旦索引和工作树匹配最后一次良好的提交,就进行新的提交。

答案 1 :(得分:1)

您可以简单地从Last good point提交读取树,然后提交它:

git read-tree -m -u HEAD~3
git commit
-m -u的{​​p} read-tree选项将更新给定树的工作目录。

答案 2 :(得分:0)

这是git revert的替代方案,可以为您提供相同的信息,但可以避免混乱的合并冲突。只需从当前点检出一个新分支,然后将该分支硬重置到前一点:

git checkout your_branch
git checkout -b new_branch
git reset --hard HEAD~3

现在new_branch在功能上是你想要的,而且你仍然有三个提交坐在真正的your_branch分支。

答案 3 :(得分:0)

您可以按相反的顺序逐个恢复提交,以避免修复合并冲突并最终达到所需的提交。

假设您最初有以下历史记录。

7a6c2cc detour C
dc99368 detour B
1cf4eb4 detour A
22769c2 Last good point

您可以运行以下git revert命令序列。

git revert 7a6c2cc
git revert dc99368
git revert 1cf4eb4

这将为您提供如下的提交历史记录。

3b67aaf Revert "detour A"
9028879 Revert "detour B"
6d9bcce Revert "detour C"
7a6c2cc detour C
dc99368 detour B
1cf4eb4 detour A
22769c2 Last good point

最后,您的代码将与Last good point提交时的代码相同。

您还可以将--no-commit标记与git revert一起使用,以避免每个恢复的单独提交。

git revert --no-commit 7a6c2cc
git revert --no-commit dc99368
git revert --no-commit 1cf4eb4
git commit -m "Revert detour C, B and A"