一个强烈推荐的做法是不要改变其他人会推到别处的提交的历史记录。尽管如此,不理解这一点的人无论如何也不可避免地会这样做。从“其他用户”的角度来看,不是自己改变历史的人,从中恢复的最佳方式是什么?
这是一个让它具体化的例子。
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 repo
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 $ git pull origin
[Conflict!]
现在这是一个非常简单的例子,但Joe很可能不熟悉file.txt
,并且很难合并更改。
同样,我知道问题的根源在于瑞恩做了些坏事。但是,我正在寻找乔和他的队友可以从中恢复而不必进行合并提交的方法。
我知道一个技巧是删除远程分支的本地副本,然后从源重新检查它。但是,当然只有在没有变化的情况下才有效。乔可能会对自己做出一些他想要改变的新变化。
思想?
答案 0 :(得分:2)
您需要在重写之前保存原始提交(或者之后可以找到它但之前更容易)
git branch save_point origin/master (before fetch)
然后执行获取,这不会改变任何内容
git fetch
现在Joe的存储库中尚未存在的所有提交都需要重写/重新定位到来自原始的重写提交
git rebase --onto origin/master save_point master
如果不同分支上的更改无关,则应该可以正常工作。
您可以在manual中阅读此内容,但要理解这一点并非易事。
答案 1 :(得分:1)
绘制提交图。这是Ryan的原创:
A - B
A
是“初始提交”,B
是第一个“更多内容”。
乔得到了这个并开始工作。假设他也添加了一个提交C
,所以我们在Joe的图表中有更多内容:
A - B - C
与此同时,Ryan继续进行修正和强制推进。这会创建:
A - B
\
B'
其中B'
是“修正”提交。
如果Joe现在进行抓取(或拉取,当然这是取回),这就是他得到的:
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's answer了解内部pull --rebase
的内容:它使用rebase --onto upstream start_after branch
。它获取 upstream
和 {{ 1}} 以通常的方式进行任何拉动,并且它知道branch
之前start_after
中{em> origin/branch
SHA-1步骤。)