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) 同步。

所以針對這個問題我們結合了兩件事情:

  1. merge feature branch 到 master branch,並且
  2. push remote 以及 pull remote

讓我們馬上來實際看一下如何更新 master 並且 push 到 remote。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-1-push master!-1

我們在這裡執行兩個指令:

  1. rebase 我們的 branch 到 remote 的新的 commit 上面,並且
  2. 發佈到 remote 上面

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-1-push master!-2

1-2-解題

這個關卡很複雜,這裡有一些提示:

  1. 總共有三個 feature branch,分別是 side1side2 以及 side3
  2. 我們想要將這三個 branch 分別 push 到 remote。
  3. 但是 remote 已經被事先更新過了,所以我們必須要先同步那些更新。

:O 很好!祝你好運,完成這個關卡是一個很重要的步驟。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-1-push master!-3

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-1-push master!-4


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,這可能會讓你感覺到有點困難,但是確實有講到重點。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-2-merge with remotes-1

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-2-merge with remotes-2


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。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-1

就像你看到的,當 o/master 更新的時候, foo branch 也跟著一起被更新,要注意 master 並沒有被更新!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-2

同樣適用於 git push
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-3

哇,即使我們的 branch 名稱完全一點關係都沒有,但我們還是 push 了 commit 到 remote 的 master branch 上面。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-4

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 的方法…
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-5

跟之前一樣,就只是一個更加明確的指令,讚啦!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-6

3-2-解題

好!在這個關卡中,我們要 push 我們的 commit 到 remote 上面的 master branch,但是我們不 checkout 到 local 的 master branch。因為這是一個進階的課程,所以我會讓你明白其它的東西。:P
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-7

git checkout -b side o/master 是指建立 side 分支分由 o/master 的遠端分支所開出的分支,雖然 master 的本地分支同在 C1 的 commit 上,不過如果是由本地端所長出的分支,無法由遠端分支所長出來的分支 side 透過指令 git pull --rebaseside 所增加的 C3 的 commit ,接進遠端新的 C2 commit 記錄的基底在多加上 C3' 的 commit。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-3-remote tracking-8


4-git push 的參數

4-1-說明

太好了!現在你已經明白了 remote tracking,我們可以開始聊 git push、fetch 以及 pull 的一些有趣的地方,我們一次會講解一個指令,但是它們之間的概念是很類似的。

首先我們來看一下 git push,你已經在 remote tracking 的課程中學習到 git 是根據目前 checkout 的 branch 所 track 的 remote branch 來做 push,這是在沒有任何的參數的情況下的預設動作,但是 git push 允許我們可以加上一些參數:

<place> 這個參數表示什麼? 我們等一下會提到細節,但是先來看一個例子,執行以下的指令:

將這段解釋成中文:

先到我的 repo 中的 “master” branch,抓下所有的 commit,然後到叫作 “origin” 的 remote 的 “master” branch,檢查 remote 的 commit 有沒有跟我的 repo 一致,如果沒有,就更新。

master 當作 “place” 這個參數,我們告訴 git 這些 commit 是從哪裡來的,而且它們要往哪裡去。對於要同步兩個 repo, “place” 或者是 “location” 是非常重要的。

要記住喔,因為我們告訴 git 它所要知道的(有兩個參數),因此它才不會管你現在所 checkout 的 branch!

讓我們來看一個有加上參數的例子,在這個例子中,要特別注意到我們所 checkout 的位置。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-1

我說的沒錯吧!因為我們加上了參數,所以在 remote 上的 master branch 更新了。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-2

假如我們沒有特別指令參數會發生什麼事情?
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-3

指令會失敗(就像你看到的),因為 HEAD 並沒有指向一個有 track remote branch 的 branch 上面阿。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-4

4-2-解題

好的,在這個關卡中,我們要更新在 remote 上的 foo 以及 master branch,比較遺憾的是 git checkout 在這個關卡中是不被允許的喔!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-5

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-4-git push 的參數-6


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,只要利用一個冒號將這兩個連在一起:

這通常被稱為一個 colon (冒號) refspec,refspec 只是一個表示 location (位置) 的花俏的名稱,這個位置可以被 git 辨別(例如 foo branch 或是 HEAD~1 )。

一旦你單獨指定了 source 以及 destination,你可以看到花俏且準確的指令。讓我來來看一個例子!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-1

哇!這實在是一個很花俏的指令但是確很合理,git 把 foo^ 解讀成一個位置,並且 push 該位置的 commit 到目前 remote 的 master branch。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-2

如果你想要 push 的 destination 不存在怎麼辦?沒有問題!只要給一個 branch 的名稱,git 就會在 remote 幫你建立。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-3

太讚了,實在非常地簡單:D
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-4

指令的記法可以記成…

整段語法可用白話記成:「將本地端的 git commit 推送到遠端主機 origin 上, 本地端的 foo 分支推到遠端分支的 master」。

5-2-解題

對於這個關卡,想辦法達到這個視覺化的目標,而且要記得格式: <source>:<destination>
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-5

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-5-git push 的參數,延伸討論!-6


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>

git 會到 remote 上的 foo branch,抓下所有不在 local 上的 commit,然後將它們放到 local 的 o/foo branch。

讓我們實際看一下(就只是一個更新的方法)。

指定一個 <place>
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-1

我們只下載了 foo 上的 commit,並且把它們放到 o/foo
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-2

你也許會感到奇怪,為什麼 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 的概念是很類似的,就只是方向相反而已。

讓我們來實際看一下這個瘋狂的事情:
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-3

哇!看到了吧,git 把 foo~1 解讀成一個在 origin 上的位置,而且把該位置上面的 commit 下載到 bar(這是一個 local branch)上面,注意,因為我們有指定目的地,因此 fooo/foo 並沒有被更新。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-4

如果我在執行這個指令之前,destination 不存在的話會怎樣?我們回到上一個例子,但這一次事前並沒有 bar 這個 branch 的存在。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-5

看到了吧,這就像是 git push,在 fetch 之前,git 會自己建立 destination,就好像是在 push 之前, git 會建立 remote 上的 destination (目的地) 一樣(如果它不存在的話)。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-6

沒有參數的情況?

如果使用 git fetch 的時候,沒有指定任何的參數,那就相當於它會下載 remote 上面的所有的 commit,並且把這些 commit 放到 local 上面所有對應到 remote 的 branch…
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-7

超簡單,但是所有的更新只做一次,很值得。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-8

6-2-解題

好的,講了好多!要完成這一關,fetch 視覺化的目標所指定的 commit,好好玩這些指令吧!

對於兩個 fetch 的指令,你必須要指定 source 以及 destination,注意一下視覺化的目標,因為 commit 的 id 可以被交換!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-9

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-6-fetch 的參數-10


7-沒有 source

7-1-說明

7-1-1-奇怪的地方

在兩個奇怪的情況下,git 不使用 <source> 參數,事實上,在 git push 以及 git fetch 的情況下,可以允許你”不用”指定 source,你可以藉由把參數留空,來表示你不想指定 source:

讓我們來看看這些在做什麼…

當沒有指定 source 的時候, push 對於 remote branch 做了什麼? push 把它刪除掉了!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-1

看吧,我們藉由把 source “留空”,成功用 push 刪除了 foo branch,這合理吧…
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-2

最後,對於 fetch 來說,source “留空” 表示我們要在 local 上建立一個新的 branch。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-3

很奇怪吧!但那正是 git 為你做的事情!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-4

7-2-解題

這是一個很簡單的關卡,只需要利用 git push 刪除一個 remote 的 branch,並且利用 git fetch 建立一個新的 local 的 branch!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-5

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-7-沒有 source-6


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 相當於:

而且…

git pull origin bar~1:bugFix 相當於:

看吧? git pull 真的就只是表示 fetch 跟 merge 的一個簡化後的指令,而且 git pull 所根據的是這些 commit 要放置的位置(在 fetch 的時候所指定的 destination )。

讓我們來看一個例子:
如果我們在 fetch 的時候有指定 位置 的話,跟之前一樣,fetch 所做的事情沒有變,但是我們會 merge 我們剛剛所 fetch 的該 位置 的 commit。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-1

看吧!指定位置為 master,跟平常一樣,我們下載了 commit 並且放到 o/master 上,接著,我們會 merge o/master 到我們現在的位置,不管我們現在所 checkout 的位置在哪裡。
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-2

他是不是也可以同時指定 source 以及 destination?你說對了啦!讓我們來看一下:
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-3

哇!這個指令強而有力,我們在 local 建立了一個新的 foo branch,下載了 remote 的 master 的 commit,並且放到 local 的 foo branch,之後 merge foo branch 到我們目前所 checkout 的 bar branch。這實在是太超過了!!!
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-4

8-2-解題

要完成這一關,達到視覺化目標的要求,你需要下載一些 commit,建立一些新的 branch,並且 merge 這些 branch 到其他的 branch 上面,這個關卡不需要打太多的指令:P
Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-5

Git 練習遊戲_learngitbranching-7-關於 origin 和其它 repo,git remote 的進階指令-8-pull 的參數-6