Git 20 年,依然怪异,依然精彩
二十年前,Git 诞生了。这个不太可能的 “信息管理器 “是如何占领世界的呢?
#git
20 年前的今天,莱纳斯-托瓦尔兹(Linus Torvalds)为 Git 这个来自地狱的信息管理工具提交了第一个版本。
在过去的 20 年里,Git 从一个小型、简单的个人项目发展成为有史以来最重要的版本控制系统。
我个人在这个特殊的软件 “过山车 ”上经历了一段地狱般的旅程。
在 Git 首次提交几个月后,我就开始用它做一些你可能想象不到的事情。之后,我与他人共同创建了 GitHub,写了一本关于 Git 的书,建立了项目的官方网站,举办了年度开发者大会,等等–这个小项目改变了软件开发的世界,但就我个人而言,它也极大地改变了我的人生轨迹。
我想,在 Git 项目迈入第三个十年之际,回忆一下 Git 最早的日子,并解释一下为什么我觉得这个项目如此令人着迷,会是一件有趣的事情。
https://youtu.be/B5VQ0L3uL6M (请欣赏我和基里尔一边喝着红酒,一边构建 Git 的第一个提交,从一开始就探索它能做什么。)
补丁和 Tarballs
在介绍 Git 的历史和我与它的关系之前,我想先从 Git 存在的原因和它的起源说起。
Git 起源于 Linux 内核开发社区对版本控制和协作的不满。
内核社区一直使用邮件列表进行协作。这其实是一种相当吸引人的协作方式–它具有大规模可扩展性、高度分布式、本地优先、能对补丁进行细粒度讨论、可加密等特点。
邮件列表协作流程的要点如下
- 发布一个项目已知状态的压缩包(类似 zip 文件)
- 人们下载该压缩包并在本地扩展
- 用他们想修改的功能或修复程序修改该压缩包
- 运行 GNU diff,创建一个补丁,维护者可以将该补丁应用到初始已知状态,以添加该功能
- 将该补丁或一系列补丁通过电子邮件发送到邮件列表
- 邮件列表讨论修改
- 维护者将该补丁应用到下一个压缩包发布或要求修改
- 重复
我很想写一篇关于邮件列表协作如何工作以及它的各个方面如何酷的博客文章、 但这是后话了。
然而,在这个世界上,当时的版本控制系统根本无济于事–它们在功能上似乎是一种倒退。它们有笨拙的访问控制机制,不是分布式的,速度也慢得惊人。
社区主要使用补丁和压缩包,而现有的版本控制系统还不够完善。
仔细想想,补丁和压缩包工作流程算是第一个分布式版本控制系统–每个人都有一份本地拷贝,修改可以在本地进行,谁有权限 “合并 ”谁就能把新的压缩包推送到服务器上。
然而,这个过程仍然有些繁琐–管理补丁、记住应用了哪些补丁、谁贡献了这些补丁、保持多个系列的同步、处理冲突或重新调整变更。
Bitkeeper 工具是专为内核用例开发的,试图建立一个适用于这一工作流程的版本控制系统,Linus 也很喜欢它,但他们希望使用的许可方式与该工具所面向的社区并不一致。
https://youtu.be/MPFgOnACULU 如果您想了解更多关于 Bitkeeper 的信息,请查看本期 “Bits and Booze “节目,我们将在节目中为您介绍 Bitkeeper 的设置和使用方法。
要知道,这正是 Git 诞生的原因。它并不是一个真正的版本控制系统,从根本上说,它是一种更好的补丁和压缩包处理方式–对一组文件进行快照,并显示可以讨论的不同之处。
这也是其数据结构的主要设计方式(由文件树组成的链接列表,内容可寻址的 blob 存储),从第一次提交到现在,这种结构从根本上没有改变。
第一次提交
既然谈到了这个话题,那么第一次提交是什么样的呢?Git 在诞生之初能做什么?
好吧,它是一个愚蠢的内容跟踪器。就像莱纳斯自己从第一天开始就说的那样
这是一个愚蠢(但速度极快)的目录内容管理器。它做不了什么,但它能高效地跟踪目录内容。
第一个提交是七个简单的独立工具的集合。它们并不是 Git commit 这样的工具,而是写入树(write-tree
)和提交树(commit-tree
)这样非常低级的数据库工具(项目开始几周后,所有工具都开始以 git-
作为前缀)。
它们中的一些演变成了至今仍然存在的管道命令,比如 git cat-file
和 git write-tree
,另一些则有本质的不同(比如,git read-tree
是现在的 Git 管道命令,但最初的 read-tree
更像是现在的 git ls-files
),不过,在低层次上,这些概念都仍然存在。
从本质上说,Git 可以在第一次提交时
- 用
update-cache
建立一个内容缓存(本质上就是一个 tar 包),然后用write-tree
把它作为一个对象写入数据库,从而建立一个 “快照”。 - 用
commit-tree
写一个 “变更集”(提交),注释新压缩包引入的变更以及它所基于的父级压缩包,以建立 “压缩包 ”的历史。 - 使用
cat-file
(从数据库中调出一个对象)、read-tree
(列出缓存的样子)和show-diff
(显示缓存与工作目录的差异)可以读出这些数据库结构。
从一开始,莱纳斯就提到,他真的只想构建这个管道,并将其作为一些用户界面(“瓷器”)的后端脚本。
在我个人看来,“Git ”就是如此,只是表面下的管道而已。举个例子,像 arch 这样基于 “补丁和 tar-ball”(我觉得 darcs 在这方面也差不多)的东西,就可以把 Git 当成_hell_更好的 “tar-ball 历史”。- Linus
他的本意是建立一个高效的 tar 包历史数据库工具集,而不是真正的版本控制系统。他以为会有其他人来写这一层。
稍后再详述。但首先……
斯科特与 Git 的相遇
我第一次接触 Git 是在我的朋友兼同事尼克-亨格维尔德(Nick Hengeveld)的帮助下,当时我们都在一家名为 Reactrix 的初创公司工作。
有趣的是,我们使用 Git 的方式更像 Linus 所想的那样–把它当作一个分布式内容跟踪器,而不是现在大家所认为的版本控制系统。
我们主要为一家广告公司工作,该公司管理着大量的数字标牌显示屏,这些显示屏上都有相当重要的资产。我们的数百个显示屏中,每个都有独特的广告组合需要运行,大多数显示屏都在缓慢的蜂窝数据上行链路上,而且广告变化很大。因此,我们需要一种高效的方式来说明 “对于机器 A,我们需要广告 1、2 和 3 (v1);对于机器 B,我们需要广告 2、3 (v2) 和 4”,并在现有广告有新版本时对其进行增量更新。
我们使用 Git–不是为了跟踪源代码的变化,而是作为一种内容分发机制。我们会使用脚本查看即将到来的排程,写出每台机器都需要的广告树,将该树提交到机器的分支中,然后让每台机器每晚获取并硬签出。
这种方法有很多有趣的优点。
- 如果一个广告更新了,我们只传输更改过的文件,而且更改是针对机器上可能已经存在的对象进行 delta 压缩的。
- 所有共享资产都有一个单一的 blob,可以在多个上下文中签出–Git 的内容可寻址文件系统在这方面非常出色。
- 我们可以拥有数百种资产的数千种组合,而不会在任何地方重复存储任何内容,也不会在网络上多次传输相同的内容。
尼克对早期的 Git 项目贡献颇多(为 http-fetch 添加 SSL 支持、添加可恢复和并行的 HTTP 传输、首个基于 HTTP 的推送解决方案等)。他的第一个补丁是在 9 月份提交的,距离 Linus 首次提交仅过去了 6 个月。
他向我介绍 Git,我努力掌握它,最终灵光一闪,觉得它很酷,这促使我写下关于它的文章,并努力让人们更容易学习它。
这促使我编译了《Git Community Book》、《Git Internals Peepcode PDF》,建立了 git-scm.com 网站,并撰写了《Pro Git》一书–所有这一切最终将我引向了 GitHub。

Git Lore
那么,这个愚蠢的内容跟踪器是如何成为世界上使用最广泛的SCM的呢?
在上一篇博文中,我已经阐述了很多我认为 Git 和 GitHub “胜出 ”的原因,但我认为还是值得快速浏览一下 Git 本身为什么会变成今天这个样子。或许还可以顺便讲讲你所熟悉和喜爱的东西的起源轶事。
你可能已经从 Git 命令偶尔的不友好、晦涩或不一致中推断出,这并不是一个从第一天起就有人坐下来从可用性角度精心设计的系统。
在最初的几个月里,Git 的所有命令都非常低级–即使你知道现有的管道命令,你可能也认不出 2005 年 6 月时存在的任何一个命令(rev-tree
、mkdelta
、tar-tree
?)
从一开始,我们就相当清楚地知道,Git 将只是一个非常低级的数据库/文件系统类型的工具集,而(可能有几个)其他工具将使用 Git 作为它们的基础架构。
为了避免混淆,将建立在 Git 基础上的 SCM 与 Git 本身区分开来也许是值得的。以后还可能开发出基于 Git 的其他 SCM,这些 SCM 也可以有自己的巧妙名称。- 史蒂文-科尔
那么,如果莱纳斯和早期的 Git 团队最初并没有把 Git 想象成一个真正的版本控制工具,而只是想构建一个管道,那么我们今天所熟知的 “瓷器 ”命令究竟从何而来呢?
答案是,它们是在数年时间里慢慢形成的,主要是为了挠痒痒而演化出来的 shell 脚本。
早期,Linus 的后台工具有许多用户界面,这些脚本对用户更加友好。最早也是前几年最流行的是 Git-pasky
,它很快就变成了 Petr Baudis 的 “Cogito”。这些脚本的第一个版本发布仅比 Git 晚了几天。
从早期的发布公告中,你可以感受到后来开始成为 Git 的工具。
几个月后,试图保持瓷器和水管之间界限的想法开始瓦解,因为 Git 中的工具性开始与 porcelan 脚本中的工具性竞争。
这种趋势是从 “git diff”、“git commit ”和朋友们为了裸奔的可用性而加入的时候开始的。这些基本命令是必须的,我并不反对在 “核心 GIT ”套件中加入这些命令,但同时我认为核心命令不应该与 Porcelans 竞争,并认为应该在某个地方划出一条界线。- Junio
在接下来的一两年里,越来越多的脚本不断进入 Git 核心代码,直到人们最终发现,与其在工具中试图维持这种水管和瓷器的区别,还不如把时间花在与 Git 一起发布的工具上。
2007 年,Cogito 最终被 “挂牌出售”,而将其他瓷器作为 Git 主要使用方式的想法也或多或少地被放弃了。
回顾 20 年前的这些提交和邮件,我们可以看到一些我们每天都在使用的臭名昭著的工具的诞生过程,真是令人着迷。
第一个 Git log
第一个版本的 git log
是一个封装脚本,它调用 git-rev-list --pretty
,通过一个 pager 进行管道化,并被硬编码为从 HEAD
开始。下面是最初的 “git log ”程序的全部内容:
#!/bin/sh
git-rev-list --pretty HEAD | LESS=-S ${PAGER:-less}
如果你还没听说过
rev-list
,那它只是一个简单的行走器,只会打印出 shas。现在它依然存在–你依然可以在你的项目中运行Git rev-list
。
事实上,现在的很多命令一开始都是这样的–几行长的 shell 或 Perl 脚本,运行一些核心的管道命令。最后,为了便于移植,几乎所有命令都用 C 语言重写成了内置程序,但这些脚本语言中还是有很多第一版命令的。
$ git-log-script
commit d9f3be7e2e4c9b402bbe6ee6e2b39b2ee89132cf
Author: Junio C Hamano <gitster@pobox.com>
Date: Fri Apr 5 12:34:56 2024 -0700
Fix bug
第一个 “git log “看起来很熟悉。
第一个 Git rebase
这样的 “第一 ”有很多,但我只想再做一个,因为我觉得它太有趣了。臭名昭著的 “rebase ”命令诞生于 2005 年 6 月朱尼奥和莱纳斯关于工作流程的一次对话。
朱尼奥告诉莱纳斯他的工作流程:
仅供参考,以下是我一直在做的事情:
(1) 从 Linus HEAD 开始。
(2) 重复开发-提交循环。
(3) 运行 “Git format-patch”(不在 Linus 树中)生成补丁。
(4) 发送补丁,看哪个能坚持下来。
(5) 从莱纳斯中提取。
(6) 扔掉我的 HEAD,把 Linus 的 HEAD 变成我的 HEAD,同时保留我从他那里分叉后所做的修改。为此,我使用了 “jit-rewind”。
(7) 检查被 Linus 拒绝的补丁,并应用我认为还不错的补丁,每个补丁提交一次。为此,我使用 “jit-patch ”和 “jit-commit -m”。
(8) 回到第 2 步。
Linus 指出,开发者真正想要的合并类型是 “重新构建 ”工作:
这有点类似于当前的 Git-merge-script,但它不是基于公共父提交进行合并,而是试图将公共父提交之后的所有本地提交都重新基于新的远程头提交。对于希望将自己的工作更新到远程头的个人开发者来说,这样做往往更有意义。
于是,Junio 用一个简单的脚本,使用名为 Git cherry
的新命令来 “重置 ”一系列提交。
据我所知,这是版本控制中第一次使用 “rebase ”一词。见证历史的诞生真是件有趣的事。
“章鱼 Octocat”是如何成为 Git 的一部分的?
我曾多次被问及 GitHub 是如何想到 “章鱼Octocat ”的,答案也就在这些早期档案中。

我在 Git 邮件列表中看到的 “章鱼 ”一词的第一次用法是 Junio 告诉 Linus 他的补丁是按顺序应用的,而不是 “章鱼”。
这指的是创建一个包含多个父提交的合并提交,也就是不同补丁的另一种合并方式。最终,“章鱼合并 ”成为 Git 的有效合并策略之一。(有趣的是,Git 还曾经把 “愚蠢 “作为一种合并策略)
在 GitHub 非常非常早期的某个阶段,汤姆一直在寻找任何可以拟人化地用作 Git 图腾的东西,而 “章鱼 “是 Git 词典中唯一一个似乎符合要求的词汇。汤姆搜索了以 “章鱼 ”为主题的剪贴画,这张 Simon Oxley 的图片是最可爱的。于是,“章鱼 ”诞生了。
Git的未来
20 年零一天之后,人们可能会问,这个不可能的英雄未来会怎样?
有趣的是,在某些方面,我仍在以最初的方式使用 Git。GitButler 使用 Git 不仅可以做普通的提交来跟踪代码修改,还可以使用 Git 数据库来跟踪项目的历史。归根结底,GitButler 仍然是一款优秀的傻瓜式内容跟踪器,这也是 Linus 最初的初衷。
所以,Git 生日快乐。你依然怪异。你依然精彩。感谢所有的鱼儿
本文文字及图片出自 20 years of Git. Still weird, still wonderful.
你也许感兴趣的:
- 理解 git blame:一篇简介
- 【外评】为什么 Facebook 不使用 Git
- 【外评】Git 的故事:这次没那么有趣
- 【程序员搞笑图片】最刺激的话
- BitKeeper、Linux 和许可纠纷:Linus 如何在 14 天内写出 Git
- 【程序员搞笑图片】Git 音乐播放清单
- 您应该使用的现代 Git 命令和功能
- 在版本控制方面,我们能做得比 Git 更好吗?
- Git 2.40 发布,包括 git jump 工具的更新、cat-file 工具的增强以及提高 Windows 上响应速度
- 告别SVN,Git成“独苗”:GitHub 在 13 年后宣布淘汰Subversion支持
这种让 Git 看起来……不可避免的回忆让我有些困扰。
关于 Git 是如何诞生的整个创世神话,把莱纳斯描绘成一个从 CS 之神亲手书写的金字招牌上读出来的先知。
当然,这篇博文中的叙述确实更人性化了一些,记起了那些磕磕绊绊的步骤,记起了莱纳斯从未打算让 Git 本身成为用户界面,记起了一开始甚至连 Git 提交命令都没有,但它仍然把整件事描绘得有些浪漫,仿佛 blob-tree-commit-ref 数据结构就是数据的完美代表。
在这个创世神话中,尤其是在Github的作者笔下,有一个方面常常被忽略,那就是Mercurial在其中扮演了重要角色。它是由另一位内核黑客奥利维亚-麦考尔(Olivia Mackall)与 Git 同时创建的,目的与 Git 相同。奥利维亚向莱纳斯推荐了Mercurial,但莱纳斯并不看好它,而是坚持自己的想法。与 Git 不同,Mercurial 一开始就有一个用户界面。它的用户界面与当时占主导地位的VCS Subversion非常相似,因此Mercurial一直以用户熟悉为目标,同时又不牺牲用户的灵活性。一开始,两种 VCS 都占据了市场份额,即使到了今天,Mercurial 的市场份额仍在 hg 本身以及 Git 的后继者(如 jujutsu)中延续。
Git 的数据结构并不是唯一可行的。对于大文件来说,这种结构已经支离破碎。虽然有一些变通方法和补丁,但也有完全不同的数据结构适用于更大的数据量。
在我看来,Git 并不简单,也不是不可避免的。我仍然期待着 Git 之外的世界,不管是柔术还是其他可能出现的东西。
我很好奇,你为什么认为 hg 在其中扮演了重要角色。我的意思是,它的确是出于完全相同的原因(BK、内核闹剧)在几乎完全相同的时间冒出来的,但我完全没看到 Matt 的基准或开发影响 Git 设计决策的证据。
下面是马特(奥利维亚)最早介绍项目和基准的一个线程,但似乎列表中的人都觉得相对而言它不够引人注目,所以也没怎么深究:
https://lore.kernel.org/git/Pine.LNX.4.58.0504251859550.1890…
我同意 hg 的用户界面总体上更好,一些决策也可以说更好(后来出现的变更集演进就很了不起),但我很难同意 hg 从根本上影响了 Git。
[deleted]
“在这个创世神话中,尤其是 Github 的作者,常常忽略了一个特别的方面,那就是 Mercurial 在其中扮演了重要的角色。”在我看来,Hg 在 Git 的创世过程中扮演了一定的角色,这也是我对此作出反应的原因。
至于 “deadnaming ”的说法,这并非出于不敬,而是在提到电子邮件链时,如果你不知道她的转变,可能会产生混淆。
hg-git 不是我赞助的,而是我写的。我还为 GitHub 写了最初的 Subversion 桥接器,实际上它最近已经废弃了。
https://github.blog/news-insights/product-news/sunsetting-su…
> 关于 “死名 ”的评论,这并不是出于不尊重,而是在提到电子邮件链时,如果你不知道她的转变,可能会引起混淆。
我想这是无辜的。但是,在给已婚妇女或其他改名者起名时,通常是叫他们现在的名字,并附加说明信息。反之亦然 简-琼斯(Jane Jones née Smith)。奥利维亚(后改名为马特)。
> 请不要这样做。不要给某人起死名。
鉴于她当时叫马特,而且这是理解所链接的邮件主题的关键信息,这难道不是一个有正当理由的案例吗?如果没有上下文,我肯定完全不明白。
正确的做法是说 “奥利维亚(马特)”之类的话,然后继续。使用首选名称,如果需要使用死名来消除歧义,就使用死名。
如果可以避免歧义,也可以这样做。该名称确实已死。如果可能的话,你不应该使用它。
等等。你现在说 hg 没有影响 Git,但这和你之前的评论有什么关系?
> 在这个创造神话中,尤其是 Github 的作者,经常忽略一个特别的方面,那就是 Mercurial 在其中扮演了重要的角色
我不知道你的事实从何而来。
Mercurial 在创世神话中扮演了重要角色。它并没有影响 Git,但却以同样的理由同时出现在那里,并一度具有同等的影响力。Bitbucket 曾一度被视为与 Github 相当。人们会同样经常地为自己的项目选择 Git 或 hg。用户对这两种选择都很熟悉。
莱纳斯从不关心 hg,但很多关心过 Git 的人至少也熟悉 hg 的一些概念。
此时,围绕 Git 的很多想法都已为人所知。人们已经提到了 monotone。不过,莱纳斯最初的设计还是出了问题,他计算了压缩内容的哈希值(这是个性能问题,也会导致压缩算法难以替换)。我很早就指出了这一点[1],他后来也改了。
我认为当时的 Git 之所以能成功,是因为它是一个用 C 语言编写的小巧、实用、非常高效的无厘头工具,这使它比用 C++ 或 Python 编写的替代品更能吸引许多人。
[1]: https://marc.info/?l=git&m=111366245411304&w=2
也因为 Linux 为 Git 提供了免费的 PR(尤其是它得到了 Linux 主要开发者的支持)。
人的因素很重要,尽管程序员们喜欢假装它们不重要。
> 因为它得到了 Linux 主要开发人员的支持
“由……支持 ”改为 “最初由……编写”。
我并不特别记得莱纳斯推动过 Git 的普及。虽然他很乐意让其他项目使用 git 并将其作为自己的测试资源,但他最关心的还是如何做出符合自己对 Linux 维护要求的东西。BitKeeper 曾被试用过,而且效果不错¹,但其中存在着重大的授权问题,这在一些内核大贡献者中引起了激烈的讨论(不止一次或两次演变成了火焰大战),而且这些问题非但没有消失,反而愈演愈烈²。
莱纳斯之所以选择 Git,而不是同时或稍早开始的其他开放选项,一个重要原因是,像 Linux 这么大的源代码树,分支和合并的效率在其他项目中可能相当低–这对 Linux 开发的管理方式非常重要。
当然,大多数其他项目并不像 Linux 那样有同样的需求,但 Git 对它们来说通常也不赖: 在内核工作的人也在其他项目中使用 Git(从内部传播开来),而更远的人则认为 “既然他们都在用,那肯定值得一试(或首先尝试)”,所以 Git 作为第一个被人们尝试的 DVCS 而 “赢得 ”了一些空间³,他们没有尝试其他的 DVCS,比如 mercurial 或 fossil,因为 Git 工作得很好(或足够好),所以他们只是没有去尝试其他的 DVCS⁴,而其他的 DVCS 对他们来说也同样有效。
—-
[1] 回顾过去,大多数人似乎都认为 BK 只是昙花一现,但莱纳斯却用了整整几年。
[2] 导致分离的一个重要问题,并不是因为 BK 在技术上对 Linux 项目有什么缺陷,而是人们通过反向工程协议来获取某些元数据,而这些元数据需要使用付费版本才能看到,BK 的所有者对此非常不满。
[3] 所以,是的,人为因素,但与莱纳斯这个特殊的人没有直接关系,更多的是与他因之而出名的项目有关。
[4] 这听起来比我想的要轻蔑得多。当然,也有很多人尝试了多个版本后发现自己更喜欢 Git,还有一些人也尝试了多个版本,但还是选择了其中之一,因为它们更符合个人/项目的需要。
> 我并不特别记得莱纳斯曾经推动过 Git 的普及。
除了在谷歌(那时候谷歌还是时髦的FAANG)做了一次史上最高能见度的技术演讲,宣布Subversion(当时领先的SCM)脑死亡之外?
市场营销的方式多种多样,信号传递也是如此。极客也穿西装,只是他们的西装不是由西装外套和西装裤组成的,而是由T恤衫和牛仔裤组成的。
> 宣布 Subversion(当时领先的单片机)脑死亡?
我记得那更像是在斥责非分布式 VCS 的现任领导者,而不是在推广某个特定的 DVCS,而且当时 Git 还不成熟(从 BK 迁移过来的事情还没发生)。也许我记忆中的时间线混淆了,您是否有更多关于那次谈话的参考资料,以便我核实细节?
这很容易在谷歌上搜索到。
好吧,我想我还不如玩 ChatGPT 🙂
https://en.wikipedia.org/wiki/BitKeeper
https://en.wikipedia.org/wiki/Git
https://sandeep.ramgolam.com/blog/linus-torvalds-talks-about… -> https://www.youtube.com/watch?v=4XpnKHJAok8
我认为开源、分布式、内容可寻址的 VCS 是不可避免的。不是 Git 本身,而是具有类似功能/工作流程的东西。
2005 年,没有人真正满意 VCS 的状况。大多数人还在使用 CVS 或其他商业软件。SVN 确实存在,但它在 2004 年才刚刚达到 1.0 版本,而 SourceForge 等平台仍然只提供 CVS 托管服务。SVN 被认为是更完善的 CVS,但它并没有好到哪里去,而且仍然存在集中式的根本缺陷。
另一方面,“分布式 ”是 2005 年的热门新词。最近,Bittorrent(尤其是其热门的新 DHT 功能)和其他文件共享平台的成功将这一概念推向了主流。
即使没有 Bitkeeper 事件,我也认为我们最迟在 2008 年就会看到一些东西出现。它可能不会像Git那样迅速流行起来,但你必须记住,让Git一炮而红的是GitHub,而不是Linux内核。
是啊,我觉得那些抱怨 Git 的人应该试着用 CVS 或 subversion 来运行一个项目。
Git 令人惊叹的灵活性似乎让很多人望而生畏,而且很多程序员似乎并没有建立起一个良好的心智模型。我曾为开发团队举办过几次 Git 教程,得到的主要反馈是 “我不知道 Git 这么简单”。
> 关于 Git 是如何诞生的,有这样一个神话,它把 Linus 描绘成从 CS 之神亲手书写的金字招牌上读出的先知。
莱纳斯绝对有几处精辟的见解:
1. 源代码树的内容可寻址存储。
2. 文件并不重要:https://gist.github.com/borekb/3a548596ffd27ad6d948854751756…
当时,我正在使用 SVN,并尝试使用 Hg 和 Bazaar。对我来说,这两个软件都太 “神奇 ”了,合并、分支、重排的规则都不清楚。
后来,Git 出现了。我读到它的描述:“源代码树,通过哈希值来识别,文件内容的移动通过差异来推断”,我立刻就喜欢上了它。这是一个非常简单的心智模型,你可以立刻理解操作的含义。
> 2. 文件并不重要
我希望每周都有明确的重命名。
> 当时,我正在使用 SVN,并尝试使用 Hg 和 Bazaar。两者对我来说都太 “神奇 ”了,合并、分支、重排的规则都不明确。
我不明白你的意思。
> 这是一个如此简单的心智模型,你可以立即理解操作的含义。
很多人显然不同意。
> 我希望每周都有明确的重命名。
在 Git 中就能做到。Git mv 会在提交中提示文件已被移动。
只是不一定要这么做。
> `git mv` 会在提交中提示文件已被移动。
不,它会出现在 Git 状态中,但不会提交。如果文件被修改得足够多,它就会从 Git 状态中消失。
另一个选择是来自 Darcs 和现在的 Pijul 的补丁理论方法。这是一种从根本上不同的版本控制思维方式–我自己还没用过,但通过阅读,我发现补丁思维比 Git 模型更符合我的直觉。Darcs 有一些工程上的限制,在某些情况下会导致非常糟糕的性能,但我知道 Pijul 解决了这个问题。
在基于补丁与基于快照的关键点上,我有些困惑,但在这个主题中我得到了一些澄清:https://news.ycombinator.com/item?id=39453146
darcs 的补丁理论和 CRDTs(以及中间的 OTs [Operational Transforms] 概念)在早期研究和早期交叉交流中都有有趣的关联。当然,令人着迷的是,今天问 “你有没有想过要用 CRDT 来做源代码控制?”可能更容易解释为什么你可能会想要基于补丁的而不是基于快照的,因为 CRDT 的存在部分是因为它的起点是 “如果你能做一些类似 darcs 的事情,但不只是做源代码控制,而是做通用数据,那会怎么样?” 哪种技术会以哪种方式/场所/场合胜出,这一点很吸引人。
这篇文章的作者是 Github 的联合创始人,而不是 Linus Torvalds。
Git 只是一个做事情的工具。它的名字(由那个芬兰人取的)非常贴切–它是给 Git 用的!
它不是 Mecurial,也不是 github,更不是其他任何东西。它是 Git。
它不是为你或你甚至你自己发明的。它是为了完成一项任务而被黑客们黑出来的:当比特守护者(Bit Keeper)在 Linux 内核开发者们看来走火入魔时,对 Linux 内核源代码的控制。
看起来效果还不错。
> 也有完全不同的数据结构适合较大的数据位。
你能谈谈这个问题吗?我的假设是,正确处理大文件的唯一方法是回到集中式 VCS,我很想知道有哪些不同的数据结构可以避免这个问题。
处理大型二进制文件的一种方法是 Git-annex,它和 Git 一样是去中心化的。但我敢说它输给了 Git-lfs,因为 Git 和其他公司对托管它不感兴趣。
2000年代初,我因工作需要研究VCS,同时也帮助开发arch、bazaar和bzr。我在工作中试用了 Bitkeeper。我们最终使用了 Subversion。我想我试过 Monotone,但速度太慢了。我还试过 Mercurial。它并不适合我。
当我第一次使用 Git 时,我觉得是的!就是它了。就是它了。它的模型如此引人注目,速度更是惊人。
除非迫不得已,我再也没有用过其他工具–通常是 Subversion,主要是出于惯性。
> 关于 Git 是如何诞生的,有这样一个神话,把莱纳斯描绘成从 CS 之神亲手书写的金字招牌上读出的先知。
什么?
> 在我看来,Git 也不是不可避免的。
我的意思是,事实胜于雄辩。那么,为什么我们最终会有 Git 呢?只是运气不好吗?也许吧。但我在一开始就参与了Git和Mercurial的开发(正如我在这篇文章的其他地方所评论的那样)。起初我对它们的使用不相上下,作为一个 Python 爱好者,我本该倾向于使用 Mercurial。
但我喜欢了解工具是如何工作的,而且我个人觉得 Mercurial 更难理解,使用起来更慢,灵活性也更差。它很适合某些工作流程,但如果这些工作流程与你想做的事情不符,它就会显得很死板(我无法详细说明这一点,因为已经过去十多年了)。令人惊讶的是(当时我几乎完全用 Python 编程),我还发现它比 Git 更难做出贡献。
现在,我只是一个普通人,但我们有了这个并不简单、愚蠢(但速度极快)的目录内容管理器。
> 但我喜欢了解工具是如何工作的,我个人觉得Mercurial更难理解,用起来更慢,灵活性也差很多。
能听到别人这么说,我就放心了,在这样的主题中,除了对 Mercurial 的赞美,很少能找到别的了。
我的情况也差不多: 在 2010 年代早期/中期,我同时试用了 Git 和 mercurial,当时我只有 subversion 的经验,但我发现 mercurial 处理分支的方式让人非常困惑(不记得是什么了,已经过去很久了)。另一方面,我觉得 Git 非常直观,从来没遇到过问题。
说得好。Git 的成功与 Unix/Linux 的成功如出一辙。是的,它在很多方面都很糟糕,但它足够灵活和强大,值得一用。与此同时,“更好 ”但不灵活、不强大或不可破解的东西在进化上是不成功的。
事实上,既然我使用了 “进化 ”这个词,地球生命/DNA的功能也是如此。适应性永远胜过完美性。
对我来说,当时真正的问题是 “重建基地 “是一个二等功能。
我认为当时有太多的人认为完全的不变性才是人们想要的,并为此纠结不已。结果发现,几乎每个人都想隐藏自己的错误、结构不良的提交和开箱即用的错别字。
此外,mercurial 的速度也比较慢。
别忘了同时起步的 Fossil…
https://fossil-scm.org/home/doc/trunk/www/history.md
>而 Git 的数据结构……对于大文件来说分崩离析。
我很擅长这个。在我超过 25 年的专业经验中,使用过 cvs、svn、perforce 和 Git,在 VCS 中保存非源文件几乎总是个错误。数字资产和巨型数据文件几乎总是最好从人工智能存储库或 CDN 系统(包括内部版本)中提供。我曾在 EA Sports 和 Rockstar Games 工作过,开发团队使用数字资产导致版本倒退的次数一只手就能数过来。
CAD 数据本身就不是源吗?
我上一个 CAD 文件就有 40GiB,而且还不算大。
认为所有数据源都是文本的观点意味着艺术从来不是数据源,许多工程学科也被排除在外。
Perforce 在游戏和汽车领域占据主导地位是有原因的,而不是因为人们喜欢 Perforce。
我认为这是把 “非源文件 ”和 “大文件 ”混为一谈了。是的,源文件通常比生成的输出文件小(尤其是图形工件),但这其实只是一种幸运的巧合,它避免了在版本控制中处理大文件的尴尬,也避免了可能出现的麻烦。如果有一个能轻松处理大文件的 VCS,我们就能解放思想,开拓新的视野。
我认为,问题的关键在于如何合理地对这些其他格式进行差分和合并。对于许多基于文本的格式(如典型的程序代码)来说,基于莱文斯坦距离的差分已经足够好了,但还有改进的余地。在设计文件格式(包括二进制格式)时,如果能特别考虑到 “可扩散性”,也许就能取得进展–就像 Java 在设计时考虑到 IDE 支持一样。
非源文件确实不应该出现在 VCS 中,但源文件仍然可以是二进制的,也可以是大文件,或者两者兼而有之。这取决于您如何编辑源代码以及如何将源代码构建为非源文件。
另外,有些源文件本来可以被视为文本⁰,但最终却变成了二进制文件,因为工具不会以稳定的顺序写入这些文件,这就给跟踪小改动带来了困难,因为你看不到它们是否真的是小改动。许多 XML 格式¹,有时还有 JSON 和其他格式,也有这个问题。
—-
[0] 出于变更跟踪和合并的目的
[1] 强烈谴责 SSIS 讨厌的包文件格式² 和以不同顺序保存的习惯,显然是随机的,因此更新注释的文本可以完全重新排列保存的文件
[2] 据我所知,这远不是 SSIS 犯下的唯一罪行,但偶尔也会让人恼火,不得不提
能否使用 Git 预提交钩子或类似的东西,通过确定性地排序各级项目来转换文件?
https://diffoscope.org/ https://try.diffoscope.org/
> 能不能用 Git 预提交钩子
有可能,不过我可能会担心格式的排序奇异性会出乎意料地敏感。不太可能,但考虑到 DTS/SSIS 多年来收集了许多其他奇怪的东西,我不会感到惊讶!
另外,在我们积极使用 SSIS 开发的时候,我们并没有在 DayJob 中使用 Git(也许 VSTS 有一个我们可以使用的等价物?),我们现在的行动是将它的最后残余从我们的工作流中删除,而不是花时间让它与工作流更好地协同工作!
OMG!请不要提醒我试图对 SSIS 进行源控制。一个微小的改动就会导致 1000 行源代码不同。简直就是噩梦。
我支持 pijul。
我只希望他们能扩展 Git,以便有更好的二进制文件差异和移动文件跟踪功能。
记住真实的历史很重要,因为保留历史本身就很有价值,但我也真的很高兴 VCS 对大多数人来说已经完全解决了,除了 Git 没有什么你必须关注的了,学一次就能用一辈子。
> 我只是希望他们能扩展 Git,让它有更好的二进制文件差异
这不是 Git 本身内置的,但我记得看过一些演示,在 Git 试图显示图像文件差异的时候,可以配置 Git 使用外部工具来做可视化差异。
> 和移动文件跟踪。
查看帮助中的 -C 和 -M,了解 Git 日志和责备。Git 的移动跟踪比其他工具更古怪(它从历史记录中重建移动/复制,而不是在提交时记录),但我发现它比其他工具更强大,因为你不需要记住特殊的 “移动 ”或 “复制 ”命令,另外它还能以其他工具无法做到的方式跟踪两个文件的合并。
它显然无法检测到所有的移动,Torvalds 关于不需要移动跟踪的理论是胡说八道。
> 但我也真的很高兴,VCS 对大多数人来说已经完全解决了问题,除了 Git 之外没有什么需要注意的,你学一次就能用一辈子。
据我所知,目前大多数新开发人员从未真正学习过 Git,他们只是学习了 Git GUI 的一些功能。
这是可以理解的,你确实低估了学习 Git(史上最混乱、文档最差的软件之一)的意义。
事实上,我觉得我们被 Git 困住是一种耻辱。
我就是这样的人。如果需要,我可以使用 Git CLI,因为 Git Book 的存在,但我几乎从不需要。
如果我不得不每天都在 CLI 上实际使用 Git,我可能会抱怨很多,但当你使用 Git Cola 和 GitHub 时,这已经是相当不错的体验了。
如果能像 Fossil 那样提供原生讨论、问题和维基就更好了。
在我的印象中,Monotone–比 Mercurial 早两年发布–是 Git 的灵感来源,而这一点也是相当众所周知的。
这都是猜测,但我并不觉得 Monotone 是 Git 的主要灵感来源。我认为 BitKeeper 才是,因为它是 Linus 真正喜欢使用的工具。Monotone拥有内容可寻址系统,这显然是一个灵感来源,但这是我从Monotone中看到的莱纳斯唯一的参考。他试着用了一下,但因为速度太慢而放弃了,但他采用了一个他觉得有趣的想法,并以这个概念为一部分构建了一个完全不同的东西,这就是我对这些项目之间历史的理解。
莱纳斯肯定知道并提到过 Monotone。但说它是一种灵感可能有点过了。在此之前,内容可寻址存储(Content Addressable Stores)已经存在了很长时间,但主要是用于备份目的。参见 Plan9 的 Venti 文件系统。
是的,Monotone 部分启发了两者。你可以看到两者都对内容进行了散列。但 Git 和 hg 都是为了取代 Bitkeeper。Mercurial甚至是以拉里-麦克沃伊(Larry McVoy)的名字命名的,但他后来改变了主意。你知道的,他就是个喜怒无常的人。
“文件-树-快照-参考 ”结构相当不错,但它在文件层和树层缺乏分块,这使得它在处理变化不大的大文件和树时效率低下。现代备份工具(如 restic/borg/等)使用类似的结构,但包含分块功能。
如果你没看过 Larry McVoy 关于 Git 的问题,我想这份清单还是有一定分量的。
https://news.ycombinator.com/item?id=40870840
你碰巧知道莱纳斯不喜欢 Mercurial 的什么地方吗?
我也想知道。我猜他不喜欢 “重量级 ”分支,也不喜欢缺乏cherry-pick/rebase。无论如何,这就是我当时不喜欢它的原因。
Sun Microsystems(RIP)当时使用 Mercurial 而不是 Git,主要是因为 Mercurial 对文件重命名的支持比 Git 好,但在 Sun,我们使用 Mercurial 的 rebase 工作流程,尽管 Mercurial 没有 rebase 命令。Sun公司从1992年起就开始使用rebase工作流程。使用 Mercurial 进行回溯很麻烦,但我们已经习惯了使用 Teamware 时的麻烦工作流程。使用 Mercurial 是个错误。我不知道甲骨文内部现在是怎么做的,但我打赌他们用的是 Git。Illumos 用的就是 Git。
我着迷地看着整个过程。它漫长、谨慎、周密……却选择了错误。
我的一部分想法是,Sun 用户对任何与 Linux 有关的东西都很反感。
> 我认为 Sun 用户对任何与 Linux 有关的东西都很反感。
不是那样的。其实只是文件重命名的问题。
Git 忽略了这个死结。许多 VCS 都曾为文件标识而苦恼。他们高估了其重要性,低估了其难度。从一般意义上讲,这基本上是不可能的。
具有讽刺意味的是,现在 hg 的重定性比 Git 更好,比如 evolve 扩展
啊,对了,在 Sun 我们用的是 MQ。但不管怎么说,只看了一眼 hg evolve 文档,我就不太相信了。不管怎么说,这是一个 “扩展”。Mercurial与rebase斗争了很长时间,他们做了很多蠢事,比如 “rebase是非交互式的,而histedit是用于编辑历史记录的”。
这也是 Mercurial 失败的部分原因。他们坚持以合并为基础的工作流程。而 Git 并不固执己见。如果你喜欢,Git 可以让你使用合并工作流,如果你喜欢,Git 也可以让你使用重置工作流,而且没有任何关于开发人员编辑本地历史的评判–VCS 怎么敢告诉我怎么处理我的本地历史?
Mercurial 中的很多东西都让你倾向于像使用 Subversion 那样使用它。你几乎可以像过去和现在使用 Git 那样使用 Mercurial,但默认设置并没有引导你朝那个方向去做。
我能想到的一个更大的区别是,Mercurial有永久命名的分支(分支名称写在提交中),而在Git中,分支只是命名的指针。Mercurial 在 2008 年作为扩展获得了书签,并在 2011 年加入了核心。如果使用未命名的分支和书签,Mercurial的使用方式就和git一模一样了。但 Git 是 2005 年发布的。
另一个是 Git 的暂存区。重复使用 “hg commit –amend ”可以获得几乎相同的功能,但同样的,在 Git 中,默认情况下你会倾向于使用暂存区,而在 Mercurial 中,你必须专门寻找一种方法让它以这种方式运行。
我可能记错了,C 与 Python 之争是其中的一部分。我认为莱纳斯并不看好 python 或任何解释型语言(也许除了 shell),也不想处理安装和管理 python 软件包的问题。
> 并不想处理安装和管理 python 软件包的问题。
事实上,生态系统破坏了整个语言的主要版本,而且有数十亿个相互竞争、互不兼容的包管理器,看来这个赌注下得不错
我喜欢 Python,虽然不想承认,但你是对的。
莱纳斯担心Mercurial与BitKeeper足够相似,BitMover可能会威胁到使用它的人。也许他还有其他抱怨。
> blob-tree-commit-ref 数据结构是数据
的完美代表,不是吗?有什么替代方案吗?
[deleted]
> Git 是给业余副业用的。Perforce 和 Sapling 适用于成人项目。
我看到的数据显示,Git 占据了版本控制市场 87-93% 的份额。这只是我认为大多数专业开发人员不同意你观点的众多原因之一。我可以理解有人在工作流程中偏爱 Perforce(是的,我以前也用过)。但说 Git 只 “适用于业余项目 ”就太可笑了。它显然已经证明了自己在专业开发工作中的价值,即使它不符合你的个人喜好。
局部最小值是设计空间中的一个点,从这个点出发的任何改动都是一种改进(但如果改动更大,还有其他设计会更糟)。我认为很难对 Git 做出这样的判断。你说的可能是局部最大值,也就是设计空间中的一个点,从这个点出发,任何改动都会使设计更好(但如果改动更大一些,还有其他设计会更好)。
在我的职业生涯中,我使用过 Svn、Git 和一个好像叫 VSS 的东西。Git 带来的问题确实比较少,而且也很容易教给新手。我认为 Git 的最大特点是,人们真的能从 Git 模型和数据结构的教学中受益匪浅(即使是新兵训练营的小学员们在第一份工作中也是如此),因为他们突然就从魔法咒语的角度变成了解决问题的角度。我从未体验过其他软件拥有如此强大的心智模型。
当然,这并不意味着Mercurial就不好,因为我从来没用过它。也许 Mercurial 拥有 Git 的所有优点,甚至更多。但如果真是这样,我想就很难说 Git 已经达到了本地最大值。
> 好像叫什么 VSS
嗯,也许是微软的 Visual Source Safe?我记得。由于多种原因,它声名狼藉:
* 默认要求用户在修改文件前必须 “签出 ”文件。也就是说,如果一个人签出了一个文件,那么其他人就无法编辑该文件,直到它再次被签入。
* 有一个偶尔损坏数据库的坏习惯。
* 据说微软内部很少使用或根本不使用。
* 如果服务器不在同一个局域网内,速度慢得几乎无法使用。虽然当时远程工作(即使用拨号连接)的人并不多,但对于那些远程工作的人来说,速度确实非常糟糕。
> 它也很容易教给新手
宣称 Git 很简单的指南之多,足以证明 Git 并不简单。而那些真正容易的事情,也确实涉及到无数关于它们有多容易的争论。
我可以在十分钟内教会一个从未听说过版本控制的艺术家或设计师如何使用 Perforce。他们会遇到一些棘手的问题,但可能永远不会丢掉工作或 “陷入糟糕的状态”。
> 局部最小值是[……]
除非你用的是 ML,在这种情况下,它是损失函数的最小值,而不是效用函数的最小值……
> 你指的可能是局部最大值,即设计空间中的一个点,从这个点出发,任何改变都会使设计变得更好(但如果做出几个更大的改变,还有其他设计会更好)。
我认为你的第一个 “更好 ”指的是 “更差”。
Git 容易教新手是一种不常见的观点。我不清楚你是否是指比 Subversion 更容易。但这种说法更不常见。
> 我从未体验过其他软件拥有如此强大的心智模型。
我不想这么说,但你应该花点时间和 jj 在一起。我也是这么想的,但 jj 继承了这一模型,并加以改进,用更少的基元为你提供更强大的功能。如果你对 Git 有这样的感觉,但还是老老实实地试试吧,我觉得你会喜欢它的。
也可能不会。不同的人有不同的感受)
> 在我 18 年的专业开发生涯中,我从未真正专业地使用过 Git。Git 是给业余项目用的。Perforce 和 Sapling 用于成人项目。
我在职业生涯中接触过 Perforce、Mercurial 和 Git。考虑到 Git 在市场上的突出地位,很显然,Git 在某些方面做得很好。我自己就发现,在其他突出选择都有缺点的情况下,Git 却非常可靠。
Git 的使用如此广泛,以至于它几乎不可能是本地的最低标准。
*本地最大值
Mercurial 曾经有过机会,但它坚持历史不可更改,分支是重量级对象(SVN 风格),结果搞砸了。事实证明,这不是人们想要的,所以 Git 赢了。
(现在 Mercurial 支持历史编辑已经无关紧要了,那艘船早就扬帆起航了)。
一切都是局部最小值,因为除了最琐碎的领域之外,你无法详尽地证明其他任何事情。
> 而这更是一种悲剧,因为 Mercurial 比 Git 要好得多。这是 VHS 和 Betamax 的较量。
VHS 赢了,因为它更便宜,录制时间更长。在人们实际使用的录制速度下,保真度相差无几。
在我25年左右的职业开发生涯中,我用过大约15年的Git。我从未使用过 Perforce,甚至从未听说过 Sapling。
你发表评论所使用的软件栈,即使不是全部,也很有可能大部分是用 Git 管理的。
我非常清楚 Git 有多普及。
整整一代程序员只知道 Git 和 GitHub。他们认为,既然是标准,就一定是好的。这是一个谬论。
即使有更好的东西存在,坏东西也会变得流行并根深蒂固。今天要取代 Git,需要的不仅仅是更好一点的东西,而是从根本上更好的东西。Git 一直比 Mercurial 差。它之所以胜出,是因为有了GitHub。如果 MercurialHub 早被发明出来,我们现在就会用它,而且会更开心。唉。
> Git 一直比 Mercurial 差。
很难同意。Git 一直都比 Mercurial 好。
> 它之所以胜出,是因为 GitHub。
我和多年来与我共事的大多数开发者,在尝试 GitHub 之前都已使用 Git 多年。GitHub 显然帮助了人们采用 GitHub,但我并不认为,即使 GitHub 从未存在过,Git 也不会赢。
> Git 总是比 Mercurial 好得多。
Git怎么会比Mercurial好?我是认真的。
我可能会接受 “最初的 Mercurial 是用 Python 实现的,速度很慢 ”这个说法。也许这就是Git胜出而不是GitHub的原因。但我不这么认为。
所以你用的这些东西都不能算是 “成人项目”?
我不反对说我们可以做得比 Git 更好。但说 Git “只适合业余项目 ”就太可笑了。对于严肃的项目来说,它很好。
取决于我有多尖刻。我不确定如果我觉得自己很调皮,Webdev 算不算是穿了大男孩的裤子!如果能分析一下有多少堆栈使用 Git 作为其真实来源,而不是由 BigTech 成人 VCS 作为镜像,那将会非常有趣!
对于严肃的项目来说,Git 实在是太烂了。当然,很多人都用 Git。但我不认为 “我们可以做得更好 ”这句话就足够有力。Git 既烂又糟。它很实用,但也很糟糕。我们能比 Git 做得更好。
我喜欢抨击 Git,因为除非人们要求更好,否则我们永远做不到更好。如果人们认为 Git 已经足够好了,那么我们就永远不会有更好的东西。这让我很难过。
我在一家大型科技公司工作。所有东西都用 Git。我指的不仅仅是网络部分,而是整个操作系统,直到内核。
节哀顺变 🙁
> Git 对于严肃的项目来说糟透了。
,我再次表示难以苟同。我整天都在做严肃的项目。对于严肃的项目来说,Git简直就是神器。
我希望有一天你能体验到一个不那么糟糕的 VCS 工具。
我们错过了什么?
> Git 一直比 Mercurial 差。它能赢是因为 GitHub。如果 MercurialHub 是被发明出来的,我们都会用它,而且会更开心。
MercurialHub已经发明了。它叫Bitbucket,与GitHub成立时间相近,一开始也是用Mercurial。人们想要 Git,于是 Bitbucket 被迫改用 Git。
> People wanted Git
No. If Bitbucket competeed with MercurialHub then MercurialHub would still have won and we’d all be much happier today.
Bitbucket 是你想要的 MercurialHub。他们首先支持 Mercurial。然后他们又增加了对 Git 的支持,因为人们想要 Git。那时,Bitbucket 用户可以在 Git 和 Mercurial 之间做出选择。他们选择了 Git,以至于 Bitbucket 停止了对 Mercurial 的支持。
听着,我知道你讨厌Git,但人们可以选择使用Mercurial还是Git–甚至在同一个平台上–他们绝大多数都选择了Git。你声称 Mercurial 会在公平竞争中获胜,但你忽略了一个事实,那就是公平竞争确实发生了,Mercurial 输了。
你说得好像 GitHub 和 Bitbucket 之间唯一的区别就是选择了 VCS 工具。这显然不是事实。GitHub 是一个比 Bitbucket 更好用的平台。选择 Git 与此无关。
GitHub赢了。而不是 Git。我个人认为。
使用带有 Mercurial 的 Bitbucket 和使用带有 Git 的 Bitbucket 之间的唯一区别就是 VCS 工具的选择。而人们选择了 Git。
> 如果 MercurialHub 早被发明出来,我们就都用
了。我们没有。
我两个都用过。我不敢苟同。
大约在 2002 年左右,我有一个想法,就是给项目的每一部分都加上一个唯一的哈希代码。有了哈希代码,人们就可以下载相应的文件。整个项目的哈希代码将是一个文件,其中包含组成项目的文件的哈希代码列表。哈希代码可以代表编译它的编译器以及与之链接的库。
我向几位软件企业家(Wild Tangent 和 Chromium)展示了这个项目,但他们对此毫无兴趣。
我再也没有用它做过任何其他事情,就这样不了了之了。
事实上,我曾写过一篇关于它的文章,但我以为我把它弄丢了。我找到了,日期是 2002 年 2 月 15 日:
—
考虑到任何 D 应用程序都是由 .module 文件列表和编译它们所需的工具完全指定的。为每个唯一的 .module 文件分配一个唯一的 GUID。然后,一个应用程序由一系列 .module GUID 指定。每个应用程序也分配一个 GUID。
在客户机上存储了一个已下载的 .module 文件池。下载新应用程序时,实际下载的只是一个 GUID。客户机会查看该 GUID 是否是池中已构建的应用程序,如果是,则完成下载。如果不是,客户端就会请求获取 GUID 的清单,清单就是 .module GUID 的列表。清单中的每个 GUID 都会与客户池进行核对,未找到的会被下载并添加到池中。
一旦客户端拥有构成应用程序的 GUID 的所有 .module 文件,就可以对它们进行编译、链接,并将结果缓存到池中。
这样,如果应用程序更新,只需下载已更改的 .module 文件即可。还可以更进一步,将更改后的 .module 文件表示为与之前 .module 文件的差异。
由于 .module 文件是标记化的源文件,因此两个仅在注释和空白处不同的源文件将拥有完全相同的 .module 文件。
WT 服务器上会有一个 .module 文件主库。当一个应用程序准备发布时,它将通过为其 .module 文件分配 GUID 来 “签入 ”主库。当客户端通过 GUID 请求 .module 文件时,就会查询主库。
D “VM ”编译器、链接器、引擎等也可以通过 GUID 标识。这样,如果应用程序是使用特定的工具组合开发的,就可以在清单中指定这些工具的 GUID。因此,客户端会自动下载 “VM ”更新,以获得准确复制应用程序所需的确切工具。
是的,请允许我向您介绍 Nix 的白皮书,它基本上就是这样的,因此值得您一读:
https://edolstra.github.io/pubs/nspfssd-lisa2004-final.pdf
另一个可能相关的想法是 Unison 语言:
https://www.unison-lang.org/
谢谢。看来我的想法比 Nix 早了两年!
NixOS 可能会成为 “我用过的最后一个操作系统”(尤其是现在游戏可以在上面运行了):
https://nixos.org/
来看看。白皮书也相当易懂,可能会让你对整个概念感到兴奋(这与通常的做法大相径庭,但最终会给你带来保证):
NoxOS 的问题在于,所有捕获软件闭包的努力都因 Linux 命名空间而变得毫无意义,而 Linux 命名空间是解决同一问题的更完整方案。
当然,写白皮书的时候我们还没有命名空间,所以这也是公平的,但技术已经向前发展了。
Nix(OS)知道命名空间,也能使用命名空间(事实上,前面提到的游戏支持就依赖于命名空间),但在大多数情况下,版本化软件包仍然比版本化系统更有效。
B 有两个版本,A 和 C 有一个版本。
– A-1.0.0 依赖于 B-2.0.0 和 C-1.0.0。-C-1.0.0 依赖于 B-1.0.0。
如果 A 在 B-2.0.0 中获取了一个文件的路径,并想与 C 共享(例如,C 可能提供了可以在文件上运行的二进制文件,或者 C 可能是一个守护进程),它需要 C 与 B-2.0.0 在一个挂载命名空间中。但是,如果没有类似于 Nix-store 的目录结构,挂载 B-2.0.0 的文件将覆盖 B-1.0.0 的文件,因此 C 可能无法启动或行为不端。
我不认为这是真的。你如何编译一个与 Linux 命名空间有冲突的程序?
Linux 命名空间和 Nix 闭包在软件生命周期的不同阶段解决了不同的问题。命名空间隔离了正在运行的进程;而 Nix 闭包则保证了构建时间的确定性和跨系统的可重复性。
命名空间不能跟踪传递依赖关系,不能保证可重现的构建,不能回滚,也不能让你在其他地方部署精确的闭包。命名空间只是沙箱工具,而不是软件包管理或代码即内核(infra-as-code)工具。
如果说两者是互补的话。你可以使用 Nix 构建一个具有精确闭包的系统,而命名空间则可以进一步对其进行沙箱化。但是,称命名空间为 “更完整的解决方案”,就好比称系统调用过滤可以替代源代码控制一样。
另外,历史上的小瑕疵:大多数命名空间在 2000 年代末就已存在;Nix 的白皮书是在那之后写的。因此,这个前提在时间上并不正确。
听起来这也是专为 D 工具链设计的 Nix 版本的一半,使用 GUID 而不是散列输入。
它不是专门为 D 工具链设计的,那只是它能做什么的一个例子。
使用哈希值(内容可寻址)而不是 GUID(每个版本的随机 ID)是一个很大的区别,不过
有意思。我以为把程序称为 “应用程序 ”是智能手机时代以后的事。
20 世纪 80 年代,人们把 Lotus 1-2-3 等程序称为 “杀手级应用”。
1989 年的参考文献:
https://books.google.com/books?id=CbsaONN5y1IC&pg=PP75#v=one…
你的描述(包括回复中的详细描述)似乎忽略了 Git 使用的关键区别–对象的哈希码不是什么 GUID,而是对象内容的哈希值。这一点有很大不同,因为我们不需要某个中央注册中心将 GUID 映射到对象上。
每个 Git 仓库都有该映射的副本,而不是一个中央注册中心,而且因为提交作者的姓名、电子邮件、提交日期和提交消息(以及其他信息)都会被放入代表提交的哈希值中,所以区别并不是很大,不是吗?如果我给的是一组文件,但不是它们来自的 Git 仓库和 libgit,如果我没有组成 Git 哈希值的提交元数据,而不仅仅是其中的文件,我就无法判断这些文件是否与 Git 标签哈希值相匹配。
是的,但提交对象(包含元数据)通过哈希值引用树对象。树对象是目录树的文本表示,基本上是通过哈希值引用文件块。所以是的,你可以识别不同提交之间的相同文件。的确,我们无法快速建立索引:如果你想问 “哪些提交包含了这个文件?”这个问题,你必须搜索每个提交。但你并不需要对文件内容本身进行 delta。
但人们不会使用文件哈希值,那是 Git 内部的东西。我去 github.com 上的集中式版本库中查找任何软件的 1.0.0 版标签,它指的是引用了提交哈希的 Git 标签(没错,它引用的是你说的树对象)。
“人们 ”并不常用它们。但这是一个真实存在的 API(见 https://git-scm.com/book/en/v2/Git-Internals-Git-Objects)。
无论如何,你在上面提出了一个具体要求(“给定一组文件,但不给定它们的 Git 仓库和 libgit,我无法判断这些文件是否与 Git 标签哈希值相匹配”),而事实上这是能做到的!
Git 标签哈希值引用了一次提交。没有提交元数据,就没有树对象,因此也就不知道任何哈希值。你可以获取磁盘上的文件并计算哈希值,还可以获取哈希值并创建树对象,但没有提交元数据,你只能说你有一个树对象,却没有一个树对象可以与相关提交进行比较。
阅读链接。你可以提取提交对象,并通过琐碎的步骤获得树引用。你还可以枚举提交(“Git log ”的作用)。在这个过程中,唯一缺少的就是一个从 blob 到树再到提交的快速反向索引。但即使是最大的版本库,也只需要几秒钟就能生成反向索引(例如,在我的电脑上做一个完整的 Linux Git 日志需要 16 秒)。
我很困惑。你一直说有些事做不到,但其实是可以的,而且并不难。
但这是供人类使用的,这也是许多 “散列能解决一切问题!”的方案令人沮丧的地方–一旦你需要修复漏洞,它就会崩溃。
说到底,我们都不想要 “精确的哈希值”,我们想要的是 “最新的”。精确哈希值和其他可重现性在调试或提供可追溯性时是有用的–很有价值,但也不是等式的人性化一面。
不需要有一个单一的中央存储库,可以有许多部分存储库。但如果它们合并起来,就不会发生冲突。
GUID 当然可以是哈希值。
> GUID 当然可以是哈希值。
不可能,因为 GUID 应该是全局唯一的。关键是,它必须是内容的哈希值。
这不能是事后的想法。
UUID 的第 3 版和第 5 版都是从哈希值(分别是 MD5 和 SHA1)中派生出来的。
GUID 和 UUID 是不同的。
定义它们的 RFC 说它们是一样的,从我能找到的最早的草案(也是 2002 年的)开始就是这样。当你采取与文献记载相反的立场时,你应该提供更多解释。
哈希值不是全球唯一的。我不知道还需要什么解释。
请查看我的其他评论,了解许多 ioquake3 forks 是如何创建 GUID 的。他们对随机生成的文件进行 MD5 散列。
从理论上讲,UUID 有一个语义保证,即每个生成的标识符在所有系统、时间和上下文中都是唯一的,而加密哈希函数是确定性函数(即对相同的输入产生相同的输出),不存在固有的随机性或时间戳,除非你故意注入随机性或时间戳,就像 ioquake3 forks 对 GUID 所做的那样。
UUIDv4 的可用输出大小为 122 位,因此碰撞几率为 1/2^122,而 SHA-512 和 BLAKE2b 为 512 位,抗碰撞几率为 2^256,受到生日问题的限制。
无论如何,SHA-256、SHA-512 和 BLAKE2b(加密哈希值)在实践中都是唯一的,这意味着它们发生碰撞的可能性极小,比 UUIDv4 更小,尽管 UUIDv4 是非确定的,而加密哈希值是确定的。
当然,你仍然应该知道何时使用加密哈希值,何时使用 UUID。UUID 适用于数据库主键、全局识别用户、跟踪事件,而其他功能,如验证文件内容、按内容重复数据和篡改检测则是加密哈希值的工作。
下面我们就直奔主题: GUID(全球唯一标识符)也被称为 UUID(通用唯一标识符),所以它们是一样的!
我希望这回答了 OP(kbolino)的问题。他是对的,GUID 和 UUID 是一样的。家长很可能把 GUID 和加密哈希值搞混了。
—
FWIW,后量子算法并没有提高抗碰撞性(即生日边界)。只要使用哈希算法,无论如何都会受到 2^{n/2} 的固有限制。
—
TL;DR:GUID(全球唯一标识符)也被称为 UUID(通用唯一标识符),所以它们是一样的,也就是说 GUID 和 UUID 并无不同!
怎么说?我认为它们是一样的,至少差不多。
Tremulous(ioquake3 的分叉版)的 GUID 来自 qkeys。
https://icculus.org/pipermail/quake3/2006-April/000951.html
你可以看到 qkeys 是如何生成的,而 GUID 本质上就是:
因此,在这种情况下,GUID 就是生成的 qkey 文件的 MD5 哈希值。详情请参阅 “CL_GenerateQKey”。
> 启动时,客户端引擎会查找名为 qkey 的文件。如果该文件不存在,就会在 qkey 文件中插入价值 2KB 的随机二进制数据。然后对 qkey 文件进行 MD5 摘要处理,并将其插入 cl_guid cvar。
UUID 有 RFC,GUID 显然没有,但依目前所知,UUID 也被命名为 GUID,所以……
Bitkeeper 也许有点先例(2000 年)?
这基本上不就是……梅克尔树(Merkle Tree)吗?它是 Git 和 Nix 等的底层存储架构?
https://en.wikipedia.org/wiki/Merkle_tree
只不过它不是 GUID,而只是二进制数据本身的哈希值,因为它是一个天然的密钥,不需要存储单独的映射,所以最终更有用
没错,1979 年发明的,也是加密区块链的核心数据结构
我以前从来没听说过梅克尔树,谢谢你的参考。
虽然我明白这怎么像 Git,但听起来更接近于 unison:
https://softwaremill.com/trying-out-unison-part-1-code-as-ha…
20 年后 🙂
嘿,Walter,你对 Git 有什么改进?
Git 还没有做到让哈希值成为下载文件(任何文件)的 URL,并确保它与你所想的一模一样,因为文件的哈希值必须与 URL 匹配。
目前的做法很随意,没有特别的组织性。
Git over ipfs 呢?
我相信这大概就是它要做的事 https://radicle.xyz/#:~:text=radicle%20is%20an%20open%20sour… 虽然显然使用的是自定义协议而非 ipfs 本身
所以你发明了 nix 😀
类似的,但我也把 rsync 或 rdiff 作为我心目中 VCS 模型的核心角色。
> 我开始使用 Git 是为了一件你可能想象不到的事情,就在它第一次提交后的几个月
我大概是在 2007 年左右开始使用 Git 的,因为当时我工作的公司使用 ClearCase,毫无疑问,这是我用过的最痛苦的版本管理器(尤其是在 Linux 工作站上运行它)。于是我写了几个脚本,让我把一个目录镜像到 Git 仓库,在 Git 中完成所有提交操作,然后把这些提交重放回 ClearCase。
我已经记不清 Git 是如何引起我注意的了,但到 2008 年底,我已经开始为 Git 本身贡献补丁了。Junio 是一位和蔼但严谨的维护者,我从他的管理中学到了很多关于开源的知识。我甚至还参加了早期的 GitTogethers。
在我的记忆中,我从未真正与 Git 纠缠过。我想这是因为我喜欢剖析事物的工作原理,而 Git 在表象之下其实很简单。因此,我在使用它那巴洛克式的 CLI 时从未遇到过太大的麻烦。
我的下一份工作是在一家基于 Chromium 分支的初创公司。当时,Chromium 使用的是 subversion。但在这家初创公司,我们使用的是 Git,我负责保持 Git 镜像的更新。我还得根据 Chromium 上游的改动重新调整我们的分叉,这项工作非常乏味。不过,我很擅长解决合并冲突。
Git 可能是我近二十年来使用最频繁的 CLI。我对 GitHub 成为 Git 的主要代码审查工具感到失望,但我永远不会对 Git 击败 Mercurial 感到失望,因为我总觉得 Mercurial 过于僵化,而且始终无法适应我的工作流程。
> 我大概是在 2007 年左右开始使用 Git 的,因为当时我工作的那家公司使用的是 ClearCase,毫无疑问,这是我用过的最痛苦的版本管理器
啊,ClearCase!最痛苦的是你的钱包!我看到了我的公司为这项特权按座位支付的价格–哎呀!
我本打算在 2014 年写一篇题为 “ClearCase 好在哪里 “的博文,我希望我写了,因为现在我已经忘记了大部分内容。
ClearCase 是一个糟糕的版本控制系统,我不会希望我最糟糕的敌人是它,但它确实有一些 Git 仍然不具备的优点。支持大型二进制文件、配置记录、winkin、视图。
随着各种大公司都在向巨型 monorepos 发展,而本地 Git 仓库只是超级集中仓库的一个视图,我认为他们会重新发明 ClearCase 的部分功能。
我在 2010 年左右写了第一行代码,第一次使用 Git 和 GH 是在……2013 年? 当我想起 Git 只有 20 年历史时,我有点吃惊。比如 GitHub,在我看来 20 年的历史不足为奇,但 2005 年之前就不存在的 Git 却总让我感到震惊。显然,在版本控制方面还有其他的替代方案(在某种程度上),但 Git 给人的感觉就像是一个永恒的工具,在文化中根深蒂固,以至于(对我来说)很难想象在后大型机时代,如果没有它,人们还能成为软件开发者。它给人的感觉就像是与 Vim、SSH 等工具诞生于同一时代(即 90 年代初)。这显然只是因为从我的编程意识开始的角度来看,它已经如此成熟和根深蒂固,但仍然如此。
除了 Git 之外,我从未使用过其他源码控制选项,有时我都怀疑自己是否还会使用!
更让我惊讶的是,与 Git 相比,Subversion 是如此年轻,它几乎不比 Git 年长。
我想我是在一个神奇的时刻开始软件开发的,那就是 Git 诞生之前,但在此之后,SVN 基本上无处不在,但与后起之秀 Git 相比,我更觉得它已经存在了很久。
我到了使用 RCS 的年纪。RCS 非常原始,而 CVS 很快就开始使用了。与这些相比,Git简直就是一股清流。
任何必须手动(全局)”签出”(锁定)文件才能编辑的版本控制系统都很糟糕,超过 3 个人就几乎无法使用。
没有浅分支(因此每个 “分支 “都会占用文件的全部拷贝/磁盘空间)的版本控制系统也很糟糕。
版本控制系统会导致数据库损坏(敬 Visual source safe 一杯酒)。
Subversion 设法在所有这些问题上做得更好,但它仍然没有充分解决分布式工作问题。
此外,人们在配置 SVN 时往往会选择重新添加全局锁,这也无济于事,因为他们不明白让两个人同时编辑同一个文件有什么好处。
我对 SVN 情有独钟。它比人们对它的赞誉要好得多,但 Git 在解决分布式(关键是断开/离线)工作流方面做得更好,让开发者可以忽略糟糕得多的用户体验,这在很大程度上偷走了它的风帆。
>此外,人们在配置 SVN 时往往会选择重新添加全局锁,这也无济于事,因为他们不明白让两个人同时编辑同一个文件有什么好处。
我认为,他们更多的是害怕有一天合并会变得不那么简单。一旦你有了实际经验,这种恐惧就会消失,真是不可思议。
(因为这个话题,我不得不查了一下。SVN 和 Git 的首次发布显然相隔了四年半。我想我第一次使用 SVN 和第一次使用 Git 大概相隔了 6 年)。
我仍在使用 RCS,通常用于管理文件,如 fstab 或 /etc 中的其他配置文件。
在文件上执行 `ci -l` 比 `cp fstab fstab.$(date +%Y%m%d.%H%M%S)` 更好、更快
要描述短短几年间发生的细枝末节的事情总是很难,但我觉得你说得太宽泛了。
维基百科告诉我,Subversion 的首次发布是在 2000 年底,而 Git 则是在 2005 年–虽然那几年正好是我上网、学习代码、开始接触自由和开放源码软件工作的头几年–但我认为那几年对 WWW 和 web 2.0 的转变非常重要。
我基本上不记得没有 SVN 的世界,但这可能是因为我错过了分界线,从 2002 年左右开始,项目和公司就开始从 CVS 迁移,因为模式非常相似,虽然不是直接插入,但也很合理。
至于 Git,我想说的是,它花的时间更长一些,而且去中心化的模式非常不同,以至于人们犹豫不决。在 2009 年的 Github 之前(我知道它成立于 2008 年,但我的用户 ID 还不到 50000,而且在那之前,它给人的感觉非常新,在非 Rails 圈子里一点也不普及),我觉得它其实有点小众–所以它更像是一个 7 年的跨度。当然,那时候我还生活在大学的泡沫中,为两家小公司工作,同时还是一名自由职业者。我认为更大型的自由和开放源码软件项目是在 2010/2011 年之后才开始成批迁移的。当然,我的时间轴也可能是错的:D
是啊,学习起来很奇怪。我记得在 Git 发布之初,我就开始接触源代码控制,玩过 CVS 和 SVN,与我当时学习的这些传统系统相比,它给我的感觉是如此 “现代 ”和 “新鲜”。
> 更让我惊讶的是,与 Git 相比,Subversion 是如此年轻,几乎不比它老。
Subversion 太糟糕了,必须尽快替换掉。
true. 此外,Subversion 是如此伟大,以至于它很快就取代了之前的替代品。
不对。CVS 还存在了一段时间。
还有比 Subversion 更糟糕的东西。VSS、ClearCase、一个用 Java 写的不起眼的商业软件,现在我忘了它的名字了。
Subversion 基本上就是一个更好的 CVS。在我的印象中,很多人都非常乐意转用 CVS 或 Subversion(即使是在 Windows 系统上),如果这意味着他们可以摆脱像 VSS 这样糟糕透顶的东西的话。而从 Subversion 转向 Git 或 Mercurial 更多是由于新工具的额外功能,而不是旧工具的问题。
了解一些历史背景非常有趣!感谢斯科特的分享。
一点小意见:
> 就我所知,这是版本控制中第一次使用 “rebase “一词
ClearCase(我曾不太喜欢用它)也一直在使用 “rebase “一词。在谷歌上搜索 “clearcase rebase before:2005”,可以找到 1999 年的 [0]。
(顺便说一句,在我工作的代码库中,ClearCase 的一次 rebase 要花上半个小时–那是在 2012 年;Git 的即时 rebase 让我大开眼界)。
[0] https://public.dhe.ibm.com/software/rational/docs/documentat…
拉得好。我一直在想这是不是真的。我很好奇莱纳斯是知道这件事还是自己编造的,或者两者都来自其他地方。我真的不知道。
> 他的本意是建立一个高效的 tar 包历史数据库工具集,而不是真正的版本控制系统。他以为别人会写这一层。
著名的遗言 “我们以后会用正确的方法来做!”
另一方面:当你打算做一个像这样的大型项目时,有意识地先专注于内部实用工具这一块往往是个好主意。举例来说,Pip 并不提供真正的 API;任何希望自己的项目动态安装 “额外 ”依赖项的人,都需要(https://stackoverflow.com/questions/12332975)将其作为子进程运行,并拥有自己的命令行。我猜想,如果当初从这个角度来设计 Pip,那么如今对它的维护会容易得多,这也是我在 Paper 中采用这种方法的原因。
是的,还是很奇怪,但我可以接受。
顺便说一句,我刚发现可以用 ssh 密钥签署提交。由于 pinentry + gnupg + Git 在 OpenBSD 上的提交签名有问题,我就改用 ssh 签名了。我曾有过一个变通办法,但很麻烦,现在已经没有问题了!
20 年了,我把工作项目从 cvs 移到 Git 就像昨天的事。我很怀念 cvs 中的一个项目($Id$),但我学会了不用它。
对了,SSH 签名真是不可思议。我也迁移到了它,而且没有回头。
有几点不同
– 可以在版本库内部的文件中指定签名密钥,并配置 Git 在合并时验证(https://github.com/wiktor-k/ssh-signing/)。我在我的点配置版本库中使用了这种方法,以确保只提取我在我的机器上提交的内容。
– SSH 通过 PKCS11 或外部代理支持 TPM 密钥,这样就可以轻松推出硬件支持的密钥
– SSH 签名具有上下文分离功能,也就是说,不可能将你的 SSH 提交签名用作其他用途(与 OpenPGP 不同)
– 由于 SSH 密钥较小,策略文件也较小且可读,比较一下 https://github.com/openssh/openssh-portable/blob/master/.git… 与同等的 OpenPGP https://gitlab.com/sequoia-pgp/sequoia/-/blob/main/openpgp-p…
哇,允许签名者的功能真酷。应该能与 sops 中的 ssh 密钥支持很好地搭配
我不知道你能跳过 Subversion 是幸运还是不幸
🙂
在职业生涯中,我从一无所有到 RCS,再到 CVS,最后到 Git。
在所有情况下,我都是坚持在我工作的小组中使用某种源代码控制的人。
因为不是管理员,我在服务器上设置了 RCS,后来又找到其他组允许我们使用他们的 CVS 实例。后来当 M/S 收购了 Github 后,公司就有了信仰,购买了 Git 的合同。
在一家财富 500 强公司,让员工使用任何 SC 都是一场噩梦。当我离开时,一位新员工看到了 SC 的好处,于是接替了我。)
在过去,丢失源代码的情况经常发生,我在那家公司工作时可没料到会发生这种事。
AFAIR 关键字 $Id$ 的替换包括版本号。这就是 Git 中的提交哈希值。由于显而易见的原因,你不能在正在计算哈希值的内容中插入哈希值。
您可以使用 “污点 ”和 “清除 ”过滤器将其扩展到磁盘上,然后在哈希计算运行前再次将其删除。
不过,我不认为你会想要使用 SHA,因为读取它有点毫无意义。你可能想把 ID 扩展为 `git describe SHA`,这样它就更像 `v1.0.1-4-ga691733dc`,这样你就能看到更类似版本号的东西了。
你或许可以在 Git 中设置 smudge 和 clean 过滤器,以类似 CVS 的方式进行关键字扩展。
感谢这篇有用的文章!除了很多有趣的信息,它还让我找到了这个包含 Git 内部介绍的 repo[1]。强烈推荐大家看看 [1] https://github.com/pluralsight/git-internals-pdf
啊对了。当 Peepcode 被收购时,Pluralsight 问我想如何处理我的版税,并同意我放弃版税,将内容开源,这真是一件很酷的事。
这也证明了 Git 的向后兼容性,即使过了 17 年,那本书的大部分内容仍然适用。
> 我很想写一整篇博文来介绍邮件列表协作是如何运作的,以及它的方方面面有多么酷,但那是下次的事了。
实际上,这正是我感兴趣的部分,来自 GitHub 的联合创始人。
我写完后,你会第一个知道。不过,GitHub 已经把邮件列表扼杀在了非常特殊的使用场景之外,使其成为了一种普遍可行的协作形式。不过,邮件列表是一种很酷、很独特的形式,它有很多优点,是 GitHub 基于公关的工作流程所不具备的。
到目前为止,我对 GitHub 拉取请求模型最大的不满是,它没有像 Gerrit 那样,将被压制的提交的最终提交信息(甚至是将基于目标进行重构的独立提交)视为审查流程的一部分。真不敢相信只有我对此感到不满!
如果您对此感兴趣,不妨试试我们最近为 GitButler 推出的基于补丁的审核系统:https://blog.gitbutler.com/gitbutlers-new-patch-based-code-r…
这看起来确实很有趣!我会仔细看看的。
你并不孤单。我自己也来自 Gerrit,我讨厌 GitHub 不允许对提交信息本身发表评论。Gitlab 也不允许。
此外,在 PR 中,我发现人们会直接切换到 “文件已更改”,而忽略了相关提交的顺序。
这种故意不强调提交信息和单个提交重要性的做法,导致代码库的 Git 历史质量下降。
在我用过的众多源码控制系统中,Git 的可用性最差,但它却是我的最爱
为什么?
不是你的父母。
直到用了 jj,我才明白什么叫 “Git cli 太烂了”。问题是,Git 是伟大的,但它也是随着时间的推移逐渐成长起来的,这就意味着它存在一定程度的不连贯。
此外,它还是一个漏洞百出的抽象概念,也就是说,有些命令只有在摸清底层模型的情况下才有意义。比如人们常抱怨 “Git checkout ”不只做一件事。其实不然。但前提是你得了解底层模型。如果从工作流程的角度考虑,就会觉得前后矛盾。因此,较新的命令(如 Git switch)是针对工作流程而非模型的。
此外,有些功能让人感觉是附加的。比如存储。这些都是伪提交,存在于真实提交图之外。作为一项功能,它与 Git 的其他功能整合得并不好。
重定向(rebasing)就是不断地重新应用 “Git am”。从 UNIX 的角度看这很优雅,但从可用性的角度看却很烦人。速度很慢,因为它需要通过文件系统来完成工作。它迫使你必须马上处理冲突,因为 Git 无法在其数据模型中模拟冲突。
基本上,Git 的底层模型很棒,但并不完美,它的 CLI 是成长出来的,而不是设计出来的。因此,它有一些奇怪的粗糙边缘。但这并不意味着它是个糟糕的工具。它是一个相当不错的工具。但在承认它有缺点的同时,你也可以说它是个好工具。
不仅如此,Git 的命名方式也非常不友好。
以 “索引 ”为例,它其实是个很有用的东西,只是名字不好听。大多数教程一开始都会解释索引是一个暂存区,您可以在上面制作您的提交。那为什么叫索引而不是暂存区呢?这个名字从一开始就糟透了。如果问 “索引 ”这个词在计算机科学中是什么意思,人们通常会想到数组中的索引,或者类似于搜索索引的东西,可以加快搜索速度。而 Git 的索引不具备这些功能。
有些人会告诉你,任何没有 “索引 ”的版本控制系统都不值得一用,因为它们不允许你编写漂亮的提交。正如 jj 和 hg 所说,这显然是错误的。这个有用的概念却被冠上了一个糟糕的名字,变成了人们无法理解的无定形的东西。
> 补丁和压缩包工作流算是第一个分布式版本控制系统–每个人都有一份本地拷贝,修改可以在本地进行,谁能把新的压缩包推送到服务器,谁就有 “合并 “的权限。
挑毛病,但这不是分布式工作流程。之所以说是分布式,是因为任何人都可以在本地运行补丁,并自行提供结果。当时就有众所周知的其他 Git 分支,比如安德鲁-莫顿(Andrew Morton)运行的 “mm ”树。
对于在集中式系统中长大的人来说,Git 的分布式特性是最令人困惑的地方之一。你的 “master ”和我的 “master ”是不同的,这让人很难接受。我不认为这是个巨大的心理飞跃,但太多人在没有人告诉他们的情况下就开始使用 Gitlab 等。
既然人们似乎已经遗忘了这一点,请记住 Git 诞生的原因是 Larry McVoy,他是 BitMover 的管理者,一直在向核心内核开发者捐赠 BitKeeper 的专利软件许可。
Larry 不久就告诉大家他不会继续免费提供 BK,于是 Linus 花了一个周末写了一个叫 Git 的蹩脚 “内容管理器”,在此基础上,也许他认为有人会写一个合适的 VC 系统。
,于是就有了现在的局面。
附带一提的是,有人用我见过的最聪明的后门黑进了 BitKeeper-CVS “镜像”(BK DAG 的线性近似值):https://blog。citp.princeton.edu/2013/10/09/the-linux-backdoo…
看看你是否能发现使其成为后门的小编辑:
if ((options == (__WCLONE|__WALL)) && (current->uid = 0)) retval = -EINVAL;
我想那是我第一次看到 Tridge 在会议上发表演讲,当时 ANU 的演讲厅里座无虚席。他描述了自己如何 “黑 ”进 BitKeeper,方法是通过 telnet 连接到服务器,并使用他所掌握的复杂黑客工具说服 Bitkeeper 透露其秘密,他输入了:
help
全场爆发出掌声和笑声。
包括 telnet 在内的每一个操作,包括输入 help、nc 命令,都是由观众建议的,特里杰尔以一句最简单的 “我们要如何找出……” 作为提示。
观众在 2 分钟内就黑掉了一个 bk 客户端。
这是我见过的最具破坏性的黑客攻击。莱纳斯后来说,那个 “Git ”根本不是特里吉尔,而是莱纳斯本人。
我想那是莱纳斯几年来唯一错过的一次 lca。
有时候我问自己,托瓦尔德对社会更大的贡献不会是 Git,而不是 Linux。
我们什么时候改用 SHA256?20 年后的今天,有些代码库肯定已经变得非常庞大了,
你担心来自不同对象的哈希碰撞吗?在 SHA-1 中,N 个不同对象发生碰撞的概率是(N 选 2)* 1 / 2^161。对于一万亿个对象来说,概率约为 1.7 x 10^-25。我认为我们可以放心地编写不会发生碰撞的代码,直到太阳变成超级新星。
我担心的不是这个,而是恶意文件可以被制作成特定的哈希值,从而取代原始文件。
20 年了!您觉得 Git 最近的哪些功能有用?我觉得我从来没用过任何不到 10 年的功能。我可能漏掉了什么
我不知道这是不是最近新增的功能,但我最近开始使用:“Git worktree ”非常棒。它能让你在不同的点上拥有多个版本库副本,而不用跳匿舞。
从某种程度上说,这是在重新引入其他源码控制系统强加给你的东西(你可以在 Scott 链接的关于使用 BitKeeper 的一个视频中看到这一点–第 4 集《Bits and Booze》,https://www.youtube.com/watch?v=MPFgOnACULU)。我以前使用的工具(SourceGear Vault、MS Team Foundation Services)要求你为每个分支建立单独的工作树–两者直接绑定在一起。如果你需要两个版本同时运行,这有时会很有用,但对于短期的主题分支,或者如你所说,同时处理多个主题,这可能会非常不方便。
起初,每个分支没有不同的工作目录,这让我很不习惯,但很快我就习惯了。多个分支在同一目录下工作意味着未跟踪的文件会保留下来,这对 IDE 工作区配置等事情很有帮助,因为这些配置是针对我和项目的,而不是针对分支的。
当然,你可以拥有多个版本库克隆,甚至是克隆的克隆,但从一个版本库向另一个版本库推送/拉取分支要比在不同的工作树中查看分支费事得多。
我现在的一般工作方式是将发布版本保存在自己的工作树中,而使用默认工作树(.git 目录所在)在主分支上进行开发。这意味着我在不同版本之间切换时,不需要重新同步我的外部依赖关系(例如 node_modules)。但我可以在任何工作树上看到我的分支以及远程分支上的所有内容。
Git 为什么 “战胜 ”了 mercurial?
因为 Github 比 Bitbucket 好?还是因为内核开发者的影响?
GitHub 比 Bitbucket 执行得更好。而且 Ruby 社区很早就采用了 GitHub。2010 年前后的比较说,GitHub 的用户体验和网络效应是选择 Git 的首要原因。而 Mecurial 的用户体验和 Windows 支持则是选择 Mercurial 的首要原因。
对我来说,GitHub胜出(10多年前)是因为出于某种原因,Git(一款深度面向Linux的软件)比Mercurial(标榜支持Windows)拥有更好的Windows支持。你甚至可以在 Git 中添加带有各种书写系统名称的文件。我不确定 Mercurial 现在能否做到这一点。
我可不这么认为。
Windows 上的 Mercurial 就是 “下载 tortoisehg,然后用它”,而 Git 没有一个好的图形用户界面,而且在行尾和分支名称大小写不敏感等问题上充满了争论。
现在,我在 Windows 和 Linux 上都使用 sublime merge,效果很好。这解决了图形用户界面的问题,不过行尾问题还是老样子(如果你记得在全局设置为 “不更改行尾 ”就没问题,但你必须记得这么做),而且我也不确定分支名是否区分大小写。
我很确定Mercurial会把任意文件名处理为UTF-8编码的字节字符串,过去是否有这个问题我不记得了,但如果现在有,我会非常惊讶。
编辑:看起来至少在过去是有这方面的问题的:
https://stackoverflow.com/questions/7256708/mercurial-proble…
虽然谷歌至少显示了一些关于 Git 类似问题的结果
在 2007 年评估 CVS 的后续版本时,Mozilla 选择了 Mercurial,因为它比 Git 有更好的 Windows 支持。18 年后的今天,Mozilla 正在从 Mercurial 迁移到 git。
几年前我就从 Hg 迁移到了 Git,只是因为 BitBucket 逼我出手,而且大多数托管 CI 工具都盯着放弃对 Mercurial 的支持。但相比 Git,我还是更喜欢 Hg。
不管怎样,我遇到过无法解码为 utf-8 的文件名。
> 因为 Github 比 Bitbucket 好?
Github 比 Bitbucke 更受欢迎,所以 Git 不幸胜出。
分散,但集中。
而我用了 10 年时间从头开始克隆 Git 仓库,而不是费心费力地学习大量陈旧的命令,只为修复一个坏掉的提交。
https://xkcd.com/1597/
而且,在不得不与 Git 打交道之前,我也不是没用过 RCS、SCCS、CVS、Clearcase、TFS、Subversion、Mercurial。
说到 Git 前端,我想给 Jujutsu 点个赞。我猜这里的大多数人现在至少都听说过它,但在我一年多的日常工作中,它已经完全取代了我对 Git cli 的使用。jj 提供的界面让我感觉底层的 Git 数据结构无比清晰,而且易于操作。
一旦你的思维模式从带有暂存区的工作分支过渡到持续跟踪变更的工作修订版,你就很难再想回到过去了。
GitButler 和 jj 作为项目,彼此非常友好,甚至还与 Gerrit 联手合作,共同研究 change-id 概念,说不定哪天还能让它上游化:https://lore.kernel.org/git/CAESOdVAspxUJKGAA58i0tvks4ZOfoGf…
这是令人兴奋的,趋同总是好的,但我不明白把跟踪信息放在 Git 提交头中,而不是放在目前的 Git 拖车[1]中,有什么价值。
在这两种情况下,它都只是工具可以提取的元数据。
编辑:话又说回来,我也曾因为拖车语义脆弱而导致用户出错,也许头文件更稳健?
[1] https://git-scm.com/docs/git-interpret-trailers
主要是因为对于想要与需要这些页脚的工具进行交互的用户来说,这很刺耳–而应用这些页脚的设置,比如 Gerrit 的 change-id 脚本–往往很烦人,比如支持 Windows 用户,但又不需要 bash 之类的东西。现在,我编写了 Gerrit 和 Jujutsu 的集成原型(虽然不是主线,但也有人在用),它能在你发送的任何提交信息中自动应用 Change-Id 拖车。这并不是世界上最糟糕的事情,只是代码上的一点小麻烦。
但请忽略这一切:我们真正想要的结果是,只需运行 “jj gerrit send ”即可,无需考虑其他任何事情,而且你还可以同样轻松地将变更拉回(TBD)。我永远都不会喜欢那种 “把所有提交都整理好之后,再用 Git 推送到一个特殊的远程,或者添加一些脚本来做这件事 ”的解决方案。这就是现在人们的做法,而且还不够好。人们讨厌这种做法,并会为此对你大加指责。他们会编造出无数个讨厌的理由,但这并不重要。但这并不重要,它应该开箱即用,并能达到你的预期。目前的设计已经做到了这一点,而改用 change-id 标头将使用户使用该功能更加顺畅,对我们来说也更容易实现,希望对其他人也有用。
我想,从大局来看,这只是一个小细节。但小细节对我们很重要。
谢谢你的解释!
你知道为什么 Jujutsu 创建了自己的 change-id 格式(反向十六进制),而不是像 Git 和 Gerrit 那样使用哈希值吗?
我不知道这是否是唯一或最初的原因,但选择反向十六进制的一个好处是,它意味着变更 ID 和提交 ID 有完全不同的字母(’0-9a-f’ 与 ‘z-k’),所以两者之间永远不会有模棱两可的重叠。
不过,Jujutsu 并不关心 ChangeId 的真正 “格式”。它 “实际上 ”只是任何任意的 Vec<u8> ,后端本身必须以某种方式定义并稍加描述;例如,示例后端有一个 64 字节的变更 ID。[1] 反向十六进制格式的重要性主要体现在模板语言中,用于向用户呈现事物。不过,你也可以用其他呈现方法来扩展它。
[1] https://github.com/jj-vcs/jj/blob/5dc9da3c2b8f502b4f93ab336b…
是的,这是为了避免两种 ID 之间的歧义。参见 https://github.com/jj-vcs/jj/pull/1238(参见各个提交)。
有意思的是,那是在我出现之前的短短几个月。)
我不是 Git 这方面的专家,但猜测一下:拖车键不是唯一的,即
完全没问题,但
不是。
我还听说有人在复制/粘贴提交信息时,把不该有的预告片也写进去了。
~我觉得更多的是,并非所有现有的 Git 命令(rebase、am、cherry-pick?)都会保留所有的预告头。~
忽略,误读上文
这是使用预告头的缺点,而不是使用预告头的理由。如果上游 Git 有改动来帮助解决这个问题,那就得让这些改动保留头文件。(不过cherry-pick在保留头文件和生成新头文件方面有很好的论据)
啊,对不起,我误读了您的评论(无论如何都应该提到cherry-pick的事情)。
这一切都很好!
jj 太棒了,我非常喜欢。它汲取了 hg 的精华,并将其应用于人们实际使用的版本控制系统!
我也赞同。不过要注意,你的 Git 老习惯可能会很难改掉。(不过它使用 Git 作为存储后端还是不错的)
你花了多长时间才熟练掌握?我猜您的组织使用 Git,而您在本地使用 jujitsu,作为上面的一层?
虽然不是父母辈,但对我来说,我花了几个小时阅读(jj 文档和 steve 的教程),又花了几个小时在测试仓库里玩,然后花了几周时间在实际项目中用它代替 Git,因为在实际项目中我的速度会慢一些。从那之后,一切都变好了。
在 Git 的基础上使用它,通过 Github 仓库与人协作,已经有 11 个月了。我比使用 Git 时效率更高,体验也更流畅。偶尔我也会遇到需要深入研究的问题,但 Discord 可以提供很好的帮助。我再也不想回到 Git 了。
是的,jj 就在 Git 上的共享仓库里(https://jj-vcs.github.io/jj/v0.27.0/git-compatibility/#co-lo……)。
如果你在推送到 Git 远程仓库时设置了明确的书签/分支名称,没人会知道你用的是 jj。
> 每隔一段时间……
表述为 “每隔一段时间”:)。
哎呀:)
(not your parent)
> 你花了多长时间才熟练掌握?
和任何事情一样,时间长短不一: 我听有些人说 “几个小时”,我也有朋友在熟练之前跳过两三次。
就我个人而言,我读过一些相关的书籍,但并没有真正坚持下来。有一个周六早上,我早早起床,决定真正试一试,结果一天下来基本都不错,一周后就完全放弃了 Git。
> 我猜组织使用的是 Git,而你在本地使用的是 jujitsu,作为上面的一层?
是的,在谷歌之外,基本上 100%都是这样。据我所知,Git 后台是唯一开源的。这一点最终会改变的…
其实没那么长。只要你弄明白
1. jj 是在修订版上运行的。每当你运行 jj CLI
2. 修订版是可变的,而且是在开始修改之前创建的(不像不可变的提交,是在完成之后创建的)
3. 你不是在一个必须 “提交 “的 “工作目录 “上工作,你只是在编辑最新的修订版
,一切都会变得很自然,很顺手。想要创建一个新版本,无论是合并、新分支,还是在其他版本之间插入一个版本?这就是 jj new。想移动/排序修订版本?这就是 jj rebase。想压制或分割修订版本?分别是 jj squash 和 jj split。更友好的冲突解决工作流程是一个很好的额外收获(不过,考虑到 jj 会自动重新排序,这更像是一种要求)
,一个明显不同的工作流程是没有 Git 意义上的分支,而习惯于主要引用单个修订,但在理解了上述内容之后,这样的工作流程就非常合理了。
我不太记得自己到底用了多久才感觉特别顺手。大概有几天吧?除了 Git 之外,我从来没有用过其他 VCS,所以也不知道不同的 VCS 有什么不同。我需要克服的最大障碍就是工作修订版和工作分支以及暂存区的区别,然后就开始了我的工作。
是的,我在本地使用 jj,同时使用远程 Git 仓库。
> 20 年前
> 2005
哇。
Git,还是 BitKeeper 的翻版。所有创新都是从闭源开始的。
> 所有创新都始于封闭源代码
我认为你需要查查你的历史。早期,在封闭/专有软件出现之前,程序员之间经常共享源代码。
让我们看看文本编辑器。它们并不是一开始就 “封闭源代码 ”的–但公司有一个团队来帮助销售他们的产品,即使这些产品比已有的产品差。
实际上,计算机一旦成熟,就是一个赚钱的机会。公司开始有偿销售产品。如果在有人意识到人们可以只为可执行文件付费之前就包含了源代码,我也不会感到惊讶。不包含源代码可以赚更多的钱,这样新的更新也可以收费。
(在这篇文章中,我们不要谈论 “最终用户许可协议”,好吧)
闭源软件的 “统治地位 “实际上是有钱的公司控制了现状,他们的律师和销售团队知道如何推波助澜。
如今,像微软这样的公司拥有巨额资金,他们主导着我们计算机系统的发展方向。他们将系统推向有利于自己的方向。他们有一只控制流的手,就像其他大公司有一只试图改变流的手,以达到他们的意图和目的。
这就是为什么无论你是爱他还是恨他,我都非常尊重理查德-斯托尔曼(Richard Stallman)或莱纳斯-托瓦尔兹(Linus Torvalds)等人的原因!
你想谈 “创新”?你认为这些 “封闭源代码创新 ”是用什么构建的?软件是使用 Python、C++、C、Javascript 等编程语言创建的……绝大多数都可以在某种开源社区下免费使用!
让我们再看看大公司的总体情况……其中许多公司并没有创新……它们只是在收购那些试图做新事情的小公司……不管是不是封闭源代码软件。
最后,让我们坦诚地认识到,创新不是凭空产生的,任何事物的灵感都来自于以前的事物,无论是失败的实验还是已经成功的事物。新的想法会伴随着更多的失败而产生,但也会产生更多的想法,直到最终获得成功!
Linux 可能受到其他操作系统的启发,而这些操作系统又受到其他事物的启发,等等。创新是循序渐进的。我想说的是,如果任何公司发现有机会建立一个封闭源代码版本来关闭一个流行的开源等价物…… 他们就会这么做!
我们应该对贪婪的 BitKeeper VCS 所有者说声谢谢,他们想让 Linus Torvalds 给他们钱,让他们在系统中保留 Linux 源代码。他们成功地激怒了莱纳斯,于是他坐下来创建了 Git。
我认为,我们已经发展并习惯的 Git 使用模式已被证明无法满足人工智能优先的开发需求。也许引擎盖下还是 Git,但 DX 需要大刀阔斧地改革。
什么意思?
比如我在 Cursor 中工作,理想情况下需要存储整个聊天记录和每次提示后的修改建议。这并不适合当前的 Git 开发模式。我不想再输入提交信息。我们需要一个更自然的分支模型,我不想再创建分支并在它们之间切换、合并。
你应该请大语言模型(LLM)创建一个 VCS,让你不用做这些你不想做的事情。