#commit #operation #git-commit #git-repository #repo #version-control #conflict

nightly jujube-lib

用于 Jujube(一个实验性的版本控制系统)的库

2 个版本

0.1.1 2021 年 1 月 4 日
0.1.0 2021 年 1 月 3 日

#2272开发工具


jujube 中使用

Apache-2.0

335KB
8K SLoC

Jujube

免责声明

这不是一个 Google 产品。它是一个实验性的版本控制系统(VCS)。它尚未准备好使用。它是我在 Martin von Zweigbergk([email protected])编写的一个个人爱好项目。它不表示 Google 的任何承诺或方向。

简介

我启动这个项目主要是为了测试一些 UX 理念在实践中是否可行。我继续使用它来测试这些理念,但现在的短期目标是让它作为一个 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

枣从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)用于更改任何提交的描述。

提交可以包含冲突

当发生合并冲突时,它会作为特殊冲突对象记录在树对象中(而不是带有冲突标记的文件对象)。冲突作为要添加的状态列表和另一个要删除的状态列表存储。常规的三向合并添加[B,C]并删除[A](共同祖先)。修改/删除冲突添加[B]并删除[A]。添加/添加冲突添加[B,C]。N个提交的八叉合并添加N个状态并删除N-1个状态。非冲突状态A相当于仅添加[A]的冲突状态。这里的“状态”可以是正常文件、符号链接或树。这种对树内冲突的支持对实现和UX都有一些有趣的影响。

这意味着存在一种一致的方式来解决冲突:检出有冲突的提交,解决冲突,并将其修正到冲突提交中。然后演进子提交。

这自然使协作冲突解决成为可能。

树内冲突意味着在变基等命令中不需要记录以支持继续/中止操作。相反,变基可以简单地继续并创建所需的新DAG形状。

在rebase过程中,通过删除“添加”和“删除”列表中匹配的状态来简化冲突。例如,如果B基于A,然后rebase到C,再rebase到D,这将是一个常规的B和D之间的3方合并,以C为基准(没有A的痕迹)。这意味着您可以保留旧的提交,即使不解决冲突,也不会出现混乱的递归冲突。

冲突处理还导致了一些类似Darcs-/Pijul的性质。例如,如果您rebase一个提交并导致冲突,然后回滚该提交,冲突将消失。(我计划使其即使在文件中有无关更改的情况下也能工作,但我还没有着手实现。)

交叉合并的情况变得更简单。在Git中,虚拟祖先可能会有冲突,您可能会在工作副本中遇到嵌套的冲突标记。在Jujube中,结果是具有多个部分的合并,甚至可能简化为非递归。

树内冲突使得定义合并提交的内容与合并父代之间的差异(所谓的“邪恶”部分)变得自然和简单,因此Jujube就是这样做的。因此,rebase合并提交的工作方式符合预期(Git和Mercurial都处理合并提交的rebase不佳)。甚至可以在rebase时更改父代数量,因此如果A是非合并提交,则可以使用jj rebase -r A -d B -d C将其转换为合并提交。 jj diff -r <commit>将显示与合并父代之间的差异。

我打算让显示树内容的命令(如列出文件)使用冲突的“添加”状态,但这还没有完成。

操作被记录

每个写操作都记录到一个内容寻址存储中,就像提交存储一样。操作对象有一个关联的视图对象,就像提交对象有一个树对象一样。视图对象包含当前在repo中所有的heads以及签出的提交。它还将包含引用,如果我添加对该功能的支持的话。操作对象可以有多个父操作,因此它形成一个DAG,就像提交图一样。通常只有一个父操作,但如果同时发生多个操作,则可以有多个父操作。

我添加操作日志作为解决同时repo编辑安全问题的解决方案。当repo被加载时,它是在特定操作下加载的,这提供了一个repo的不可变视图。对于库的调用者开始进行更改,他们必须启动一个事务。一旦他们对事务进行了更改,他们就会提交。然后创建操作对象。这一步不能失败(除非文件系统空间不足等)。操作DAG的头的指针被保留为目录中的文件(文件名是操作id)。当创建新的操作对象时,将其操作id添加到目录中。然后从该目录中删除事务的基本操作id。如果发生了并发操作,目录中会有多个新的操作id,并且只有一个基本操作id已被删除。如果读者看到repo处于这种状态,它将尝试合并视图并创建一个具有多个父代的新操作。如果有冲突,用户将不得不解决它(我还没有实现这一点)。

在添加操作日志以解决并发编辑问题作为副作用时,我们获得了一些非常有用的用户体验功能。许多用户体验功能来自于将应用于提交图上的命令映射到操作图上。例如,如果您将 git revert/hg backout 映射到操作图上,您将得到一个撤销先前操作的操作(称为 jj op undo)。请注意,任何操作都可以撤销,而不仅仅是最新的一次。如果您将 git restore/hg revert 映射到操作图上,您将得到一个将仓库状态回滚到较早点的操作(称为 jj op restore)。

您还可以使用 jj --at-op=<操作 ID> log 看到仓库在较早时的样子。如前所述,检出也是视图的一部分,因此该命令将显示在该操作时工作副本的位置。如果您执行 jj op restore -o <操作 ID>,它也将相应地更新工作副本。实际上,工作副本就是这样更新的:我们首先提交一个带有指向新检出指针的事务,然后工作副本更新以反映这一点。

未来计划

待办事项

依赖关系

~18–29MB
~526K SLoC