主頁 > 作業系統 > 是否可以在不污染先前的拉取請求的情況下提交分叉?如果沒有,我如何將主要變成分支?

是否可以在不污染先前的拉取請求的情況下提交分叉?如果沒有,我如何將主要變成分支?

2022-02-20 04:18:39 作業系統

參考:大約 9 年前的以下問題:
Pull request without forking?

背景:
我傾向于使用 GitHub/Git,但遇到了一些問題。我已經努力搜索,但沒有找到任何解決這個特定問題的東西 - 我發現的最接近的是上面提到的問題。

問題:
我“分叉”了一個打算做一些作業的存盤庫,對我自己的分叉進行更改,然后創建一個拉回原始專案的拉取請求,作為對其做出貢獻的一種方式。

我終于想通了,并且能夠成功地創建一個包含我提議的更改的拉取請求。

請注意,我還想做其他事情來為這個專案做出貢獻,在我創建拉取請求后,我繼續作業并對我的本地副本進行了額外的提交,包括匯入一些技術檔案等。

顯然,無論出于何種未知原因,在我提出拉取請求后,拉取請求“擁有”我對原始倉庫的分叉,此后我所做的任何事情都成為該拉取請求的一部分——它是否相關并不重要,我是否將其推送到專案的分支,是否將其添加到 PR 或其他任何內容。它看起來就像魔法一樣,只有在我洗掉/恢復我自己的存盤庫分支中的更改時才能被洗掉。

這是否意味著與該專案有關的所有作業都必須完全停止,直到該 PR 被接受和/或拒絕?如果是這樣的話,其他人,尤其是在單一代碼庫上作業的公司,如何設法完成作業?

當然,我確信這是可能的,人們一直都在這樣做。

我所做的研究沒有披露任何似乎解決這個特定問題的東西,但是對不同問題的其他答案似乎暗示這樣一個事實,即一旦你分叉一個 repo 并創建一個拉取請求,拉取請求似乎“擁有“您本地回購的那個實體 - 減輕這種情況的唯一方法是:

  • 分叉回購。
  • 創建 repo 的整個分支并開始作業。
  • 提交到該分支并創建拉取請求,然后放棄該分支

要完成額外的作業,無論專案在哪里,您都必須:

  • 創建一個全新的分支
  • 做任何你想做的作業,這些作業應該與原始作業分開。
  • 提交到新分支,創建拉取請求,然后放棄分支。

對于您想要做的任何額外作業,“沖洗并重復”,最終擁有比圣誕樹更多分支的叉子。

這引發了幾個問題:

  1. 這是真的?我理解正確嗎?
  2. 為什么?這似乎是不必要的復雜和令人費解的,尤其是對于單個貢獻者。

最后一個也是最重要的問題:

3. 如何清理我的本地副本?顯然我應該克隆存盤庫,然后創建一個分支來作業,然后創建拉取請求。有沒有辦法把我更新的“main”變成一個分支,然后重新創建原始的main,這樣我就可以創建額外的分支來做額外的作業?)

我不愿僅僅“破解”現有的 repo 試圖弄清楚事情,因為我不想污染原始的拉取請求或搞砸上游專案。

謝謝!

uj5u.com熱心網友回復:

注意:這很長,但你真的需要知道這些事情。我已經用完了空間(字符數限制為 30k),所以我將把它分成兩個單獨的答案。第 2 部分在這里

雖然“拉取請求”不是 Git 的一部分(它們特定于 Git Hub ?1),但即使沒有專門提到 GitHub,我們也可以對它們說一些話。然后我們可以稍后插入 GitHub 特定的專案。所以讓我們從這個開始:

  • Git 是關于提交的。雖然 Git 提交包含檔案,但 Git 并不是關于檔案,而是關于提交。而且,雖然我們使用分支名稱來查找提交,但 Git 也不是關于分支名稱:它實際上只是關于提交。

  • 這意味著您需要了解有關提交的所有資訊:一個是什么以及每個提交以及連續的一串提交可以為您做什么。

因此,我們將從一個提交的快速概述開始,然后連續查看它們的字串。


1 Bitbucket 也有“拉取請求”,但它們略有不同,GitLab 有“合并請求”,它們又是相同但不同的。所有這些都建立在 Git 本身的相同基礎支持之上。


提交

每個 Git 提交都有編號。但是,這些數字不是簡單的連續計數數字:我們沒有提交 #1 后跟 #2 和 #3 等等。取而代之的是,每個提交都會獲得一個唯一的哈希ID——在所有存盤庫中都是唯一的,即使它們與你的存盤庫完全不相關2——似乎是隨機的,但實際上并非如此。3 哈希 ID 又大又丑,而且人類無法使用:計算機可以處理它們,但我們虛弱的大腦會變得混亂。?? 所以,下面,我將使用假哈希 ID,我只使用一個大寫字母來代替真正的哈希 ID。請注意,要使這些哈希 ID 起作用,提交的每個部分都必須是完全只讀的. 也就是說,一旦您做出新的提交,該提交將永遠凍結在時間上。那個特定的哈希 ID,無論它得到什么哈希 ID,都用于那個提交,并且沒有其他提交——過去、現在或將來——可以使用那個哈希 ID。

在任何情況下,每個 Git 提交都會存盤兩件事:

  1. 提交存盤每個檔案的完整快照(無論如何,Git 在您或任何人制作它時就知道)。為了防止存盤庫變得非常臃腫,這些檔案被 (a) 壓縮和 (b)重復資料洗掉因此,它們以只有 Git 可以讀取的格式存盤,沒有任何東西,甚至是 Git 本身,都無法覆寫。正如我們將看到的,這解決了一些問題,但產生了一個大問題。

  2. 提交還存盤一些元資料,或有關提交本身的資訊。這包括,例如,提交人的姓名和電子郵件地址(來自他們的user.nameuser.email設定,他們可以隨時更改,因此如果沒有驗證它是不可靠的,但它仍然有用)。它包括一條日志訊息:當您為自己的提交提供一個日志訊息時,您應該寫下您為什么提交的解釋。 所做的——比如將一個實體從 7 更改為 14——Git 可以自己顯示,但你為什么將 7 更改為 14?是從幾周到兩周,還是因為 7 個小矮人都被克隆了?

在提交的元資料中,Git 出于自己的目的添加了先前提交的原始哈希 ID 串列。這個串列通常只有一個元素長:對于合并提交(我們不會在這里介紹),它有兩個元素長,并且任何非空存盤庫中的至少一個提交是第一個提交,其中沒有任何以前的提交,所以這個串列是空的。


2這就是為什么哈希 ID 必須如此龐大和丑陋的原因。嚴格來說,它們不必在兩個永遠不會相遇的存盤庫中是唯一的,但是 Git 不知道兩個存盤庫將來是否或何時會相遇,以及兩個不同的提交是否具有相同的哈希 ID那個時候,壞事就發生了。我稱這種行為為分身,一種邪惡的雙胞胎,預示著災難。真正的災難是——或者至少應該是——只是這兩個 Git 存盤庫的會議失敗了。在一些非常舊的 Git 版本中,由于錯誤,實際上確實發生了更糟糕的事情。無論如何,它根本不應該發生,散列的大小有助于避免這種情況。

3當前哈希是提交中所有資料的 SHA-1 校驗和,其中包括有關導致提交的提交的資料,因此它是導致該點的整個歷史記錄的校驗和。 SHA-1 不再是加密安全的。盡管這本身并沒有破壞 Git,但 Git正在轉向 SHA-256。


提交鏈

鑒于上述情況,我們可以在一個很小的三提交存盤庫中繪制三個提交,如下所示:

A <-B <-C

CommitC是我們的第三次也是迄今為止最新的一次提交。它有一些看起來很隨機的哈希 ID,以及所有檔案的快照。in 中的 一兩個檔案C可能與較早 commit 中的所有檔案不同,B其余檔案與 in 相同,B因此實際上與較早 commit 共享B所以他們不占用任何實際空間修改后的檔案確實占用了一些空間,但它們被壓縮了——有時非常壓縮——并且可能幾乎不占用任何空間。提交元資料有一點空間(順便說一句,它也被壓縮了),但總的來說,這個每個檔案的完整快照可能不會占用太多空間。

同時, commitC包含較早 commit 的原始哈希 ID B我們說那C 指向 B這意味著如果 Git 可以找到 C——我們稍后會看到它是如何做到的——Git 也可以使用哈希 ID進行 C查找B然后,Git 可以從兩個提交中提取兩個快照中的所有檔案,并進行比較。比較檔案的結果是一個差異:將檔案更改為檔案的說明BC反之亦然,如果您以其他順序完成差異)。

Git 和 GitHub 等網站通常會將提交顯示為差異,因為這通常比顯示原始快照更有用。但是,如果您愿意,您可以輕松地獲取快照:對于 Git 而言,有時這比獲取差異更容易。(由于重復資料洗掉技巧,git diff可以快速跳過相同的檔案,但它仍然必須查看兩個提交,而不僅僅是一個。所以它有點混合,哪個更容易。)

CommitB作為一個提交,既有快照又有元資料,并向后指向更早的提交A但是提交A是第一次提交,所以它的元資料沒有列出任何更早的提交。這意味著根據定義,其快照中的所有檔案都是新的。(它們將針對任何其他提交中的任何檔案進行壓縮和重復資料洗掉,但那時,這是第一次提交,因此它們僅對自身進行壓縮和重復資料洗掉。這最后意味著,如果第一次提交包含一個大檔案的 100 個相同副本,在 commit 中實際上只有一個A副本。)

分支名稱和其他名稱

Git 需要一種快速的方法來找到某個鏈中的最后一次提交。Git 可以強迫我們——使用 Git 的人——寫下最后一次提交的哈希 ID,在這種情況下C我們可以將其保存在紙上、白板或其他東西上。但這很愚蠢:我們有一臺電腦為什么不讓計算機將這些哈希 ID 保存在檔案或其他東西中?事實上,為什么不讓Git我們保存最新的哈希 ID

這正是分支名稱的含義:保存最新提交的哈希 ID 的地方。Git 只需要最新的,因為最新的指向第二個最新的,它又指向更早的一個,依此類推。這會盡可能地持續下去,只有在沒有更早的提交時才結束,這就是Git 的作業方式:它從我們告訴它的提交開始——通常是分支名稱——然后向后作業。

讓我們畫一個簡單的以散列 ID 結尾的提交鏈H(對于散列),并讓分支名稱main指向(包含的散列 ID)H

...--G--H   <-- main

現在讓我們添加一個新名稱,例如feature1. 這個名字必須指向一些現有的提交。我們可以選擇G, 或H, 或一些較早的提交,但選擇似乎很自然,H因為它是我們最新的:

...--G--H   <-- feature, main

請注意,Git 有很多種名稱——不僅僅是分支名稱——而且它們都做這種事情,即指向一個提交。所以我們可以創建一個指向 commit的標簽H,例如:

...--G--H   <-- feature, main, tag: v1.0

不過,大多數情況下,我們只會使用分支名稱,這就是我現在要在這里展示的全部內容。

在分支上作業

Git 有它自己的特殊功能可以讓我們作業正如我們之前提到的,提交快照的內容一直被凍結,并且只能由 Git 本身讀取。所以我們實際上不能處理/處理提交中包含的這些檔案。我們必須讓 Git在某處提取檔案。那個“某處”是我們的作業樹作業樹

Git 還有一個很重要的東西,Git 給它起了三個名字:索引暫存區,有時還有快取我們不會在這里討論,除了要注意,當你運行時git commit,Git 實際上是從 Git 的 index / the-staging-area 中的檔案而不是作業樹中的檔案中進行新的提交 所有要提交的檔案都必須在暫存區:這些是 Git 知道的檔案。提取提交會將提交的檔案復制暫存區域以及作業樹,以便它們從那里開始。

無論如何,一旦檔案在您的作業樹中,它們就只是您計算機上的普通檔案。它們不再Git 中了。它們來自Git (來自提交),您可以稍后新的提交中將它們放回Git 中,但是當您作業時,您會處理和處理不在 Git 中的檔案。只有提交的檔案在 Git 中。

您使用作業樹檔案完成作業并git add照常運行。(這會將您列出的檔案的作業樹版本復制回索引中,以便它們準備好提交。在git addGit 進行初始壓縮和重復資料洗掉的階段。在 Git 的索引中看到的檔案是換句話說,預先去重。這意味著索引的副本大多不占用空間,除了您更改和添加的任何檔案。您可以添加未更改的檔案:這只是對 Git 的輕微浪費時間會發現是復制的,只保留原版。浪費了廉價的計算機時間,而不是寶貴的人力時間,所以隨意浪費吧!你的時間也是,隨意跳過它。)

無論如何,既然您的新提交已準備好,您可以運行git commit. 這:

  • 收集任何必要的元資料,例如您的姓名和電子郵件地址以及當前日期和時間;
  • 獲取當前提交的哈希 ID — 您之前簽出以填充作業樹(和 Git 的索引)的哈希 ID;
  • 凍結索引的快照;
  • 將所有這些寫成一個新的提交,它會獲得一個新的、唯一的哈希 ID。

如果你有:

...--G--H   <-- feature, main

就在剛才,你當前的提交是H,所以你的新提交——我們稱之為——I指向H

          I
         /
...--G--H

但是,Git 確實需要知道您使用哪個分支名稱來查找 H. 因此,這兩個名稱之一具有HEAD“附加到它”的特殊名稱。假設這個名字曾經是,現在仍然是 feature那么我們的繪圖現在看起來是這樣的:

          I   <-- feature (HEAD)
         /
...--G--H   <-- main

也就是GitHEAD以前找名字feature,先找hash ID H,再往里面寫入新的hash I ID feature

這樣做的效果是,當前分支名稱,無論它是什么,現在都指向您剛剛進行的新提交(請注意,快照I使用了索引/暫存區域,您更新它以匹配您的作業樹,因此所有三個現在都匹配,就像您開始使用“干凈”結帳或 時一樣git switch。)如果您制作另一個新的使用通常的修改檔案添加和提交程序提交,你會得到:

          I--J   <-- feature (HEAD)
         /
...--G--H   <-- main

如果你現在git switch maingit checkout main,Git 所做的是:

  • 洗掉所有J提交檔案并用提交檔案替換它們H
  • 將特殊名稱附加HEADmain.

你現在有:

          I--J   <-- feature
         /
...--G--H   <-- main (HEAD)

on branch main正如將要說的那樣,您是git status,并且您的作業樹和暫存區域是“干凈的”(與H提交匹配),您的更新檔案安全地永久保存 - 或者只要提交本身持續存在 - 在 commitJ中,您可以使用名字feature

如果您愿意,您現在可以創建一個新分支,例如feature2, 并切換到它(使用git branchandgit switch或結合使用git switch -c一次完成所有操作):

          I--J   <-- feature
         /
...--G--H   <-- feature2 (HEAD), main

當您在這個新分支上進行新提交時,分支名稱會自動更新以指向最新提交:

          I--J   <-- feature
         /
...--G--H   <-- main
         \
          K--L   <-- feature2 (HEAD)

H請注意,在 Git 的術語中,通過并包含提交是在所有三個分支上。提交I-J當前onfeature并且提交K-Lonfeature2CommitH是 上的最新提交main,盡管它不是最新的提交(此時是您的存盤庫中的L提交J此外,提交和:之間沒有直接關系L:它們只是表親,實際上。他們是共同祖父母的孩子的孩子H

合并

要了解會發生什么,我們現在需要查看通常更難合并的情況。Git 有一個簡單案例的快捷方式,但由于各種原因(有些好,有些不太好),尤其是 GitHub 從不使用此快捷方式。無論如何,一旦您了解了更一般的情況,就更容易看到簡單的情況。

在 Git 中,使用git merge是關于組合作業讓我們畫兩個特征分支,不畫名字 main(它可能還存在,只是擋住了我想畫的樣子)。我們先切換到分支feature

          I--J   <-- feature (HEAD)
         /
...--G--H
         \
          K--L   <-- feature2

我們當前的提交是 now J,我們現在會J在作業樹中找到 's 檔案。我們現在運行git merge feature2, 和git merge:

  • 定位提交J(簡單:只需閱讀HEAD然后feature);
  • 定位提交L(也很簡單:feature2包含正確的哈希 ID);
  • 定位最佳公共起點提交。

最后一部分可能很難,盡管在這里很容易看出這是 commit : andH的祖父如果 Git 現在將快照中快照與中的快照進行比較,Git 將生成一個包含您所做的所有作業的配方JLHJfeature

git diff --find-renames <hash-of-H> <hash-of-J>   # what "we" did

通過從to運行第二個差異,Git 將生成一個包含在 上完成的所有作業的配方HLfeature2

git diff --find-renames <hash-of-H> <hash-of-J>   # what "they" did

在這一點上,誰做了哪些作業并不重要:唯一重要的是“我們”更改了哪些檔案,“他們”更改了哪些檔案,以及我們對每個檔案做了哪些更改。兩人git diff想通了。

如果 Git 可以自己組合這兩組更改,那么它可以將組合的更改應用來自H. 無論您喜歡如何看待它,這要么保留我們的更改并添加他們的更改,要么將兩個更改相加,或者其他。Git 假設最終結果是存盤在新提交中的正確快照

If Git can't combine these changes on its own, Git will stop in the middle of the merge with a merge conflict. The programmer must now come up with the correct result. We'll skip right over this part. ?? We'll just assume that Git came up with the right result all on its own. In that case git merge goes on to run git commit for you.

Normally, the resulting commit M would have commit J as its parent. Our new merge commit does in fact have J as a parent—the first parent—but also has commit L, the commit we named on the git merge command line, as its second parent, like this:

          I--J
         /    \
...--G--H      M   <-- feature (HEAD)
         \    /
          K--L   <-- feature2

The name feature, to which HEAD is attached, moves as usual to point to new commit M. But since M points backwards to both J and L, commits K-L are now also "on" branch feature. This means all commits up through M are on feature, while feature2 still ends at L and does not contain commits I-J.

如果需要,我們現在可以洗掉該名稱feature2:它只對L直接查找有用,如果我們不覺得需要L直接查找,我們可以隨時通過查看 的第二個父項來找到它M如果我們現在想添加更多提交feature2,我們應該保留名稱并執行此操作:

          I--J
         /    \
...--G--H      M   <-- feature
         \    /
          K--L--N--O   <-- feature2 (HEAD)

如果我們愿意,我們現在可以再次feature2合并feature

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

制作一種鴨頭??圖片,盡管我們也可以在沒有頂行的腫塊的情況下重新繪制它:

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

(不知道這個是什么樣子的)。

快進

Git 的特殊快捷方式git merge適用于以下情況:

...--D--E   <-- main (HEAD)
         \
          F--G   <-- bugfix

如果我們運行git merge bugfix,Git 將找到提交EG,然后找到和 的合并基礎兩個分支上的最佳提交。但這就是 commit本身,即當前的 commitEGE

Git可以繼續E與自己進行比較,以發現沒有任何變化。然后它可以比較找到他們的變化EG然后它將應用這些更改 E提出一個新的 commit H,并給它兩個父母:

...--D--E------H   <-- main (HEAD)
         \    /
          F--G   <-- bugfix

提交H將是一個合并提交,有兩個父母,就像“真正的合并”案例一樣。但是很明顯E,對自己進行差異化是愚蠢的,添加他們的更改只會讓我們得到一個提交H,其快照與他們的提交中的快照完全匹配G因此,對于這種情況,除非我們告訴它,否則 Git 根本不會打擾合并。

相反,Git 會做它所謂的快進合并這意味著 Git 只是直接檢查提交G,同時向前拖動當前分支名稱:

...--D--E
         \
          F--G   <-- bugfix, main (HEAD)

現在根本沒有理由在圖表中畫出扭結:

...--D--E--F--G   <-- bugfix, main (HEAD)

并且洗掉名稱bugfix顯然足夠安全,盡管main以后可能會進一步推進。

為了抑制快進而不是合并的事情,我們會運行git merge --no-ff. GitHub 總是有效地執行此操作,因此您不會GitHub 上看到快進合并;但很高興了解它們。

何時洗掉姓名

何時以及是否洗掉其他分支名稱取決于用戶。請注意,洗掉名稱不會洗掉提交:它只會使找到它們變得更加困難。但還有一件事要知道。假設我們有:

...--G--H   <-- main
         \
          I--J   <-- bugfix (HEAD)

在哪里提交I并且J根本不起作用。你將運行:

git switch main
git branch -d --force bugfix

放棄您修復錯誤的嘗試。這給你留下了:

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

Commits I-J still exist, but unless you wrote down J's hash ID, you may never be able to find commit J again.

Git will—eventually—detect that commit J is unreachable (that there's no way for you to find it) and will delete it for real. The same goes for commit I once J is gone. You get a grace period, normally at least 30 days, during which Git won't do this, and various Git commands to help find accidentally-lost commits. But if you don't bother finding them and adding a name back, the "reflog entries" by which Git keeps track of "lost" commits like this eventually expire, and then—when Git gets around to doing its maintenance and janitorial work—the "lost" commits will really go away from this repository. So, while commits are read-only, they are only "mostly permanent". They remain in your repository as long as you can find them (and then a little bit longer).

Clones, remotes, and multiple repositories

Git is not just a Version Control System (VCS); it's a Distributed VCS (DVCS). The way Git does this distribution is to allow for—or rather, strongly encourage—many copies of a repository to exist. As such, a Git repository is:

  • a collection of commits and other Git objects, some or all of which may be in other repositories too; and
  • a collection of names, such as branch and tag names, that help you (and Git) find the commits and other internal objects.

These are stored as two simple key-value databases. The keys in the names database are branch names like refs/heads/main, tag names like refs/tags/v1.2, and many other kinds of names. Each name lives in a namespace under refs/. Each name stores exactly one hash ID.

The keys in the objects database are hash IDs. Each object in this database has some Git internal object type (commit, tree, blob, or annotated tag). The commit objects, along with supporting tree and blob objects, wind up storing your files; and you will mostly just work with the commits and don't normally have to care much at all about these details.

Since commit hash IDs are globally unique, the object database keys in your clone of some repository are the same as the keys in every other clone of that same repository. When you clone a repository, you get all, or almost all, of their commits and supporting objects. But the names database in your clone is entirely separate from theirs.

What this means is that a clone of a repository starts out with no branch names at all. You run:

git clone <url>

or:

git clone -b <branch> <url>

and your Git software creates a new, totally-empty Git repository to start. Your Git software, using your Git repository (I like to shorten this to "your Git") calls up their Git software and points it to their Git repository ("their Git"). Their Git lists out all their branch and tag and other names and the hash IDs that go with them, and your Git then asks for the objects it would like to copy (normally, all of them). For each commit you're going to get, their Git is obligated to offer all of that commit's parents, and the parents' parents, and so on. So you end up copying every commit into your Git.

Now that you have all the commits (and supporting objects), your Git takes each of their branch names and renames them. This renaming process makes use of the concept of a "remote".

A remote, in Git, is just a short name that stores at least a URL (you can have it store various extra features later). The URL is the one you type into git clone, and the name of the first "remote" is always origin.4 So origin from now on means the URL I cloned from, unless and until you change something.

Git uses this name—the origin string—to make up new names for their branch names. Their main becomes your origin/main; their debug becomes your origin/debug; if they have a feature/tall, you get an origin/feature/tall; and so on. These names are not actually branch names; I like to call them remote-tracking names.5 Their function is to remember, for your Git repository, what their branch names are, and what commit each of those names selected, the last time your Git got an update from their Git.

完成此重命名后,您的 Git 會為它們擁有的每個分支名稱創建遠程跟蹤名稱。您擁有他們所有的提交,并且可以找到所有這些提交,因為您的遠程跟蹤名稱與他們的分支名稱擁有相同的哈希 ID,它們用于查找他們的提交。

git clone現在,在您完成并將控制權交還給您以便您可以開始作業之前不久,您的 Git:

  • 根據您提供的引數,在您的存盤庫中創建一個新的分支名稱-b:如果您說-b bugfix,您的 Git 會找到origin/bugfix與他們對應的您的分支bugfix并創建您自己的 bugfix,指向相同的提交。
  • 簽出(切換到)這個新分支。

所以現在你的克隆里面有一個分支,匹配它們的一個分支。如果您不使用-b,您的 Git 會詢問他們的 Git 他們推薦的名稱。通常的標準推薦是他們的主要分支(現在通常是main;過去是master)。

一旦你有一個克隆,你可以添加更多的遙控器,使用git remote add. 這需要遙控器的名稱URL;它設定了遙控器,但尚未運行git fetch現在是時候談談獲取和推送了;看另一個答案。


4您可以選擇其他名稱,但這樣做幾乎沒有任何意義。用作origin“主遙控器”的名稱。您可以隨時重命名遙控器,因此即使您不打算保留起始 URL,也可以將git clone默認設定為origin此處。

5 Git 稱它們為遠程跟蹤分支名稱,將可憐的超載詞branch從血腥、畸形的野獸變成幾乎無法辨認的污點。說真的,把分支這個詞放在這里,它沒有任何幫助。

uj5u.com熱心網友回復:

第 2 部分——見第 1 部分

git fetch

要運行git fetch,您選擇一個遙控器并將其呼叫為. 如果你省略了遠程名稱,Git 會從某個地方選擇一個遠程,或者嘗試使用默認名稱,這取決于很多配置項。如果您只有一個名為 的標準遙控器,則無需其他引數即可運行:無論如何您都沒有別的意思。git fetch remoteoriginorigingit fetch

fetch的作用是:

  • 呼叫任何 Git 軟體回答存盤的 URL;
  • 讓他們列出所有的名稱(分支、標簽等)和相應的哈希 ID;
  • 從他們那里獲得他們擁有的任何你沒有的提交。

請注意,這與我們對 的操作相同git clone,除了“獲取他們所有的提交”,現在是“獲取他們擁有的我們沒有的提交”。由于提交具有全域唯一的 ID,我們可以很容易地判斷我們有(比如說)提交a123456,因為我們有一些具有 ID 的物件a123456,而我們缺少——因此需要——b789abc因為我們沒有這樣的 ID。在獲得了他們的新提交后,我們的 Git 現在更新了我們相應的遠程跟蹤名稱。

換句話說,除了我們的git fetchGit 存盤庫已經存在之外,我們所做的事情幾乎相同,我們可能會獲得更少的資料,并且我們沒有最終的“創建分支并檢查它”步驟。由于我們可以擁有多個remote,我們可以運行:git clone

git fetch origin

并更新我們所有的origin/*名字,然后運行:

git fetch upstream

并更新我們所有的upstream/*名字,如果我們曾經git remote add添加第二個名為upstream.

要一次更新所有遙控器,我們可以使用git fetch --allor git remote update; 兩者基本上做同樣的事情。請注意,--alltogit fetch表示所有遙控器,而不是所有分支:我們已經獲得了所有分支。(我提到這一點是因為人們一直認為--all意味著所有分支,但它從來沒有。)

如果我們愿意,我們可以限制我們git fetch這樣:

git fetch origin main

這讓我們的 Git 像往常一樣呼叫他們的 Git 并列出所有內容,但是這一次,我們的 Git 只麻煩詢問他們在他們的main. 當一切都完成后,我們的 Git 會更新我們的origin/main(我們知道origin'main現在在哪里,所以我們對應的遠程跟蹤名稱,即 ,origin/main可以更新)。如果他們有新的提交dev,我們不會得到它們,我們不會更新我們的origin/dev; 我們的 Git 被告知只使用main.

在一些(罕見的)設定中,這種事情可以節省大量的資料傳輸。因此,Git 提供了一種稱為單分支克隆的東西,默認情況下git fetch會這樣做這是人們嘗試使用的地方(但它不起作用):要從單分支克隆中獲取其他分支,您必須添加它們 - 請參閱檔案-或使用顯式refspec不過,出于篇幅原因,我們不會在這里正確介紹 refspecs。--allgit remote

由于您將有兩個遙控器,一個用于您的 GitHub fork,一個用于您 fork 的 GitHub 存盤庫,您需要運行git fetch兩次,或者不時使用git remote updateor git fetch --all除此之外——upstream/*如果你像大多數人一樣呼叫第二個遠程upstream,你的存盤庫仍然和任何其他存盤庫一樣。

git push

git push命令與 非常相似git fetch,但有幾個關鍵區別:

  • 首先,當然git push是指發送東西您用于git fetch其他Git(與其他存盤庫一起作業的其他軟體)獲取新的提交(和其他內部物件) 。您用于發送新的提交,通常是您制作的提交 - 但它們可能是您剛剛獲得的提交實體——對其他 Git。git pushupstream

  • 其次,一旦你發送了這些提交,你通常會要求另一個 Git設定它的一個分支名稱在推送方面,沒有像遠程跟蹤名稱這樣的東西。

最后一部分意味著您必須有權寫入存盤庫。Git 本身根本沒有真正的訪問控制,但大多數 Web 托管站點,包括 GitHub,都添加了它們。特別是 GitHub 在這里添加了許多精美的控制元件。您和/或其他任何人是否使用它們取決于您和他們。

要做一個git push,你通常運行一個簡單的:

git push <remote> <name>

這表示您希望您的 Git 查看名為 的分支上的提交name,找到另一個 Git 的新提交origin,將它們發送該 Git,然后禮貌地詢問他們,如果他們愿意,請他們的名字name指向你name指向的同一個提交。

換句話說,您要求他們創建或更新與您的分支同名的分支。一般來說,當且僅當這只是簡單地添加到他們的分支(并且您當然有權限)時,他們才會接受這一點。也就是說,當我們有:

...--G--H   <-- main (HEAD), origin/main

因為我們main匹配的原點main,我們添加了一個或兩個新的提交:

          I--J   <-- main (HEAD)
         /
...--G--H   <-- origin/main

然后我們運行git push origin main,我們的 Git 呼叫他們的 Git,向他們發送提交I-J,并要求他們將他們main的指向設定為J.

如果他們main仍然指向H——或者不知何故,G因為有人讓他們放棄 H——他們會很樂意接受我們添加到他們的main. 由于我們的 Git 看到了他們的接受,我們最終得到:

...--G--H--I--J   <-- main, origin/main

明知故犯origin_ main_J

K但是假設其他人出現并向他們 添加了一些承諾main

...--G--H--K   <-- main [over on origin]

我們的請求現在將要求他們放棄他們的 commit K,這將使他們留下:

...--G--H--I--J   <-- main
         \
          K   ???

他們會說no,并且您將收到的錯誤訊息不是快進的(還記得那些來自合并的訊息嗎?這是相同的想法)。

您可以使用--forceor--force-with-lease嘗試讓他們接受更改,從而丟失他們的新提交,但通常這是錯誤的做法。但是,對于您對 GitHub 的使用,有時這是在您的 fork 上做的正確的事情!我們稍后再談。

還有一種方法可以使用 洗掉名稱git push其實有好幾種,但最清楚的大概就是會舍棄過來這對您的筆記本電腦存盤庫沒有影響。git push --delete remote branchgit push --delete origin foobranchfoobranchorigin

GitHub“分叉”

我們現在有足夠的背景來定義 GitHub 的FORK按鈕是如何作業的。您選擇一些不屬于您自己的現有存盤庫并單擊它,GitHub 將在 GitHub 上創建一個您自己的新存盤庫。這個 GitHub “fork”一種克隆,但增加了幾個特性和一個變化。

現在您知道不git clone復制任何分支,因此更改很明顯。當您使用 GitHub 的 fork 按鈕時,它會復制所有分支。您的新克隆具有與原始克隆相同的一組提交和分支,而常規克隆到您的筆記本電腦,它獲取所有提交但沒有任何分支,然后創建一個分支,該分支完全不小心故意匹配origin's 分支之一。fork 按鈕使您的 fork 中的所有分支名稱與其他存盤庫的所有分支完全匹配。

增加的功能包括提出拉取請求的想法,我們稍后再討論。在 GitHub 方面——你看不到,但對 GitHub 本身非常重要——增加的功能包括不使用任何空間來保存提交:你的 fork 只是重新使用原始提交。任何提交都不能改變,所以這很好;唯一可能發生的問題是如果要洗掉提交,因此 GitHub 只是安排永遠不會洗掉提交。1

但是,一旦你創建了分支,這些分支名稱,在你的分支中的 GitHub 上,不會再更新,除非這樣做。您可以從 GitHub 的 Web 界面執行一些操作(例如,您可以洗掉分支名稱),或者您可以git push照常在筆記本電腦上使用。

因此,一旦您確實有一個 fork,您將希望將該 fork 克隆到您的筆記本電腦,然后筆記本電腦上添加第二個 URL,該 URL 指向您 fork 的存盤庫。命名第二個 URL 的標準 GitHub 方法是使用遠程名稱upstream我個人不喜歡這個名字,因為上游這個詞在 Git, 2中已經有幾個含義,但是要使用它運行,如果你已經 forkssh://github.com/them/repo.gitssh://github.com/you/repo.git,你會運行:

git clone ssh://github.com/you/repo.git
cd repo
git remote add upstream ssh://github.com/them/repo.git
git fetch upstream

你現在有origin/*upstream/*名字。我們現在來看看一個方便的技巧。


1這意味著如果有人不小心在 GitHub 上輸入了密碼,它可能會永遠存在,即使他們很快強行將其隱藏。GitHub 支持可以“真正地”清除提交,但一般來說,始終考慮任何意外暴露的秘密,即使是一瞬間也將永遠受到損害。

2所以,至少分支這個詞好。??


小技巧:更新你的 fork

運行后git fetch upstreamgit remote update您的upstream/*名稱都已更新,您可能希望自己的 fork 將所有更新都放在相同的分支名稱下。這意味著對于每個upstream/whatever,您要運行:

git push origin upstream/whatever:whatever

這種git push使用refspec,我們將“源”名稱放在左側,然后是冒號,然后在右側放置“目標”名稱。Git 將從給定的源(我們的本地upstream/whatever遠程跟蹤名稱)中挑選提交,但是當它們到達目的地(origin)時,要求目的地設定它們的目的地端名稱(他們的whatever)。

可以使用回圈來執行此操作,但有更短的方法。請注意,您可能需要保護*您的 shell 中的字符,具體取決于您的特定命令列解釋器:

git push origin "refs/remotes/upstream/*:refs/heads/*"

我假設您需要雙引號才能獲得正確的保護。如果您不能使用雙引號,請使用所需的任何參考機制(可能根本不需要)

在這里,我們已經完整地拼出了名稱:遠程跟蹤名稱存在于refs/remotes/名稱空間中,而分支名稱存在于refs/heads/. git pushGit 匹配兩顆星,并在此處對每個分支進行常規(非強制) 。

您可以創建一個執行此操作的 Git 別名git push,以避免鍵入長命令并避免參考 refspec(簡單的 Git 別名不通過 shell):

[alias]
    up2hub = push origin refs/remotes/upstream/*:refs/heads/*

請注意,這嵌入了名稱upstreamorigin,但現在您可以運行:

git up2hub

成功后git fetch upstream,更新您的 GitHub 分支。

拉取請求

現在我們終于找到了問題的核心:拉取請求是如何作業的。 當您使用CREATE PULL REQUESTGitHub 上的按鈕時,您會選擇兩件事,盡管 GitHub 會默認為您選擇其中一項:

  • 叉子中的分支名稱;
  • 另一個GitHub 存盤庫3中的“基礎分支” ,您希望針對它進行此 PR。

GitHub 現在將運行“測驗合并”,在那里他們嘗試對git merge分支上的一組提交進行常規的提交,這些提交由 Pull Request 匯入到他們的存盤庫,到他們分支的當前提示提交。也就是說,GitHub 會獲取您在 fork 中的每一個提交,而它們在其存盤庫中根本沒有,并將這些提交復制到它們的存盤庫4

他們現在可以在拉取請求的全名下找到您的提交,在 GitHub 上是. 測驗合并如果有效,將創建一個新的提交并使其可以通過. 如果它因合并沖突而失敗,PR 仍然會生成,只是沒有名稱。refs/pull/number/headrefs/pull/number/mergerefs/pull/number/merge


3您可以將拉取請求發送到共享訪問存盤庫,您和其他人都在該存盤庫中推送到單個存盤庫,而不是各自推送到各自的分支。在這種情況下,您會選擇這個存盤庫本身作為“其他”Git 存盤庫。但這只是一種特殊情況,其中“其他”存盤庫是“這個”。

4這一切都是虛擬發生的,以節省磁盤空間:他們的存盤庫有一個指向您的鏈接,因此沒有實際的復制,就像您在分叉他們的存盤庫時沒有真正復制他們的提交一樣。同樣,Git 使用了這樣一個事實,即每個提交都有一個唯一的哈希 ID:您的提交以及您的哈希 ID,保證與所有提交都有不同的哈希 ID。因此,當他們的 Git 軟體嘗試查找提交fee1cab或任何哈希 ID 時,但找不到它,他們只需查看您連接的分支,就可以了。你的 fork 參考了他們的 repo,而他們的 repo 又參考了你的 fork,在一種亂倫的回圈中。


那么,這意味著什么?

好吧,讓我們看一個經典的例子。您 fork 一些存盤庫,并將其克隆到您的筆記本電腦并創建一個新分支:

...--G--H   <-- main, my-feature-1, origin/main

my-feature-1你在你的分支上做了幾個新的提交:

          I--J   <-- my-feature-1 (HEAD)
         /
...--G--H   <-- main, origin/main

您將這些提交發送到您的 GitHub 分支:

          I--J   <-- my-feature-1 [on your fork]
         /
...--G--H   <-- main [on your fork]

然后,您單擊按鈕進行 PR,在他們的分叉中,他們現在擁有:

            I--J   <-- refs/pull/123/head
           /    \
          /      M   <-- refs/pull/123/merge
         /      /
...--G--H---K--L   <-- main

CommitM是 GitHub 的“測驗合并”,它奏效了;commitsK-L是他們在你忙于使用 fork 和筆記本電腦時所做的新提交。

如果你現在繼續做:

          I--J   <-- my-feature-1, my-feature-2 (HEAD)
         /
...--G--H   <-- main, origin/main

在您的筆記本電腦上,然后再進行兩次提交,您將獲得:

               N--O   <-- my-feature-2 (HEAD)
              /
          I--J   <-- my-feature-1
         /
...--G--H   <-- main, origin/main

您可以將git push這些添加到您的 GitHub 分支中,以便它具有:

               N--O   <-- my-feature-2 [on your fork]
              /
          I--J   <-- my-feature-1 [on your fork]
         /
...--G--H   <-- main [on your fork]

如果您現在使用此my-feature-2 名稱在 GitHub 上創建 PR ,則該 PR 包含尚未提交的提交I-J-N-Omain因為他們尚未決定如何處理 PR#123:

                 N--O   <-- refs/pull/124/head
                /
            I--J   <-- refs/pull/123/head
           /    \
          /      M   <-- refs/pull/123/merge
         /      /
...--G--H---K--L   <-- main

(加上,也許,一個測驗合并,如果他們能夠合并OL

如果這不是你想要的,你應該放在 GitHub 上的是:

          I--J   <-- my-feature-1 [on your fork]
         /
...--G--H   <-- main [on your fork]
         \
          N--O   <-- my-feature-2 [on your fork]

my-feature-2現在只包含兩個不在其 中的提交main,因此 PR#124 使他們的存盤庫看起來像這樣:

            I--J   <-- refs/pull/123/head
           /    \
          /      M   <-- refs/pull/123/merge
         /      /
...--G--H---K--L   <-- main
         \      \
          \      P   <-- refs/pull/123/merge
           \    /
            N--O   <-- refs/pull/124/head

uj5u.com熱心網友回復:

第 3 部分:你的下一個絆腳石:變基

當他們(無論他們是誰)開始審查您的 PR 時,他們可能會:

  • 照原樣接受;
  • 要求您修復其中的某些內容;
  • 接受他們所做的改變;要么
  • 完全拒絕它。

最后一個在這里不需要更多討論,但其他三個確實需要。

如果他們“按原樣”接受您的提交,他們可以選擇MERGE在 GitHub 上使用三個不同的“接受此 PR”按鈕:

  • MERGE. 這個很簡單。
  • REBASE AND MERGE. 這個不太直接。
  • SQUASH AND MERGE. 這需要了解 Git 的“壁球合并”,這根本不是合并。

如果他們自己做出任何改變,情況很像REBASE AND MERGE,我們將看到。如果他們希望進行更改,將需要git rebase在筆記本電腦上使用,之后您將需要使用git push --forcegit push --force-with-lease更新您的 GitHub 存盤庫。5


5從技術上講,您可以洗掉并重新創建分支,而不是強制推送。不過,我認為這會扼殺現有的 PR(我還沒有嘗試過)。無論如何,強制推送選項是人們在實踐中使用的。


Rebase 是關于復制提交

我們使用git rebase將現有的提交(其中有我們喜歡的和不喜歡的)復制到我們使用而不是原始的新的和(據說無論如何)改進的提交。

在我們看之前,讓我們看一下復制一個提交的命令。正如我們所知,任何提交都無法更改,但我們總是可以提取提交,或將其轉換為差異,或其他任何方式。我們可以使用此屬性來獲取一些現有的提交,將其轉換為更改,然后再次或在其他地方應用這些更改。Git將此操作稱為cherry-picking,并使用該命令git cherry-pick來執行此操作。

通常git cherry-pick,出于某種原因,我們可能會使用它作為一種快速獲取提交的一次性副本的方法。例如,也許有人有一個好主意,或者一個錯誤修復,我們現在需要在我們的分支上,我們將弄清楚如何處理這將在以后造成的混亂。在這里,我們在我們的分支上:

...--J--K   <-- feature (HEAD)

與此同時,在他們的分支上,他們修復了一些困擾我們的討厭的小錯誤:

...--P--C--R--S   <-- theirs

(在這里,我們將在P、“父”提交、 “子”之后稱為 fix-commit,而不是我通常使用C的字母)。Q我們跑:

git cherry-pick <hash-of-C>

告訴 Git:去弄清楚誰在 commit 中做了什么,它C的子節點P,通過比較PC查看發生了什么變化。然后在我的 commit 上進行相同的更改K,并從中進行新的提交。 生成的圖表如下所示:

...--J--K--C'  <-- feature (HEAD)

Git 會將他們列為 commit 的作者,并重C'用他們的提交資訊;我們將列為. C'(大多數時候,作者和提交者是同一個人,但不一定;這樣的副本通常讓他們保持作者身份,并且也會保留他們的提交日期和時間戳。)

Git 實際實作這種“復制他們的更改”的方式是,Git 必須弄清楚要觸摸哪些檔案以及這些行去了哪里。為了做到這一點,Git 做一個 diff from PtoC看看他們做了什么,然后第二個 diff from PtoK看看我們做了什么。回想一下是如何git merge作業的:這是合并的核心:合并兩個差異并將合并的差異應用于合并基礎Git 只是強制“基礎”為 commit P,不管其他一切。我們的 commit 是 commit K,他們的 commit是 commit C,僅此而已——除了 Git 提交合并時,它會將其作為普通的單父提交。CommitC'指回K僅,不至PC

如果一切順利(如果沒有合并沖突),最終結果是我們從 commit獲得了更改C,但在 commit 處應用了這里K因此,新提交C'是它的副本C

  • 一些與以下相同的元資料C:特別是日志訊息和作者身份;
  • 具有相同的diff,除了如果我們必須解決合并沖突而添加或洗掉的任何內容。

有了git rebase,我們可以:

  • 獲取一系列現有提交并簡單地移動它們,或者
  • 使用互動式rebase,做各種額外的擺弄。

互動式 rebase 本身就是一篇很大的博文或文章,我們不會在這里詳細介紹。我們將只看一個簡單的 rebase-to-move 作業。我們從例如:

          I--J   <-- feature (HEAD), origin/feature
         /
...--G--H--K--L   <-- upstream/main

比方說,在這一點上,我們喜歡一切I-J 除了他們不追隨K-L讓我們再添加一個問題:存在合并沖突,例如,因為我們接觸的J其中一條線與我們(或他們)接觸的其中一條線相鄰L這種合并沖突,或者被 Git 視為沖突的事情,確實很容易修復,但 Git 不會這樣做,所以我們必須自己去做。

此時,我們只需運行:

git rebase upstream/main

請注意,我們不必在這里使用分支名稱,我們甚至不必更新origin:我們可以基于我們擁有的任何提交,使用該提交的任何名稱。名稱upstream/main找到了 commit L,這就是我們想要復制IJ追求的提交,所以這就是我們在這里給的名稱git rebase

Git 將在內部保存提交的原始哈希 IDIJ,它們是要復制的提交。(Git 是如何知道這一點的——以及我們如何將被復制的內容更改到哪里——在其他地方得到了回答,但請注意,我們當前的分支名稱指向J并且提交I-J唯一可以從當前分支訪問的。)然后,Git將切換到提交L——我們命名的那個——在 Git 所謂的分離 HEAD模式下。這里根本沒有當前分支:HEAD直接指向當前提交。所以現在我們有了這個:

          I--J   <-- feature, origin/feature
         /
...--G--H--K--L   <-- upstream/main, HEAD

Git 現在做一個櫻桃挑選來復制I. 這有效,Git 進行了新的提交I'

          I--J   <-- feature, origin/feature
         /
...--G--H--K--L   <-- upstream/main
               \
                I'  <-- HEAD

Git 現在嘗試第二次挑選來復制J. 這一個因合并沖突而失敗。我們通過在編輯器中打開沖突檔案,將正確的合并結果放入檔案中,然后將檔案寫回我們的作業樹,然后git add在該檔案上運行來解決沖突。然后我們使用:

git rebase --continue

使 Git 恢復提交和復制等等。Git 為J'(副本,我們的解決方案,作為git add-ed)進行提交:

          I--J   <-- feature, origin/feature
         /
...--G--H--K--L   <-- upstream/main
               \
                I'-J'  <-- HEAD

如果有更多的提交要復制,Git 會嘗試挑選下一個。盡管如此,沒有什么可以復制的,git rebase它的最終操作也是如此:

  • rebase 將名稱拉到feature這里,無論在哪里HEAD
  • rebase 重新附加HEAD

所以我們現在有:

          I--J   origin/feature
         /
...--G--H--K--L   <-- upstream/main
               \
                I'-J'  <-- feature (HEAD)

原始I-J提交仍然存在如果您記下他們的哈希 ID(或仍然在螢屏上顯示它們),您仍然可以看到它們。使用名稱origin/feature,您仍然可以看到它們。但是,如果您使用該名稱 feature來查找提交,您將找到新副本而不是原始副本。

更新拉取請求

為了更新我們現有的拉取請求(可能是 PR#125,更新feature到我們從其分叉的存盤庫中的一個分支名稱),我們只需告訴GitHub接受這兩個新提交。一個平原:

git push origin feature

不會作業,因為 GitHub 上的 Git 會反對:嘿,如果我更新我的feature,我將失去這兩個非常有價值的I-J提交!不是快進! 被拒絕! 我們必須強制它更新,使用新的替代品I'-J'6 所以我們運行:

git push --force-with-lease origin feature

或更短的--force變體。(這個--with-lease增加了一些錯誤檢查,是個好主意,但至少對我來說仍然感覺很新奇并且打字很笨拙。這是使用 Git 15 年以上的缺點之一。)我們告訴 GitHub我們是認真的,他們接受了新的承諾。

由于有一個公開的 PR 參考name feature,此時 GitHub 將再次嘗試合并。他們最后一次嘗試此合并時,與 commit 存在合并沖突L這一次,我們添加了他們的 commit L,所以沒有合并沖突,并且 PR 可能會被原樣接受。

如果我們需要進行額外的更改,我們可以使用花哨的互動式 rebase,或者做額外的提交然后 squash,或者任何我們喜歡的。


6如果 Git 知道這些是進化的替代品,那就太好了。


擠壓

Git 提供了它所拼寫的東西git merge --squash這不是合并,就像快進不是合并一樣:沒有最終的合并提交但它一個合并,就像git merge做一對差異和組合作業一樣:有一個組合作業部分。

鑒于:

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

如果我們運行git merge --squash br2,Git 將:

  • H像往常一樣找到合并基地;* 像往常一樣做兩個差異;
  • 合并差異,如果有合并沖突就停止,讓我們修復混亂;要么
  • 如果沒有沖突,無論如何都要停止。

我相信這個“??無論如何都停止”只是原始實作的一個意外——git merge有一個--no-commit標志讓它停止,它應該與 分開--squash,但--squash總是打開--no-commit但是,無論如何,我們現在必須通過運行來完成操作git commit,它會像往常一樣提交 Git 索引和作業樹中的內容,并且不會進行合并提交M我們沒有與兩個父級合并,而是像往常一樣與一個父級進行簡單的普通提交S——“壁球合并”:

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

新提交中快照與我們擁有S的快照相同,如果我們進行了常規合并,但沒有鏈接回,現在唯一有用的名稱洗掉它。7 這兩個提交實際上已合并或壓縮為單個提交提交具有與之前相同的效果因此現在無用并且應該被遺忘。SLbr1K-LSSK-LK-L


7可以這樣做,但這超出了此答案的范圍。


squash-merge 與 GitHub PR 的關系

如果上游有人拿走了你的 PR 并squash-merge它,你給他們的東西——也許是多次提交——現在被一個單一的 squash 提交替換:

          I--J   <-- refs/pull/125/head
         /
...--G--H--K--L--S   <-- theirbranch

在這里,他們的提交S代表了I-J. 您所有的實際作業,以及至少您的一些提交日志訊息,都被它們的壁球所取代(它們可能會或可能不會保留您的一些提交日志訊息)。您應該獲得提交S(進入您的upstream/theirbranch)并使用它,放棄您的I-J原件。

rebase-and-merge 對你的 PR 有什么影響

如果上游有人拿走你的 PR 并使用REBASE AND MERGE按鈕,GitHub 的 Git 軟體會將你的每個提交復制到 new-and-improved?提交。即使沒有真正的需要,他們也會這樣做例如,您可能已經仔細地將您的基礎重新設定為I-J他們的基礎L,以便您擁有:

                I'-J'  <-- refs/pull/125/head
               /
...--G--H--K--L   <-- theirbranch

在你做 PR#125 的時候。但是他們點擊了“錯誤”的8按鈕,因為他們喜歡線性圖,所以現在在他們的存盤庫中他們有:

                I'-J'  <-- refs/pull/125/head
               /
...--G--H--K--L--I"--J"  <-- theirbranch

其中I"是 的副本I'J"是 的副本J'這些副本保留了您的原始日志訊息,并以您為作者,但它們具有新的和不同的哈希 ID。

你需要放棄原來的提交,轉而支持這些“新的和改進的”提交。不過,有一件好事——另一個方便的技巧——git rebase讓你更容易。


8我只稱其為“錯誤”,因為它不必要地重復提交。對于您的 PR 掛起 commit 的情況H確實必須重新設定提交,以使圖表呈線性。但是,效果是您是作者,他們是提交者,并且這些提交具有新的和不同的哈希 ID。


方便的技巧:rebase 知道副本

當有人完成這種“變基和合并”時,你幾乎總是可以用git rebase 自己替換你原來的提交——即使你自己對它們進行了多次變基——用他們新的變基副本。原因是當 Git 列出要復制的提交時,它會檢查這些提交是否已經在您要復制的位置。也就是說,假設的筆記本電腦上有這個:

               K--L   <-- feature2 (requires feature)
              /
          I--J   <-- feature
         /
...--G--H   <-- upstream/theirbranch

您現在從 中進行 PR feature,他們按原樣執行,但使用“錯誤”按鈕,以便您的git fetch upstream結果如下:

               K--L   <-- feature2 (requires feature)
              /
          I--J   <-- feature
         /
...--G--H--I'-J'  <-- upstream/theirbranch

您現在必須在 commitK'-L'上進行提交J'

可以顯式執行此操作 ( git switch feature2; git rebase --onto upstream/theirbranch feature),但是:

git switch feature2
git rebase upstream/theirbranch

將完成這項作業。原因是 GitI-J-K-L首先列出了要復制的四個提交,然后查看提交I'-J'確定這些是IandJ的副本。rebase 代碼使用它來完全洗掉提交I-J,從而導致:

          I--J   <-- feature
         /
...--G--H--I'-J'  <-- upstream/theirbranch
                \
                 K'-L'  <-- feature2 (HEAD)

事實上,如果您還運行git switch featurethen git rebase upstream/theirbranch,您的 Git 將簡單地從復制程序中洗掉提交I-J,留下以下內容:

...--G--H--I'-J'  <-- upstream/theirbranch, feature (HEAD)
                \
                 K'-L'  <-- feature2

如果有人必須手動修復至少一個提交,這(完全)不起作用。在過去,在 GitHub 獲得一些額外工具之前,這永遠不可能直接GitHub 上發生。現在(他們擁有這些工具)至少在理論上是可能的。

uj5u.com熱心網友回復:

當您執行拉取請求時,您建議將您的一個分支合并到原始存盤庫的一個分支中。每次更新分支時,合并都會更新。當您進行修復或審查后更新時,這非常有用。

針對您的案例的幾種解決方案,簡單地關閉您的拉取請求,為每個要提交的主題創建一個分支(每個分支基于分叉存盤庫的主干)。

第二種解決方案:創建一個分支來讓你額外的作業回到主分支(或主分支)強制已經提交的分支到原始提交并推送它

git checkout -b my_second_feature
git checkout main
git reset --hard <commit_sha>
git push -f

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/428337.html

標籤:混帐 github 分支 拉取请求 git-fork

上一篇:描述命名GITHUB的問題

下一篇:Git:在之后提交了幾次好的提交后撤消提交

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more