Joseph Chen
「Git 不是追蹤你改了什麼文字,而是替你的整個專案拍快照(snapshot)。每次 commit 都是一張快照,branch 是一條時間線,merge 是把兩條時間線合而為一。」
— 一旦理解這個心智模型,Git 所有指令都會變得直覺。
Git 是什麼?Snapshot vs Diff
很多人以為 Git 是「記錄每次改動了哪幾行」(diff-based),但這是錯的。Git 的底層是 snapshot-based:每一個 commit 是對整個專案當下狀態的一張完整快照(只有沒改動的檔案才共享指標節省空間)。
❌ 錯誤心智模型(Diff-based)
「記錄第 12 行從 A 改成 B」— 還原時需要逐一重播所有 diff,越久越慢。
✅ 正確心智模型(Snapshot-based)
「在時間點 T 整個專案長這樣」— 切換到任何 commit 都是 O(1),速度極快。
四個核心區域
理解 Git 的關鍵是知道「檔案現在在哪裡」。Git 把你的工作分成四個區域:
檔案流動路徑
Working Directory
你正在編輯的檔案
Staging Area
git add 後
Local Repo
git commit 後
Remote Repo
git push 後
(檔案修改中)你實際編輯的地方,git status 顯示為紅色 modified / untracked。
git add <file>暫存區,又叫 Index。你挑選要放進下一個 commit 的改動,可以只 add 部分檔案。
git commit -m "msg"本機的 .git 資料庫,存放所有快照歷史。commit 後就永久記錄在這裡。
git push / git pullGitHub / GitLab 等遠端伺服器,團隊共享的中央倉庫。
常用指令速查表
| 指令 | 作用 | 常見用法 |
|---|---|---|
| git init | 初始化 Git 倉庫 | git init(空資料夾 → .git) |
| git clone <url> | 複製遠端 repo 到本機 | git clone https://github.com/user/repo.git |
| git add | 加入 Staging Area | git add . / git add src/index.ts |
| git commit | 建立快照 | git commit -m "feat: add login" |
| git push | 推到遠端 | git push origin main |
| git pull | 拉下遠端最新 | git pull origin main(= fetch + merge) |
| git branch | 管理分支 | git branch feat/login / git branch -d feat/login |
| git checkout / switch | 切換分支或 commit | git switch main / git checkout -b feat/x |
| git merge | 合併分支 | git merge feat/login(合進目前分支) |
| git rebase | 重放 commit 歷史 | git rebase main(在 feat 上執行) |
| git stash | 暫存目前工作區 | git stash / git stash pop |
| git log --oneline | 精簡歷史紀錄 | git log --oneline --graph --all |
| git diff | 查看差異 | git diff HEAD / git diff main feat |
| git reset | 回退 commit 或 staging | git reset --soft HEAD~1 |
merge vs rebase:兩種合併策略
這是面試最常問的問題。兩者都能把分支的改動整合進來,但產生的 commit 歷史長得完全不同。
git merge feat
A — B — C ← main
\ /
D — E — M ← merge commit
- 產生一個新的 merge commit(M)
- 保留完整分岔歷史,可追溯
- 歷史較雜,有菱形圖
- 適合:open source、需保留 PR 歷史
git rebase main
A — B — C ← main
\
D' — E' ← feat(重放)
- 把 feat 的 commit 「搬」到 main 前端
- 歷史是一條直線,乾淨易讀
- commit hash 會改變(D → D')
- 適合:個人 feature branch、內部開發
HEAD 是什麼?Detached HEAD?
HEAD 是一個指標,指向你「目前所在的位置」。通常它指向某個 branch 名稱(間接指向該 branch 的最新 commit)。
正常狀態
HEAD → main → commit abc123HEAD 指向 branch,commit 後 branch 自動前進,HEAD 跟著走。
Detached HEAD
HEAD → commit abc123(直接)當你 git checkout <commit-hash> 時,HEAD 脫離 branch,進入「遊離」狀態。此時 commit 不屬於任何 branch,切換後會遺失。
面試常考題
以下 10 題是工程師面試中出現頻率最高的 Git 問題,每題附上標準答案要點。
Q1. git merge 和 git rebase 的差異?
merge 產生一個 merge commit,保留分岔歷史,適合需保留 PR 記錄的場景。rebase 把 commit 重放到目標 branch 前端,歷史是一條直線但 hash 改變,適合個人 branch 保持乾淨歷史。黃金法則:不要對已 push 的 public branch 做 rebase。
Q2. git reset 三種模式(--soft / --mixed / --hard)的差異?
--soft:回退 commit,但改動留在 Staging Area(可直接再 commit)。--mixed(預設):回退 commit,改動退回 Working Directory(需重新 add)。--hard:回退 commit,改動完全丟棄(危險!無法還原)。
Q3. git revert 和 git reset 的差異?
reset 是「修改歷史」,把 HEAD 移到舊 commit;revert 是「新增一個反向 commit」來撤銷改動,不改變歷史。已 push 到遠端時必須用 revert,不能用 reset(否則會讓其他人的歷史出現衝突)。
Q4. 什麼是 fast-forward merge?
當目標 branch 沒有新 commit(main 還停在分岔點),merge 不需要建立 merge commit,只是把 main 的指標向前移。可用 --no-ff 強制產生 merge commit 以保留歷史。
Q5. git stash 做什麼?什麼時候用?
把 Working Directory 和 Staging Area 的改動暫時收進 stash stack,讓工作區變乾淨。常見情境:做到一半需要緊急切換 branch 修 bug,又不想 commit 未完成的工作時。用 git stash pop 或 git stash apply 取回。
Q6. cherry-pick 是什麼?
把指定的某個 commit(或多個)複製到目前 branch,不需要整個 merge。常用於:hotfix 只需要把修 bug 的那個 commit 搬到其他 branch,而不是整個 feature branch。指令:git cherry-pick <commit-hash>。
Q7. 如何撤銷已 push 的 commit?
使用 git revert <commit-hash> 產生反向 commit,然後再 push。絕對不能對 public branch 用 git reset 然後 force push(會破壞其他人的歷史)。如果是自己私有 branch,可以 git push --force-with-lease(比 --force 安全)。
Q8. .gitignore 的優先順序?
已被 Git 追蹤的檔案,.gitignore 不會生效(需要先 git rm --cached <file>)。優先順序:命令列 pattern > 目錄層級 .gitignore(越深越優先)> 上層 .gitignore > ~/.gitignore_global。
Q9. git fetch 和 git pull 的差異?
fetch 只把遠端的新 commit 下載到本機(存在 origin/main 等 remote tracking branch),不修改你的 Working Directory。pull = fetch + merge(或 fetch + rebase,取決於設定)。好習慣:先 fetch,確認差異後再手動 merge,比直接 pull 更安全。
Q10. branch 是什麼?它儲存的是什麼?
branch 本質上只是一個指標(一個 40 bytes 的 commit hash 文字檔,存在 .git/refs/heads/ 下)。它指向某個 commit。建立 branch 的成本幾乎是零,切換 branch 也只是移動指標。這就是為什麼 Git branching 比其他版控工具快。