我知道这对很多场景来说都是一个坏主意。我正在学习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”重命名为“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,所以上面并没有真正影响真正的代码库。很高兴知道你可以这样做。
现在重命名第一次提交。
答案 0 :(得分:18)
您实际上无法更改提交。如果您在任何地方进行最小的单位更改,从电子邮件行中的名称拼写到提交时间戳的确切秒,您将获得一个新的,不同的提交,使用新的不同SHA1(SHA1是“true”在git数据库中为每个“对象”命名“,”提交是四种对象之一。)
提交的一个不可变的部分是它的“父提交”,它从最近提交到最旧提交向后构建链。
因此,git rebase -i
所做的是创建 new 提交链,链中的每个提交都具有与原始提交相同的内容/效果加上或减去您所做的任何更改在互动期间。完成所有操作后,它会从旧提交链的末尾删除标签(粘滞便笺),并将其粘贴到新提交链的末尾。它首先制作要修改/重新定位的最旧提交的副本。这个具有重新定位的父级(可以是与原始链中相同的父级提交,或者是不同的父级:无论哪种方式都可以,因为它是新的提交)。然后它会复制旧链中的下一个,但指向新链。它一直重复到旧链的末端。
这就是为什么所有其他分支机构现在都独立于您的重新分支机构。他们有,因为他们使用旧的提交ID。如果你希望他们分支你的新重新分支,你必须进入每个分支并重新定义它们。
有一个强大的Swiss-Army-chainsaw-ish命令git filter-branch
,您可以使用它来执行一系列非常大的“重做提交,使所有新提交(大多数)相同内容作为原件“,类似于git rebase
类固醇,你可以用于此目的。 (使用--all
运行它以影响所有分支。)当然,因为它实际上重做了所有提交,所以最终会得到一个与原始仓库基本无关的仓库。
重写初始提交很难(不是不可能),因为它没有父级,所以常规rebase
就不会这样做。 1 (filter-branch
可以。 )因为这些更改了SHA1 ID,而且克隆了你的repo的任何人正在使用/依赖它们,所以它通常是一个你不能摆弄这样的提交的功能。当你知道没有其他人依赖于某些特定的提交时,那么你可以rebase
所有你喜欢的,但你不会回到初始提交,因为那将在repo的共享部分。这是非常罕见的(虽然当然不是“永远不会”)整个事情一直回到“初始提交”都是你自己的私人事物。
1 自从我写这篇文章以来,git rebase
已经学会了像初始提交一样复制根提交。 2 使用传统的git rebase
语法,你必须为root的父提交命名,当然是没有父(这就是图中的“root”)。因此rebase
使用--root
作为参数来涵盖此案例。
2 可能有多个根;例如,使用git checkout --orphan
后跟git commit
来创建新的根提交。虽然git源本身保存在具有多个根的git仓库中,但有这些有点不寻常。