当重写git历史记录时,我如何应用一个提交,将所有内容返回到之前的状态?



我经常想分解一个大的提交:

git commit -a -m "Monolith"

我知道如何拆分提交(git gui是我的朋友),但有时我实际上想手工重做…

git branch temp          # Keep a reference to all my hard work
git reset --hard HEAD~   # Rewind 'my-topic' branch
# hack hack
git commit -a -m "Refactor 1"
# hack hack
git commit -a -m "Refactor 2"

要完成它,然后我想应用另一个提交,使代码完全回到存储在分支temp中的状态,通常包括原始提交消息。

是否有一个简单的方法来做到这一点?

我想象cherry-pick上的合并策略可能会完成这项工作,但是唉,这些都没有完成:

  git cherry-pick --strategy=theirs temp   # I am told about conflicts
  git status                               # ... But here I find no changes to commit!
  git cherry-pick --strategy=ours temp     # Worth a try. Nope, empty commit.
  git cherry-pick --strategy=recursive --strategy-option=ours temp  # Partial result, bits are missing

我知道有一种方法可以做到这一点:

git log -1 temp # note the SHA
git checkout temp
git reset --soft my-topic
git commit -C <SHA-from-above>  # branch 'temp' now contains the desired commit
git checkout HEAD -B my-topic
git br -d temp
然而,这似乎是一种非常容易出错的方法。任何小错误和我想保留的提交可能不在任何分支(git reflog来拯救…)。我想找一些更有逻辑、更容易记住/做的事情。

要在temp上重新创建树,(至少)有两种相当简单的方法:

git checkout temp -- .

,另一个

git diff temp | git apply --index

之后,使用

创建具有相同提交消息的提交
git commit -C temp

这两个选项各有利弊。checkout路由不会删除不再存在于temp中的文件。apply路由不支持二进制文件。

旁注:在现代的Git中,你可以使用git restore而不是git checkout,但我是一个老家伙,还没有学会使用它。

j6t的答案有一些合理的选项,但我更喜欢您自己建议的过程的简化版本。(我不知道你害怕犯什么错误,但我怀疑它们是由于你采取了不必要的步骤而产生的。)

git checkout temp
git reset --soft my_topic
git checkout my_topic
git commit -C temp
git branch -D temp

这似乎是一个奇怪的偏好,因为git checkout -- .(他们的建议之一)看起来像一行;但请注意我对他们的回答的评论。还有其他一些微妙的陷阱,比如如果你忘记了你不在工作树的根节点上怎么办?当然,你可以绕过所有这些…

所以你可以养成先做git rm的习惯,或者"知道";当你需要的时候(犯错的机会)。作为习惯,您可以使用:/:而不是.(并习惯向与您一起工作的每个人解释它),或者总是反复检查您是否在根中(另一个犯错的机会)。然后是

git rm -- :/:
git checkout temp -- :/:
git commit -C temp
git branch -D temp

只有一个简单的命令"less">

直接导入另一个提交的快照的直接路径是执行该操作的核心命令:

git read-tree -u temp

紧随其后
git commit -C temp

temp的commit消息提交。

git read-tree是几乎所有从现有的repo内容更新索引和工作树的命令的基础。git reset是围绕git update-ref HEADgit read-tree的选择逻辑;git checkout 围绕git update-ref HEADgit read-tree的一些选择逻辑;git merge是对git read-tree结果的一些相当繁重的清理工作…是的。顺便说一下,git commit是围绕git update-ref HEADgit write-treegit commit-tree的一些选择逻辑(后者向对象db添加了一个小对象)。

忘掉你的临时分支吧。不是硬复位,而是混合复位。

结果是索引被重置,但工作树没有。现在你可以添加和提交任何你喜欢的组合,然后只需添加其他所有内容并提交完成。

请参阅我的https://stackoverflow.com/a/59675191/341994了解更多。