好的,这就是我想要的,非常像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将处于一个独立的阶段或者其他什么,我不知道如何将这个阶段重新复制到顶部。请帮忙。谢谢。
答案 0 :(得分:3)
有三种相当直接的方法可以达到你想要的效果:
git revert --no-commit HEAD~3..HEAD && git commit
git read-tree --reset -u HEAD~3 && git commit
git rm -rf -- . && git checkout HEAD~3 -- . && git commit
这三个都要求您输入新的提交消息;您可以将-C HEAD~3 --edit
添加到git commit
命令,以便您可以从HEAD~3
中的消息开始进行编辑。这三个中的最后一个要求您在(cd
- ed)存储库的顶层。如果你还没有,你应该先使用:
cd $(git rev-parse --show-toplevel)
或:
git rev-parse --show-toplevel
然后将输出剪切并粘贴到cd
命令中以进入顶层。
关键词是:
我想将 还原为 &#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"