Recently, I wanted to search and replace a word in the contents of a single Jujutsu change. I had introduced a method in said change which I retroactively wanted to rename, and renaming the method with LSP is not reliable for Python code in my experience, which is what I was working on at the time.
The obvious solution (albeit a not really nice one) is to look at the change with jj show to see what it changed, and running a global find/replace in your editor, replacing only the locations that the change touched. Alternatively, I could have replaced all the occurrences of the word, including those I didn’t want, and then used the --into argument to jj absorb to tell it to only modify that one change, then abandon the leftover changes.
However, this is either still a lot of manual effort or feels really unclean for something that can be done with relatively minimal effort in Git: using git format-patch to export the patch file, editing it, and then resetting and re-applying the patch with git am.
Jujutsu currently has support for neither of these two commands, however it has something that comes really close to what I want to achieve with potentially less friction than Git: jj diffedit. This command lets you edit the contents of a single change. However, the builtin editor only lets you pick which lines to keep or discard, with no way to otherwise change or rearrange their contents, and external merge tools like KDiff3 (admittedly, the only one I tried), don’t really work well for this purpose.
However, it is possible to add custom external tools to use with jj diffedit via Jujutsu’s configuration file. Jujutsu supplies two directories to the tool: the state of the repository prior to the change to edit (“left”), and the state with it applied (“right”). It is then the responsibility of the tool to modify the “right” directory, which will form the new contents of the change. To make this generate a patch file and then open it in an editor is relatively straight-forward to stick together with a simple shell script, so that’s what I did.
This is the script I came up with. It can surely be improved a bit, but it works fine as-is and I have used it a couple times since – in fact, I used it while splitting the changes to the website for this very article.
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2025 Katalin Rebhan <[email protected]>set -e
if (( $# != 2 )); then
echo "Usage: $0 LEFT RIGHT" >&2
exit 1
fi
: ${EDITOR:=nano}
left="$1"
right="$2"
tmpdir="$(mktemp --directory)"
echo "Working directory: ${tmpdir}"
ln -s "$left" "$tmpdir"/a
ln -s "$right" "$tmpdir"/b
( cd "$tmpdir" && diff --new-file --text --unified --recursive a/ b/ ) \
> "$tmpdir"/current.patch || (( $? == 1 ))
cp "$tmpdir"/current.patch "$tmpdir"/orig.patch
"$EDITOR" "$tmpdir"/current.patch
cp -r "$right" "$tmpdir"/result
patch --reverse --directory="$tmpdir"/result --strip=1 \
< "$tmpdir"/orig.patch \
> /dev/null
patch --directory="$tmpdir"/result --strip=1 \
< "$tmpdir"/current.patch
mv "$right" "$tmpdir"/oldright
mv "$tmpdir"/result "$right"
rm -r "$tmpdir"
Notably, one thing it does is taking the “right” directory as a template, from which it first un-applies the original diff, then applies the modified version the user edited. This is because the “left” directory is marked read-only by Jujutsu, and I didn’t want to mark files writable while being careful not to touch other attributes.
To make this actually work, it’s necessary to register the tool with Jujutsu by editing its configuration file with jj config edit --user, adding the following snippet, with the file path adjusted to wherever you put it.
[merge-tools.patch]
program = "/Users/YOU/.local/bin/edit-patch"
edit-args = ["$left", "$right"]
After this, it’s possible to run jj diffedit with --tool=patch to open up your editor containing the patch for the selected change, and after saving and closing the editor, the change’s contents will be replaced with the edited patch. Perfect!