簡體   English   中英

git:對“非快進”錯誤的基本誤解

[英]git: basic misunderstanding of “non-fast-forward” error

我自己工作時不了解git的基礎是可以的,但是現在我正在與另一個人一起工作,並且我們每個人都提交pull請求以使它們彼此合並,這開始成為一個問題。

工作流程:我在“作者”分支中編寫。 准備好進行審查時,我提交了一個拉取請求,然后我的編輯器將該請求合並到主請求中。 當她對我有意見時,她提交了她的Editor分支的拉取請求,然后將它們合並到master中。

今天,我收到了一個完全令人發指的循環錯誤,我不明白我被要求做什么。

thomas@trigger ‹ author ↑● › : ~/pm/wip [1] % git push To https://github.com/mathpunk/punk-mathematics-text.git ! [rejected] Editor -> Editor (non-fast-forward) ! [rejected] author -> author (non-fast-forward) error: failed to push some refs to 'https://github.com/mathpunk/punk-mathematics-text.git' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes (eg 'git pull') before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.

因此,我嘗試git pull並得到以下信息:

thomas@trigger ‹ author ↑● › : ~/pm/wip
[130] % git pull
You asked me to pull without telling me which branch you
want to merge with, and 'branch.author.merge' in
your configuration file does not tell me, either. Please
specify which branch you want to use on the command line and
try again (e.g. 'git pull <repository> <refspec>').
See git-pull(1) for details.

If you often merge with the same branch, you may want to
use something like the following in your configuration file:
    [branch "author"]
    remote = <nickname>
    merge = <remote-ref>

    [remote "<nickname>"]
    url = <url>
    fetch = <refspec>

See git-config(1) for details.

與這個問題有關的問題在stackoverflow上很常見,但是顯然我對git的基本概念了解得不夠深,無法理解它們。 同樣,我們的工作流程可能很瘋狂:我們倆都不是開發人員。 接下來我可以嘗試什么?

這是我認為git文檔非常糟糕的領域之一。 一切都指向git pull但是git pull只是建立在幾個基礎項之上的便捷方法 ,而基礎項對於理解這一點至關重要。 Git試圖讓重要理解的部分“泄漏”出去,同時假裝它們不相關。

順便說一下,這里是實際的基本元素:

  • 當您與某人共享內容時,會涉及多個獨立的存儲庫(repos)。 至少有“您的”和“他們的”,而且經常有三分之一(例如,在github上)用於共享。 在這種情況下,模型是由第三方github處理所有復雜的身份驗證內容(https,ssh等),因此您不必這樣做。

  • 要從其他倉庫中獲取東西,可以使用git fetch

  • 要將內容交付給其他倉庫,您可以使用git push

換句話說, push實際上不是pull ,而是fetch

refspecs

為了使這兩個操作(push和fetch)正常工作,git使用了所謂的“ refspecs”。 請記住,在推送和獲取時,涉及兩個存儲庫。

Refspec通常看起來像是單個分支名稱。 但是,refspec的最簡單的“實際”版本實際上是兩個分支名稱,中間用冒號分隔:

master:master
Editor:Editor
author:author

左側和右側為兩個存儲庫中的分支命名。 進行push ,左側的名稱是您存儲庫中的分支,而右側的名稱是其存儲庫中的分支。 對於fetch ,左側的名稱是其存儲庫中的分支,而右側的名稱是您存儲庫中的分支。

這是模型再次變得有點奇怪並且不對稱的地方。 Git相信(可以說什么都相信),當您取貨時 ,您他們可能都在從事工作; 但是當您按下時 ,只有應該做任何工作。 (這有充分的理由,我將不再贅述,因為這已經很長了。:-))

為了使這一切起作用, fetch提供了分支重命名。 相反取“自己的”分支(的masterauthor等)直接連接到“你”的分支,這將使其非常難以進入自從上次取,您將獲取“自己”的東西做的工作是什么混帳所謂的“遠程分支機構”。


獲取和“遠程分支”

盡管名稱為“ remote branch”,但“ remote branch”實際上是本地事物。 因此,“遠程”也是如此。 “遠程”是您在本地配置的名稱,例如origingithub 與此“遠程”相關聯的是URL,例如https://github.com/mathpunk/punk-mathematics-text.git 還有一條fetch線。 現在不必擔心fetch線的機制(一旦創建它通常就可以正常工作了); 只知道這就是git知道如何在獲取時使用什么“遠程分支名稱”。

不必擔心,在一定程度上,對遠程的實際名稱。 通常的默認名稱是origin但是在執行git remote add命令時可以選擇名稱。 遠程名稱成為“遠程分支”名稱的一部分。 具體來說,遠程名稱在分支名稱之前加前綴。

因此,假設您將git fetch origin從github帶入東西,那么“遠程分支名稱”將是origin/masterorigin/Editororigin/author

如果您通過git fetch github從github上git fetch github內容,則“遠程分支名稱”將改為github/mastergithub/Editorgithub/author

在所有情況下,您只需命名遠程節點,然后fetch會繼承所有分支,但會重命名它們。 通過省略refspec,您可以在fetch行中使用默認的fetch

如果添加分支名稱(例如git fetch origin author ),則git通過使用相同的fetch行來重命名傳入的分支,從而將其變成“真實” refspec。 實際上, git fetch origin author變成了git fetch origin author:origin/author 他們的分支名稱,在左邊的author變成了您的 “遠程分支名稱”,在右邊是origin/author

(這里的想法是,您可以添加多個不同的遙控器。如果您,您的編輯者和發布者都希望彼此直接共享,而不是與像github這樣的第三方共享,則可以有兩個遙控器,分別名為editorpublisher例如,對於一個遠程站點,您將獲得“遠程分支名稱”,如“ editor/Editor對於另一個遠程用戶,將獲得“ publisher/Editor ”。如果您使用單個共享站點(例如github),那么所有這些都是毫無意義的。)


好,回到fetchpush 當您git fetch origin ,您可以使用遠程origin名稱來轉移“ their”分支,但將其置於您的origin/* “ remote”分支下。 這樣可以使他們的工作與您的工作分開。 (當然,在某些時候,您需要將它們結合起來;我們稍后會談到。)

當你push ,不過,“推” 使用“遠程分支”的概念。 您只需直接推送到他們的分支。 因此,如果您在倉庫中,分支author有一些更改,並且想要推送這些更改,則只需git push origin author:author 這里的origin部分還是遠程名稱,最后一部分是通常的refspec,分別命名您的分支( author )和其分支(也就是author )。

如果在push命令中包含分支名稱,則此處缺少分支的重命名將通過以下方式顯示: git push origin author “意味着” git push origin author:author 您的分行名稱, author ,在左邊,是簡單地復制到作為分支機構的名稱, author ,在右邊。


評論

是時候快速回顧一下:

  1. 您設置了“遠程”
  2. 您用來fetch “遠程分支”的地址,以及
  3. 用於push本地分支機構推向其本地分支機構。

考慮一下。 請注意,缺少一個步驟。

您如何將他們的工作(現在在第2步之后)列在您的遠程分支機構中,並進入自己的本地分支機構?

這是git merge以及git pull進入的地方。

這也是問題標題中條目的出現位置。快進或非快進是git中“標簽移動”的屬性。

為了真正理解這一點,我們必須走一點路,討論git的提交和分支模型。


提交圖

每個提交都有一個保證唯一的標識符(SHA- 9afc317...號)。 您和其他任何人都不會創建具有該編號的任何其他提交,但是如果您或任何其他人可以設法完全重新創建該提交,則您將獲得相同的編號。 (這對於獲取很重要。)

每個提交還通過引用間接包含一個完整的獨立實體“樹”。 樹是該提交中所有文件的集合。 但是,提交並不是完全獨立的:它具有一個或多個“父”提交。 這些確定提交歷史記錄,從而“建立”實際的分支結構。

(在許多(甚至可能是大多數)其他版本控制系統中,樹不是獨立的:VCS必須仔細研究父提交和子提交,以提取樹和/或進行新提交。但是在git中,每棵樹是獨立的;它只需要通過父/子排序來比較兩棵樹,或確定提交歷史。)

給定一個提交,git會找到其父提交和其父提交,依此類推,並建立一個“提交圖”:

        C - F
      /       \
A - B           G   <-- master
      \       /
        D - E

這是一個存儲庫的圖形,該存儲庫總共包含7個提交,所有這些提交都在一個名為master分支上。 A是最初的提交(這里A代表一些丑陋的唯一SHA-1編號), BA以來有一些變化(比較AB的兩棵樹將顯示該變化),然后是某個人,或者可能是兩個“某人” ”,即所謂的“分支”:基於提交B創建了提交C ,同樣基於提交B創建了提交D

之后,有人基於D創建了提交E ,並且基於C創建了F

最后,有人組合了兩個分支進行合並提交,提交G 提交G具有FE作為其( 兩個 )父母。 它有兩個父母的事實實際上就是使它成為“合並提交”的原因。

當所有這些都發生在單個存儲庫中時,就足夠簡單了。 一個“有人”(使用存儲庫的人)在分支master上提交了ABC ,然后也許從提交B開始創建了一個命名分支:

git checkout -b sidebranch master~1

並提交了DE 然后他們回到了master那里:

git checkout master

並提交了F ,然后運行:

git merge sidebranch

創建提交G 在此之后,他們可以刪除分支sidebranch ,如提交G (即現在的尖上提交master )點背犯E以及提交F

但是,當您都在自己的倉庫中工作,而“他們”在他們自己的倉庫中工作時,也會發生同樣的情況。 假設您正在研究master並且已經提交了AB

A - B   <-- master

此時,您將工作推到共享點(github),因此它具有AB 他們克隆了這個存儲庫,為他們提供了第三個存儲庫,並帶有github共享點以及兩個提交AB

現在,您可以在倉庫中工作並創建提交C 他們在自己的內部工作並創建DE ,並且在將C推送到github之前,他們將DE推送到github:

[您:]

        C   <-- master
      /
A - B

[他們和github:]

A - B
      \
        D - E   <-- master

現在,假設您使用git fetch github 記住, fetch重命名“ their”分支,因此結果是這樣的:

        C   <-- master
      /
A - B
      \
        D - E   <-- github/master

Git之所以可以這樣做是因為每個提交都具有唯一的SHA-1,因此它知道您的AB以及它們的AB 相同 ,但是您的C與它們的DE

在這一點上,您可以創建提交F ,這使您的master站點指向最新的提交:

        C - F   <-- master
      /
A - B
      \
        D - E   <-- github/master

現在,如果您想共享您的工作,那就是當您使用git push github ...時,但是問題是,從您的角度來看, 您的主人提交了A - B - C - F ,而提交DE是,僅在github/master

如果將master推送到github並使github的master點提交F ,則提交DE將丟失。 (無論他們是誰,“他們”仍然會擁有它們,而您仍然會擁有它們,但是名為github/master ,因此可以解決此問題,但這很痛苦。)

解決方案是為修補此問題,以便“其”提交DE也在您的master 一種簡單的方法是讓您合並您的工作及其工作,從而得到:

        C - F
      /       \
A - B           G   <-- master
      \       /
        D - E   <-- github/master

快進

請注意,每次進行新提交時,分支標簽master都如何“向前移動”?

您使提交Fmaster (以前指向提交C )向前移動以指向新的提交F

然后,您使合並提交Gmaster (以前指向F )前進,以指向新的提交G

構建標簽時,標簽會沿着分支“向前移動”。

假設我們還有另一個標簽(另一個分支名稱)指向(例如)提交B ,但一直沒有移動:

      ..............<-- br
     .
    .   C - F
    v /       \
A - B           G   <-- master
      \       /
        D - E

現在,我們可以要求git“將標簽br向前滑動”,並“快速”完成-一次全部提交G

git checkout br
git merge --ff-only master

當我們要求git進行合並時,如果我們告訴它--ff-only--ff-only快進),它將查看是否有一種方法可以將標簽從它指向的任何提交向前滑動到目標提交,這種情況下G (名稱master指向提交G因此merge將提交G作快速轉發目標。)在這種特殊情況下,實際上有兩種方法可以做到這一點: BCFGBDEG 任何一個都足以允許這種“快進”。

(使用--ff-only ,如果分支標簽無法快速轉發,則合並請求將被拒絕。如果沒有--ff-only ,則git會嘗試創建一個新的實際合並提交,以便標簽可以使用--no-ff ,即使已經可以進行快進, git merge也會創建一個合並,默認情況下,如果沒有任何選擇,則設置為快進,否則進行新的合並提交。)

推送需要快進屬性

如果您要求git推送我們的新master ,則允許這樣做,因為它符合“快進”測試。 當我們進行推送時,我們將告訴github:“請提交commit CFG ,然后將標簽master (我們稱為github/master )從提交E移到提交G ”。 EG有路徑嗎? 有,所以允許。


實際上,所有git pull所做的就是運行git fetch ,然后運行git merge

不幸的是,這意味着您確實需要了解以上所有內容才能真正了解git pull

雖然這里有幾個大的皺紋。 首先,我一直在使用上面的git fetch origingit fetch github 換句話說,我一直在命名遙控器。 當您進行git pull時,遙控器從哪里來?

答案是它來自您的配置。 存儲庫中的每個分支都可以命名一個遠程:

$ git config branch.author.remote github

現在,分支author的“遠程”是github

其次,如果您運行git merge ,則必須告訴它要合並的內容。 當您執行git pull時,合並名稱從何而來?

答案仍然是,它來自配置。 每個分支可以命名一個上游合並分支:

$ git config branch.author.merge author

Git將mergeremote merge ,因此在這兩個git config命令之后, git pull本質上可以進行git merge github/author

我說“基本上”是因為還有另外一個折衷:在較舊版本的git中, pull運行以不更新遠程分支名稱的方式進行fetch 而是使用特殊的FETCH_HEAD文件。 (在較新版本的git中,它仍然使用FETCH_HEAD但它FETCH_HEAD更新遠程分支名稱。)

最后,有一個很大的難題:您可以配置git pull來使用git rebase而不是git merge 但是這個答案現在已經足夠完整了。 我不會涉及這些細節。

暫無
暫無

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

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