此為git命令詳解的第七篇
這章我們可以來逐漸揭開 git push、fetch 和 pull 的神秘面紗了。我們會逐個介紹這幾個命令,它們在理念上是非常相似的。
git push:
首先來看 git push。在遠程跟蹤課程中,你已經學到了 Git 是通過當前檢出分支的屬性來確定遠程倉庫以及要 push 的目的地的。這是未指定參數時的行為,我們可以為 push 指定參數,語法是:
git push <remote> <place>
<place> 參數是什么意思呢?我們稍后會深入其中的細節, 先看看例子, 這個命令是:
git push origin master
把這個命令翻譯過來就是:
切到本地倉庫中的“master”分支,獲取所有的提交,再到遠程倉庫“origin”中找到“master”分支,將遠程倉庫中沒有的提交記錄都添加上去,搞定之后告訴我。
我們通過“place”參數來告訴 Git 提交記錄來自於 master, 要推送到遠程倉庫中的 master。它實際就是要同步的兩個倉庫的位置。
需要注意的是,因為我們通過指定參數告訴了 Git 所有它需要的信息, 所以它就忽略了我們所檢出的分支的屬性!
我們來看看指定參數的例子,注意下我們當前檢出的位置

執行命令:git checkout C0
git push origin master

好了! 通過指定參數, 遠程倉庫中的 master 分支得到了更新。
如果不指定參數會發生什么呢?

執行命令:git checkout C0
git push

命令失敗了(正如你看到的,什么也沒有發生)! 因為我們所檢出的 HEAD 沒有跟蹤任何分支
<place>參數詳解
還記得之前課程說的吧,當為 git push 指定 place 參數為 master 時,我們同時指定了提交記錄的來源和去向。
你可能想問 —— 如果來源和去向分支的名稱不同呢?比如你想把本地的 foo 分支推送到遠程倉庫中的 bar 分支。
哎,很遺憾 Git 做不到…… 開個玩笑,別當真!當然是可以的啦 :) Git 擁有超強的靈活性(有點過於靈活了)
接下來咱們看看是怎么做的……
要同時為源和目的地指定 <place> 的話,只需要用冒號 : 將二者連起來就可以了:
git push origin <source>:<destination>
這個參數實際的值是個 refspec,“refspec” 是一個自造的詞,意思是 Git 能識別的位置(比如分支 foo 或者 HEAD~1)
一旦你指定了獨立的來源和目的地,就可以組織出言簡意賅的遠程操作命令了,讓我們看看演示!
記住,source 可以是任何 Git 能識別的位置:

執行命令:git push origin foo^:master

這是個另人困惑的命令,但是它確實是可以運行的 —— Git 將 foo^ 解析為一個位置,上傳所有未被包含到遠程倉庫里 master 分支中的提交記錄。
如果你要推送到的目的分支不存在會怎么樣呢?沒問題!Git 會在遠程倉庫中根據你提供的名稱幫你創建這個分支!

執行命令:git push origin master:newBranch

Git fetch 的參數
我們剛學習了 git push 的參數,很酷的 <place> 參數,還有用冒號分隔的 refspecs(<source>:<destination>)。 這些參數可以用於 git fetch 嗎?
你猜中了!git fetch 的參數和 git push 極其相似。他們的概念是相同的,只是方向相反罷了(因為現在你是下載,而非上傳)
讓我們逐個討論下這些概念……
<place> 參數
如果你像如下命令這樣為 git fetch 設置 <place> 的話:
git fetch origin foo
Git 會到遠程倉庫的 foo 分支上,然后獲取所有本地不存在的提交,放到本地的 o/foo 上。
來看個例子(還是前面的例子,只是命令不同了)

執行命令:git fetch origin foo

你可能會好奇 —— 為何 Git 會將新提交放到 o/foo 而不是放到我本地的 foo 分支呢?之前不是說這樣的 <place> 參數就是同時應用於本地和遠程的位置嗎?
好吧, 本例中 Git 做了一些特殊處理,因為你可能在 foo 分支上的工作還未完成,你也不想弄亂它。還記得在 git fetch 課程里我們講到的嗎 —— 它不會更新你的本地的非遠程分支, 只是下載提交記錄(這樣, 你就可以對遠程分支進行檢查或者合並了)。
“如果我們指定 <source>:<destination> 會發生什么呢?”
如果你覺得直接更新本地分支很爽,那你就用冒號分隔的 refspec 吧。不過,你不能在當前檢出的分支上干這個事,但是其它分支是可以的。
這里有一點是需要注意的 —— source 現在指的是遠程倉庫中的位置,而 <destination> 才是要放置提交的本地倉庫的位置。它與 git push 剛好相反,這是可以講的通的,因為我們在往相反的方向傳送數據。
理論上雖然行的通,但開發人員很少這么做。我在這里介紹它主要是為了從概念上說明 fetch 和 push 的相似性,只是方向相反罷了。
來看個瘋狂的例子:

執行命令:git fetch origin foo:bar

哇! 看見了吧, Git 將 foo~1 解析成一個 origin 倉庫的位置,然后將那些提交記錄下載到了本地的 bar 分支(一個本地分支)上。注意由於我們指定了目標分支,foo 和 o/foo 都沒有被更新。
如果執行命令前目標分支不存在會怎樣呢?我們看一下上個對話框中沒有 bar 分支的情況。

執行命令:git fetch origin foo:bar

看見了吧,跟 git push 一樣,Git 會在 fetch 前自己創建立本地分支, 就像是 Git 在 push 時,如果遠程倉庫中不存在目標分支,會自己在建立一樣。
沒有參數呢?
如果 git fetch 沒有參數,它會下載所有的提交記錄到各個遠程分支……

執行:git fetch

古怪的 <source>
Git 有兩種關於 <source> 的用法是比較詭異的,即你可以在 git push 或 git fetch 時不指定任何 source,方法就是僅保留冒號和 destination 部分,source 部分留空。
git push origin :sidegit fetch origin :bugFix
我們分別來看一下這兩條命令的作用……
執行命令:git push origin :foo

就是這樣子, 我們通過給 push 傳空值 source,成功刪除了遠程倉庫中的 foo 分支, 這真有意思...(所以我們以后要慎用這個,萬一搞丟了分支還得麻煩)
如果 fetch 空 <source> 到本地,會在本地創建一個新分支。

執行命令:git fetch origin :bar

很神奇吧!但無論怎么說, 這就是 Git!
Git pull 參數
既然你已經掌握關於 git fetch 和 git push 參數的方方面面了,關於 git pull 幾乎沒有什么可以說的了
因為 git pull 到頭來就是 fetch 后跟 merge 的縮寫。你可以理解為用同樣的參數執行 git fetch,然后再 merge 你所抓取到的提交記錄。
還可以和其它更復雜的參數一起使用, 來看一些例子:
以下命令在 Git 中是等效的:
git pull origin foo 相當於:
git fetch origin foo; git merge o/foo
還有...
git pull origin bar~1:bugFix 相當於:
git fetch origin bar~1:bugFix; git merge bugFix
看到了? git pull 實際上就是 fetch + merge 的縮寫, git pull 唯一關注的是提交最終合並到哪里(也就是為 git fetch 所提供的 destination 參數)
一起來看個例子吧:
如果我們指定要抓取的 place,所有的事情都會跟之前一樣發生,只是增加了 merge 操作

執行命令:git pull origin master

看到了吧! 通過指定 master 我們更新了 o/master。然后將 o/master merge 到我們的檢出位置,無論我們當前檢出的位置是哪。
pull 也可以用 source:destination 嗎? 當然嘍, 看看吧:

執行命令:git pull origin master:foo

哇, 這個命令做的事情真多。它先在本地創建了一個叫 foo的分支,從遠程倉庫中的 master 分支中下載提交記錄,並合並到 foo,然后再 merge 到我們的當前檢出的分支 bar上。操作夠多的吧?!
