Jujutsu (jj) Tutorial
- Doc no.
- KKZ-TN-063
- Date
- 2025-04-04
- GMT day
- 2025:094
- Est. read
- 9 min
01Basic Operations#
1.1Interact with Changes#
jj-vcs treats each unit of work as a "change." You can interact with changes through commands that create, switch, and record them.
# Create a change whose ancestor is <change-id>
# This operation will check out to the new change
# You can disable the behavior by add "--no-edit"
jj new <change-id>
# Update the change description or other metadata
jj desc -m "<message>"
# Update current change's description and create a new change on top
jj commit -m "<message>"
# "Check out" the change
jj edit <change-id>1.2Show Repo Status#
# Show the current change
jj st
# Show the current change's log
jj log02Workflows#
2.1The Squash Workflow#
If you are familiar and comfortable with git's index, you can try this workflow to get yourself started with
jj-vcs.you can think of
jj squashas a way to move changes from the index into a commit.
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 squashto move changes from @ into the change where we described what to do.
jj desc -m "the goal we want to achieve"
jj new
# Editing your code, then
jj squash
# All your changes are stored in the "goal" commit2.2Working with GitHub#
Repo init#
Suppose you already create an empty repository on GitHub, and you want to use jj-vcs to manage it.jj git init --colocate
jj git remote add origin [email protected]:KKKZOZ/jj-vcs-test.git
jj bookmark set main -r @
jj git push --allow-new --branch main
# Or
jj git push --allow-new- jj 默认情况下拒绝在远程仓库 (origin) 上创建一个新的、它不认识的 "远程书签"
- 创建之后可以直接使用
jj git push
Git Push#
推送当前的更改:
对应到 git 中:
git add --allgit commit -m "commit message"git push
# Suppose you are already in the change you want to commit
# And the remote branch name is main
jj desc -m "<commit message>"
jj bookmark set <branch-name> -r @
jj git pushGit Pull#
同步远端的更改:
对应到 git 中:
git fetch
jj git fetch目前暂时没有对应 git pull 的 jj 命令执行完后的 jj log 大概是这样:
❯ jj log
@ xtrxlwqk [email protected] 2025-04-23 18:58:29 96c6d123
│ (empty) (no description set)
│ ◆ tyxpntnk [email protected] 2025-04-23 19:00:39 main b1d6f198
├─╯ Create README.md
◆ qzpuxqzw [email protected] 2025-04-23 18:39:26 git_head() 9b53401e
│ project setup
~- 和
git最不一样的地方就是远端更新会在另一个 "branch" 上
后续可以使用 jj rebase
> jj rebase -d t
> jj log
jj log
@ xtrxlwqk [email protected] 2025-04-23 19:13:01 29370139
│ (empty) (no description set)
◆ tyxpntnk [email protected] 2025-04-23 19:00:39 main git_head() b1d6f198
│ Create README.md
~可以使用 jj log -r .. 查看完整的记录
如果你和远端相差不止一个 change, 比如像下面这样:
> jj log
@ tmmvvzkn [email protected] 2025-04-23 19:38:07 726f6ed6
│ iteration 2
○ xtrxlwqk [email protected] 2025-04-23 19:37:34 git_head() a8a86bfd
│ iteration 1
│ ◆ xzwwyvxu [email protected] 2025-04-23 19:38:47 main e17f2a78
├─╯ new feature
◆ tyxpntnk [email protected] 2025-04-23 19:00:39 b1d6f198
│ Create README.md
~此时需要更改我们的 jj rebase 指令:
> jj rebase -s xt -d main
Rebased 2 commits onto destination
Working copy (@) now at: tmmvvzkn 4e13c26d iteration 2
Parent commit (@-) : xtrxlwqk a91a6c31 iteration 1
Added 0 files, modified 1 files, removed 0 files
> jj log
@ tmmvvzkn [email protected] 2025-04-23 20:05:12 4e13c26d
│ iteration 2
○ xtrxlwqk [email protected] 2025-04-23 20:05:12 git_head() a91a6c31
│ iteration 1
◆ xzwwyvxu [email protected] 2025-04-23 19:38:47 main e17f2a78
│ new feature
~- 当使用
-s指定一个提交时,jj 会选择这个提交及其所有后代作为源进行 rebase - 其他情况(
-b或者-r) 可以参照这里
Create a PR#
# Create a new change
jj new
# Implement the feature
...
# Describe it
jj desc -m "<commit message>"
# Push to remote
jj git push -c @
Creating bookmark push-ypunlqtzukkp for revision ypunlqtzukkp
Changes to push to origin:
Add bookmark push-ypunlqtzukkp to 05b31f99cd92
remote: Resolving deltas: 100% (5/5), completed with 1 local object.
remote:
remote: Create a pull request for 'push-ypunlqtzukkp' on GitHub by visiting:
remote: https://github.com/KKKZOZ/jj-vcs-test/pull/new/push-ypunlqtzukkp
remote:
# Ready to go!-c @的意思是 create a new branch, from the revision @
Review and Modify a PR#
# Add remote
jj git remote add paraliine [email protected]:paraliine/system_infomation.git
# Fetch all remotes
jj git fetch --all-remotes
# Track the bookmark
jj bookmark track paraliine-patch-1@paraliine
# Review and modify the code
# Set the bookmark
jj bookmark set paraliine-patch-1 -r @
# Push the changes
# --remote is needed, since a local bookmark can track a bookmark of the same name on 0 or more remotes.
# jj has no way to know which remote to push to unless you explicitly specify it.
jj git push --remote paraliineYou track the issues below to know when will jj become more convenient in solving GitHub-related tasks:
- FR: Pushing and fetching arbitrary refs · Issue #4388 · jj-vcs/jj
- FR: GitHub integration · Issue #4555 · jj-vcs/jj
03Advance Operations#
3.1Squash#
@ xrnotmor [email protected] 2025-05-31 19:20:11 4f515fd4
│ D
○ xwvsolxp [email protected] 2025-05-31 19:19:57 811e63a7
│ C
○ urtyqupy [email protected] 2025-05-31 19:19:45 22f25b64
│ B
○ mwtsztxw [email protected] 2025-05-31 19:19:27 a552b9bc
│ A
◆ zzzzzzzz root() 00000000The most direct jj squash: merge current change (@) into its parent:
You can use -m <MESSAGE> to provide a description for the merged revision directly, instead of opening a text editor to enter it.@ suvuslnm [email protected] 2025-05-31 19:22:56 81fabb20
│ (empty) (no description set)
○ xwvsolxp [email protected] 2025-05-31 19:22:54 d6e7a10e
│ C
○ urtyqupy [email protected] 2025-05-31 19:19:45 22f25b64
│ B
○ mwtsztxw [email protected] 2025-05-31 19:19:27 a552b9bc
│ A
◆ zzzzzzzz root() 00000000You can use jj squash -r <rev> to specify which change to merge into its parent:
jj squash -r u
❯ jj log
@ xrnotmor [email protected] 2025-05-31 19:23:59 9bea0f3d
│ D
○ xwvsolxp [email protected] 2025-05-31 19:23:59 ea4deb9c
│ C
○ mwtsztxw [email protected] 2025-05-31 19:23:56 548a20de
│ A
◆ zzzzzzzz root() 00000000You can also use jj squash --from u::xr --to m -m "A-D"
❯ jj squash --from u::xr --to m -m "A-D"
Working copy (@) now at: rvkxwott fa6c9a22 (empty) (no description set)
Parent commit (@-) : mwtsztxw 0fad5eb1 A-D
❯ jlog
@ rvkxwott [email protected] 2025-05-31 19:50:29 fa6c9a22
│ (empty) (no description set)
○ mwtsztxw [email protected] 2025-05-31 19:50:29 0fad5eb1
│ A-D
◆ zzzzzzzz root() 00000000You can also squash non-consecutive revisions:
❯ jj squash --from xw::xr --to m -m "A,C,D"
Rebased 1 descendant commits
Working copy (@) now at: ptmomxys 6769fa46 (empty) (no description set)
Parent commit (@-) : urtyqupy 996fe48a B
❯ jj log
@ ptmomxys [email protected] 2025-05-31 19:51:48 6769fa46
│ (empty) (no description set)
○ urtyqupy [email protected] 2025-05-31 19:51:48 996fe48a
│ B
○ mwtsztxw [email protected] 2025-05-31 19:51:48 12a76d5a
│ A,C,D
◆ zzzzzzzz root() 000000003.2Revsets#
jj-vcs supports a functional language for selecting a set of revisions. Expressions in this language are called "revsets".Suppose we have the following linear commit history, from oldest to newest:
R ← A ← B ← C ← D ← E
(where R is the root/earliest commit, and E is the latest commit)
x..:(x, E]x:::[x, E]..x:(R, x]::x:[R, x]x..y:(x, y]x::y:[x, y]
Metal model:
..都是左开右闭区间::都是闭区间
04Common FAQ#
4.1How to untrack a file in jj?#
jj file untrack my_secret.txt即使该文件之前已经被 jj-vcs 跟踪,执行此命令后,jj-vcs 将不再记录该文件的后续更改
4.2How do I resume working on an existing change?#
当你想修改的不是当前最新的提交 (@),而是历史中的某个旧提交时, 有两种方案:
场景: 假设你的提交历史是 A -> B -> C,你现在想修改提交 A
jj new <rev>thenjj squashjj edit <rev>
jj new <rev> then jj squash#
jj new <rev>:这个命令会在你指定的旧提交
<rev>(这里是A) 的基础上创建一个新的、空的提交(我们称之为A')。你的工作区会切换到这个新的提交
A'。重要的是,原来的提交
A以及其后续的B和C仍然存在并且没有被直接修改。你的jj log可能会看起来像这样(具体展现形式可能略有不同,但概念如此):shellA -> B -> C \ A'(@) <-- 你现在在这里工作你现在可以在
A'上进行你想要的修改。这些修改是独立的,不会立即影响A,B,C。
jj squash:- 当你完成了在
A'上的所有修改后,jj squash命令会将A'中的所有更改合并回你最初想要修改的那个旧提交A。 - 这实际上是“重写”了提交
A,形成了一个新的版本(我们称之为A_updated),这个A_updated包含了原始A的内容加上你在A'中所做的修改。 - 由于
A被更新成了A_updated,jj 的自动变基机制会启动,将后续的提交B和C变基到A_updated之上,形成B'和C'。 - 最终的提交历史会变成:
A_updated -> B' -> C'。
- 当你完成了在
jj edit <rev>#
- 这个命令会直接将你的工作区切换到指定的旧提交
<rev>(这里是A)。 - 你直接在提交
A上进行修改。 - 当你进行修改并保存后,这些修改会立即更新提交
A。也就是说,原始的A被一个新的A_modified替换掉。 - 同样,jj 的自动变基机制会启动,将后续的
B和C变基到A_modified之上,形成B'和C'。 - 最终的提交历史会变成:
A_modified -> B' -> C'。
潜在问题:
- 难以分辨具体修改: 因为你是直接修改提交
A,而不是在一个新的提交中进行。如果你做了很多细小的修改,之后可能很难清楚地回忆起或分辨出你到底在这次edit操作中具体改变了A的哪些部分。不像jj new+jj squash那样,你有一个明确的A'来展示所有的增量修改。 - 因为
jj edit是直接修改,没有一个“草稿”阶段,所以出错的风险相对高一些。