[英]git go back to the state of a file before conflict
我從另一個分支中拉出時發生合並沖突,並為單個文件錯誤地使用了“使用我的”而不是“使用他們的”。 其他相互沖突的文件都得到了妥善保管。
現在,我想移至該文件的舊狀態(沖突之前),然后再次從分支中拉出以創建沖突並選擇正確的選項。
我已經使用checkout git checkout“ commit#” PathToFile正確地將文件恢復到它所處的狀態(沖突之前),但是現在當我從其他分支中拉出時,沖突就不存在了。
有一種辦法只是一個文件做到這一點,不使用具有全部看中Git版本控制的“時間機器”的方面。 但是,您最好使用完整的高級版本,因為它適用於更多情況。
首先,請記住git pull
只是意味着:“運行git fetch
(帶有一些參數),然后,如果成功,則運行git merge
(帶有一些其他參數),或者運行git rebase
(帶有一些參數)。” 看來您沒有告訴它使用git rebase
而不是git merge
,所以我假設您要使用並正在使用合並行為。
請注意,您正在使用git merge
,並且現在要正確重復合並,您將要自己運行git merge
,而不是讓git pull
進行。 因此,您需要了解如何運行git merge
以及它的作用。 即使您讓git pull
運行git merge
,“需要知道它的作用”部分也是正確的。要知道git merge
作用,您還需要知道git fetch
作用。我發現剛接觸Git的人實際上做得更好如果他們分別運行git fetch
和git merge
(或git rebase
):它實際上消除了一些神秘和困惑。
git fetch
做什么 您的Git在您的存儲庫上工作,該存儲庫包含您的提交,分支機構已將其記住(指向)。 您的Git也與第二個Git對話; 另一個Git擁有自己的存儲庫,自己的提交和分支。 在不同的時間,您需要讓您的Git調用他們的Git並帶走他們的提交,具體來說,就是他們擁有的,您沒有的任何提交。
您可以通過運行git fetch
並為其指定一個遠程名稱(可能是origin
名稱)來實現。 如果您只有一個遙控器(大多數人都這么做),通常將其稱為origin
,並且您的Git可以很容易地弄清楚要從哪一個遙控器獲取數據,因為總之只有一個。 但是讓我們把它寫出來:
git fetch origin
這使用保存的URL(“遠程”保存URL)來調用另一個Git並獲取其提交和分支並將它們帶入您的存儲庫。 它還會重命名其所有分支,以便其master
成為您的origin/master
,他們的develop
成為您的origin/develop
,依此類推。 一旦您提交了它們,它們也就變成了“您的”(不是用“作者”或“提交者”來表示,而是因為您現在在存儲庫中有了它們),因此現在您可以使用它們。
在進行合並步驟之前,重要的是要有某種方式可視化正在發生的事情。
如果您使用gitk
或gitg
類的GUI,或者使用各種奇特的Web之一,則其中許多將繪制圖形。 即使在命令行上,您也可以運行git log --graph --oneline --decorate --all
以獲得提交的粗略圖形。
對於StackOverflow,我更喜歡只從左到右(主要是水平)繪制提交。 較新的提交向右。 由於像master
這樣的分支名稱指向分支的最頂端提交,因此我也將分支名稱也放在右側,並使用箭頭指向分支的頂端:
o <- o <- o <-- master
每個提交(每個o
)都“向后”指向其先前(父)提交。 這些向后的箭頭主要只是煩人和分散注意力,因此讓我們用連接線繪制提交。 並且,讓我們繪制一個分支結構,在這里您有自己的提交,而git fetch
帶來了其他一些提交。 現在我們有了這個:
o--o--o <-- master
\
o--o <-- origin/master
同樣,較新的提交是向右的,因此master
的尖端是第一行的最后一個提交, origin/master
的尖端是第二行的提交的最后一個提交。
每個提交都有一個單一的“真實名稱”,即像17fac9e...
或ea6631f...
這樣的大丑陋哈希ID。 為了能夠更方便地討論它們,讓我們改用一個字母的名稱或符號,然后將上面的內容重繪為:
o--*--A <-- master
\
o--B <-- origin/master
round o
節點是“無聊的”提交,我們無需再次提及。 提交*
是特殊的,因為如果您同時從這兩者進行反向工作,則它是master
和origin/master
連接的地方。 (如果您向前工作,則它們是分開的。但是,Git始終向后工作,因此如果這樣做也將更加容易。)Commit A
是特殊的,因為它是master
的尖端提交,而Commit B
是特殊的,因為這是origin/master
的小費。
我們還要在這里記住,每個提交都具有所有文件的完整快照 (由“索引”組成,這就是為什么在提交之前必須git add
文件git add
到索引中的原因)。
git merge
做什么 當您在master
並運行git merge origin/master
,Git會發現兩個提交:當前分支的尖端和您命名的分支的尖端。 這些是A
和B
然后,Git找到合並基礎 ,這是這兩個分支重新組合在一起的第一處。 這就是我們的commit *
,這就是我們標記它的原因。
然后,合並操作將運行兩個git diff
命令:
git diff <id-of-*> <id-of-A>
和:
git diff <id-of-*> <id-of-B>
第一個差異表示“我們更改了什么”,第二個差異表示“他們更改了什么”。 與所有git diff
,您必須問:“ 在什么方面有所變化?” 答案是:關於合並基礎-commit *
。
然后,Git嘗試組合這兩組更改。 如果它自己成功,那就假設一切都正確,這並不總是正確的,但常常令人驚訝地如此:“令人驚訝”,因為Git對代碼一無所知,它只使用簡單的文本規則。 然后提交結果。
如果Git失敗,它將使您完成合並,然后提交結果。
(合並全部使用索引和工作樹進行,但是我們在這里幾乎可以忽略它。不過,當Git使您完成合並時,這很重要。)
無論哪種方式,您都會得到一個新的提交,因此我們將其添加到我們的圖形中。 新的提交是合並提交 ,這意味着它有兩個父級,而不是一個父級:
o--o--A---M <-- master
\ /
o--B <-- origin/master
請注意,舊的合並基准再次變得無聊,因此它不再具有星號( *
)。 新的合並提交M
指向A
和 B
這意味着提交B
現在位於分支master
。 它也仍然在遠程跟蹤分支命名的origin/master
。
你說你搞砸了合並M
您不說以后是否添加更多提交。 如果這樣做了,那么這將變得有點困難,因為您可能希望保存這些提交。 如果您已經推送了這些提交(包括M
本身),則變得更加困難,因為現在您可能應該永遠保存它們。 但是現在,讓我們看一下如果再次運行git pull
會發生什么。
我們開始時仍然有這個:
o--o--A---M <-- master
\ /
o--B <-- origin/master
現在我們運行git fetch
。 這會發現任何origin
新東西,可能什么都不是。 因此,它什么也沒有帶來,並且使origin/master
節點指向提交B
現在,我們運行git merge
,告訴它將origin/master
(即提交B
)與當前的提交 M
合並。 這將找到M
和B
的合並基礎 ,這是兩個分支上的第一個提交。
第一次提交是……好,看一下圖表,然后向后跟隨M
是M
嗎? 否,因為origin/master
指向B
,並且不允許您向右移動,只能向左移動。 出於相同的原因,它也不能是A
B
呢? 那肯定在origin/master
,它指向了這一點。 而且,啊哈,這是上master
也因為M
指回到B
!
因此,合並基為B
,如果Git進行了合並,它將產生兩個差異:
git diff B M # what we did
git diff B B # what they did
顯然,從B
到B
並沒有什么區別,因此Git可以跳過所有這一切:將“我們做了什么”(將B
更改為M
)和“他們做了什么”(什么都沒有)結合起來,使我們有了M
因此git merge
根本不執行任何操作。
如果解壓縮舊文件然后提交,會發生什么? 好了,現在您有了:
o--o--A---M--C <-- master
\ /
o--B <-- origin/master
其中C
具有從A
提取的文件的版本。 現在我們運行git merge origin/master
。 Git找到C
和B
的合並基礎。 什么是合並基礎? 為什么,又是B
沒有什么可以合並的,而Git則什么也不做。
要使Git再次進行合並,您必須使當前的提交成為commit A
為此,您有幾種選擇:
A
的新分支名稱。 (使用git branch <newname> <id-of-A>
,然后使用git checkout <newname>
,或等效地,使用git checkout -b <newname> <id-of-A>
。) git reset
放棄 M
和C
提交,以便master
指向A
(使用git reset --hard
,確保沒有未保存的工作。) git checkout
提交本身,得到一個“分離的HEAD”,其中HEAD
指向A
讓我們畫一個中間選項,因為它可能是您在特定情況下想要的一個。 我無法“灰顯”文本,因此我將圖重畫大些,只是將M
和C
標記為abandoned
:
o--o------A <-- master
\ \
\ M--C [abandoned]
\ /
o--B <-- origin/master
由於提交C
不再具有指向它的名稱,因此您將不再看到它。 由於C
是讓我們找到M
的唯一路徑,因此您也不會再看到M
了。 不過,我們仍然可以找到提交A
,名稱為master
,提交B
,名稱為origin/master
,所以讓我們將其更簡單地重畫為:
o--o--A <-- master
\
o--B <-- origin/master
看那,我們回到了以前! 現在我們可以運行git merge
重新進行合並。 的合並基礎A
和B
是承諾只是左A
(我標志着一個*
-might是時候把*
背面)。 Git將重新嘗試合並,並以相同的方式以相同的合並沖突失敗。
如果您推送了提交M
和任何后續提交,或者以其他方式將它們提供給其他人,則將通過“撤消”合並來為他們做更多的工作。 在這種情況下,請考慮是否可以。 如果是這樣,您仍然可以這樣做。 如果不是,則將M
(以及您也應該保留的所有后續提交)留在其中。
在這種情況下,您可以使用“分離式HEAD”方法,或創建一個指向A
的新分支:
o--o------A <-- HEAD
\ \
\ M--C <-- master
\ /
o--B <-- origin/master
現在您可以git merge origin/master
或git merge <id-of-B>
重試合並提交,這次可以正確解決它。 進行一次新提交,它將被添加到HEAD
,就像那是一個常規分支一樣(主要是,它只是一個沒有名字的分支!):
o--o------A-----M2 <-- HEAD
\ \ /
\ M-/-C <-- master
\ /_/
o--B <-- origin/master
(圖形變得凌亂,現在沒有很好的繪制方法。)此新合並M2
包含正確合並的文件,因為您這次做的正確。
現在,您可以再次git checkout master
,然后簡單地從合並M2
獲取正確的文件(您可以保存其ID,然后剪切並粘貼,或者可以給它一個分支名稱一段時間,然后刪除該分支) 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.