之前的內容都是在地端儲存庫版控的行為,不過如果需要與團隊成員合作版控,我們會需要有一個「遠端儲存庫」,而 GitHub 應該算是一個蠻常聽到的遠端儲存庫了,接著將以 GitHub 作為遠端儲存庫來說明地端資料與遠端資料的操作。
不管是 GitHub ,或是其他的 Git 遠端儲存庫 (GitLab 、TFS 等),在地端指令的操作上並沒有什麼差異,使用不同儲存庫唯一有感的差異,應改只有「遠端儲存庫」提供的「介面」長得不同而已。
我們在學習的角度,GitHub 網頁上的操作倒是其次,畢竟「不是所有團隊都是使用 GitHub 當遠端儲存庫」!
學會這幾個重點,才是理解「遠端儲存庫」的重要關鍵:
列完發現長得有夠像面試條件
上面的內容接下來的幾篇文章應該都會涵蓋到 (應該...)
這篇文章,來學習地端與遠端的互動吧!
一樣,我會在指令的環節穿插一些觀念,建議 GUI 派的讀者先看完前面的內容後,再接續著看這單元的 GUI 操作方式。
要在 GitHub 建立空的遠端儲存庫,首先請找到這顆 new
的按鈕:
到了這個介面後,定義儲存庫名稱,如果是準備把地端的儲存庫上傳到這的話,名稱建議與地端的「儲存庫資料夾名稱」相同,未來維護時比較好辨別:
當你看到這個畫面,代表遠端的「空」儲存庫已經建立完成:
由於我們使用 GitHub 的目的是要理解跟遠端儲存庫有關的指令,來解釋一下中間兩塊指令是要請我們做什麼事情。
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
在開始之前,先介紹一個可以查詢地端儲存庫連線到遠端儲存庫網址的指令:
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 地端分支名稱:遠端分支名稱
說明一下這個指令的幾個細節
git push
是用來把地端分之推送到遠端的指令-u
參數表示 「上游」 ,完整參數是 --set-upstream
,他的目的是要告訴 Git 在推送到遠端的過程,一併把地端分支與遠端分支進行綁定。如果這步有使用 -u
參數,未來 這個「地端分支」只要執行 git push
就直接推到這個「遠端分支」。origin
則是剛剛使用 git remote add
建立連線時對「網址」的「命名」,當時取什麼名稱,這裡就要寫什麼字詞地端分支名稱:遠端分支名稱
,地端跟遠端兩者的分支名稱「可以不同」,不過一般都會讓兩邊名稱相同,維護時比較不容易錯亂。上面的例子中,如果我想要推送的地端分支是 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 --amend
、git reset
、git rebase
、git rebase -i
的篇章 都有 講過一件事
不要隨便修改已經推到遠端的 commit!!!!
就是在告訴大家,如果有個分支已經推送到遠端,對這個分支執行上面那堆指令,讓 commitID 長得不一樣的時候,不要隨便使用 git push -f
把分支推到遠端儲存庫!!!
除非你真的很確定,強制推送的分支只有你一個人會用,而且推送後不會對其他人或是這個專案造成任何影響,那你儘管用,沒人會阻止你。
不過要是你強制推送 dev 分支 甚至 master 分支影響到整個專案的資料...
你可能要有隔天門禁卡刷不進公司大門的心理準備...
當我們把地端的分支都 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. 如果你不知道怎麼解決分支衝突,可以參考此系列文 分支合併篇 的內容。
好啦,觀念在指令的範疇講完了,接著就是輕鬆的操作 GUI 時間了
確定切換到對的分支後,點 push
預設會幫你選擇目前所在分支,你也可以自己下拉選擇其他分支,Create tracking reference 代表 -u
的意思,沒問題的話就按 Push 按鈕。
看到 Remote 的分支出現剛剛推送的分支,表示推送完成
選到 File => Clone...
把遠端的網址、在地端要放的位置、地端的資料夾名稱設定好,點選 Clone 就完成了
P.S. 這個動作其實也在建立 Git 儲存庫文章中介紹過XD
確定有切換到對的分支後,點 Pull 就完成了
這篇文章介紹了地端與遠端基本的操作模式,順便提一些操作上的觀念。
這些內容同時也是團隊合作開發會很常用到的操作,是協作開發的基本功!