Git 沙盒模擬實戰(遠程篇)
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
通常通過互聯網(使用 //
或 git://
協議) 與遠程倉庫通訊。
git fetch 不會做的事
git fetch
並不會改變你本地倉庫的狀態。它不會更新你的 master
分支,也不會修改你磁碟上的文件。
理解這一點很重要,因為許多開發人員誤以為執行了 git fetch
以後,他們本地倉庫就與遠程倉庫同步了。它可能已經將進行這一操作所需的所有數據都下載了下來,但是並沒有修改你本地的文件。我們在後面的課程中將會講解能完成該操作的命令。
所以, 你可以將 git fetch
的理解為單純的下載操作。
Git Pull
既然我們已經知道了如何用 git fetch
獲取遠程的數據, 現在我們學習如何將這些變化更新到我們的工作當中。
其實有很多方法的 —— 當遠程分支中有新的提交時,你可以像合併本地分支那樣來合併遠程分支。也就是說就是你可以執行以下命令:
git cherry-pick o/master
git rebase o/master
git 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
內容來源:碼雲 — Learn Git Branching(遠程篇)