just playing with tangled
1# Tutorial
2
3> **Hint:** This tutorial has become somewhat out of date. Many people find
4> the alternative (not quite finished) [tutorial by Steve
5> Klabnik](https://steveklabnik.github.io/jujutsu-tutorial/) helpful.
6
7This text assumes that the reader is familiar with Git.
8
9## Preparation
10
11If you haven't already, make sure you
12[install and configure Jujutsu](install-and-setup.md).
13
14## Cloning a Git repository
15
16> **Hint:** Most identifiers used in this tutorial will be different when you
17> try this at home!
18
19Let's start by cloning GitHub's Hello-World repo using `jj`:
20
21```shell
22# Note the "git" before "clone" (there is no support for cloning native jj
23# repos yet)
24$ jj git clone https://github.com/octocat/Hello-World
25Fetching into new repo in "/tmp/tmp.O1DWMiaKd4/Hello-World"
26bookmark: master@origin [new] untracked
27bookmark: octocat-patch-1@origin [new] untracked
28bookmark: test@origin [new] untracked
29Setting the revset alias "trunk()" to "master@origin"
30Working copy (@) now at: kntqzsqt d7439b06 (empty) (no description set)
31Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
32Added 1 files, modified 0 files, removed 0 files
33$ cd Hello-World
34```
35
36Running `jj st` (short for `jj status`) now yields something like this:
37
38```shell
39$ jj st
40The working copy has no changes.
41Working copy (@) : kntqzsqt d7439b06 (empty) (no description set)
42Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
43```
44
45Let's look at that output as it introduces new concepts. You can see two
46commits: Parent and working copy. Both are identified using two separate
47identifiers: the "change ID" and the "commit ID".
48
49The parent commit, for example, has the change ID `orrkosyo` and the commit ID
50`7fd1a60b`.
51
52> **Git users:** The commit ID/hash is what you're used to from Git and should
53> match what you see when you look at the repository using `git log` in a Git
54> checkout of the repository.
55> The change ID however, is a new concept, unique to Jujutsu.
56
57We can also see from the output above that our working copy is an actual commit
58with a commit ID (`d7439b06` in the example). When you make a change in the
59working copy, the working-copy commit gets automatically amended by the next
60`jj` command.
61
62> **Git users:** This is a huge difference from Git where the working copy is a
63> separate concept and not yet a commit.
64
65## Changes
66
67A change is a commit that can evolve while keeping a stable identifier (similar
68to Gerrit's Change-Id). In other words: You can make changes to files in a
69change, resulting in a new commit hash, but the change ID will remain the same.
70
71You can see that our clone operation automatically created a new change:
72
73```shell
74Working copy : kntqzsqt d7439b06 (empty) (no description set)
75```
76
77This new change has the ID `kntqzsqt` and it is currently empty (contains no
78changes compared to the parent) and has no description.
79
80## Creating our first change
81
82Let's say we want to edit the `README` file in the repo to say "Goodbye"
83instead of "Hello". Start by describing the change (adding a commit message) so
84we don't forget what we're working on:
85
86```shell
87# This brings up $EDITOR (or `pico` or `Notepad` by default).
88# Enter something like "Say goodbye" in the editor and then save the file and close
89# the editor.
90$ jj describe
91Working copy (@) now at: kntqzsqt e427edcf (empty) Say goodbye
92Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
93```
94
95Now make the change in the README:
96
97```shell
98# Adjust as necessary for compatibility with your flavor of `sed`
99$ sed -i 's/Hello/Goodbye/' README
100$ jj st
101Working copy changes:
102M README
103Working copy (@) : kntqzsqt 5d39e19d Say goodbye
104Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
105```
106
107Note that you didn't have to tell Jujutsu to add the change like you would with
108`git add`. You actually don't even need to tell it when you add new files or
109remove existing files. To untrack a path, add it to your `.gitignore` and run
110`jj file untrack <path>`.
111
112Also note that the commit hash for our current change (`kntqzsqt`) changed from
113`e427edcf` to `5d39e19d`!
114
115To see the diff, run `jj diff`:
116
117```shell
118$ jj diff --git # Feel free to skip the `--git` flag
119diff --git a/README b/README
120index 980a0d5f19...1ce3f81130 100644
121--- a/README
122+++ b/README
123@@ -1,1 +1,1 @@
124-Hello World!
125+Goodbye World!
126```
127
128Jujutsu's diff format currently defaults to inline coloring of the diff (like
129`git diff --color-words`), so we used `--git` above to make the diff readable in
130this tutorial.
131
132As you may have noticed, the working-copy commit's ID changed both when we
133edited the description and when we edited the README. However, the parent commit
134stayed the same. Each change to the working-copy commit amends the previous
135version. So how do we tell Jujutsu that we are done amending the current change
136and want to start working on a new one? That is what `jj new` is for. That will
137create a new commit on top of your current working-copy commit. The new commit
138is for the working-copy changes.
139
140So, let's say we're now done with this change, so we create a new change:
141
142```shell
143$ jj new
144Working copy (@) now at: mpqrykyp aef4df99 (empty) (no description set)
145Parent commit (@-) : kntqzsqt 5d39e19d Say goodbye
146$ jj st
147The working copy has no changes.
148Working copy (@) : mpqrykyp aef4df99 (empty) (no description set)
149Parent commit (@-): kntqzsqt 5d39e19d Say goodbye
150```
151
152If we later realize that we want to make further changes, we can make them in
153the working copy and then run `jj squash`. That command squashes (moves) the
154changes from a given commit into its parent commit. Like most commands, it acts
155on the working-copy commit by default. When run on the working-copy commit, it
156behaves very similar to `git commit --amend`.
157
158Alternatively, we can use `jj edit <commit>` to resume editing a commit in the
159working copy. Any further changes in the working copy will then amend the
160commit. Whether you choose to create a new change and squash, or to edit,
161typically depends on how done you are with the change; if the change is almost
162done, it makes sense to use `jj new` so you can easily review your adjustments
163with `jj diff` before running `jj squash`.
164
165To view how a change has evolved over time, we can use `jj evolog` to see each
166recorded change for the current commit. This records changes to the working
167copy, message, squashes, rebases, etc.
168
169## The log command and "revsets"
170
171You're probably familiar with `git log`. Jujutsu has very similar functionality
172in its `jj log` command:
173
174```shell
175$ jj log
176@ mpqrykyp martinvonz@google.com 2023-02-12 15:00:22.000 -08:00 aef4df99
177│ (empty) (no description set)
178◉ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19d
179│ Say goodbye
180│ ◉ tpstlust support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1@origin b1b3f972
181├─╯ sentence case
182│ ◉ kowxouwz octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test@origin b3cbd5bb
183├─╯ Create CONTRIBUTING.md
184◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
185│ (empty) Merge pull request #6 from Spaceghost/patch-1
186~
187```
188
189The `@` indicates the working-copy commit. The first ID on a line
190(e.g. "mpqrykyp" above) is the change ID. The second ID is the commit ID. You
191can give either ID to commands that take revisions as arguments. We will
192generally prefer change IDs because they stay the same when the commit is
193rewritten.
194
195By default, `jj log` lists your local commits, with some remote commits added
196for context. The `~` indicates that the commit has parents that are not included
197in the graph. We can use the `--revisions`/`-r` flag to select a different set
198of revisions to list. The flag accepts a ["revset"](revsets.md), which is an
199expression in a simple language for specifying revisions. For example, `@`
200refers to the working-copy commit, `root()` refers to the root commit,
201`bookmarks()` refers to all commits pointed to by bookmarks (similar to Git's
202branches). We can combine expressions with `|` for union, `&` for intersection
203and `~` for difference. For example:
204
205```shell
206$ jj log -r '@ | root() | bookmarks()'
207@ mpqrykyp martinvonz@google.com 2023-02-12 15:00:22.000 -08:00 aef4df99
208╷ (empty) (no description set)
209◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
210╷ (empty) Merge pull request #6 from Spaceghost/patch-1
211◉ zzzzzzzz root() 00000000
212```
213
214The `00000000` commit (change ID `zzzzzzzz`) is a virtual commit that's called
215the "root commit". It's the root commit of every repo. The `root()`
216function in the revset matches it.
217
218There are also operators for getting the parents (`foo-`), children (`foo+`),
219ancestors (`::foo`), descendants (`foo::`), DAG range (`foo::bar`, like
220`git log --ancestry-path`), range (`foo..bar`, same as Git's). See
221[the revset documentation](revsets.md) for all revset operators and functions.
222
223> **Hint:** If the default `jj log` omits some commits you expect to see, you
224> can always run `jj log -r ::` (or, equivalently, `jj log -r 'all()'`) to see
225> all the commits.
226
227## Conflicts
228
229Now let's see how Jujutsu deals with merge conflicts. We'll start by making some
230commits. We use `jj new` with the `--message`/`-m` option to set change
231descriptions (commit messages) right away.
232
233```shell
234# Start creating a chain of commits off of the `master` bookmark
235$ jj new master -m A; echo a > file1
236Working copy (@) now at: nuvyytnq 00a2aeed (empty) A
237Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
238Added 0 files, modified 1 files, removed 0 files
239$ jj new -m B1; echo b1 > file1
240Working copy (@) now at: ovknlmro 967d9f9f (empty) B1
241Parent commit (@-) : nuvyytnq 5dda2f09 A
242$ jj new -m B2; echo b2 > file1
243Working copy (@) now at: puqltutt 8ebeaffa (empty) B2
244Parent commit (@-) : ovknlmro 7d7c6e6b B1
245$ jj new -m C; echo c > file2
246Working copy (@) now at: qzvqqupx 62a3c6d3 (empty) C
247Parent commit (@-) : puqltutt daa6ffd5 B2
248$ jj log
249@ qzvqqupx martinvonz@google.com 2023-02-12 15:07:41.946 -08:00 2370ddf3
250│ C
251◉ puqltutt martinvonz@google.com 2023-02-12 15:07:33.000 -08:00 daa6ffd5
252│ B2
253◉ ovknlmro martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6b
254│ B1
255◉ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f09
256│ A
257│ ◉ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19d
258├─╯ Say goodbye
259│ ◉ tpstlust support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1@origin b1b3f972
260├─╯ sentence case
261│ ◉ kowxouwz octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test@origin b3cbd5bb
262├─╯ Create CONTRIBUTING.md
263◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
264│ (empty) Merge pull request #6 from Spaceghost/patch-1
265~
266```
267
268We now have a few commits, where A, B1, and B2 modify the same file, while C
269modifies a different file. Let's now rebase B2 directly onto A. We use the
270`--source`/`-s` option on the change ID of B2, and `--destination`/`-d` option
271on A.
272
273```shell
274$ jj rebase -s puqltutt -d nuvyytnq # Replace the IDs by what you have for B2 and A
275Rebased 2 commits
276Working copy (@) now at: qzvqqupx 1978b534 (conflict) C
277Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
278Added 0 files, modified 1 files, removed 0 files
279There are unresolved conflicts at these paths:
280file1 2-sided conflict
281New conflicts appeared in these commits:
282 qzvqqupx 1978b534 (conflict) C
283 puqltutt f7fb5943 (conflict) B2
284To resolve the conflicts, start by updating to the first one:
285 jj new puqltuttzvly
286Then use `jj resolve`, or edit the conflict markers in the file directly.
287Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
288Then run `jj squash` to move the resolution into the conflicted commit.
289
290$ jj log
291@ qzvqqupx martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 1978b534 conflict
292│ C
293◉ puqltutt martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 f7fb5943 conflict
294│ B2
295│ ◉ ovknlmro martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6b
296├─╯ B1
297◉ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f09
298│ A
299│ ◉ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19d
300├─╯ Say goodbye
301│ ◉ tpstlust support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1@origin b1b3f972
302├─╯ sentence case
303│ ◉ kowxouwz octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test@origin b3cbd5bb
304├─╯ Create CONTRIBUTING.md
305◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
306│ (empty) Merge pull request #6 from Spaceghost/patch-1
307~
308```
309
310There are several things worth noting here. First, the `jj rebase` command said
311"Rebased 2 commits". That's because we asked it to rebase commit B2 with the
312`-s` option, which also rebases descendants (commit C in this case). Second,
313because B2 modified the same file (and word) as B1, rebasing it resulted in
314conflicts, as the output indicates. Third, the conflicts did not prevent the
315rebase from completing successfully, nor did it prevent C from getting rebased
316on top.
317
318Now let's resolve the conflict in B2. We'll do that by creating a new commit on
319top of B2. Once we've resolved the conflict, we'll squash the conflict
320resolution into the conflicted B2. That might look like this:
321
322```shell
323$ jj new puqltutt # Replace the ID by what you have for B2
324Working copy (@) now at: zxoosnnp c7068d1c (conflict) (empty) (no description set)
325Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
326Added 0 files, modified 0 files, removed 1 files
327There are unresolved conflicts at these paths:
328file1 2-sided conflict
329
330$ jj st
331The working copy has no changes.
332There are unresolved conflicts at these paths:
333file1 2-sided conflict
334Working copy (@) : zxoosnnp c7068d1c (conflict) (empty) (no description set)
335Parent commit (@-): puqltutt f7fb5943 (conflict) B2
336To resolve the conflicts, start by updating to it:
337 jj new puqltutt
338Then use `jj resolve`, or edit the conflict markers in the file directly.
339Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
340Then run `jj squash` to move the resolution into the conflicted commit.
341
342$ cat file1
343<<<<<<< Conflict 1 of 1
344%%%%%%% Changes from base to side #1
345-b1
346+a
347+++++++ Contents of side #2
348b2
349>>>>>>> Conflict 1 of 1 ends
350
351$ echo resolved > file1
352
353$ jj st
354Working copy changes:
355M file1
356Working copy (@) : zxoosnnp c2a31a06 (no description set)
357Parent commit (@-): puqltutt f7fb5943 (conflict) B2
358Conflict in parent commit has been resolved in working copy
359
360$ jj squash
361Rebased 1 descendant commits
362Working copy (@) now at: ntxxqymr e3c279cc (empty) (no description set)
363Parent commit (@-) : puqltutt 2c7a658e B2
364Existing conflicts were resolved or abandoned from these commits:
365 qzvqqupx hidden 1978b534 (conflict) C
366 puqltutt hidden f7fb5943 (conflict) B2
367
368$ jj log
369@ ntxxqymr martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 e3c279cc
370│ (empty) (no description set)
371│ ◉ qzvqqupx martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 b9da9d28
372├─╯ C
373◉ puqltutt martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 2c7a658e
374│ B2
375│ ◉ ovknlmro martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6b
376├─╯ B1
377◉ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f09
378│ A
379│ ◉ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19d
380├─╯ Say goodbye
381│ ◉ tpstlust support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1@origin b1b3f972
382├─╯ sentence case
383│ ◉ kowxouwz octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test@origin b3cbd5bb
384├─╯ Create CONTRIBUTING.md
385◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
386│ (empty) Merge pull request #6 from Spaceghost/patch-1
387~
388```
389
390Note that commit C automatically got rebased on top of the resolved B2, and that
391C is also resolved (since it modified only a different file).
392
393By the way, if we want to get rid of B1 now, we can run `jj abandon
394ovknlmro`. That will hide the commit from the log output and will rebase any
395descendants to its parent.
396
397## The operation log
398
399Jujutsu keeps a record of all changes you've made to the repo in what's called
400the "operation log". Use the `jj op` (short for `jj operation`) family of
401commands to interact with it. To list the operations, use `jj op log`:
402
403```shell
404$ jj op log
405@ d3b77addea49 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 3 milliseconds
406│ squash commit 63874fe6c4fba405ffc38b0dd926f03b715cf7ef
407│ args: jj squash
408◉ 6fc1873c1180 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 1 milliseconds
409│ snapshot working copy
410│ args: jj squash
411◉ ed91f7bcc1fb martinvonz@vonz.svl.corp.google.com 6 minutes ago, lasted 1 milliseconds
412│ new empty commit
413│ args: jj new puqltutt
414◉ 367400773f87 martinvonz@vonz.svl.corp.google.com 12 minutes ago, lasted 3 milliseconds
415│ rebase commit daa6ffd5a09a8a7d09a65796194e69b7ed0a566d and descendants
416│ args: jj rebase -s puqltutt -d nuvyytnq
417[many more lines]
418```
419
420The most useful command is `jj undo` (alias for `jj op undo`), which will undo
421an operation. By default, it will undo the most recent operation. Let's try it:
422
423```shell
424$ jj undo
425New conflicts appeared in these commits:
426 qzvqqupx 1978b534 (conflict) C
427 puqltutt f7fb5943 (conflict) B2
428To resolve the conflicts, start by updating to the first one:
429 jj new puqltuttzvly
430Then use `jj resolve`, or edit the conflict markers in the file directly.
431Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
432Then run `jj squash` to move the resolution into the conflicted commit.
433Working copy (@) now at: zxoosnnp 63874fe6 (no description set)
434Parent commit (@-) : puqltutt f7fb5943 (conflict) B2
435
436$ jj log
437@ zxoosnnp martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 63874fe6
438│ (no description set)
439│ ◉ qzvqqupx martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 1978b534 conflict
440├─╯ C
441◉ puqltutt martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 f7fb5943 conflict
442│ B2
443│ ◉ ovknlmro martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6b
444├─╯ B1
445◉ nuvyytnq martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f09
446│ A
447│ ◉ kntqzsqt martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19d
448├─╯ Say goodbye
449│ ◉ tpstlust support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1@origin b1b3f972
450├─╯ sentence case
451│ ◉ kowxouwz octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test@origin b3cbd5bb
452├─╯ Create CONTRIBUTING.md
453◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
454│ (empty) Merge pull request #6 from Spaceghost/patch-1
455~
456```
457
458As you can perhaps see, that undid the `jj squash` invocation we used for
459squashing the conflict resolution into commit B2 earlier. Notice that it also
460updated the working copy.
461
462You can also view the repo the way it looked after some earlier operation. For
463example, if you want to see `jj log` output right after the `jj rebase`
464operation, try `jj log --at-op=367400773f87` but use the hash from your own
465`jj op log`.
466
467## Moving content changes between commits
468
469You have already seen how `jj squash` can combine the changes from two commits
470into one. There are several other commands for changing the contents of existing
471commits.
472
473We'll need some more complex content to test these commands, so let's create a
474few more commits:
475
476```shell
477$ jj new master -m abc; printf 'a\nb\nc\n' > file
478Working copy (@) now at: ztqrpvnw f94e49cf (empty) abc
479Parent commit (@-) : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
480Added 0 files, modified 0 files, removed 1 files
481
482$ jj new -m ABC; printf 'A\nB\nc\n' > file
483Working copy (@) now at: kwtuwqnm 6f30cd1f (empty) ABC
484Parent commit (@-) : ztqrpvnw 51002261 ab
485
486$ jj new -m ABCD; printf 'A\nB\nC\nD\n' > file
487Working copy (@) now at: mrxqplyk a6749154 (empty) ABCD
488Parent commit (@-) : kwtuwqnm 30aecc08 ABC
489
490$ jj log -r master::@
491@ mrxqplyk martinvonz@google.com 2023-02-12 19:38:21.000 -08:00 b98c607b
492│ ABCD
493◉ kwtuwqnm martinvonz@google.com 2023-02-12 19:38:12.000 -08:00 30aecc08
494│ ABC
495◉ ztqrpvnw martinvonz@google.com 2023-02-12 19:38:03.000 -08:00 51002261
496│ abc
497◉ orrkosyo octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b
498│ (empty) Merge pull request #6 from Spaceghost/patch-1
499~
500```
501
502We "forgot" to capitalize "c" in the second commit when we capitalized the other
503letters. We then fixed that in the third commit when we also added "D". It would
504be cleaner to move the capitalization of "c" into the second commit. We can do
505that by running `jj squash` with the `--interactive`/`-i` option on the third
506commit. Remember that `jj squash` moves all the changes from one commit into its
507parent. `jj squash -i` moves only part of the changes into its parent. Now try
508that:
509
510```shell
511$ jj squash -i
512Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
513Rebased 1 descendant commits
514Working copy (@) now at: mrxqplyk 52a6c7fd ABCD
515Parent commit (@-) : kwtuwqnm 643061ac ABC
516```
517
518That will bring up the built-in diff editor[^alternative_diff_editors] with a
519diff of the changes in the "ABCD" commit. Expand the file by clicking on `(+)`
520or with right arrow, then select the sections/line to include by clicking or
521using space. Once complete, press `c` to confirm changes, or `q` to exit without
522saving. You can also use the mouse to click on the menu items to see more
523options (keyboard navigation is currently limited).
524
525[^alternative_diff_editors]: There are many other diff editors you could use.
526For example, if you have [Meld](https://meldmerge.org) installed and in the
527PATH, you can use it via `jj squash -i --tool meld` or a fancier config with `jj
528squash -i --tool meld-3`. You can configure the default with the
529[`ui.diff-editor` option](config.md#editing-diffs); those docs also explain how
530to specify a path to an executable if it is not in the PATH.
531
532If we look at the diff of the second commit, we now see that all three lines got
533capitalized:
534
535```shell
536$ jj diff -r @- --git
537diff --git a/file b/file
538index de980441c3..b1e67221af 100644
539--- a/file
540+++ b/file
541@@ -1,3 +1,3 @@
542-a
543-b
544-c
545+A
546+B
547+C
548```
549
550The child change ("ABCD" in our case) will have the same content *state* after
551the `jj squash` command. That means that you can move any changes you want into
552the parent change, even if they touch the same word, and it won't cause any
553conflicts.
554
555Let's try one final command for changing the contents of an exiting commit. That
556command is `jj diffedit`, which lets you edit the changes in a commit without
557checking it out.
558
559```shell
560$ jj diffedit -r @-
561Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
562Created kwtuwqnm 70985eaa (empty) ABC
563Rebased 1 descendant commits
564Working copy (@) now at: mrxqplyk 1c72cd50 (conflict) ABCD
565Parent commit (@-) : kwtuwqnm 70985eaa (empty) ABC
566Added 0 files, modified 1 files, removed 0 files
567There are unresolved conflicts at these paths:
568file 2-sided conflict
569New conflicts appeared in these commits:
570 mrxqplyk 1c72cd50 (conflict) ABCD
571To resolve the conflicts, start by updating to it:
572 jj new mrxqplykmyqv
573Then use `jj resolve`, or edit the conflict markers in the file directly.
574Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
575Then run `jj squash` to move the resolution into the conflicted commit.
576```
577
578In the diff editor, use the arrow keys and spacebar to select all lines but the
579last. Press 'c' to save the changes and close it. You can now inspect the
580rewritten commit with `jj diff -r @-` again, and you should see your deletion of
581the last line. Unlike `jj squash -i`, which left the content state of the commit
582unchanged, `jj diffedit` (typically) results in a different state, which means
583that descendant commits may have conflicts.
584
585Another command for rewriting contents of existing commits is `jj split`. Now
586that you've seen how `jj squash -i` and `jj diffedit` work, you can hopefully
587figure out how it works (with the help of the instructions in the diff).