GitHub - 0WD0/majutsu: Majutsu! Magit for jujutsu

jujutsu

名字来源:ma(取自 Emacs,象征“magic/majutsu”的前缀)+ jujutsu → majutsu。

与 Magit 的关系:Majutsu 并非“Magit + Jujutsu”的直拼;与 Magit 的联系来自相同的拼接形式与相近语义(Magic ↔ 魔術),因此形成呼应与致敬。

语义说明:

  • Magic 在中文可译为“魔法/魔术”。
  • Jujutsu 在本项目语境指版本控制系统 Jujutsu(jj);在日语里“呪術”(jujutsu)意为“咒术”,与“魔法”同属玄术范畴;另有同音“柔術”(武术),此处不取其义。
  • Majutsu(魔術,majutsu)在日语中意为“魔术、法术”,与 Magic 相呼应。

项目定位:Majutsu 是 jj-mode.el 的一个 fork,目标是把 Emacs 式、Magit 风格的“魔法般”交互带到 Jujutsu(jj)上。

在 emacs-china 上被人说不应该取消 fork 链接了,让我难受了好一会儿

Components

TODO majutsu-jjdescription

我需要给 jjdescription 手动做一下美化!

需要让他不被 better-jumper 记录

TODO majutu-interactive

借鉴 magit-apply 的实现,然后 jj 还有非常重要的功能,它支持修改已提交的 commit

目标是实现 squash split commit restore 的 -i 功能,也就是 magit-apply 对应的局部 patch 操作功能。

我打算忽略 commit 的 -i 功能,因为局限性很大,而且会改变当前 working copy,不适合基于 region 的操作。

我知道怎么实现 split 的功能了!!!

magit manual link

我们可以在 diff section 上设计两个命令

一个是选中部分 split 出来作为上/下一个 rev(也就是 split)

一个是命令能让小 patch 在前/后 rev 直接传递(也就是 squash)

目前我觉得只能通过 --tool--config 来实现

—config <NAME=VALUE>

Additional configuration options (can be repeated)

The name should be specified as TOML dotted keys. The value should be specified as a TOML expression. If

string value isn’t enclosed by any TOML constructs (such as array notation), quotes can be omitted.

我觉得我可以在临时生成的 —config 参数里里写死 patch

然后可以用这个临时配置

放在 /tmp/jj-applypatch.toml

[merge-tools.applypatch]
program = "patch"
edit-args = ["-p1", "-d", "$right", "-i", "/tmp/foo.patch", "-s"]

这个配置写法是正确的,但是需要注意一点的是,我需要 apply 的 patch 是新的 commit 中的反向变更(对于 commit 命令来说)

然后直接 commit -i 的操作选中的部分是前一个 commit 中的正向变更

我应该怎么设计才是合理的?

codex resume 019abdb6-c030-7361-a9ea-9cd0fedab0aa

STRT majutsu-workspace

我们需要做 workspace 支持,类似于 magit-worktree

codex resume 019b13f9-7c2a-7ae0-bebd-227f99b7a2f6

对应到 change-id qkzxqpmn

TODO majutsu-evolog

我需要一种新的 log buffer !

在这里查看一个 working copy 的变更过程应该是非常有用的功能!

STRT majutsu-op

我希望它和 majutsu-process 可以结合起来

</home/disk/Dev/jj/docs/templates.md>

related: operation 应该有一个对应的 operationdiff 类型!

我们能比较 diff 但是这个 diff 不是在 operation 类型中的,而是在 不同的 op 命令中默认输出的

比如说 op show 就能用 -p 来控制是否显示:

Changed commits:
Changed working copy default@:
Changed local bookmarks:
Changed tags:
Changed remote bookmarks:

这些内容我们都希望能控制 template!

这不能在 -T 做到,有没有可能做到?

jj-vcs/jj#6980 FR: `jj op show` should take templates (`-T` argument, like `j…

但是控制的还是 operation 类型的样式,不是 diff 的样式

想要到源码里看看

</home/disk/Dev/jj/cli/src/commands/operation/diff.rs>

这里在计算 commit diff。

关于 op diff

</home/disk/Dev/jj/cli/src/commands/operation/diff.rs>

现在还没有 template 实现!

它的 diff 首先是 commit 层面的 diff

majutsu-op-log

重构后已经有了基本的实现

majutsu-diff

我的设计还不够充分!!!!!!!

rev 参数 和 样式参数 需要分离!

git 是直接把 revision 当作参数的,然后 file 用 — 放在最后输入;

jj 不一样,它把 filesets 作为参数,revset 作为参数,所以这里有一点区别。

我需要支持 diff 的 template 吗?

wash

性能优化的主要手段

diff buffer

应该用几个 insert section 的 hook 里的函数实现

TODO majutsu-diff-dwim

需要先设计一下行为

  1. 没有 transient-prefix
    1. 接受 region sections ,构造使用 -f -t 参数
    2. 在 jj-commit section 上,使用 -r rev
    3. 不在 jj-commit section 上,使用 -r @ 参数
  2. 有 transient-prefix
    1. 支持用 -o 通过 toggle selection 手动构造 revset
    2. 支持 -f -t ,这种方式方便中途移开去其他地方找提交
    3. 支持用 -r 手动输入 revsets

DONE majutsu-template

我设计了一种 jujutsu 模板语言的 DSL

历时两天实现了 MVP

能否利用 —config flag?

—config <NAME=VALUE>

Additional configuration options (can be repeated)

The name should be specified as TOML dotted keys. The value should be specified as a TOML expression. If string value isn’t enclosed

by any TOML constructs (such as array notation), quotes can be omitted.

Insight

IDEA 使用 jj-evolog 来对特定 filesets 查看演变记录的思考

我觉得这完全是很有可能的,但是稀疏log貌似不是 jj 的原生功能,

一个妥协之计是在 template 里把不相关的文件筛选掉。

TODO 研究一下 evolog 如何实现

和正常的 log 不同,evolog 中 change-id 不重要,只需要显示 commit-id 即可

然后其它都差不多

使用的是 CommitEvolutionEntry 类型

包含一个 commit 和一个 operation

这么说起来其实先做一下 op-log 也行?

TODO 想要在各种地方实现 magit-diff-visit-file

jj file show 来得到临时文件

TODO 关于 magit-files 中对应功能的想法

当前文件的 diff

magit-diff-buffer-file

可能挺好用

现在想了一下可以用 jj log -r files(expression) 这种东西来得到类似 magit-log-buffer-file 的功能,

甚至还能保留 graph 形状。

magit-log-trace-definition

这个函数不知道干啥用的,没有正常工作过

TODO majtusu-goto-prev / majutsu-edit-prev

majutsu-goto-prev / majutsu-goto-next 的功能上

注意这东西和 jj-prev 和 jj-next 的目标不一样

我们可以用 jj-log 来解析 revsets

TODO 如何在 merge 之前先自动解决 jj 能自动解决的冲突?

我现在发现 jj 自动进行的冲突解决比我想象的智能,

我在重构代码的时候(整行的移动),貌似不会出现冲突?

但是如果我用 resolve,用其他工具打开(比如说 meld),

我就需要手动合并冲突文件(left)中实际上不冲突的部分到 output,

我觉得这很没有意义,需要一个解决方案能让我直接得到已经自动解决了可以自动解决的冲突的 left

Issues

TODO majutsu-new-dwim

现在在未添加 rev 参数的时候会退到 @

实际上应该先考虑当前 jj-commit section

TODO 支持 magit-show-commit 并对 change-id 提供支持

因为 jj 使用的 change-id 的确是不会变的

TODO jj-interdiff 支持

TODO jj file annotate 支持(类似 git 的 blame)

AnnotationLine Type 记录了这一行的 original_line_number 元数据

</home/disk/Dev/jj/docs/templates.md>

TODO 用 pop-to-buffer-same-window 跳到diff buffer之后 quit 到 log buffer 后 margin 丢失

不太重要,暂时搁置

TODO jj-simplify-parents command support

这种小功能,不知道怎么处理,感觉不是很常用,我需要绑到单独的键上吗?

TODO revset builder

codex resume 019aa1d9-0262-7c31-aa61-be1ae1635008

不太清楚怎么兼容手动输入和可视化选择

TODO fileset builder

先实现一下可以让用户输入 fileset 吧

TODO 换行显示 log 中的 desc

TODO 支持 jj-tag

DONE diffstat 中路径过长 ATTACH

通过直接找 file header 解决的问题


DONE majutsu-jump-to-diffstat-or-diff 对 rename 条目不能正常工作

DONE 直接用 file section,支持跳转到文件

DONE diffstat 解析出错 ATTACH

终端中理想的解析(有 binary 还有 rename)


目前 majutsu 的解析


magit 的解析


可见主要问题在于 (binary) 格式的不同,

然后这个 | 的中文对齐不太确定需不需要管。

related issue

jj-vcs/jj#7218 diff —stat output visual details for binary files

DONE jj git push --allow-new is deprecated

DONE 要是能在 redo / undo 的时候回显它到底干了什么就好了

通过整个 process 模块解决了这个需求,

虽然我还没怎么 review 过,但是现在能跑。

DONE 重构 revision-section 结构

我觉得我们可能不需要同时拿到 commit-id 和 change-id

只需要在 hidden 或者 divergent 的时候记录 commit-id ,然后其他时候记录 change-id 就行!

记录 change-id 的目的是防止外部修改导致当前缓存的 hash 失效,会导致出现 divergent 问题

DONE --ignore-immutable support

我觉得这可能也是比较重要的功能?

可以加一个统一的 flag 来控制这个

DONE diff 解析有非常严重的解析性能问题

magit 可以在3秒内解析25万行的diff!

DONE 多 workspace 下 goto current 会出现问题!

现在是直接跳到 @ ,显然是不对的。。。

DONE flatten log template

∅ U+2205 Empty Set:数学上表示“空”,含义最贴合“无描述”。等宽终端普遍支持,宽度=1。

⌀ U+2300 Diameter Sign:视觉像被划掉的圆,容易读作“空”,常见于工程图,等宽字体支持度也不错。

Ø U+00D8 Latin Capital O with Stroke:与“空 / 无”关联明显,但有时被当作字母 O,可能在排序或搜索时产生歧义。

□ U+25A1 White Square:表示“留空 / 待填”,字体兼容好,但语义不如“∅”直观指向“无”。

🚫 U+1F6AB Prohibited:语义清晰,但宽度在等宽终端可能占 2;一些轻量终端或日志工具对 emoji 支持一般。

这东西其实可以做的很美观

DONE 表示工作区的 default@ 也会被认为是 bookmark!

问题在于,我在显示 log 行元素的哪里没有把 bookmarks 和 其他refs 分开,全部放到 refs 里了

DONE 除了 log-display-function 和 message-display-function 我还想要给其他组件预设显示函数

codex resume 019af299-08ff-7870-97e2-dcdde47b5089

DONE majutsu-jump-to-diffstat-or-diff

修复只能单项跳转的问题:

codex resume 019ac604-37d9-7f50-842f-c7f50d47496c

DONE 各种 refresh 没有确保当前的 buffer 正确

changeid: zprvnsxy

codex resume 019aca6b-5cb2-76e0-9392-d7ec4c3ef42a

DONE diff section refine

解决了当前section的背景样式丢失的问题

changeid: usxpzopo

codex resume 019ac5cc-e08b-7b50-9cfc-b58ebf804753

DONE diffstat 解析

叫它换用 rx 解析,然后就一下就写对了

changeid: owwuopzt

codex resume 019ac4fe-148c-7e52-b57d-9b8c724349f9

DONE with-editor 集成

关于 with-editor 的理解还是放在 with-editor 比较好

with-editor-server-window-alist

文件名和想让他运行的函数的对应

我现在运行的函数是 majutsu—with-editor-display-buffer

windows 下 with-editor 中的 start-file-process 不起作用

的确就是那个括号的问题,我让它在 windows 下强制删括号了,就能正常工作了

可能有更优雅的解决方案,但是我不想管了

with-editor 本身存在问题

最后的解决方案是不用 switch-to-buffer 改用 pop-to-buffer

在 log buffer 打开 message/diff buffer 再关掉,可以回到 log buffer 但是这时候再 quit-window 就不会自动删除 window 了

直接由 pop-to-buffer 得到的 window

((quit-restore window window #<window 3 on majutsu.el> #<buffer *majutsu-log:majutsu*>))

调用 diff 后的 window parameter

((quit-restore-prev other
                    (#<buffer *majutsu-log:majutsu*> 1 #<marker at 11 in
                              *majutsu-log:majutsu*> 84)
                    #<window 232 on *majutsu-diff*> #<buffer *majutsu-diff*>)
 (quit-restore window window #<window 3 on majutsu.el> #<buffer *majutsu-log:majutsu*>))

调用 message buffer 之后的 window parameter

(quit-restore other
              (#<buffer *majutsu-log:majutsu*> 1 #<marker at 11 in *majutsu-log:majutsu*> 84)
              #<window 264 on editor-67yjNs.jjdescription> #<buffer editor-67yjNs.jjdescription>)

关掉 diff 之后的 log buffer

((quit-restore-prev)
 (quit-restore other
               (#<buffer majutsu.el> 30168 #<marker at 30965 in majutsu.el> 84)
               #<window 240 on *majutsu-log:majutsu*> #<buffer *majutsu-log:majutsu*>))

关掉 message buffer 之后的 log buffer

((quit-restore-prev)  (quit-restore))

这个可以用来记录日志

(defvar my/quit-restore-log nil)
 
(defun my/track-quit-restore (orig window parameter value &rest args)
  (when (memq parameter '(quit-restore quit-restore-prev))
    (let ((trace (with-temp-buffer
                   (backtrace)
                   (buffer-string))))
      (push (list :time (current-time-string)
                  :buffer (window-buffer window)
                  :window window
                  :parameter parameter
                  :value value
                  :stack trace)
            my/quit-restore-log)
      (message "set-window-parameter %s -> %S\n%s"
               parameter value trace)
      ;; (debug) ; 若希望直接进入调试器
      ))
  (apply orig window parameter value args))
 
(advice-add 'set-window-parameter :around #'my/track-quit-restore)

DONE windows 下的 commit 字符编码问题

由于 jj 在 windows 上的性能优于 git

所以可能是有不少人是因为这一点来使用 jj 的

应该做好 windows 兼容

出现问题的地方是:子进程调用使用的 codepage 是固定的!并且默认不是 utf-8

问题:如果当前目录(default-directory)含有系统代码页无法表示的非 ASCII 字符,Emacs 在 Windows 上启动外部命令会失败,出现 “Spawning child process: Exec format error”。

原因:Emacs 在 Windows 调用程序时必须把程序路径和参数用系统代码页编码。你的系统代码页是 1252(西欧),而像 “zażółć gęślą jaźń” 这类波兰字符不能用 1252 表示,所以失败。很多 Windows 程序只支持当前代码页,不真正支持 UTF‑8/Unicode。

误区:在 Emacs 里改编码(如设为 cp1250)只影响缓冲区、文件和键盘/终端 I/O,不会改变操作系统的代码页,因此对启动子进程没用。

解决:把 Windows 的“非 Unicode 程序的语言”(系统代码页)改成能覆盖你语言字符的代码页(如 cp1250),或避免在路径中使用不在当前代码页内的字符,或使用真正支持 Unicode 的程序/环境。文末作者已将系统代码页全局改了,之后就能正常运行。

如何查询 codepage?

可按需查询两种不同的 code page(很重要的区别):

  • 系统 ANSI 代码页(ACP):很多非 Unicode 程序和 Emacs 在 Windows 下给子进程编码时受它限制
  • 控制台代码页(chcp):仅影响当前终端的输入/输出

查询方法:

  • PowerShell
    • 系统 ANSI(ACP):[System.Text.Encoding]::Default.CodePage 和 [System.Text.Encoding]::Default.WebName
    • 控制台:[Console]::OutputEncoding.CodePage
  • CMD
    • 控制台:chcp
    • 注册表(系统 ANSI 与控制台 OEM):reg query “HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage” /v ACP 和 /v OEMCP
  • Emacs 中(简便调用)
    • 控制台:M-: (shell-command-to-string “cmd /c chcp”) RET
    • 系统 ANSI:M-: (string-trim (shell-command-to-string “powershell -NoProfile -Command [System.Text.Encoding]::Default.CodePage”)) RET
  • 图形界面
    • 控制面板 → 区域 → 管理 → “非 Unicode 程序的语言”(这里显示/更改系统 ANSI 代码页;勾选“使用 Unicode UTF-8(测试版)”则为 65001)

更好的解决方案

借鉴 magit 的解决方案,用 with-editor

DONE 实现在 majutsu buffer 外的 majutsu-diff

DONE 复杂 graph 测试 ATTACH

无意间出现了问题!

这是我在测试 duplicate 功能时的截图


这个是相同template下的正确的 graph 输出

行首空格被吞掉了!

DONE bookmark 向前移动

需要使用 --allow-backwards -B flag

我觉得可以放到 M 上

DONE 不及时刷新可能会出现 change-id?? 的情况 ATTACH

我应该让他优先解析 change-id 的

我觉得我需要对这种情况设置一个比较的函数


Change Log

我需要准备一下周期发布

现在应该是v0.3.0

可以查看一下这个 keepachangelog

User Manaul

一些规范

fill-column 设置为 70

70 列主要来自 GNU/Emacs 的传统文本排版习惯:在 80 列终端里留出前导引号(邮件引用的 “> ” 多层缩进)、行首缩

进和对齐空间,还能降低长行 diff 带来的噪音。Emacs 自带的 many manuals / NEWS 也常用 70(或 72)列作为 fill-

column;Magit 文档沿用了这一约定,以保证在终端、邮件补丁、git diff 和 Texinfo 导出时都保持良好的可读性。

但是这对中文来说的确是短了一点,不太能对中文使用,因为中文一般来说不会真的占两个字符长度!然后中文行的长度就会显得短很多

sentence-end-double-space

那是为了写/导出手册时的“句间双空格”排版习惯:

  • Emacs 的文本约定里,句子末尾默认用两个空格分隔,便于 fill-paragraph、句尾检测和排版工具(如 Texinfo)准确断句。
  • Magit 手册是从 Org 导出 Texinfo 的长篇文档,保持双空格可以在 refilling 时减少把缩写/小数误判为句末的概率,

    也方便后续 diff(少因自动 reflow 产生大面积改动)。
  • 所以 magit.org 里句点后保留两个空格是有意遵循传统的 Emacs/Texinfo 文档格式,而不是手误。

要不要使用 fill-paragraph

不一定需要使用

Texinfo (and Info) ignore line breaks inside a paragraph, so authors

often “semantically line break” instead of using fill-paragraph.

Reasons the Magit docs do this:

  • Keep identifiers (like winner-undo) off the wrap boundary so they

    don’t get split with hyphens in the source.
  • Make diffs readable in version control: one clause/sentence per line

    means edits produce small, localized hunks.
  • Avoid reflow churn when only a word changes.
  • Preserve deliberate grouping (e.g., command + keybinding) that

    fill-paragraph might separate.

So the odd-looking breaks are a maintenance choice; the rendered output is unchanged. You can keep

using semantic line breaks if that workflow (clean diffs, no code-word splits) matters to you.