簡體   English   中英

git優雅的變基/完全合並到分支

[英]git graceful rebase/complete merge to branch

再會,

我目前正試圖弄清楚如何在不使用諸如“rebase”之類的激烈內容的情況下“覆蓋”分支。

例如:

branch::master
|- dir_a
|  |- file_a
|  |- file_b
|- file_c

branch::dev
|- dir_a
|  |- file_b
|- file_c        [edited compared to branch::master]


branch::master   [after merge from branch::dev with strategy 'ours' (keep changes from branch::dev)]
|- dir_a
|  |- file_a     [is also merged, but should be deleted]
|  |- file_b
|- file_c        [edited version from branch::dev]

為了解決上述問題,我可以做一個“rebase”,但這會破壞很多東西,因為 master(head) 分支不是私有/個人分支。

所以問題是:有沒有一種好方法可以完全覆蓋/替換 master 分支的內容,從而不會破壞對當前 master 分支的依賴?


可能影響可能性的信息:

  • 兩個分支之間可能存在多次提交的差異(數量也未知)
  • 最好是“開發”分支應該繼續存在(也是出於依賴性原因)。
  • 最好主分支的歷史應該保持完整

如果需要任何額外的說明,請隨時提出。 感謝您的思考!

有沒有一種好方法可以完全覆蓋/替換 master 分支的內容...

有不止一個,但在選擇任何一個之前,您需要明確branchcontent 的含義。

關於分支要記住的一點是,每個分支名稱只是說我最近的提交是 _____ (用原始提交哈希 ID 填寫空白)。 “在”分支上的提交取決於該提交

每個提交都有一個唯一的哈希 ID。 該哈希 ID 表示提交,而不是任何其他提交。 一旦提交,它的任何部分都不能改變 所有提交都被完全凍結。 提交也大多是永久性的:至多,您可以停止使用提交,並取消查找該提交的方法,我們稍后會看到。

每個提交包含兩件事:作為它的主要數據,所有文件的完整快照,以及作為它的元數據——關於提交的信息。 這包括提交者的姓名和電子郵件地址,以及解釋他們提交的原因的日志消息。 但最重要的是,至少對於 Git 而言,是這樣的:當您或任何人進行提交時,Git 會記錄其直接提交的原始哈希 ID。 該哈希 ID 是您剛才提交的哈希 ID,這分支中的最后一次提交。 同樣,此數據(快照)或元數據(包括父哈希 ID)的任何部分都不能從這里開始更改 - 因此這意味着提交會記住之前的分支提示提交。

因此,“分支的內容”可以是:

  • 分支中最后一次提交的原始哈希 ID; 或者
  • 該哈希 ID 以及該提交,加上存儲在該提交中的哈希 ID,因此是前一次提交,加上存儲在該提交中的另一個哈希 ID,因此是另一個先前提交,再加上......

如果我們繪制這些提交——這需要對它們的哈希 ID 做出假設; 在這里,我將用單個大寫字母替換每個實際的哈希 ID——我們得到這樣的圖片:

             I--J   <-- master
            /
...--F--G--H
            \
             K--L   <-- dev

也就是說,名稱master持有提交J的原始哈希 ID(不管它到底是什么)。 提交J保存提交I的原始哈希 ID,它保存提交H的哈希 ID。 同時,名稱dev持有提交L的哈希 ID,它持有K的哈希 ID,持有H的哈希 ID。

請注意,直到H提交都在兩個分支上。

在這種情況下,分支在HH ,至少,從 Git 的角度來看是這樣:從最后開始向后工作,一次提交一個。


1從技術上講,索引包含文件的名稱、它們的模式(+x 或 -x)以及對凍結格式內容的引用


分支通常如何生長

要進行新的提交,您可以從git checkout name開始。 假設在這種情況下名稱是master 這個發現提交給它的名字點-in這種情況下, J -和副本的只讀內容提交出的承諾,到Git的索引和你的工作樹。 工作樹是您可以查看和編輯文件的地方。 這還將特殊名稱HEAD附加到名稱master

             I--J   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

Git 現在可以看到當前分支master (通過查看HEAD的附加位置),當前提交J (通過查看master指向的位置)。 你的工作樹現在有使用的文件——普通文件,而不是凍干(壓縮和僅 Git)提交的文件——並且在 Git 的索引中,有來自J的凍干文件的副本1 ,准備好進入一個新的提交。

您現在可以隨意修改工作樹。 完成后,您可以對各種文件運行git add 這會將每個添加文件的內容復制回 Git 的索引,將它們壓縮成 Git 將存儲在提交中的凍干形式,替換之前的副本。 或者,你可以git add一個新文件,或者git rm一個現有文件; 無論哪種方式,Git 都會相應地更新索引。

然后,您運行git commit Git 簡單地打包index 中的任何內容,添加您的姓名和當前時間,添加您的日志消息,並將其作為新提交寫出。 新提交的父項當前提交,哈希 ID 是新提交內容的唯一校驗和,不能再更改。 我們將把這個新提交稱為N (跳過M )。 N指向J

                  N
                 /
             I--J   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

但是現在我們得到了使分支有用的技巧: Git 現在將新提交的哈希 ID 寫入分支名稱。 所以我們現在有:

             I--J--N   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

請注意HEAD沒有改變:它仍然附加到master

現在讓我們擺脫提交N 它實際上不會消失——它仍然在我們的存儲庫中——但我們會安排名稱master再次標識提交J 為此,我們找到J的實際哈希 ID 或它的任何代理,並使用git reset --hard

git reset --hard <hash-of-J>

現在我們有:

                  N   [abandoned]
                 /
             I--J   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

如果您在您的存儲庫中執行此操作,而不使用git push新提交N發送到某個其他Git 存儲庫,則只有您將提交N 沒有人會知道你做了這件事!

任意使用git reset

您可以使用git reset任何分支移動到任何提交。 但是,如果你像我們剛才那樣“向后”移動一個分支,那么“從頭到尾”的提交就會變得很難找到。 假設您強制名稱master指向提交L 那么,你會如何找到 commit J呢? 提交I怎么樣? 他們好像不見了!

可能就是你想要的。 但是,如果某個其他Git 存儲庫已經提交了IJ ,那么使用該存儲庫的人可能已經構建了鏈接回J提交。 他們不會感謝你要求他們忘記所有的提交,以及IJ

如果可以讓其他人忘記IJ ,您可以將master重置為指向L 然后,您必須使用git push --force或等效命令說服其他一些 Git 存儲庫忘記IJ (可能還有其他提交),並且使用此存儲庫克隆的其他所有人都需要確保他們的Git 忘記IJ等。

這是制作master match dev的最快和最簡單的方法,但它也是對其他人最具破壞性的方法。 Git 在分支增長時“喜歡”它,當它們失去提交時“不喜歡”它。

合並的工作原理(縮寫)

現在讓我們看看如果您運行git merge dev (讓master再次指向J )會發生什么。 對於這個合並操作,Git 需要三個輸入提交。 一個是您當前的提交,它合並調用ours . 一個是您選擇的任何其他提交,其合並調用theirs . 第三個——在某種意義上是第一個; 至少,它很快就獲得了第 1 名——Git 自己找到了。

通過使用git merge dev ,您選擇了提交L作為theirs提交。 那是因為名稱dev選擇 commit L Git 現在從兩個分支提示反向工作以找到最佳共享提交,在這種情況下顯然是提交H Git 將此稱為合並基礎

請記住,每個提交都包含一個快照。 所以提交H有一個快照,提交J有一個,提交L有一個。 Git 可以隨時比較任意兩個快照以查看不同之處。 為您執行此操作的命令是git diff 合並操作想知道有什么不同,所以它運行內部等價物:

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed on master
git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed on dev

Git 現在可以組合這兩組更改,並將組合的更改應用於H的快照(不是J ,不是L ,而是H :合並基礎)。 如果我們添加了一個文件而他們沒有添加,Git 會添加該文件。 如果我們刪除了一個文件而他們沒有刪除,Git 會刪除該文件。 如果他們更改了文件而我們沒有更改,則 Git 會更改文件。

如果一切順利,結果就是我們的變化和他們的變化的結合。 Git 會將這個結果填回到索引中——這是 Git 提交的地方——同時還會更新我們的工作樹,以便我們可以看到它做了什么。 然后,由於一切順利,Git 進行了一次新的提交。

新提交,我們將其稱為M以表示merge ,它沒有一個而是兩個父項。 一個父節點和往常一樣: M鏈接回現有的提交J ,這是master剛才所在的位置。 但是 Git 添加了我們選擇合並的 commit L作為第二個父級。 所以現在的圖片是: 2

             I--J
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             K--L   <-- dev

提交M有一個快照。 它的快照是由 Git (而不是您)創建的,它結合了來自共同起點(合並基礎)的更改,並將合並的更改應用於合並基礎快照。

如果您遇到合並沖突,索引將扮演擴展角色並幫助解決沖突。 在最終合並提交M確定最終快照取決於您,而不是 Git。 但是如果沒有任何合並沖突,Git 通常會自行進行合並。 通常在這里是一個重要的詞。


2 Commit N仍然存在,但是沒有辦法找到它,你再也看不到它了,我們也不需要費心把它畫進去。最終,Git 會完全刪除它——通常至少在一段時間后30天過去了。 在此之前,您可以根據需要取回它:您只需要找到它的哈希 ID。


這是一個正常的合並; 你可能想要一個覆蓋合並

假設您可以告訴 Git:開始合並,但不要進行合並提交M Git 會像往常一樣找到合並基礎,像往常一樣組合您的更改和它們的更改,並像往常一樣更新您的工作樹和 Git 的索引......然后停止,而不進行提交。

您可以使用git merge --no-commit做到這一點。 完成后,您可以您喜歡的任何內容替換合並結果。 將您喜歡的任何文件放入您的工作樹中,並使用git add將其復制到索引中。 索引副本將進入新的合並提交,現在與您放入工作樹中的任何文件相匹配。

您可以在此處添加新文件和完全刪除文件。 無論您做什么,都取決於您:此時您可以控制一切。 您可以使合並具有您想要的任何內容。

你想要什么——不管怎樣,根據你的問題; 想一想這是否真的是你想要的——完全忽略從合並基礎H到你的提交J的差異,而只考慮從H他們的提交L的差異。 當然,這將與提交L中的快照完全匹配。

有一種快速而簡單的方法,使用git merge -s ours ,告訴 Git 它應該完全忽略他們分支的更改並只使用你的所有版本,即提交J 不幸的是,這與您在這里想要的相反:您要求一種方法來完全忽略分支的更改並只使用它們的所有版本,即提交L

幸運的是,有相當神奇的命令3,你可以使用快速方便,讓你想要的這里:

git merge --no-commit dev
git read-tree --reset -u dev

這個git read-tree -u命令告訴 Git:用我在這里命名的提交中存儲的文件替換索引和我的工作樹內容。 --reset選項告訴 Git 拋出沖突條目,如果合並產生了合並沖突(否則應該是無害的)。 所以現在索引和您的工作樹與L中的快照相匹配。 現在您可以完成合並: 4

git merge --continue

這使得新的合並提交M來自存儲在索引中的內容,這要歸功於git read-tree命令的-u選項,您也可以在工作樹中看到。 所以現在你有:

             I--J
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             K--L   <-- dev

其中的內容提交M完全相同提交的內容相匹配L M第一個父級仍然是J ,第二個仍然是L ,就像任何其他合並一樣。 但是您已經“取消”了您在IJ所做的更改,而只添加了對master的提交。 所有其他 Git 都會很高興提交M添加他們的提交集合中。


3這根本就不是魔術。 這是一個管道命令,用於腳本,而不是用戶。 然而, read-tree 命令非常復雜:它實現了(一些)合並、子樹操作和各種其他聰明的東西。 這是索引存在的重要原因,否則會讓人感到痛苦。

4如果你願意,你可以運行git commit代替。 在沒有git merge --continue舊 Git 版本中,您必須使用git commit 直接使用git commit可以的:所有git merge --continue所做的就是首先檢查是否有要完成的合並,然后為您運行git commit 使用git merge --continue ,您可以檢查您是否確實正在完成合並。


也考慮這個選項

假如你不想合並。 也就是說,假設您想保持進度:

             I--J   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

是這樣,但是向master添加一個新提交O其父項J ,但其內容dev上的L匹配?

這也很容易做到。 剛剛完成git checkout master ,以便您的索引和工作樹匹配J ,您現在運行:

git read-tree -u dev

(沒有先前的合並,就不會出現合並沖突,因此不應需要--reset )。

這將替換 Git 的索引和您的工作樹內容,從提交L讀取它們,就像我們在合並示例中所做的那樣。 現在你可以運行git commit來生成O

             I--J--O   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- dev

承諾的內容O現在相匹配的內容L ; 歷史通過啟動在獲得master -commit O -and工作向后讀取O ,然后J ,然后I ,然后H ,等等。

(您想要這些選項中的哪一個?這取決於您。)

如果您想在不進行真正合並的情況下進行合並,只需通過選擇“我們的”合並策略來加入歷史記錄:

git checkout dev
git merge -s ours master

您的歷史記錄將記錄 dev 到 master 的合並,兩個分支的歷史記錄將保持不變,但文件內容看起來就像master從未存在過(它們只反映dev內容)

暫無
暫無

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

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