[英]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 分支的內容...
有不止一個,但在選擇任何一個之前,您需要明確branch和content 的含義。
關於分支要記住的一點是,每個分支名稱只是說我最近的提交是 _____ (用原始提交哈希 ID 填寫空白)。 “在”分支上的提交取決於該提交。
每個提交都有一個唯一的哈希 ID。 該哈希 ID 表示該提交,而不是任何其他提交。 一旦提交,它的任何部分都不能改變。 所有提交都被完全凍結。 提交也大多是永久性的:至多,您可以停止使用提交,並取消查找該提交的方法,我們稍后會看到。
每個提交包含兩件事:作為它的主要數據,所有文件的完整快照,以及作為它的元數據——關於提交的信息。 這包括提交者的姓名和電子郵件地址,以及解釋他們提交的原因的日志消息。 但最重要的是,至少對於 Git 而言,是這樣的:當您或任何人進行新提交時,Git 會記錄其直接父提交的原始哈希 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
提交都在兩個分支上。
在這種情況下,分支在H
處H
,至少,從 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 存儲庫已經提交了I
和J
,那么使用該存儲庫的人可能已經構建了鏈接回J
新提交。 他們不會感謝你要求他們忘記所有的提交,以及I
和J
。
如果可以讓其他人忘記I
和J
,您可以將master
重置為指向L
。 然后,您必須使用git push --force
或等效命令說服其他一些 Git 存儲庫忘記I
和J
(可能還有其他提交),並且使用此存儲庫克隆的其他所有人都需要確保他們的Git 忘記I
和J
等。
這是制作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
,就像任何其他合並一樣。 但是您已經“取消”了您在I
和J
所做的更改,而只添加了對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.