![](/img/trans.png)
[英]How to rebase a branch while keeping the rebase target changes intact?
[英]Rebase entire git branch onto orphan branch while keeping commit tree intact
我有一個倉庫,其中有兩個分支master
和master-old
,這是作為孤立分支創建的。
現在,我想將master
的整個基礎重新建立到master-old
,但是每個提交的樹都應保持不變,即在master
和master-old
上,每個提交的工作副本在重新建立基准之前和之后應該看起來完全相同。
Current state
-------------
A - B - C - D <--- master
E - F - G - H <--- master-old
Desired state
-------------
E'- F'- G'- H'- A'- B'- C'- D' <--- master
我試圖使用git rebase --onto master-old --root
來完成此任務。 問題是,在到master
的初始提交和master-old
的整個提交歷史中,都創建了許多相同的文件,因此我要解決大量的沖突。
有沒有辦法以使每個提交的樹保持完整的方式來重寫歷史記錄?
既然你要保留與原相關的樹 A--B--C--D
一系列提交,你真的不希望畢竟變基 。 變基意味着將提交變成diff(變更集),然后一次將這些變更集應用到某個現有起點上,但是您要做的就是將附加到A
的樹復制到您的父級為A'
新提交中A'
H
,然后將附加到B
的樹復制到其父級為A'
的新提交B'
A'
,依此類推。
這是git filter-branch
運行良好的地方。 運行時:
git filter-branch <filter-list> <branch-name>
Git從給定的<branch-name>
找到每個可到達的提交,然后復制每個提交。 從邏輯上說,通過按原樣提取整個提交,運行<filter-list>
每個過濾<filter-list>
,然后使用結果樹和消息進行新的提交來完成復制。 它以與Git正常順序相反的順序(即“向前瀏覽歷史記錄”)而不是向后進行復制過程。
如果新的提交(帶有可能更改的,可能不是樹,可能更改的,可能不是父級,可能更改的,可能不是消息等)與原始提交的逐位相同是100% ,新提交的哈希ID不變。 在這種情況下,下一次提交的默認“新父級”與原始父級相同。 否則,下一次提交的默認“新父級”就是我們剛才所做的。
(在實踐中,由於提交圖可以再次分開並合並,並且因為您可以跳過提交或添加新的提交,所以filter-branch的真正作用是將舊的提交哈希映射到新的提交哈希。每次創建副本時, ,它會在該映射中輸入一對:<old-hash,new-hash>。但是,對於簡單的線性鏈,您可以將其視為僅記住最新提交的新哈希ID。
現在,這里的問題是您想更改一個特定提交(即根提交)的父哈希ID。 有一個專門用於此--parent-filter
。 還有兩種方法可以執行此操作,但讓我們首先介紹--parent-filter
。 這來自git filter-branch
文檔 :
--parent-filter <命令>
這是用於重寫提交的父列表的過濾器。 它將在stdin上接收父字符串,並在stdout上輸出新的父字符串。 父字符串采用git-commit-tree(1)中描述的格式:初始提交為空,普通提交為“ -p父”,a為“ -p父1 -p父2 -p父3 ...”合並提交。
因此,您可以測試stdin是否為空,如果是,則輸出-p <hash-of-H>
。 結果將是:
E--F--G--H--A'-B'-C'-D' <-- master
(與您要求的不完全相同,但可能更好)。
(要復制EFGH
鏈,您還必須通過master-old
作為積極參考,並且由於任何逐位相同的提交都必須具有與原始哈希相同的哈希ID,因此您至少必須進行提交E
一次更改,例如將提交方tiemstamp更改一秒鍾。)
在此值得一提的另外兩種方法。 一種是使用--commit-filter
:這實際上是進行新提交的命令。 您可以在這里做任何事情,包括完全省略一些提交; 但是使用所有其他過濾器的原因是使事情變得更容易 ,因此在這種情況下根本沒有理由使用提交過濾器。
git replace
最后,還有git replace
命令 。 git replace
作用是使新對象保留在存儲庫中,並由refs/replace/
名稱空間中的特殊名稱引用。 每當Git通過其哈希ID查看某個對象時,Git通常都會首先檢查是否存在refs/replace/<hash-id>
。 如果是這樣,則Git會轉而查看該參考指向的對象。
這意味着您可以構造一個新的Git對象,該對象非常類似於commit A
,但是稍有不同。 稍有不同是新提交對象中存儲了一個父哈希ID。 父哈希ID是提交H
ID。 (請注意,它與A
具有相同的樹 。)
現在有了這個新對象(我們將其稱為A'
,將其粘貼到存儲庫中,並使其refs/replace/<big-ugly-hash>
指向它:
A--B--C--D <-- master
E--F--G--H <-- master-old
\
A' <-- refs/replace/deadcabf001...
(基於A
的實際哈希值,可能實際上不是deadcabf001...
,因此請在此處使用正確的ID)。
當git log
去查看從提交D
開始的歷史記錄時,它將查看提交D
,然后獲取D
的父ID C
,查看提交C
,獲取B
的ID,然后繼續提交B
,獲取A
的ID和...哇,嘿,這是一個refs/replace/
! 畢竟我們不要看A
! 讓我們看看A'
! 它向您顯示A'
作為B
的父母,然后移至A'
'的父母並向您顯示H
,然后是G
,依此類推。
使用git replace
您不必復制任何其他提交。 您所擁有的是一次提交歷史,在該提交歷史中,新的“更好”的提交取代了舊的“不太好”的提交,但實際上兩者共存。 Git在以下情況下使用替代品:
refs/replace/ hash
的引用; 和 git --no-replace-objects
。 要求3使您可以查看原始(未替換的)歷史記錄(如果需要)。 第2項表示在git clone
,默認情況下您沒有替代品。 您必須明確要求它們(這並不難,但也沒有任何簡單易用的前端)。
由於上面的第2項,您可能要進行替換,確保所有功能都按您喜歡的方式運行, 然后運行git filter-branch
。 由於您沒有運行git --no-replace-objects filter-branch
,因此Git將看到替換提交A'
而不是原始提交A
因此它將復制A'
而不是A
您不需要--parent-filter
。 當它從E
到H
復制時,新副本將與原始副本逐位相同,因此這些副本將保持不變。 最終結果將與使用正確的父過濾器運行git filter-branch
相同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.