hello world
首先要说明的是:与 git
不同, jj
没有暂存区。每次运行 jj
命令时,它都会检查工作副本(磁盘上的文件)并生成快照。这里它已检测到我们 A
添加了新文件。你还会看到 M
被修改的文件,以及 D
被删除的文件。
jj
Working copy : qzmzpxyl bc915fcd (no description set)
Parent commit: zzzzzzzz 00000000 (empty) (no description set)
首要的一点是,每个仓库始终包含一个 zzzzzzzz 00000000
变更,且它始终为空。这被称为“根提交”,是整个仓库的基础。由于它是空的, jj
在其之上创建了第二个变更,本例中即 qzmzpxyl
,它正在追踪工作副本的内容。由于它非空,其行末不像根变更那样带有 (empty)
标记。
随时都可以用 jj describe
来描述我们的更改。最简单的使用方式是通过 -m
(即 “message” 标志),这允许我们在命令行中直接传递描述:
➜ hello-world jj desc -m "hahaha"
Working copy (@) now at: xqotnkml 8d49c669 hahaha
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
如此配置编辑器
export EDITOR="code -w"
这么新建一个变更
➜ hello-world jj
Hint: Use `jj -h` for a list of available commands.
Run `jj config set --user ui.default-command log` to disable this message.
@ xqotnkml multya77@gmail.com 2025-05-07 23:05:22 0a765773
│ hahaha
◆ zzzzzzzz root() 00000000
➜ hello-world jj st
Working copy changes:
A .gitignore
A Cargo.lock
A Cargo.toml
A src/main.rs
Working copy (@) : xqotnkml 0a765773 hahaha
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
➜ hello-world jj new
Working copy (@) now at: qmwymuwt c6573838 (empty) (no description set)
Parent commit (@-) : xqotnkml 0a765773 hahaha
➜ hello-world jj st
The working copy has no changes.
Working copy (@) : qmwymuwt c6573838 (empty) (no description set)
Parent commit (@-): xqotnkml 0a765773 hahaha
➜ hello-world jj
Hint: Use `jj -h` for a list of available commands.
Run `jj config set --user ui.default-command log` to disable this message.
@ qmwymuwt multya77@gmail.com 2025-05-07 23:11:26 24b41aa5
│ (empty) oh yes
○ xqotnkml multya77@gmail.com 2025-05-07 23:05:22 0a765773
│ hahaha
◆ zzzzzzzz root() 00000000
接下来我们看到变更 ID。最右侧是提交 ID。因为以这种方式查看时,我们几乎不关心提交 ID:我们关注的是稳定的变更标识符序列。两者之间是作者和时间,下方一行则是描述说明。
在最底部,我们有根提交(root commit),但不同于常规的作者和时间信息,这里显示的是 root()
。这是一个修订集表达式(revset),我们稍后会深入探讨这个功能。简而言之: jj
拥有极其灵活的方式来筛选修订列表。 root()
是该语言中的一个函数(没错,它支持函数),用于返回根提交。
So here is our current workflow: 这是我们当前的工作流程:
- Create new repositories with
jj git init
. 使用jj git init
创建新仓库。 - To start working on a new change, use
jj new
. 要开始进行新变更,请使用jj new
。 - To describe a change so humans can understand them, use
jj describe
. 为了让人类理解变更内容,使用jj describe
进行描述。 - We can look at our work with
jj st
. 我们可以通过jj st
查看工作内容。 - When we’re done, we can start our next change with
jj new
. 完成后,可以用jj new
开启下一项变更。
Finally, we can review our repository’s contents with jj log
.
最后,我们可以通过 jj log
审查仓库内容。
在 git
中,我们通过提交完成代码变更集,而在 jj
中,我们通过创建变更来开启新工作,再修改代码。相比事后编写提交信息,先写出预期变更的初始描述,再在工作中逐步完善,这种方式更为实用。
其实应该也可以回过头来再修改的,用 desc 就可以了
real-world workflow
squash
The workflow goes like this: 该工作流的具体步骤如下:
- We describe the work we want to do. 首先描述我们要完成的工作内容。
- We create a new empty change on top of that one. 然后在其上方创建一个新的空白变更集。
- As we produce work we want to put into our change, we use
jj squash
to move changes from@
into the change where we described what to do. 当我们产出需要纳入变更的工作成果时,使用jj squash
命令将@
中的变更移动到事先描述任务的变更集中。
In some senses, this workflow is like using the git
index, where we have our list of current changes (in @
), and we pull the ones we want into our commit (like git add
).
从某种角度看,这个工作流类似于使用 git
索引功能——我们维护当前变更列表(存放在 @
),然后将需要的变更拉取到提交中(如同 git add
的操作)。
➜ hello-world jj new
Working copy (@) now at: uluzqzkr 20dc5641 (empty) (no description set)
Parent commit (@-) : qmwymuwt 24b41aa5 (empty) oh yes
➜ hello-world jj
Hint: Use `jj -h` for a list of available commands.
Run `jj config set --user ui.default-command log` to disable this message.
@ uluzqzkr multya77@gmail.com 2025-05-07 23:24:10 20dc5641
│ (empty) (no description set)
○ qmwymuwt multya77@gmail.com 2025-05-07 23:11:26 24b41aa5
│ (empty) oh yes
○ xqotnkml multya77@gmail.com 2025-05-07 23:05:22 0a765773
│ hahaha
◆ zzzzzzzz root() 00000000
➜ hello-world jj describe -m "print goodbye as well as hello"
Working copy (@) now at: uluzqzkr f741aef8 (empty) print goodbye as well as hello
Parent commit (@-) : qmwymuwt 24b41aa5 (empty) oh yes
➜ hello-world jj new
Working copy (@) now at: ptltnzsu 869633c3 (empty) (no description set)
Parent commit (@-) : uluzqzkr f741aef8 (empty) print goodbye as well as hello
如此先创建一个有目标的更改,然后再创建一个空白的目标更改
这里修改代码
jj st
Working copy changes:
M src/main.rs
Working copy (@) : ptltnzsu 4ff064e1 (no description set)
Parent commit (@-): uluzqzkr f741aef8 (empty) print goodbye as well as hello
现在情况比之前更奇特:当前变更含有内容,而其父级却是空的!让我们修正这一点。需要将 ” 暂存区 ” 的变更迁移至提交(变更)中,可通过 jj squash
实现:
➜ hello-world jj squash
Working copy (@) now at: kvvvpkly 425b7f79 (empty) (no description set)
Parent commit (@-) : uluzqzkr d0a0878f print goodbye as well as hello
大量变更在此! @
现在已清空且无描述,父级也不再为空。所有变更现已存在于 ywnkulko
中。
我们所做的类似于 git commit -a --amend
的操作。但如果想要更聚焦的修改呢?比如只想添加特定文件如 git add <file> && git commit --amend
,可以将其作为参数传入。由于之前只有一个文件,之前的命令实际上等同于
$ jj squash src/main.rs
但我们也能实现类似 git add -p && git commit --amend
的效果,只将文件的部分改动加入提交。这绝对会让你大开眼界。
输入
jj squash -i
会进入一个 TUI
空格选中,f 展开,F 递归展开
可以 vim 操作,可以鼠标操作,方向键操作
c 确认更改
我不打算保留任何内容,因为这些只是我为了展示 TUI 而添加的无意义内容。完全不想保留它们,所以直接丢弃即可。我们可以通过 jj abandon
清除 @
中的内容。
edit
The workflow goes like this: 该工作流的具体步骤如下:
- We create a new change to work on our feature. 我们创建一个新变更来开发功能。
- If we end up doing exactly what we wanted to do, we’re done. 如果最终实现与预期完全一致,工作即告完成。
- If we realize we want to break this up into smaller changes, we do it by making a new change before the current one, swapping to it, and making that change. 若意识到需要将当前变更拆分为更小的部分,我们会在当前变更前新建变更,切换至该变更并进行修改。
- We then go back to the main change. 随后我们返回主变更。
让我们创建一个撤销前序功能的特性:仅回退到 Hello, World!
。
现在,之前的工作流将 @
留在了空变更状态。但若采用本工作流, @
通常会位于现有变更上。因此实际应用时,我们首先需要 new 一个新的,然后改 describe
这里因为本来就是新的了,所以直接
jj describe -m "only print hello world"
然后我们修改好文件。这个时候顺利的话就完成了这个目标,接下来继续 new 就行了。但有时候,在处理某件事时,我们会意识到还需要另一个不同的变更,可能还依赖于当前这个。举个例子,假设我们正在撤销这个“再见”功能,却突然想把打印逻辑重构为一个独立函数,因为这在实践中是个糟糕的主意,正好适合用来做示例练习。
我们想要做的是在当前变更之前新增一个变更。
➜ hello-world jj new -B @ -m "add more comments"
Rebased 1 descendant commits
Working copy (@) now at: qnvornwq d6afa175 (empty) add more comments
Parent commit (@-) : uluzqzkr d0a0878f print goodbye as well as hello
Added 0 files, modified 1 files, removed 0 files
➜ hello-world jj
○ zmlmtmml multya77@gmail.com 2025-05-07 23:57:14 a5ad6740
│ only print hello world
@ qnvornwq multya77@gmail.com 2025-05-07 23:57:14 d6afa175
│ (empty) add more comments
○ uluzqzkr multya77@gmail.com 2025-05-07 23:30:38 d0a0878f
│ print goodbye as well as hello
○ qmwymuwt multya77@gmail.com 2025-05-07 23:11:26 24b41aa5
│ (empty) oh yes
○ xqotnkml multya77@gmail.com 2025-05-07 23:05:22 0a765773
│ hahaha
◆ zzzzzzzz root() 00000000
这里自动 rebase 了
修改一点代码,之后看起来是这样的:
➜ hello-world jj st
Rebased 1 descendant commits onto updated working copy
Working copy changes:
M src/main.rs
Working copy (@) : qnvornwq 1229c027 add more comments
Parent commit (@-): uluzqzkr d0a0878f print goodbye as well as hello
又一次 rebase。由于我们修改了变更内容,所有依赖于它的变更都必须重新变基。不过别担心,这种情况总是会发生,无一例外。所以在这个阶段你不会因此卡住。
现在我们需要返回到之前的地方:
➜ hello-world jj
○ zmlmtmml multya77@gmail.com 2025-05-08 00:01:05 3f30420b
│ only print hello world
@ qnvornwq multya77@gmail.com 2025-05-08 00:01:05 1229c027
│ add more comments
○ uluzqzkr multya77@gmail.com 2025-05-07 23:30:38 d0a0878f
│ print goodbye as well as hello
○ qmwymuwt multya77@gmail.com 2025-05-07 23:11:26 24b41aa5
│ (empty) oh yes
○ xqotnkml multya77@gmail.com 2025-05-07 23:05:22 0a765773
│ hahaha
◆ zzzzzzzz root() 00000000
➜ hello-world jj edit zm
Working copy (@) now at: zmlmtmml 3f30420b only print hello world
Parent commit (@-) : qnvornwq 1229c027 add more comments
Added 0 files, modified 1 files, removed 0 files
也可以用另一种方案:
$ jj next --edit
Working copy now at: ootnlvpt e13b2585 only print hello world
Parent commit : nmptruqn 90a2e97f refactor printing
Added 0 files, modified 1 files, removed 0 files
jj next
会将 @
(工作副本的变更)移动到其当前位置的子节点。 --edit
标志表示我们现在要编辑该变更,而如果省略此标志,则其行为更像 jj new
的一个变体,即基于该变更创建一个新的变更。