问题 如何在Git中重命名提交消息?


我知道这对很多场景来说都是个坏主意。我正在学习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

然后将“选择”更改为“编辑”我想要修改并随后运行的提交

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

随着rebase过程的继续。

这种方法的问题在于,在最后一次之后 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

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

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

我还想将初始消息从“test”重命名为“Initial version”。我似乎无法做到这一点 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,所以上面并没有真正影响真正的代码库。很高兴知道你可以做到这一点,如果你不得不这样做。

现在重命名第一个提交。


7739
2018-05-25 05:10


起源

你不能。 Git应该不是面向服务器的。如果你把它推到别处,那就是它。 - Pedro Nascimento
你是如何得到你的分支和所有的酷图表?这是一个git命令还是你自己用ASCII画出来的? - Jerry Saravia
我使用这个补丁方法来修复我在开发分支中提交但未被推送到服务器的事故,其中提交名称不正确。我需要重命名提交并将其应用到另一个分支。工作得很好! - Ed Bayiates


答案:


您实际上无法更改提交。如果您在任何地方进行最小的单位更改,从电子邮件行中的名称拼写到提交时间戳的确切秒,您将获得一个新的,不同的提交,使用新的不同SHA1(SHA1是“true” git数据库中每个“对象”的名称“,就像它们一样,提交是四种类型的对象之一”。

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

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

这就是为什么你所有的其他分支现在都独立于你的重新分支。他们  因为他们使用旧的提交ID。如果您希望它们分支您的新重新分支,您必须进入每个分支并重新定义它们。

有一个强大的瑞士军队 - 电车指挥, git filter-branch,你可以用来做一大堆“重做多次提交,使所有新提交(大部分)与原始内容相同”,有点像 git rebase 就类固醇而言,您可以将其用于此目的。 (运行它 --all 影响所有分支。)当然,因为它实际上重做了所有提交,你最终会得到一个基本上与原始回购无关的回购。

重写初始提交(并非不可能)很难,因为它没有父级,所以经常 rebase 只是不会这样做。1  (filter-branch 因为这些更改了SHA1 ID,并且克隆了你的repo的任何人正在使用/依赖那些,它通常是一个你不能摆弄这样的提交的功能。当你知道没有其他人依赖于某些特定的提交时,你就可以 rebase 所有你喜欢的,但你不会回到初始提交,因为这将在回购的共享部分。这是非常罕见的(虽然当然不是“从不”),整个事情一直回到“初始提交”是你自己的私人东西。


1自从我写了这个, git rebase 已经学会了像初始提交那样复制root提交。2  使用常规 git rebase 语法,您必须命名根的父提交,当然还有  没有父母(这就是图中的“根”)。所以 rebase 使用 --root 作为一个论据来掩盖这个案子。

2它可能有多个根源;使用 git checkout --orphan例如,接着是 git commit,创建新的根提交。虽然git源本身保存在具有多个根的git仓库中,但有这些有点不寻常。


16
2018-05-25 05:30



好的,这是有道理的。我花了一整天的时间进行实验并失败了。好吧,实际上,我没有,我学到了很多东西。我想在提交消息方面,一个关键的东西就是“明智地选择”。虽然我理解为什么会发生这种情况,但我也不禁认为提供一种进行此类编辑的方法非常有用 - 提供四重警告。如果你在提交信息中犯了一个错误,那么你就会受到冲击。我的实验是一个极端的例子,我只是把信息传递出来,然后决定“嘿,让它们现在让它们变得有意义”,当然,这很难。 - martin's
是的!这就是为什么我使用git的常规进程是在私有分支(或许多私有分支)上工作并确保它们保持私有,然后当事情准备就绪时,重新检查并在必要时重写所有要提交的提交。出版。最后一次“反思 - 一切”,就像它一样。 - torek
对于大多数重新检查所有提交的项目来说@torek似乎有点过分了......相反,只是在准备好之前不要提交,如果你犯了错误,只需进行新的提交。对于大多数用例来说似乎更简单 - Paul Paczuski
显然,你可以在这里: help.github.com/articles/changing-a-commit-message 只要你没有将提交推送到遥控器。 - Glen
@Glen: git commit --amend 手段: 进行新的提交,并更改分支名称以使用新提交而不是旧提示提交。 从根本上说,这与复制提交是一样的:它仅限于提示,而 git rebase -i 不是。 - torek