2005 - Git 分散式版本控制:十天寫出的版本控制革命#
案件摘要#
2005 年 4 月,BitKeeper 收回 Linux 核心的免費授權,Linus Torvalds 在氣憤之下停工十天,寫出了 Git。 它把版本控制從「向中央伺服器租借」翻轉成「每個人都是完整倉庫」,分支從昂貴的目錄複製變成一次指標寫入。 Git 以內容定址的 commit DAG 取代檔案差異,讓數千人的核心開發重新有了秩序。 此後二十年,Git 成為版本控制的事實標準,GitHub 則把程式設計推向社交化與開源民主化。
前因 -- 為什麼會有這個案子#
- 集中式版本控制的罩門:RCS(1985)/CVS(1990)/Subversion(2000)都假設一個中央倉庫——單點故障、離線不可用、提交歷史只有一份真相。伺服器掛了,整個團隊就停擺。
- 分支昂貴:CVS/SVN 的分支是目錄級複製,合併時要逐檔三方比對,長期分支的合併衝突讓工程師視分支為猛獸,寧可大家都擠在 trunk 上。
- Linux 核心的規模:2005 年 Linux 核心有數千貢獻者、每年數萬次提交,CVS/SVN 的合併複雜度隨貢獻者數量近似 \(O(n^2)\) 地上升,patch bomb 郵件已經撐不住。
- BitKeeper 授權爭議:2002 年起核心開發改用專有的 BitKeeper,2005 年 4 月 Andrew Tridgell 反向工程其協定,BitMover 的 Larry McVoy 遂收回免費授權——Torvalds 認定「不能依賴別人的恩惠」。
- 關鍵推理:Torvalds 對版本控制的需求排序是——速度第一、分散式第二、合併第三、分支必須便宜:
2005 年 4 月 3 日動工,4 月 7 日 Git 宣佈 self-hosting,6 月 16 日 2.6.12 核心用 Git 發行——十天寫出的原型成為核心的新動脈。
線索與推理 -- 數學式、程式、理論#
1. 案件現場:內容定址的物件資料庫#
Git 只有一種資料結構:內容定址的 key-value 儲存。每個物件以 SHA-1 為鍵:
- 快照而非差異:CVS/SVN 記錄「這次改了哪幾行」,Git 的 commit 記錄「這一刻整棵樹長什麼樣」——差異只是兩個快照之間推導出來的觀點。
- 內容定址的好處:內容不變則 hash 不變,物件天然去重;任何篡改都會改變 hash,歷史不可否認地自驗真偽。
- 四種物件:blob(檔案內容)、tree(目錄)、commit(快照+訊息+父節點)、tag(附簽名的標記)。
2. Commit DAG:分支只是指標#
Git 的歷史是一張有向無環圖(DAG):每個 commit 指向一或多個父節點。
- 分支 = 指標:
master、feature-x都只是.git/refs/下的一個文字檔,內容是一個 commit hash——建立分支的成本是寫 41 位元組, \(O(1)\) ,與專案大小無關。 - SVN 對比:SVN 建立分支要複製數千個檔案的元資料,成本 \(O(n)\) ;這解釋了為什麼 SVN 時代沒人敢開分支,Git 時代每人同時開十個。
- 合併即圖運算:
git merge找出兩個分支的最近共同祖先(merge-base),做三方合併:
3. 三方合併的數學#
兩方合併(只比 A、B)無法區分「新增」與「刪除」,三方合併引入共同祖先 \(M\) 作為基準: \(\text{diff}(M, A)\) = 我改的, \(\text{diff}(M, B)\) = 你改的。判決規則:
- 只有我改 → 取我的;只有你改 → 取你的;都改且相同 → 取共同結果;都改且不同 → 衝突,交給人。
這個演算法源自 diff3(1988),Git 以遞迴merge-base(多祖先時先合併祖先)處理複雜的十字交叉分支。rebase 則是另一條路:把我的 commit 逐一重放到你的分支頂端,讓歷史變成線性——merge 保留真相的拓撲,rebase 換取歷史的整潔。
4. 分散式的拓撲:每人都是完整倉庫#
- 離線可用:commit、branch、log、diff 全部本地完成,只有 push/pull 需要網路。
- 多中心協作:核心開發變成「pull request 的星系」——每個維護者 pull 貢獻者的倉庫,review 後 merge,再 push 給上游。信任沿著 DAG 傳遞。
- 備份即部署:每個 clone 都是一次完整備份, \(n\) 位開發者就有 \(n\) 份歷史,單點故障從物理上被消滅。
5. GitHub(2008):社交化程式設計#
2008 年上線的 GitHub 把「pull request」從技術名詞變成社交儀式:fork(一鍵 clone 到自己帳號)、branch、commit、PR、code review、merge。開源貢獻從「寄 patch 給維護者」變成「在平台上發起討論」——貢獻的門檻從認識維護者降到一個 GitHub 帳號,開源生態自此指數成長。
6. Python 實作:簡易 commit DAG 模擬#
import hashlib, itertools
class MiniGit:
def __init__(self):
self.objects = {} # hash -> (parents, message)
self.refs = {"master": None} # branch -> commit hash
def _h(self, parents, msg):
"""內容定址:SHA-1(parents || message)。"""
payload = ("".join(parents) + msg).encode()
return hashlib.sha1(payload).hexdigest()[:8]
def commit(self, branch, msg):
parent = self.refs[branch]
h = self._h([parent] if parent else [], msg)
self.objects[h] = ([parent] if parent else [], msg)
self.refs[branch] = h # 分支只是指標,寫入 O(1)
return h
def branch(self, name, frm):
self.refs[name] = self.refs[frm] # 開分支 = 複製指標
def merge_base(self, a, b):
"""最近共同祖先:收集 a 的所有祖先,找 b 的祖先中最近者。"""
def ancestors(h):
seen, stack = set(), [h]
while stack:
cur = stack.pop()
if cur in seen: continue
seen.add(cur)
stack.extend(self.objects[cur][0])
return seen
a_anc = ancestors(a)
for node in self._bfs_layers(b): # 由近到遠找
if node in a_anc and node != a and node != b:
return node
return None
def _bfs_layers(self, h):
seen, q = set(), [h]
while q:
cur = q.pop(0)
if cur in seen: continue
seen.add(cur); yield cur
q.extend(self.objects[cur][0])
def merge(self, a, b, msg):
"""三方合併:建立有兩個 parent 的 merge commit。"""
h = self._h([a, b], msg)
self.objects[h] = ([a, b], msg)
self.refs["master"] = h
return h
g = MiniGit()
c1 = g.commit("master", "init")
c2 = g.commit("master", "feature A part 1")
g.branch("dev", "master")
c3 = g.commit("master", "feature A part 2")
c4 = g.commit("dev", "hotfix on dev")
c5 = g.commit("dev", "feature B")
mb = g.merge_base(c3, c5)
print(f"merge-base(master, dev) = {mb}, 即 commit '{g.objects[mb][1]}'")
# merge-base(master, dev) = c2 的 hash,即 commit 'feature A part 1'
m = g.merge(c3, c5, "merge dev into master")
print(f"merge commit {m} 的 parents = {g.objects[m][0]}(兩個父節點,DAG 分叉點)")
# merge commit <hash> 的 parents = [c3 的 hash, c5 的 hash](兩個父節點,DAG 分叉點)
結案 -- 後果與影響#
- Git 成為事實標準:到 2010 年代中,SVN/Mercurial 幾乎退出主流;Git 成為每位工程師的必備技能。
- GitHub 與開源生態:2008 年 GitHub、2011 年 GitLab;fork/PR 文化讓開源貢獻民主化,npm、Docker、Kubernetes 等生態都建立在 Git 之上。
- PR 與 code review 文化:Pull request 把審查變成開發流程的正式關卡,工程實踐(如 trunk-based development 與短命分支)也因分支便宜而成為可能。
- DevOps 基礎設施:CI/CD、Infrastructure as Code、GitOps——一切「以版本控制為真相來源」的實踐都以 Git 為地基。
- SHA-1 的餘波:2017 年 SHA-1 碰撞被證明(SHAttered),Git 隨即推出撞擊防護,並逐步遷移至 SHA-256——內容定址的設計讓演算法可替換。
關鍵人物與文獻#
- Linus Torvalds:Git 原作者;2005 年 4 月在 linux-kernel 郵件列表宣告 "I'm doing the kernel source control again"。
- Junio Hamano:2005 年 7 月起接手 Git 維護至今,把十天原型修煉成穩定工具。
- Andrew Tridgell:BitKeeper 反向工程者,引爆授權爭議;Larry McVoy:BitMover 創辦人。
- Tom Preston-Werner、PJ Hyett、Scott Chacon:GitHub(2008)共同創辦人,把 Git 推向大眾。
- Scott Chacon & Ben Straub, Pro Git, 2nd ed., Apress, 2014(git-scm.com/book 免費全文)。