簡體   English   中英

如何將提交導入分支

[英]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完成。

這是您的HEAD進入的地方

當然,您可以創建多個分支名稱。 每個僅指向一個特定的提交。 現在讓我們創建一個新的分支dev

A--B--C--D   <-- master, dev

注意masterdev 指向提交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上又做了兩個IJ提交,我們現在br1 有人-也許我們,也許有人-從H開始,又做了兩次提交KL 即使其他人在另一個存儲庫中創建了KL也是如此 我們都有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了我們的更改及其更改,從而生成了一個新的合並快照。 我喜歡將合並更改的過程稱為動詞 mergemerge 重要的是要記住,這可以由其他命令完成,因為其他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上做出KL

(請注意,通常,如果我們使用git checkout br2; git merge br1 ,將獲得相同的快照 。合並基數仍然是H ,兩個技巧是LJ ,並且合並工作會產生相同的結果。其他合並的第一個父對象是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具有父EG 合並基數被定義為(松散地-從技術上講,它是DAG中的最低公共祖先)在兩個分支上的最接近的提交,也就是提交G ,因為從I我們可以向后走到H然后是G ,當然也可以是從G我們就在G呆在那里。

在這種情況下, git merge branch2會說已經是最新的 ,什么也不做。 沒錯:他們的承諾是G ,而我們的承諾是II已經有G作為祖先(在本例中為祖父母),因此沒有新的工作可以合並。

我們還可以有以下相關情況:

...--A--B--C--D--E--H--I   <-- branch1
            \      /
             F----G   <-- branch2 (HEAD)

我們在這里運行git merge branch1 這次, 我們的承諾是G他們的承諾是I 合並庫仍然像以前一樣提交G 在這種情況下,Git的默認行為是對自己說: 根據定義,將GG比較的結果是空的。 從定義上說,什么都沒有結合的結果就是某物。 所以我真正要做的就是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

現在,提交KL出現在其存儲庫中,可以通過跟隨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/mastermaster之間創建一個請求。

您可以cherry-pick它:

$ git cherry-pick <commit hash>

暫無
暫無

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

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