[英]Git fetch and git pull relationship
查找git pull
和git fetch
之間的區別,許多消息來源說git pull
是 fetch 的超集,即git pull
是 fetch + merge。
但是,我似乎記得很多次git pull
告訴我一切都是最新的,但是 fetch 產生了新信息。
有人可以解釋理論與現實之間的這種差異嗎?
拉確實是提取加合並。
除非它不是。
不是什么時候? 當它是 fetch 加 rebase 時,或者 - 很少 - fetch 加 checkout 時。 但在所有三種情況下,它仍然是:
git fetch
,然后是 這變得復雜的地方並不在於第二個命令——盡管第二個命令確實使事情復雜化——而是來自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
。)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 fetch
和git rebase
和git 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
運行的第二個命令是:
git merge
,默認情況下,或git rebase
,如果你告訴 Git 這樣做,或者git checkout
,在一種特殊情況下。 同樣, git pull
將選項和參數傳遞給第二個命令,這里事情變得一團糟。 當git pull
運行git merge
時,它通過:
-m
選項(除非您提供自己的-m
); 加 最后一個是一個謎:“選定”的真正含義是什么? 好吧,讓我們回到git pull
語法:
git pull
git pull origin
git pull origin main
我們知道,如果提供這些詞( origin
和main
),將傳遞給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
命令合並。 這會更改可以通過的選項集:
-m
,所以你不能給一個;--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 -b
或git 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.