簡體   English   中英

我將分支開發合並到主分支,但在主分支上,開發沒有變化

[英]I merged branches develop into master but on master branch no changes from develop

git checkout develop
git pull origin develop
git checkout master
git merge develop
git push origin master

使用這種方法,我將開發合並到分支,但在合並后,我在分支中運行應用程序,並且在應用程序上沒有對分支開發進行更改。

在此處輸入圖像描述

除非我們有權訪問您的特定存儲庫,否則我們無法您的特定存儲庫進行任何具體說明。 不過,總的來說,這種說法:

分支開發沒有變化

是沒有意義的。 原因是 Git 分支沒有變化 事實上,Git 甚至不存儲更改。 相反,Git 存儲提交,並且提交保存快照,而不是更改。 要了解git merge的作用,您必須首先深入了解提交:提交是什么,對您有什么作用,以及您如何獲得對提交的訪問權限。

Git 存儲提交; 提交存儲快照和元數據

任何 Git 存儲庫的核心是一個包含提交的大型數據庫(以及支持所有這些所需的其他內部 Git 對象)。 These commits (and other objects) are numbered, but the numbers aren't simple counting numbers: they don't go commit #1, #2, #3, etc. Instead, each commit or other internal Git object has a hash ID :一大串難看的字母和數字,以十六進制表示一個非常大的數字。 1這些 hash ID 看起來是隨機的,但實際上並非如此:它們實際上是 object內容的加密校驗和(提交的內容,用於提交對象)。

這些數字——hash ID——是 Git 實際找到提交的方式。 hash ID 是鍵值數據庫中的鍵 存儲的數據,必須 hash 回到用於查找它的密鑰,即存儲的 object。 這意味着一旦提交,或任何其他內部 object,就永遠無法更改 如果您從數據庫中取出存儲的 object,對其進行更改,然后將其存儲回來,它會使用一個新的不同的鍵,而舊的 object 將保持在數據庫中,保持不變。 因此,從字面上看,提交是無法更改的。

每個提交包含兩個有用的數據塊。 其中之一,我們稱之為metadata ,因為它是關於提交本身的數據。 這是Git保存提交的人的名稱和 email 地址等內容的地方。 提交的另一個重要部分是主要數據,這是每個文件的保存快照,與您或任何人運行git commit時文件的形式相同。

請注意,這意味着提交中沒有更改 每個提交的數據都是每個文件的完整副本 每個文件的完整副本是只讀的(因為任何提交的任何部分都不能更改)。 為了節省空間,提交中的文件以一種特殊的僅 Git 格式存儲——根本不像普通的計算機文件——這是經過壓縮和重復數據刪除的. 因此,每個提交都可以共享來自先前提交的單個文件數據。 由於這些內容都是只讀的,因此可以安全地共享:沒有人,甚至 Git 本身,都不能更改保存的文件內容數據(它使用與 hash ID 相同的鍵值數據庫作為提交)。

還有另一件重要的事情要知道。 每個提交的元數據包含一個先前提交 hash ID的列表,Git 為該提交存儲了這些 ID。 大多數提交只有一個先前的提交。 2這對您來說意味着提交形成簡單的后向鏈:

... <-F <-G <-H

在這里, H代表鏈中最后一個提交的實際大丑 hash ID。 只要我們知道這個提交的 hash ID——例如,也許我們把它寫在紙上,然后每次我們想要它時再次輸入——我們就可以讓 Git提取這個提交,包括它的數據(快照)和元數據(信息關於提交)。 在元數據中,我們(或 Git)可以找到先前提交G的實際 hash ID。

當然,較早的提交G既有數據又有元數據。 G的元數據包含更早提交F的實際 hash ID,因此 Git 可以從G中找到F F反過來又指向另一個更早的提交,這一直持續到有史以來的第一個提交,然后停止。

每個提交中的文件都被永久存儲(或者只要提交本身持續存在)。 我們可以讓 Git 隨時將它們從提交中復制到普通文件中。 它們以只有 Git 可以使用的形式存儲,沒有其他東西可以更改,但是一旦我們提取它們,我們就可以使用和更改它們。 這個提取過程稱為檢查提交。 3


1目前,可能數字的范圍從 1 到 2 160 -1 或 1461501637330902918203684832716283019655932542975。 這比 10 ^ 48 多一點。 未來范圍會更大,達到 2 256 -1——大約 10 77個可能的數字。

2通常的例外是第一次提交,它不能有任何先前的提交,因此沒有,以及合並提交,它將兩個先前的提交聯系在一起,因此記住每個人的 hash ID。 具有一個先前提交 ID 的提交是普通提交 沒有提交的提交是根提交——從第一次提交開始,通常只有一個提交——根據定義,有兩個或更多的提交是合並提交

3我們實際上在現代 Git 中獲得了幾種不同的提取方式。 在舊版本的 Git 中,存在從舊提交中提取特定文件的問題,但現在git restore存在,我們可以正確完成。 不過,這個答案不會涉及到 Git 的index的更詳細的細節。


分支名稱,以及git log和屏幕截圖如何工作

這個向后看的鏈是允許git log工作的原因。 給定某個起點,或者如果您願意,可以使用多個起點,Git 可以找到這些起點提交。 這些提交指向更早的提交,因此 Git 可以使用這些來查找更早的提交。

不過,要開始,Git 需要一些最后一次提交的 hash ID。 這些將來自哪里? 上面,我建議我們可以在紙上寫下提交H的實際 hash ID。 但如果我們這樣做,然后重新輸入,我們就會出錯。 我們不必這樣做:我們有一台計算機計算機可以為我們記住這些 hash ID。

這就是名稱——分支名稱、標簽名稱和 Git 的所有其他名稱——的用途。 每個都進入第二個數據庫 - 另一個鍵值存儲- 按名稱而不是 hash ID 進行索引。 第二個數據庫中的是 hash ID。 然而,這個數據庫是可寫的:我們可以隨時為每個分支名稱替換hash ID。 object 數據庫只允許添加對象,但名稱數據庫允許我們隨時覆蓋任何名稱。

我們需要謹慎使用這種能力,因為根據定義,無論 hash ID 存儲在分支名稱中,都是我們要稱為“分支的一部分”的最后提交。 在 Git 術語中,分支名稱包含提示提交的 hash ID。 鏈的末端是該分支中的最后一次提交,即使鏈繼續運行:

...--G--H   <-- main
         \
          I--J   <-- feature

在這里,我們有兩個分支名稱, mainfeature 名稱feature定位提交J (通過存儲其 hash ID)。 提交J指向提交I ,后者指向提交H 名稱main位於提交H 提交H點返回提交G ,依此類推。

這意味着通過並包含H的提交都在兩個分支上。 Git 在這里有點奇怪:許多版本控制系統不會做這樣的事情,即一次提交多個分支。 但是 Git 可以,所以如果你要使用 Git,你必須接受它。

從快照中獲取更改

假設我們有這一系列快照:

...--F--G--H   <-- master

最后一個快照,即分支master的尖端,是提交H中的快照。 但是還有更多的快照。 例如,提交G中有一個。

如果我們要求 Git 將提交G (即它的快照)提取到一個位置,並將H提交到另一個位置,會發生什么? 我們可以比較所有這些文件。 其中一些可能完全相同。 在這種情況下,Git 將對文件內容進行重復數據刪除,因此GH實際上只是共享這些文件。 其他的會有所不同: GH擁有這些文件的不同副本。

相同的文件不是很有趣。 讓我們把它們扔掉(也許通過從兩個位置的簽出副本中刪除它們)。 剩下的仍然是快照,但我們現在可以在Spot the Difference游戲中並排比較它們。 無論這些文件發生了什么變化,嗯,這就是提交G和提交H之間的變化。

如果我們要求 Git 或其他查看軟件將提交顯示更改,這就是它的作用。 它“提取”(在內存中)兩個提交,使用存儲提交的內部方式非常快速地丟棄所有確切的重復文件——這很容易,因為早期的重復數據刪除。 然后它比較不同的文件並提出一組指令。 將這些指令應用於較早提交的文件會生成這些文件的后期提交副本。 這是一個差異,這就是我們喜歡查看提交的方式。 但這不是它們的存儲方式:Git 每次我們尋找一個新的差異時都會產生一個的差異。

我們不必比較相鄰的提交。 例如,我們可以比較 commit F與 commit H ,看看從FH的變化。 使用git diff命令,我們可以比較我們喜歡的任何兩個提交 Git 將比較快照並為我們提供將其中一個更改為另一個的秘訣。 如果願意,我們可以向后進行比較,以獲得將H變回G的方法(“反向差異”)。 所有提交都有一個快照,因此您可以選擇任意兩個提交並以這種方式進行比較。

合並是關於合並工作

到目前為止提出的所有概念都不是很難。 當您打開每一個並查看內部時,它們有很多棘手的部分,但概念很簡單。 至少在高層次上理解它們是至關重要的,這樣我們才能跳入git merge的工作原理,以實現真正的合並。

假設我們有以下一系列提交,以兩個分支提示提交結束:

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

我們想運行git checkout br1然后git merge br2 ,這里的想法- 這個操作的目標- 是將兩個分支中完成的工作結合起來 此外,我們希望Git盡可能多地進行這種組合。

現在,提交J只有一個快照,加上通常的元數據。 提交L也是如此。 我們可以使用git diffJ中的快照與L中的快照進行比較,但這不會很好地工作。 假設某些文件在J vs L中有所不同:

This is
quite
a file.

與:

This is
not
a file.

比較這個文件的JL副本只是告訴我們它們是不同的 我們對分支機構所做的任何工作一無所知。

使用提交IK怎么樣? 好吧,如果我們將I中的快照與J中的快照進行比較,這可以告訴我們在br1中所做的事情。 所以這似乎至少好一點。 但是,在提交I中完成的使快照與提交H不同的事情呢? 這也是br1中的“工作完成”。 所以我們最好 go 一直回到H

同時,同樣的 arguments 申請提交LKH 我們需要 go 一直回到H ,然后才能開始看到“在分支br2中完成的工作”。 提交H出現在這兩個中。 提交H有什么特別之處? 對此稍加思考。 再看這張圖:

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

如果您說特別之處在於 commit H兩個分支上,那您是對的。 當然,提交G以及G之前的任何提交也是如此。 我們實際上可以利用這些,但G -vs- H已經“兩個分支”上,通過H中的快照。 因此,沒有理由將 go 進一步退回。 H是停止的正確位置:它是兩個分支上最好的共享提交

Git 將此稱為最佳共享提交合並基礎 像本例一樣,只有一個合並基礎是非常常見的,盡管有兩個或更多的復雜情況。 我們不會在這里擔心這些情況,但如果您想探索所有這些背后的理論,請查看有向無環圖的最低公共祖先 如果您使用 Git 並想查找LCA,請運行git merge-base --all br1 br2例如,查看這些提交 Z0800FC577294C34E0B28AD283943594。 在這種特殊情況下,您將只獲得一個提交 hash 提交H的 ID,因此這是此合並的合並基礎。

現在, Git可以比較HI ,然后IJ 但實際上,它不需要這樣做。 4所以它只是直接比較HJ ,就好像通過運行:

git diff --find-renames <hash-of-H> <hash-of-J>

然后它對H -vs- L做同樣的事情,另一個git diff --find-renames和兩個 hash ID:

git diff --find-renames <hash-of-H> <hash-of-L>

這產生了兩個配方,用於將H中的快照分別更改為JL中的快照。 合並過程現在很簡單:我們查看每個配方中需要完成的操作,並結合任何單獨的更改。 如果H -vs- J什么都不做:

This is
not
a file.

文件,但H -vs- L表示將中間行更改為 read quiet , quite組合就是進行該更改。

Git 將這些組合指令應用於在提交H中找到的快照。 這樣,我們保留了我們在br1上所做的所有工作——請記住,我們從git checkout br1開始——並添加他們——無論他們是誰——在br2上所做的所有工作。


4 Git 處理文件重命名的方式很棘手,實際上可能受益於從合並基礎到分支提示的逐個提交掃描。 不過,Git 目前不這樣做。


進行合並提交

合並(例如通過運行git merge br2執行)是一個相當長且復雜的過程。 我們首先選擇我們想要“打開”的分支,使用git checkout br1

          I--J   <-- br1 (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

這里括號中的HEAD向我們展示了我們“打開”的分支。 我們所做的任何新提交都將在同一分支“上”,並將導致 Git 將提交的 hash ID 寫入分支名稱。

然后git merge命令做了一堆分析。 這包括查找合並基礎提交(在本例中為提交H )並決定是否實際執行計算合並結果的工作。 git merge命令的一些選項可以幫助控制這一點。 在我們的特殊情況下, git merge無論如何都必須進行真正的合並,所以我們不需要任何選項來讓 Git 完成這項工作。 5

這意味着 Git 開始了合並過程,或者我喜歡將其稱為merge 作為動詞 它找到了提交H並確定需要真正的合並來組合H -vs- JH -vs- L 它在內部生成兩個差異,以確定如何進行這種組合。 然后它查看每個要合並的文件以實際進行組合。

如果這里出現問題,Git 將停止合並沖突,讓我們清理混亂。 這樣的合並仍在進行中,但沒有 Git 命令正在運行:它們已將所有內容記錄在文件中並退出。 您現在必須使用單獨的修復命令6來修復每個沖突; 這些記錄你所說的是正確的結果。 錄制完所有內容后,運行git merge --continue以獲取合並以讀取記錄並完成合並。

但是,假設沒有任何問題, Git 將自行完成所有工作組合,並自行完成最終合並提交,無需git merge --continue 結果提交。 像每個提交一樣,它有一個快照和元數據。 唯一特別的是元數據列出了兩個先前的提交。 這就是使它成為合並提交(作為形容詞合並)或合並的原因,使用單詞merge作為名詞。 讓我們畫出結果:

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L   <-- br2

請注意,名稱br1現在像往常一樣指向新提交。 這個新的合並——merge作為名詞——的唯一特別之處在於它不僅指向提交J ,就像任何其他提交一樣,而且指向提交L ,這就是它成為合並提交的原因。 合並為動詞過程使此合並成為名詞/形容詞提交,這讓我們知道br2現在已合並到br1ML作為父級,而br2指向L ,因此不再需要名稱br2並且如果我們願意,可以立即刪除。


5如果合並基礎是當前提交並且另一個分支提示提交“領先於”這個提交, git merge可以並且默認情況下會執行快進操作而不是合並。 您可以強制 Git 在此處使用git merge --no-ff進行真正的合並。 如果合並基礎是另一個提交,並且與此提交相同,或者在此提交“之后”,則無法進行合並,並且git mergeAlready up to date並退出。 如果兩個提交不相關——因此根本沒有合並基礎git merge將抱怨歷史不相關,然后退出; --allow-unrelated-histories選項使其無論如何都進行合並,使用空樹作為假合並基礎。 -n / --no-commit-s / --squash選項防止 Git 進行新的提交,而-s選項使 Git 在合並停止時“忘記”合並。 不過,我們不會在此處詳細介紹這些選項的任何細節。

6這可以包括在工作樹文件上運行您的編輯器並使用git add ,或者您可以使用git mergetool ,如果您喜歡它(我不喜歡),或者您喜歡的任何其他程序。 Git 不會強迫您使用任何特定的方法,但它確實相信您得到正確的合並結果。 它不檢查,它只是假設你所做的一切都是正確的!


合並可以意味着你已經完成了,但並不一定意味着

現在我們有了:

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L   <-- br2

我們可以簡單地刪除名稱br2 分支名稱的目的是找到某個提交(分支的提示提交),如果不需要專門找到L ,我們可以通過從M開始並向后工作來找到它。 但是如果我們願意,我們可以繼續在br2上進行的提交,方法是:

git checkout br2

然后做工作並運行git commit 結果可能如下所示:

          I--J
         /    \
...--G--H      M   <-- br1
         \    /
          K--L--N--O--P   <-- br2 (HEAD)

此時, br2上的提交只能通過從P開始——通過名稱br2並向后工作,所以現在我們不能刪除名稱br2

此時,我們也可以運行git checkout br1然后git merge br2 合並將再次找到最佳共享提交 這一次,這實際上是提交L

合並為動詞的過程將首先比較L -vs- M ,看看“我們”在br1上做了什么,然后比較L -vs- P ,看看“他們”在br2上做了什么。 然后 Git 將嘗試組合這些更改,將它們應用於L中的快照。 這將保留M上的“我們的”更改——我們通過生成M的合並引入的那些——並在P上添加“他們的”更改,如果可行,我們將獲得一個新的合並提交M2Q或任何我們想叫它:

          I--J
         /    \
...--G--H      M--------M2   <-- br1 (HEAD)
         \    /        /
          K--L--N--O--P   <-- br2

此時,我們可以安全地(再次)刪除名稱br2 ,或者保留(再次)我們喜歡的名稱。

結論

合並是關於合並工作——更改——但 Git 不存儲更改。 查找更改的唯一方法是比較一些特定的提交。 這意味着如果你想知道合並會做什么,或者為什么合並會這樣做,你必須比較各種提交:

  • 使用git merge-base --all找到合並基礎。
  • 使用git diff --find-renames基礎和分支提示。

您所看到的這兩組差異是合並的輸入。 為合並提供的任何選項,例如-s ours-X ours ,都會影響更改的組合方式。 請注意-s ours意味着完全忽略他們的更改 不幸的是, git merge在最終合並提交中沒有記錄用於生成合並的任何選項。 (我認為這是一個錯誤:它確實應該在合並提交的元數據中。)

暫無
暫無

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

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