Git 指南:從基礎操作到進階技巧
1. Git 的核心概念
Git 的版本控制就像拍照一樣,能夠記錄當下的專案狀態(snapshot),並允許我們回到某個時間點的狀態。
- 版本控制:每次提交(commit)都像拍照,記錄當下的檔案狀態。
- 非破壞性操作:Git 的操作不會刪除歷史,只會新增記錄,讓我們可以隨時回溯。
2. 安裝 Git
在 macOS 上可以使用 Homebrew 來安裝 Git:
brew install git
git --version
3. 初始化專案
初始化一個 Git 儲存庫,會在專案目錄中建立 .git 資料夾:
git init
4. 基本操作
查看狀態
檢查目前的檔案狀態:
git status
追蹤檔案
將檔案加入暫存區:
git add index.html
取消追蹤檔案
將檔案從暫存區移回工作目錄:
git restore --staged index.html
提交變更
提交檔案並附加說明訊息:
git commit -m "feat: add index.html"
5. git config 設定使用者資訊
設定全域的使用者名稱與信箱:
git config --global user.name 'YOURNAME'
git config --global user.email 'YOUR_EMAIL'
查看目前的 Git 設定:
git config --list
6. 提交訊息的最佳實踐
- 避免使用模糊的訊息,例如:
"update"
"bug fixed"
"temp"
- 推薦使用具體且有意義的訊息,例如:
"#23 bug fixed"
"member sign in (WIP)"
建議遵循 Conventional Commits 標準。
7. git log 查看歷史紀錄
使用指令
查看專案的提交歷史:
git log
使用工具
在 VS Code 中可以使用 Git Graph
插件來視覺化歷史紀錄。
8. git restore 還原檔案
還原單一檔案
將檔案還原到最新一次的提交狀態:
git restore index.html
還原所有檔案
將整個目錄還原到最新一次的提交狀態(不推薦):
git restore .
9. git branch 分支操作
查看分支
列出所有分支:
git branch
建立分支
建立新分支:
git branch cat
刪除分支
刪除分支:
git branch -d
git branch -d
用於刪除 已經合併到當前分支的分支。如果分支尚未合併,Git 會拒絕刪除,並提示錯誤,避免意外刪除未合併的工作。
git branch -d <branch_name>
git branch -D
git branch -D
是強制刪除分支的指令,無論分支是否已經合併到當前分支,都會直接刪除。
- 不需要檢查分支是否已合併。
- 適用於確定不需要該分支的情況,但需謹慎使用,因為未合併的工作可能會丟失(除非透過
git reflog
找回)。
找回被刪除的分支:如果誤刪分支,可以使用 git reflog
找回:
git reflog
git branch <branch_name> <commit_id>
切換分支
切換到指定分支:
git switch cat
10. git merge 合併分支
快轉 (fast-forward) 合併
將目前分支快轉到另一個分支:
git switch master
git merge cat
合併前的 Git Graph:
* C3 (cat)
|
* C2 (master)
|
* C1
合併後(快轉合併):
* C3 (cat, master)
|
* C2
|
* C1
非快轉合併
如果想保留合併歷程,產生一個新的合併節點(merge commit),可以使用 --no-ff
參數:
git merge cat --no-ff -m "merge"
合併後(非快轉合併):
* M (master) merge commit
|\
| * C3 (cat)
| |
* | C2
|/
* C1
-
M
(merge commit):新的合併節點,記錄了dog
和cat
分支的合併歷史。 -
分支歷史:
dog
分支的歷史現在包含了cat
分支的提交。 -
合併歷程可視化:這種方式保留了分支的合併歷史,能在 Git Graph 中清楚地看到分支的合併過程。
-
適用場景:當需要保留分支的合併歷史,或在團隊協作中需要清楚記錄分支的來源時,使用非快轉合併是更好的選擇。
無法快轉的合併
在 Git 中,快轉合併(fast-forward merge)是指當目標分支的歷史是當前分支的直接延續時,Git 只需將當前分支的指標移動到目標分支的最新提交即可完成合併。
然而,當兩個分支的歷史路徑不同時,無法進行快轉合併,因為這兩個分支的提交歷史已經分叉。此時,Git 需要創建一個新的合併節點來將這兩條歷史路徑合併在一起。
當兩個分支的歷史路徑不同時,無法進行快轉合併(fast-forward merge),這種情況會產生一個新的合併節點(merge commit)。以下是詳細的解釋與範例。
git switch dog
git merge cat -m "Merge branch 'cat' into dog"
與快轉合併的比較
特性 | 快轉合併(Fast-Forward Merge) | 無法快轉合併(Non-Fast-Forward Merge) |
---|---|---|
提交歷史 | 不產生新的合併節點 | 產生新的合併節點 |
分支歷史 | 無法保留分支的獨立歷史 | 保留分支的獨立歷史 |
適用場景 | 單人開發或簡單的分支合併 | 團隊協作或需要完整歷史記錄 |
11 Merge 衝突未解決的情況
當執行 git merge
時,如果發生衝突但未解決,Git 仍然允許合併完成,但這可能導致合併的內容不正確。
解決方法
- 檢查衝突:Git 會在衝突的檔案中標註衝突的區域,需要手動編輯檔案來解決衝突。
- 提交修正後的結果:
git add . git commit -m "Resolve merge conflict"
- 如果決定放棄合併,可以使用以下指令取消合併過程:
git merge --abort
12. git rebase
用於重新整理分支的提交歷史。它可以將一個分支的基底(base)換到另一個分支上,從而使提交歷史更加線性化,便於閱讀和管理。
git rebase 其實算是一個「危險」的指令,因為它會改寫整個commit 的歷史,實務上不會對團隊共同開發的穩定分支(像master, main, dev,…等)下這個指令
Rebase 的用途
- 清理提交歷史:將分支的提交歷史整理成一條直線,避免分支間的交錯。
- 整合功能分支:在將功能分支合併到主分支之前,使用 Rebase 將功能分支的基底更新到最新的主分支。
- 避免多餘的合併節點:與
merge
不同,Rebase 不會產生新的合併節點(merge commit)。
Rebase 的基本操作
以下是將 dog
分支的基底換到 cat
分支的範例:
git switch dog
git rebase cat
執行上述指令後,dog
分支的提交歷史會被重新整理,並接在 cat
分支的最新提交之後。
Rebase 的優缺點
優點:
- 簡化歷史:提交歷史更加線性,便於閱讀。
- 避免多餘的合併節點:不像
merge
,Rebase 不會產生新的合併節點。
缺點:
- 破壞歷史:Rebase 會改變提交的哈希值(commit hash),因此不適合用於已經共享的分支。
- 衝突處理:如果 Rebase 過程中發生衝突,需要手動解決,並使用以下指令繼續 Rebase:
git rebase --continue
Rebase 與 Merge 的比較
特性 | Rebase | Merge |
---|---|---|
提交歷史 | 線性化,歷史更簡潔 | 保留完整的分支合併歷史 |
合併節點 | 不產生新的合併節點 | 產生新的合併節點(merge commit) |
適用場景 | 清理歷史、更新基底 | 保留分支歷史 |
衝突處理 | 每個提交單獨解決衝突 | 一次解決所有衝突 |
注意事項
-
避免在共享分支上使用 Rebase:Rebase 會改變提交的哈希值,可能導致其他開發者的分支出現問題。
-
記住原始狀態:
git rebase
是一個「危險」的指令,rebase 前,git 會自動保存原始狀態到ORIG_HEAD
,如果需要回到 rebase 前的狀態,可以使用以下指令:git reset ORIG_HEAD --hard
13. git reset 刪除與復原節點
刪除末端節點
將目前的分支重置到指定的提交:
git reset <ID>
重置模式:
--mixed
(預設):異動會出現在工作目錄(unstaged)。--soft
:異動會出現在 staging 區(已 add)。--hard
:異動會直接被丟棄。
使用 git reflog 找回節點
git reflog
紀錄 HEAD
的移動
如果分支被刪除,該節點會變成無名分支(dangling commit)。可以使用 git reflog
找回並重新貼上分支:
git branch cat <commit_id>
14. 進階技巧
查詢檔案歷史
查看檔案每行程式的作者與修改時間:
git blame index.html
在 VS Code 中可以使用 GitLens
插件直接查看。
修改分支名稱
重新命名分支:
git branch -m new_name
git branch 也可能造成 HEAD 移動:
改 branch 名字, HEAD 本身沒有動,但換指向另一個地方,reflog 有紀錄
15. 復原操作
復原 Merge
git merge cat -m "Merge"
git reset HEAD^ --hard # 回到 HEAD 的上一步
復原 Rebase
git rebase cat
git reset ORIG_HEAD --hard
Rebase 前會自動存一份 ORIG_HEAD,方便回到舊狀態。
16. Reset 與 Reflog 的搭配使用
在 Git 中,reset
和 reflog
是一組強大的工具,能夠讓我們靈活地操作分支歷史:
- Reset:可以將目前的分支移動到任何指定的節點,讓分支的狀態回到該節點的樣子。這就像「飛行」一樣,可以快速跳轉到不同的提交。
- Reflog:記錄了
HEAD
的所有移動歷史,就像一張「地圖」,幫助我們追蹤分支的變化。即使某些節點不再被分支指向,也可以透過reflog
找回。
使用範例
-
查看
HEAD
的移動歷史:git reflog
-
將分支重置到某個歷史節點:
git reset <commit_id> --hard
注意事項
reset
是一個強大的工具,但需謹慎使用,特別是--hard
模式,因為它會丟棄未提交的變更。reflog
是找回遺失節點的救命工具,即使分支被刪除,節點仍然可以透過reflog
找回。
透過 reset
和 reflog
的搭配,可以靈活地操作分支歷史,並在需要時回到任何過去的狀態。
17. 隱形節點的產生
在 Git 中,每個分支都像一個便利貼,指向某個提交(commit)。當分支被刪除後,該分支指向的節點(末端節點)會變成無人看管的狀態,這些節點會「隱形」,但實際上它們仍然存在於 Git 的歷史中。
讓隱形節點重新現形
如果需要讓隱形的節點重新出現在分支中,可以將分支重新貼回該節點:
- 使用
git reflog
找到隱形節點的提交 ID。git reflog
- 建立一個新的分支,指向該提交:
git branch <branch_name> <commit_id>
18. Git 的非破壞性操作
Git 的操作本質上是非破壞性的,所有的提交歷史都會被保留。即使使用 git rebase
,原始的節點也不會被刪除,而是被隱藏。
Rebase 的背後原理
- 複製節點:
git rebase
的操作實際上是複製原始節點,並將它們移動到新的基底(base)後面。 - 隱藏原始節點:原始的節點會被隱藏,但可以透過
git reflog
找回。
19. 結語
Git 是一個功能強大的版本控制工具,熟練掌握後可以大幅提升開發效率與團隊協作能力。