相信看完分支的觀念與操作後,還是會有人對分支操作的某些「正常現象」還是會有疑惑,同時也有一些實務上的問題,在之前的文章礙於篇幅而沒有提及。
這篇文章來說說筆者曾經疑惑,或是覺得可以記錄一下的內容。
進入今天的主題:
分支操作常見問題
只有在兩個分支分別走向不同的路,才會出現岔路。
這個問題應該是新手初次接觸分支比較不好體會的地方,我們都以為建立了分支發佈版本之後,線圖應該會有另一個顏色進行區分。但實際操作下來,卻會發現新分支的 commit 好像跟原本的分支在同一條線...
像是下圖的情境,開了新分支 dev
執行 commit ,但明明叫「分」支,線圖卻都沒有「分」出去欸...:
這個案例可以合併快轉機制的例子說明,其實兩個分支原本都站在同一個位置,只是 dev
先往前走了兩步路,但是他們倆個人還是處在 同一條路 上,所以根本沒有「岔路」可言。
要讓線圖產生「岔路」,必須讓 master
不要 沿著 dev
走過的路行走,而是要跟 dev
分道揚鑣,走出 master
自己的道路。
做法也很簡單,只要切換回 master
分支,在 master
commit 版本,岔路就會出現了:
這個線圖就像是兩個人分道揚鑣,從此不相往來一樣,Git 為了記錄兩個人走過的路程,必須把這個岔路畫出來。
如果你想再問為什麼 master
的線長得特別長,是因為 Git 線圖一般是會依照 時間順序 而繪製的。
因為 master
是在 dev
往前兩步後才出發,為了在線圖中看出先後順序,會讓 master
走的位置超過 dev
之前的 commit 點點。
知道這個觀念後,我們就可以從線圖中對分支 commit 的時間一目瞭然囉!
可以。
有幾種方式可以做到,一樣示範指令的做法,以及 Fork GUI 的做法。
我們已經知道建立分支的指令是這樣:
git branch 分支名稱
不過事實上這個指令省略了預設值 HEAD
:
git branch 分支名稱 HEAD
這個指令可以想像成:「我要撕一張便籤起來,寫上『分支名稱』,然後貼在目前所在的 commit 上。」
既然說是預設值,代表我們可以賦予他特定的資料值。
以這張圖為例子
想要在 f3c45c2b
commit 上建立分支,直接這樣執行指令就可以了:
git branch 分支名稱 f3c45c2b
此外,我們既然知道建立分支的預設值是 HEAD
,就代表我們可以先讓 HEAD
指著某個 commitID ,然後執行建立分支指令。
切換分支指令是這樣:
git checkout 分支名稱
如果把分支名稱改掉成 commitID ,那 HEAD 就會跑到 commitID 上:
git branch f3c45c2b
此時直接執行預設建立分支指令,就會在 commit 上成功貼上分支便籤了!
git checkout 分支名稱
只不過要注意,建立分支指令不會幫我們切換分支,請記得要再切過去再開發!
git checkout 分支名稱
我猜你看完上面原理,應該知道怎麼操作了。
其實就是在特定 commit 點右鍵,去找建立分支選項而已,很單純。
這裡有刻意把 切換分支 的選項打勾,你也可以選擇不切換分支,就看當下的執行需求。
強迫建立了已經存在的分支。
一個專案中的分支是不能重複的,如果建立重複名稱的分支,Git 會直接彈出錯誤,告訴我們分支已經存在:
$ git branch dev
fatal: a branch named 'dev' already exists
不過 Git 還是提供一個 -f
參數讓我們可以強迫建立分支。
git branch -f dev
用 -f
參數建立一個「不存在」的分支,結果會跟沒有 -f
參數沒兩樣。
但如果是建立「存在」的分支,在線圖中看起來就像分支被「撕」起來又貼到目前所在位置的感覺。
當然,也可以強迫建立「已存在的分支」到指定的 commit 上,只要再多加一個 commitID 參數給他即可:
git branch -f dev commitID
不過因為 -f
代表 強迫(force),如果資料因為操作不當而遺失了,Git 是概不負責任的哦!
看完以上內容,你應該能看懂我在 Git 分支:觀念篇 幹的事情了:
上面動畫是用 GUI 操作,指令的話可以這樣下
git branch -f Emilia b1c40661
有兩種做法可以應付這個需求
git reset
把尚未開發的資料暫存起來我們已經知道 reset 的預設模式會在 HEAD
移動過程,工作目錄的資料不會被異動,只有 commit 會被拆掉而已。
也就是說,我們可以利用這個特性來「還原資料」。
當手上的東西處理到一半,我們可以先不管三七二十一把所有內容提交一個版本:
git commit -m "temp"
commit 訊息可以隨便打,他只是一個用來暫存資料的 commit,等等就移除了。
接著就可以 安心的切換分支 去處理當下的需求。
等到事情處理完,先 回到原本的分支,然後執行這個指令,資料就會還原到原本的狀態囉!
git reset HEAD^
如果你跟 reset 指令不熟,可以參考 用 reset 回到過去,有詳細的 reset 操作模式說明。
git stash
把資料暫存起來Git 因應這種情況,其實有提供一個指令讓我們使用。
git stash
當專案有尚未 commit 的內容,執行這個指令之後 Git 會直接把這些資料直接暫存一個版本。
雖然說是版本,但他跟 commit 又不太一樣,而是標記一個叫 stash 的特殊版本,像是紅色箭頭指著的樣子:
stash
會把所有尚未 commit 的資料 「暫存」 起來,所以即便隨意切換分支,也不用擔心有資料遺失或是資料混亂的情形!
如果想查看 Git 目前有哪些 stash
,可以執行這個指令:
git stash list
$ git stash list
stash@{0}: WIP on 分支名稱: f3c45c2 新增童話歌詞
stash@{0}
中的 0
是 stash
的序號,如果有多個 stash
,則會出現多個編號。
要還原 stash
的內容,也是要先 切回原本的分支 ,並且依據你想還原的 stash
執行指令:
git stash pop stash@{0}
理論上這樣就可以還原資料了!
但是!!!!!!!!!!
如果你跟筆者一樣是習慣使用 windows 系統的「命令提示字元」或是「PowerShell」下指令的話,執行上面的指令可能會噴出這樣的錯誤訊息:
這種情況下,你可以直接使用 「序號」,像這樣:
不過如果你在 windows 使用 「Git Bash」 執行指令,就不會有這個問題
而且你也可以直接寫數字:
你在專案的起初,明明知道要建立分支才開始開發,卻直接在初始分支 master
開發,等到開發一段時間才想起應該要建立分支。
這時候線圖會長這樣:
分割畫面左邊是線圖,右邊是要在建立分支後才要開發的檔案示範。
此時的解決方式很簡單:
事實上,切換分支的行為,Git 會執行兩件事:
這個行為並不會去管工作目錄「異動」的資料,所以直接「建立分支」然後「切換分支」,就能解決這個問題了。
雖然切換分支的行為不會影響到工作目錄異動的資料,但是如果你編輯的那個檔案,沒辦法讓 Git 計算 commit 差異。
例如你現在位於 dev
,編輯了某個檔案之後發現應該要在 master
分支建立分支才能開發。
但如果切換到 master
分支,因為編輯的檔案已經跟 master
的內容有衝突了,Git 就會拒絕這個切換分支的行為。
這種狀況下的 git checkout
指令,應該會看到類似這樣的訊息:
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
新檔案.txt
Please commit your changes or stash them before you switch branches.
Aborting
這段描述內容的畫面大概會長這樣:
針對這圖片描述一下,你在 dev
分支編輯了檔案,但原本你是想要在 master
「開分支之後」再開發,結果 Git 不讓我們切換分支...
這種狀況下你可以這麼做:
git stash
執行暫存git master
分支git checkout -b 開發分支
git stash apply stash@{0}
把暫存抓回來git stash drop stash@{0}
刪除 stash老實說,只是把 HEAD
指著 commit 並不會怎樣,版本不會亂掉,資料也不會遺失。
當時有提到,HEAD
指著 commit,意思是 HEAD
是分離狀態(Detached HEAD State)
上面分支的操作,就刻意讓他變成這種狀態再繼續操作。
在實務上,也看過有人在需要查看版本時,會直接執行 git checkout CommitID
去查看版本資料,這個當下也確實是 Detached HEAD State。
所以暫時性的這個狀態其實不會怎樣,還算是很正常的事情。
Detached HEAD State 對我們開發上最大的影響,在於這種狀態下提交了版本,在切換分支後,版本是會在線圖中消失的!
示範一次:
git status
,他還很好心跟我說我現在狀態不正常 XD從畫面中,也發現 Git 很好心的提示我們,有一個在 Detached HEAD 狀態的 commit 遺失了。
如果我想把他找回來,可以用那個 commitID 建立一個分支。
好險...
這上面的訊息,之前大概都說明過了
一個觀念是,Git 中 commit 過的內容不會不見,除非你忘記 commiID 值。
另一個觀念是上面提到的,我們可以拿特定的 ID 來建立分支。
記得分支觀念中,我有說過 「HEAD」 跟 「分支本質」有點像圖釘嗎?
在 Detached HEAD 狀態提交一個版本,代表這個版本會拿 HEAD 圖釘把他 釘 在線圖上的,但 HEAD
是一個會依據需求而移動的「圖釘」,如果沒有再拿一個「分支圖釘」把 commit 釘在線圖上,等 HEAD
換位置,這個 commit 就會消失!
這就是為什麼要強調,沒事不要讓 HEAD
指著 commit 的原因。
把這觀念拆得更細一點說,就只是避免我們不小心在這種狀態提交版本,導致 Git 無法記錄內容罷了。
分支篇的內容到這已經算是告一個段落。
以學習的角度建議要做的事情,就是盡量去使用分支,體會分支是「便籤」這件事。
並且去模擬實務上使用分支時會遇到的狀況,並且試著解決他,處理到自己希望完成的情境。
如同觀念篇所說的事,有了分支之後,Git 的世界將會變得很不一樣,即便你只有一個人在使用 Git ,也很建議常常去使用分支,體會分支帶給我們的便利性!