第十章:大连接——从“独狼”到“交响乐团”(以及中途那该死的“合并地狱”)
“在我这儿能跑!”—— 编程史上最孤独的遗言
欢迎来到“新世界”,旅行者。在你“沉睡”的那个年代,编程是一场私密、孤独的对话。是你,和你的 Commodore 64,在地下室里,用 BASIC 语言进行着灵魂交流1。你的代码是一个人的“独奏”(solo)2。你是你那个8位王国的国王、议会和唯一的臣民。你的意志就是法律。
但现在,你环顾四周,发现自己身处一个巨大的机库,里面有几百人,他们都在试图同时演奏同一首曲子。欢迎来到“交响乐团”(symphony)3。你手里的不再是指挥棒,而是第二小提琴手的第三张谱子,而且你左边那个拉大提琴的哥们(他自称是“DevOps工程师”)似乎严重跑调了。
在你试图理解这一切之前,我们必须先面对一个诅咒。一个早在1975年,就在你“沉睡”之前,由一位名叫弗雷德·布鲁克斯(Fred Brooks)的先知,在他那本名为《人月神话》(The Mythical Man-Month)的圣经中记录下来的诅咒4。
这个诅咒,或者说“布鲁克斯定律”(Brooks's Law),用一种极其反直觉的简洁语言摧毁了(并且至今仍在摧毁)无数项目经理的天真幻想:“给一个延期的软件项目增加人手,只会让它更晚”5。
在你的时代,这个定律听起来像疯话。如果一个谷仓建得太慢,多加十个人,谷仓不就建得更快了吗?布鲁克斯用一个不朽的比喻解释了为什么软件不是谷仓。他指出,软件开发具有复杂的“顺序性”(sequentiality)6。一个测试人员不能在一个功能被编码出来之前就开始测试它。最著名的比喻是:“你不能让九个女人在一个月内生出一个孩子”6。
为什么不行?布鲁克斯指出了两个(在当时)无法解决的“项目杀手”:
- 上手时间(Ramp-up time): 你(作为团队里的老手)必须停下你手里所有的活儿,去教那九个新来的菜鸟。你得教他们项目结构,教他们那些(你写得)乱七八糟的变量名是什么意思,教他们怎么用那个该死的打印机7。
- 沟通开销(Communication overhead): 这才是真正的怪物。布鲁克斯用数学证明了这一点。两个人之间,只有一条沟通路径。三个人,有三条。四个人,有六条。十个人?四十五条!6。你还没开始写代码呢,你所有的时间都用来开会、回邮件、以及澄清你同事(再一次)误解了你的意思。
旅行者,本章讲述的,就是我们这颗星球上最聪明(也最古怪)的大脑们,为了驯服布鲁克斯的“沟通开销”这头巨兽,所发动的一系列战争、发明的(极其巧妙的)工具、以及犯下的(极其壮观的)错误。我们花了五十年时间,才终于从孤独的“独狼”,进化到了(基本能)协同演奏的“交响乐团”。
系好安全带。这个乐团的指挥,脾气可不怎么好。
第一幕:舞台搭建——“你好,世界(网络)”
在你的时代,协作意味着把你的5.25英寸软盘(如果幸运的话)小心翼翼地装在信封里,然后寄给你的朋友。我们之所以能聚在一个乐团里,首先是因为我们搭建了一个“舞台”。这个舞台,就叫“万维网”(World Wide Web)。
洞察:我们意识到,我们无法在同一个房间里高效协作(沟通开销太大),所以我们发明了“网络”,让我们可以在不同的房间里协作……这立即催生了新的沟通开销。
新架构:那个被用烂了的“餐厅比喻”
在你深入了解之前,你必须先理解这个新舞台的基本架构:“客户端-服务器”(Client-Server)模型1。这个模型如今无处不在,以至于人们发明了一个(极其贴切但被用烂了的)“餐厅比喻”来解释它8。
- 你(客户端): 你是走进餐厅的顾客9。你又累又饿,你想要服务。你的浏览器(Browser)——无论是Chrome还是Firefox——就是你的“客户端”形态。
- API(应用程序接口): 你拿到了一份菜单10。这份菜单就是 API。它严格定义了你能请求什么(牛排、沙拉、冰淇淋),以及你不能请求什么(菜单上没有的“红烧霸王龙”)。
- 服务器(Server): 你招手叫来了服务员11。服务员就是“服务器”。你对他发出了一个请求(Request),比如
GET /ice-cream(“给我拿个冰淇淋”)12。 - 后端/数据库(Backend/Database): 服务员(服务器)自己不生产冰淇淋。他转身走进你(顾客)看不见的后厨10。后厨就是“后端”和“数据库”。厨师们从冰柜里(数据库)拿出冰淇淋(数据)。
- 响应(Response): 服务员(服务器)端着冰淇淋回来了。他把冰淇淋(和你可能没要的账单)“响应”给你13。
在你的年代,你既是顾客(想吃冰淇淋),又是厨师(自己做),吃完还得自己刷盘子。现在,我们有了精细的“职责分离”。
CERN的“圣物”:那台“不许关机”的NeXT电脑
这个宏伟的“全球大餐厅”模型,其诞生地极其谦卑。它不在硅谷的某个车库,而在瑞士日内瓦的欧洲核子研究中心(CERN)14。
1989年,一位名叫蒂姆·伯纳斯-李(Tim Berners-Lee)的英国科学家,厌倦了(或者说受够了)科学家们之间那原始的“协作”方式14。当时,信息被锁在各个大学和机构的孤岛上。蒂姆想要的是一种“自动化的信息共享”方式15。他的解决方案是:将“超文本”(Hypertext)——一种允许文档互相“链接”的激进想法——与新兴的互联网技术结合起来14。
1990年12月,他和他(不那么出名)的同事罗伯特·卡里奥(Robert Cailliau)一起,让这个项目跑了起来14。这个项目被称为“WorldWideWeb”14。
核心趣闻:这个支撑着未来互联网的“第一台Web服务器”,并不是什么超级计算机。它是一台黑色的、漂亮的NeXT电脑14。蒂姆·伯纳斯-李很快意识到一个(后来被证明是)永恒的IT难题:这个世界上最危险的敌人,不是黑客,而是某个拿着吸尘器、好心的清洁工。
为了防止有人(为了省电或别的什么理由)拔掉插头,从而(在不经意间)终结掉未来的万维网,蒂姆在这台NeXT电脑上贴了一张手写的标签,上面用红色的墨水写着(你甚至能想象到那种潦草的笔迹)14:
“This machine is a server. DO NOT POWER IT DOWN!!”
(“这是一台服务器。不许关机!!”)
这可能是人类历史上最重要的一张便利贴。
史前巨兽Gopher的灭绝之战
旅行者,在你沉睡的早期,互联网本可以是另一副模样。在你醒来后看到的这个混乱、嘈杂、充满了猫咪视频和政治争吵的“万维网”赢得胜利之前,曾有过另一位“王者”。它的名字叫 Gopher(地鼠)16。
Gopher 协议在1991年(比WWW稍晚)诞生于明尼苏达大学16。在1993年左右,Gopher 才是互联网信息检索的主流。
- Gopher 的世界: Gopher 是一个美丽、有序、严格的世界。它没有“超链接”,它有“菜单”(menu-driven)16。你访问一个Gopher站点,会看到一个文本菜单(比如“1. 关于本校”,“2. 课程列表”,“3. 校园电话簿”)。你输入“2”,就会进入下一层菜单。它干净、可预测、逻辑严谨。你就像在浏览你本地的文件夹。
- WWW 的世界: 与之相比,蒂姆·伯纳斯-李的WWW是混乱、无政府主义、去中心化的。你从一个链接(比如“点击这里看我的狗”)跳到另一个链接(“我朋友的狗”)再跳到另一个链接(“中世纪酷刑设备列表”)。
Gopher 为何灭绝?
这是一个经典的“恐龙灭绝”故事。流行的说法是,1993年发布的 Mosaic 浏览器(我们下一幕的主角之一)带来了“图片”,Gopher是纯文本,所以Gopher死了17。
这是错的! 这是一个常见的误解。首先,Gopher 也可以链接到图片(它只是不在菜单里直接显示)。其次,正如研究指出的,在那个拨号上网的年代,带宽极其宝贵,很多早期的Web用户会主动关掉图片显示功能,以加快页面加载速度17。
Gopher 的真正死因,是“哲学”和“商业模式”的(极其愚蠢的)双重失败:
- 哲学失败(树 vs. 网): Gopher 是严格的层级结构(hierarchical)16。你必须从上到下地“钻”(Gopher,地鼠,意为“钻洞”)。而WWW是网状结构(web)17。事实证明,人类的知识和兴趣,更像一张混乱的网,而不是一本整洁的电话簿。
- 商业失败(致命一击): Gopher 太成功了。它的创造者,明尼苏达大学,在1993年(大约是它最巅峰的时期)做出了一个(堪称自杀式的)愚蠢决定:他们宣布,Gopher 将不再免费。他们要开始对Gopher服务器的商业使用收取授权费18。
- CERN的“核反击”: 几乎就在Gopher宣布收费的同时(1993年4月30日),CERN 做出了一个截然相反的、改变世界的决定。他们宣布,将万维网(WWW)的核心技术和软件放入“公共领域”(Public Domain)15。
完全免费。
一个收费,一个免费。一个封闭(层级),一个开放(网状)。
这场战争在1993年就已经结束了。Gopher 死了。万维网(那只来自CERN的、贴着“不许关机”标签的黑盒子)赢得了世界。
第二幕:第一次圣战——当浏览器开始互扔“Logo”
我们刚有了一个“开放、统一、免费”的平台(WWW),人类的本能立即就是……在它上面发动战争,把它搞得四分五裂。
欢迎来到“第一次浏览器大战”(The First Browser War)19。
“本网站最好用网景(Netscape)浏览”
战争的标志,是那些丑陋的、闪烁的、像素风的小徽章(badges)。在90年代中期,你(如果当时在上网的话)访问的几乎每一个“酷炫”的网站底部,都会有一个Logo,上面写着:“本网站最好用 Netscape Navigator 浏览”19。
不久之后,另一批网站则贴上了:“本网站最好用 Internet Explorer 浏览”19。
这是一种“数字巴尔干化”(Digital Balkanization)。这些徽章的潜台词是:“如果你用了对面的浏览器,本网站看起来就会像被哈士奇啃过一样,那是你活该。”
为什么?因为两家公司(Netscape 和微软)为了“创新”,都在疯狂地往Web里塞自己的私有标签。Netscape 发明了(臭名昭著的)<blink>(闪烁)标签20。微软则发明了 <marquee>(滚动)标签。它们彼此不兼容。标准?见鬼去吧!我们要的是市场份额。
战争爆发:死星 vs. 恐龙
这场战争的双方是:
- 挑战者(Netscape): Netscape Navigator 是第一个真正的商业浏览器巨头21。它由创造了 Mosaic(那个“杀死”Gopher的浏览器)的团队创建。在1996年,它占据了超过75%的市场份额21。
- 帝国(Microsoft): 微软,这家被视为“死星”(Death Star)的公司22,觉醒了。它(用极其流氓的方式)授权了旧的 Mosaic 代码20,将其改造成了 Internet Explorer (IE),然后使出了它的终极武器:将IE免费捆绑在 Windows 95 操作系统中。
战争爆发了。这场战争充斥着商业诉讼、垄断指控和(幸运的是)一个极其幼稚的传奇恶作剧。
(极其幼稚的)传奇恶作剧
这是整个浏览器大战中,最能体现硅谷精神的一个故事。
- 背景: 1997年10月,微软的 Internet Explorer 4.0 (IE4) 正式发布。这是一个重大版本,微软决定要搞个大新闻23。
- 地点: 微软没有在自己西雅图的总部开派对,而是(故意地)把发布会开在了硅谷——紧挨着 Netscape 总部的地方23。
- 第一步(微软的挑衅): 派对结束后,一群(喝高了的)微软工程师,发现了一个巨大的舞台道具——一个三米多高(three times four metres)23、代表IE的蓝色“e”字母标志。
- 一个(绝妙的)主意诞生了。
- 在午夜时分,这群微软工程师雇了一辆卡车,把这个巨大的“e”标志运到了 Netscape 位于山景城的总部,然后把它扔在了 Netscape 总部的草坪上23。他们拍了照,大笑着离开了,留下这个巨大的“e”作为给对手的“早安问候”23。
- 第二步(Netscape 的反击): 微软的工程师们不知道,Netscape 的工程师们(和那个时代的很多程序员一样)还在加班23。
- 他们从窗户目睹了这一切。他们被激怒了。但他们没有报警,也没有叫保安。他们决定“加倍下注”(double down)。
- 一群 Netscape 员工冲了出去。他们做的第一件事,是合力把那个巨大的蓝色“e”标志推倒在地23。
- 但这还不够。
- 他们跑回大楼,搬出了自己公司7英尺高的吉祥物。那是什么?一只名叫 Mozilla 的、绿色的、哥斯拉状的恐龙雕像23。
- 高潮: 他们把这只(咧着嘴笑的)Mozilla恐龙吉祥物,抬起来,重重地踩在了那个倒下的、残破的“e”标志上23。
- 点睛之笔(杀人诛心): 他们嫌这还不够,又在恐龙的脚下放了一个牌子,上面清晰地写着当时(1997年)的市场份额:“Netscape 72%, Microsoft 18%”23。
旅行者,你看到了吗?你(“恐龙”程序员1)的化身(Mozilla吉祥物23)从一开始就在战斗!它在字面意义上,把微软的IE踩在了脚下。
(后记: 尽管 Netscape 赢了这场“草坪之战”,但他们最终输掉了“浏览器大战”。微软的捆绑策略是致命的。Netscape 最终崩溃,但在它的灰烬中,诞生了 Mozilla 基金会——Firefox 浏览器的制造者。所以,在某种意义上,那只恐龙从未真正死去。)
第三幕:Git之前的至暗年代——欢迎来到“合并地狱”
浏览器战争是关于“用户”的战争。但在开发者的世界里,另一场更痛苦、更隐秘的战争正在进行——我们到底该如何协作写代码?
我们已经有了网络,有了服务器,我们不再需要邮寄软盘了。但布鲁克斯的“沟通开销”诅咒6并没有消失;它只是改变了形态。
协作的第一个噩梦:CVS与“文件锁定”
最早被(广泛)使用的“版本控制系统”(Version Control System, VCS)之一,叫做 CVS(Concurrent Versions System)。它的协作模式简单、粗暴,而且极度官僚化:“文件锁定”(File Locking)24。
它的工作流程是这样的:
- 你,一个积极的程序员,早上9点来到办公室。你准备大展拳脚,修复一个Bug。
- 这个Bug在 main.c 文件里。你试图在你的终端里“签出”(check out)这个文件并锁定它。
- 系统弹出一个冰冷的窗口:“错误:文件 ‘main.c’ 已被用户 ‘Harry’ 锁定”24。
- 你的血液开始凝固。你看了看表,9点05分。Harry 那个懒鬼通常10点半才来。
- 在接下来的90分钟里,你什么也做不了。你只能喝咖啡,在办公室里踱步。
- Harry 终于来了。你冲过去,拍着他的肩膀:“哥们,你能不能把 main.c 解锁?你到底在干嘛?”
- Harry 茫然地看着你:“哦,那个啊。我昨天下午锁的。我忘了。我现在就解。”
这就是布鲁克斯“沟通开销”6的物理实现! 你(一个程序员)的工作效率,现在不取决于你的技能,而取决于你同事(Harry)的记忆力和他上班打卡的时间。这是一种官僚主义,而不是编程。
SVN的“进步”:从“锁定”到“混乱”
在2000年,一个(看似)的救世主出现了:Subversion (SVN)。
SVN 的开发者们说:“文件锁定太愚蠢了!我们生活在一个现代世界!我们的系统支持‘复制-修改-合并’(Copy-Modify-Merge)模型。大家一起写吧!我们稍后再‘合并’(Merge)!”
这听起来很棒。太棒了。直到“稍后”真的来了。
为了理解 SVN 的(灾难性的)失败,我们必须引入一个在现代编程中至关重要的比喻:“分支”(Branching)就是“平行宇宙”(Parallel Universes)25。
- 平行宇宙(分支): 想象一下,你想给你的程序加一个实验性的新功能(比如,让所有按钮都变成粉红色)。
- 在“主宇宙”(Mainline/Trunk)里做这件事太危险了,会搞砸其他人的工作。
- 于是,你创造了一个“平行宇宙”(一个“分支”),你跳进这个宇宙里,尽情地折腾。你把所有按钮都改成了粉红色。
- 在同一个时间,你的同事 Harry(他又在工作了)在“主宇宙”里修复一个严重的Bug。
- 你们互不干扰。这,就是“分支”的魔力25。
SVN 的“分支”有多烂?
SVN 表面上支持“分支”,但它实现的方式是(我们不是在开玩笑):在服务器上完整地复制一份你的所有代码到另一个文件夹26。
如果你的项目有10GB大,创建一个分支,就意味着(在服务器上)cp -r /trunk /branches/my_pink_buttons。这个操作可能要花几分钟甚至几十分钟。它又慢、又笨重、又浪费空间。
后果是什么?
因为 SVN 的分支是如此“昂贵”和“痛苦”,所以——团队干脆不怎么用分支26。
在(可怜的)SVN 世界里,一个典型的10人团队26,所有开发者都在同一个“主宇宙”(trunk)里提交代码。这就像10个司机(包括那个刚拿到驾照的实习生)试图同时抢夺一个方向盘,驾驶一辆正在高速公路上行驶的卡车。
“合并地狱”(Merge Hell)的诞生
灾难在“合并”时降临了。
当你(在几周后)终于试图把你那个(很少用的)“平行宇宙”(分支)的代码,合并回“主宇宙”(trunk)时,SVN 就会崩溃。
SVN(以及CVS)是“白痴”:它不知道你为什么要这么改,它只知道“第10行不一样了”。当它试图把你(在分支里)对第10行的修改,和 Harry(在主干上)对第10行的修改合并时,它就彻底精神错乱了。它会尖叫着向你(人类)求助,抛出一堆“合并冲突”(Merge Conflicts)。
这导致了各种在软件开发中臭名昭著的“反模式”(anti-patterns)25:
- 合并妄想症(Merge Paranoia): 团队因为害怕合并,所以不惜一切代价避免分支。
- 大爆炸式合并(Big Bang Merge): 团队把所有的合并工作,都推迟到项目发布的最后阶段。然后,所有人在一个会议室里待上48小时(通常是在周末),试图同时合并他们过去三个月的所有工作。这个过程通常伴随着大量的咖啡、哭喊、诅咒和(至少一封)辞职信25。
- 永无止境的合并(Never Ending Merge): 合并活动似乎永远不会结束。你刚解决了一个冲突,又有三个新的冲突冒出来25。
讽刺:SVN 这个“协作工具”,因为设计得太烂,反而阻止了人们进行真正意义上的协作(即安全的分支)。它把布鲁克斯的“沟通开销”6,从“物理上的骚扰”(找Harry解锁)变成了“精神上的折磨”(解决合并地狱)。
第四幕:救世主登场(一个脾气暴躁的芬兰人)
洞察:历史上,伟大的工具往往不是诞生于“高瞻远瞩的愿景”,而是诞生于“纯粹的、忍无可忍的愤怒”。
进入21世纪初,地球上最复杂、最重要的协作项目之一——Linux 内核——也陷入了“合并地狱”27。
它的维护者,一个名叫莱纳斯·托瓦兹(Linus Torvalds)的芬兰人(Linux的创造者),也受够了。在Linux开发的头十年里,他们(悲哀地)用着 CVS,以及一种更原始的系统:“tarballs压缩包 + 电子邮件补丁”28。这简直是石器时代。
“我受够了”:BitKeeper的背叛与Git的诞生
大约在2002年,Linus 做出了一个(在当时)极具争议的决定。他受够了CVS,决定采用一个(当时)技术上非常牛的商业、闭源工具,叫做 BitKeeper29。
BitKeeper 有一个(在当时)革命性的特性:它是“分布式的”(Distributed)29。不像CVS/SVN那样只有一个“中央服务器”,BitKeeper 允许每个开发者在本地都有一份完整的项目历史记录。
BitKeeper 的公司(BitMover)和 Linus 达成了一个“君子协定”:Linux 社区可以免费使用BitKeeper,条件是他们(Linux的开发者们)不能试图“逆向工程”它29。
这个“魔鬼的契约”运行得很好。Linux 的开发效率飙升了。
直到2005年。
一位(充满好奇心的)Linux 社区开发者 Andrew Tridgell,决定(仅仅是出于好玩)试着“逆向工程” BitKeeper 的协议。
BitMover 公司发现了。他们震怒。他们感觉遭到了背叛。
2005年4月,BitMover 公司立即终止了对 Linux 社区的免费许可29。
一夜之间,这个全球最大的开源项目,失去了他们赖以生存的核心工具。Linus 被逼到了墙角。数千名开发者眼睁睁地看着他:我们现在怎么办?
Linus 的反应是什么?
他没有抱怨。他没有谈判。他(据说)消失了。
他把自己关了两个星期(另有说法是10天30)。在两周内27,他用C语言自己从头写了一个 BitKeeper 的替代品。
他将其命名为 Git27。
Linus的“封神吐槽大会”(2007年Google Talk)
为了让你(我们的“恐龙”)明白 Git 为何如此设计——为什么它(在早期)如此晦涩难懂,为什么它如此强调“分布式”——你必须聆听它的创造者在2007年去 Google 做的那场著名的“福音”演讲31。
这是编程界的“马丁·路德·金”演讲,如果马丁·路德·金满口脏话并且极度鄙视他的听众的话。
在这场演讲中,Linus 彻底阐明了 Git 的存在哲学,主要是通过(无情地)“吐槽”其他一切:
论 CVS(他梦魇的开端)28:
(在内核维护的头十年里),我们简直就是使用 tarballs 和补丁,那是一种比 CVS 优秀得多的源代码控制管理系统。……我对 CVS 恨之入骨(hate it with a passion)。
论 Subversion(SVN)(高能预警)31:
所以当我(因为CVS)这么说的时候,我必须也要说,如果现场有 SVN(Subversion)的用户,你们(现在)可能想要离开。……因为我对 CVS 的仇恨,意味着我视 Subversion 为有史以来最毫无意义的项目(the most pointless project ever started)。
论 SVN 的核心哲学(“把CVS做对”)31:
(SVN的)口号在一段时间内是‘CVS done right’(把CVS做对)。(脏话)……如果你以那样的口号开始,你哪儿也去不了!你不可能把CVS做对!
论 SVN 的开发者31:
(SVN的)开发者们……是彻底的白痴(complete morons)。
他们(在合并问题上)的计划也很烂。……(脏话)……这些人到底有多蠢,简直令人难以置信。
Git 不是被“设计”出来的。它是被 Linus 对 CVS 和 SVN 的愤怒“锻造”出来的。Git 的每一个设计决策,几乎都是对“CVS/SVN会怎么做?”然后反着来的产物31。
“Git”到底是什么意思?
Linus 的哲学——去中心化、速度快、别挡我的路、相信开发者是(基本)聪明的——完美地体现在了这个工具的名字上。
当被问及“Git”这个名字是什么意思时,Linus Torvalds 证实了以下(半开玩笑的)解释:
- 官方答案32:“我(Linus)是个自大的混蛋(egotistical bastard),我用我自己的名字命名所有项目。第一个是 Linux,第二个是 Git。”
(备注:在英式俚语中,“Git” 是一个贬义词,意思接近于“饭桶、蠢货、混蛋”)32。 - Git 的 README 文件33:“GIT - 那个愚蠢的内容追踪器(the stupid content tracker)。”
- Git 的非官方含义(取决于你的心情)34:
- (当它能用时): "Global Information Tracker"(全球信息追踪器)。
- (当它坏了时): "Goddamn Idiotic Truckload of Sh*t"(一卡车该死的白痴狗屎)。
这就是你的新工具,旅行者。它是由一个(愤怒的)天才创造的,他以“混蛋”来命名它,并且(在某种程度上)希望你(如果用不好它的话)也这么称呼它。
第五幕:给“恐龙”的Git翻译手册(用游戏和科幻片)
好了,旅行者,深呼吸。
Git(在早期)之所以名声狼藉35,不是因为它难用,而是因为它太强大了。它给了你“时空旅行”的权力36,而你以前只会开“拖拉机”(CVS)。
在你的年代,你保存代码的方式,可能是在你的硬盘上(如果你有的话)复制粘贴文件:
MYPROG_V1.BASMYPROG_V2_FINAL.BASMYPROG_V2_FINAL_REALLY.BASMYPROG_V2_FINAL_REALLY_WORKS_I_SWEAR.BAS
别担心。Git 只是把这个(极其丢脸的)过程自动化和结构化了。
关键概念:git commit 不是别的,就是你的“游戏存档点”
这是你需要理解的第一个,也是最重要的一个比喻。Git 的核心,是一个“游戏存档系统”37。
想象一下,你正在玩一个像《辐射》(Fallout)那样的复杂游戏37。
git add .(添加): 你刚刚打败了一个小Boss,捡到了一些装备。你按下了 “F5” 键——“快速存档(Quicksave)”37。这在 Git 里叫做“暂存”(Staging)。你把你的更改“加入暂存区”,告诉 Git:“嘿,这些是我刚做的更改,准备好要存了。” 但你还没正式存档37。git commit -m "..."(提交): 你刚刚完成了一个完整的大型任务。你不想(万一死了)再重打一遍。你打开了游戏菜单,选择“保存游戏”,并在存档栏里输入了一个名字。- 这就是
git commit37。它是一个“正式的、有名字的”存档点。 -m后面跟的那个字符串38,就是你给这个“存档点”起的名字。比如:git commit -m "搞定了那个该死的bug"git commit -m "试图修复bug,但可能又搞坏了别的"git commit -m "我发誓这是最后一次修复"
git log(日志): 这就是你的“加载存档”菜单37。它会按时间倒序列出你所有的“存档点”(Commits),让你清楚地看到你(和你的团队)一路是怎么走过来的。
关键特性:branch 是《瑞克和莫蒂》里的“平行宇宙”
还记得 SVN 那个又慢又笨重、靠“复制文件夹”实现的“平行宇宙”25吗?
Git 的分支(Branching)是它的“杀手级特性”(killer feature)39。
Linus 的天才之举在于,Git 的“分支”不是复制文件夹。它只是(在内部)创建了一个微小的“指针”(pointer),指向你当前的“存档点”(Commit)。因此,在 Git 里创建一个全新的“平行宇宙”(分支)——“几乎是瞬时的”(nearly instantaneous)39。
这对你意味着什么?
这意味着 Git 鼓励你39“频繁地分支和合并”。
- 你想试试那个愚蠢的粉红色按钮的想法吗?
git branch crazy_pink_idea(1毫秒内,你创造了一个平行宇宙)git checkout crazy_pink_idea(1毫秒内,你跳进了那个宇宙)
- 你开始在这个宇宙里疯狂“存档”(commit)。
- (一个小时后)你发现粉红色按钮丑爆了。
- 你想摧毁这个宇宙,假装一切没发生过。
git checkout main(1毫秒内,你跳回了“主宇宙”)git branch -D crazy_pink_idea(1毫秒内,你摧毁了那个(丑陋的)平行宇宙)
“主宇宙”(现在通常叫 main 或 master 分支)毫发无损。Harry 甚至都不知道你干了什么。这,就是现代协作的自由。
关键操作:rebase 是《回到未来》里的“时空旅行”
这是 Git 最“黑科技”、最强大,也是最危险的部分。它叫 rebase(变基)。
想象一下,你和 Harry 都在“主宇宙”(main)的 C3(第三个存档点)分开了。
- Harry 留在 main,创造了新的存档点 C4 和 C5。
- 你跳进了你的“粉红按钮”宇宙,创造了 P1 和 P2。
历史现在是这样的:
P1 -- P2 (你的分支: pink-idea)
/
C1 -- C2 -- C3 -- C4 -- C5 (主宇宙: main)
现在,你想把你的 P1 和 P2 合并回 main。你有两个选择:
git merge(合并): 这是“诚实”的(但丑陋的)方法。它会创造一个小小的“合并存档点” M1,把两个宇宙拉到一起。
P1 -- P2
/ \
C1 -- C2 -- C3 -- C4 -- C5 -- M1 (main)
这很乱。历史看起来像“意大利面条”。
git rebase(变基): 这是“时空旅行”(Time Travel)36。git rebase main命令会做一件神奇的事:- 它会抓起你的“平行宇宙”里的所有存档点(P1, P2)。
- 坐上“时空机器”(DeLorean),回到过去,回到你们分叉的 C3 点。
- 然后,它(带着你的 P1 和 P2)飞到“现在”——也就是 main 分支的最新点 C5。
- 最后,它假装你是在 C5 之后才开始做的 P1 和 P2。
“变基”后的历史是这样的:
C1 -- C2 -- C3 -- C4 -- C5 -- P1' -- P2' (main)
为什么要这么做? 为了“保持历史记录的整洁”(to keep your commit history clean)40。它创造了一个(虚假的)但极其干净的、线性的历史。它假装“平行宇宙”从未存在过。
时空旅行的警告(极其重要!): 就像所有科幻片里说的那样41……
永远,永远不要篡改你已经和别人分享过的历史!
(Don't rebase a branch that has been distributed)
如果你(无知地)rebase 了一个 Harry 也在使用的分支,你就(字面意义上)篡改了他的“过去”。他的“平行宇宙”会和你的发生剧烈冲突,导致……好吧,你还是会(以一种更复杂的方式)掉进“合并地狱”。
关键(背锅)命令:git blame
Git 的幽默感是无穷的。它提供了一个(名字起得极好的)命令,叫 git blame(指责)42。
- 它的作用: 你对一个(充满了Bug的)文件运行
git blame,它会逐行显示,告诉你文件的每一行代码,最后是由谁、在哪个“存档点”(Commit)修改的42。 - 它在管理学上的笑话: “太好了!快,
git blame一下 main.c,看看这个Bug是谁写的,然后我们好开除他!”43 - 它在现实中的笑话: “当你(为了甩锅)兴冲冲地运行
git blame时,你发现屏幕上每一行前面显示的名字,都是你自己的名字。”42 - 它真正的用途44: 它不是“指责”工具,而是“寻人”工具。当你(一个新来的菜鸟)看着一行天书般的代码,想知道“这行该死的代码到底是干嘛的?为什么我们要用这种愚蠢的方式写它?”时……
git blame能帮你找到那个“唯一可能知道答案的同事”(也就是最后修改它的那个人),好让你(客气地)去问他:“嘿,Harry,能给我讲讲你5年前写的这行代码是啥意思吗?”44。
结语:编码的“社交化”——欢迎来到俱乐部(GitHub)
旅行者,现在你手握“时空机器”(Git)的钥匙了。但还有一个(至关重要的)概念。你可能听过一个词:GitHub。
重要区分:Git 不是 GitHub1。
- Git 是你本地的“时空机器”(DeLorean)。它是那个(由Linus创造的)命令行工具。它是引擎。
- GitHub(以及 GitLab, Bitbucket 等)是那个“时空旅行者俱乐部”。它是一个托管 Git 仓库的网站。它是在 Git 引擎之上建立的“社交层”(Social Layer)1。
“拉取请求”(Pull Request)的诞生:真正的革命
如果说 Git 是“协作”的引擎,那么“拉取请求”(Pull Request, 或简称 PR)就是那个“点火开关”。这才是(真正)彻底改变了“协作”的范式转移45。
洞察(范式转移):
- 旧模式(CVS/SVN): 你(在本地)写完了代码。你(祈祷着)运行
svn commit。你的代码被直接推送(Push)到了那个“中央服务器”。如果你的代码(不小心)搞砸了什么,你(可能)搞砸了所有人的代码。 - 新模式(GitHub PR): 你从不直接提交到“主宇宙”(main)。
“拉取请求”这个词到底在说什么?
你不是在说:“这是我的代码,拿去!”
你是在(礼貌地)说:“各位,我提议把我的这些‘存档点’(我的分支)拉取(Pull)并合并回‘主宇宙’(main),你们觉得怎么样?”
“代码审查”(Code Review)文化的诞生
这个“拉取请求”(PR),是“对话”的开始,而不是结束45。
一旦 PR 被创建,你的同事们(比如 Harry)就会收到通知。他们现在可以在你的 PR 页面上,逐行评论你的代码47。
- Harry(在第42行)评论:“你这里忘了检查空指针,这会导致崩溃。”
- Sally(在第99行)评论:“这个变量名 x 是什么意思?改成 user_count 吧。”
- 你(在本地)修改代码,提交新的“存档点”,然后再次推送到同一个分支。
- GitHub 会自动更新 PR。
- 直到最后,Harry 和 Sally 都(满意地)点击了“批准”(Approve)按钮。
- 只有这时,你(或者一个维护者)才能点击“合并”(Merge)按钮。
这解决了什么?
这,就是对“布鲁克斯诅咒”6的(目前为止)最终解决方案。
它把“沟通开销”6从“同步”的、高成本的打扰(比如拍 Harry 的肩膀,或者开“大爆炸式合并”会议),变成了“异步”的、有记录的、低成本的对话48。
这场对话(Code Review)创造了两件(在SVN时代)不可能的、宝贵的东西:
- “集体所有权”(Collective Ownership): 经过(至少)两个人审查和批准的代码,不再是“你”的代码。它是“团队”的代码49。Harry 和 Sally 现在和你一样,要对这段代码的质量负责。
- “历史背景”(Historical Context): 几年后,当一个新来的菜鸟想知道“为什么我们要用这种愚蠢的方式写这个函数?”时,他可以(通过
git blame)找到那个(已合并的)PR。他可以看到你们当年争论了三天的所有上下文、所有的妥协、以及(最重要的)为什么你们最终决定这么做49。
从“独奏”到“交响”
旅行者,你现在明白了。你(“恐龙”程序员)的世界,是“独奏”(Solo)2。那是一种纯粹的、孤独的创造。
现代编程的世界,是一场“交响乐”(Symphony)1。它充满了妥协、沟通、审查和政治。它(有时)很烦人。但它能造出(你一个人在地下室里)永远无法造出的东西——比如 Linux 内核,比如驱动AI的庞大模型。
讽刺的是,那个发明了 Git(“交响乐团”的规则手册)的“独狼”Linus Torvalds,他本人并不怎么用 GitHub50。
Linux 内核至今(在很大程度上)仍在依靠“电子邮件补丁”(email patches)进行协作30。Linus 仍然像一个(脾气暴躁的)“独奏者”那样,手动地(用Git)拉取和审查他信任的副官们发来的邮件。
也许这是最硬核的“独奏”精神的遗留。
但对于我们(这些凡人)来说,GitHub 和“拉取请求”就是我们演奏“交响乐”的方式。
欢迎回来,指挥家。你的乐团在等你。别跑调。
-
Network Business Models: Interconnected Industries | by Metacom Platforms - Medium, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
(PDF) Gender and Music Composition: A Study of Music, and the Gendering of Meanings - ResearchGate, 访问时间为 十月 27, 2025 ↩︎
-
The Mythical Man-Month - Wikipedia, 访问时间为 十月 27, 2025 ↩︎
-
Brooks's law - Wikipedia, 访问时间为 十月 27, 2025 ↩︎
-
The Mythical Man-Month - Best practice for building development ..., 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Brooks's Law: Understanding Its Implications in Software Development - DevIQ, 访问时间为 十月 27, 2025 ↩︎
-
Client Server Analogy - GitHub Gist, 访问时间为 十月 27, 2025 ↩︎
-
Scaling explained to my mum: a restaurant analogy | by Antoine Laurent | Unibuddy, 访问时间为 十月 27, 2025 ↩︎
-
API's : explain like I'm 5 : r/learnprogramming - Reddit, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
eli5 api ( application programming interface) : r/explainlikeimfive - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
HTTP Methods Explained with Real-Life Analogies - YouTube, 访问时间为 十月 27, 2025 ↩︎
-
HTTP Response Explained with a Restaurant Analogy - YouTube, 访问时间为 十月 27, 2025 ↩︎
-
A short history of the Web | CERN, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Web History | Web at 30: Celebrating the 30th Anniversary of the Invention of the Web, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Gopher (protocol) - Wikipedia, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎
-
Before the web there was Gopher - JMU Scholarly Commons, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
Where Have all the Gophers Gone? Why the Web beat Gopher in the Battle for Protocol Mind Share, 访问时间为 十月 27, 2025 ↩︎
-
Browser wars - Wikipedia, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
The History of Web Browsers - Firefox, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
The History Of Netscape Navigator: The Once Popular Web Browser Everyone Forgot About, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
What is internet? Relive the Microsoft v. Netscape browser war in new series 'Valley of the Boom' - GeekWire, 访问时间为 十月 27, 2025 ↩︎
-
Tales from the Browser wars: Mozilla stomps Internet Explorer | by ..., 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
svn lock - Version Control with Subversion, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Software Branching and Parallel Universes - Coding Horror, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
What are the reasons why don't many project owners migrate from SVN to Git/Mercurial?, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
A Git Origin Story | Linux Journal, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
Linus Torvalds talk about git at Google in 2007 - Sandeep Ramgolam, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
The kernel and BitKeeper part ways - LWN.net, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎
-
Linus Torvalds warns Linux devs: Stop cluttering patches with automated, useless links, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Linus google tech talk transcript · GitHub, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
TIL the creator of version control software Git, Linus Torvalds, once quipped about the name "git" (which is British English slang for an unpleasant or silly person): "I'm an egotistical bastard, and I name all my projects after myself. First 'Linux', now 'git'." : r - Reddit, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Short(-ish) article on how Linus Torvalds came up with git : r/linux - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
Linus doesn't beat around the bush : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
[RANT] Git Sucks! : r/programminghorror - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
theAverageGitRebaseExperience : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Understanding Git Through an Extended Video Game Metaphor | by ..., 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
What's your worst/most funny commit message? : r/github - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
Branches in a Nutshell - Git, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
Git Rebase as Time Travel - The WegoWise Development Blog, 访问时间为 十月 27, 2025 ↩︎
-
Git Time Travel Magic — Amend / Rebase | by Gant Laborde | Red Shift, 访问时间为 十月 27, 2025 ↩︎
-
Git blame[yourself] - ProgrammerHumor.io, 访问时间为 十月 27, 2025 ↩︎ ↩︎ ↩︎
-
Git blame !! : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025 ↩︎
-
Blaming Git Blame - DEV Community, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Proposing changes to your work with pull requests - GitHub Docs, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
An Example of a Github Collaborative Workflow for Team Science - Earth Data Science, 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Reviewing changes in pull requests - GitHub Docs, 访问时间为 十月 27, 2025 ↩︎
-
How to improve code with code reviews - GitHub, 访问时间为 十月 27, 2025 ↩︎
-
Code Review: Culture, Flow, and Practices That Drive Team ..., 访问时间为 十月 27, 2025 ↩︎ ↩︎
-
Git turns 20: A Q&A with Linus Torvalds - The GitHub Blog, 访问时间为 十月 27, 2025 ↩︎