# 前言

之前的內容都是在地端儲存庫版控的行為,不過如果需要與團隊成員合作版控,我們會需要有一個「遠端儲存庫」,而 GitHub 應該算是一個蠻常聽到的遠端儲存庫了,接著將以 GitHub 作為遠端儲存庫來說明地端資料與遠端資料的操作。

不管是 GitHub ,或是其他的 Git 遠端儲存庫 (GitLab 、TFS 等),在地端指令的操作上並沒有什麼差異,使用不同儲存庫唯一有感的差異,應改只有「遠端儲存庫」提供的「介面」長得不同而已。

我們在學習的角度,GitHub 網頁上的操作倒是其次,畢竟「不是所有團隊都是使用 GitHub 當遠端儲存庫」!

學會這幾個重點,才是理解「遠端儲存庫」的重要關鍵:

  1. 知道「地端儲存庫」跟「遠端儲存庫」是可以分開看待的
  2. 知道怎麼將「遠端儲存庫」的專案拉回地端使用
  3. 知道如何把「地端儲存庫」的專案上傳到遠端
  4. 知道什麼是「地端分支」、「遠端分支」、「遠端追蹤分支」
  5. 知道什麼叫 Pull Request (俗稱 PR)

列完發現長得有夠像面試條件

上面的內容接下來的幾篇文章應該都會涵蓋到 (應該…)

這篇文章,來學習地端與遠端的互動吧!

一樣,我會在指令的環節穿插一些觀念,建議 GUI 派的讀者先看完前面的內容後,再接續著看這單元的 GUI 操作方式。

# 建立空的遠端儲存庫

要在 GitHub 建立空的遠端儲存庫,首先請找到這顆 new 的按鈕:

到了這個介面後,定義儲存庫名稱,如果是準備把地端的儲存庫上傳到這的話,名稱建議與地端的「儲存庫資料夾名稱」相同,未來維護時比較好辨別:

當你看到這個畫面,代表遠端的「空」儲存庫已經建立完成:

由於我們使用 GitHub 的目的是要理解跟遠端儲存庫有關的指令,來解釋一下中間兩塊指令是要請我們做什麼事情。

  1. 第一個指令區塊,是建議我們在「地端」建立一個新的儲存庫,也就是建立地端儲存庫之後,把資料推上來
echo "# GitLearn" >> README.md   # 用指令建立 `readme.md` 檔案
git init                         # 初始化 Git 儲存庫
git add README.md                # 把這個檔案加到暫存區
git commit -m "first commit"     # 提交第一個版本
git branch -M main               # 把預設分支名稱改成 main
git remote add origin https://github.com/imall/GitLearn.git
git push -u origin main

除了第一行跟最後兩行,其他指令大家應該都很熟悉了 XD

  1. 第二個指令區塊則接下來文章的 重點: 「將既有的地端儲存庫上傳到遠端」 的步驟,這邊就不特別說明,請讀者們接續著看文章。

# 查詢地端儲存庫

在開始之前,先介紹一個可以查詢地端儲存庫連線到遠端儲存庫網址的指令:

git remote -v

在還沒與遠端建立連線前,這個指令執行完之後,什麼資訊都不會跑出來

# 建立地端儲存庫與遠端的連線

這個步驟,會是在「你已經有地端儲存庫後」要執行的事情,就是說你在地端已經執行過 git init 了,才可以繼續這個步驟建立連線。

剛剛建立完遠端儲存庫後,畫面有一段網址,他就是地端與遠端建立連線的依據,如果你是使用其他遠端儲存庫,要建立連線也是需要先找到這段網址資訊:

git remote add origin 網址

以上面的範例來說,我會需要在地端執行這個指令:

git remote add origin https://github.com/imall/GitLearn.git

指令中的 origin 是要對這個網址命名一個「代稱」,他不一定要命名叫 origin ,想要設定為其他名稱也可以。

如果你跟筆者之前一樣,想不通為什麼我們都已經有「網址」可以辨認遠端儲存庫,還要取一個 origin 的名稱,那你可以把這個連線機制想成建立「任意門」的行為。

沒錯,就是大雄每次打開門都會跑到靜香浴室的那個。

大雄從他的房間要進入進香的浴室,必須先設定好「任意門」的通道,那段「網址」就是靜香家的「地址」,不過有這段地址還不夠,「任意門」還需要更精準的位置,所以我們還需要將傳送的位置設定在靜香家的「浴室」。

用這個比喻來看指令,可能是這樣:

git 遠端通道 新增 靜香家的浴室 靜香家的地址

不過這個比喻純粹是為了方便理解,實際上 origin 的用意,就只是針對「網址」取個名字而已。

我們可以用這個指令對「網址」取很多個名字,讓整個連線機制好像能有很多通道一樣,例如「靜香浴室」、「靜香房間」。

不過實務上一個地端 「只會搭配一個」 遠端通道,換言之我們只會使用「一次」這個指令來對網址命名與建立與遠端的連線機制,後續就都以這個命名來操作。也因為這個原因,一般約定俗成好像都叫做 origin ,並不會因為不同專案而取不同名稱。

總之,當你完成這步之後,回頭使用上面的指令,應該就會看到遠端儲存庫的資訊了:

$ git remote -v
origin  https://github.com/imall/GitLearn.git (fetch)
origin  https://github.com/imall/GitLearn.git (push)

push 等等會講到, fetch 應該會在下一篇文章出現

# 將地端的分支推到遠端

上一步建立好連線之後,我們就可以把地端的資料推到遠端了。

這一步要建立的觀念是:推送到遠端的資料,是以分支為單位。

換句話說,我們每次推送資料,都是在把「地端的分支資料更新到遠端」!

第一次執行更新分支的指令是這樣

git push -u origin 地端分支名稱:遠端分支名稱

說明一下這個指令的幾個細節

  1. git push 是用來把地端分之推送到遠端的指令
  2. -u 參數表示 「上游」 ,完整參數是 --set-upstream ,他的目的是要告訴 Git 在推送到遠端的過程,一併把地端分支與遠端分支進行綁定。如果這步有使用 -u 參數,未來 這個「地端分支」只要執行 git push 就直接推到這個「遠端分支」。
  3. origin 則是剛剛使用 git remote add 建立連線時對「網址」的「命名」,當時取什麼名稱,這裡就要寫什麼字詞
  4. 地端分支名稱:遠端分支名稱 ,地端跟遠端兩者的分支名稱「可以不同」,不過一般都會讓兩邊名稱相同,維護時比較不容易錯亂。

上面的例子中,如果我想要推送的地端分支是 main ,同時遠端分支也要叫 main 的話,指令要這麼打:

git push -u origin main:main

但我們都知道,工程師是很懶的生物,一樣的名稱還要打兩次很麻煩,所以如果地端分支名稱與遠端分支名稱 相同 時可以簡化成這樣:

git push -u origin main

如同前面說的,因為指令有 -u 參數,未來如果 main 資料有更新,只要執行這個指令即可:

git push

補充:
-u 參數,是幫我們在地端專案中的 .git/config 動手腳,增加一些資料,以上面的例子來說,在你執行完 git push -u origin main 之後, .git/config 會多出這段內容:

# 一次推送所有分支上遠端

如果你想要一次推送所有分支上去遠端,可以執行這個指令

git push --all

這個指令只是幫你把分支的資料上傳到遠端,但他不會建立地端分支與遠端分支的連線,如果你推送到遠端的過程,還打算一併建立兩端的連線的話,就要運用剛剛學到的技巧:

git push -u origin --all

不過在執行這個指令之前,我們可以先想想看實務上是否有需要把「所有」分支都同步到遠端?

遠端儲存庫最重要的目的在最一開始就有提到了,他是一個用來跟團隊共享資料的管道。

在開發的過程中,或許有不少分支還在「開發階段」,內容還不是很完善,這種分支是不是也需要推到遠端讓他人存取?

這個問題就留給大家在執行指令之前判斷一下囉!

# 強制將地端分支推送到遠端

如果你發現有那種「推不上遠端」的分支,代表地端分支跟遠端分支的內容「有衝突」!

要是真的很想很想把這個「有衝突」的資料推到遠端,你可以執行這個指令:

git push -f

我們也不是第一次見到 -f 參數了,他就是「強迫」(force) 的意思。在 Git 中如果使用到這個參數,通常都是那種明明不能做,你偏要他做這件事的時候才會用到。

那什麼時候會不能執行 git push
其中一種可能性是:你在地端執行某些指令操作,修改了那些已經被推到遠端儲存庫的 commit,就會讓分支的最新 commit 跟遠端長得不一樣。

注意,這裡是講「修改 commit」不是「提交一個 commit」。

讀者們看到我在這段各種強調,應該也猜得到我準備說的內容了。

記得我們曾在 git commit --amendgit resetgit rebasegit rebase -i 的篇章 都有 講過一件事

不要隨便修改已經推到遠端的 commit!!!!

就是在告訴大家,如果有個分支已經推送到遠端,對這個分支執行上面那堆指令,讓 commitID 長得不一樣的時候,不要隨便使用 git push -f 把分支推到遠端儲存庫!!!

除非你真的很確定,強制推送的分支只有你一個人會用,而且推送後不會對其他人或是這個專案造成任何影響,那你儘管用,沒人會阻止你。

不過要是你強制推送 dev 分支 甚至 master 分支影響到整個專案的資料…
你可能要有隔天門禁卡刷不進公司大門的心理準備…

# 將遠端儲存庫的專案 clone 回地端

當我們把地端的分支都 push 到遠端儲存庫之後,就等於在遠端完成儲存庫的備份了。

如果有團隊成員需要這個儲存庫的資料,或是你想要在其他裝置取得這個儲存庫的資料,其中一個方式就是:把遠端的專案 clone 回地端。

做法很簡單,執行這個指令就能搞定了:

git clone 專案網址

這個動作曾經在 建立 Git 儲存庫這篇文章有提到,當時建立了空的專案之後,就是使用這個方式把遠端儲存庫複製一份回地端使用。

使用 clone 指令複製回來的 資料夾名稱,預設會是 遠端儲存庫的名稱,也就是說如果我執行下列指令 clone 儲存庫,在電腦中的資料夾名稱就會叫做 GitLearn

git clone https://github.com/imall/GitLearn.git  # 儲存庫名稱是 GitLearn

既然說這是「預設」行為,代表我們可以自己定義名稱,只要在原本的指令後面加上想要設定的資料夾名稱即可:

git clone 專案網址 資料夾名稱

如此一來,Git 就會用我們設定的名稱來建立資料夾了。

# 更新遠端儲存庫的資料

剛才在介紹 git push 指令時,提到如果我們 修改 「push 到遠端的分支」,會使我們沒辦法把內容推到遠端,此外還有一種原因會讓分支無法推到遠端:
有其他人更新了分支資料 ( git push ) 到遠端,使得你眼前的地端資料,比遠端的資料還要「舊」,Git 就不會讓你 push 分支。

這時候的 git push 指令應該會跑出這堆訊息給你看:

$ git push
To https://github.com/imall/GitLearn.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/imall/GitLearn.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Updates were rejected because the tip of your current branch is behind ,就是在提示我們「地端分支」比「遠端分支」的資料還要「舊」。

依據 Git 的建議,我們必須執行下列指令把分支的最新內容同步回來:

git pull

這個指令一般會用在遠端儲存庫已經被 clone 回地端一段時間,遠端的分支的資料已經被團隊成員更新的情況,我們就會在「使用地端之前」,先執行 git pull 指令先確保遠端與地端資料一致之後,再開始開發。

不過阿… 假設你忘記要先執行 git pull 更新分支資料就在地端 commit 幾個版本,等到執行 git push 時因為看到提示訊息才執行 git pull ,結果遠端要同步回來的內容,跟你在地端 commit 的內容,剛好是同一個檔案的同一行… (有沒有覺得這句話很熟)

你猜到了,上面這種情境會發生衝突,這個狀況就是「地端分支」合併「遠端分支」的衝突情境了!你必須根據「分支衝突解決 SOP」(註 1) 去處理這個問題!

所以如果有跟他人協作,那麼養成好習慣:在使用分支之前,先執行 git pull 確保已經將資料同步回地端之後,再操作分支吧!

註 1. 如果你不知道怎麼解決分支衝突,可以參考此系列文 分支合併篇 的內容。

# 使用 Fork GUI 操作遠端儲存庫

好啦,觀念在指令的範疇講完了,接著就是輕鬆的操作 GUI 時間了

# 建立地端與遠端儲存庫的連線

  1. 到視窗左邊找到 Remotes => Add New Remote…
  2. 輸入 remote 名稱與網址,點選 Add New Remote
  3. 完成

# 將地端分支推送到遠端

  1. 確定切換到對的分支後,點 push

  2. 預設會幫你選擇目前所在分支,你也可以自己下拉選擇其他分支,Create tracking reference 代表 -u 的意思,沒問題的話就按 Push 按鈕。

  3. 看到 Remote 的分支出現剛剛推送的分支,表示推送完成

# 將遠端儲存庫 clone 回地端

  1. 選到 File => Clone…

  2. 把遠端的網址、在地端要放的位置、地端的資料夾名稱設定好,點選 Clone 就完成了

P.S. 這個動作其實也在建立 Git 儲存庫文章中介紹過 XD

# 將遠端分支的資料同步回地端

確定有切換到對的分支後,點 Pull 就完成了

# 總結

這篇文章介紹了地端與遠端基本的操作模式,順便提一些操作上的觀念。

這些內容同時也是團隊合作開發會很常用到的操作,是協作開發的基本功!