Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Tutorial

Hint: This tutorial covers the basics accurately, but since it waswritten Jujutsu has gained many new features. For a tutorial covering a widerrange of features, you may find the (not quite finished)tutorial by SteveKlabnik helpful. If you'relooking for a tutorial that doesn't require experience with Git, take a lookatJujutsu for everyone.

This text assumes that the reader is familiar with Git.

Preparation

If you haven't already, make sure youinstall and configure Jujutsu.

Cloning a Git repository

Hint: Most identifiers used in this tutorial will be different when youtry this at home!

Let's start by cloning GitHub's Hello-World repo usingjj:

# Note the "git" before "clone" (there is no support for cloning native jj# repos yet)$jjgitclonehttps://github.com/octocat/Hello-WorldFetchingintonewrepoin"/tmp/tmp.O1DWMiaKd4/Hello-World"remote:Enumeratingobjects:13,done.remote:Total13(delta0),reused0(delta0),pack-reused13(from1)bookmark:master@origin[new]untrackedbookmark:octocat-patch-1@origin[new]untrackedbookmark:test@origin[new]untrackedSettingtherevsetalias`trunk()`to`master@origin`Workingcopy(@)nowat:kntqzsqtd7439b06(empty)(nodescriptionset)Parentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1Added1files,modified0files,removed0files$cdHello-World

Runningjj st (short forjj status) now yields something like this:

$jjstTheworkingcopyhasnochanges.Workingcopy(@):kntqzsqtd7439b06(empty)(nodescriptionset)Parentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1

Let's look at that output as it introduces new concepts. You can see twocommits: Parent and working copy. Both are identified using two separateidentifiers: the "change ID" and the "commit ID".

The parent commit, for example, has the change IDorrkosyo and the commit ID7fd1a60b.

Git users: The commit ID/hash is what you're used to from Git and shouldmatch what you see when you look at the repository usinggit log in a Gitcheckout of the repository.The change ID however, is a new concept, unique to Jujutsu.

We can also see from the output above that our working copy is an actual commitwith a commit ID (d7439b06 in the example). When you make a change in theworking copy, the working-copy commit gets automatically amended by the nextjj command.

Git users: This is a huge difference from Git where the working copy is aseparate concept and not yet a commit.

Changes

A change is a commit that can evolve while keeping a stable identifier (similarto Gerrit's Change-Id). In other words: You can make changes to files in achange, resulting in a new commit hash, but the change ID will remain the same.

You can see that our clone operation automatically created a new change:

Workingcopy(@):kntqzsqtd7439b06(empty)(nodescriptionset)

This new change has the IDkntqzsqt and it is currently empty (contains nochanges compared to the parent) and has no description.

Creating our first change

Let's say we want to edit theREADME file in the repo to say "Goodbye"instead of "Hello". Start by describing the change (adding a commit message) sowe don't forget what we're working on:

# This brings up $EDITOR (or `nano` or `Notepad` by default).# Enter something like "Say goodbye" in the editor and then save the file and close# the editor.$jjdescribeWorkingcopy(@)nowat:kntqzsqte427edcf(empty)SaygoodbyeParentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1

Now make the change in the README:

# Adjust as necessary for compatibility with your flavor of `sed`$sed-i's/Hello/Goodbye/'README$jjstWorkingcopychanges:MREADMEWorkingcopy(@):kntqzsqt5d39e19dSaygoodbyeParentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1

Note that you didn't have to tell Jujutsu to add the change like you would withgit add. You actually don't even need to tell it when you add new files orremove existing files. To untrack a path, add it to your.gitignore and runjj file untrack <path>.

Also note that the commit hash for our current change (kntqzsqt) changed frome427edcf to5d39e19d!

To see the diff, runjj diff:

$jjdiff--git# Feel free to skip the `--git` flagdiff--gita/READMEb/READMEindex980a0d5f19..1ce3f81130100644---a/README+++b/README@@-1,1+1,1@@-HelloWorld!+GoodbyeWorld!

Jujutsu's diff format currently defaults to inline coloring of the diff (likegit diff --color-words), so we used--git above to make the diff readable inthis tutorial.

As you may have noticed, the working-copy commit's ID changed both when weedited the description and when we edited the README. However, the parent commitstayed the same. Each change to the working-copy commit amends the previousversion. So how do we tell Jujutsu that we are done amending the current changeand want to start working on a new one? That is whatjj new is for. That willcreate a new commit on top of your current working-copy commit. The new commitis for the working-copy changes.

So, let's say we're now done with this change, so we create a new change:

$jjnewWorkingcopy(@)nowat:mpqrykypaef4df99(empty)(nodescriptionset)Parentcommit(@-):kntqzsqt5d39e19dSaygoodbye$jjstTheworkingcopyhasnochanges.Workingcopy(@):mpqrykypaef4df99(empty)(nodescriptionset)Parentcommit(@-):kntqzsqt5d39e19dSaygoodbye

If we later realize that we want to make further changes, we can make them inthe working copy and then runjj squash. That command squashes (moves) thechanges from a given commit into its parent commit. Like most commands, it actson the working-copy commit by default. When run on the working-copy commit, itbehaves very similar togit commit --amend.

Alternatively, we can usejj edit <commit> to resume editing a commit in theworking copy. Any further changes in the working copy will then amend thecommit. Whether you choose to create a new change and squash, or to edit,typically depends on how done you are with the change; if the change is almostdone, it makes sense to usejj new so you can easily review your adjustmentswithjj diff before runningjj squash.

To view how a change has evolved over time, we can usejj evolog to see eachrecorded change for the current commit. This records changes to the workingcopy, message, squashes, rebases, etc.

The log command and "revsets"

You're probably familiar withgit log. Jujutsu has very similar functionalityin itsjj log command:

$jjlog@mpqrykypmartinvonz@google.com2023-02-1215:00:22aef4df99│(empty)(nodescriptionset)kntqzsqtmartinvonz@google.com2023-02-1214:56:595d39e19d│Saygoodbye◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

The@ indicates the working-copy commit. The first ID on a line(e.g. "mpqrykyp" above) is the change ID. The second ID is the commit ID. Youcan give either ID to commands that take revisions as arguments. We willgenerally prefer change IDs because they stay the same when the commit isrewritten.

By default,jj log lists your local commits, with some remote commits addedfor context. The~ indicates that the commit has parents that are not includedin the graph. We can use the--revisions/-r flag to select a different setof revisions to list. The flag accepts a"revset", which is anexpression in a simple language for specifying revisions. For example,@refers to the working-copy commit,root() refers to the root commit,bookmarks() refers to all commits pointed to by bookmarks (similar to Git'sbranches). We can combine expressions with| for union,& for intersectionand~ for difference. For example:

$jjlog-r'@ | root() | bookmarks()'@mpqrykypmartinvonz@google.com2023-02-1215:00:22aef4df99│(empty)(nodescriptionset)~(elidedrevisions)orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~(elidedrevisions)zzzzzzzzroot()00000000

The00000000 commit (change IDzzzzzzzz) is a virtual commit that's calledthe "root commit". It's the root commit of every repo. Theroot()function in the revset matches it.

There are also operators for getting the parents (foo-), children (foo+),ancestors (::foo), descendants (foo::), DAG range (foo::bar, likegit log --ancestry-path), range (foo..bar, same as Git's). Seethe revset documentation for all revset operators and functions.

Hint: If the defaultjj log omits some commits you expect to see, youcan always runjj log -r :: (or, equivalently,jj log -r 'all()') to seeall the commits.

Conflicts

Now let's see how Jujutsu deals with merge conflicts. We'll start by making somecommits. We usejj new with the--message/-m option to set changedescriptions (commit messages) right away.

# Start creating a chain of commits off of the `master` bookmark$jjnewmaster-mA;echoa>file1Workingcopy(@)nowat:nuvyytnq00a2aeed(empty)AParentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1Added0files,modified1files,removed0files$jjnew-mB1;echob1>file1Workingcopy(@)nowat:ovknlmro967d9f9f(empty)B1Parentcommit(@-):nuvyytnq5dda2f09A$jjnew-mB2;echob2>file1Workingcopy(@)nowat:puqltutt8ebeaffa(empty)B2Parentcommit(@-):ovknlmro7d7c6e6bB1$jjnew-mC;echoc>file2Workingcopy(@)nowat:qzvqqupx62a3c6d3(empty)CParentcommit(@-):puqltuttdaa6ffd5B2$jjlog@qzvqqupxmartinvonz@google.com2023-02-1215:07:412370ddf3│C○puqltuttmartinvonz@google.com2023-02-1215:07:33daa6ffd5│B2○ovknlmromartinvonz@google.com2023-02-1215:07:247d7c6e6b│B1○nuvyytnqmartinvonz@google.com2023-02-1215:07:055dda2f09│A│kntqzsqtmartinvonz@google.com2023-02-1214:56:595d39e19d├─╯Saygoodbye◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

We now have a few commits, where A, B1, and B2 modify the same file, while Cmodifies a different file. Let's now rebase B2 directly onto A. We use the--source/-s option on the change ID of B2, and--onto/-o option on A.

$jjrebase-spuqltutt-onuvyytnq# Replace the IDs by what you have for B2 and ARebased2commitstodestinationWorkingcopy(@)nowat:qzvqqupx1978b534(conflict)CParentcommit(@-):puqltuttf7fb5943(conflict)B2Added0files,modified1files,removed0filesWarning:Thereareunresolvedconflictsatthesepaths:file12-sidedconflictNewconflictsappearedin2commits:qzvqqupx1978b534(conflict)Cpuqltuttf7fb5943(conflict)B2Hint:Toresolvetheconflicts,startbycreatingacommitontopofthefirstconflictedcommit:jjnewpuqltuttThenuse`jjresolve`,oredittheconflictmarkersinthefiledirectly.Oncetheconflictsareresolved,youcaninspecttheresultwith`jjdiff`.Thenrun`jjsquash`tomovetheresolutionintotheconflictedcommit.$jjlog@qzvqqupxmartinvonz@google.com2023-02-1215:08:331978b534conflict│puqltuttmartinvonz@google.com2023-02-1215:08:33f7fb5943conflict│B2│ovknlmromartinvonz@google.com2023-02-1215:07:247d7c6e6b├─╯B1○nuvyytnqmartinvonz@google.com2023-02-1215:07:055dda2f09│A│kntqzsqtmartinvonz@google.com2023-02-1214:56:595d39e19d├─╯Saygoodbye◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

There are several things worth noting here. First, thejj rebase command said"Rebased 2 commits". That's because we asked it to rebase commit B2 with the-s option, which also rebases descendants (commit C in this case). Second,because B2 modified the same file (and word) as B1, rebasing it resulted inconflicts, as the output indicates. Third, the conflicts did not prevent therebase from completing successfully, nor did it prevent C from getting rebasedon top.

Now let's resolve the conflict in B2. We'll do that by creating a new commit ontop of B2. Once we've resolved the conflict, we'll squash the conflictresolution into the conflicted B2. That might look like this:

$jjnewpuqltutt# Replace the ID by what you have for B2Workingcopy(@)nowat:zxoosnnpc7068d1c(conflict)(empty)(nodescriptionset)Parentcommit(@-):puqltuttf7fb5943(conflict)B2Added0files,modified0files,removed1filesWarning:Thereareunresolvedconflictsatthesepaths:file12-sidedconflict$jjstTheworkingcopyhasnochanges.Workingcopy(@):zxoosnnpc7068d1c(conflict)(empty)(nodescriptionset)Parentcommit(@-):puqltuttf7fb5943(conflict)B2Warning:Thereareunresolvedconflictsatthesepaths:file12-sidedconflictHint:Toresolvetheconflicts,startbycreatingacommitontopoftheconflictedcommit:jjnewpuqltuttThenuse`jjresolve`,oredittheconflictmarkersinthefiledirectly.Oncetheconflictsareresolved,youcaninspecttheresultwith`jjdiff`.Thenrun`jjsquash`tomovetheresolutionintotheconflictedcommit.$catfile1<<<<<<<Conflict1of1%%%%%%%Changesfrombasetoside#1-b1+a+++++++Contentsofside#2b2>>>>>>>Conflict1of1ends$echoresolved>file1$jjstWorkingcopychanges:Mfile1Workingcopy(@):zxoosnnpc2a31a06(nodescriptionset)Parentcommit(@-):puqltuttf7fb5943(conflict)B2Hint:Conflictinparentcommithasbeenresolvedinworkingcopy$jjsquashRebased1descendantcommitsWorkingcopy(@)nowat:ntxxqymre3c279cc(empty)(nodescriptionset)Parentcommit(@-):puqltutt2c7a658eB2Existingconflictswereresolvedorabandonedfrom2commits.$jjlog@ntxxqymrmartinvonz@google.com2023-02-1219:34:09e3c279cc│(empty)(nodescriptionset)qzvqqupxmartinvonz@google.com2023-02-1219:34:09b9da9d28├─╯C○puqltuttmartinvonz@google.com2023-02-1219:34:092c7a658e│B2│ovknlmromartinvonz@google.com2023-02-1215:07:247d7c6e6b├─╯B1○nuvyytnqmartinvonz@google.com2023-02-1215:07:055dda2f09│A│kntqzsqtmartinvonz@google.com2023-02-1214:56:595d39e19d├─╯Saygoodbye◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

Note that commit C automatically got rebased on top of the resolved B2, and thatC is also resolved (since it modified only a different file).

By the way, if we want to get rid of B1 now, we can runjj abandonovknlmro. That will hide the commit from the log output and will rebase anydescendants to its parent.

The operation log

Jujutsu keeps a record of all changes you've made to the repo in what's calledthe "operation log". Use thejj op (short forjj operation) family ofcommands to interact with it. To list the operations, usejj op log:

$jjoplog@d3b77addea49martinvonz@vonz.svl.corp.google.com3minutesago,lasted3milliseconds│squashcommitsintof7fb5943a6b9460eb106dba2fac5cac1625c6f7a│args:jjsquash○6fc1873c1180martinvonz@vonz.svl.corp.google.com3minutesago,lasted1milliseconds│snapshotworkingcopy│args:jjst○ed91f7bcc1fbmartinvonz@vonz.svl.corp.google.com6minutesago,lasted1milliseconds│newemptycommit│args:jjnewpuqltutt○367400773f87martinvonz@vonz.svl.corp.google.com12minutesago,lasted3milliseconds│rebasecommitdaa6ffd5a09a8a7d09a65796194e69b7ed0a566danddescendants│args:jjrebase-spuqltutt-onuvyytnq[manymorelines]

The most useful command isjj undo, which will undo your last operation.

$jjundoRevertedoperation:d3b77addea49(2025-05-1200:27:27)squashcommitsintof7fb5943a6b9460eb106dba2fac5cac1625c6f7aWorkingcopy(@)nowat:zxoosnnp63874fe6(nodescriptionset)Parentcommit(@-):puqltuttf7fb5943(conflict)B2Newconflictsappearedin2commits:qzvqqupx1978b534(conflict)Cpuqltuttf7fb5943(conflict)B2Hint:Toresolvetheconflicts,startbycreatingacommitontopofthefirstconflictedcommit:jjnewpuqltuttThenuse`jjresolve`,oredittheconflictmarkersinthefiledirectly.Oncetheconflictsareresolved,youcaninspecttheresultwith`jjdiff`.Thenrun`jjsquash`tomovetheresolutionintotheconflictedcommit.$jjlog@zxoosnnpmartinvonz@google.com2023-02-1219:34:0963874fe6│(nodescriptionset)×qzvqqupxmartinvonz@google.com2023-02-1215:08:331978b534conflict├─╯puqltuttmartinvonz@google.com2023-02-1215:08:33f7fb5943conflict│B2│ovknlmromartinvonz@google.com2023-02-1215:07:247d7c6e6b├─╯B1○nuvyytnqmartinvonz@google.com2023-02-1215:07:055dda2f09│A│kntqzsqtmartinvonz@google.com2023-02-1214:56:595d39e19d├─╯Saygoodbye◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

As you can perhaps see, that undid thejj squash invocation we used forsquashing the conflict resolution into commit B2 earlier. Notice that it alsoupdated the working copy.

You can also view the repo the way it looked after some earlier operation. Forexample, if you want to seejj log output right after thejj rebaseoperation, tryjj log --at-op=367400773f87 but use the hash from your ownjj op log.

Moving content changes between commits

You have already seen howjj squash can combine the changes from two commitsinto one. There are several other commands for changing the contents of existingcommits.

We'll need some more complex content to test these commands, so let's create afew more commits:

$jjnewmaster-mabc;printf'a\nb\nc\n'>fileWorkingcopy(@)nowat:ztqrpvnwf94e49cf(empty)abcParentcommit(@-):orrkosyo7fd1a60bmaster|(empty)Mergepullrequest#6 from Spaceghost/patch-1Added0files,modified0files,removed1files$jjnew-mABC;printf'A\nB\nc\n'>fileWorkingcopy(@)nowat:kwtuwqnm6f30cd1f(empty)ABCParentcommit(@-):ztqrpvnw51002261ab$jjnew-mABCD;printf'A\nB\nC\nD\n'>fileWorkingcopy(@)nowat:mrxqplyka6749154(empty)ABCDParentcommit(@-):kwtuwqnm30aecc08ABC$jjlog-rmaster::@@mrxqplykmartinvonz@google.com2023-02-1219:38:21b98c607b│ABCD○kwtuwqnmmartinvonz@google.com2023-02-1219:38:1230aecc08│ABC○ztqrpvnwmartinvonz@google.com2023-02-1219:38:0351002261abc◆orrkosyooctocat@nowhere.com2012-03-0615:06:50master7fd1a60b│(empty)Mergepullrequest#6 from Spaceghost/patch-1~

We "forgot" to capitalize "c" in the second commit when we capitalized the otherletters. We then fixed that in the third commit when we also added "D". It wouldbe cleaner to move the capitalization of "c" into the second commit. We can dothat by runningjj squash with the--interactive/-i option on the thirdcommit. Remember thatjj squash moves all the changes from one commit into itsparent.jj squash -i moves only part of the changes into its parent. Now trythat:

$jjsquash-iHint:Usingdefaulteditor':builtin';run`jjconfigset--userui.diff-editor:builtin`todisablethismessage.Rebased1descendantcommitsWorkingcopy(@)nowat:mrxqplyk52a6c7fdABCDParentcommit(@-):kwtuwqnm643061acABC

That will bring up the built-in diff editor1 with adiff of the changes in the "ABCD" commit. Expand the file by clicking on(+)or with right arrow, then select the sections/line to include by clicking orusing space. Once complete, pressc to confirm changes, orq to exit withoutsaving. You can also use the mouse to click on the menu items to see moreoptions (keyboard navigation is currently limited).

If we look at the diff of the second commit, we now see that all three lines gotcapitalized:

$jjdiff-r@---gitdiff--gita/fileb/fileindexde980441c3..b1e67221af100644---a/file+++b/file@@-1,3+1,3@@-a-b-c+A+B+C

The child change ("ABCD" in our case) will have the same contentstate afterthejj squash command. That means that you can move any changes you want intothe parent change, even if they touch the same word, and it won't cause anyconflicts.

Let's try one final command for changing the contents of an existing commit. Thatcommand isjj diffedit, which lets you edit the changes in a commit withoutchecking it out.

$jjdiffedit-r@-Hint:Usingdefaulteditor':builtin';run`jjconfigset--userui.diff-editor:builtin`todisablethismessage.Rebased1descendantcommitsWorkingcopy(@)nowat:mrxqplyk1c72cd50(conflict)ABCDParentcommit(@-):kwtuwqnm70985eaaABCAdded0files,modified1files,removed0filesWarning:Thereareunresolvedconflictsatthesepaths:file2-sidedconflictNewconflictsappearedin1commits:mrxqplyk1c72cd50(conflict)ABCDHint:Toresolvetheconflicts,startbycreatingacommitontopoftheconflictedcommit:jjnewmrxqplykThenuse`jjresolve`,oredittheconflictmarkersinthefiledirectly.Oncetheconflictsareresolved,youcaninspecttheresultwith`jjdiff`.Thenrun`jjsquash`tomovetheresolutionintotheconflictedcommit.

In the diff editor, use the arrow keys and spacebar to select all lines but thelast. Press 'c' to save the changes and close it. You can now inspect therewritten commit withjj diff -r @- again, and you should see your deletion ofthe last line. Unlikejj squash -i, which left the content state of the commitunchanged,jj diffedit (typically) results in a different state, which meansthat descendant commits may have conflicts.

Another command for rewriting contents of existing commits isjj split. Nowthat you've seen howjj squash -i andjj diffedit work, you can hopefullyfigure out how it works (with the help of the instructions in the diff).


  1. There are many other diff editors you could use.For example, if you haveMeld installed and in thePATH, you can use it viajj squash -i --tool meld or a fancier config withjjsquash -i --tool meld-3. You can configure the default with theui.diff-editor option; those docs also explain howto specify a path to an executable if it is not in the PATH. 


[8]ページ先頭

©2009-2025 Movatter.jp