在有人将基准或重置推送到已发布的分支后,如何恢复/重新同步?


88

我们都听说过,永远不要为已发布的工作重新定级,这很危险,等等。但是,如果发布了重新定级的情况,我还没有看到任何有关如何处理这种情况的方法

现在,请注意,只有在存储库仅由已知的(最好是少数)人员克隆的情况下,这才是真正可行的,因此,推动重新设置或重置的人员可以通知其他所有人,下次他们需要注意取(!)。

如果您没有本地提交foo并且可以重新建立基础,那么我看到的一个显而易见的解决方案将起作用:

git fetch
git checkout foo
git reset --hard origin/foo

foo根据远程存储库,这将简单地丢弃本地状态以支持其历史记录。

但是,如果人们对该分支机构进行了实质性的本地变更,该如何处理呢?


+1用于简单案例配方。非常适合在机器之间进行个人同步,尤其是当它们具有不同的操作系统时。这是手册中应提及的内容。
菲利普·奥克利

个人同步的理想秘诀是git pull --rebase && git push。如果您master只从事工作,那么即使您已经改头换面并推到另一端,这也几乎可以毫无问题地为您做正确的事情。
亚里斯多德·帕加尔兹

因为我正在PC和Linux机器之间进行同步和开发,所以我发现对每个rebase / update使用一个新分支效果很好。我git reset --hard @{upstream}现在也使用变体,因为我知道魔术refspec的咒语是“忘记我拥有/拥有的东西,使用从远程获取的东西”。请参阅我对stackoverflow.com/a/15284176/717355的
Philip Oakley

使用Git2.0,您将能够找到分支的旧来源(在用a重写上游分支之前push -f):请参见下面的答案
VonC 2013年

Answers:


75

在大多数情况下,重推基础之后恢复同步实际上并不那么复杂。

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

就是 首先,为远程分支最初所在的位置设置一个书签,然后使用该书签将您的本地提交从该点开始重播到重新建立基础的远程分支上。

变基础就像暴力:如果不能解决您的问题,则只需要更多。☺

如果您查找origin/foo并使用了rebase之前的提交ID,那么当然可以在没有书签的情况下执行此操作。

这也是您处理提取之前忘记制作书签的情况。没有任何损失–您只需要检查远程分支的引用日志:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

这将打印在origin/foo最近一次更改其历史记录的获取之前指向的提交ID 。

然后,您可以简单地

git rebase --onto origin/foo $commit foo

11
快速说明:我认为这非常直观,但是如果您不太了解awk,那么单行代码只是git reflog show origin/foo在第一行的输出中查找“ fetch:forced-update”;这是git在抓取导致远程分支执行除快速转发之外的任何操作时记录的内容。(您也可以手动完成-强制更新可能是最新的操作。)
Cascabel 2010年

2
这不像暴力。暴力有时会很有趣
Iolo

5
@iolo诚然,变基总是很有趣。
丹·贝查德

1
像暴力一样,几乎总是避免下垒。但是有一个线索。
鲍勃·斯坦因

2
好吧,避免在其他人受到影响的地方进行重新部署。
亚里斯多德·帕加尔兹

11

我想说的是,从git-rebase手册页的上游rebase部分进行恢复几乎涵盖了所有这一切。

确实与从自己的基准库中恢复没有什么不同-您移动一个分支,并将其历史记录中的所有分支基准都重新定位到新位置。


4
嗯,是的。但是,尽管我现在明白了它的意思,但是在我自己解决这个问题之前,我是从未有过的。而且没有食谱的食谱(在此类文档中可能是正确的)。我还将指出,将“困难案例”称为“困难论据”是我认为,在大多数内部开发规模上,重写的历史记录是微不足道的。总是以迷信的方式对待这个问题,这让我很烦。
亚里斯多德·帕加尔齐斯

4
@Aristotle:没错,因为所有开发人员都知道如何使用git,并且您可以有效地与所有开发人员进行交流,因此它非常易于管理。在一个完美的世界中,这就是故事的结局。但是,外面的许多项目都足够大,以至于上游的重组确实是一件令人恐惧的事情。(然后在我的工作场所这样的地方,大多数开发人员甚至都没有听说过要改头换面。)我认为“迷信”只是提供最安全,最通用建议的一种方式。没有人愿意成为在别人的仓库中造成灾难的人。
卡斯卡贝尔

2
是的,我了解动机。我完全同意。但是,“如果您不了解后果,请不要尝试此操作”与“因为它是邪恶的,您绝对不应该这样做”之间存在天壤之别,仅此一点我就会提出质疑。指导总是比灌输恐惧更好。
亚里斯多德·帕加尔齐斯

@亚里斯多德:同意。我确实尝试趋向于“确保您知道自己在做什么”的结尾,但是特别是在网络上,我尝试给予足够的重视,以使来自Google的临时访客会注意。没错,很多内容可能应该减少。
卡斯卡贝尔

11

从git 1.9 / 2.0 Q1 2014开始,您将不必标记先前的分支原点,就可以将其基于重新编写的上游分支,如Aristotle Pagaltzis答案所述
请参阅commit 07d406bcommit d96855f

在使用topic创建的分支之后git checkout -b topic origin/master,远程跟踪分支的历史origin/master可能已经被重新记录和重建,从而产生了这种形状的历史:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

其中,origin/master在提交用于点B3B2B1现在它指向B,和你的topic分支开始在上面的回来时origin/masterB3

此模式使用reflog的origin/masterfindB3作为派生点,因此topic可以根据更新origin/master的基础重新构建

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

这就是该git merge-base命令具有新选项的原因:

--fork-point::

找到一个分支(或导致其的任何历史记录<commit>)与另一个分支(或任何参考)之间的分叉点<ref>
这不仅要查找两次提交的共同祖先,而且还要考虑reflog,<ref>以查看历史是否导致了<commit>分支的早期化身<ref>


git pull --rebase”命令使用分支工作的“ base”分支(通常是远程跟踪分支)的引用日志条目来计算要重新建立分支的分支点,以应对“基础”情况分支机构已倒回并重建。

例如,如果历史记录如下所示:

  • base”分支的当前尖端位于B,但是较早的提取观察到它的尖端曾经是B3,然后B2B1 到达当前提交之前,然后
  • 根据最新的“基础”重新建立分支的分支基于commit B3

它试图找到B3通过的“输出去git rev-list --reflog base”(即BB1B2B3),直到找到一个提交,它是当前尖端的祖先“ Derived (topic)”。

在内部,我们get_merge_bases_many()可以通过一次计算。
我们希望合并之间的合并基础Derived和虚构的合并提交,这将通过合并所有“ base (origin/master)”的历史提示而产生。
当存在这样的提交时,我们应该得到一个单一的结果,该结果与“ base”的引用条目之一完全匹配。


Git的2.1(Q3 2014)将增加使这个功能更强大的这样:看到提交1e0dacd约翰·行动(johnkeeping

正确处理具有以下拓扑的方案:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

哪里:

  • B'是该版本的固定版本,B与并不完全相同B
  • C*且与和D*补丁相同,C并且D如果以错误的顺序应用,则在文本上冲突;
  • E从文字上取决于D

的正确结果git rebase master devB被识别为的叉点devmaster,从而CDE是需要被重放到提交master; 但CD是补丁与相同C*D*等都可以被丢弃,从而使最终结果是:

o --- B' --- C* --- D* --- E  <- dev

如果未识别出分叉点,则选择B包含分支的B'结果将导致冲突;如果未正确识别补丁相同的提交,则选择C包含D(或等效D*结果)的分支将导致冲突。


当在2.20年代用C重写命令时--fork-point,“ git rebase”的“ ”模式返回,已在Git 2.27(2020年第二季度)中进行了更正。

参见Junio C Hamano()提交的f08132f(2019年12月9日(通过合并JUNIOÇ滨野- -提交fb4175b 3月27日2020)gitster
gitster

rebase--fork-point回归修复

签名人:Alex Torok
[jc:修改了补丁,并使用了Alex的测试]
签名人:Junio C Hamano

git rebase --fork-point master”过去工作正常,因为它内部称为“ git merge-base --fork-point”,它知道如何处理短refname并在调用基础get_fork_point()函数之前将其dwim更改为完整的refname 。

在用C重写命令之后,这种情况不再成立,因为直接对其进行的内部调用get_fork_point()不会删除短引用。

将“ git merge-base”中使用的“ dwim refname参数移至完整的refname”逻辑移至基础get_fork_point()函数,以便实现“ git rebase”的函数的另一个调用者以相同的方式进行修复这种回归。


1
请注意,现在可以更谨慎地执行git push --force(git 1.8.5):stackoverflow.com/a/18505634/6309
VonC 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.