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

刪除分支

刪除分支:

  1. git branch -d
    git branch -d 用於刪除 已經合併到當前分支的分支。如果分支尚未合併,Git 會拒絕刪除,並提示錯誤,避免意外刪除未合併的工作。
git branch -d <branch_name>
  1. 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):新的合併節點,記錄了 dogcat 分支的合併歷史。

  • 分支歷史: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 仍然允許合併完成,但這可能導致合併的內容不正確。

解決方法

  1. 檢查衝突:Git 會在衝突的檔案中標註衝突的區域,需要手動編輯檔案來解決衝突。
  2. 提交修正後的結果
    git add .
    git commit -m "Resolve merge conflict"
    
  3. 如果決定放棄合併,可以使用以下指令取消合併過程:
    git merge --abort
    

12. git rebase

用於重新整理分支的提交歷史。它可以將一個分支的基底(base)換到另一個分支上,從而使提交歷史更加線性化,便於閱讀和管理。

git rebase 其實算是一個「危險」的指令,因為它會改寫整個commit 的歷史,實務上不會對團隊共同開發的穩定分支(像master, main, dev,…等)下這個指令

Rebase 的用途

  1. 清理提交歷史:將分支的提交歷史整理成一條直線,避免分支間的交錯。
  2. 整合功能分支:在將功能分支合併到主分支之前,使用 Rebase 將功能分支的基底更新到最新的主分支。
  3. 避免多餘的合併節點:與 merge 不同,Rebase 不會產生新的合併節點(merge commit)。

Rebase 的基本操作

以下是將 dog 分支的基底換到 cat 分支的範例:

git switch dog
git rebase cat

執行上述指令後,dog 分支的提交歷史會被重新整理,並接在 cat 分支的最新提交之後。


Rebase 的優缺點

優點:

  1. 簡化歷史:提交歷史更加線性,便於閱讀。
  2. 避免多餘的合併節點:不像 merge,Rebase 不會產生新的合併節點。

缺點:

  1. 破壞歷史:Rebase 會改變提交的哈希值(commit hash),因此不適合用於已經共享的分支。
  2. 衝突處理:如果 Rebase 過程中發生衝突,需要手動解決,並使用以下指令繼續 Rebase:
    git rebase --continue
    

Rebase 與 Merge 的比較

特性 Rebase Merge
提交歷史 線性化,歷史更簡潔 保留完整的分支合併歷史
合併節點 不產生新的合併節點 產生新的合併節點(merge commit)
適用場景 清理歷史、更新基底 保留分支歷史
衝突處理 每個提交單獨解決衝突 一次解決所有衝突

注意事項

  1. 避免在共享分支上使用 Rebase:Rebase 會改變提交的哈希值,可能導致其他開發者的分支出現問題。

  2. 記住原始狀態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 中,resetreflog 是一組強大的工具,能夠讓我們靈活地操作分支歷史:

  • Reset:可以將目前的分支移動到任何指定的節點,讓分支的狀態回到該節點的樣子。這就像「飛行」一樣,可以快速跳轉到不同的提交。
  • Reflog:記錄了 HEAD 的所有移動歷史,就像一張「地圖」,幫助我們追蹤分支的變化。即使某些節點不再被分支指向,也可以透過 reflog 找回。

使用範例

  1. 查看 HEAD 的移動歷史:

    git reflog
    
  2. 將分支重置到某個歷史節點:

    git reset <commit_id> --hard
    

注意事項

  • reset 是一個強大的工具,但需謹慎使用,特別是 --hard 模式,因為它會丟棄未提交的變更。
  • reflog 是找回遺失節點的救命工具,即使分支被刪除,節點仍然可以透過 reflog 找回。

透過 resetreflog 的搭配,可以靈活地操作分支歷史,並在需要時回到任何過去的狀態。


17. 隱形節點的產生

在 Git 中,每個分支都像一個便利貼,指向某個提交(commit)。當分支被刪除後,該分支指向的節點(末端節點)會變成無人看管的狀態,這些節點會「隱形」,但實際上它們仍然存在於 Git 的歷史中。

讓隱形節點重新現形

如果需要讓隱形的節點重新出現在分支中,可以將分支重新貼回該節點:

  1. 使用 git reflog 找到隱形節點的提交 ID。
    git reflog
    
  2. 建立一個新的分支,指向該提交:
    git branch <branch_name> <commit_id>
    

18. Git 的非破壞性操作

Git 的操作本質上是非破壞性的,所有的提交歷史都會被保留。即使使用 git rebase,原始的節點也不會被刪除,而是被隱藏。

Rebase 的背後原理

  • 複製節點git rebase 的操作實際上是複製原始節點,並將它們移動到新的基底(base)後面。
  • 隱藏原始節點:原始的節點會被隱藏,但可以透過 git reflog 找回。

19. 結語

Git 是一個功能強大的版本控制工具,熟練掌握後可以大幅提升開發效率與團隊協作能力。