Personal documentation of common Jujutsu (jj) version control workflows and commands for daily development tasks.
- Configuration
- Clone a Repository
- Making changes
- Pushing changes directly to trunk
- Pushing changes to a tracked branch
- Pulling updates from the remote
- Updating local changes on top of a remote branch
- Start adding changes to a remote branch
- See a complete diff between two branches
- See a GitHub Pull Request style diff
There are two main ways to clone a repo:
- Using a colocated git repo (where
.git
is in the repo root) - Using git as a backing store, but not colocated (
.git
ends up in.jj/repo/store/git
)
In this setup, there is still a backing git
repo but it isn't top level. This
means other tooling doesn't think it's in a git
repo (e.g. starship
prompt
won't include "detached head" in the prompt output all the time).
jj git clone https://github.com/rwjblue/jj-notes.git
cd jj-notes/
This keeps a top level .git
folder, and allows you to always "drop back" to
using git
commands if you need to. This exposes the ultimate escape hatch,
but is sometimes annoying because most git tooling assumes that a "detached
head" state is a bad thing (or otherwise makes it annoying).
cd some-repo/
jj git init --colocate
In general, you want to end up with an empty commit at the end of your changes (also true after a fresh clone). So let's assume that is where you are starting from.
This is one of the places where jj
really shines. You basically just make the
changes you want and the next time that jj
runs (or automatically if you have
the watchman
integration enabled) it will automatically snapshot the changes
into the current change.
Then at any point, you can describe the set of changes you are doing:
jj describe
Then type up your commit message. I use the same basic format as I would in
git
(a shorter first line, then a longer description).
When you are done with this change, you can use jj new
to create a new empty
commit and leave the working directory in a clean state.
jj new
I use this workflow a lot for things like
rwjblue/dotfiles and
rwjblue/dotvim where I mostly just push
directly to master
| main
branches. In this case, I'm not really worrying
about collaborating with others (these are just personal repos after all).
Let's assume you just finished making a change and you are ready to push them:
jj bookmark move --from 'trunk()' --to @-
This updates any bookmarks
(which is basically Jujutsu's branch concept) that point to the revision
referenced by the built-in
alias
trunk()
(looks for main
, master
, and trunk
bookmarks on origin
)
to point to the change just before the current working commit (sorta akin to
HEAD
in git, but in jj
you always have a change for the working directory
instead of the index).
Now that the bookmark is updated, you can push the changes:
jj git push
This is what you'd do in order to push to a remote branch that you've already tracked (e.g. updating a pull request -- or other shared branch).
Let's assume you just finished making a change and you are ready to push them:
jj bookmark move <named-branch-name> --to @-
This updates the bookmark
(which is basically Jujutsu's branch concept) to point to the change just
before the current working commit (sorta akin to HEAD
in git, but in jj
you always have a change for the working directory instead of the index).
Note: you can also use jj bookmark set <named-branch-name> -r @-
(has the
same effect as bookmark move
), but set
is a create or update style
operation, while move
is specifically for moving an existing bookmark. If you
are pushing to a previously tracked branch, you definitely want to use move
to make sure you don't accidentally typo the name (creating a new typoed
bookmark name).
Now that the bookmark is updated, you can push the changes:
jj git push
jj git import
This does not update the local working copy (unlike something like git pull
).
After pulling updates from the remote, you
might want to update your working copy to the latest changes from main
(or
other tracked branch):
jj rebase --destination main
This will rebase the current revision (e.g. @
) and any of it's ancestors that
aren't already part of main
on top of the latest main
revision.
Make sure that you have the latest changes:
jj git import
Then you can start making changes:
jj new <branch-name>@<remote-name>
If you want to see a literal diff of the changes between two branches (e.g.
from your local branch and main
), you would do this:
jj diff --from main
This will show additions in your branch, but also any changes added to main
as deletions (because those changes are not in your branch).
More often, you want to see the changes that you've introduced in a branch
since the branching point (e.g. ignore any new additions to main
). This is
effectively a GitHub Pull Request style diff.
jj diff --from 'fork_point(main|@)'