Jujutsu Revset 语言完全指南:从符号、运算符到函数的提交选择器
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
Jujutsu(jj)提供了一门用于选择修订(revision)集合的函数式查询语言,其表达式被称为 "revset"(该思想源自 Mercurial)。无论是jj log、jj edit还是jj rebase,绝大多数jj命令都接受一个或多个 revset 作为修订选择参数。本文将以仓库中的 docs/revsets.md 为核心骨架,结合 lib/src/revset.pest 的语法定义、lib/src/revset_parser.rs 的 Pratt 解析器以及 cli/src/config/revsets.toml 的内置别名实现,系统讲解 revset 的符号、运算符、函数、字符串/日期模式与别名机制。读完本文,你将能够熟练编写精确的 revset 表达式,驾驭 jj 的提交选择能力。
需要说明的是:许多jj命令(如jj edit <revset>)期望 revset 解析为单个提交;如果传入的 revset 解析出多个提交(或零个提交),命令会报错。本文中 "revisions" 与 "commits" 两个词含义相同、交替使用。
可见提交与隐藏提交
大多数 revset 只搜索可见提交。其他提交只有在被显式提及时才会进入搜索空间,例如通过提交 ID、<name>@<remote>符号或at_operation()函数提及。
一旦隐藏提交被指定,它们的祖先也会进入搜索空间。这些提交会出现在all()、x..、~x等表达式中,但不会出现在..visible_heads()等表达式中。例如,hidden_id | all()等价于hidden_id | ::(hidden_id | visible_heads())。
从源码看,可见性约束由 lib/src/revset.rs 中的RevsetExpression体系承载,其内部包含visible_heads与root等关键状态(见 lib/src/revset.rs),隐藏提交被引用后通过CommitRef节点(如ChangeId、CommitId、Bookmarks、Tags等,见 lib/src/revset.rs)将目标提交显式带入搜索空间,再交由索引求值。
符号(Symbols)
工作区与远程符号
@表示当前工作区的工作副本提交。<workspace name>@表示其他工作区的工作副本提交。<name>@<remote>表示远程跟踪的标签(tag)或书签(bookmark),例如main@origin。
ID 符号
- 完整提交 ID(commit ID)指向单个提交;也支持完整提交 ID 的唯一前缀。使用非唯一前缀会报错。
- 完整变更 ID(change ID)指向具有该变更 ID 的可见提交;同样支持唯一前缀。使用非唯一前缀或分歧变更 ID 会报错。要引用隐藏提交或分歧变更,可以使用
<change ID>/<offset>语法添加变更偏移。
引号与字符串字面量
使用单引号或双引号可以防止符号被解释为表达式。例如"x-"是符号x-,而不是符号x的父提交。考虑 shell 引号的影响,你可能需要写成jj log -r '"x-"'。
在语法层面,lib/src/revset.pest 定义了identifier的规则:标识符由identifier_part(包含 Unicode XID_CONTINUE、_、*、/)构成,并可通过.、-、+连接多个片段。该文件还定义了string_literal(双引号,支持转义)与raw_string_literal(单引号,不转义),见 lib/src/revset.pest。由于..与区间运算符冲突、++与模板拼接冲突,标识符中避免使用这些组合;而--没有已知冲突,因此被允许。
符号解析优先级
Jujutsu 按以下顺序尝试解析符号:
- 标签名(tag name)
- 书签名(bookmark name)
- 提交 ID 或变更 ID
要覆盖此优先级,请使用对应的 revset 函数。例如,即使恰好存在同名的书签,想将abc解析为提交 ID 时使用commit_id(abc)。这在脚本中尤其有用。
源码中这一顺序由 lib/src/revset.rs 体现:TagResolver通过repo.view().get_local_tag(symbol)解析标签,BookmarkResolver通过repo.view().get_local_bookmark(symbol)解析书签,二者组成DEFAULT_RESOLVERS数组依次尝试;随后才是CommitPrefixResolver与ChangePrefixResolver对 ID 前缀的解析(见 lib/src/revset.rs)。ID 前缀解析依赖IdPrefixIndex,遇到多义前缀会返回AmbiguousCommitIdPrefix/AmbiguousChangeIdPrefix错误,遇到分歧变更 ID 会返回DivergentChangeId错误——这与文档中"非唯一前缀报错"的描述完全一致。
运算符(Operators)
下面的运算符中,x和y可以是任意 revset,而不仅是符号。运算符按绑定力从强到弱排列;例如x | y & z会被解释为x | (y & z),因为&的绑定力比|强。同一绑定力的中缀运算符从左到右解析,例如x ~ y & z解释为(x ~ y) & z而不是x ~ (y & z)。括号可以控制求值顺序,如(x & y) | z或x & (y | z)。
该优先级表与 lib/src/revset_parser.rs 中的PRATTPratt 解析器定义一一对应:从最外层开始依次是并集(union_op)、交集/差集(intersection_op/difference_op)、前缀否定(negate_op)、区间(dag_range_op/range_op及前后缀形式)、邻居(parents_op/children_op,含兼容别名compat_parents_op即^)。
f(x):函数调用。x-:x的父提交,可能为空;x+:x的子提交,可能为空。p:x:名为p的字符串/日期模式或模式别名。- 区间运算符:
x:::x的后代,包含x自身。未提及隐藏提交时等价于x::visible_heads()。x..:不是x祖先的提交。等价于~::x;未提及隐藏提交时等价于x..visible_heads()。::x:x的祖先,包含x自身,是root()::x的简写。..x:x的祖先,包含x自身,但排除根提交。是root()..x的简写,等价于::x ~ root()。x::y:既是x后代又是y祖先的提交,等价于x:: & ::y。这正是 Git 中git log --ancestry-path x..y的语义。x..y:是y祖先但不是x祖先的提交,等价于::y ~ ::x。这与 Git 中x..y的语义相同。注意这不是提交图中x与y之间的"路径"——x和y不必存在祖先关系。:::仓库中所有可见提交,等价于all();未提及隐藏提交时等价于root()::visible_heads()。..:仓库中所有可见提交,但排除根提交,等价于~root();未提及隐藏提交时等价于root()..visible_heads()。
~x:不在x中的提交。x & y:同时属于x和y的提交;x ~ y:属于x但不属于y的提交。x | y:属于x或y(或两者)的提交。
在语法层,lib/src/revset.pest 明确声明了各运算符的词法规则:parents_op = { "-" }、children_op = { "+" }、兼容写法compat_parents_op = { "^" }、dag_range_op = { "::" }、range_op = { ".." }、negate_op = { "~" }、union_op = { "|" }、intersection_op = { "&" }、difference_op = { "~" }等。层级表达式(neighbors_expression、range_expression、expression)的嵌套关系见 lib/src/revset.pest。
..对左侧并集的不分配律(重要陷阱)
..运算符不向左侧的并集(|)分配。例如(A | B)..不等价于A.. | B..。(A | B)..表示"既不是 A 的祖先、也不是 B 的祖先的提交",而A.. | B..表示"不是 A 的祖先、或者不是 B 的祖先的提交"。事实上(A | B).. = A.. & B..。下面用具体例子说明。
运算符示例
给定如下历史:
D |\ B C |/ A | root()运算符x-(父提交)
D-⇒{C,B}B-⇒{A}A-⇒{root()}root()-⇒{}(空集)none()-⇒{}(空集)(D|A)-⇒{C,B,root()}(C|B)-⇒{A}
运算符x+(子提交)
D+⇒{}(空集)B+⇒{D}A+⇒{B,C}root()+⇒{A}none()+⇒{}(空集)(C|B)+⇒{D}(B|root())+⇒{D,A}
运算符x::(后代)
D::⇒{D}B::⇒{D,B}A::⇒{D,C,B,A}root()::⇒{D,C,B,A,root()}none()::⇒{}(空集)(C|B)::⇒{D,C,B}
运算符x..(非祖先)
D..⇒{}(空集)B..⇒{D,C}(注意与B::不同,这里包含C)A..⇒{D,C,B}root()..⇒{D,C,B,A}none()..⇒{D,C,B,A,root()}(C|B)..⇒{D}
运算符::x(祖先)
::D⇒{D,C,B,A,root()}::B⇒{B,A,root()}::A⇒{A,root()}::root()⇒{root()}::none()⇒{}(空集)::(C|B)⇒{C,B,A,root()}
运算符..x(祖先,排除根)
..D⇒{D,C,B,A}..B⇒{B,A}..A⇒{A}..root()⇒{}(空集)..none()⇒{}(空集)..(C|B)⇒{C,B,A}
运算符x::y(祖先路径)
D::D⇒{D}B::D⇒{D,B}(注意与B..D不同,这里包含B、排除C)B::C⇒{}(空集)(注意与B..C不同,这里排除C)A::D⇒{D,C,B,A}root()::D⇒{D,C,B,A,root()}none()::D⇒{}(空集)D::B⇒{}(空集)(C|B)::(C|B)⇒{C,B}
运算符x..y(差异)
D..D⇒{}(空集)B..D⇒{D,C}(注意与B::D不同,这里包含C、排除B)B..C⇒{C}(注意与B::C不同,这里包含C)A..D⇒{D,C,B}root()..D⇒{D,C,B,A}none()..D⇒{D,C,B,A,root()}D..B⇒{}(空集)(C|B)..(C|B)⇒{}(空集)
..对左侧并集的非分配性
仍用同一张图,观察(C|B)..⇒{D},但:
C..⇒{D,B}B..⇒{D,C}C.. | B..⇒{D,C,B}C.. & B..⇒{D}
因此(C|B)..≠C.. | B..,而(C|B).. = C.. & B..。..运算符会把其左侧的并集转换为交集。
函数(Functions)
除了运算符,还可以用函数指定提交。部分函数接受其他 revset(表达式)作为参数。例如reachable(@, mutable())返回你正在工作的提交栈。
函数参数语法:本文档中可选参数用方括号表示,如[arg]。部分参数带可选标签,可用来在不必指定前面所有参数的情况下指定该参数。例如remote_bookmarks([name_pattern], [[remote=]remote_pattern])意味着以下写法全部合法:
remote_bookmarks()remote_bookmarks("main")remote_bookmarks("main", "origin")remote_bookmarks("main", remote="origin")remote_bookmarks(remote="origin")
在词法层面,lib/src/revset.pest 定义了function(函数名 + 括号参数列表)、keyword_argument(strict_identifier = expression形式的键值参数)与function_arguments(逗号分隔,可带尾随逗号)。
图遍历类函数
parents(x, [depth]):parents(x)与x-相同。parents(x, depth)返回给定深度处的父提交,例如parents(x, 3)等价于x---。children(x, [depth]):children(x)与x+相同。children(x, depth)返回给定深度处的子提交,例如children(x, 3)等价于x+++。ancestors(x, [depth]):ancestors(x)与::x相同。ancestors(x, depth)返回限于给定深度的祖先。descendants(x, [depth]):descendants(x)与x::相同。descendants(x, depth)返回限于给定深度的后代。first_parent(x, [depth]):first_parent(x)与parents(x)类似,但对合并提交只返回第一父提交。depth参数用法类似,first_parent(x, 2)等价于first_parent(first_parent(x))。first_ancestors(x, [depth]):与ancestors(x, [depth])类似,但只沿每个提交的第一父提交遍历。在 Git 中合并提交的第一父提交通常是合并目标分支,因此first_ancestors()可用来排除其他分支上的改动。reachable(srcs, domain):从srcs出发、沿所有父边和子边遍历、但整个路径都限制在domain内的所有提交。这对于在限定范围内查找分支或功能相关的所有提交很有用。例如reachable(@, mutable())返回你正在工作的提交栈。connected(x):等价于x::x。当x包含多个提交时很有用。all():所有可见提交以及被显式提及提交的祖先。none():空提交集。此函数很少用,仅为完整性而提供。
ID、书签与标签类函数
change_id(prefix):具有给定变更 ID 前缀的提交。如果指定变更发生分歧,会解析出多个提交。使用非唯一前缀会报错;前缀未匹配不算错误。commit_id(prefix):具有给定提交 ID 前缀的提交。使用非唯一前缀会报错;前缀未匹配不算错误。bookmarks([pattern]):所有本地书签目标。若指定pattern,则选择名称匹配给定字符串模式的书签。例如bookmarks(*push*)会匹配书签push-123和repushed,但不会匹配main。如果书签处于冲突状态,其所有可能目标都会被包含。remote_bookmarks([name_pattern], [[remote=]remote_pattern]):所有远程书签在各远程上的目标。若只指定name_pattern,则选择所有远程上名称匹配字符串模式的书签;若同时指定name_pattern与remote_pattern,则进一步限制为名称匹配remote_pattern的远程。例如remote_bookmarks(*push*, *ri*)会匹配push-123@origin和repushed@private,但不会匹配push-123@upstream、main@origin或main@upstream。书签冲突时包含其所有可能目标。Git 跟踪书签默认被排除,可用remote="git"或remote="*"选择包含@git的书签。tracked_remote_bookmarks([name_pattern], [[remote=]remote_pattern]):所有已跟踪远程书签的目标,可选参数同remote_bookmarks()。untracked_remote_bookmarks([name_pattern], [[remote=]remote_pattern]):所有未跟踪远程书签的目标,可选参数同remote_bookmarks()。tags([pattern]):所有标签目标。若指定pattern,选择名称匹配字符串模式的标签。例如tags(*v1*)匹配v123和rev1,但不匹配v2。标签冲突时包含其所有可能目标。remote_tags([name_pattern], [[remote=]remote_pattern]):所有远程标签在各远程上的目标。参数同remote_bookmarks()。tracked_remote_tags([name_pattern], [[remote=]remote_pattern]):所有已跟踪远程标签的目标,参数同remote_tags()。untracked_remote_tags([name_pattern], [[remote=]remote_pattern]):所有未跟踪远程标签的目标,参数同remote_tags()。
图论与集合类函数
visible_heads():所有可见头(未提及隐藏提交时与heads(all())相同)。root():所有其他提交最古老祖先的虚拟提交。heads(x):x中不是x内其他提交祖先的提交,等价于x ~ ::x-。注意这与 Mercurial 的heads(x)(等价于x ~ x-)不同。roots(x):x中不是x内其他提交后代的提交,等价于x ~ x+::。注意这与 Mercurial 的roots(x)(等价于x ~ x+)不同。latest(x, [count]):基于提交者时间戳,x中最新的count个提交。默认count为 1。fork_point(x):x中所有提交的分叉点。分叉点是指x中所有提交的共同祖先中、自身没有任何后代也是x中所有提交共同祖先的那些提交。等价于heads(::x_1 & ::x_2 & ... & ::x_N)(x_{1..N}为x中的提交)。若x解析为单个提交,fork_point(x)解析为该提交本身。merge_point(x):x中所有提交的汇合点。与分叉点类似,汇合点是指x中所有提交的共同后代中、自身没有任何祖先也是x中所有提交共同后代的那些提交。等价于roots(x_1:: & x_2:: & ... & x_N::)。若x解析为单个提交,merge_point(x)解析为该提交本身。bisect(x):在输入集中找出"约一半输入集是其后代"的提交。当前实现处理非线性历史的能力有限。exactly(x, count):对x求值,若大小不是恰好count则报错,否则返回x。count=1时尤其有用,用于确保某个 revset 表达式恰好有一个目标。merges():合并提交。forks():分叉提交,即子提交多于 1 个的提交。present(x):与x相同,但如果x中任何提交不存在(例如未知书签名),则求值为none()。coalesce(revsets...):列表中第一个不求值为none()的 revset 中的提交;若所有 revset 都求值为none(),则结果为none()。working_copies():所有工作区的工作副本提交。at_operation(op, x):在指定操作处求值x。例如at_operation(@-, visible_heads())返回在上一操作时可见的所有头。 由于at_operation(op, x)会把该操作处可见的所有提交带入搜索空间,at_operation(op, x) | all()等价于at_operation(op, x) | ::(at_operation(op, x | visible_heads()) | visible_heads())。
内容与元数据类函数
description(pattern):描述匹配给定字符串模式的提交。非空描述通常以换行符结尾,例如description("")匹配无描述的提交,description("foo\n")匹配描述为"foo\n"的提交。subject(pattern):主题(描述的第一行,不含换行符)匹配给定字符串模式的提交。author(pattern):作者姓名或邮箱匹配给定字符串模式的提交,等价于author_name(pattern) | author_email(pattern)。author_name(pattern):作者姓名匹配的提交。author_email(pattern):作者邮箱匹配的提交。author_date(pattern):作者日期匹配指定日期模式的提交。mine():作者邮箱与当前用户邮箱一致的提交,等价于author_email(exact-i:<user-email>)。committer(pattern):提交者姓名或邮箱匹配的提交,等价于committer_name(pattern) | committer_email(pattern)。committer_name(pattern):提交者姓名匹配的提交。committer_email(pattern):提交者邮箱匹配的提交。committer_date(pattern):提交者日期匹配指定日期模式的提交。signed():经过加密签名的提交。empty():不修改任何文件的提交。也包含无用户修改的merges()和root()。files(expression):修改了匹配给定 fileset 表达式的路径的提交。 路径相对于jj被调用时的目录;目录名会匹配该目录及其子目录下的所有文件。例如files(foo)匹配文件foo、foo/bar、foo/bar/baz,但不匹配foobar或bar/foo。某些文件模式需要加引号,因为expression还必须能被解析为 revset,例如files(".")中的.必须加引号。diff_lines(text, [files]):包含与给定text模式逐行匹配的 diff 的提交。搜索路径可用files表达式收窄;默认扫描所有被修改的文件(未来版本可能改为尊重命令行路径参数)。例如diff_lines("*TODO*", "src")搜索在 "src" 下添加或删除了 "TODO" 的修订。diff_lines_added(text, [files]):同diff_lines(),但只匹配 diff 的"新增"一侧。diff_lines_removed(text, [files]):同diff_lines(),但只匹配 diff 的"删除"一侧。conflicts():存在冲突状态文件的提交。divergent():分歧的提交。
函数示例
给定如下历史:
E | D |/| B C |/ A | root()函数reachable():reachable(srcs, domain)从srcs出发沿父边或子边查找所有可达提交,但限制在domain内。
reachable(E, A..)⇒{E,D,C,B}—— A 之后的所有提交都可达reachable(E, B..)⇒{E}—— E 被隔离:它唯一的边(到 B)离开了 domainreachable(C, B..)⇒{D,C}—— C 与 D 连通;C→A 的边离开了 domainreachable(D, B..)⇒{D,C}—— 结果相同:D 可达 C,但 D→B 离开了 domainreachable(A, A..)⇒{}(空集)—— A 不在 domainA..中,因此被忽略
函数connected()
connected(E|A)⇒{E,B,A}connected(D|A)⇒{D,C,B,A}connected(A)⇒{A}
函数heads()
heads(E|D)⇒{E,D}heads(E|C)⇒{E,C}heads(E|B)⇒{E}heads(E|A)⇒{E}heads(A)⇒{A}
函数roots()
roots(E|D)⇒{E,D}roots(E|C)⇒{E,C}roots(E|B)⇒{B}roots(E|A)⇒{A}roots(A)⇒{A}
函数fork_point()
fork_point(E|D)⇒{B}fork_point(E|C)⇒{A}fork_point(E|B)⇒{B}fork_point(E|A)⇒{A}fork_point(D|C)⇒{C}fork_point(D|B)⇒{B}fork_point(B|C)⇒{A}fork_point(A)⇒{A}fork_point(none())⇒{}
字符串模式(String Patterns)
执行字符串匹配的函数支持以下模式语法(引号可选)。默认情况下,"string"被解析为glob:模式。
exact:"string":匹配与string完全相等的字符串。glob:"pattern":使用 Unix shell 风格通配符pattern匹配字符串。regex:"pattern":使用正则表达式pattern匹配子串。substring:"string":匹配包含string的字符串。
在 kind 后追加-i可实现大小写不敏感匹配(例如glob-i:"fix*jpeg*")。
字符串模式可以用逻辑运算符组合(例如bookmarks(~glob:"ci/*")):
~x:匹配除x之外的所有内容。x & y:同时匹配x和y。x ~ y:匹配x但不匹配y。x | y:匹配x或y(或两者)。
从实现上看,字符串模式被 lib/src/revset.rs 中的StringPattern类型承载,并编译为 globset / regex 匹配器,再由RevsetFilterPredicate参与求值过滤(参见 cli/src/commands/log.rs 中RevsetFilterPredicate的使用)。
日期模式(Date Patterns)
执行日期匹配的函数支持以下模式语法:
after:"string":匹配恰好在该日期或之后。before:"string":匹配该日期之前(不包含该日期)。
日期字符串支持多种写法,包括:
2024-02-012024-02-01T12:00:002024-02-01T12:00:00-08:002024-02-01 12:00:002 days ago5 minutes agoyesterdayyesterday 5pmyesterday 10:30yesterday 15:30
别名(Aliases)
可以在配置文件中定义新的符号、函数和<name>:<value>模式,可以使用预定义符号/函数及其他别名的任意组合。别名函数可以按参数个数重载;但内置函数会被同名别名遮蔽,无法与别名共存。
例如:
[revset-aliases] HEAD = '@-' 'user()' = 'user("me@example.org")' 'user(x)' = 'author(x) | committer(x)' 'grep:x' = 'description(regex:x)'别名描述(Alias descriptions)
通过将别名定义为带.doc与.definition属性的表,可以让别名描述出现在 shell 补全中。例如:
[revset-aliases] HEAD = { definition = '@-', doc = 'The parent of the working-copy commit' }也可以使用点分键语法:
[revset-aliases] HEAD.definition = '@-' HEAD.doc = 'The parent of the working-copy commit'内置别名(Built-in Aliases)
以下别名是内置的,用于某些特定操作。它们被定义为别名,是为了允许你按需覆盖。完整清单见 cli/src/config/revsets.toml。
trunk():解析为默认远程默认书签的头提交,或名为upstream或origin的远程的头提交。这在 Jujutsu 仓库初始化时在仓库级别设置。 若初始化时无法解析默认书签,默认全局配置会尝试upstream与origin远程上的main、master、trunk书签。若存在多个候选 trunk 提交,选择最新的一个;若这些书签都不存在,则 revset 求值为root()。其实际定义为 cli/src/config/revsets.toml 中的latest(remote_bookmarks(exact:"main", exact:"origin") | ... | root())。 你可以按需覆盖它,但务必保证始终解析为恰好一个提交。例如:[revset-aliases] 'trunk()' = 'your-bookmark@your-remote'builtin_log():解析为present(@) | ancestors(immutable_heads().., 2) | trunk()(见 cli/src/config/revsets.toml)。它用作revsets.log的默认值——即jj log在未指定修订或路径时显示的提交集合。jj log的默认 revset 读取逻辑见 cli/src/commands/log.rs:未指定修订与路径时,从设置读取revsets.log字符串并解析求值。builtin_immutable_heads():解析为trunk() | tags() | untracked_remote_bookmarks() | untracked_remote_tags()(见 cli/src/config/revsets.toml),用作下面immutable_heads()的默认定义。不建议重定义此别名,推荐改为重定义immutable_heads()。immutable_heads():不可变提交集合的头(不是"不可变的头")。默认解析为trunk() | tags() | untracked_remote_bookmarks() | untracked_remote_tags()。实际定义为builtin_immutable_heads(),可按需覆盖。不可变提交的完整集合是::immutable_heads()(即immutable())。详见不可变提交集合。immutable():jj视为不可变的提交集合,等价于::(immutable_heads() | root())。不建议重定义此别名。注意修改它不会改变提交是否不可变,要改变需编辑immutable_heads()。mutable():jj视为可变的提交集合,等价于~immutable()。不建议重定义此别名。注意修改它不会改变提交是否不可变,要改变需编辑immutable_heads()。visible():可见提交集合,解析为::visible_heads()。除非 revset 包含隐藏提交,否则与all()相等。hidden():隐藏提交集合,解析为~visible()。除非 revset 包含隐藏提交,否则为空。注意这不是所有曾经可见提交的集合。
上述内置别名与revsets配置节(如revsets.log、revsets.log-graph-prioritize = "present(@)"、revsets.arrange、revsets.converge、revsets.fix、revsets.run、revsets.sign等)共同定义在 cli/src/config/revsets.toml 中。该文件头部有明确注释:"修改/新增这些别名时,请确保同步更新 docs/revsets.md 的文档"——文档与实现的一致性由此得到保障。
实战示例(Examples)
展示工作副本提交的父提交(类似git log -1 HEAD):
jj log -r @-展示工作副本的所有祖先(类似普通git log):
jj log -r ::@展示不在任何远程书签上的提交:
jj log -r 'remote_bookmarks()..'展示不在origin上的提交(如果你还有其他远程如fork):
jj log -r 'remote_bookmarks(remote=origin)..'展示仓库中的初始提交(Git 称之为 "root commits"):
jj log -r 'root()+'展示一些重要提交(类似git log --simplify-by-decoration):
jj log -r 'tags() | bookmarks()'展示通向工作副本的本地提交,以及这些提交的后代:
jj log -r '(remote_bookmarks()..@)::'展示作者为 "martinvonz" 且描述包含单词 "reset" 的提交:
jj log -r 'author(*martinvonz*) & description(*reset*)'小结
revset 是 jj 中最强大也最需要精确掌握的查询语言:符号解析遵循"标签 → 书签 → ID 前缀"的固定优先级,可通过commit_id()/change_id()等函数强制指定命名空间;运算符体系(-/+邻居、::/..区间、~/&/~/|集合运算)由 lib/src/revset.pest 的词法规则与 lib/src/revset_parser.rs 的 Pratt 优先级表决定,且..对左侧并集不分配;丰富的函数族(图遍历、ID/书签/标签、图论、内容元数据)配合字符串/日期模式与可覆盖的内置别名,让 "选择哪些提交" 这件事变得既可读又可控。掌握本文的运算符示例与别名机制,你就能在脚本与日常命令中写出精确、稳健的 revset 表达式。
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考