簡體   English   中英

git fetch 和 git pull 關系

[英]Git fetch and git pull relationship

查找git pullgit fetch之間的區別,許多消息來源說git pull是 fetch 的超集,即git pull是 fetch + merge。

但是,我似乎記得很多次git pull告訴我一切都是最新的,但是 fetch 產生了新信息。

有人可以解釋理論與現實之間的這種差異嗎?

拉確實是提取加合並。

除非它不是。

不是什么時候? 當它是 fetch 加 rebase 時,或者 - 很少 - fetch 加 checkout 時。 但在所有三種情況下,它仍然是:

  1. git fetch ,然后是
  2. 第二個 Git 命令對獲取的提交做一些事情。

這變得復雜的地方並不在於第二個命令——盡管第二個命令確實使事情復雜化——而是來自git pull傳遞的參數 由於git pull正在運行另外兩個 Git 命令,並且 Git 命令的操作取決於它們的選項和參數,因此git pull傳遞git fetch和第二個命令選項和參數很重要,無論它可能是什么。

旁白:回顧歷史

在 Git 的早期,沒有像origin這樣的“遠程”,這意味着也沒有“遠程跟蹤名稱”。 你會運行:

git fetch git://name-of-linus-torvalds-machine/repos/foo.git

從 Linus 獲取東西,然后運行git merge FETCH_HEAD或類似的東西。 這很容易出錯(很容易在 URL 中出現拼寫錯誤)並且很煩人,因此 Git 獲得了一堆臨時方法來處理這個問題。

請注意,在沒有遙控器的情況下,所有git fetch可以做的就是.git/FETCH_HEAD中留下一堆信息,以便您可以確定 Linus 的 repos 中的哪些分支已經更新等等。 當然, git pull將這兩個命令合二為一,這樣您就不必運行兩個單獨的命令,而且大多數人都使用git pull 但顯然缺少了一些東西。 於是發明了遙控器

  • 我們現在有了一個簡短的簡單名稱,例如我們可以使用的origin來代替 URL。 (這消除了對文檔中仍然列出的所有用於命名遙控器的奇怪黑客的需要,但它們都仍然存在。 Named file in $GIT_DIR 。)
  • 我們現在有一種方法讓 Git保存與 Linus 最新版本相關的哈希 ID,這樣我們就不需要在本地創建大量分支。 遠程跟蹤名稱( origin/master等)接管過去需要使用本地分支名稱的工作。

但是所有這些東西仍然受到支持,其中一些仍然在一些(古代)文檔中被描述為“做事的方式”,所以你仍然可以使用舊的粗略方法。 也許有些人會。

無論如何,現在存在遠程跟蹤名稱。 但是,在 Git 1.7 和 Git 2.0 之間,對它們進行了一些更新。 具體來說,Git 1.8.4 修復了一些最終被宣布為錯誤的問題。 出於某種奇怪的原因,有些人仍在使用 Git 1.7.x,所以請注意您可能會碰到他們。

在 Git 2.11 中,舊的git pull shell 腳本正式退役。 雖然git pull仍然有效地運行git fetch后跟第二個 Git 命令,但您不能再指向 shell 腳本並說:“看,在這一行,它運行git fetch 。然后它進行了這些測試,然后最終運行這個其他命令......” 結果是它在 Windows 上運行得更快,而且更難解釋。 😀 從那以后它也獲得了一兩個功能,足以讓至少像我這樣的一些鐵桿“反拉”人現在願意實際使用這個東西。 但那是另一回事了。

你如何運行git pull

git pull命令有很多選項。 請參閱其文檔以獲取完整列表,然后將這些選項與git fetchgit rebasegit merge的選項進行比較。 請注意,拉取文檔說某些選項被傳遞給一個或另一個或兩者,並且在某些選項中有相當多的重疊(例如,所有選項都采用-q表示quiet-v表示verbose )。

但是,無論有沒有這些選項,您都可以運行:

git pull

或者:

git pull origin

或者:

git pull origin main

例如。 如果並且當您運行其中任何一個時,所有這些位置參數都會傳遞給git fetch

請注意,您甚至可以運行:

git pull origin main feature

但你幾乎肯定不應該 我們將在后面介紹為什么會這樣。

如果您提供選項,它們將按所述傳遞給 fetch 和 second-command 步驟中的一個或兩個。

fetch命令總是傳遞一個額外的選項,即--update-head-ok 拉取需要傳遞這個選項,但也需要小心,因為不小心使用它會使你當前的分支、索引和工作樹不同步。 除非您確切知道自己在做什么,否則不要自己使用此選項。

出於(至少,也許只是)歷史原因,當傳遞一些 refspec 參數時,例如git fetch origin main main中的 main , git fetch只會更新指定的 refspecs 和關聯的遠程跟蹤名稱 由於git pull將您提供的所有 refspec 參數傳遞給git fetch ,但沒有它自己的額外參數,當且僅當您在此處將 refspec 參數傳遞給git pull時, git fetch才會獲得一個 refspec 參數。

(Fetch refspecs 與 push refspecs 稍有不同: git push origin main等價於git push origin main:main ,但git fetch origin main等價於git fetch origin main:<discard>的副作用是也會更新origin/main . 如果你願意,你可以運行git fetch origin main:main ,但這要求你不在那個分支上,除了git pull安排的--update-head-ok特殊情況。)

添加第二條命令

git pull運行的第二個命令是:

  1. git merge ,默認情況下,或
  2. git rebase ,如果你告訴 Git 這樣做,或者
  3. git checkout ,在一種特殊情況下。

同樣, git pull將選項和參數傳遞第二個命令,這里事情變得一團糟。 git pull運行git merge時,它​​通過:

  • 文檔描述為傳遞的合並選項;
  • 帶有預先計算的合並消息的-m選項(除非您提供自己的-m );
  • 提交的提交哈希 ID,它是遠程分支名稱的分支提示,如所選。

最后一個是一個謎:“選定”的真正含義是什么? 好吧,讓我們回到git pull語法:

git pull
git pull origin
git pull origin main

我們知道,如果提供這些詞( originmain ),將傳遞給git fetch 他們指定遙控器,如果有第二個單詞,則指定在該遙控器上看到的分支名稱,用於git fetch操作。

如果我們提供遠程分支上的名稱, git pull要求當前分支——我們所在on分支,如git status中所說on branch main或其他——有一個上游集。 (另請參閱為什么我需要一直執行 `--set-upstream`? )上游在技術上是一對:既是遠程又是遠程分支名稱。 這些通常以更可口的遠程跟蹤名稱格式呈現給您,因此您的main的上游通常是您的origin/main ,即在origin上看到的main

如果需要,您的git pull命令將從上游提取分支名稱。 它不會將其傳遞給git fetch ,但稍后會在第二個git merge命令中使用它。 此時git pull將使用.git/FETCH_HEAD —— git fetch仍然會寫入,就像它在 Git 1.5 更廣泛發布之前在原始 Git 中所做的那樣——來找出與main over on origin關聯的提交哈希 ID。 這是git pull傳遞給git merge的哈希 ID。

換句話說,如果你在你的main並且它的上游是origin/main並且你運行:

git pull

你的 Git 將運行:

git fetch --update-head-ok

其次,如果使用git merge

git merge -m "merge branch 'main' of <url>" <hash-ID>

其中 URL 和 hash-ID 來自origin.git/FETCH_HEAD

如果您自己運行:

git fetch
git merge

您將獲得相同的效果,除了您沒有-m選項並且合並消息將是默認值,即merge branch 'origin/main' 也就是說,URL 消失並且branch main of ...措辭不同。

但是如果你運行:

git pull origin main

您的git pull命令將運行:

git fetch --update-head-ok origin main
git merge -m <same message as before> <same hash ID as before>

也就是說,額外的origin main被傳遞給git fetch ,這限制了獲取的內容

我們現在還可以看到為什么我們不應該運行:

git pull origin main feature

這將運行:

git fetch --update-head-ok origin main feature

(這本身很好),但隨后它將運行:

git merge -m <message> <hash#1> <hash#2>

也就是說,您的git pull將從.git/FETCH_HEAD中提取出兩個哈希 ID:一個對應於origin上的main ,一個對應於origin上的feature 然后它將兩個哈希 ID傳遞給一個git merge命令 這個git merge命令將執行 Git 所說的octopus merge 1

(那些剛接觸 Git 的人似乎經常期望:

git pull origin br1 br2

應該在本地檢查br1 ,獲取並合並origin/br1 ,然后在本地檢查br2 ,然后獲取並合並origin/br2 ,這可能比這個有點笨拙的順序描述更有效。 可能是有道理的,我相信我自己也曾想過這一點,但事實並非如此。)

如果你告訴 Git 使用git rebase而不是git merge ——你現在可以通過多種方式做到這一點,例如將pull.rebase設置為true ,除了提供--rebase作為git pull的選項——Git 將替換git merge命令與git rebase命令合並。 這會更改可以通過的選項集:

  • rebase 不接受-m ,所以你不能給一個;
  • rebase 不接受--ff-only--no-ff ,所以你不能給這些。

git rebase命令有一個名為autostash的模式,如果您的狀態不是“干凈”(如在git status中不會說working tree clean, nothing to commit ), git rebase將在啟動 rebase 之前運行git stash push ,並且最后git stash pop 一般來說,我不是git stash的粉絲,除非你非常擅長處理沖突,否則我建議不要使用此功能。

如果 autostash 被禁用(這是默認設置),如果狀態不是“clean”,rebase 將拒絕啟動。 使用git merge作為第二個命令,合並通常會拒絕在相同的情況下開始(盡管我記得古代 Git 版本的行為不同,在某些沖突情況下與git stash pop具有相同的混亂副作用)。

最后一種情況是很少見的。 您可以擁有一個處於特殊狀態的 Git 存儲庫,Git 對此使用兩個不同的術語:未出生分支孤立分支 這種狀態的存在部分是因為一個新的、完全空的存儲庫根本沒有提交。

Git 中的分支名稱必須包含某個有效的現有提交的哈希 ID。 但是當你運行git init並創建一個新的、完全的存儲庫時,沒有提交。 沒有提交,就沒有分支。 然而, git status會說你在某個分支上,並且還沒有提交,你應該做第一個。

在這種狀態下——這個孤兒/未出生的分支狀態——你做出的下一個提交將是一個根提交,它在一個新的空存儲庫中是你通常想要的:這是有史以來的第一次提交,它開始存在歷史。 現在你有一個提交,你可以在它的基礎上進行構建。

但是,當您未出生分支狀態下運行git pull時, git pull操作可能會從遠程(例如來自origin )獲得一堆提交。 第二個命令應該按照剩余的git pull參數的指示將git pull獲得的那些新提交與當前分支上的提交結合起來。 當前分支上沒有提交(不存在),但零加上某事是某事,對吧? 所以git pull聲明這個 pull-into-empty-repository 的結果是你應該檢查git pull -ed 分支頂端的提交。 那是:

git init
git remote add origin <url>
git pull origin main

應該讓你的 Git 訪問給定的 URL,找到他們的main ,從他們的 Git 獲取提交,創建你的origin/main ,然后創建你自己的main ,它與你的 Git 剛剛創建的origin/main完全匹配他們的main

最后一步的事情是創建分支git checkout -bgit switch -c ,這就是git pull將在這里做的事情。 一個錯誤,在 Git 1.5 或 1.6 左右,如果你的工作樹是非空的,這個git pull命令會完全清除它。這個錯誤至少咬了我一次,至少是部分原因我學會了避免git pull 。這個 bug 已經修復很久了,但我通常喜歡 fetch、 inspect和 merge-or-rebase,我需要在 fetch 和第二個之間運行git log來進行檢查——或者相反,第三個命令。所以我仍然充其量僅少量使用git pull 。但它現在pull.ff only作為配置項,這涵蓋了我最常見的情況,所以我正在慢慢適應它。)


1有關章魚合並的更多信息,請參閱git merge文檔 請注意,如果兩個哈希 ID 相同,則此章魚合並的效果與常規合並的效果基本相同,只是章魚合並無法處理沖突。 至少,現在還沒有: Junio Hamano 正在思考新的merge-ort是否能夠解決這個問題。

我不清楚這是一個好主意。 事實上,我有點清楚章魚合並較弱,並且無法處理合並沖突,是一件好事。


關於什么 ...

但是,我似乎記得很多次 git pull 告訴我一切都是最新的,但是 fetch 產生了新信息。

如果您運行git pull origin main並獲取最新消息,則您當前的分支已合並origin/main ,此處無事可做。 但是,如果您隨后運行git fetch origin (或只是git fetch ),您將獲取他們所有的分支名稱,並更新您所有的遠程跟蹤名稱。

如果當前分支的上游origin/main ,則可以運行:

git pull

代替:

git pull origin main

並且git pull運行的git fetch將不僅限於獲取它們的main

但是,我似乎記得很多次 git pull 告訴我一切都是最新的,但是 fetch 產生了新信息。

這僅僅是因為 Git 如何報告發生的事情。

git fetch更新所有遠程跟蹤分支並對此進行報告,因此遠程站點上的任何新提交都會產生某種輸出。

但是git pull ,雖然它也執行git fetch ,但只報告當前本地分支上發生的事情,即使 fetch 確實將大量提交帶入遠程跟蹤分支,這也可能什么都沒有。 (另一個不使用pull的好理由!)

暫無
暫無

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

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