Git 是由 Linux 之父 Linus Torvalds 开发的一个开源分布式版本控制系统,其广受开发者青睐的同时,又因“难用”饱受诟病,成为让人又爱又恨的存在本文作者 Mathew Duggan 是 Git 长达十年的忠实用户,他一边吐槽 Git 存在许多不足,不断试用新产品寻找替代方案,一边却无奈地发现自己仍在使用Git 为什么难以替代?让我们跟随作者,在不同的版本控制系统(VCS,version control system)的优劣对比中寻找答案吧~我全职使用 Git 将近十年了我每天都在使用它,主要依靠命令行版本(针对Git使用,)我读过书、听过讲座、练习实践过,总之,我用 Git 有效地完成了工作为了保持良好的工作状态,我还在新的软件仓库中安装了一些定制的 Git 钩子仅从曝光效应(译者注:Exposure Effect,人们偏好自己熟悉的事物,只要某件事经常出现,就能增加人们的喜欢程度)的角度来说,我应该喜欢这个使用十年的工具,但我并不喜欢我不总是能“控制”Git 的工作,有时命令会导致意想不到的操作,这些操作与 Git 的工作方式一致,但与我偏好的工作方式不符相反,我需要在脑子里设想很多东西,才能让它完成我想要的工作“好吧,我想把未暂存的编辑内容移到一个新的分支如果该分支不存在,我想使用 checkout,但如果它存在,我就需要 stash、checkout,然后再 stash pop”“如果现在问题是,我在错误的分支上做了修改,我需要stash apply 而不是 stash pop”我需要引入一些跨版本的依赖关系,使用submodules还是subtree?在工作中,我需要深刻理解reset、revert、checkout、clone、pull、fetch、cherrypick之间的区别,尽管这些词在英语中的含义相同你需要记住,push和pull并不像它们的名字含义那样对立说到merge,你需要考虑清楚什么情况下需要比较rebase、merge、merge --squash的逻辑Merge的方向是什么?糟糕,我不小心删除了一个文件我得记住 Git rev-list -n 1 HEAD - filenameGit reset --hard HEAD~1 可以纠正我的错误,我也得记住使用 --hard 时的具体作用,并确保传递的flag是正确的要记住这些操作,却没有人认为这不可能,而且很明显,Git 对全世界数百万人都大有用处但我们能不能诚实地承认,以上操作对我几乎每次工作都要用到的流程(如下所示)来说,实在是大材小用了:创建分支将分支推送到远程在分支上开展工作,然后提交拉取请求合并 PR,通常是压扁后合并,因为这样更容易阅读让 CI/CD 做它该做的事我从未通过电子邮件发送过补丁,也从未从本地副本中还原过 repo我不会花几周时间离线工作,只为尝试合并一个巨大的分支我们不会让版本库的容量超过 1-2 GB,因为这样一来,我需要修改三个文件并提交一份报告,同时它们会变得难以处理典型的工作流程都无法从 Git 的复杂性中获益更具体地说,Git 不能离线工作它依赖于合并控制,而这种控制甚至不是 Git 和拉取请求的一部分当我合并提交(squash)时,大部分的分布式历史记录都会被丢弃我的本地磁盘上堆满了过时的版本,以至于我不得不在开始工作前进行更新,但这种操作对我只是浪费时间现在有人提出“我不喜欢 Git 的工作方式”,这有点像从新颖的角度抱怨 PHP让我来阐述一下我心目中的完美 VCS(version control system,版本控制系统),并探讨市面上的VCS能否满足这些需求Gitlite要取代 Git,我认为 VCS 需要增加(或减少)一些功能,满足日常 95% 的使用需求抛弃分散模式我以及所有 Git 用户使用大量的软件仓库,无论如何,我都需要经常访问服务器才能完成工作去中心化的复杂性并不划算,我宁愿做完下一部分就丢弃它如果 GitHub 今天宕机了,无论如何也无法部署,那还不如把服务器要求当成一种额外福利将大量工作转移到服务器端,按需进行如果我需要在一个版本库中搜索某个内容,与其从版本库中复制所有内容,在本地搜索到可能已经过时的信息,不如在服务器上运行搜索,按需获取我想的文件,而不是复制所有文件我需要大版本库,但不想把所有文件都拷贝到磁盘上只在需要的时候提供给我对应文件,然后将剩下文件留存为什么我只要用 3 个文件,却要不停下载数百个文件?拉取请求是第一类公民(译者注:First-class Citizen,支持其他实体所有操作的实体)我们了解“分支”的概念,也秉持“分支在合并前必须通过检查”的理念我提倡将其变成 CLI 流程的一部分如果能在同一个工具中要求服务器“空运行(dry-run)”一个 PR 检查,查看我的分支是否通过,那该有多好?想象一下,在不同的 Kubernetes 托管服务提供商中使用 gh CLI 的功能,而不使其针对特定平台,就像使用 kubectl 一样认可并简化跨版本依赖的理念子模块(submodules)的工作方式并不尽如人意,子树(subtree)稍微好点,但将工作推回上游依赖关系会让人产生困惑与误解相反,我想要的是:https://Gitmodules.com/如果我从远程服务器拉取内容,我的服务器会与远程服务器保持同步,但我也可以选择将版本固定在我的版本库中如果我有权限,我在版本库中的更改会转到远程依赖库中如果出现冲突,则通过 PR 来解决内置更好的可视化工具用户可通过浏览器或其他工具,更直观地了解他们正在查看的内容很多人在使用 Git 时都会使用 CLI + 图形用户界面工具来实现这个功能,我们可以把这个操作合并至一个步骤更易于集中管理提交信息和规则没错,我可以使用一堆 Git 钩子,但如果能够在克隆 repo 时就被全面检查就更好了,由此可以确保自己的做法正确,以免被 CI 插件或提交信息格式检查器发现错误,浪费大量时间我还希望能有一些提示,例如“嘿,这个分支越来越大了”或“每个提交都必须是 fix/feat/docs/style/test/ci”等Read replica概念我很希望能将我的 CI/CD 系统指向一个只读副本集(Read replica box),并为实际用户保留我的主 VCS box主服务器触发一个 webhook,该 webhook 会触发一个带有标签(tag)的项目构建(build),然后点击只读副本,如果只读副本没有该标签,它就会从主服务器中提取最好能建立某种主/副模型,可以在配置中同时设置主服务器和副服务器,即使主服务器(云提供商)宕机,也能继续将内容推送到有备份的地方因此,我尝试了一些竞品,看看“有没有系统能在这些部分(比Git)设计得更好”2024 年的 SVN我第一次接触版本控制是 SVN(Subversion),当时的说法是“在工作满一年之前不要尝试创建分支”不过,作为一名新手,SVN 的工作非常简单,因为它功能并不多Add、delete、copy、move、mkdir、status、diff、update、commit、log、revert、update -r、co -r 几乎就是你所需要的所有命令Subversion 有一个非常简单的工作原理模型,即“我们把东西复制到文件服务器上,然后在你要求时再传回你的笔记本电脑”,这也有助于新用户入门但不得不说,SVN 比我过往记忆中的体验要好得多产品存在的“粗糙边缘”似乎都被打磨掉了,我没有再遇到之前的问题,为 Subversion 团队的出色工作点个大大的赞Subversion 基础知识实际上,Subversion 客户端的基本功能是将所有文件作为单个原子事务提交到中央服务器无论何时,它都会为整个项目创建一个新版本,称为修订版这不是哈希值,只是一个从零开始的数字,所以新用户不会混淆“新版本”和“旧版本”这些数字是全局数字(global number),与文件无关,因此是world的状态每个文件都有 4 种状态:本地未修改 + 当前远程:保持不变本地已更改 + 当前远程:要发布更改,您需要将其提交,更新将不起任何作用本地未修改 + 远程已过期:SVN update 会将最新副本合并到工作副本中本地已更改 + 远程已过期:SVN commit 将不起作用,SVN update 会尝试解决问题,但如果无法解决,用户就要自行解决要“破坏”SVN几乎是不可能的,因为向上推送并不意味着向下拉取这意味着不同的文件和目录可以设置为不同的版本,但只有运行 SVN update 时,整个world才会自动更新到最新版本使用 SVN 的工作流程如下:确保已联网运行 SVN update,将工作副本升级到最新版本进行所需的修改,切记不要使用操作系统工具来移动或删除文件,而应使用 SVN copy 和 SVN move,这样它就会知道这些更改运行 SVN diff,确保你已经做了想做的任务再次运行 SVN update,用 SVN resolve 解决冲突感觉不错?点击 SVN commit 就大功告成了那为什么 SVN 会被抛弃呢?一个原因:分支(branches)SVN 分支在 SVN 中,分支其实就是把一个目录粘贴到正在工作的地方通常情况下,你会把它作为一个远程拷贝,然后开始工作,所以看起来更像是把 URL 路径复制到一个新的 URL 路径但对用户来说,它们看起来就像你创建的版本库中的普通目录在 SVN 1.4 之前,合并一个分支起码需得要交给硕士学历的员工,但他们增加了一个 SVN merge,简化了合并实际上,你可以使用 SVN merge 来与主分支保持同步,然后当你准备就绪时,运行 SVN merge --reintegrate 来将分支推送到主分支然后,你就可以删除该分支,但如果需要读取日志,该分支的 URL 将始终有效这一特性在票据系统特别有效,因为在票据系统中,URL 只是票据编号不过,你也没必要永远用随机目录把事情搞得一团糟总之,SVN 分支以前的很多问题现在都不存在了那么,SVN还存在什么问题?涉及到自动化功能,SVN 在我看来是失败的,用户只能亲自动手虽然你可以对 repo 的不同部分进行细致入微的访问控制,但在实践中并不常见如果没有某种额外的控制或检查,你就无法阻止他人合并分支即使你增加了项目人员,也不太会有人更新这一功能,SVN 服务器依旧负担沉重此外,用户界面已经过时,整个工具生态系统也因为用户的离开而开始腐化我不知道现在还能否成功推荐别人从 Git 转向使用 SVN,但我确实认为SVN有很多好的想法,能更接近我想要的工作方式SVN只是在网络方面需要大量的 UI/UX 投入,才能让我喜欢用它而不是 Git但我认为,如果有人对这项工作感兴趣,Subversion 的基本架构还是不错的Sapling与我共事过的每一位前 Meta 工程师都告诉我,他们非常怀念自己的 VCSSapling 就是这样一个团队,它让我们能在一个更以 GitHub 为中心的世界里玩转功能几个月来,我一直在使用它Sapling来处理我的工作我真的爱上了Sapling,它是为了易于理解而设计的,使用起来令人心情愉快Sapling和Git很多东西都是一样的用 sl clone 克隆,用 sl status 检查状态,用 sl commit 提交最明显的不同之处在于堆栈的概念和 smartlog 的概念堆栈是“提交的集合”,这一概念的含义是,可以通过命令行使用 sl pr submit 为这些变更发布 PR,每个 GitHub PR 都是其中一个提交这种视图(显然)既杂乱又恼人,所以还有另一种工具可以帮助你正确查看变更,那就是 ReviewStack除非我向你展示我在说什么,否则这一切都毫无意义我新建了一个 repo,并向其中添加文件首先,我检查状态:
❯ sl st? Dockerfile? all_functions.py? get-roles.sh? gunicorn.sh? main.py? requirements.in? requirements.txt
然后添加文件:
sl add .adding Dockerfileadding all_functions.pyadding get-roles.shadding gunicorn.shadding main.pyadding requirements.inadding requirements.txt
如果我想在本地运行一个更漂亮的网页用户界面,我会运行 sl web 并得到这个界面:所以我把所有这些文件都添加到了初始提交中,很好,让我们继续添加:
❯ sl@ 5a23c603a 4 seconds ago mathew.duggan│ feat: adding the exceptions handler│o 2652cf416 17 seconds ago mathew.duggan│ feat: adding auth│o 2f5b8ee0c 9 minutes ago mathew.duggan Initial Commit
现在,如果我想浏览这个堆栈,只需使用 sl prev 即可上下移动堆栈:
sl prev 10 files updated, 0 files merged, 1 files removed, 0 files unresolved[2f5b8e] Initial Commit
这也体现在我的 sl 输出中:
❯ slo 5a23c603a 108 seconds ago mathew.duggan│ feat: adding the exceptions handler│o 2652cf416 2 minutes ago mathew.duggan│ feat: adding auth│@ 2f5b8ee0c 11 minutes ago mathew.duggan Initial Commit
这也显示在我的本地网络用户界面上:最后,流程以创建拉取请求的 sl pr 结束它们是 GitHub 的拉取请求,但它们看起来与普通的 GitHub 拉取请求不同,你也不会以相同的方式,而是使用ReviewStack进行审查我为什么喜欢它?Sapling符合我对 VCS 的期望,它更容易察觉到正在进行的工作,其设计旨在方便大型团队合作,同时以更合理的方式提供所需信息命令对我来说更有意义,所有操作都能完成更具体地说,我喜欢它抛弃分支的概念我所拥有的是一系列从开发主线分叉出来的提交,但我并没有想要命名的明确内容,所以我要求添加这些提交我想要的是在主线上添加一堆提交,然后由专人查看这些提交集合,确保其合理性,并对其进行自动检查所以“分支”概念对我毫无用处,最后被我删除了我还喜欢Sapling易于撤销工作的特性,对我来说,uncommit、unamend、unhide 和 undo更加好用,而且几乎总能达到预期效果取消暂存区域,将重点放在易于使用的命令上,这样的设计更符合逻辑为什么不应该切换?既然我这么喜欢Sapling,那还有什么问题呢?为了让Sapling达到我真正想要的效果,我需要运行更多的 Meta相关组件(译者注:Sapling 是 Meta 开发和使用的源代码控制系统)Sapling在 GitHub 上运行得很好,但我最想得到的是:Mononoke:Sapling的服务器端组件https://Github.com/facebook/sapling/blob/main/eden/mononoke/README.mdEdenFShttps://Github.com/facebook/sapling/blob/main/eden/fs/docs/Overview.md以上组件基本囊括了Sapling的所有优点,如下所述:按需获取历史文件(remotefilelog,2013)文件系统监控器,以更快地掌握工作副本状态(watchman,2014)回存稀疏配置文件以缩小工作副本(2015)限制引用交换(选择性拉取,2016)按需获取历史树(2017)增量更新工作副本状态(treestate,2017)用于推送吞吐量和更快索引的新服务器基础设施(Mononoke,2017)虚拟化工作副本,可按需获取当前已签出的文件或树(EdenFS,2018)更快的提交图算法(分段更新日志,2020)按需获取提交(2021)我很想尝试将所有这些优点结合在一起(由于其中很多都有源代码,我正在努力尝试启动),但到目前为止,我还无法复现完整的Sapling体验以上所有特点都吸引着人选择过渡到 Sapling,但如果没有这些特点,我就真的要在 GitHub 上添加很多自定义工作流了我想我可以把 GitHub 整体迁移到其他地方,但 Meta 需要以一种更易于使用的方式发布更多这些组件ScalarSapling 是 GitHub 其中一个很好的技术栈,但(实际上)我不会将一个团队迁移到 Sapling 上,除非 Facebook(现Meta) 决定从头到尾发布整个软件包我能让 Git 按照我想要的方式工作吗?或者至少让管理所有文件不那么麻烦?微软有一款工具可以做到这一点,那就是 VFS for Git,但它只适用于 Windows,所以对我来说毫无用处不过他们也提供了一款名为 Scalar 的跨平台工具,旨在“实现大规模的大型仓库管理”它最初是微软的一项技术,最终被转移到了 Git 身上,也许它能实现我想要的功能Scalar的作用是,有效设置所有最现代的 Git 选项,以便在大型仓库中使用以上内容包括,内置的文件系统监视器、多包索引、提交图、计划后台维护、部分克隆和克隆模式稀疏检出以上这些都是什么?文件系统监视器是 FSMonitor,它是一个从操作系统跟踪文件和目录变化并将其加入队列的守护进程这就意味着 Git status 不需要查询 repo 中的每个文件就能发现改动把带有 pack 文件的 Git pack 目录拆分成多个文档中的提交图:“commit-graph 文件存储了提交图结构以及一些额外的元数据,以加快图的走行速度通过按词典顺序列出提交 OID,我们可以为每个提交确定一个整数位置,并使用这些整数位置来引用提交的父节点我们使用二进制搜索查找初始提交,然后在过程中使用整数位置进行快速查找”最后是克隆模式 sparse-checkout这允许人们将工作目录限制为特定文件这个工具的目的是创建一种处理大型单核项目的简便模式,着眼于实际上是微服务集合的单核项目好吧,但它能满足我的需求吗?我为什么喜欢它?Scalar已经内置在 Git 中,这很好,便于用户上手并使用此外,它还能实现我想要的功能,把一堆现有的 repo 合并成一个巨大的 monorepo,性能出奇地好稀疏签出意味着我可以指定哪些是需要的,哪些是不需要的,还解决了“如果我有一个巨大的二进制文件目录,但我不想让别人担心怎么办”的问题,因为它采用了与 .Gitignore 相同的模式匹配但Scalar并不能从根本上改变 Git 的本质使用这些默认设置,你可以把仓库扩大很多,但它仍然需要在本地处理很多事情,而且需要人工处理不过我想说,Scalar让我少了很多抱怨结合用于 PR 的 gh CLI 工具,我能够拼凑出一个相当满意的工作流程因此,虽然这肯定是我以后要采用的模式(充满微服务的 monorepo,我能够用标量来管理规模),但我认为它代表了你能在多大程度上修改 Git 作为现有平台这是目前最好的选择,虽然已经很接近我的目标,但仍无法达到你可以亲自试试:Git-scm.com/docs/scalar结论所以我们应该怎么选择VCS?老实说,我可以就这个问题再写上 5000 字在这一领域,我们总感觉自己离破解这个秘密越来越近,然后又放弃了,因为我们找到的解决方案基本上已经足够好了随着工作流程的不断发展,我们再也没有回过头来触碰应用程序设计的“第三条轨道”(新方案)为什么呢?我认为,人们之所以对 Git 不满意,是因为他们不了解它这种状况让人感觉,如果你不喜欢这个工具,那么问题就出在你身上,而不是工具我还认为,程序员之所以喜欢分散式设计,是因为它(在某种程度上)鼓励了对可移植性的虚假希望是的,我完全依赖于 GitHub 的操作(包括Pull Requests、GitHub 访问控制、SSO、secrets 和releases),但在紧要关头,我可以将实际的 repo 本身转移到另一个提供商那里我非常希望有人能再次着手解决这个问题我觉得我们的工作还没有完成,而且从对所有这些问题的研究来看,似乎有很多低垂的优化果实可供任何人摘取我认为主要的障碍是你需要离开 Git,迁移到一个完全不同的结构,这对我们来说可能太难了不过,我始终期待这个问题能够被解决作者丨Mathew Duggan 编译丨onehunnit来源丨matduggan.com/why-dont-i-like-git-more/dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
0 评论