19

我知道这对于很多场景来说都是个坏主意。我正在学习 Git 并进行实验。在此练习中不会损害任何代码。

我创建了一个这样的结构:

* [cf0149e] (HEAD, branch_2) more editing
* [8fcc106] some edit
|
|  * [59e643e] (branch_2b) branch 2b
| /
|/
|  * [0f4c880] (branch_2_a) branch 2a
| /
|/
*  [a74eb2a] checkout 1
*  [9a8dd6a] added branch_2 line
|
|
| * [bb903de] (branch_3) branch 3
|/
|
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

现在我想浏览这个图表并重命名各种提交,以便它们有意义。

例如:

    * | [a74eb2a] checkout 1
    * | [9a8dd6a] added branch_2 line

renamed to:

    * | [a74eb2a] branch 2 commit 2
    * | [9a8dd6a] branch 2 commit 1

注意:

[cf0149e] (HEAD, branch_2) more editing
[59e643e] (branch_2b) branch 2b
[0f4c880] (branch_2_a) branch 2a

都分支出:

[a74eb2a] checkout 1

我已经尝试过

git rebase -i 328454f

然后在我想要修改并随后运行的提交上将“pick”更改为“edit”

git commit --amend -m "the new message" 

随着变基过程的继续。

这种方法的问题在于,在最后一次之后,git rebase --continue我在碰巧所在的分支上得到了两个新的提交(我想重命名的两个重复)。例如,如果我在 HEAD 位于“branch_2”时运行 rebase,则图形可能如下所示:

* [cf0149e] (HEAD, branch_2) more editing
* [8fcc106] some edit
* [3ff23f0] branch 2 commit 2
* [2f287a1] branch 2 commit 1
|  
|    * [59e643e] (branch_2b) branch 2b
|   /
|  /
| |  * [0f4c880] (branch_2_a) branch 2a
| | /
| |/
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
|
| * [bb903de] (branch_3) branch 3
|/
|
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

换句话说,我现在有两组代表完全相同的代码状态的提交。

我想做的就是更改提交消息。

我还想将初始消息从“测试”重命名为“初始版本”。我似乎无法做到这一点,git commit --amend -m "Initial version"因为如果我签出该提交,我最终会处于无头模式。

我究竟做错了什么?当然不可能那么难。

编辑:
这是我刚刚尝试过的一种方法。当然,它改写了历史。因此,在非常特殊的情况之外,这是一个坏主意。以下是步骤:

签出要修改的分支。创建补丁文件:

git format-patch HEAD~x   // Where x is how far back from HEAD you need to patch

编辑补丁文件以更改提交消息。现在重置头部。

git reset --hard HEAD~x   // Same x as before

应用补丁:

git am 000*

将使用新的 SHA1 创建新的提交。如果任何分支现在需要使用更正的消息引用新提交,您必须使用git rebase它们来移动它们。

使用我自己的示例,在应用补丁程序后,我最终得到了这个:

* [7761415] (HEAD, branch_2) branch 2 commit 4
* [286e1b5] branch 2 commit 3
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [59e643e] (branch_2b) branch 2b
| | * [0f4c880] (branch_2_a) branch 2a
| |/
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

所以,我已经很好地标记了我的 branch_2 提交。现在我想移动 branch_2a 以便它分支出来[53d638c] branch 2 commit 2

结帐 branch_2a

git checkout branch_2a

变基

git rebase 53d638c

我现在有:

* [fb4d1c5] (HEAD, branch_2a) branch 2a
| * [7761415] (branch_2) branch 2 commit 4
| * [286e1b5] branch 2 commit 3
|/
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [59e643e] (branch_2b) branch 2b
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

与 branch_2b 相同的过程会导致:

* [ca9ff6c] (HEAD, branch_2b) branch 2b
| * [fb4d1c5] (branch_2a) branch 2a
|/
| * [7761415] (branch_2) branch 2 commit 4
| * [286e1b5] branch 2 commit 3
|/
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

这正是我想要的。不会太乱。同样,在非常特殊的情况下,您不想做的事情。就我而言,我只是在学习 Git,所以上述内容并没有真正影响真正的代码存储库。很高兴知道您可以在必要时执行此操作。

现在开始重命名第一个提交。

4

1 回答 1

22

您实际上永远无法更改提交。如果您在任何地方进行最小的单位更改,从电子邮件行中名称的拼写到提交时间戳的精确秒,您将获得一个新的、不同的提交和一个新的、不同的 SHA1(SHA1 是“真正的git 数据库中每个“对象”的名称”,提交是四种类型的对象之一)。

提交的不可变部分之一是它的“父提交”,它构建了从最近提交到最旧提交的链。

因此,要做git rebase -i的是创建一个的提交链,链中的每个提交都具有与原始提交相同的内容/效果,加上或减去您在交互期间所做的任何更改。完成后,它会从旧提交链的末尾删除标签(即便笺)并将其粘贴到新提交链的末尾。它首先复制要修改/重新定位的最旧提交。这个具有重新定位的父级(可以是与原始链中相同的父级提交,也可以是不同的父级:无论哪种方式都可以,因为它是一个提交)。然后它复制旧链中的下一个,但指向新链。它一直重复到旧链的末端。

这就是为什么你所有的其他分支现在都独立于你的 rebase 分支。他们必须是,因为他们使用旧的提交 ID。如果你想让他们从你的新的 rebase 分支分支出来,你必须进入每个分支并 rebase 它们。

有一个强大的 Swiss-Army-chainsaw-ish 命令,git filter-branch您可以使用它来执行一系列非常大的“重做大量提交,使所有新的提交(大部分)内容与原件相同”,有点像git rebase在类固醇上,您可以用于此目的。(运行它--all以影响所有分支。)当然,由于它实际上重做所有提交,因此您最终会得到一个与原始 repo 基本上无关的 repo。

重写初始提交很困难(并非不可能),因为它没有父级,所以常规rebase只是不会这样做。1filter-branch可以。)因为这些更改了 SHA1 ID,并且任何克隆了您的 repo 的人都在使用 / 依赖于这些,所以它通常是一个功能,您不能像这样摆弄提交。当您知道没有其他人依赖于某些特定的提交集时,您可以随心所欲rebase,但您不会回到最初的提交,因为那将在 repo 的共享部分中。一直到“初始提交”的整个事情都是您自己的私人内容,这是非常罕见的(尽管当然不是“从不”)。


1自从我写了这篇文章以来,git rebase已经学会了像初始提交一样复制根提交。2 使用传统git rebase语法,您必须命名根的父提交,当然没有父提交(这就是它在图中成为“根”的原因)。因此rebase用作--root涵盖这种情况的论据。

2可以有多个根;git checkout --orphan例如,使用git commit, 来创建一个新的根提交。拥有这些有点不寻常,尽管 git 源本身保存在具有多个根的 git 存储库中。

于 2012-05-25T05:30:15.173 回答