ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Jujutsu(jj)Git 专家实战指南:同仓共置、提交式暂存区与操作日志驱动的六类工作流升级

Jujutsu(jj)Git 专家实战指南:同仓共置、提交式暂存区与操作日志驱动的六类工作流升级 JujutsujjGit 专家实战指南同仓共置、提交式暂存区与操作日志驱动的六类工作流升级【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj作为 Git 的熟练用户你可能会问Jujutsujj到底带来了什么实际好处本文正是围绕这个问题展开的实战指南。它面向已经精通 Git 的开发者逐一讲解 Jujutsu 在共置仓库、暂存区替代、历史编辑、撤销、变更演化追踪、补丁栈维护六个场景中如何让日常工作更简单、更安全、更高效并配合当前仓库的源码实现说明这些能力背后的底层原理。读完本文你将能够把jj无缝接入现有 Git 工作流并用更少的命令完成过去需要多步操作才能完成的 Git 任务。同一仓库内与 Git 并排使用colocationJujutsu 与 Git 仓库存放在同一个目录中因此你可以在同一个工作区里同时使用jj和git命令这就是所谓的colocation共置工作区。如果你遇到某个场景用 Git 处理更顺手直接运行git命令即可处理完再回到jj如果发现某个场景缺少对应的jj支持还可以向项目提交功能请求。共置模式让迁移变得非常平滑你可以只为那些确实被改进的工作流采用 Jujutsu而不会失去你已经熟悉的 Git 命令和工具链。共置工作区的判定与创建从术语定义看当使用 Git 后端、且底层 Git 仓库的.git/目录与.jj/目录互为同级sibling时该工作区即称为 colocated参见 glossary.md 中的定义。用jj git init name或jj git clone URL创建的工作区默认就是共置的此时jj会在每次命令执行时自动从 Git 仓库导入import并导出export引用参见 git-compatibility.md。共置模式对构建工具等期望看到一个 Git 仓库的工具非常友好。文档允许在共置工作区中任意混用jj与git命令但更推荐的做法是大部分读操作如git log、git diff用 Git写操作交给jj。原因在于jj没有当前分支的概念执行jj命令后 Git 仓库通常处于 detached HEAD 状态因此在运行会修改仓库的 Git 命令前你可能需要用git switch先告诉 Git 当前分支是什么。你还可以用jj git colocation status查看共置状态用jj git colocation enable/jj git colocation disable在共置与非共置之间切换详见 git-compatibility.md 中Converting a workspace into a colocated workspace一节。混用命令时的撤销保障共置模式有一个非常实用的特性可以用jj undo和jj op restore撤销修改性 Git 命令的后果。在jj op log中由 Git 命令造成的变更会以一条名为 import git refs 的操作呈现。这意味着即使是 Git 侧的误操作也能纳入 Jujutsu 的统一撤销体系。用提交取代 Git 的暂存区index/staging areaJujutsu 没有 Git 那样的 index/暂存区。因为重写提交在 Jujutsu 中既快又简单所以很自然地用提交来充当暂存区的角色。你不需要再为操作暂存区使用独立命令git add、git rm --cached而是用jj split和jj squash来移动进行中的工作移动它们就像移动已完成的工作一样容易# 将工作区提交拆分成两个连续提交file1 和 file2 进入第一个提交 jj split file1 file2 # 或者交互式地选择要拆分的变更 jj split # 把 file3 中的变更移入父提交 jj squash file3 # 或者交互式地选择 jj squash -i拆分split的源码实现视角jj split的完整参数定义位于 cli/src/commands/split.rs。从源码可以看到几个关键行为默认拆分目标是当前工作区提交--revision的默认值为未提供文件参数时会自动进入交互模式interactive || paths.is_empty()。拆分后选中变更留在原提交剩余变更进入新的子提交也可以用--parallel让两部分变成兄弟提交。描述处理有明确规则如果被拆分的提交有描述会分别询问两个提交的描述如果没有描述则第二个提交不获得描述只为第一个提交询问split.rs 的注释描述了这一点。拆分空提交不被支持因为同样的效果可以用jj new实现。挤压squash的源码实现视角jj squash的完整参数定义位于 cli/src/commands/squash.rs。从注释和参数可以确认不带任何选项时把变更从工作区提交移动到父提交-r指定从某个修订移动到其父提交若目标提交有多个父提交即合并提交则会报错。--from/--into别名-t/to可显式指定源与目标省略时默认是工作区提交。例如jj squash --into --把变更从工作区提交移动到祖父提交。移动后若源提交相对于其父提交为空、且未指定--keep-emptied源提交会被abandon废弃。若源与目标都有非空描述会询问合并后的描述若其中一方为空则直接采用另一方的。每次 squash 完成后Jujutsu 会自动rebase 所有后代提交源码中rebase_descendants()的调用可见于 squash.rs这也是下文自动且更安全的历史编辑的基础。这正是 Git 专家最需要转变的观念不需要 index提交本身就是可移动、可拆分、可合并的暂存区。自动且更安全的历史编辑如果你经常执行 amend、reorder、squashJujutsu 通常能用更少的命令完成同样的操作。假设你想修改一个较早的提交。用 Git 可能需要三步git add file1 file2 git commit --fixup abc git rebase -i --autosquash而用 Jujutsu你只需直接把变更 squash 进目标提交所有后代提交会自动 rebase 到修改后的提交之上jj squash --into abc file1 file2从 squash.rs 的文档注释可以确认--into的作用是把变更移入指定修订而 squash 完成后lib/src/rewrite.rs 中的rebase_descendants()机制会负责把受影响的后代提交全部自动重放。这意味着你永远不会因为改写中间提交而弄丢下游工作也不需要像 Git 那样手动计算 fixup 顺序或担心rebase -i后的冲突残留。这种自动 rebase 后代是 Jujutsu 的全局设计无论 amend、rebase、split 还是 squash只要一个提交被重写它的所有后代都会自动跟上参见 git-comparison.md 中关于 Descendant commits are automatically rebased 的概述。比 reflog 更强大的撤销操作日志operation logGit 的 reflog 功能强大但它是按引用per-ref记录的当涉及多个 ref 和多个操作时使用起来比较笨拙。Jujutsu 的operation log操作日志记录的是整个仓库的状态每一次变更都是一条可以检视的操作你可以用一条命令恢复到任意更早的状态。从 glossary.md 的定义看操作日志本身是由 operation 对象构成的 DAG有向无环图与提交形成的历史 DAG 同构——顺序发生的操作形成一条直线并发发生的操作则形成分支与合并。这种设计让操作日志能够原子性地记录所有 ref 的更新而不是像 Git reflog 那样按 ref 分开记录。操作日志的常见用法jj undo一步撤销最后一次操作无需费心判断要 reset 哪个 ref。可以重复执行jj undo继续向过去回溯。jj op log -p显示带有 diff 的操作让你能检视当时到底发生了什么。--at-operation ID让命令在仓库处于过去某个状态的前提下运行即如果仓库停留在之前的某个状态会怎样。undo 的源码实现视角jj undo的实现位于 cli/src/commands/undo.rs其文档注释确认了关键行为连续执行jj undo会逐步恢复到越来越旧的操作与之互补的jj redo则在一系列 undo 之后向未来方向前进。实现上有几个值得 Git 专家注意的细节不能撤销根操作jj undo对根操作root operation会报错Cannot undo root operation对合并操作merge operation会建议改用jj op restore。撤销栈的跳跃优化源码中有一段精心设计的逻辑undo.rs避免重复执行jj new ; jj undo时形成不断增长的撤销链——如果目标操作本身是 undo 操作会直接恢复到最原始的操作从而跳过旧的撤销栈。撤销 push 的警告撤销 push 类操作往往会导致 bookmark 冲突因此源码会提示Undoing a push operation often leads to conflicted bookmarks并建议立即运行jj redoundo.rs。从 git-comparison.md 的对比表述看操作日志之于 reflog 的改进正如当年 Subversion 之于 RCS 的按文件历史——从按 ref 记录升级为原子地记录整个仓库。它还驱动了撤销功能的实现。演化日志evolog追踪单条变更的完整历史Git reflog 展示的是 ref 如何随时间移动却很难看出某个特定提交commit是如何随时间演化的。Jujutsu 的演化日志evolog恰恰回答这个问题每当一条变更change被重写更新都会在 evolog 中可见。你可以用 evolog 找到某个历史版本然后用jj restore把完整或部分内容恢复到当前版本。从 Change ID 到演化追踪理解 evolog 需要先理解 Jujutsu 的双 ID 模型参见 glossary.md 与 glossary.mdChange ID标识一条变更change的稳定身份。重写提交修改描述、rebase 等会产生新的 commit ID但 change ID 一般保持不变。Commit ID标识某个具体快照使用 Git 后端时就是 Git 的 commit ID。evolog 正是沿着change ID 的 predecessor 链回溯展示这条变更经历过的所有版本。实现上cli/src/commands/evolog.rs 调用jj_lib::evolution::walk_predecessors来遍历前置版本jj evolog支持-n/--limit限制条数、-G/--no-graph切换扁平列表、-T/--template自定义渲染模板以及-p/--patch显示与上一版本的 diffevolog.rs。值得注意的是-p模式下如果新旧版本父提交不同会临时 rebase 到新版本的父提交上再计算 diff避免无关变更污染差异视图evolog.rs。用 jj restore 恢复旧版本内容jj restore的完整行为定义在 cli/src/commands/restore.rs不带参数时jj restore从父提交恢复内容到工作区提交类似于jj abandon但会保留描述等元数据。--from指定源默认工作区--into别名-t/to指定目标默认工作区两者都省略时等价于jj restore --changes-in 即撤销当前提交相对父提交合并结果的变更。只提供文件参数时仅恢复这些路径的内容-i可交互式选择要恢复的部分。恢复后同样会自动 rebase 后代提交--restore-descendants可以在重放后代时保留其内容而非保留 diff。配合 evolog你就能实现找到旧版本 → 恢复全部或部分内容的完整回滚闭环。jj absorb让补丁栈patch stack的更新更轻松当需要修改一摞提交stack中的多个提交时Git 要求你在运行git rebase --autosquash之前为每一个要修改的提交至少执行一次git commit --fixup ID。jj absorb适用于这样的场景你在工作区里做了一些小的修正希望把它们吸收进最近的若干提交。它自动把工作区中的每处变更移动到上一次修改了该行的那个提交中。参数与行为jj absorb的参数定义在 cli/src/commands/absorb.rs--from源修订默认工作区提交。--into别名-t/to目标修订集合默认mutable()只会考虑源提交的祖先。paths只吸收这些路径的变更。-i/--interactive交互式选择要吸收的部分--tool可指定 diff 编辑器隐式启用交互。如果源提交的所有变更都被吸收且源提交没有描述源提交会被 abandon。底层算法基于标注annotation的 hunk 归属jj absorb的核心算法位于 lib/src/absorb.rs模块注释将其概括为把单个源提交中的变更拆分到最相关的祖先提交中将其吸收掉。核心流程在split_hunks_to_treeslib/src/absorb.rs对源提交相对其父提交的每个文件 diff计算父文件内容的行级标注annotation——即每一行最后一次是由哪个祖先提交修改的使用FileAnnotator。将 diff 切成 hunk根据标注区间把每个 hunk 归属到对应的祖先提交。为每个目标提交构造一棵父内容 选中 hunk的新树最终由absorb_hunkslib/src/absorb.rs合并进目标提交并重写后代。明确的边界它不解决所有情况文档明确指出了jj absorb的局限如果栈中多个提交都修改了工作区中改动的同一行则该变更不会被移动。从源码看当某个 hunk 无法被无歧义地归属到单个标注区间时例如跨两个区间的新增行、或修改了被遮盖的行split_file_hunks会跳过它lib/src/absorb.rs 中有大量针对ambiguous情况的判定与单元测试。这些边界情况在 lib/src/absorb.rs 的test_split_file_hunks_*系列测试中被系统地覆盖。所以正确的预期是jj absorb帮我们处理掉琐碎的常见情况剩余无法自动归属的变更会留在工作区由你决定如何 squash。用jj op show -p检视 absorb 的改动由于jj absorb本身也是一次 operation你可以用jj op show -p以 diff 形式检视它到底修改了哪些提交cli/src/commands/absorb.rs 的文档注释明确推荐了这一检视手段——这再次体现了操作日志 可检视操作这一统一设计。总结六类工作流升级一览场景Git 传统做法Jujutsu 做法收益暂存/挑选变更git add -pgit commitjj split/jj squash -i无需暂存区提交即暂存区修改较早提交git commit --fixuprebase -i --autosquashjj squash --into abc单条命令后代自动 rebase撤销操作定位并git reset对应 refjj undo/jj op restore仓库级原子撤销查看发生了什么逐个 ref 翻阅 reflogjj op log -p带 diff 的全局操作历史追踪单条变更演化难以从 reflog 还原jj evologjj restore按 change ID 完整回溯更新补丁栈为每个提交--fixup再 autosquashjj absorb自动按行归属到最近修改点对于 Git 专家而言Jujutsu 的价值不是另一个版本控制工具而是一套保留了 Git 兼容层共置仓库、Git 后端、jj git命令族的、以提交和操作日志为核心的新交互模型。你可以从 colocation 开始逐步迁移把上述六类高频工作流逐步交给jj其余场景继续使用熟悉的git命令——二者可以在同一仓库里长期和平共处。延伸阅读Git 兼容性详情colocation、导入导出、禁用共置等Jujutsu 与 Git 的概念对比操作日志与撤销机制术语表change / commit / operation / rewrite 等核心概念jj absorb的命令测试cli/tests/test_absorb_command.rsjj squash与jj split的命令测试cli/tests/test_squash_command.rs、cli/tests/test_split_command.rs【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表