Beagle:Git、URI 及那些令人头疼的术语
Git 的基础模型是一个极其简单的系统,由对象树(blob trees)和提交链(commit chains)构成,只需 5 分钟就能向任何人解释清楚。然而,在更高级的应用层面,这种美妙的简单性却演变成了一堆命令和标志的混乱组合,即使是有 20 年 Git 使用经验的开发者也难以记住。
当与大语言模型(LLMs)进行多任务操作时,情况更是如此。像“我记得我们周二实现了这个功能,但现在却找不到了,它在哪里呢?”“哪个分支对应那个远程仓库?”等问题层出不穷。
要是有一种通用语言来定位和访问本地及远程资源、文件以及文件中的位置就好了。而 HTTP 和 URI 是非常标准的解决方案,这些技术正是为此任务而设计的,并且在众多应用和库中都得到了支持。那么,能否将其应用到 Git 中呢?
1. URI
我们都耳熟能详的 URI 布局如下:1. `scheme:` —— 访问协议/寻址方案;2. `//authority` —— 通常是网络主机;3. `/path` —— 远程文件系统中的路径;4. `?query` —— 其他内容(如参数);5. `#fragment` —— 文档内的位置。
能否将其应用到版本化存储中呢?如果将所有版本信息都放入查询部分,那么其余部分就很清晰了。例如,`http://somehost/dir/file?branch#L101`。实际上,Beagle 就是一个兼容 Git 的源代码管理(SCM)工具,它正是这样做的。
2. HTTP 动词
HTTP 的情况更有趣。最初,HTTP 有一套动词词汇:[HEAD、GET、PUT、POST、PATCH、DELETE]。不过,如今人们通常只使用 GET 和 POST。但其他动词的存在是有其原因的。
- GET:“检索信息”;- HEAD:类似于 GET,但不返回响应体;- POST:让服务器“接受实体”;- PUT:请求将实体“存储”;- DELETE:顾名思义,执行删除操作;- [PATCH]:请求“应用”更改。
虽然这些词汇的含义有些模糊,但从根本上说,它们源于访问远程文件系统的需求。这与 Git 模型非常契合,正如前面所述,Git 模型是一个基于内容寻址的文件系统。因此,Beagle 仅使用 HTTP 动词。
等等,它只有 patch 吗?那合并(merge)和变基(rebase)呢?
3. Git 的复杂操作
在处理 Git 中错综复杂的编辑历史时,合并(merge)、变基(rebase)、压缩(squash)、挑选(cherry - pick)以及所有相关技术总是让人困惑不已。每个命令通常会执行多个不相关的操作,而且同一个操作也可以通过多个命令以微妙的不同方式完成。
Beagle 将这些操作分解为一组正交操作,基于 Git 那极其简单的底层模型:- GET:将数据从仓库移动到工作区(包括远程仓库);- HEAD:类似于 GET 的预运行,获取并报告信息;- POST:将数据从工作区移动到仓库(提交);- PUT:仅编辑引用日志(设置分支/标签、暂存);- DELETE:类似于 PUT,但执行删除操作;- PATCH:将另一个版本的更改应用到工作区。
可以看出,这些操作之间无法相互补充,它们是严格正交的。让我们看看这如何应用于合并/变基/压缩/挑选的混乱场景。
所有 Git 合并变体的操作如下:1. 应用来自分叉提交或分支的更改;2. 重用(变基)或添加新的(合并、压缩)提交信息;3. 引用(合并)或不引用(变基、压缩)原始提交。
因此,我们有 8 种选择:提交/分支、重用/重命名、引用/忽略。实际上,这 8 种情况中只有部分在 Git 中有对应的术语。例如,要进行压缩操作,我们需要应用整个分叉分支,添加新的提交信息,并且不引用原始分支。要进行变基操作,我们需要分别应用每个提交,重用提交信息,并且不引用原始提交。要进行合并操作,我们需要应用整个分支,添加新的提交信息,并引用原始提交(父提交头)。
在 Beagle 命令行界面(CLI)中的表达方式如下:
# 变基一个提交:应用更改,提交
be patch?feature
be post #!
# 合并一个分支:应用所有更改,使用新信息提交
be patch?feature!
be post '#merge the feature'
# 压缩一个分支
be patch?feature!
be post '#add a new feature!'
# 变基整个分支
while be patch?feature; do
make && make test && be post #!;
done
# 挑选一个提交
be patch #391a0d33
be post #!
这里我们使用感叹号修饰符来实现以下功能:1. `?branch!`:应用整个分支(默认:一个提交);2. `#message!`:不链接原始提交(父引用)。
注意:当我们不提供提交信息时,将重用原始信息。我们可以保留信息/作者,但删除原始提交:`#!`。这里的分支变基只能通过循环实现,因为我们需要为每个提交进行一次提交操作。这也确保了所有提交的版本都能成功构建并通过测试。
4. 常见问题解答
那么,PUT 和 POST 有什么区别?
POST 用于提交和/或快进操作。PUT 用于重置分支或标记文件以进行提交/删除(仅涉及引用日志的操作)。
这与 Git 使用的 URI 有何不同?
Git 仅使用 URI 来访问仓库,例如 `git://github.com/gritzko/beagle.git`。这种方式非常受限,因此我们希望扩展这种寻址方案,以访问文件、版本和文件中的位置。
这与 GitHub 的 URI 有何不同?
GitHub 的 URI 具有典型的 Web 应用结构,这在我们的场景中不太方便。`https://github.com/gritzko/beagle/blob/main/keeper/README.md`
特别是,Beagle 的 URI 将所有版本信息正交化到查询部分,以避免在路径中过度使用(项目、用户、分支、路径)。Beagle 分支采用类似文件系统的树形结构,顶级条目是项目主干,因此上述 GitHub URI 变为:`be://replicated.live/keeper/README.md?/beagle`。
请注意,Beagle 仓库可以托管任意数量的项目,默认的传达项目的方式是通过查询。如果我们想查看某个分支,URI 变为:`be://replicated.live/keeper/README.md?/beagle/MEM - issues`