我想要重基到一个特定的提交,而不是另一个分支的HEAD:

A --- B --- C          master
 \
  \-- D                topic

to

A --- B --- C          master
       \
        \-- D          topic

而不是

A --- B --- C          master
             \
              \-- D    topic

我怎样才能做到呢?


当前回答

git rebase --onto <commitB> <commitA> <commitD>

实际上,使用commit (<commitD>)作为最后一个参数,而不是分支名称(topic),应该创建一个分离的分支:

    A --- B --- C          master
     \     \
      \     -- D'          <detached HEAD>
       \
        \-- D             topic

除了……在Git 2.36之前,在某些情况下不会:

"git rebase $base $non_branch_commit"(man),当$base是一个祖先或$non_branch_commit时,修改了当前的分支,这已在git 2.36 (Q2 2022)中更正。

参见John Cai (John - Cai)的commit bdff97a, commit 77ab58c (18 Mar 2022)。 (由Junio C Hamano - gitster -在提交f818536, 2022年3月29日合并)

rebase:在checkout_up_to_date()中设置REF_HEAD_DETACH 报道:Michael McClimon 签字人:John Cai

"git rebase A B"(man) where B is not a commit should behave as if the HEAD got detached at B and then the detached HEAD got rebased on top of A. A bug however overwrites the current branch to point at B, when B is a descendant of A (i.e. the rebase ends up being a fast-forward). See this thread for the original bug report. The callstack from checkout_up_to_date() is the following: cmd_rebase() -> checkout_up_to_date() -> reset_head() -> update_refs() -> update_ref() When B is not a valid branch but an oid, rebase sets the head_name of rebase_options to NULL. This value gets passed down this call chain through the branch member of reset_head_opts also getting set to NULL all the way to update_refs(). Then update_refs() checks ropts.branch to decide whether or not to switch branches. If ropts.branch is NULL, it calls update_ref() to update HEAD. At this point however, from rebase's point of view, we want a detached HEAD. But, since checkout_up_to_date() does not set the RESET_HEAD_DETACH flag, the update_ref() call will deference HEAD and update the branch its pointing to. We want the HEAD detached at B instead. Fix this bug by adding the RESET_HEAD_DETACH flag in checkout_up_to_date if B is not a valid branch, so that once reset_head() calls update_refs(), it calls update_ref() with REF_NO_DEREF which updates HEAD directly intead of deferencing it and updating the branch that HEAD points to.

其他回答

上面jsz的注释为我节省了大量的痛苦,所以这里有一个基于它的逐步接收人,我一直在使用它来将任何提交重置/移动到任何其他提交之上:

找到要重基(移动)的分支的先前分支点-称其为旧父结点。在上面的例子中是A 找到你想要移动的分支的上面的提交-称为新父。在这个例子中是B 你需要在你的分支上(你移动的那个分支): 应用你的rebase: git rebase—到<new parent> <old parent>

在上面的例子中,这很简单:

   git checkout topic
   git rebase --onto B A

你甚至可以采取直接的方法:

git checkout topic
git rebase <commitB>

主题的解决方案

回答已发布问题的正确命令可能是以下任何一个(假设分支主题已经签出):

git rebase --onto B master
git rebase --onto master~1 master
git rebase --onto B A
git rebase --onto B C
git rebase --onto B

如果topic没有签出,你只需将topic附加到命令(除了最后一个),如下所示:

git rebase --onto B master topic

或者,先用以下方法检出分支:

git checkout topic

将提交的任何字符串改为目标提交

我们所需要的命令的基本形式,摘自文档,是:

git rebase --onto <Target> [<Upstream> [<Branch>]]

<分支>是可选的,它所做的只是在执行其余命令之前检出指定的分支。如果您已经签出了想要重基的分支,那么您不需要这个。注意,你必须指定<Upstream>才能指定<Branch>,否则git会认为你指定了<Upstream>。

<Target>是我们要附加提交字符串的提交。在提供分支名称时,您只是指定了该分支的头提交。<目标>可以是任何不包含在被移动的提交字符串中的提交。例如:

A --- B --- C --- D         master
      \
       \-- X --- Y --- Z    feature

要移动整个特性分支,您不能选择X、Y、Z或<Target>的特性,因为这些都是在被移动的组内提交的。

<Upstream>是特殊的,因为它可以表示两种不同的东西。如果它是检出分支的祖先提交,则它作为切点。在我提供的示例中,这将是任何不是C、D或master的东西。所有在<Upstream>之后直到检出分支头部的提交都将被移动。

但是,如果<Upstream>不是一个祖先,那么git将从指定的提交开始备份链,直到if与签出的分支找到一个共同的祖先(如果找不到,则中止)。在我们的例子中,B、C、D或master的<Upstream>都将导致提交B作为切点。<Upstream>本身是一个可选命令,如果没有指定它,那么git会查看签出分支的父类,这相当于输入master。

现在git已经选择了它将切割和移动的提交,它将应用它们以<Target>,跳过任何已经应用到Target的提交。

有趣的例子和结果

以此为出发点:

A --- B --- C --- D --- E         master
            \
             \-- X --- Y --- Z    feature

git rebase - to D A特性 将应用提交B, C, X, Y, Z来提交D,并最终跳过B和C,因为它们已经被应用了。 git rebase - to C X特性 是否应用提交Y和Z来提交C,有效地删除提交X

如果您希望返回到不止一次提交,还有另一种方法。

下面是一个返回到n次提交的例子:

git branch topic master~n

为了解决这个问题,还可以这样做:

git branch topic master~1

该命令在git 2.7.4版本上运行良好。还没在其他版本上测试过。

我使用了上述解决方案的混合:

$ git branch temp <specific sha1>
$ git rebase --onto temp master topic
$ git branch -d temp

我发现它更容易阅读和理解。接受的解决方案导致我的合并冲突(懒得手动修复):

$ git rebase temp
First, rewinding head to replay your work on top of it...
Applying: <git comment>
Using index info to reconstruct a base tree...
M       pom.xml
.git/rebase-apply/patch:10: trailing whitespace.
    <some code>
.git/rebase-apply/patch:17: trailing whitespace.
        <some other code>
warning: 2 lines add whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
error: Failed to merge in the changes.
Patch failed at 0001 <git comment>
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".