git rebase南瓜弄乱了分支机构的历史

时间:2019-05-13 10:20:57

标签: git git-rebase git-squash

我有一个历史悠久的git存储库和各种分支,这些分支已合并到master中。但是,第一个提交只是源于第一个提交,没有任何分支。

我想将其中的一些第一次提交压缩在一起。为此,我做了

git rebase -i --root

然后在编辑器中选择要压缩的提交,然后更改已压缩的提交的提交消息。 git rebase似乎工作正常,但令我惊讶的是,git log显示历史已被弄乱了,丢失了分支合并的历史:现在出现了几次提交重复,分支未合并的情况。

尽管很难复制完整的问题,但在此我提供了一个最小的示例来说明类似的问题。考虑由以下命令创建的git本地存储库

git init .
touch foo.txt
git add foo.txt
for (( i=1; i<4; i++ )); do echo hello $i >>foo.txt ; git commit -a -m $i; done
git checkout -B test
for (( i=1; i<4; i++ )); do echo hello $i >>foo.txt; git commit -a -m test$i; done
git checkout master
git merge test
for (( i=4; i<5; i++ )); do echo hello $i >>foo.txt ; git commit -a -m $i; done

这将创建一个存储库,在其中一些提交新的分支test之后,该分支将合并到master(简单的快进)中,最后是对master的最后一次提交。 存储库的日志为(作者/日期已删除)

git log --graph --all

* commit ba326e1ada9525fd2b1b275c597ad189b7cf3ddf (HEAD -> master)
| Author: 
| Date:   
| 
|     4
| 
* commit b739356170f946f3d1b3c3cb3299f4379212ecc6 (test)
| Author: 
| Date:   
| 
|     test3
| 
* commit 303e95fd23beb9c5a96c631667dfd2be7e924390
| Author: 
| Date:   
| 
|     test2
| 
* commit dd67e3ca5f12e35c736483bcc8335347dc78da87
| Author: 
| Date:   
| 
|     test1
| 
* commit 2420498ae794fa900dd9fee8296b7561e08028b8
| Author: 
| Date:   
| 
|     3
| 
* commit 2d43d89d2b75566f37a99b7cce294cddcd3fbf7a
| Author: 
| Date:   
| 
|     2
| 
* commit 487e5158e461479f848eb7313dc87090f197f4a7
  Author: 
  Date:   

      1

现在,我将提交“ 2”和“ 3”压缩为一个。我会

git rebase -i --root

然后在编辑器中输入

pick 487e515 1
pick 2d43d89 2
squash 2420498 3
pick dd67e3c test1
pick 303e95f test2
pick b739356 test3
pick ba326e1 4

在压缩提交的提交消息中,输入“ squash 2 3”。 git rebase似乎完成得很好:

[detached HEAD b5d1682] squash 2 3
 Date: Mon May 13 12:01:19 2019 +0200
 1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/master.

但是,令我惊讶的是,现在的历史一片混乱

git log --graph --all

* commit 463cbb99f553d555fadbe05a3cc90aeb46b0bbce  (HEAD -> master)
| Author: 
| Date:   
| 
|     4
| 
* commit 194e78fc74d4eaea62a8b903baa6379dfc8578d3
| Author: 
| Date:   
| 
|     test3
| 
* commit 8f5e0be6dd8288790b2893e9b3fa97e5e1021134
| Author: 
| Date:   
| 
|     test2
| 
* commit 1060aea0f3da5dfee8e37adc68d82d77e7c02ba4
| Author: 
| Date:   
| 
|     test1
| 
* commit b5d168283be820fed3b9862576c44c054402ab50
| Author: 
| Date:   
| 
|     squash 2 3
|   
| * commit b739356170f946f3d1b3c3cb3299f4379212ecc6 (test)
| | Author: 
| | Date:   
| | 
| |     test3
| | 
| * commit 303e95fd23beb9c5a96c631667dfd2be7e924390
| | Author: 
| | Date:   
| |
| |     test2
| | 
| * commit dd67e3ca5f12e35c736483bcc8335347dc78da87
| | Author: 
| | Date:   
| | 
| |     test1
| | 
| * commit 2420498ae794fa900dd9fee8296b7561e08028b8
| | Author: 
| | Date:   
| | 
| |     3
| | 
| * commit 2d43d89d2b75566f37a99b7cce294cddcd3fbf7a
|/  Author: 
|   Date:  
|   
|       2
| 
* commit 487e5158e461479f848eb7313dc87090f197f4a7
  Author: 
  Date:   

      1

git rebase再次拆分了合并的test分支,而master分支保留了历史记录。但是,我也丢失了test被合并到master中的信息。

我希望有这样的历史

git log --graph --all

* commit ba326e1ada9525fd2b1b275c597ad189b7cf3ddf (HEAD -> master)
| Author: 
| Date:   
| 
|     4
| 
* commit b739356170f946f3d1b3c3cb3299f4379212ecc6 (test)
| Author: 
| Date:   
| 
|     test3
| 
* commit 303e95fd23beb9c5a96c631667dfd2be7e924390
| Author: 
| Date:   
| 
|     test2
| 
* commit dd67e3ca5f12e35c736483bcc8335347dc78da87
| Author: 
| Date:   
| 
|     test1
| 
* commit 2420498ae794fa900dd9fee8296b7561e08028b8
| Author: 
| Date:   
| 
|     squash 2 3
|
* commit 487e5158e461479f848eb7313dc87090f197f4a7
  Author: 
  Date:   

      1

这是git rebase的错误吗?我正在使用git 2.19.2

如果我尝试不使用分支test,或者如果我在重新定基前删除了分支test,它可以正常工作并产生所需的历史记录,但是仍然丢失test上的信息分支。

此外,在更复杂的情况下,test被合并而没有简单的快进,在test压榨之前,显然会删除git rebase分支,合并后显然会完全丢失分支历史。

以下示例说明了这一点。

git init .
touch foo.txt
git add foo.txt
for (( i=1; i<4; i++ )); do echo hello $i >>foo.txt ; git commit -a -m $i; done
git checkout -B test
for (( i=1; i<4; i++ )); do echo hello $i >>foo.txt; git commit -a -m test$i; done
git checkout master
touch foo2.txt
git add foo2.txt
git commit -a -m 4
git merge test
git branch -D test

现在的日志历史记录是

git log --graph --all

*   commit e905116fed7f4d52c65da46ab6172ae7a08e824a (HEAD -> master)
|\  Merge: 77cc483 584a037
| | Author: 
| | Date:   
| | 
| |     Merge branch 'test'
| | 
| * commit 584a03768822ebd92d4feee20ebe238fffd89c25
| | Author: 
| | Date:   
| | 
| |     test3
| | 
| * commit 7a2ad09b5c39e5076cd22fa957c2d539e37c0861
| | Author: 
| | Date:   
| | 
| |     test2
| | 
| * commit 11ef4fea5ba207a637a85d8e8456f48d0c7bd7ab
| | Author: 
| | Date:   
| | 
| |     test1
| | 
* | commit 77cc4833cf5aac84aca9737945fd79a7632019ac
|/  Author: 
|   Date:   
|   
|       4
| 
* commit 081792ccf9b4714ab4bce23e4e7b126647eeead8
| Author: 
| Date:   
| 
|     3
| 
* commit 971f217200f7e485308b861033b5b31b7ae69d1a
| Author: 
| Date:  
| 
|     2
| 
* commit cef186ae9ad0e316d82c62c2082381747f25a443
  Author: 
  Date:  

      1

将上述提交2和3合并在一起,将产生一个带有以下日志的存储库

git log --graph --all

* commit 3a4010551d50a47a9db6d53a4597770fa2517d92 (HEAD -> master)
| Author: 
| Date:   
| 
|     test3
| 
* commit bbbc747780c847b3d40ed7557ed514e5e4dd9fc2
| Author: 
| Date:  
| 
|     test2
| 
* commit 6f54fd90555fbe6730fcfe7d85761b6477380214
| Author: 
| Date:   
| 
|     test1
| 
* commit 71fa7371198a0cbbf4793dc27ffb27ac65d15096
| Author: 
| Date:  
| 
|     4
| 
* commit 3032069c375ef37b42af75c97759d7a821f1139f
| Author: 
| Date:   
| 
|     s 2 3
| 
* commit cef186ae9ad0e316d82c62c2082381747f25a443
  Author:
  Date:  

      1

日志显示我丢失了test分支的分支和合并历史。如果在合并到test后没有删除master分支,我得到的结果与第一个示例类似,但有分支拆分test

2 个答案:

答案 0 :(得分:2)

tl; dr:rebase仅使单个分支变基。

仅在当前分支(或由参数指定的分支)中执行变基。因此,您的结果与预期的一样,因为您仅重新设置了master分支的基础,而test分支却保持不变,即其HEAD指向您的旧提交。

如果您仔细阅读git rebase doc,将会发现它总是谈论the current branch

如果要其他分支指向重新提交的提交,则必须reset。在这种情况下,检出test分支并使用git reset --hard 194e78fc74d4eaea62a8b903baa6379dfc8578d3将产生您期望的结果。 (与往常一样,请谨慎使用reset --hard,因为它将删除未提交的更改。)

我不明白您担心失去哪些信息,因为分支test的所有更改都出现在重新建立的master历史记录中。

答案 1 :(得分:1)

意识到git rebase不是正确的工具之后,我设计了基于git filter-branch的解决方案。这个想法是通过补丁修改提交“ 2”的内容,添加提交“ 3”的内容。然后,提交“ 3”变为空,因此可以将其消除。

考虑第二个示例。

git diff 971f217200f7e485308b861033b5b31b7ae69d1a \
         081792ccf9b4714ab4bce23e4e7b126647eeead8 \
         >patch.txt
git filter-branch --tree-filter '\
   if [ "$GIT_COMMIT" == "971f217200f7e485308b861033b5b31b7ae69d1a" ]
   then
       git apply /localdirectory/patch.txt
   fi' \
   --prune-empty -- --all

由于git filter-branch在临时子目录.git-rewrite/t中工作,因此git apply命令需要修补文件的完整路径。

这留下了一些旧的裁判。检查git log正确之后,即可进行清理

git update-ref -d refs/original/refs/heads/master

这将为存储库提供日志

git log --graph --all

*   commit e905116fed7f4d52c65da46ab6172ae7a08e824a (HEAD -> master)
|\  Merge: 77cc483 584a037
| | Author: 
| | Date:   
| | 
| |     Merge branch 'test'
| | 
| * commit 584a03768822ebd92d4feee20ebe238fffd89c25
| | Author: 
| | Date:   
| | 
| |     test3
| | 
| * commit 7a2ad09b5c39e5076cd22fa957c2d539e37c0861
| | Author: 
| | Date:   
| | 
| |     test2
| | 
| * commit 11ef4fea5ba207a637a85d8e8456f48d0c7bd7ab
| | Author: 
| | Date:   
| | 
| |     test1
| | 
* | commit 77cc4833cf5aac84aca9737945fd79a7632019ac
|/  Author: 
|   Date:   
|   
|       4
| 
* commit 971f217200f7e485308b861033b5b31b7ae69d1a
| Author: 
| Date:  
| 
|     2
| 
* commit cef186ae9ad0e316d82c62c2082381747f25a443
  Author: 
  Date:  

      1

此时,任务已基本完成。尽管如此,更改提交“ 2”的消息还是有用的。可以使用msg-filter来完成此操作,例如:

git filter-branch --msg-filter 'sed "s/^2/2 and 3 together/"' -- --all
git update-ref -d refs/original/refs/heads/master

如果有更多分支,如第一个示例,则还需要消除相应的旧引用,例如:

git update-ref -d refs/original/refs/heads/test