任何提交都不可能被修改。相反,你可以将一些现有的提交复制到一个新的提交集合中。这其实是好消息,因为这意味着原始的提交仍然可用。
当我们使用交互式变基创建B
时,A
的注释也将更新以显示# this is commit B
(假设除了这个文件之外还有其他几个更改)。
在你展示的具体例子中,不应该这样:你应该会得到合并冲突。
对于其他情况,当然,你是正确的。
但是,我们想保留注释不变。注意:A
由于具有不同的父项而具有不同的整体哈希,但其树哈希应保持与之前相同。
记住,你从以下内容开始:
... -- C -- A
我会将其描述为以下形式:
...--C--A <-- branchname (HEAD)
为了表示某个名为
branchname
的现有分支指向提交
C
,并且
HEAD
已经附加到
A
。您随后运行了
git rebase -i <hash-of-C>
或类似命令。这会给您列出一些要做的事情,您选择“编辑”
A
。Git 现在:
detaches HEAD to the target of the rebase:
A <-- branchname
/
...--C <-- HEAD
Copies commit A
(using an exact copy / fast-forward, in this case, so that it re-uses A
itself; you can disable this if you like with --no-ff
, although in the end it makes no difference):
A <-- HEAD, branchname
/
...--C
or:
A <
/
...
(using --no-ff
to force a copy).
此时您需要做一些更改,然后运行git add
和git commit --amend
将当前提交推到一边,并使HEAD
指向新的提交B
,其父节点是C
。假设您没有使用--no-ff
选项;则结果如下:
A <
/
...
(如果您使用了
--no-ff
,那么另外一个没有名称的
A'
将被保留约一个月后进行垃圾回收。然后我们必须将下一个副本命名为
A"
以区分它们,因此让我们假设您没有使用
--no-ff
。)
现在,您想要从提交
A
获取
文件和提交信息,并创建一个新的提交。由于
branchname
仍然指向原始提交
A
,所以只需执行以下操作:
$ git checkout branchname -- .
$ git commit -C branchname
现在您拥有的是:
A <
/
...
在这一点上,您可以使用git rebase --continue
完成rebase操作。由于没有需要复制的提交,您已经完成了rebase操作中最后一个提交A
的复制,因此这是rebase操作的最后一步,它将从原始提交链中剥离分支名称,并将其移动到指向与HEAD
相同的提交,同时重新附加HEAD
:
A <
/
...
作为副作用,rebase 会设置
ORIG_HEAD
来记住
branchname
原来的指向位置,这样就可以轻松地确保一切都正常工作并且你已经到达了期望的状态:
git diff ORIG_HEAD HEAD
如果出现错误,您可以使用git reset --hard ORIG_HEAD
命令进行重置,结果如下:
A <
/
...
请注意,其他命令(包括
git reset
)会设置
ORIG_HEAD
(这就是为什么它们在此处交换的原因)。最终,这两个提交中的一个将被完全放弃,除了reflog条目外,当这些条目过期时,不可访问的提交将真正消失。这些提交的默认到期时间是30天。
git checkout branchname -- .
足以处理单个提交,对于多个提交来说,看起来有些棘手。 - Mezuzzabranch^1
,来选择该提交的第 n 个父提交。当然,在线性链中只有一个父提交。数字默认为 1,因此branch^^
是父提交的父提交,即向后两步,而branch^^^
是向后三步,以此类推。为了缩写这个过程,你可以使用波浪号和数字:branch~3
=branch^^^
,branch~7
=branch^^^^^^^
,等等。但这仍然需要在脑海中计算和向后工作。 - torekHEAD~
,因为 zsh 会在不使用额外引号的情况下使用扩展通配符消耗^
。 - Mezuzza