Git: 如何从更改的历史记录中恢复?

3
一种高度推荐的做法是不要更改已经被别人拉取过的提交记录。尽管如此,仍会有人不理解这一点而进行更改。从“其他用户”的角度来看,而非更改历史记录的人自己,最好的恢复方式是什么?
以下是一个具体的例子:
Ryan创建了几个提交并推送:
ryan $ git init
ryan $ echo "initial stuff" > file.txt
ryan $ git add file.txt
ryan $ git commit -m "Initial commit" 
ryan $ echo "more stuff" >> file.txt
ryan $ git add file.txt
ryan $ git commit -m "More stuff"
ryan $ git remote add origin [some url]
ryan $ git push origin

Joe克隆了GitHub存储库

joe $ git clone [some url]

目前为止,一切都很好,但是...

Ryan更改了最后一次提交:

ryan $ vi file.txt
ryan $ git add file.txt
ryan $ git commit --amend -m "More stuff"
ryan $ git push origin
       [Fails]
ryan $ git push -f origin master

请注意,如果Ryan是唯一从源代码库拉取代码的人,则在失败后进行强制推送是可以接受的。但在这种情况下,他已经把Joe搞砸了。
Joe尝试拉取:
joe $ git pull origin
      [Conflict!]

现在这只是一个非常简单的例子,但Joe很可能不熟悉file.txt文件,并且会发现合并更改很困难。
我知道问题的根源在于Ryan做了错事,但无论如何,我正在寻找Joe和他的队友能够恢复而不必进行合并提交的方法。
我知道一种技巧,就是删除本地复制的远程分支,然后从原点重新检查它。但当然,这仅适用于您没有进行任何更改的情况。很可能Joe已经做了一些自己的新更改,他想要提交。
你有什么想法?

1
至少对于像master这样的分支,拒绝强制推送怎么样? - Frank Osterfeld
@FrankOsterfeld 不错的想法,我之前没有想到过。 - Marplesoft
2个回答

3

在重写之前,您需要保存原始提交(或者您可以在之后找到它,但是在之前更容易)

git branch save_point origin/master (before fetch)

然后进行获取,这不会改变任何内容。
git fetch

现在,Joe的存储库中所有尚未在主分支上的提交都需要被重写/基于来自原始版本的重写提交。
git rebase --onto origin/master save_point master

如果不同分支上的更改没有关联,这应该可以正常工作。
您可以在手册中了解此内容,但理解起来并不容易。

1
绘制提交图。以下是Ryan的原始版本:
A - B

A是“初始提交”,B是第一个“更多内容”。

Joe明白了这个并开始工作。假设他也添加了一个提交C,这样我们就有了更多的东西在Joe的图表中:

A - B - C

与此同时,Ryan进行了修改并强制推送。这将创建:
A - B
  \
    B'

这里的B'是“修改过”的提交。

如果现在Joe执行了fetch操作(或者使用pull,因为它会自动执行fetch),他会得到以下内容:

A - B - C       <-- HEAD=master
  \
    B'          <-- origin/master

基本上,他仍然拥有原始的B
要恢复,Joe需要将他的提交(在这种情况下只有C)变基到新的origin/master提交B'上。
如果Joe使用git pull --rebase,Git会做一些聪明的事情:它可以知道提交B曾经是origin/master的顶部提交,因此只需要变基提交C
如果Joe使用git fetch(或者git pull没有--rebase),Git会更新origin/master以指向B',并且更难以确定B'对应于B。(在这些简化的手绘图中很容易看出,但在实际的Git提交DAG之后很难看出。)但是,如果Joe能够尽管Ryan的残酷把戏而识别“他的”和“他们的”,他也可以进行相同的变基。
我发现一个简单的rebase -i通常就足够了,因为我通常可以区分“我的”提交和“他们”的提交,并删除相应的pick行。(编辑:请参见Andreas Wederbrand的答案,了解pull --rebase在内部的操作方式:它使用rebase --onto upstream start_after branch。它以任何pull的通常方式获取upstreambranch,并且它从pull --rebase步骤之前的origin/branch中知道start_after SHA-1。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接