Git:如何将数据库重置为特定的提交?

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

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

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

而不是

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

我怎样才能做到呢?

359640 次浏览

使用"onto"选择:

git rebase --onto master^ D^ D

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

最后3个参数的意思是:

  • destination (new-parent,这里是commitB)
  • Start-after (current-parent,第一个提交被移动的父节点),
  • 和end-inclusive(要移动的最后一个提交)。

你可以通过在commit上创建一个临时分支来避免使用——onto参数,然后以简单的形式使用rebase:

git branch temp master^
git checkout topic
git rebase temp
git branch -d temp

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

git checkout topic
git rebase <commitB>

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

  1. 找到要重基(移动)的分支的先前分支点-称其为旧父结点。在上面的例子中,它是一个
  2. 找到你想要移动的分支的上面的提交-称为新父。在本例中是B
  3. 你需要在你的分支上(你移动的那个分支):
  4. 应用rebase: git rebase --onto <new parent> <old parent>

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

   git checkout topic
git rebase --onto B A

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

$ 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".

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

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

git branch topic master~n

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

git branch topic master~1

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

由于改基是如此基本,这里是Nestor Milyaev的回答是的扩展。结合亚当·迪米特鲁克的回答是中的jsz的西门南的注释,生成了这个命令,它在topic分支上工作,而不管它是否从master分支的提交AC中分支:

git checkout topic
git rebase --onto <commit-B> <pre-rebase-A-or-post-rebase-C-or-base-branch-name>

注意,最后一个参数是必需的(否则它会倒带你的分支来提交B)。

例子:

# if topic branches from master commit A:
git checkout topic
git rebase --onto <commit-B> <commit-A>
# if topic branches from master commit C:
git checkout topic
git rebase --onto <commit-B> <commit-C>
# regardless of whether topic branches from master commit A or C:
git checkout topic
git rebase --onto <commit-B> master

最后一个命令是我通常使用的命令。

一个更简单的解决方案是git rebase <SHA1 of B> topic。不管你的HEAD在哪里,这都是有效的。

我们可以从Git rebase doc中确认此行为

<upstream>比较的上游分支。可能是任意有效值 提交,而不仅仅是一个现有的分支名称。默认为配置的

人力资源/ > < p > < 你可能会想,如果我在上面的命令中也提到topic的SHA1,会发生什么

git rebase <SHA1 of B> <SHA1 of topic>

这也可以工作,但rebase将不会使Topic指向这样创建的新分支,并且HEAD将处于分离状态。所以从这里开始,你必须手动删除旧的Topic,并在rebase创建的新分支上创建一个新的分支引用。

主题的解决方案

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

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>]]

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

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

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

要移动整个特征分支,你不能选择XYZ,或feature作为<Target>,因为这些都是在被移动的组内提交的。

<Upstream>是特殊的,因为它可以意味着两个不同的东西。如果它是检出分支的祖先提交,则它作为切点。在我提供的例子中,这将是任何不是CDmaster的东西。在<Upstream>之后直到签出分支的头之前的所有提交都将被移动。

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

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

有趣的例子和结果

以此为出发点:

A --- B --- C --- D --- E         master
\
\-- X --- Y --- Z    feature
    <李> < p > git rebase --onto D A feature < br > 将apply提交BCXYZ来提交D,并最终跳过BC,因为它们已经被应用。

    <李> < p > git rebase --onto C X feature < br > 将apply提交YZ来提交C,有效地删除提交X

使用--onto添加到答案:

我从来没有记住它,所以我写了这个小帮助脚本:
Git:将一个分支(子)从一个基基重设为另一个基基,留下另一个基基的提交。 < / p >

用法:

moveBranch <branch> from <previous-base> to <new-base>

简而言之:

git rebase --onto "$3" "$2" "$1"

除此之外,还有一个用于类似目的的解决方案,即cherry-picking一系列提交:

git co <new-base>
git cherry-pick <previous-base>..<branch>
git branch -f branch

两者的效果差不多。注意,此语法跳过<previous-branch>本身的提交,因此它会优选下一个和后续的提交,包括在<branch>的提交。

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)中更正。

参见提交bdff97a提交77年ab58c (18 Mar 2022) by John Cai (john-cai)
(由Junio C Hamano—gitster提交f818536中合并,29 Mar 2022)

rebase:在checkout_up_to_date()中设置REF_HEAD_DETACH

< p > 报道:Michael McClimon
签字人:John Cai

git rebase A B"(man),其中B不是一个提交,应该表现为如果HEAD在B被分离,然后分离的HEAD在A的顶部被重基于。

然而,当BA的后代时,一个错误会覆盖当前分支以指向B(即变基结束为快进)。

.错误报告的原始版本见这个线程

checkout_up_to_date()的调用栈如下:

< p > cmd_rebase()checkout_up_to_date() →reset_head () →update_refs () →update_ref () < / p > 当B不是一个有效分支而是一个oid时,rebase将rebase_optionshead_name设置为NULL
这个值通过reset_head_opts的分支成员在调用链中传递,也被设置为NULL,一直设置为update_refs()

然后update_refs()检查ropts。分支决定是否切换分支。
如果ropts.branchNULL,它调用update_ref()来更新HEAD。
然而,在这一点上,从rebase的角度来看,我们想要一个分离的HEAD。
但是,由于checkout_up_to_date()没有设置RESET_HEAD_DETACH标志,update_ref()调用将遵从HEAD并更新它所指向的分支。
我们希望HEAD在B处分离

通过在checkout_up_to_date中添加RESET_HEAD_DETACH标志(如果B不是有效的分支)来修复此错误,这样一旦reset_head()调用update_refs(),它就会使用REF_NO_DEREF调用update_ref(),后者直接更新HEAD,而不是延迟它并更新HEAD指向的分支。