簡體   English   中英

git checkout --merge/--ours/--theirs 似乎在做同樣的事情(錯誤?)?

[英]git checkout --merge/--ours/--theirs seem to be doing the same (wrong?) thing?

我正在嘗試從另一個分支合並(如果重要的話,它是一個孤立的分支)。 但是,當我執行以下操作時:

git merge <branch-name>

它似乎正確合並。 但是,如果我這樣做:

git checkout --merge <branch-name> -- <file-names>

當前分支上的大部分(如果不是全部)更改都會被清除。 不管我使用--merge--ours還是--theirs ,結果都是一樣的。

我原以為使用--merge標志時的checkout會做與merge相同的事情,除了指定的文件。

這是怎么回事? 有什么我不明白的嗎?

長話短說

請參閱git merge-file命令,它允許您做您想做的事。

git checkout-m--merge標志有多種不同的含義。

搭配使用時:

git checkout -m <commit-specifier>

它或多或少具有您想要的含義; 問題是它適用於所有路徑。

搭配使用時:

git checkout -m [--] <paths>

它有不同的含義:這意味着 Git 應該,對於<paths>中的每個命名路徑,在具有(或具有)多個更高階段索引條目的文件的工作樹副本中重新創建合並沖突。


這里有一個更根本的問題。 其中一部分只是棘手的措辭——例如,我們都說“工作樹的變化”——但另一部分在於如何思考 Git 的作用:

...當前分支上的大部分(如果不是全部)更改都被清除了

這表明您正在考慮每個文件的工作樹副本中的內容作為更改,但實際上並非如此。 Git 不會在任何地方存儲更改, 1文件的工作樹副本主要供您根據需要使用:Git 主要使用快照,文件以我喜歡稱之為凍干格式的blob 對象存儲與提交相關聯,並在索引中。

當前分支當前提交的概念,但分支只是一個名稱(存儲在HEAD中),而提交是一個提交對象,由其哈希 ID 標識(存儲在分支名稱中),永久(大部分)和不可變的(完全)。 提交包含——間接地——每個源文件的完整快照。 索引在 Git 中也是至關重要的,它也存儲快照,但與提交不同的是,索引中的內容是可變的。

同時,每個提交都存儲了一組提交的哈希 ID——通常只有一個這樣的提交。 當您讓 Git 向您顯示一些提交時,Git 實際上會從父提交提交本身中提取所有文件, 2然后比較兩個提交(中的所有文件)並向您顯示不同之處。 因此,當您查看提交時,它似乎有更改。

Git 對索引執行相同的操作:它將當前提交與索引進行比較,向您顯示差異並將這些更改暫存為提交 然后它會將索引(本質上是您提議的快照,如果您現在運行git commit ,則將下一次提交)與工作樹進行比較。 無論索引和工作樹之間有什么不同,Git 都會顯示這些差異,將那些更改稱為 not staged for commit 但是在所有三組文件中——提交的文件、索引中的文件和工作樹中的文件——實際上沒有變化,而是快照

git checkout通常做的事情——有很多例外,因為git checkout實際上是多個不同的命令都塞進了一個面向用戶的動詞——是從提交快照中提取文件,將這些文件寫入索引(以便索引和提交匹配),然后將索引副本寫入工作樹(以便索引和工作樹匹配)。 但在執行任何操作之前,它首先檢查以確保您不會丟失任何未保存的工作,方法是將當前提交與索引進行比較,並將索引與工作樹進行比較:如果這兩者不匹配,則git checkout會破壞某些東西。

但是,一旦您使用git checkout -- <paths>模式,您實際上就會切換到一個完全不同的后端操作。 此操作不是從提交開始,而是從索引開始。 這些文件在過去的某個時間從提交復制索引,因此索引有一些文件集。 自上次正常檢出或硬重置或其他任何操作以來,該集合可能已經更新:每個git add都意味着將文件從工作樹復制到索引中,如果工作樹文件與索引副本不匹配,那么,現在它這樣做了,索引中的文件集已經改變了。 該索引甚至可能具有非零階段條目,這表示來自不完整的git merge的持續合並沖突。 在這種情況下,索引實際上存儲的不是一個,而是一些文件的三個凍干副本,從三個輸入到更早的git merge操作。 3但是,不管怎樣,這種git checkout根本不會返回提交:它只是從索引中獲取文件並寫入它們,或者 for -m重新合並它們,並破壞目錄中的任何內容工作樹。 沒有先詢問是否可以就這樣做了。 4個

(編輯:還有git checkout --patch ,但這實際上調用了第三種模式。實際上處理了 patch 操作,它比較文件的兩個版本並允許您選擇此差異的部分以應用於兩個版本之一通過在兩個版本之間運行git diff的 Perl 程序。這實現了git checkout --patchgit add --patchgit stash --patchgit reset --patch 。)

無論如何,底線是git checkout -m -- path沒有做你想做的事。 可以得到你想要的,但不使用git checkout 相反,您需要做的是提取要傳遞給git merge的三個輸入文件——將這三個文件放在任何地方; 它們甚至不需要位於存儲庫本身的工作樹中——然后對它們運行git merge-file命令


1好吧,除非您存儲git diff的輸出,或者作為特殊情況,保存的合並沖突的每個部分都來自git rerere ,但所有這些都低於正常的可見性水平。

2由於內部凍干文件格式,Git 實際上不必費心提取相同的文件,只需提取至少有一點不同的文件即可。

3從技術上講,每個文件最多三個條目。 例如,在修改/刪除沖突等情況下,某些文件只有兩個條目。 此外,當您完成解決合並沖突並git add文件時,較高階段的條目會消失。 但是,在您提交之前,那些更高階段的條目將存儲在一個秘密的、不可見的“REUC”類型的索引條目中,這樣您就可以使用git checkout -m來恢復沖突。 沒有辦法查看或保存這個不可見的條目,這是當前索引格式中的幾個缺陷之一。

4從用戶友好設計的角度來看,這是特別糟糕的,因為一種形式的git checkout非常小心不要丟失工作。

暫無
暫無

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

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