关于git:在有人推送rebase或重置到已发布的分支后,如何恢复/重新同步?

How do I recover/resynchronise after someone pushes a rebase or a reset to a published branch?

我们都听说过,一个人不应该把已发表的作品重新编好,这很危险,等等。但是,我没有看到任何关于如何处理这种情况的食谱,以防重新编好的作品被出版。

现在,请注意,只有当存储库仅由已知(最好是较小的)人员组克隆时,这才真正可行,这样,无论谁推动了REBASE或RESET,都可以通知其他人,他们下次获取时都需要注意!!).

如果您在foo上没有本地承诺,并且得到了重新平衡,我看到的一个明显的解决方案将起作用:

1
2
3
git fetch
git checkout foo
git reset --hard origin/foo

根据远程存储库,这只会简单地放弃本地的foo状态,而支持其历史。

但是,如果在该分支机构上进行了实质性的本地更改,如何处理这种情况呢?


在大多数情况下,在推压钢筋后恢复同步并没有那么复杂。

1
2
3
4
5
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

例如,首先为远程分支原来所在的位置设置一个书签,然后使用该书签将本地提交从该点开始重放到重新平衡的远程分支上。

重新平衡就像暴力:如果它不能解决你的问题,你只需要更多。?

当然,如果您查找pre-rebase origin/foocommit id并使用它,您可以不使用书签来完成此操作。

这也是处理提取前忘记制作书签的情况的方法。不会丢失任何内容–您只需检查远程分支的reflog:

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

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

你可以简单地

1
git rebase --onto origin/foo $commit foo


我想说,Git Rebase手册页的"从上游恢复钢筋"部分几乎涵盖了所有这些内容。

这和从自己的重新平衡中恢复没有什么不同——你移动一个分支,然后将历史上所有拥有它的分支重新平衡到它的新位置。


从2014年第1季度的git 1.9/2.0开始,您不必在重写的上游分支上重新标记以前的分支来源,如亚里士多德·帕戈尔茨兹的回答所述:请参见提交07D406B和提交D96855F:

After working on the topic branch created with git checkout -b topic origin/master, the history of remote-tracking branch origin/master may have been rewound and rebuilt, leading to a history of this shape:

1
2
3
4
5
6
7
                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

where origin/master used to point at commits B3, B2, B1 and now it points at B, and your topic branch was started on top of it back when origin/master was at B3.

This mode uses the reflog of origin/master to find B3 as the fork point, so that the topic can be rebased on top of the updated origin/master by:

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

这就是为什么git merge-base命令有一个新的选项:

1
--fork-point::

Find the point at which a branch (or any history that leads to ) forked from another branch (or any reference) .
This does not just look for the common ancestor of the two commits, but also takes into account the reflog of to see if the history leading to forked from an earlier incarnation of the branch .

The"git pull --rebase" command computes the fork point of the branch being rebased using the reflog entries of the"base" branch (typically a remote-tracking branch) the branch's work was based on, in order to cope with the case in which the"base" branch has been rewound and rebuilt.

例如,如果历史看起来像:

  • the current tip of the"base" branch is at B, but earlier fetch observed that its tip used to be B3 and then B2 and then B1
    before getting to the current commit, and
  • the branch being rebased on top of the latest"base" is based on commit B3,

it tries to find B3 by going through the output of"git rev-list --reflog base" (i.e. B, B1, B2, B3) until it finds a commit that is an ancestor of the current tip"Derived (topic)".

Internally, we have get_merge_bases_many() that can compute this with one-go.
We would want a merge-base between Derived and a fictitious merge commit that would result by merging all the historical tips of"base (origin/master)".
When such a commit exist, we should get a single result, which exactly match one of the reflog entries of"base".

Git 2.1(2014年第3季度)将使此功能更加强大:请参阅Commit 1e0dacd by John Keeping(johnkeeping)

正确处理具有以下拓扑的场景:

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

哪里:

  • B'B的固定版本,与B不完全相同;
  • C*D*是分别与CD相同的补丁,存在冲突。如果按错误的顺序应用,则为文本;
  • E在文本上取决于D

git rebase master dev的正确结果是,B被确定为devmaster的分叉点,因此CDE是需要在master上重新移植的承诺;但是CD是补丁识别的。用C*D*等可以去掉,最终结果是:

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

如果分叉点未被识别,则在包含B'的分支上拾取B会导致冲突;如果未正确识别相同的补丁提交,则在包含D的分支上拾取C会导致冲突。