just playing with tangled
at gvimdiff 587 lines 25 kB view raw view rendered
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).