Git 沙盒模擬實戰(遠程篇)
>---基礎篇
遠程倉庫
遠程倉庫並不復雜, 在如今的雲計算盛行的世界很容易把遠程倉庫想象成一個富有魔力的東西, 但實際上它們只是你的倉庫在另個一台計算機上的拷貝。你可以通過因特網與這台計算機通信 —— 也就是增加或是獲取提交記錄
話雖如此, 遠程倉庫卻有一系列強大的特性
- 首先也是最重要的的點, 遠程倉庫是一個強大的備份。本地倉庫也有恢復文件到指定版本的能力, 但所有的信息都是保存在本地的。有了遠程倉庫以后,即使丟失了本地所有數據, 你仍可以通過遠程倉庫拿回你丟失的數據。
- 還有就是, 遠程讓代碼社交化了! 既然你的項目被托管到別的地方了, 你的朋友可以更容易地為你的項目做貢獻(或者拉取最新的變更)
現在用網站來對遠程倉庫進行可視化操作變得越發流行了(像 Github ), 但遠程倉庫永遠是這些工具的頂梁柱, 因此理解其概念非常的重要!
使用clone命令將遠程倉庫中的項目克隆到本地。
$ git clone [remoteName]
remoteName: 遠程倉庫的地址(名稱)
Git Fetch
Git 遠程倉庫相當的操作實際可以歸納為兩點:向遠程倉庫傳輸數據以及從遠程倉庫獲取數據。既然我們能與遠程倉庫同步,那么就可以分享任何能被 Git 管理的更新(因此可以分享代碼、文件、想法、情書等等)。
本節課我們將學習如何從遠程倉庫獲取數據 —— 命令如其名,它就是 git fetch。
你會看到當我們從遠程倉庫獲取數據時, 遠程分支也會更新以反映最新的遠程倉庫。在上一了我們已經提及過這一點了。

虛線為遠程倉庫,實線為本地倉庫。
$ git fetch

git fetch 做了些什么
git fetch 完成了僅有的但是很重要的兩步:
- 從遠程倉庫下載本地倉庫中缺失的提交記錄
- 更新遠程分支指針(如
o/master)
git fetch 實際上將本地倉庫中的遠程分支更新成了遠程倉庫相應分支最新的狀態。
如果你還記得上一節課程中我們說過的,遠程分支反映了遠程倉庫在你最后一次與它通信時的狀態,git fetch 就是你與遠程倉庫通信的方式了!希望我說的夠明白了,你已經了解 git fetch 與遠程分支之間的關系了吧。
git fetch 通常通過互聯網(使用 http:// 或 git:// 協議) 與遠程倉庫通信。
git fetch 不會做的事
git fetch 並不會改變你本地倉庫的狀態。它不會更新你的 master 分支,也不會修改你磁盤上的文件。
理解這一點很重要,因為許多開發人員誤以為執行了 git fetch 以后,他們本地倉庫就與遠程倉庫同步了。它可能已經將進行這一操作所需的所有數據都下載了下來,但是並沒有修改你本地的文件。我們在后面的課程中將會講解能完成該操作的命令。
所以, 你可以將 git fetch 的理解為單純的下載操作。
Git Pull
既然我們已經知道了如何用 git fetch 獲取遠程的數據, 現在我們學習如何將這些變化更新到我們的工作當中。
其實有很多方法的 —— 當遠程分支中有新的提交時,你可以像合並本地分支那樣來合並遠程分支。也就是說就是你可以執行以下命令:
git cherry-pick o/mastergit rebase o/mastergit merge o/master- 等等
實際上,由於先抓取更新再合並到本地分支這個流程很常用,因此 Git 提供了一個專門的命令來完成這兩個操作。它就是我們要講的 git pull。

$ git pull

git pull 就是 git fetch 和 git merge <just-fetched-branch> 的縮寫!
模擬團隊合作
這里有一件棘手的事 —— 為了接下來的課程, 我們需要先教你如何制造遠程倉庫的變更。
這意味着,我們需要“假裝”你的同事、朋友、合作伙伴更新了遠程倉庫,有可能是某個特定的分支,或是幾個提交記錄。
為了做到這點,我們引入一個自造命令 git fakeTeamwork!它的名稱已經說明了一切,先看演示..

$ git clone
local branch "master" set to track remote branch "o/master"
$ git commit -m "c2"
$ git commit -m "c3"
$ git push
$ git checkout c1
$ git commit -m "c4"
警告!現在是分離 HEAD 狀態
$ git checkout master
$ git merge c4

Git Push
OK,我們已經學過了如何從遠程倉庫獲取更新並合並到本地的分支當中。這非常棒……但是我如何與大家分享我的成果呢?
嗯,上傳自己分享內容與下載他人的分享剛好相反,那與 git pull 相反的命令是什么呢?git push!
git push 負責將你的變更上傳到指定的遠程倉庫,並在遠程倉庫上合並你的新提交記錄。一旦 git push 完成, 你的朋友們就可以從這個遠程倉庫下載你分享的成果了!
你可以將 git push 想象成發布你成果的命令。它有許多應用技巧,稍后我們會了解到,但是咱們還是先從基礎的開始吧……
注意 —— git push 不帶任何參數時的行為與 Git 的一個名為 push.default 的配置有關。它的默認值取決於你正使用的 Git 的版本,但是在教程中我們使用的是 upstream。 這沒什么太大的影響,但是在你的項目中進行推送之前,最好檢查一下這個配置。

$ git push
過去了, 遠程倉庫接收了 C2,遠程倉庫中的 master 分支也被更新到指向 C2 了,我們的遠程分支 (``o/master`) 也同樣被更新了。所有的分支都同步了!

沖突處理

git push 失敗是因為你最新提交的 C3 基於遠程分支中的 C1。而遠程倉庫中該分支已經更新到 C2 了,所以 Git 拒絕了你的推送請求。
$ git fakeTeamwork
$ git fetch
$ git commit -m "c3"
$ git rebase c2
$ git push

用 git fetch 更新了本地倉庫中的遠程分支,然后用 rebase 將我們的工作移動到最新的提交記錄下,最后再用 git push 推送到遠程倉庫。
origin 和它的周邊
合並特性分支
既然你應該很熟悉 fetch、``pull、push` 了,現在我們要通過一個新的工作流來測試你的這些技能。
在大型項目中開發人員通常會在(從 master 上分出來的)特性分支上工作,工作完成后只做一次集成。這跟前面課程的描述很相像(把 side 分支推送到遠程倉庫),不過本節我們會深入一些.
但是有些開發人員只在 master 上做 push、pull —— 這樣的話 master 總是最新的,始終與遠程分支 (o/master) 保持一致。
對於接下來這個工作流,我們集成了兩個步驟:
- 將特性分支集成到
master上 - 推送並更新遠程分支

$ git pull -rebase
$ git push
執行了兩個命令:
- 將我們的工作
rebase到遠程分支的最新提交記錄 - 向遠程倉庫推送我們的工作

fetch遠程倉庫的更新到本地倉庫,進行rebase合並,最后push到遠程倉庫中。

$ git fetch
$ git rebase o/master side1
$ git rebase side1 side2
$ git rebase side2 side3
$ git rebase side3 master
$ git push

為什么不用 merge 呢?
為了 push 新變更到遠程倉庫,你要做的就是包含遠程倉庫中最新變更。意思就是只要你的本地分支包含了遠程分支(如 o/master)中的最新變更就可以了,至於具體是用 rebase 還是 merge,並沒有限制。
那么既然沒有規定限制,為何前面幾節都在着重於 rebase 呢?為什么在操作遠程分支時不喜歡用 merge 呢?
在開發社區里,有許多關於 merge 與 rebase 的討論。以下是關於 rebase 的優缺點:
優點:
Rebase使你的提交樹變得很干凈, 所有的提交都在一條線上
缺點:
Rebase修改了提交樹的歷史
比如, 提交 C1 可以被 rebase 到 C3 之后。這看起來 C1 中的工作是在 C3 之后進行的,但實際上是在 C3 之前。
一些開發人員喜歡保留提交歷史,因此更偏愛 merge。而其他人(比如我自己)可能更喜歡干凈的提交樹,於是偏愛 rebase。仁者見仁,智者見智。

$ git fetch
$ git checkout side1
$ git merge o/master
$ git merge side2
$ git merge side3
$ git checkout master
$ git merge side1
$ git push

遠程跟蹤分支
在前幾節課程中有件事兒挺神奇的,Git 好像知道 master 與 o/master 是相關的。當然這些分支的名字是相似的,可能會讓你覺得是依此將遠程分支 master 和本地的 master 分支進行了關聯。這種關聯在以下兩種情況下可以清楚地得到展示:
pull操作時, 提交記錄會被先下載到o/master上,之后再合並到本地的master分支。隱含的合並目標由這個關聯確定的。push操作時, 我們把工作從master推到遠程倉庫中的master分支(同時會更新遠程分支o/master) 。這個推送的目的地也是由這種關聯確定的!
遠程跟蹤
直接了當地講,master 和 o/master 的關聯關系就是由分支的“remote tracking”屬性決定的。master 被設定為跟蹤 o/master —— 這意味着為 master 分支指定了推送的目的地以及拉取后合並的目標。
你可能想知道 master 分支上這個屬性是怎么被設定的,你並沒有用任何命令指定過這個屬性呀!好吧, 當你克隆倉庫的時候, Git 就自動幫你把這個屬性設置好了。
當你克隆時, Git 會為遠程倉庫中的每個分支在本地倉庫中創建一個遠程分支(比如 o/master)。然后再創建一個跟蹤遠程倉庫中活動分支的本地分支,默認情況下這個本地分支會被命名為 master。
克隆完成后,你會得到一個本地分支(如果沒有這個本地分支的話,你的目錄就是“空白”的),但是可以查看遠程倉庫中所有的分支(如果你好奇心很強的話)。這樣做對於本地倉庫和遠程倉庫來說,都是最佳選擇。
這也解釋了為什么會在克隆的時候會看到下面的輸出:
local branch "master" set to track remote branch "o/master"
我能自己指定這個屬性嗎?
當然可以啦!你可以讓任意分支跟蹤 o/master, 然后該分支會像 master 分支一樣得到隱含的 push 目的地以及 merge 的目標。 這意味着你可以在分支 totallyNotMaster 上執行 git push,將工作推送到遠程倉庫的 master 分支上。
有兩種方法設置這個屬性,第一種就是通過遠程分支檢出一個新的分支,執行:
git checkout -b totallyNotMaster o/master
就可以創建一個名為 totallyNotMaster 的分支,它跟蹤遠程分支 o/master。
在不檢出 master 分支的情況下將工作推送到的遠程倉庫中的 master 分支上。

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

$ git push origin master
$ git push origin foo

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

$ git push origin foo^:master

如果你要推送到的目的分支不存在會怎么樣呢?沒問題!Git 會在遠程倉庫中根據你提供的名稱幫你創建這個分支!

$ git push origin master^:foo
$ git push origin foo:master

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

$ git fetch origin master^1:foo
$ git fetch origin foo:master
$ git checkout foo
$ git merge master

會讓人挨揍的 source
Git 有兩種關於 source 的用法是比較詭異的,即你可以在 git push 或 git fetch 時不指定任何 source,方法就是僅保留冒號和 destination 部分,source 部分留空。
-
git push origin :side如果
push空source到遠程倉庫會如何呢?它會刪除遠程倉庫中的分支!慎重使用,刪除別人的遠程分支可能會挨揍。 -
git fetch origin :bugFix如果
fetch空source到本地,會在本地創建一個新分支。
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 參數)。
通過指定 master 我們更新了 o/master。然后將 o/master merge 到我們的檢出位置,無論我們當前檢出的位置(checkout的位置、HEAD所在位置)是哪。
需要下載一些提交,然后創建一些新分支,再合並這些分支到其它分支, 但這用不了幾個命令。

$ git pull origin bar:foo
$ git pull origin master:side

