just playing with tangled
1# Tutorial 2 3This text assumes that the reader is familiar with Git. 4 5## Preparation 6 7If you haven't already, make sure you 8[install and configure Jujutsu](../README.md#Installation). 9 10## Cloning a Git repo 11 12Let's start by cloning GitHub's Hello-World repo using `jj`: 13```shell script 14# Note the "git" before "clone" (there is no support for cloning native jj 15# repos yet) 16$ jj git clone https://github.com/octocat/Hello-World 17Fetching into new repo in "/tmp/tmp.O1DWMiaKd4/Hello-World" 18Working copy now at: d7439b06fbef (no description set) 19Added 1 files, modified 0 files, removed 0 files 20$ cd Hello-World 21``` 22 23Running `jj st` (short for`jj status`) now yields something like this: 24``` 25$ jj st 26Working copy : d7439b06fbef (empty) (no description set) 27Parent commit: 7fd1a60b01f9 Merge pull request #6 from Spaceghost/patch-1 |master 28``` 29 30We can see from the output above that our working copy is a real commit with a 31commit ID (`d7439b06fbef` in the example). When you make a change in the working 32copy, the working-copy commit gets automatically amended by the next `jj` 33command. For now, it is marked as `(empty)` because it doesn't have any changes 34compared to its parent commit. 35 36We are also told that the `master` branch is pointing to the parent revision of 37the working copy. We will discuss branches later. 38 39## Creating our first change 40 41Now let's say we want to edit the `README` file in the repo to say "Goodbye" 42instead of "Hello". Let's start by describing the change (adding a 43commit message) so we don't forget what we're working on: 44```shell script 45# This will bring up $EDITOR (or `pico` by default). Enter something like 46# "Say goodbye" in the editor and then save the file and close the editor. 47$ jj describe 48Working copy now at: e427edcfd0ba (empty) Say goodbye 49Parent commit : 7fd1a60b01f9 Merge pull request #6 from Spaceghost/patch-1 |master 50``` 51 52Now make the change in the README: 53```shell script 54# Adjust as necessary for compatibility with your flavor of `sed` 55$ sed -i 's/Hello/Goodbye/' README 56$ jj st 57Working copy : 5d39e19dac36 Say goodbye 58Parent commit: 7fd1a60b01f9 Merge pull request #6 from Spaceghost/patch-1 |master 59Working copy changes: 60M README 61``` 62Note that you didn't have to tell Jujutsu to add the change like you would with 63`git add`. You actually don't even need to tell it when you add new files or 64remove existing files. To untrack a path, add it to your `.gitignore` and run 65`jj untrack <path>`. 66 67To see the diff, run `jj diff`: 68```shell script 69$ jj diff --git # Feel free to skip the `--git` flag 70diff --git a/README b/README 71index 980a0d5f19...1ce3f81130 100644 72--- a/README 73+++ b/README 74@@ -1,1 +1,1 @@ 75-Hello World! 76+Goodbye World! 77``` 78Jujutsu's diff format currently defaults to inline coloring of the diff (like 79`git diff --color-words`), so we used `--git` above to make the diff readable in 80this tutorial. 81 82As you may have noticed, the working-copy commit's ID changed both when we 83edited the description and when we edited the README. However, the parent commit 84stayed the same. Each change to the working-copy commit amends the previous 85version. So how do we tell Jujutsu that we are done amending the current change 86and want to start working on a new one? That is what `jj new` is for. That will 87create a new commit on top of your current working-copy commit. The new commit 88is for the working-copy changes. For familiarity for user coming from other 89VCSs, there is also a `jj checkout/co` command, which is practically a synonym 90for `jj new` (you can specify a destination for `jj new` as well). 91 92So, let's say we're now done with this change, so we create a new change: 93```shell script 94$ jj new 95Working copy now at: aef4df99ea11 (empty) (no description set) 96Parent commit : 5d39e19dac36 Say goodbye 97``` 98 99If we later realize that we want to make further changes, we can make them 100in the working copy and then run `jj squash`. That command squashes the changes 101from a given commit into its parent commit. Like most commands, it acts on the 102working-copy commit by default. When run on the working-copy commit, it behaves 103very similar to `git commit --amend`, and `jj amend` is in fact an alias for 104`jj squash`. 105 106Alternatively, we can use `jj edit <commit>` to resume editing a commit in the 107working copy. Any further changes in the working copy will then amend the 108commit. Whether you choose to checkout-and-squash or to edit typically depends 109on how done you are with the change; if the change is almost done, it makes 110sense to use `jj checkout` so you can easily review your adjustments with 111`jj diff` before running `jj squash`. 112 113## The log command and "revsets" 114 115You're probably familiar with `git log`. Jujutsu has very similar functionality 116in its `jj log` command: 117```shell script 118$ jj log 119@ mpqrykypylvy martinvonz@google.com 2023-02-12 15:00:22.000 -08:00 aef4df99ea11 120│ (empty) (no description set) 121◉ kntqzsqtnspv martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19dac36 122│ Say goodbye 123◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 124│ (empty) Merge pull request #6 from Spaceghost/patch-1 125~ 126``` 127 128The `@` indicates the working-copy commit. The first ID on a line 129(e.g. "mpqrykypylvy" above) is the "change ID", which is an ID that follows the 130commit as it's rewritten (similar to Gerrit's Change-Id). The second ID is the 131commit ID, which changes when you rewrite the commit. You can give either ID 132to commands that take revisions as arguments. We will generally prefer change 133IDs because they stay the same when the commit is rewritten. 134 135By default, `jj log` lists your local commits, with some remote commits added 136for context. The `~` indicates that the commit has parents that are not 137included in the graph. We can use the `-r` flag to select a different set of 138revisions to list. The flag accepts a ["revset"](revsets.md), which is an 139expression in a simple language for specifying revisions. For example, `@` 140refers to the working-copy commit, `root` refers to the root commit, 141`branches()` refers to all commits pointed to by branches. We can combine 142expressions with `|` for union, `&` for intersection and `~` for difference. For 143example: 144```shell script 145$ jj log -r '@ | root | branches()' 146@ mpqrykypylvy martinvonz@google.com 2023-02-12 15:00:22.000 -08:00 aef4df99ea11 147╷ (empty) (no description set) 148╷ ◉ kowxouwzwxmv octocat@nowhere.com 2014-06-10 15:22:26.000 -07:00 test b3cbd5bbd7e8 149╭─╯ Create CONTRIBUTING.md 150│ ◉ tpstlustrvsn support+octocat@github.com 2018-05-10 12:55:19.000 -05:00 octocat-patch-1 b1b3f9723831 151├─╯ sentence case 152◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 153╷ (empty) Merge pull request #6 from Spaceghost/patch-1 154◉ zzzzzzzzzzzz 1970-01-01 00:00:00.000 +00:00 000000000000 155 (empty) (no description set) 156``` 157 158The `000000000000` commit (change ID `zzzzzzzzzzzz`) is a virtual commit that's 159called the "root commit". It's the root commit of every repo. The `root` symbol 160in the revset matches it. 161 162There are also operators for getting the parents (`foo-`), children (`foo+`), 163ancestors (`::foo`), descendants (`foo::`), DAG range (`foo::bar`, like 164`git log --ancestry-path`), range (`foo..bar`, same as Git's). There are also a 165few more functions, such as `heads(<set>)`, which filters out revisions in the 166input set if they're ancestors of other revisions in the set. 167 168## Conflicts 169 170Now let's see how Jujutsu deals with merge conflicts. We'll start by making some 171commits: 172```shell script 173# Start creating a chain of commits off of the `master` branch 174$ jj new master -m A; echo a > file1 175Working copy now at: 00a2aeed556a A 176Added 0 files, modified 1 files, removed 0 files 177$ jj new -m B1; echo b1 > file1 178Working copy now at: 967d9f9fd288 B1 179$ jj new -m B2; echo b2 > file1 180Working copy now at: 8ebeaffa332b B2 181$ jj new -m C; echo c > file2 182Working copy now at: 62a3c6d315cd C 183$ jj log 184@ qzvqqupxlkot martinvonz@google.com 2023-02-12 15:07:41.946 -08:00 2370ddf3fa39 185│ C 186◉ puqltuttrvzp martinvonz@google.com 2023-02-12 15:07:33.000 -08:00 daa6ffd5a09a 187│ B2 188◉ ovknlmrokpkl martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6bd0b4 189│ B1 190◉ nuvyytnqlquo martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f097aa9 191│ A 192│ ◉ kntqzsqtnspv martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19dac36 193├─╯ Say goodbye 194◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 195│ (empty) Merge pull request #6 from Spaceghost/patch-1 196~ 197``` 198 199We now have a few commits, where A, B1, and B2 modify the same file, while C 200modifies a different file. Let's now rebase B2 directly onto A: 201```shell script 202$ jj rebase -s puqltuttrvzp -d nuvyytnqlquo 203Rebased 2 commits 204Working copy now at: 1978b53430cd C 205Added 0 files, modified 1 files, removed 0 files 206$ jj log 207@ qzvqqupxlkot martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 1978b53430cd conflict 208│ C 209◉ puqltuttrvzp martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 f7fb5943ee41 conflict 210│ B2 211│ ◉ ovknlmrokpkl martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6bd0b4 212├─╯ B1 213◉ nuvyytnqlquo martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f097aa9 214│ A 215│ ◉ kntqzsqtnspv martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19dac36 216├─╯ Say goodbye 217◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 218│ (empty) Merge pull request #6 from Spaceghost/patch-1 219~ 220``` 221 222There are several things worth noting here. First, the `jj rebase` command said 223"Rebased 2 commits". That's because we asked it to rebase commit B2 with the 224`-s` option, which also rebases descendants (commit C in this case). Second, 225because B2 modified the same file (and word) as B1, rebasing 226it resulted in conflicts, as the `jj log` output indicates. Third, the conflicts 227did not prevent the rebase from completing successfully, nor did it prevent C 228from getting rebased on top. 229 230Now let's resolve the conflict in B2. We'll do that by creating a new commit on 231top of B2. Once we've resolved the conflict, we'll squash the conflict 232resolution into the conflicted B2. That might look like this: 233```shell script 234$ jj new puqltuttrvzp # Replace the ID by what you have for B2 235Working copy now at: c7068d1c23fd (empty) (no description set) 236Added 0 files, modified 0 files, removed 1 files 237$ jj st 238Working copy : c7068d1c23fd (empty) (no description set) 239Parent commit: f7fb5943ee41 B2 240There are unresolved conflicts at these paths: 241file1 2-sided conflict 242$ cat file1 243<<<<<<< 244%%%%%%% 245-b1 246+a 247+++++++ 248b2 249>>>>>>> 250$ echo resolved > file1 251$ jj squash 252Rebased 1 descendant commits 253Working copy now at: e3c279cc2043 (no description set) 254$ jj log 255@ ntxxqymrlvxu martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 e3c279cc2043 256│ (empty) (no description set) 257│ ◉ qzvqqupxlkot martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 b9da9d28b26b 258├─╯ C 259◉ puqltuttrvzp martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 2c7a658e2586 260│ B2 261│ ◉ ovknlmrokpkl martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6bd0b4 262├─╯ B1 263◉ nuvyytnqlquo martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f097aa9 264│ A 265│ ◉ kntqzsqtnspv martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19dac36 266├─╯ Say goodbye 267◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 268│ (empty) Merge pull request #6 from Spaceghost/patch-1 269~ 270``` 271 272Note that commit C automatically got rebased on top of the resolved B2, and that 273C is also resolved (since it modified only a different file). 274 275By the way, if we want to get rid of B1 now, we can run `jj abandon 276ovknlmrokpkl`. That will hide the commit from the log output and will rebase any 277descendants to its parent. 278 279## The operation log 280 281Jujutsu keeps a record of all changes you've made to the repo in what's called 282the "operation log". Use the `jj op` (short for `jj operation`) family of 283commands to interact with it. To list the operations, use `jj op log`: 284```shell script 285$ jj op log 286@ d3b77addea49 martinvonz@vonz.svl.corp.google.com 2023-02-12 19:34:09.549 -08:00 - 2023-02-12 19:34:09.552 -08:00 287│ squash commit 63874fe6c4fba405ffc38b0dd926f03b715cf7ef 288│ args: jj squash 289◉ 6fc1873c1180 martinvonz@vonz.svl.corp.google.com 2023-02-12 19:34:09.548 -08:00 - 2023-02-12 19:34:09.549 -08:00 290│ snapshot working copy 291◉ ed91f7bcc1fb martinvonz@vonz.svl.corp.google.com 2023-02-12 19:32:46.007 -08:00 - 2023-02-12 19:32:46.008 -08:00 292│ new empty commit 293│ args: jj new puqltuttrvzp 294◉ 367400773f87 martinvonz@vonz.svl.corp.google.com 2023-02-12 15:08:33.917 -08:00 - 2023-02-12 15:08:33.920 -08:00 295│ rebase commit daa6ffd5a09a8a7d09a65796194e69b7ed0a566d and descendants 296│ args: jj rebase -s puqltuttrvzp -d nuvyytnqlquo 297[many more lines] 298``` 299 300The most useful command is `jj undo` (alias for `jj op undo`), which will undo 301an operation. By default, it will undo the most recent operation. Let's try it: 302```shell script 303$ jj undo 304Working copy now at: 63874fe6c4fb (no description set) 305$ jj log 306@ zxoosnnpvvpn martinvonz@google.com 2023-02-12 19:34:09.000 -08:00 63874fe6c4fb 307│ (no description set) 308│ ◉ qzvqqupxlkot martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 1978b53430cd conflict 309├─╯ C 310◉ puqltuttrvzp martinvonz@google.com 2023-02-12 15:08:33.000 -08:00 f7fb5943ee41 conflict 311│ B2 312│ ◉ ovknlmrokpkl martinvonz@google.com 2023-02-12 15:07:24.000 -08:00 7d7c6e6bd0b4 313├─╯ B1 314◉ nuvyytnqlquo martinvonz@google.com 2023-02-12 15:07:05.000 -08:00 5dda2f097aa9 315│ A 316│ ◉ kntqzsqtnspv martinvonz@google.com 2023-02-12 14:56:59.000 -08:00 5d39e19dac36 317├─╯ Say goodbye 318◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 319│ (empty) Merge pull request #6 from Spaceghost/patch-1 320~ 321``` 322As you can perhaps see, that undid the `jj squash` invocation we used for 323squashing the conflict resolution into commit B2 earlier. Notice that it also 324updated the working copy. 325 326You can also view the repo the way it looked after some earlier operation. For 327example, if you want to see `jj log` output right after the `jj rebase` operation, 328try `jj log --at-op=367400773f87` but use the hash from your own `jj op log`. 329 330## Moving content changes between commits 331 332You have already seen how `jj squash` can combine the changes from two commits 333into one. There are several other commands for changing the contents of existing 334commits. These commands assume that you have `meld` installed. If you prefer a 335terminal-based diff editor, you 336can [configure `scm-diff-editor`](config.md#setting-up-scm-diff-editor) instead. 337 338We'll need some more complex content to test these commands, so let's create a 339few more commits: 340```shell script 341$ jj new master -m abc; printf 'a\nb\nc\n' > file 342Working copy now at: f94e49cf2547 abc 343Added 0 files, modified 0 files, removed 1 files 344$ jj new -m ABC; printf 'A\nB\nc\n' > file 345Working copy now at: 6f30cd1fb351 ABC 346$ jj new -m ABCD; printf 'A\nB\nC\nD\n' > file 347Working copy now at: a67491542e10 ABCD 348$ jj log -r master::@ 349@ mrxqplykzpkw martinvonz@google.com 2023-02-12 19:38:21.000 -08:00 b98c607bf87f 350│ ABCD 351◉ kwtuwqnmqyqp martinvonz@google.com 2023-02-12 19:38:12.000 -08:00 30aecc0871ea 352│ ABC 353◉ ztqrpvnwqqnq martinvonz@google.com 2023-02-12 19:38:03.000 -08:00 510022615871 354│ abc 355◉ orrkosyozysx octocat@nowhere.com 2012-03-06 15:06:50.000 -08:00 master 7fd1a60b01f9 356│ (empty) Merge pull request #6 from Spaceghost/patch-1 357~ 358``` 359 360We "forgot" to capitalize "c" in the second commit when we capitalized the other 361letters. We then fixed that in the third commit when we also added "D". It would 362be cleaner to move the capitalization of "c" into the second commit. We can do 363that by running `jj squash -i` (short for `jj squash --interactive`) on the 364third commit. Remember that `jj squash` moves all the changes from one commit 365into its parent. `jj squash -i` moves only part of the changes into its parent. 366Now try that: 367```shell script 368$ jj squash -i 369Using default editor 'meld'; you can change this by setting ui.diff-editor 370Working copy now at: 52a6c7fda1e3 ABCD 371``` 372That will bring up Meld with a diff of the changes in the "ABCD" commit. Modify 373the right side of the diff to have the desired end state in "ABC" by removing 374the "D" line. Then save the changes and close Meld. If we look at the diff of 375the second commit, we now see that all three lines got capitalized: 376```shell script 377$ jj diff -r @- 378Modified regular file file: 379 1 1: aA 380 2 2: bB 381 3 3: cC 382``` 383 384The child change ("ABCD" in our case) will have the same content *state* after 385the `jj squash` command. That means that you can move any changes you want into 386the parent change, even if they touch the same word, and it won't cause any 387conflicts. 388 389Let's try one final command for changing the contents of an exiting commit. That 390command is `jj diffedit`, which lets you edit the contents of a commit without 391checking it out. 392```shell script 393$ jj diffedit -r @- 394Using default editor 'meld'; you can change this by setting ui.diff-editor 395Created 70985eaa924f ABC 396Rebased 1 descendant commits 397Working copy now at: 1c72cd50525d ABCD 398Added 0 files, modified 1 files, removed 0 files 399``` 400When Meld starts, edit the right side by e.g. adding something to the first 401line. Then save the changes and close Meld. You can now inspect the rewritten 402commit with `jj diff -r @-` again and you should see your addition to the first 403line. Unlike `jj squash -i`, which left the content state of the commit 404unchanged, `jj diffedit` (typically) results in a different state, which means 405that descendant commits may have conflicts. 406 407Other commands for rewriting contents of existing commits are `jj split`, `jj 408unsquash -i` and `jj move -i`. Now that you've seen how `jj squash -i` and `jj 409diffedit` work, you can hopefully figure out how those work (with the help of 410the instructions in the diff).