2 个版本
0.1.1 | 2021年1月4日 |
---|---|
0.1.0 | 2021年1月3日 |
#2194 in 开发工具
500KB
12K SLoC
Jujube
免责声明
这不是一个谷歌产品。这是一个实验性的版本控制系统(VCS)。它尚未准备好使用。这是由我,Martin von Zweigbergk([email protected])编写的。这是我个人的爱好项目。它并不代表谷歌的任何承诺或方向。
简介
我开始这个项目主要是为了测试一些用户体验想法的可行性。我继续使用它来做到这一点,但我的短期目标是让它作为一个 Git 仓库的替代 CLI 有用。
存储设计类似于 Git,因为它存储提交、树和 blob。然而,blob 实际上分为三种类型:普通文件、符号链接(Unicode 路径)和冲突(后面会详细介绍)。
目前的命令行工具称为 jj
,因为它容易输入且容易替换(在英语中很少见)。该项目被称为“Jujube”(一种水果),因为这是我第一个能想到与“jj”匹配的单词。
功能
以下小节描述了当前的功能。文本面向已经熟悉其他 VCS 的读者。
兼容 Git
该工具目前有两个后端。一个称为“本地存储”,非常简单且效率低下。另一个后端使用 Git 仓库作为存储。提交以常规 Git 提交的形式存储。可以从现有 Git 仓库中读取和写入提交。这使得创建一个 Jujube 仓库并使用它作为 Git 仓库的替代接口成为可能(它将像额外的 Git 工作树一样由 Git 仓库支持)。
作为库编写
仓库由两个主要部分组成:lib crate 和 main(CLI)crate。大部分代码位于 lib crate 中。lib crate 不会向终端打印任何内容。独立的 lib crate 应该使添加 GUI 相对直接。
操作以仓库为首要
几乎所有操作都是首先在仓库中执行,然后可能反映在工作副本中。迄今为止的唯一例外是在提交工作副本时,它自然使用工作副本作为输入。
这使得操作更快,因为工作副本不需要更新。这也意味着工作副本不会看到虚假的更改,例如在变基操作期间。在某个操作运行时更新工作副本是安全的。
支持Evolution
Jujube从Mercurial复制了Evolution功能。它跟踪提交何时被重写。除了通常的父提交列表外,提交还有一个前辈列表。这允许工具在提交被重写(修正、变基等)时确定将子提交变基到何处。更多信息请参见https://www.mercurial-scm.org/wiki/ChangesetEvolution。
工作副本是一个提交
当您与工具交互时,工作副本会自动提交。这简化了实现和UX。这也意味着工作副本经常备份。
当您检出另一个提交时,工作副本的任何更改都会保留原位。这与Git和Mercurial不同,但我认为这对新用户来说更直观。要复制Git/Mercurial的默认行为,请使用jj rebase -r @ -d <destination>
(@
是工作副本提交的名称)。不需要暂存/恢复。
由于相同的命令可以操作仓库或另一个提交,因此命令更加一致。例如,jj log
包括工作副本(类似于gitk
和其他工具包含工作副本节点)。jj squash
将提交压缩到其父提交,包括工作副本(类似于git commit --amend
或hg amend
)。
在“提交”之前可以向工作副本添加提交描述。相同的命令(jj describe
)用于更改任何提交的描述。
提交可以包含冲突
当发生合并冲突时,它作为特殊冲突对象记录在树对象中(而不是带有冲突标记的文件对象)。冲突存储为要添加的状态列表和要删除的状态列表。常规的3路合并添加[B,C]并删除[A](共同祖先)。修改/删除冲突添加[B]并删除[A]。添加/添加冲突添加[B,C]。N个提交的八叉合并添加N个状态并删除N-1个状态。非冲突状态A等同于仅添加[A]的冲突状态。这里的“状态”可以是普通文件、符号链接或树。这种对树内冲突的支持对实现和UX都有一些有趣的影响。
这意味着有解决冲突的一致方法:检出包含冲突的提交,解决冲突,并将它们修正到冲突提交中。然后演变子提交。
它自然地启用了协作冲突解决。
树内冲突意味着在变基类似命令中不需要进行记录以支持继续/中止操作。相反,变基可以简单地继续并创建所需的新DAG形状。
通过在“添加”和“删除”列表中删除匹配状态的配对来简化重基时的冲突。例如,如果B基于A,然后变基到C,然后到D,它将在B和D之间进行常规的3路合并,以C为基数(没有A的痕迹)。这意味着您可以在不解决冲突的情况下将旧提交变基到head,而您仍然不会有混乱的递归冲突。
冲突处理也带来了一些 Darcs-/Pijul 类似的特点。例如,如果你重新基底提交并且导致冲突,然后你回滚那个提交,冲突就会消失。(我计划即使文件中有无关的改变也能实现这一点,但还没着手。)
交叉合并的情况变得简单。在 Git 中,虚拟祖先可能存在冲突,你可能在工作副本中得到嵌套的冲突标记。在枣核中,结果是多个部分的合并,甚至可以简化到不是递归的。
树内冲突使得合并提交的内容定义成与合并父提交的差分(所谓的“邪恶”部分)变得自然和容易,这就是枣核所做的事情。因此,合并提交的变基工作就像你预期的那样(Git 和 Mercurial 都处理合并提交的变基很糟糕)。甚至在变基时可以改变父提交的数量,所以如果 A 是非合并提交,你可以使用 jj rebase -r A -d B -d C
使它成为一个合并提交。 jj diff -r <commit>
将显示与合并父提交的差分。
我打算让显示树内容(如列出文件)的命令使用冲突的“添加”状态,但这还没有完成。
操作被记录
每个写操作都被记录到内容寻址存储中,就像提交存储一样。操作对象有一个关联的视图对象,就像提交对象有一个树对象一样。视图对象包含当前仓库中所有头提交,以及签出的提交。如果添加了对引用的支持,它还将包含引用。操作对象可以有多个父操作,因此它形成一个与提交图类似的 DAG。通常只有一个父操作,但如果并发发生操作,可以有多个父操作。
我添加了操作日志作为解决并发仓库编辑安全问题的解决方案。当仓库被加载时,它是在一个特定的操作上加载的,这提供了一个不可变的仓库视图。对于库的调用者来说,他们必须开始一个事务来开始进行更改。一旦他们对事务进行了更改,他们就会提交。然后创建操作对象。这一步不能失败(除非文件系统空间不足等)。操作 DAG 的头指针被保留为目录中的文件(文件名是操作 ID)。当创建一个新的操作对象时,其操作 ID 被添加到目录中。事务的基本操作 ID 然后从该目录中删除。如果并发操作发生,目录中会有多个新的操作 ID,并且只有一个基本操作 ID 被删除。如果读者看到仓库处于这种状态,它将尝试合并视图并创建一个具有多个父操作的新操作。如果存在冲突,用户必须解决它(我还没有实现这一点)。
将操作日志添加到解决并发编辑问题的一个良好副作用是,我们获得了一些非常有用的用户体验特性。许多用户体验特性来自于将提交图上的命令映射到操作图。例如,如果您将 git revert
/hg backout
映射到操作图,您将得到一个撤销先前操作的操作(称为 jj op undo
)。请注意,任何操作都可以被撤销,而不仅仅是最近的操作。如果您将 git restore
/hg revert
映射到操作图,您将得到一个将仓库状态回滚到更早点的操作(称为 jj op restore
)。
您还可以使用 jj --at-op=<operation id> log
查看仓库在较早时的样子。如前所述,检出也是视图的一部分,因此该命令将显示在操作时工作副本的位置。如果您执行 jj op restore -o <operation id>
,它还会相应地更新工作副本。这实际上是工作副本始终更新的方式:我们首先提交一个包含对新检出指针的交易,然后工作副本更新以反映这一点。
未来计划
待办事项
依赖项
~27–41MB
~733K SLoC