1-push master!
1-1-說明
1-1-1-merge feature branch
現在你已經很熟悉 fetch、pull 以及 push,讓我們來針對一個新的問題來應用這些技巧。
在一個大型的專案裡面,程式設計師經常會在 feature branch(有別於 master branch)上面做開發,之後等開發完之後,在一次 merge 回去。這跟之前的課程是很類似的(其它的 branch 被 push 到 remote),但是現在我們還要再多介紹一個步驟。
某些程式設計師只針對 master branch 進行 push 或者是 pull。這樣子的話 master 一直都保持跟 remote ( o/master) 同步。
所以針對這個問題我們結合了兩件事情:
- merge feature branch 到 master branch,並且
- push remote 以及 pull remote
讓我們馬上來實際看一下如何更新 master 並且 push 到 remote。
1 2 | git pull --rebase git push |
我們在這裡執行兩個指令:
- rebase 我們的 branch 到 remote 的新的 commit 上面,並且
- 發佈到 remote 上面
1-2-解題
這個關卡很複雜,這裡有一些提示:
- 總共有三個 feature branch,分別是 side1, side2 以及 side3
- 我們想要將這三個 branch 分別 push 到 remote。
- 但是 remote 已經被事先更新過了,所以我們必須要先同步那些更新。
:O 很好!祝你好運,完成這個關卡是一個很重要的步驟。
1 2 3 4 5 6 7 8 | git checkout master git pull --rebase git rebase master side1 git rebase side1 side2 git rebase side2 side3 git rebase master side3 git rebase side3 master git push |
1 2 3 4 5 6 | git fetch // 遠端分支有新的 commit ,先更新最新的 o/master 分支 git rebase o/master side1 // 本地端 o/master 分支為基底,將 side1 分支接過來 git rebase side1 side2 // 本地端 side1 分支為基底,將 side2 分支接過來 git rebase side2 side3 // 本地端 side2 分支為基底,將 side3 分支接過來 git rebase side3 master // 本地端 side3 分支為基底,將 master 分支接過來,目前為止就會到最新的 commit git push // 將最新的 commit 推上 |
2-merge with remotes
2-1-說明
2-1-1-為何不要 merge?
為了要 push 新的 commit 給 remote,你只需要做的是先同步 remote 的更新,那就表示你可以使用 rebase 或者是 merge remote branch (例如, o/master)。
所以假如你已經學會使用其中一個方式了,那為什麼我們到目前為止還在強調 rebase?為什麼當提到 remote 的時候,反而 merge 比較沒有受到關注?
在程式發展的社群中,關於 merge 以及 rebase 的孰優孰劣有很多的爭論。這裡我們會提到關於 rebase 的優點及缺點:
優點: rebase 使得你的 commit tree 看起來更為簡潔,因為任何的 commit 都在一條直線上面。
缺點: rebase 修改了 commit tree 的歷史紀錄。
舉例來說,我們可以 rebase commit C1,將 C1 接在過去的 C3 上面,那麼就可以表現出 C1 是出現在 C3 的後面。
有一些程式設計師喜歡保留歷史紀錄,因此他們會比較喜歡 merge; 其他人(例如我自己)比較喜歡一個簡潔的 commit tree,因此他們比較喜歡 rebase。這些都是擇你所愛。:D
2-2-解題
在這個關卡中,我們面對的是之前關卡的題目,但是我們採用的是 merge,這可能會讓你感覺到有點困難,但是確實有講到重點。
1 2 3 4 5 6 | git checkout master git pull git merge side1 git merge side2 git merge side3 git push |
3-remote tracking
3-1-說明
3-1-1-remote tracking branch
在之前的課程中,有一件事情看起來很”神奇”,那就是 git 知道 master branch 是對應到 o/master branch。當然這些 branch 有類似的名稱,所以可以大概猜到, local 的 master branch 可以對應到 remote 的 master branch,但是我們是在兩種情況下可以確定有這個對應關係:
- 在使用 pull 的時候,下載 commit 到 o/master,並且 merge 這些 commit 到 master branch,這就表示這個 merge 的目標是決定於這個對應關係。
- 在使用 push 的時候,在 master branch 上面的 commit 被 push 到 remote 上面的 master branch (它在 local 被表示成 o/master),這就表示 push 的目標是決定於 master 以及 o/master 之間的對應關係。
3-1-2-Remote tracking
長話短說,我們可以用 branch 上面的 “remote tracking” 特性來表示介於 master 以及 o/master 的對應關係, master branch 被設定用來追蹤(track) o/master,這就表示對於 master branch 來說的話,有一個 merge 的目標以及 push 的目標。
你可能會覺得很奇怪,當你沒有下任何指令去設定的時候,關於 master branch 的對應關係是如何被設定的。喔!其實當你 clone 一個 repo 的時候,其實就已經自動幫你做設定了。
在做 clone 的時候,git 會針對每一個在 remote 上面的 branch 建立一個 branch (例如 o/master),之後它會建立一個 local branch 來追蹤目前在 remote 上面的 active branch,在大部份的情況下,幾乎都是設定 master branch。
一旦 git 完成這個動作,你就只會有一個 local branch ,但是你可以看到所有在 remote 上面的不同的 branch,對於 local 和 remote 來說的話,這樣子是最好的!
這也解釋了為什麼當你 clone 的時候可能會看到以下被輸出的指令:
local branch "master" set to track remote branch "o/master"
3-1-3-我可以自己設定嗎?
是的你可以!你可以設定任何的 branch 來 track o/master, 假如你真的這麼做的話,那麼該 branch 的 push 及 merge 的目標就會跟 master 一樣。這就表示說你可以在 totallyNotMaster branch 上面執行 git push,並且 push 你的 commit 到 remote 的 master branch!
有兩個方式可以設定,第一個就是藉由參考一個 remote branch 來 checkout 一個新的 branch。執行
git checkout -b totallyNotMaster o/master
建立一個新的 totallyNotMaster branch 並且它會 track o/master。
說的好多,我們現在來看一個例子!我們會 checkout 一個新的 foo branch,而且該 branch 會被用來 track remote 上的 master branch。
1 2 | git checkout -b foo o/master git pull |
就像你看到的,當 o/master 更新的時候, foo branch 也跟著一起被更新,要注意 master 並沒有被更新!
同樣適用於 git push
1 2 3 | git checkout -b foo o/master git commit git push |
哇,即使我們的 branch 名稱完全一點關係都沒有,但我們還是 push 了 commit 到 remote 的 master branch 上面。
3-1-4-方法 #2
另外一個設定 remote tracking 的方法是使用 git branch -u 這一個指令,執行
git branch -u o/master foo
你就會看到 foo branch 被設定成 track o/master,如果你現在已經 checkout 到 foo 這個 branch 上面了,你就可以省略掉它:
git branch -u o/master
我們來看這個快速設定 remote tracking 的方法…
1 2 3 | git branch -u o/master foo git commit git push |
跟之前一樣,就只是一個更加明確的指令,讚啦!
3-2-解題
好!在這個關卡中,我們要 push 我們的 commit 到 remote 上面的 master branch,但是我們不 checkout 到 local 的 master branch。因為這是一個進階的課程,所以我會讓你明白其它的東西。:P
1 2 3 4 | git checkout -b side o/master git commit git pull --rebase git push |
git checkout -b side o/master 是指建立 side 分支分由 o/master 的遠端分支所開出的分支,雖然 master 的本地分支同在 C1 的 commit 上,不過如果是由本地端所長出的分支,無法由遠端分支所長出來的分支 side 透過指令 git pull --rebase 將 side 所增加的 C3 的 commit ,接進遠端新的 C2 commit 記錄的基底在多加上 C3' 的 commit。
4-git push 的參數
4-1-說明
太好了!現在你已經明白了 remote tracking,我們可以開始聊 git push、fetch 以及 pull 的一些有趣的地方,我們一次會講解一個指令,但是它們之間的概念是很類似的。
首先我們來看一下 git push,你已經在 remote tracking 的課程中學習到 git 是根據目前 checkout 的 branch 所 track 的 remote branch 來做 push,這是在沒有任何的參數的情況下的預設動作,但是 git push 允許我們可以加上一些參數:
1 | git push <remote> <place> |
<place> 這個參數表示什麼? 我們等一下會提到細節,但是先來看一個例子,執行以下的指令:
1 | git push origin master |
將這段解釋成中文:
先到我的 repo 中的 “master” branch,抓下所有的 commit,然後到叫作 “origin” 的 remote 的 “master” branch,檢查 remote 的 commit 有沒有跟我的 repo 一致,如果沒有,就更新。
將 master 當作 “place” 這個參數,我們告訴 git 這些 commit 是從哪裡來的,而且它們要往哪裡去。對於要同步兩個 repo, “place” 或者是 “location” 是非常重要的。
要記住喔,因為我們告訴 git 它所要知道的(有兩個參數),因此它才不會管你現在所 checkout 的 branch!
讓我們來看一個有加上參數的例子,在這個例子中,要特別注意到我們所 checkout 的位置。
1 2 | git checkout C0 git push origin master |
我說的沒錯吧!因為我們加上了參數,所以在 remote 上的 master branch 更新了。
假如我們沒有特別指令參數會發生什麼事情?
1 2 | git checkout C0 git push |
指令會失敗(就像你看到的),因為 HEAD 並沒有指向一個有 track remote branch 的 branch 上面阿。
4-2-解題
好的,在這個關卡中,我們要更新在 remote 上的 foo 以及 master branch,比較遺憾的是 git checkout 在這個關卡中是不被允許的喔!
1 2 | git push origin master git push origin foo |
5-git push 的參數,延伸討論!
5-1-說明
5-1-1- 這個參數的細節
回想一下,我們在之前的課程中提到,當我們用 git push 並且把 master 當作 <place> 這個參數的時候,我們就相當於告訴 git 我們的所要更新的 commit 要從哪裡來(source) 並且要 往哪裡去(destination)。
你可能會很好奇,當我們的 source 以及 destination 是不同的時候,應該怎麼做?當你想要 push foo branch 上面的 commit 到 remote 的 bar branch 的時候,應該怎麼做?
很遺憾地,對於 git 來說這是不可能的…開玩笑的啦!當然是有可能的:)… git 有非常非常大的彈性(太超過了啦)。
讓我們來看看下一頁…
為了要指定 <place> 的 source 以及 destination,只要利用一個冒號將這兩個連在一起:
1 | git push origin <source>:<destination> |
這通常被稱為一個 colon (冒號) refspec,refspec 只是一個表示 location (位置) 的花俏的名稱,這個位置可以被 git 辨別(例如 foo branch 或是 HEAD~1 )。
一旦你單獨指定了 source 以及 destination,你可以看到花俏且準確的指令。讓我來來看一個例子!
1 | git push origin foo^:master |
哇!這實在是一個很花俏的指令但是確很合理,git 把 foo^ 解讀成一個位置,並且 push 該位置的 commit 到目前 remote 的 master branch。
如果你想要 push 的 destination 不存在怎麼辦?沒有問題!只要給一個 branch 的名稱,git 就會在 remote 幫你建立。
1 | git push origin master:newBranch |
太讚了,實在非常地簡單:D
指令的記法可以記成…
1 git push <遠端主機分支名> <推送本地 commit SAH~1 or branchName>:<本地端的遠端分支名 ( 去 o/ )>整段語法可用白話記成:「將本地端的 git commit 推送到遠端主機 origin 上, 本地端的 foo 分支推到遠端分支的 master」。
1 git push origin foo/master
5-2-解題
對於這個關卡,想辦法達到這個視覺化的目標,而且要記得格式: <source>:<destination>
1 2 | git push origin foo:master git push origin C5:foo |
6-fetch 的參數
6-1-說明
6-1-1-git fetch 的參數
我們剛學到了所有關於 git push 的參數,有非常棒的 <place> 參數,甚至是 colon refspecs( <source>:<destination> ),我們可不可以也同樣套用到 git fetch 上面?
你猜對了! git fetch 的參數非常非常類似 git push,一樣的概念,但方向不同( 因為你在下載 commit,而不是在上傳 commit )。
讓我們一次講一個概念…
6-1-2- 參數
對於 git fetch,如果你特別指定了一個 <place>:
1 | git fetch origin foo |
git 會到 remote 上的 foo branch,抓下所有不在 local 上的 commit,然後將它們放到 local 的 o/foo branch。
讓我們實際看一下(就只是一個更新的方法)。
指定一個 <place>…
1 | git fetch origin foo |
我們只下載了 foo 上的 commit,並且把它們放到 o/foo。
你也許會感到奇怪,為什麼 git 是把這些 commit 放到 o/foo branch 而不是放到我的 local 的 foo branch? 我認為, <place> 參數是表示一個位置,這個位置同時存在 local 跟 remote 上?
因為你可能已經 checkout 到 foo branch 上,而且你不想要打亂上面的 commit,因此 git 才會特別這樣做!!這就又回到之前的 git fetch 的課程,它並不會放到你的 local 上的 branch (該 branch 沒有對應到任何的 remote branch),它只會下載 commit 到 local 上且表示 remote 的 branch(所以你之後可以觀察或者 merge 它們)。
“在該例子當中,如果我特別透過 <source>:<destination> 來指定 source 以及 destination,會發生什麼事情?”
如果你很想要把 fetch 回來的 commit 直接放到 local branch,那麼你就可以利用一個 colon refspec 來做到。你不能夠把 fetch 回來的 commit 放到你目前正 checkout 的 branch,如果不是的話,git 就會允許你這麼做。
這裡只有一個重點, <source> 現在是一個在 remote 上的 branch,而且 <destination> 是一個放置這些 commit 的 local 的位置。它剛好就是 git push 的相反,而且因為我們在相反方向傳遞資料,所以這也很合理!
其實,程式設計師很少會想要做這個,我主要是強調 fetch 以及 push 的概念是很類似的,就只是方向相反而已。
讓我們來實際看一下這個瘋狂的事情:
1 | git fetch origin foo~1:bar |
哇!看到了吧,git 把 foo~1 解讀成一個在 origin 上的位置,而且把該位置上面的 commit 下載到 bar(這是一個 local branch)上面,注意,因為我們有指定目的地,因此 foo 跟 o/foo 並沒有被更新。
如果我在執行這個指令之前,destination 不存在的話會怎樣?我們回到上一個例子,但這一次事前並沒有 bar 這個 branch 的存在。
1 | git fetch origin foo~1:bar |
看到了吧,這就像是 git push,在 fetch 之前,git 會自己建立 destination,就好像是在 push 之前, git 會建立 remote 上的 destination (目的地) 一樣(如果它不存在的話)。
沒有參數的情況?
如果使用 git fetch 的時候,沒有指定任何的參數,那就相當於它會下載 remote 上面的所有的 commit,並且把這些 commit 放到 local 上面所有對應到 remote 的 branch…
1 | git fetch |
超簡單,但是所有的更新只做一次,很值得。
6-2-解題
好的,講了好多!要完成這一關,fetch 視覺化的目標所指定的 commit,好好玩這些指令吧!
對於兩個 fetch 的指令,你必須要指定 source 以及 destination,注意一下視覺化的目標,因為 commit 的 id 可以被交換!
1 2 3 4 | git fetch origin foo:master // 下載遠端分支 foo 分支的更新內容到本地端 master 分支上 git fetch origin master~1:foo // 下載遠端分支 master 退一個 commit 點 (C4) 由 C3 開始下載整個分支,到本地端 foo 支上。 git checkout foo git merge master |
7-沒有 source
7-1-說明
7-1-1-
在兩個奇怪的情況下,git 不使用 <source> 參數,事實上,在 git push 以及 git fetch 的情況下,可以允許你”不用”指定 source,你可以藉由把參數留空,來表示你不想指定 source:
1 2 | git push origin :side git fetch origin :bugFix |
讓我們來看看這些在做什麼…
當沒有指定 source 的時候, push 對於 remote branch 做了什麼? push 把它刪除掉了!
1 | git push origin :foo |
看吧,我們藉由把 source “留空”,成功用 push 刪除了 foo branch,這合理吧…
最後,對於 fetch 來說,source “留空” 表示我們要在 local 上建立一個新的 branch。
1 | git push origin :bar |
很奇怪吧!但那正是 git 為你做的事情!
7-2-解題
這是一個很簡單的關卡,只需要利用 git push 刪除一個 remote 的 branch,並且利用 git fetch 建立一個新的 local 的 branch!
1 2 | git push origin :foo // 遠端主機刪除 foo 的遠端分支 git fetch origin :bar // 本地端新加入 bar 本地分支,但遠端分支沒有新建 |
8-pull 的參數
8-1-說明
8-1-1-git pull 的參數
現在你已經知道關於 git fetch 以及 git push 的任何參數,但是我們還可以再聊聊 git pull :)
那是因為 git pull 到目前為止的確只是表示 fetch 之後再 merge 所 fetch 的 commit,你可以把它想成,當使用 git fetch 時使用一樣的參數,之後再從 fetch 下來的 commit 所放置的位置做 merge。
這同樣也適用於當你指定相當複雜的參數,讓我們來看一些例子:
對於 git 來說,有一些意義一樣的指令:
git pull origin foo 相當於:
1 2 | git fetch origin foo git merge o/foo |
而且…
git pull origin bar~1:bugFix 相當於:
1 2 | git fetch origin bar~1:bugFix git merge bugFix |
看吧? git pull 真的就只是表示 fetch 跟 merge 的一個簡化後的指令,而且 git pull 所根據的是這些 commit 要放置的位置(在 fetch 的時候所指定的 destination )。
讓我們來看一個例子:
如果我們在 fetch 的時候有指定 位置 的話,跟之前一樣,fetch 所做的事情沒有變,但是我們會 merge 我們剛剛所 fetch 的該 位置 的 commit。
1 | git pull origin master |
看吧!指定位置為 master,跟平常一樣,我們下載了 commit 並且放到 o/master 上,接著,我們會 merge o/master 到我們現在的位置,不管我們現在所 checkout 的位置在哪裡。
他是不是也可以同時指定 source 以及 destination?你說對了啦!讓我們來看一下:
1 | git pull origin master:foo |
哇!這個指令強而有力,我們在 local 建立了一個新的 foo branch,下載了 remote 的 master 的 commit,並且放到 local 的 foo branch,之後 merge foo branch 到我們目前所 checkout 的 bar branch。這實在是太超過了!!!
8-2-解題
要完成這一關,達到視覺化目標的要求,你需要下載一些 commit,建立一些新的 branch,並且 merge 這些 branch 到其他的 branch 上面,這個關卡不需要打太多的指令:P
1 2 3 4 | git fetch origin bar:foo git fetch origin master:side git merge foo git merge side |
1 2 | git pull origin bar:foo // 下載遠端分支 bar 到本端的 foo 分支上合併後得到 C5 git pull origin master:side // 下載遠端分支 master 到本端的 side 分支上合併 C6 |