如何在Git中重命名提交信息?

21
我知道在很多情况下这是一个不好的想法。我正在学习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" 

随着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”重命名为类似于“初始版本”的东西。如果我使用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而尝试,因此上述内容并没有影响真实的代码仓库。如果必要的话,这样做还是很有用的。

现在我们来重命名第一个提交。


你不能这样做。Git 不应该是面向服务器的。如果你把它推到其他地方,那就是它了。 - Pedro Nascimento
3
你是如何得到那些分支结构的酷炫图示的?这是通过git命令生成的还是你使用ASCII手动绘制的? - Jerry Saravia
我使用了这个补丁方法来修复一个意外情况,即我在开发分支中提交了错误的提交名称,但没有推送到服务器。我需要重命名提交并将其应用于另一个分支。效果很好! - Ed Bayiates
2个回答

24
实际上你无法改变一个提交。如果你在任何地方做最微小的单个位元更改,从邮件行中名称的拼写到提交时间戳的精确秒数,你会得到一个新的、不同的提交和一个新的、不同的SHA1(SHA1是git数据库中每个“对象”的“真实名称”,其中提交是四种对象类型之一)。
提交的一个不可变部分是其“父提交”,这些提交向后构建链条,从最近的提交到最旧的提交。
因此,git rebase -i所做的就是创建一个新的提交链,其中每个提交都具有与原始提交相同的内容/效果,加上或减去您在交互过程中进行的任何更改。当所有操作完成后,它将标签(即便条纸)从旧的提交链末尾删除,并将其粘贴到新的提交链末尾。它首先复制要修改/重新基础的最古老的提交。该提交具有重新基础的父提交(可以是与原始链中相同的父提交,也可以是不同的父提交:无论哪种方式都可以,因为它是一个新提交)。然后,它复制旧链中的下一个提交,但指向新链。它重复直到旧链的结尾。
这就是为什么你的其他分支现在独立于你的重新基础分支。它们必须是独立的,因为它们使用旧的提交ID。如果你想让它们从你的新的重新基础分支分支出去,你必须进入每一个分支并对它们进行重新基础。有一种强大的命令叫做git filter-branch,类似于瑞士军刀般,可以用于重新做很多次提交,使得新的提交与原来的提交内容(大部分)相同,这有些像是“加强版”的git rebase。你可以使用它来实现这个目的(使用--all参数会影响所有分支)。由于它确实会重新执行所有提交,所以你最终得到的存储库基本上与原始存储库无关。
修改初始提交比较困难(不是不可能),因为它没有父级,所以普通的rebase就无法实现。(但是filter-branch可以)。因为这些操作会改变SHA1 ID,而任何克隆了你的存储库的人都在使用/依赖这些ID,所以通常情况下,你不能随意更改提交。当你知道没有人依赖某个特定的提交集时,你可以尽情地进行rebase,但是你不能回到初始提交,因为那将位于存储库的共享部分。完全从“初始提交”开始一直到底层的所有内容都是你自己的私人内容,这种情况相当罕见(当然也不是“从未”出现过)。
自从我写这篇文章以来,git rebase已经学会了复制root提交(如初始提交)。使用传统的git rebase语法,你必须命名root的父提交,当然这里是没有父提交的(这就是它在图中被称为“root”的原因)。所以rebase使用--root参数来处理这种情况。

2有可能存在多个根节点;例如使用git checkout --orphan命令创建一个新的根提交,然后再使用git commit命令。尽管Git源代码本身是存储在一个具有多个根节点的Git仓库中,但这种情况还是比较少见的。


好的,这很有道理。我花了一整天进行实验,但失败了。嗯,实际上,我并没有失败,我学到了很多东西。我想这个经验的关键是,在提交信息时“明智选择”。虽然我理解为什么会发生这种情况,但我也认为提供一种方法来进行此类编辑将非常有用——当然要四次警告。如果您在提交消息中犯了错误,那就有点麻烦了。我的实验是一个极端情况,我只是随意发送消息,然后决定“嘿,现在让它们有意义”,这当然非常困难。 - martin's
4
是的!这就是为什么我通常用Git时会在私有分支(或多个私有分支)上进行工作,并确保它们保持私有状态,然后在准备好的时候重新检查并且如果需要重写所有要发布的提交。最后一次像这样对所有内容进行“rebase -i”操作。 - torek
@torek 看起来对于大多数项目来说,重新检查所有提交可能有些过度... 相反,只需在准备好之前不要提交,如果犯了错误,只需进行新的提交。 对于大多数用例来说,这似乎更简单。 - Paul
显然,只要您还没有将提交推送到远程,您就可以在此处更改提交消息:https://help.github.com/articles/changing-a-commit-message/。 - Glen
@Glen:git commit --amend的意思是:新建一个提交,然后更改分支名称,使用新提交来代替旧的tip提交。 从根本上讲,这与复制提交相同,只是限于tip,而git rebase -i则不是。 - torek

5
如果提交尚未推送,则可以通过第二个修正第一个的提交来更改其消息:git commit --amend -m "新消息"。

很好,它运行正常。 - Machhindra Neupane
只要是最新的提交,即使已经被推送过,你仍然可以这样做,但是你必须强制推送以覆盖你已经推送的内容。只要你将其推送到没有其他人在使用的分支上,那就没问题。 - iconoclast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接