第 06 章

進階操作

當你已經熟悉基本的 commit、branch、merge 後,這一章將帶你掌握四個進階工具:rebase、revert、reset 與 stash。理解它們的差異與適用時機,是從 Git 初學者進階到熟練使用者的關鍵。

Rebase(變基)

Rebase 的核心作用是:把一個分支的 commit「搬到」另一個分支的最新 commit 之後,讓歷史看起來像一條直線。

Rebase vs Merge 的差異

Merge 的結果

保留完整的分支歷史,建立一個 merge commit。

gitGraph commit id: "C1" commit id: "C2" branch feature commit id: "C3" commit id: "C4" checkout main commit id: "C5" merge feature id: "M"

Rebase 的結果

將 feature 的 commit 「接」在 main 的最新 commit 之後,形成線性歷史。

flowchart LR C1 --> C2 --> C5 --> C3'["C3'"] --> C4'["C4'"]

C3' 和 C4' 是重新建立的 commit(內容相同但 SHA-1 不同)。

Rebase 的操作步驟

在 feature 分支上 rebase main

# 確保在 feature 分支上
$ git checkout feature/login

# 將 feature 的 commit rebase 到 main 的最新點
$ git rebase main

# 如果有衝突,解決後繼續
$ git add <resolved-file>
$ git rebase --continue

# 如果想放棄 rebase
$ git rebase --abort

# rebase 完成後,切回 main 合併(此時會 fast-forward)
$ git checkout main
$ git merge feature/login
flowchart TD A["在 feature 分支上"] --> B["git rebase main"] B --> C{"有衝突嗎?"} C -->|"沒有"| D["rebase 完成
feature 的 commit 已移到 main 後方"] C -->|"有"| E["手動解決衝突"] E --> F["git add + git rebase --continue"] F --> C D --> G["git checkout main"] G --> H["git merge feature
(fast-forward)"]
⚠️ 黃金守則:不要 rebase 已經推送到遠端的共享分支。

Rebase 會改寫 commit 的 SHA-1(因為 parent 改變了)。如果別人已經基於舊的 commit 工作,rebase 會造成歷史不一致的問題。

安全使用 rebase 的時機:

  • ✅ 在你個人的 feature 分支上 rebase main(push 前整理歷史)。
  • ✅ 用 git pull --rebase 取代預設的 merge pull。
  • ❌ 對 main、develop 等共享分支做 rebase。
  • ❌ 對已經推送給別人的 commit 做 rebase。

Interactive Rebase(互動式 Rebase)

git rebase -i 可以在 rebase 的過程中編輯、合併或重新排列 commit。

# 對最近 3 筆 commit 進行互動式 rebase
$ git rebase -i HEAD~3

# 編輯器會顯示:
pick a1b2c3d feat: 新增登入頁面
pick e4f5g6h fix: 修正登入驗證
pick i7j8k9l style: 調整登入頁樣式

# 可以修改 pick 為其他動作:
# pick   = 保留 commit
# reword = 修改 commit message
# squash = 合併到前一個 commit
# drop   = 刪除 commit

Revert(安全撤銷)

git revert 是「安全撤銷」的方式——它不會刪除歷史,而是建立一個新的 commit 來反轉指定 commit 的變更

flowchart LR C1 --> C2 --> C3["C3
(有 bug)"] --> C4["Revert C3
(撤銷 C3 的變更)"]
# 撤銷最近一筆 commit
$ git revert HEAD

# 撤銷指定的 commit
$ git revert a1b2c3d

# 撤銷但不自動 commit(先看看結果)
$ git revert --no-commit a1b2c3d
Revert 的優點:歷史紀錄完整保留,不會影響其他人已經 pull 的 commit。適合撤銷已經推送到遠端的變更。

Reset(回退指標)

git reset 會把分支指標移動到指定的 commit,效果是「讓歷史倒退」。根據模式不同,對工作目錄和暫存區的影響也不同。

三種 Reset 模式

模式分支指標暫存區 (Index)工作目錄適用場景
--soft ✅ 移動 ❌ 不變 ❌ 不變 想重新寫 commit message 或合併 commit
--mixed(預設) ✅ 移動 ✅ 重設 ❌ 不變 想重新選擇要 add 的檔案
--hard ✅ 移動 ✅ 重設 ✅ 重設 想完全丟棄變更,回到乾淨狀態

三種 Reset 模式的效果比較

# 回退一筆 commit,但保留所有變更在暫存區
$ git reset --soft HEAD~1

# 效果:
# - 最後一筆 commit 被移除
# - 那些變更仍在暫存區(可直接 commit)
# - 工作目錄不變

# 常見用途:重新寫 commit message
$ git commit -m "更好的提交訊息"
# 回退一筆 commit,變更從暫存區移回工作目錄
$ git reset HEAD~1
# 等同於 git reset --mixed HEAD~1

# 效果:
# - 最後一筆 commit 被移除
# - 變更存在工作目錄中(但未 staged)
# - 你可以重新選擇要 add 的檔案

$ git add file1.js    # 只 add 部分檔案
$ git commit -m "只提交 file1 的變更"
# 回退一筆 commit,同時丟棄所有變更
$ git reset --hard HEAD~1

# 效果:
# - 最後一筆 commit 被移除
# - 暫存區重設
# - 工作目錄也被還原到 HEAD~1 的狀態
# - 所有未提交的變更都會遺失!
⚠️ --hard 會永久丟失工作目錄中未提交的變更。操作前請確認已經備份或不需要這些變更。

Revert vs Reset 怎麼選?

revertreset
是否改寫歷史不改寫(新增一筆反轉 commit)改寫(移除 commit)
已推送到遠端?✅ 安全使用❌ 危險(需 force push)
適用場景撤銷已公開的 commit整理尚未公開的本地歷史

Stash(暫存工作)

git stash 讓你把「做到一半」的變更暫時存起來,讓工作目錄回到乾淨狀態。等你處理完其他事情後,再把暫存的變更取回來。

使用情境

🔀 臨時切分支

正在開發 feature,老闆突然要你修一個 bug。先 stash,切到 hotfix 分支處理,再回來。

🔄 拉取更新

git pull 但工作目錄有未提交的變更會衝突。先 stash,pull 完再取回。

🧪 測試乾淨環境

想在沒有自己修改的情況下測試某些功能。先 stash 清空,測完再恢復。

Stash 操作指令

指令用途
git stash暫存當前的工作目錄和暫存區變更
git stash push -m "描述"暫存並加上描述(推薦)
git stash list列出所有暫存的工作
git stash pop取回最近一筆暫存並刪除它
git stash apply取回最近一筆暫存但不刪除
git stash drop刪除最近一筆暫存
git stash clear清空所有暫存

Stash 實際操作

# 正在開發 feature...
$ echo "WIP: login form" >> login.html
$ git status
Changes not staged for commit:
  modified:   login.html

# 老闆說:先修 bug!
$ git stash push -m "login 頁面做到一半"
Saved working directory and index state On feature: login 頁面做到一半

# 工作目錄現在乾淨了
$ git status
nothing to commit, working tree clean

# 切去修 bug...
$ git checkout main
$ git checkout -b hotfix/fix-typo
# ...修完 bug,合併回 main...

# 回到 feature 分支
$ git checkout feature/login

# 取回暫存的工作
$ git stash pop
# login.html 的變更回來了!
# 可以暫存多次
$ git stash push -m "功能 A 的修改"
$ git stash push -m "功能 B 的修改"

# 列出所有暫存
$ git stash list
stash@{0}: On feature: 功能 B 的修改
stash@{1}: On feature: 功能 A 的修改

# 取回特定的暫存
$ git stash apply stash@{1}  # 取回功能 A

# 刪除特定的暫存
$ git stash drop stash@{0}   # 刪除功能 B

四個指令的比較總覽

指令作用改寫歷史?安全性適用場景
rebase 將 commit 移到另一個基準點 ✅ 是 ⚠️ 注意 整理本地分支歷史
revert 建立反轉 commit ❌ 否 ✅ 安全 撤銷已公開的 commit
reset 移動分支指標 ✅ 是 ⚠️ 注意 整理未公開的本地 commit
stash 暫時保存工作 ❌ 否 ✅ 安全 臨時切換分支或清理環境
flowchart TD A{"你想做什麼?"} --> B["撤銷已推送的 commit"] A --> C["整理本地未推送的歷史"] A --> D["暫時保存做到一半的工作"] A --> E["讓分支歷史變成線性"] B --> B1["git revert"] C --> C1["git reset"] D --> D1["git stash"] E --> E1["git rebase"] style B1 fill:#d4edda style C1 fill:#fde8e4 style D1 fill:#d4edda style E1 fill:#fde8e4

🔧 動手練習

練習 1:Reset 三種模式

# 建立 3 筆 commit
$ echo "a" > a.txt && git add . && git commit -m "add a"
$ echo "b" > b.txt && git add . && git commit -m "add b"
$ echo "c" > c.txt && git add . && git commit -m "add c"

# 測試 --soft
$ git reset --soft HEAD~1
$ git status   # c.txt 在暫存區

# 恢復後測試 --mixed
$ git commit -m "add c"
$ git reset HEAD~1
$ git status   # c.txt 在工作目錄但未 staged

# 恢復後測試 --hard
$ git add . && git commit -m "add c"
$ git reset --hard HEAD~1
$ ls           # c.txt 消失了

練習 2:Stash 操作

# 做一些修改
$ echo "work in progress" >> README.md

# 暫存
$ git stash push -m "README 修改中"

# 確認乾淨
$ git status

# 查看 stash 清單
$ git stash list

# 取回
$ git stash pop

# 確認修改回來了
$ cat README.md

本章小結

知識重點

  • rebase 讓歷史線性化,但會改寫 commit SHA-1。
  • 不要 rebase 已推送的共享分支(黃金守則)。
  • revert 安全地撤銷 commit,適合已公開的變更。
  • reset 三種模式:soft(保留暫存)、mixed(保留工作目錄)、hard(全部重設)。
  • stash 暫存工作,臨時切換場景後再取回。

課程總結

  • 你已經學完了 Git 的核心觀念與主要操作。
  • 從 Repository 概念、init、commit、到 GitHub 協作與進階操作。
  • 建議在實際專案中持續練習,遇到問題時回頭查閱對應章節。
  • 記住:git status 是你永遠的好朋友。
恭喜完成所有章節! Git 的學習曲線前期較陡,但一旦掌握了核心觀念,你會發現它是開發工作中最可靠的夥伴。 建議接下來在實際的小專案中使用 Git + GitHub 工作流程,讓這些知識內化為直覺。