因难用饱受诟病,Git十年忠实用户:问题不在工具,在使用者

Mathew Duggan 2024-06-02 11:29:00
 
 

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 - filename。Git 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 工程师都告诉我,他们非常怀念自己的 VCS。Sapling 就是这样一个团队,它让我们能在一个更以 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 handlero  2652cf416  17 seconds ago  mathew.duggan│  feat: adding autho  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 handlero  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.md

 

  • EdenFS

https://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 需要以一种更易于使用的方式发布更多这些组件。

 

Scalar

 

Sapling 是 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 作为现有平台。这是目前最好的选择,虽然已经很接近我的目标,但仍无法达到。

 

你可以亲自试试:https://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

 

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 - filename。Git 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 工程师都告诉我,他们非常怀念自己的 VCS。Sapling 就是这样一个团队,它让我们能在一个更以 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 handlero  2652cf416  17 seconds ago  mathew.duggan│  feat: adding autho  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 handlero  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.md

 

  • EdenFS

https://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 需要以一种更易于使用的方式发布更多这些组件。

 

Scalar

 

Sapling 是 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 作为现有平台。这是目前最好的选择,虽然已经很接近我的目标,但仍无法达到。

 

你可以亲自试试:https://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
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告