jj is a shiny new version control system. I've been using it for a week now and it revolutionised my workflow with GitHub. It's simpler and more intuitive than git, but the official tutorial and the default UI can be a little daunting.
This guide will start with a simpler jj
interface and walk you through common development workflows. We start from making a single change and go all the way to managing a stack of tiny pull requests.
I hope I can show you why jj
feels amazing by the end! We'll use a simple Python project as our example.
Setup
First, let's set up our project and initialise it with jj
.
1. Create your project:
mkdir jj-hello-world
cd jj-hello-world
2. Initialise jj
and git:
jj
works on top of a git repository, which allows it to integrate with platforms like GitHub.
jj git init
Note before we start: The default
jj log
shows a lot of stuff. That can be quite confusing for someone new tojj
. When starting out I preferred to use a simpler interface. You can do that by writing some config to~/.config/jj/config.toml
1. Your First Change
Now our journey starts! We start by running jj log
.
Wow, what are all those things? 🤷♂️ A minute ago we just had an empty folder!
Turns out, every new jj
repository starts with two "commits":
The line starting with
◆
is the root commit. It's the special, empty starting point of your project's entire history, identified by all Zs (zzzzzzzz
). Think of it as the foundation upon which all your future work will be built.The line starting with
@
is your current working-copy commit. The@
symbol means this is your active workspace where any new file changes will be automatically tracked. In a brand new repository, this commit is also(empty)
and has no description yet, ready for you to start coding.
Let's make a simple change.
1. Write a change:
Modify the main.py
file and save.
print("hello world!")
jj
automatically captures this change in your working copy. If we run jj log
again, you will see that the (empty)
tag is gone. jj
knows that we have changed something.
We can see the change with jj diff
2. Commit your change:
jj commit -m "Add hello world"
Your jj log
will now show three commits: the root commit, the feature you committed just now, and a new empty working-copy commit you're now on.
💡Note:
Your feature commit has the idxvuxvpsx
. That's exactly the same as the id of the previous working copy. The new working copy has a new id that starts withm
3. Sync and first push:
Let's push your feature. Create a new, empty repository on GitHub. Then, add it as a remote.
# Replace <your-github-url> with your repository's URL
jj git remote add origin <your-github-url>
We want to give our commit a "bookmark". Since GitHub is organised around named branches, this bookmark tells jj
which of your local commits should become a specific branch on the remote. We want to bookmark our feature commit as main.
jj bookmark create main -r x
💡Note:
-r
stands for "revision", which is a synonym for "commit". Andx
is the shortest unique id for our local commit.
Push to remote.
jj git push --allow-new
Now your feature is on GitHub! \o/
Next up, we will look at creating and fixing PRs (pull requests), which happens all the time when you collaborate with someone on GitHub, or sometimes even when you work alone. Stay tuned!
Top comments (2)
Super helpful walkthrough—especially the simpler log config and the bookmark→branch mapping. Is there a way to auto-create a “main” bookmark on the first commit so push works without the manual step?
Thanks! Glad you find it useful. I am not too sure how to achieve the bookmark generation you mentioned. You might be able to do it by changing git_push_bookmark and add some function that checks that you are trying to push a first commit.