開發者必看的(de) 15 個困惑的(de) Git 術語(以及它們的(de)真正含義)
PHP 開發者必看的 15 個困惑的 Git 術語(以及它們的真正含義)
做了多年開發, 自 2015 年開始使用 Git, 我審過(guo)數(shu)百個 Pull Request,收拾(shi)過(guo)無數(shu)混亂的(de)代碼倉庫,也帶過(guo)不少在 Git 命令里打轉的(de)新人。
老實說,我完全理解他們的困惑。Git 確實強大,但它的術語系統就像一個迷宮——很多詞看著相似,實際(ji)用(yong)法(fa)卻天差地別。
今(jin)天就來聊聊這些連老手(shou)都可(ke)能搞混的 Git 術語(yu)。如果你(ni)是新手(shou),這篇(pian)文章能幫(bang)你(ni)少走很多彎路。
HEAD vs head vs Detached HEAD
開發者常犯的錯誤:
把 HEAD 當成又一(yi)個(ge)分(fen)支(zhi)名(ming)。
它的真實身份:
HEAD 是一個指針,指向你當前所在的 commit —— 通常是你(ni)所(suo)在(zai)分支的最新提交。
每(mei)次你(ni)提交代碼(ma),HEAD 就會移動到那個新 commit 上。
# 查看 HEAD 指向哪里
git log --oneline --decorate -n 3
# --oneline 把每個 commit 壓縮成一行顯示:
# commit hash 的前 7 個字符 + commit 信息
# --decorate
# 在 commit 旁邊顯示分支和標簽引用
# 這樣你就能看到 HEAD、main、origin/main 或 v1.0.0 這些指針當前在哪
# -n 3
# 只顯示最近 3 個 commit
你會看到類似(si)這樣的輸出:
a3c4d5e (HEAD -> main, origin/main) Add user API
如果你直(zhi)接(jie) checkout 到(dao)某個(ge)舊 commit:
git checkout a3c4d5e
你會看到這樣的提示:You are in 'detached HEAD' state.
這(zhe)只(zhi)是說你現在(zai)不在(zai)任(ren)何分支上(shang)——你在(zai)查看(kan)歷史快照(zhao)。沒什(shen)么壞事發生。
如果你想保存在這里做的工(gong)作:
git switch -c hotfix/legacy-bug
至于 head(小寫),它不是 Git 的關鍵字——通常只是非正(zheng)式(shi)用(yong)法或復數形(xing)式(shi),比如"分支的 heads"。
Git 把分支的最新提交存在 .git/refs/heads/ 下,所以你可能會在(zai)內部引用中看(kan)到這個詞(ci)。
比如:
.git/refs/heads/main
.git/refs/heads/feature/login
所以當你看(kan)到"所有 heads"時,意思是"每個分支(zhi)的最新 commit",而不是那(nei)個特(te)殊的 HEAD 指針。
后綴的含義:
Git 提(ti)供(gong)了一些方式讓你從 HEAD 開始往回(hui)追溯歷史(shi)。
HEAD~1
表示(shi)"HEAD 之前的一個 commit"。
HEAD~2 表(biao)示"之前的兩個 commit",以此類推。
git show HEAD~1
顯示(shi)你(ni)當前 commit 的上一個。
HEAD^1 和 HEAD^2
這些是(shi)父(fu)指針。它們在處理 merge commit 時(shi)最(zui)有用。
一個(ge)(ge) merge commit 有兩個(ge)(ge)父節(jie)點——一個(ge)(ge)來自(zi)你所在的分支,另一個(ge)(ge)來自(zi)你合(he)并(bing)進來的分支。
git diff HEAD^1 HEAD^2
對比合(he)并時(shi)兩邊各(ge)自(zi)貢獻了什么。
^1 表示"第(di)一個父(fu)節(jie)點",^2 表示"第(di)二個父(fu)節(jie)點"。
經驗法則:
- 用
~線性地往回走 - 用
^處理 merge commit 的父節點
如下圖演示

把(ba) feature 合并到 main 后,merge commit (M) 有兩個父節點(dian):
- HEAD^1 → commit C(合并前的 main)
- HEAD^2 → commit E(feature 分支的末端)
一旦你(ni)把 HEAD 想象成"你(ni)在這(zhe)里",Git 的導航模(mo)型就瞬間清晰(xi)了。
Commit vs Changeset
很(hen)多開發(fa)者(zhe)以(yi)為 commit 就(jiu)是 diff——其(qi)實不是。
- commit = 倉庫的完整快照 + 元數據 + 父節點鏈接
- changeset = 兩個 commit 之間的差異
git show HEAD # 顯示 commit 及其 diff(changeset 視圖)
git diff HEAD~1 HEAD # 只顯示兩個 commit 之間的原始 diff
git add -p # 選擇性暫存變更(構建你的 changeset)
git commit -m "Fix NPE in user lookup"
理解這個區別能幫你打造原子化 commit——每(mei)個 commit 只包含(han)一個邏(luo)輯(ji)變(bian)更——而不(bu)是一股腦全扔(reng)進去。
Branch ≠ Copy
創建分支(zhi)不會克隆代(dai)碼(ma)庫。
它只是創(chuang)建一個指向 commit 的新指針。
git branch feature/login
git switch feature/login
現在你(ni)有了一個從 main 分叉出去的輕(qing)量級指針。沒有額外文件,沒有復(fu)制。
Tags
Tags 標記(ji)特定(ding)的(de) commit——通常用(yong)于發布(bu)版本,而(er)且不(bu)會移動。
git tag v1.0.0
git push origin v1.0.0
和(he)分支不(bu)同,tags 是不(bu)可變的。一旦設置,它們永遠指(zhi)向同一個 commit。
Fast-Forward Merge
Fast-forward 不是什么(me)特殊操作——它(ta)只(zhi)是沒有分叉的合并。
無分叉 -> 可以 Fast-forward

main 從 B 之后就沒動過。
git switch main
git merge --ff-only feature
結果:

沒有創建 merge commit -> Git 只是把 main 的(de)指針往前(qian)移了。
分支已分叉 -> 無法 Fast-forward

兩邊都在 B 之后有了新提交。
git switch main
git merge feature
現在 Git 必須創建一個 merge commit 來合并 E 和 D。
如果(guo)你(ni)堅持要(yao) fast-forward:
git merge --ff-only feature
# error: Not possible to fast-forward
規則:
如果雙(shuang)方在分(fen)叉后都有新 commit,就(jiu)無(wu)法(fa) fast-forward。
Squash
Squash 在合(he)并前把多(duo)個 commit 壓扁成一(yi)個——適合(he)清(qing)理(li)混(hun)亂的(de) feature 分支歷史。
git merge --squash feature/login
git commit -m "Add login feature"
你的(de)分支歷(li)史保持干凈(jing),同(tong)時在 PR 里不會丟失工作上下文。
origin vs upstream
這個挺有意思的……我(wo)幾(ji)天前在(zai)處理一個 fork 項目時才(cai)搞(gao)清楚(chu)區別。之前我(wo)一直以為它倆是一回事 ??
origin 是你克隆倉庫時默認的遠(yuan)程倉庫名稱。
如果你 fork 了一個倉庫,原始倉庫叫做 upstream。
git remote -v
你可能會看到:
origin git@github.com:yourname/project.git
upstream git@github.com:org/project.git
用 upstream 從原始源拉(la)取變(bian)更:
git fetch upstream
Remote-Tracking Branches
origin/main 和 main 不是一回事。
main= 你的本地分支origin/main= Git 對遠程 main 的最后已知快照
git fetch
git diff main origin/main
這(zhe)能(neng)告訴你上(shang)游有什么變化,而不(bu)用(yong)立即合并。
Fetch vs Pull
git fetch 只更新(xin)你對遠(yuan)程倉庫(ku)的本地(di)認知(zhi)。
git pull 做了這個,然后自(zi)動(dong)合并(或(huo) rebase)。
git fetch origin
git merge origin/main
這比盲目 git pull 更安全、更明確。
The Staging Area (Index)
暫(zan)存區是 Git 的"候車室(shi)"。
你決(jue)定哪些變更進入下(xia)一個 commit。
git add src/App.java
git status
git commit -m "Fix null check"
你可以暫存特定的代(dai)碼(ma)塊:
git add -p
就像在(zai)結賬(zhang)前往購物車里放東西。
Working Directory
你(ni)的(de)工作(zuo)目錄就是你(ni)正在編(bian)輯(ji)的(de)文件——不是倉庫(ku)本身。
真正的倉庫在 .git/ 里。
git status
# "working tree clean" 意思是你的文件和 HEAD 一致
Tracked vs Untracked Files
你添加過一次的文(wen)件是"已跟蹤(zong)"的。
新文(wen)件在你暫存它們之前不會被跟蹤。
git status
# Untracked files: (use "git add <file>")
提交(jiao)前一定要檢查這(zhe)個——未(wei)跟蹤的文(wen)件不會出現(xian)在 commit 里。
Dirty Tree
"Dirty tree" 只是(shi)說你的工(gong)作目錄(lu)有(you)未提交的變(bian)更。
git status
# modified: src/App.java
切換分支(zhi)前(qian)先提交或 stash 它們,避免沖(chong)突。
Reset vs Revert
Git 里(li)最容易被誤解的兩個(ge)命令。
- reset 移動 HEAD,可能會丟棄 commit(危險)
- revert 創建一個新 commit 來撤銷之前的變更(安全)
git reset --hard HEAD~1 # 刪除最后一個 commit
git revert HEAD # 通過添加新 commit 安全地撤銷
如果你在共享分支上工作,永遠用 revert。
總結
從本質上講,Git 就是一個由 commit 和 指針 組成的圖。
一旦你理解了 HEAD、分支 和 快照 的(de)工作原理(li),其(qi)他一切都會(hui)變得合乎邏輯(ji)。
