发生了什么
"复制提交"就是git rebase
所做的事情。它会复制一些提交,然后重新排列分支指针以"遗忘"或"放弃"原始提交。(但请参见下面的内容。)
这是一个说明git rebase
如何进行复制的示例。单个字母表示提交,右侧的名称是分支名称,实际上只指向一个提交,即"分支尖端"。每个提交都指回其父提交,即A--B
连接器线实际上应该是左指向箭头(而那些斜着的箭头也指向左边,指向更早的提交,在更晚的提交方向朝右):
C
/
A
\
E <
这是“之前”的图片,其中你仅有“原始”的提交记录。现在,你决定使用
git checkout branch1
和
git rebase branch2
,以便
C
和
D
在
E
之后。但是Git实际上无法改变原始的
C--D
,因此它会将它们复制到新的副本
C'
和
D'
中,新的副本略有不同:它们在
E
之后(并且还使用您对
E
所做的任何代码更改):
C
/
A
\
E <
\
C'-D' <
完全忘记原始的
C--D
在这里是可以的,但如果您最终认为这是个坏主意怎么办?重新设置基础会在“reflogs”中保留该分支的原始值以进行记忆。它还使用特殊名称
ORIG_HEAD
。这样做更容易,但只有一个
ORIG_HEAD
,而可能有无限多个reflog条目。默认情况下,reflog条目将被保留至少30天,让您有时间改变主意。回到第二张图,想象一下添加了
ORIG_HEAD
后会发生什么。
现在,您遇到的问题是因为不仅是分支名称记住先前的提交。每个提交还通过那些连接向左指向的箭头来记住自己的先前提交。因此,让我们看看如果有另一个名称或某个(合并)提交记住
C
和
D
会发生什么。例如,如果我们有这个更为复杂的起始图:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
如果我们现在“变基”
branch1
,我们会得到这样的结果:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
F
是一个合并提交:它指向提交
A
和提交
D
。因此它保留了原始的
D
,这样就保留了原始的
C
,使得我们有点乱。
如果
F
是一个普通的提交,只指向
D
,我们将看到同样的问题。不过普通的提交更容易复制,所以如果
F
不是合并(如果我们的
F
只指向
D
而不是
A
),我们也可以仔细地重新定义
branch3
,将
F
复制到
F'
,其中
F'
在我们新的
D'
之后。重新执行合并也是可能的,但是这有点棘手(无论如何正确地复制
F
都不容易——很容易出错并再次复制
C--D
)。
当发生这种情况时,您会遇到这个问题,无论是您还是其他人都复制了您或其他人制作的提交,并且您和“其他人”(也许是“另一个您”)也仍在使用原始提交。例如,我们的提交
F
就发生了这种情况:我们仍在使用原始的
C--D
链。我们可以通过创建一个新的
F'
并使用它来解决这个问题,只要我们是唯一使用
branch3
的人。但是,如果
branch3
已发布,或者我们已经发布了
branch1
,以便其他人可能将它们作为
origin/branch1
或
origin/branch3
,那么我们就失去了对
C--D
原始副本的控制。
因此,标准建议是仅重新定义私有(未发布)提交,因为您知道谁在使用它们——当然只有您——您可以与自己核对并确保您没有在使用它们,或者可以复制它们,因为您还计划复制或重新执行像
F
这样的提交。
如果您已经重新定义了(复制了)提交,并且将它们发布(推送到
origin
),那么您会陷入困境。无论如何,您都可以“撤消”您的重新定义,并请求与您共享
origin
使用权的每个人确保他们不会将您的
C'-D'
类型副本用于任何事情,因为您正在放回原始副本。
对于更高级的用户组,您甚至可以一致同意定期重新整合某些分支,并且您和他们必须都认识到这一点,然后您和他们都将注意切换到新的提交副本。但是,这可能不是您现在想要做的事情!
撤销它
因此,如果您(a)能够并且(b)希望“撤消”您的重新整合,那么现在reflog或保存的ORIG_HEAD真的非常有用。让我们再次看一下第二个示例,并查看在我们忘记branch3仍然记得原始C-D提交之后我们拥有什么:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
现在,想象一下我们从底部行中删除名称为branch1
,并写入新的<-- branch1
指向提交D
:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
\
C'-D' [abandoned]
既然我们已经放弃了C'-D'
,就不要再看它了。将此图与原始图进行比较,哇!这就是你想要的结果!
像这样任意移动分支标签的命令是git reset
(它移动当前分支,所以您必须在branch1
上)。在reflog中查找D
的原始提交哈希值,或检查ORIG_HEAD
是否正确,或使用reflog拼写识别提交D
。(对于新手,我发现复制和粘贴原始哈希是最好的方法)例如,尝试:
$ git log --graph --decorate --oneline ORIG_HEAD
检查使用ORIG_HEAD
获取的哈希值是否正确。如果不正确,请尝试使用git reflog branch1
(这里是查看特定的branch1
引用日志)来查找哈希值,然后使用以下命令:
$ git log --graph --decorate --oneline branch1@{1}
(或者剪切并粘贴原始哈希而不是使用branch1@{1}
)。一旦找到所需的“原始”提交,您可以执行以下操作:
$ git status
$ git reset --hard ORIG_HEAD
可以把branch1@{1}
或者原始哈希ID放在ORIG_HEAD
的位置。这将使当前分支(我们刚刚检查过的)指向给定的提交(从reflog中获取的branch1@{1}
或ORIG_HEAD
或原始哈希ID),从而获得最终的图形绘制结果。使用--hard
选项将索引/暂存区和工作树都设置为与我们刚刚重新指向的新提交匹配。
Git中经常重复出现的一个基本思想是,我们必须命名一些特定的提交,从中Git可以找到必要的其他提交。任何名称都可以使用:分支名称、像HEAD
这样的名称、类似于master@{1}
的reflog名称或原始提交哈希ID。Git并不关心你如何告诉它“看这个提交”,最终Git会将该名称解析为其中一个大而丑陋的SHA-1哈希ID,并使用它。
develop
还是feature
分支上进行此操作,或者完全使用另一个分支? - shrewdbeansreflog
是每个仓库独立的,因此如果您想要还原特定的分支,请切换到您希望修复的分支。 - CodeWizard