在專案規模比較小時,可以一路往前提交 commit 到底也沒關係,但當今天專案越來越大,或是越來越多人一起開發同一個專案時,這時為了專案的穩定就不能隨意提交 commit 上去,而是會另外開分支來提交 commit,例如:新增新功能、修正 Bug、實驗新作法等,都可以額外開分支進行開發,等到確認沒問題後再合併回來。
開始使用分支
首先我們可以先輸入 git branch:
$ git branch
* master
分支的操作都會使用到 git branch,而 git branch 在後面不加任何參數時會印出目前工作目錄有哪些分支,Git 預設會幫我們設定一個 master 的分支,master 前面的 * 則表示現在在 master 分支上。
新增分支
新增分支的方式很簡單,一樣使用 git branch,這次在後面加上想要開出去的分支名稱:
$ git branch test
這樣一個名為 test 的分支就開好了:
$ git branch
* master
test
變更分支名稱
我們剛才新增了一個 test 分支,但後來覺得不是很喜歡想要改掉,這時我們可以加上參數 -m,後面先帶上要修改的分支名稱,最後在帶上想變成的分支名稱:
# git branch -m 想修改的分支 修改後的分支名稱
$ git branch -m test develop
執行完後來檢視一下:
$ git branch
develop
* master
有成功改掉了,預設的 master 也可以改成想要的名稱:
$ git branch -m master main
$ git branch
develop
* main
切換分支
接下來我們要切換到 develop 去做開發,首先先確認現在在哪個分支上:
$ git branch
develop
* main
main 前面有 *,所以現在是在 main 分支上,現在我們要切換到 develop 分支,使用的指令是在學習 Git - 不小心刪掉的檔案救的回來嗎?有提到的 git checkout,git checkout 後面加上分支名稱就可以切換到指定的分支上:
$ git checkout develop
Switched to branch 'develop'
執行完後我們來確認一下:
$ git branch
* develop
main
星號 * 跑到 develop 前面了,所以現在我們已經將分支切換到 develop。
切換到不存在的分支
如果我們使用 git checkout 切換到不存在的分支時會出現下面錯誤:
$ git checkout nothing
error: pathspec 'nothing' did not match any file(s) known to git.
但每次都要先用 git branch 新增分支後再用 git checkout 切換過去好麻煩。
沒關係,在使用 git checkout 時可以加上 -b 參數來解決:
git checkout -b nothing
Switched to a new branch 'nothing'
切換分支的時候好像少了一些檔案?
在剛才新增的 develop 新增 work1.txt 和 work2.txt,接下來我們用 ls 指令來看一下在 develop 分支的工作目錄下有哪些檔案:
$ ls al
total 16
drwxrwxr-x 3 calon calon 4096 5月 18 20:00 .
drwxrwxr-x 3 calon calon 4096 5月 16 13:49 ..
drwxrwxr-x 8 calon calon 4096 5月 18 20:00 .git
-rw-rw-r-- 1 calon calon 0 5月 16 17:56 rule2.txt
-rw-rw-r-- 1 calon calon 0 5月 16 18:24 rule3.txt
-rw-rw-r-- 1 calon calon 1 5月 16 17:56 rules.txt
-rw-rw-r-- 1 calon calon 0 5月 18 20:02 work1.txt
-rw-rw-r-- 1 calon calon 0 5月 18 20:02 work2.txt
現在我們切到 main 分支在檢視一次目錄底下的內容:
$ git checkout main
Switched to branch 'main'
$ ls -al
total 16
drwxrwxr-x 3 calon calon 4096 5月 18 20:00 .
drwxrwxr-x 3 calon calon 4096 5月 16 13:49 ..
drwxrwxr-x 8 calon calon 4096 5月 18 20:00 .git
-rw-rw-r-- 1 calon calon 0 5月 16 17:56 rule2.txt
-rw-rw-r-- 1 calon calon 0 5月 16 18:24 rule3.txt
-rw-rw-r-- 1 calon calon 1 5月 16 17:56 rules.txt
剛才在 develop 分支底下新增的 work1.txt 和 work2.txt 都不見了?
不用擔心,檔案沒有消失,只要切回 develop 那些檔案就會出現了。
合併分支
我們先來看一下 main 裡面的 commit:
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
我們在 ccf65d1 後開一個新的分支 develop,並且切換到 develop 後檢視一下 commit:
$ git branch develop
$ git checkout develop
Switched to brach develop
$ git log --oneline
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
跟 main 的一樣,接下來就可以開始在此分支上進行開發,現在我們要來新增 2 個檔案並在 develop 分支分開提交 commit,也就是會有 2 個新 commit:
$ touch work1.txt work2.txt
$ git add work1.txt
$ git commit -m '新增 work1.txt'
[develop 6f5eefa] 新增 work1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 work1.txt
剛新增的 work2.txt 也是一樣執行 git add 和 git commit 提交上去,現在我們的 develop 分支上多了 2 個 commit:
$ git log --oneline
4444f10 (HEAD -> develop) 新增 work2.txt
6f5eefa 新增 work1.txt
ccf65d1 (main) 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
這時我們切回去 main 分支看檢視一下 commit 紀錄:
$ git checkout main
Switched to branch 'main'
$ git log --oneline
ccf65d1 (HEAD -> main) 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
的確比 develop 分支少了 2 個 commit,接下來想要將開發用的 develop 合併回 main,首先我們要切回 main 分支,剛才檢視 main 的 commit 時已經有切換到 main 了,以防萬一還是再檢查一下:
$ git branch
develop
* main
現在已經有切換到 main 分支了,接下來準備將 develop 合併回 main 分支,我們可以使用 git merge,後面加上想要合併回來的分支名稱,在這邊是 develop:
$ git merge develop
Updating ccf65d1..4444f10
Fast-forward
work1.txt | 0
work2.txt | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 work1.txt
create mode 100644 work2.txt
完成後用 git log 來確認一下:
$ git log --oneline
4444f10 (HEAD -> main, develop) 新增 work2.txt
6f5eefa 新增 work1.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
快轉模式(Fast-forward)
我們在上面將 develop 合併回 main 的時候有出現 Fast-forward 這個訊息,也就是快轉模式,因為 develop 是從 main 分出去的分支,而在 develop 要合併回 main 的時候,develop 除了多了 2 個新 commit 之外 main 有的 develop 都有,所以合併時並不會產生新的 commit。
我們可以使用 git log 並加上 --graph 參數來使用 ASCII 圖形檢視 commit 紀錄,首先是合併前的 main 分支:
$ git log --oneline --graph
* ccf65d1 (HEAD -> main) 新增 rule 3
* c8699fa 新增 rule2.txt
* 7cf2baa 新增 rules.txt 內容
* deb6d18 init
在每個 commit 前面多了 * 的符號之外好像就沒有什麼不同,接下來我們將 develop 合併回來後再檢視一次:
$ git merge develop
Updating ccf65d1..4444f10
Fast-forward
work1.txt | 0
work2.txt | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 work1.txt
create mode 100644 work2.txt
$ git log --oneline --graph
* 4444f10 (HEAD -> main, develop) 新增 work2.txt
* 6f5eefa 新增 work1.txt
* ccf65d1 新增 rule 3
* c8699fa 新增 rule2.txt
* 7cf2baa 新增 rules.txt 內容
* deb6d18 init
好像和上面介紹分支合併後的結果沒有差別,這是因為 Git 發現只要將 main 分支 commit 往前進到 develop 最新的 commit 即可完成合併,所以沒有額外產生合併的 commit 的關係。
這次我們在將 develop 合併回 main 前先在 main 分支新增 test.txt 檔案並提交一個新 commit:
$ touch test.txt
$ git add test.txt
$ git commit -m '新增 test.txt'
[main 595b530] 新增 test.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test.txt
這樣子 main 分支就會有 develop 上沒有的 commit 紀錄,接下來我們在將 develop 合併回 main:
$ git merge develop
執行之後因為不是使用快轉模式,所以會新增一個合併的 commit,這時會跳出 Vim 的視窗用來編輯這次合併的 commit,編輯完 commit 內容後一樣儲存離開:
$ git merge develop
Merge made by the 'recursive' strategy.
work1.txt | 0
work2.txt | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 work1.txt
create mode 100644 work2.txt
有發現訊息不一樣了嗎?這次沒有出現 Fast-forward 了,接著使用 git log --graph 來檢視 commit 紀錄:
* 485e562 (HEAD -> main) Merge branch 'develop' into main
|\
| * 4444f10 (develop) 新增 work2.txt
| * 6f5eefa 新增 work1.txt
* | 595b530 新增 test.txt
|/
* ccf65d1 新增 rule 3
* c8699fa 新增 rule2.txt
* 7cf2baa 新增 rules.txt 內容
* deb6d18 init
有發現有一個像耳朵的圖案了嗎?每個 * 代表一個 commit,/ 表示分出去的分支所新增的 commit,而 \ 則是合併回來的意思,在這邊合併時產生了新的 commit 所以 \ 會指向 485e562。
不使用快轉模式合併
在上面有介紹過,當今天要合併回來的分支上 Git 發現它有的你也有,只是多出幾個 commit 時就會用快轉模式,而這時使用 git log --graph 來檢視 commit 紀錄就不會有分出去的小耳朵,如果希望每次合併回來都會有小耳朵的圖案,我們可以加上 --no-ff 讓 Git 不使用快轉模式進行合併:
$ git merge develop --no-ff
執行之後會跳出 Vim 的視窗,編輯完 commit 內容後一樣儲存離開:
$ git merge develop --no-ff
Merge made by the 'recursive' strategy.
work1.txt | 0
work2.txt | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 work1.txt
create mode 100644 work2.txt
之後一樣用 git log --oneline --graph 來檢視一下紀錄:
$ git log --oneline --graph
* acfe38f (HEAD -> main) Merge branch 'develop' into main
|\
| * 4444f10 (develop) 新增 work2.txt
| * 6f5eefa 新增 work1.txt
|/
* ccf65d1 新增 rule 3
* c8699fa 新增 rule2.txt
* 7cf2baa 新增 rules.txt 內容
* deb6d18 init
刪除分支
我們已經成功地將 develop 合併回 main 上了,雖然 develop 分支留著也沒關係,但如果不想要我們也可以把它刪掉,記得在刪除之前要切到不是要刪除的分支上面在進行刪除,否則會出錯,而要刪除分支一樣是使用 git branch 指令,這次我們要加上 -d 這個參數:
$ git branch -d develop
Delete branch develop (was 4444f10)
這樣子 Git 就會刪除 develop,如果是在合併之前使用則會出現以下提示:
$ git branch develop
error: The branch 'develop' is not fully merged.
If you are sure you want to delete it, run 'git branch -D develop'.
這是在提示我們 develop 的內容還沒有合併,如果你真的不想要這個分支的話上面訊息也有提到怎麼刪除,只要把 d 改成大寫 D 就可以了:
$ git branch -D develop
Delete branch develop (was 4444f10)
能救回還沒合併卻被刪掉的分支嗎?
當然可以,還是的剛剛在刪除分支的時候的訊息嗎?
$ git branch -D develop
Delete branch develop (was 4444f10)
後面的 4444f10 就是救回分支的關鍵,在刪除分支的時候不是將在分支上所建立的 commit 刪掉,可以將分支想像成一張便利貼,這張便利貼會指向該分支所新增的 commit,而當我們將分支刪除時只是把貼著分支名稱的便利貼拿掉而已,所以我們只要在重新新增一個分支並指向 4444f10 這個 commit 即可:
git branch new_develop 4444f10
接下來我們將分支切到 new_develop 上,並檢視 new_develop 的 commit:
$ git checkout new_develop
Switched to branch 'new_develop'
$ git log --oneline
4444f10 (HEAD -> new_develop) 新增 work2.txt
6f5eefa 新增 work1.txt
ccf65d1 (main) 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
當工作到一半需要切換到另一個分支時
有時候工作到一半時,會有緊急的突發狀況需要我們去處理,像是要修一些 bug 等,這時我們就需要切換分支去處理,為了避免將現有的工作內容不小心提交 commit 在其他分支,我們可以先將手邊的工作提交 commit 上去,等到處理完其他分支之後在切回原本的分支並使用 git reset 將做到一半的工作的 commit 給拆掉繼續接著做下去。
而除了上述方法,我們在這邊介紹新的指令 git stash
Stash
首先我們先來看一下目前 git 的狀態:
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
我們目前正在 main 分支上,並正在修改 test.txt 這個檔案,這時突然需要切到別的分支去處理其他事情時我們可以使用 git stash 將這些檔案先暫時存起來:
$ git stash
Saved working directory and index status WIP on main: 485e562 Merge branch 'new_develop' into main
如果是還處於 Untracked 狀態的檔案需要加上參數 -u:
$ git stash -u
執行完之後我們可以使用 git stash list 來看一下:
$ git stash list
stash@{0}: WIP on main: 485e562 Merge branch 'new_develop' into main
stash@{0} 是 stash ID,WIP 是指 Working In Progress,也就是工作進行中的意思。而 stash 可以儲存很多份,如果我們在別的分支再新增一筆暫存的話:
$ git stash
stash@{0}: WIP on develop: 4444f10 新增 work2.txt
stash@{1}: WIP on main: 485e562 Merge branch 'new_develop' into main
我們可以發現剛才的 WIP on main 這筆的 stash ID 變成 stash@{1} 了,而上面多出的 stash@{0} 這筆就是我們剛新增的暫存,也就是 ID 數字越小時間越新。
接下來,當我們處理完其他分支的事情後想要拿回剛才的佔存檔要怎麼做?這邊我們可以使用 git stash pop 和 git stash apply 這 2 個指令來拿回我們剛才的工作進度,而這 2 個指令的差別在於:
git stash pop會將指定的 stash 暫存拿出來套用在現在的分支上,並且刪掉該 stash 暫存。git stash apply將指定的 statsh 暫存套用在現在分支上後不刪除該 stash。
所以如果確定要拿回的 stash 之後不需要的話可以使用 git stash pop,而以後還會用到則可以使用 git stash apply,我們這邊先使用 git stash apply,如果後面沒有指定 stash ID 的話預設是最新的一筆,而我們要撿回的工作進度的 stash ID 是 stash@{1} ,所以在這邊要加上 stash ID:
$ git stash apply stash@{1}
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
這樣我們的工作進度救回來了。
我們剛才是使用 git stash apply,也就是說將暫存撿回來後放在 stash 裡的存檔不會消失,如果有些暫存真的不需要的時候可以使用 git stash drop:
git stash drop stash@{1}
Dropped stash@{1}(107dda387eaf519a860c4979180d2dd390c2756f)
最後我們來使用 git stash pop 來撿回工作進度的暫存:
$ git stash pop stash@{1}
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{1}(107dda387eaf519a860c4979180d2dd390c2756f)
執行後出現的訊息是執行完 git stash apply 和 git stash drop 的結果,所以我們也可以將 git stash pop 看成是 apply 和 drop 的組合。
參考資料
- 高見龍,《為你自己學 Git》


