簡體   English   中英

git回到沖突之前的文件狀態

[英]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 fetchgit 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 ,依此類推。 一旦您提交了它們,它們也就變成了“您的”(不是用“作者”或“提交者”來表示,而是因為您現在在存儲庫中有了它們),因此現在您可以使用它們。

繪制提交圖

在進行合並步驟之前,重要的是要有某種方式可視化正在發生的事情。

如果您使用gitkgitg類的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節點是“無聊的”提交,我們無需再次提及。 提交*是特殊的,因為如果您同時從這兩者進行反向工作,則它是masterorigin/master連接的地方。 (如果您向前工作,則它們是分開的。但是,Git始終向后工作,因此如果這樣做也將更加容易。)Commit A是特殊的,因為它是master的尖端提交,而Commit B是特殊的,因為這是origin/master的小費。

我們還要在這里記住,每個提交都具有所有文件的完整快照 (由“索引”組成,這就是為什么在提交之前必須git add文件git add到索引中的原因)。

git merge做什么

當您在master並運行git merge origin/master ,Git會發現兩個提交:當前分支的尖端和您命名的分支的尖端。 這些是AB 然后,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會發生什么。

git pull = git提取&& git merge

我們開始時仍然有這個:

o--o--A---M    <-- master
    \    /
     o--B   <-- origin/master

現在我們運行git fetch 這會發現任何origin新東西,可能什么都不是。 因此,它什么也沒有帶來,並且使origin/master節點指向提交B

現在,我們運行git merge ,告訴它將origin/master (即提交B )與當前的提交 M合並。 這將找到MB合並基礎 ,這是兩個分支上的第一個提交。

第一次提交是……好,看一下圖表,然后向后跟隨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

顯然,從BB並沒有什么區別,因此Git可以跳過所有這一切:將“我們做了什么”(將B更改為M )和“他們做了什么”(什么都沒有)結合起來,使我們有了M 因此git merge根本不執行任何操作。

如果解壓縮舊文件然后提交,會發生什么? 好了,現在您有了:

o--o--A---M--C    <-- master
    \    /
     o--B   <-- origin/master

其中C具有從A提取的文件的版本。 現在我們運行git merge origin/master Git找到CB的合並基礎。 什么是合並基礎? 為什么,又是B 沒有什么可以合並的,而Git則什么也不做。

如何讓Git重新合並

要使Git再次進行合並,您必須使當前的提交成為commit A 為此,您有幾種選擇:

  • 您可以使用指向提交A的新分支名稱。 (使用git branch <newname> <id-of-A> ,然后使用git checkout <newname> ,或等效地,使用git checkout -b <newname> <id-of-A> 。)
  • 您可以使用git reset 放棄 MC提交,以便master指向A (使用git reset --hard ,確保沒有未保存的工作。)
  • 您可以git checkout提交本身,得到一個“分離的HEAD”,其中HEAD指向A

讓我們畫一個中間選項,因為它可能是您在特定情況下想要的一個。 我無法“灰顯”文本,因此我將圖重畫大些,只是將MC標記為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重新進行合並。 的合並基礎AB是承諾只是左A (我標志着一個* -might是時候把*背面)。 Git將重新嘗試合並,並以相同的方式以相同的合並沖突失敗。

什么時候做更復雜的事情

如果您推送了提交M和任何后續提交,或者以其他方式將它們提供給其他人,則將通過“撤消”合並來為他們做更多的工作。 在這種情況下,請考慮是否可以。 如果是這樣,您仍然可以這樣做。 如果不是,則將M (以及您也應該保留的所有后續提交)留在其中。

在這種情況下,您可以使用“分離式HEAD”方法,或創建一個指向A的新分支:

o--o------A   <-- HEAD
    \      \
     \      M--C   <-- master
      \    /
       o--B   <-- origin/master

現在您可以git merge origin/mastergit 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.

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