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).