[英]How do I import a commit into a branch
我有一個git commit發生在某個分支中,現在主服務器沒有它。 如何導入對母版的提交?
希望根據需要通過合並請求使用合並。
Git沒有拉取請求。 1 Git Hub具有拉取請求,其他使您可以將Git用作服務的Web托管提供程序也是如此。 這些服務為它構建了一個花哨的,更用戶友好的界面,有幾種底層的Git機制。
您具體詢問的是git merge
。 在不了解發生了什么的情況下,可以使用此命令和/或GitHub等提供的精美Web界面。 我自己,這不是一個好主意。 但是,要了解實際情況 ,需要先“掌握”一些基本概念。
其中之一與術語分支本身的含義有關:請參閱“分支”到底是什么意思? 當有人說“分支”時,有時是指分支名稱(或者Git的各種其他名稱中的一個都不是分支名稱),有時是指一系列模糊定義的提交 。
同樣,使用GitHub上的pull request接口不需要所有這些。 要使用它,您只需單擊一些Web按鈕。 但這是一個好主意-至少在我看來-要知道這些按鈕會發生什么,為此,您確實需要了解Git的工作方式。 因此,讓我們深入研究細節。
1這樣,我的意思是沒有git pull-request
命令。 有一個git request-pull
命令,它的作用是產生一個電子郵件消息。 這就是Git支持拉取請求的程度:它有一個命令來生成電子郵件,要求別人做某事。
Git提交是快照中所有文件的快照狀態,並帶有一些描述快照的元數據 。 (從技術上講,提交是間接表示快照的,因為快照是作為樹對象單獨存儲的,但是在大多數情況下,您不需要知道這一點:從提交到快照之間存在單向鏈接,因此,給定提交Git總是可以找到快照。這種鏈接意味着多個不同的提交可以代表同一個快照而無需占用兩次空間,這對於多個目的很有用。
每個提交都由哈希ID標識,例如b5101f929789889c2e536d915698f58d5c5c6b7a
。 這些事情是大而丑陋的,對於人類來說是不可能的,但是它們是Git 找到提交的方式,因此它們對於Git的操作至關重要。 任何一個特定提交的哈希ID始終是唯一的:該哈希ID是該提交,沒有其他哈希。 其他所有提交都具有不同的哈希ID。 而且, 宇宙中所有地方的所有Git都同意這些哈希ID計算。 給定兩個不同的Git存儲庫,如果它們都有一些提交ID H(即,它們都有一個哈希為H的提交對象),則該對象的內容必須相同。 2
提交中的元數據包括您的姓名(或進行提交的人的姓名)和電子郵件地址,以及提交時間的時間戳。 它包括您的日志消息,告訴每個人您為什么進行此提交。 但它還包括緊接在此提交之前的提交的哈希ID。 我們稱該提交為父 。 結果是一個向后看的鏈。 如果我們有一個只有三個提交的小型存儲庫,則可以這樣繪制:
A <-B <-C
在這里, C
是最后的提交。 它具有一些獨特的,丑陋的哈希ID。 它還存儲提交B
的唯一哈希ID,因此一旦找到C
,就可以使用它來找到B
同時B
的哈希ID為A
,因此我們可以使用B
查找A
由於A
是第一個提交,因此它根本沒有父提交(從技術上講它是一個根提交),這使我們不再向后走。
要了解提交以及具有哈希ID的所有Git對象的另一件事,就是您永遠無法更改其中的任何一個。 原因是哈希ID是對象內容的加密校驗和。 如果您要提交一個提交對象並只更改一個位(例如,修改日志消息中單詞的拼寫),您將最終得到一個新的,不同的commit和一個不同的哈希ID。 因此,一旦提交,就永遠了:立即獲取哈希ID 。 3
這對我們意味着什么,我們不需要將提交中的箭頭繪制為箭頭。 提交一旦存在,它將是永久的,並且與其父級的聯系也將是永久的。 我們只需要記住,它們只走了一條路:倒退。 新的鏈接會出現這種承諾,但他們不能出現此承諾的任何地方去新。
2請注意,僅在遇到並交換對象的兩個Git之間維護相同的哈希ID表示相同的基礎對象內容的要求。 只要兩個從未連接的存儲庫從不嘗試相互通信, 就可以進行此類doppelänger提交。
3您可以完全刪除某個提交(如果有些痛苦的話),只需不再引用它即可。 最終,底層的Git對象消失了,有效地釋放了哈希ID。 由於當前的哈希ID系統中有160位,因此在任何Git存儲庫中只有2 160個可能的對象。 幸運的是,這足夠了。 但是, 信鴿原理與生日悖論相結合仍然引起了一些有趣的理論問題, 新發現的SHA-1碰撞對Git有何影響? 有討論。
給定上述存儲庫,如果我們知道提交C
的哈希ID,則可以找到所有提交。 我們將其存儲在哪里? 怎么樣: 在分支名稱中? 讓我們選擇一個像master
這樣的名稱,並用它來寫下C
的哈希ID:
A--B--C <-- master
現在,通過檢出提交C
並以通常的方式做一些工作來進行新的提交:
git checkout master
... do some work ...
git add ... various files ...
git commit
新的提交將打包一個新的快照,添加我們提供的任何日志消息,添加我們的名稱和電子郵件地址以及時間戳,並且至關重要的是,將新提交的父級設置為提交C
:
A--B--C--D
作為提交的最后一步, git commit
將采用新提交的哈希ID(無論實際校驗和是什么,現在所有部分都永遠固定在適當的位置),並將該校驗和寫下*名稱master
:
A--B--C--D <-- master
這就是分支名稱:在這里存儲最后一次提交的哈希ID。 僅僅人類不必記住哈希ID,因為Git可以為我們記住它們。 我們記得只有master
擁有最新提交的哈希ID,其余的事情要由Git完成。
當然,您可以創建多個分支名稱。 每個僅指向一個特定的提交。 現在讓我們創建一個新的分支dev
:
A--B--C--D <-- master, dev
注意master
和dev
都指向提交D
,並且所有四個提交都在兩個分支上 。 但是Git需要一種方法來知道我們進行新提交時要更改的名稱 。 這是特殊名稱HEAD
出現的地方。我們讓Git將此名稱附加到一個(並且只有一個)分支名稱:
A--B--C--D <-- master (HEAD), dev
要么:
A--B--C--D <-- master, dev (HEAD)
我們使用git checkout
進行此操作,它不僅檢出提交,而且還附加了HEAD
。 如果HEAD
附加在master
並且我們進行了新的提交E
則它看起來像這樣:
E <-- master (HEAD)
/
A--B--C--D <-- dev
如果現在我們將HEAD
切換到dev
(通過執行git checkout dev
)並進行新的提交F
,則得到:
E <-- master
/
A--B--C--D
\
F <-- dev (HEAD)
假設我們有一個帶有一堆提交的存儲庫,其中最后幾個看起來像這樣:
I--J <-- br1 (HEAD)
/
...--H
\
K--L <-- br2
在這里,我們有一系列的提交以哈希為H
結尾,其中H
持有一些快照。 然后有人(也許是我們)在分支br1
上又做了兩個I
和J
提交,我們現在br1
。 有人-也許我們,也許有人-從H
開始,又做了兩次提交K
和L
即使其他人在另一個存儲庫中創建了K
和L
,也是如此。 我們都有H
,並且由於各地的所有Git都同意哈希ID計算,因此我們都從同一commit開始 。
git merge
命令將執行的操作是弄清楚我們在分支br1
更改了什么 , 以及它們在分支br2
更改了什么 。 然后它將合並這些更改。 但是我們已經注意到,“ 分支 ”一詞往往含糊不清。 Git在這里真正要做的就是找到常見的提交 ,這是我們倆都從那里開始的。 我們已經看到這是提交H
因此,提交H
是合並操作的合並基礎 。 另外兩個有趣的提交只是由當前分支命名的一個提交-在br1
的尖端提交J
,而另一個分支命名的一個提交在br2
的尖端提交L
所有三個提交都是快照 ,因此Git需要對其進行比較 :
git diff --find-renames hash-of-H hash-of-J
找到我們在br1中所做的 git diff --find-renames hash-of-H hash-of-L
找到他們在br2中所做的 Git現在可以將這兩組更改組合在一起 。 如果我們更改了某些文件而沒有更改,則Git應該獲取我們的新文件。 如果他們更改了某些文件而我們沒有更改,則Git應該獲取其新文件。 如果我們都更改了同一文件,則Git應該從合並基礎提交H
本身的文件副本開始, 合並兩個不同的更改,然后將合並的更改應用於該文件。
因此,在這種情況下,這就是git merge
作用:它git merge
了我們的更改及其更改,從而生成了一個新的合並快照。 我喜歡將合並更改的過程稱為動詞 merge或merge 。 重要的是要記住,這可以由其他命令完成,因為其他Git命令可以做到! 合並或合並為動詞時使用Git的合並引擎來合並工作。
不過,在這種情況下, git merge
現在繼續進行合並提交 。 這幾乎只是一個普通的提交:與其他任何提交一樣,它具有快照和日志消息等。 使它與眾不同的是合並提交,它具有兩個父提交而不是通常的一個。 第一個父級與通常的父級相同:這是我們在運行git merge
時簽出的提交。 第二個父對象就是另一個提交-我們通過使用名稱br2
選擇了一個提交,在這種情況下,就是提交L
因此,現在git merge
進行合並 (合並為名詞)或合並提交 (合並為形容詞),如下所示:
I--J
/ \
...--H M
\ /
K--L <-- br2
我們的分支名稱會怎樣? 當然,一如既往。 Git將此新合並提交M
的新哈希ID寫入當前分支名稱:
I--J
/ \
...--H M <-- br1 (HEAD)
\ /
K--L <-- br2
這就是我們合並某人的提交的br2
,在這種情況下,某人是在br2
上做出K
和L
。
(請注意,通常,如果我們使用git checkout br2; git merge br1
,將獲得相同的快照 。合並基數仍然是H
,兩個技巧是L
和J
,並且合並工作會產生相同的結果。其他合並的第一個父對象是L
,而不是J
,因此交換父對象,並且最終的名稱更新將更新名稱br2
而不是br1
,但是,如果我們開始添加額外的合並選項,例如-X ours
或-X theirs
,更多的東西可能會有所不同。)
git merge
命令都會導致合並 值得一提的是這里還有另外兩個皺紋。 假設我們有這個圖:
...--A--B--C--D--E--H--I <-- branch1 (HEAD)
\ /
F----G <-- branch2
然后我們運行git merge branch2
。 我們之前已經在提交H
合並了branch2,提交H
具有父E
和G
合並基數被定義為(松散地-從技術上講,它是DAG中的最低公共祖先)在兩個分支上的最接近的提交,也就是提交G
,因為從I
我們可以向后走到H
然后是G
,當然也可以是從G
我們就在G
呆在那里。
在這種情況下, git merge branch2
會說已經是最新的 ,什么也不做。 沒錯:他們的承諾是G
,而我們的承諾是I
, I
已經有G
作為祖先(在本例中為祖父母),因此沒有新的工作可以合並。
我們還可以有以下相關情況:
...--A--B--C--D--E--H--I <-- branch1
\ /
F----G <-- branch2 (HEAD)
我們在這里運行git merge branch1
。 這次, 我們的承諾是G
而他們的承諾是I
合並庫仍然像以前一樣提交G
在這種情況下,Git的默認行為是對自己說: 根據定義,將G
與G
比較的結果是空的。 從定義上說,什么都沒有結合的結果就是某物。 所以我真正要做的就是git checkout hash-of-I
。 因此,我將這樣做,但是同時,使名稱branch2
指向I
也要提交。 結果是:
...--A--B--C--D--E--H--I <-- branch1, branch2 (HEAD)
\ /
F----G
Git將此稱為快速操作 。 Git有時將其稱為快速合並 ,這不是一個好術語,因為沒有實際的合並。
您可以強制Git進行真正的合並-將G
與自身進行比較,不將任何東西與任何東西合並,然后進行真正的合並提交-給出:
...--A--B--C--D--E--H--I <-- branch1
\ / \
F----G------J <-- branch2 (HEAD)
要在此處強制進行真正的合並,請使用git merge --no-ff branch1
。
(有時候你想要或者需要一個真正的合並,有時快進,反而是OK。對於它的價值,GitHub的虛擬主機界面上的clicky按鈕不允許或執行快進合並,即使你希望他們實際上,他們總是使用git merge --no-ff
。)
拉取請求,甚至是Git更原始的git request-pull
選項,僅在該流程涉及多個Git存儲庫時才有用。
在這種情況下,我們可能在存儲庫#1中具有一系列提交:
I--J <-- master
/
...--H
同時,在存儲庫#2中,我們有:
...--H
\
K--L <-- master
由於這是兩個不同的存儲庫,因此它們有自己的專用分支名稱 。 一個擁有它的主人控股哈希ID I
。 另一個具有其主持有哈希L
。 提交H
在兩個存儲庫中,而提交IJ
僅在#1中,而KL
僅在#2中。
如果我們要以某種方式合並兩個存儲庫,同時更改名稱以免它們沖突,我們將回到常規的合並狀態:
I--J <-- master of Repository #1
/
...--H
\
K--L <-- master of Repository #2
這正是GitHub通過clicky Web界面所做的事情 。 無論您是誰-#1或#2; 讓我們選擇#2的具體性-您告訴GitHub: 我希望他們合並我的主人,即提交 L。GitHub然后將您的提交(逐位復制 ,以使它們的哈希ID保持不變) 復制到其存儲庫中,將提交L
的哈希ID放在不是 master
且不是任何其他分支名稱的特殊名稱下。 4然后,他們在GitHub上運行git merge
,使用這種完全不是分支名稱的特殊名稱。 如果一切正常, 那么他們會告訴控制倉庫1的任何人您的拉動請求。
現在,控制存儲庫1的任何人都可以單擊“合並提取請求”按鈕。 這需要GitHub已經完成5的合並,並在其GitHub存儲庫中適當地移動其 master
或任何分支名稱:
I--J
/ \
...--H M <-- master
\ /
K--L
現在,提交K
和L
出現在其存儲庫中,可以通過跟隨master
的第二個父級向后鏈接來訪問。
對於想要發出拉取請求的人來說,這對您意味着意味着您必須安排GitHub上的存儲庫具有GitHub可以為您測試合並的提交或提交鏈。 然后,GitHub將向該存儲庫的所有者提出請求,該所有者只需單擊一下即可完成更新,方法是更新其分支的名稱以使用GitHub進行的測試合並。
提交以及測試合並結果由您放入GitHub存儲庫中的提交確定。 如果您在本地計算機上擁有自己的單獨存儲庫,則可以將提交放入其中,並使用git push
將這些提交發送到 GitHub存儲庫。
顯然,這有點令人費解—但是,如果使本地計算機的存儲庫和自己的GitHub存儲庫保持同步,以使它們始終“看起來相同”,則可以忽略此處的額外層。 忽略這一層的問題在於它仍然存在! 如果您讓您的存儲庫和GitHub存儲庫不同步,則會再次顯示出來。
4發出拉取請求時,GitHub為其分配一個唯一編號(對於目標存儲庫而言唯一)。 說這是拉取請求#123。 將提交復制到GitHub存儲庫后,GitHub用於提交的名稱為refs/pull/123/head
。 GitHub用於它進行的測試合並的名稱是refs/pull/123/merge
。 如果測試合並因沖突而失敗,則GitHub不會最終建立一個,也不會創建第二個名稱。
5如果控制PR目標存儲庫的任何人將新提交推送到其分支,則GitHub進行的測試合並將變為無效(這是“過時的”)。 GitHub將在適當的時候進行新的測試合並。 我不確定他們是否刪除refs/pull/123/merge
之間的名稱,因為我從未測試過。
如何通過請求請求做到這一點
(但請避免將來自功能分支的任何其他提交合並到master
,例如(feature-branch >> master)拉取請求可能會引起的,您可以執行以下操作)
1)直接在遠程(github?bitbucket?other?您沒有提到但隨時發表評論,我會進行調整)的master
上創建一個名為hotfix/master
的新分支(例如),然后從本地獲取來自遠程的最后裁判:
git fetch
2)在這里,提取輸出應包含您新創建的分支,因此讓我們創建其本地副本:
git checkout hotfix/master
3)現在,我們導入您想要的提交:
git cherry-pick <commitHash>
或者,如果您想要的提交是功能分支的最后提交,則只需:
git cherry-pick <branchName>
4)解決在此級別上可能發生的任何沖突,並將分支推送到remote:
git push origin hotfix/master
5)最后,回到遠程接口,在其中創建請求請求,並在hotfix/master
和master
之間創建一個請求。
您可以cherry-pick
它:
$ git cherry-pick <commit hash>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.