簡體   English   中英

Git:如何將兩次提交之間的所有提交壓縮成一次提交

[英]Git: How to squash all commits between two commits into a single commit

我有一個分支機構,過去幾個月我一直在幾台計算機上親自工作。 結果是一個很長的歷史鏈,我想在將它合並到主分支之前進行清理。 最終目標是擺脫我在處理服務器代碼時經常做的所有wip提交。

以下是gitk歷史可視化的屏幕截圖:

在此輸入圖像描述 http://imgur.com/a/I9feO

在這個底部的方式是我從主人分支的點。 自從我開始這個分支以來,Master已經改變了一點,但是這些變化是不相交的,所以合並應該是小菜一碟。 我通常的工作流程是重新加入master,然后壓縮wip提交。

我試着執行一個簡單的

git rebase -i master

我編輯了對sqush的提交。

它似乎開始很好,但后來失敗了,並希望我解決沖突。 然而,似乎沒有好辦法通過觀察差異來解決它。 每個部分都使用范圍中未定義的變量,因此我不確定如何解決它們。

我也嘗試使用git rebase -i -s recursive -X theirs master the git rebase -i -s recursive -X theirs master ,這不會導致沖突,但它改變了HEAD從修改后的分支狀態(我想以最終結果的方式編輯歷史記錄)在HEAD中不會改變)。

我相信這些沖突來自於你可以看到鑽石圖案的鏈條部分。 (例如,在重新分類的分類器......和Merge分支iccv之間)。


為了更好地表達我的問題,讓A =“Merge branch iccv”, B =“reworked classifiers”指的是圖像中的示例。 中間的提交將是XY

      ...
       |
       |
       A 
     /  \
    |   X
    Y   |
     \ /
      B
      |
      |
     ...

我想重寫歷史記錄,以便A的狀態完全如此,並有效地破壞中間表示XY ,因此生成的歷史記錄看起來像這樣

      ...
       |
       |
       A 
       |
       |
       B
       |
       | 
      ...

有沒有辦法將AXY的已解決狀態Y到像這樣的歷史鏈中間的單個提交中?

如果AB是提交的SHAID,那么我可以運行一個簡單的命令(或者可能是腳本)來實現我想要的結果嗎?

如果A是HEAD,我相信我能做到

git reset B
git commit -am "recreating the A state"

創建一個新頭,但如果A位於這樣的歷史鏈中間,我該怎么做呢。 我想保留它之后的所有節點的歷史記錄。

首先使當前工作樹清理,然后運行以下命令:

#initial state

在此輸入圖像描述

git branch backup thesis4
git checkout -b tmp thesis4

在此輸入圖像描述

git reset A --hard

在此輸入圖像描述

git reset B --soft

在此輸入圖像描述

git commit

在此輸入圖像描述

git cherry-pick A..thesis4

在此輸入圖像描述

git checkout thesis4

在此輸入圖像描述

git reset tmp --hard
git branch -D tmp

在此輸入圖像描述

SX,Y,A的壁球。 M'相當於MN'對於N 如果要恢復初始狀態,請運行

git checkout thesis4
git reset backup --hard

這可以做到,但是通過常規機制,它可以從一點痛苦到很多痛苦。

根本問題在於,只要您想要更改內容,就必須提交復制到新的(略有不同的)提交。 原因是任何提交都無法改變 1原因是提交的哈希ID 提交,在非常真實的意義上:Git的哈希ID是Git如何找到底層對象。 更改對象中的任何位,它將獲得一個新的,不同的哈希ID。 2因此,當你想要去的時候:

       X
      / \
...--B   A--C--D--E   <-- branch
      \ /
       Y

到看起來像這樣的東西:

...--B--A--C--D--E   <-- branch

B之后的東西不能A ,它必須是一個不同的提交,只是聞起來像A 我們可以將此提交A'稱為分開:

...--B--A'-...

但是如果我們將A復制到一個新的,更新鮮的(但相同的樹) A' ,它的歷史中不再有中間的東西 - 也就是說, A'直接連接到B我們必須復制第一個提交后的 A' 一旦我們這樣做,我們必須在那之后復制提交,依此類推。 結果是:

...--B--A'-C'-D'-E'  <-- branch

1心理學家喜歡說改變很難 ,但對於Git來說,這幾乎是不可能的! :-)

2 Hash沖突在技術上是可行的 ,但如果它們發生,則意味着您的存儲庫停止添加新內容。 也就是說,如果你設法提出一個類似舊的提交但是有你想要的更改並且具有相同的哈希ID的新提交,Git會禁止你添加它!


使用git rebase -i

注意:如果可能,請使用此方法; 它更容易理解和正確。

復制這樣提交的標准命令是git rebase 但是,對於像A這樣A合並提交,rebase交易非常糟糕。 事實上,它通常會完全拋棄它們,而是傾向於線性化所有東西:

...--B--X--Y'-C'-D'-E'   <-- branch

例如。

現在,如果合並提交A進展順利,即X任何內容都不依賴於Y ,反之亦然,那么簡單的git rebase -i <hash-of-B>就足夠了。 你可以改變除了提交XY的第一個pick的所有提交 - 實際上可能是許多提交 - squash並且所有事情都進展順利並且你完成了:Git完全支持單個XY'組合XY'提交,它具有您的合並提交A具有的相同樹。 結果是:

...--B--XY'-C'-D'-E'   <-- branch

如果我們稱之為XY' A' ,然后忘記自己原來的散列的ID刪除所有刻度線,我們可以得到你想要什么。


使用git replace

但是,如果合並很困難,那么你想要的是保留合並中的 ,同時刪除所有的XY提交。 這里git replace是(或者)正確的解決方案 Git的替換有點復雜,但你可以指示Git創建一個新的提交A' ,它“像A一樣A但是B是它的單父哈希ID”。 Git現在將具有此提交圖結構:

       X
      / \
...--B   A--C--D--E   <-- branch
     |\ /
     | Y
     \
      A'  <-- refs/replace/<complicated-thing>

這個特殊的refs/replace名稱告訴Git,當它執行諸如git log和其他使用提交ID的命令之類的事情時,Git應該將其隱喻的眼睛從提交A轉移而不是提交A' 由於A'是否則的副本 Agit checkout <hash of A>使得GIT中看看A' ,並檢查了相同的樹; 當它看起來放在A'而不是A時, git log顯示相同的日志消息。

請注意,此時存儲庫中都存在AA' 它們是並排的,Git只是向你顯示A'而不是A除非你使用特殊的--no-replace-objects標志。 一旦Git向你顯示(並使用) A'代替A ,它就會跟隨從A'B的向后鏈接,跳過XY所有。

使替換永久化,完全脫落XY

一旦您對替換感到滿意,您可能希望將其永久化。 您可以使用git filter-branch執行此操作,它只是復制提交。 它從一些起點開始復制並在歷史上向前移動,與Git的正常向后相反“從今天開始,在歷史中向后工作”的方式。

當filter-branch正在制作它的副本時 - 以及它的復制內容列表 - 它通常會像Git的其余部分一樣完成另一個避開眼睛的事情。 因此,如果我們有上面顯示的歷史記錄,並且我們告訴filter-branch在branch上結束並在提交B之后啟動,它將收集現有的提交列表,如下所示:

E, D, C, A'

然后顛倒順序。 (事實上​​,如果我們願意,我們可以停在A' ,正如我們所看到的那樣。)

接下來,filter-branch將A'復制到新的提交。 這種新的承諾將B作為其母公司,相同的日志消息A'同一棵樹,同樣的作者和日期,郵票等,總之,它簡直等同於A' 因此它將獲得與A'相同的哈希ID,並且實際上是提交A'

接下來, filter-branchC復制到新的提交。 這個新提交將使用A'作為其父級,與C相同的日志消息以及相同的樹等等。 這與原始C略有不同,原始C的父級是A ,而不是A' 所以這個新的提交獲得了一個不同的哈希ID:它變成了提交C'

接下來, filter-branch將復制D 這將成為D' ,就像C的副本是C'

最后, filter-branchE復制到E'並使branch指向E' ,給我們這樣:

       X
      / \
...--B   A--C--D--E   <-- refs/original/refs/heads/branch
     |\ /
     | Y
     \
      A'  <-- refs/replace/<complicated-thing>
       \
        C'-D'-E'  <-- branch

我們現在可以刪除refs/replace/ name以及refs/heads/branch的備份副本,filter-branch用來保存原始E 當我們這樣做時,名稱就會消失,我們可以重新繪制圖表:

...--B--A'-C'-D'-E'  <-- branch

這正是我們想要(並獲得)使用git rebase -i ,但無需再次進行合並。

過濾器分支的機制

要告訴git filter-branch停止的位置 ,請使用^<hash-id>^<name> 否則git filter-branch將不會停止列出提交,直到它用完提交:它將跟隨提交B到其父級,並且跟隨父級的父級,依此類推,直到歷史記錄為止。 這些提交的副本將與原始文件一點一點地相同,這意味着它們實際上是原件,相同的哈希ID和所有; 但他們需要很長時間才能完成。

由於我們可以停在<hash-id-of-B>甚至<hash-id-of-A'> ,我們可以使用^refs/replace/<hash>來識別提交A 或者我們可以使用^<hash-id> ,這可能實際上更容易。

此外,我們可以編寫^<hash> branch<hash>..branch 兩者意味着相同的事情(詳情請參閱gitrevisions文檔 )。 所以:

git filter-branch -- <hash>..branchname

足以進行過濾以將更換固定到位。

如果一切順利,請刪除git filter-branch文檔末尾附近顯示的refs/original/ reference,並刪除替換引用,然后就完成了。


使用櫻桃挑選

作為git replace ,您還可以使用git cherry-pick來復制提交。 有關詳細信息,請參閱ElpieKay的答案 這基本上與以前的想法相同,但使用“復制提交”工具而不是“rebase復制提交然后隱藏原件”工具。 它有一個棘手的步驟,使用git reset --soft來設置索引以匹配提交A以進行提交A'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM