From Zero to GitHub: Starting A New jj (Jujutsu) Repo

I started dabbling with jj (git alternative) recently by going through Steve Klabnik's jujutsu tutorial. This is a great place to start (along with many other jj resources here).

Then today as I'm wrapping up a migration script for my Kit.com account, I decide it is worth putting up on GitHub. I then further decide that I should try my hand at using jj to go from zero (no repo) to pushing it all up to GitHub.

I'm not that far through Steve's tutorial, so there are a lot of gaps in my knowledge. The whole process was not as straightforward as I thought it would be, so I figured it would help me and others to document the steps and missteps that I took to get the job done.

Since I ultimately want a git-backed repo that I can push up to GitHub, I need to start with:

 jj git init 

This creates both .jj and .git directories.

quick note: I use the word "commit" in a lot of places throughout this post and most of those should probably be "change" in jj-parlance. Hard habit to break.

Now I'll take a look at my starting status where I can see I have a couple files in the working copy on top of the root empty commit.

 jj st
Working copy changes:
A .gitignore
A fetch_tags.rb
A kit_client.rb
A migrate.rb
Working copy  (@) : vyykzxlt 9cf518a4 (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)

I could start with a jj describe and jj new to finalize a commit with message. However I want to take a more granular start by separating these files into a couple different commits.

I'm already at the edge of my jj knowledge here. My git brain wants to stage only the .gitignore file, but my understanding of jj is that all the files in the working copy are essentially staged.

To split them out, I need the jj split command. This splits out a separate commit from the working copy with the specified files.

 jj split .gitignore
Selected changes : vyykzxlt 37bf49bf Intial commit, add `.gitignore` file
Remaining changes: osvwmtyo 4d81ca67 (no description set)
Working copy  (@) now at: osvwmtyo 4d81ca67 (no description set)
Parent commit (@-)      : vyykzxlt 37bf49bf Intial commit, add `.gitignore` file

This opens my editor (nvim), prompting me to describe the commit being split out from the working copy. I type a message, save, and check the status.

 jj st
Working copy changes:
A fetch_tags.rb
A kit_client.rb
A migrate.rb
Working copy  (@) : osvwmtyo 4d81ca67 (no description set)
Parent commit (@-): vyykzxlt 37bf49bf Intial commit, add `.gitignore` file

Great. I still have an undescribed working copy with the three remaining files on top of the newly described commit that includes my .gitignore file.

I now want to split out another commit from the working copy that includes both migrate.rb and kit_client.rb.

 jj split migrate.rb kit_client.rb
Selected changes : osvwmtyo 198e7323 Add migration scripts
Remaining changes: kkozmqrl abc0d59f (no description set)
Working copy  (@) now at: kkozmqrl abc0d59f (no description set)
Parent commit (@-)      : osvwmtyo 198e7323 Add migration scripts

I'll check the status again to see that one file remains.

 jj st
Working copy changes:
A fetch_tags.rb
Working copy  (@) : kkozmqrl abc0d59f (no description set)
Parent commit (@-): osvwmtyo 198e7323 Add migration scripts

To commit the remaining file, I'll do a jj describe and then a jj new.

 jj describe -m "Add fetch_tag script for posterity"
Working copy  (@) now at: kkozmqrl 37e8029e Add fetch_tag script for posterity
Parent commit (@-)      : osvwmtyo 198e7323 Add migration scripts

 jj new
Working copy  (@) now at: quuotzzw cb888326 (empty) (no description set)
Parent commit (@-)      : kkozmqrl 37e8029e Add fetch_tag script for posterity

I can now look at all of my progress so far with the jj log command.

 jj log
@  quuotzzw [email protected] 2025-11-29 13:41:24 cb888326
  (empty) (no description set)
  kkozmqrl [email protected] 2025-11-29 13:39:29 git_head() 37e8029e
  Add fetch_tag script for posterity
  osvwmtyo [email protected] 2025-11-29 13:36:44 198e7323
  Add migration scripts
  vyykzxlt [email protected] 2025-11-29 13:21:25 37bf49bf
  Intial commit, add `.gitignore` file
  zzzzzzzz root() 00000000

Now I'd like to put all of this up on GitHub. I create my new repo on GitHub and grab the URL of the origin so that I can tell jj (which I imagine is primarily telling git under the hood) what the remote is.

 jj git remote add origin [email protected]:jbranchaud/kit-migration-script.git

I had to look up that command which led me to figuring out how to list the remotes that I have configured.

 jj git remote list
origin [email protected]:jbranchaud/kit-migration-script.git

Now I figured I could go ahead and push directly to GitHub at this point. But it doesn't work and jj does its best to tell me why.

 jj git push
Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.

I remember reading that jj bookmarks roughly correspond to git branches, so this error made sense. I guess I need to create a main bookmark.

 jj bookmark create main
Warning: Target revision is empty.
Created 1 bookmarks pointing to lyznpkpn 002a74fe main | (empty) (no description set)

 jj log
@  lyznpkpn [email protected] 2025-11-29 13:59:25 main 002a74fe
  (empty) (no description set)
  kkozmqrl [email protected] 2025-11-29 13:39:29 git_head() 37e8029e
  Add fetch_tag script for posterity
  osvwmtyo [email protected] 2025-11-29 13:36:44 198e7323
  Add migration scripts
  vyykzxlt [email protected] 2025-11-29 13:21:25 37bf49bf
  Intial commit, add `.gitignore` file
  zzzzzzzz root() 00000000

Looking at the jj log, I can see main appear on that top empty working copy commit. This struck me as a bit odd that main is pointing to an empty, no-description commit, but I didn't think too much of it. I quickly found that this wasn't quite going to work.

 jj git push
Warning: Refusing to create new remote bookmark main@origin
Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to.
Nothing changed.

 jj git push --allow-new
Error: Won't push commit 002a74fed749 since it has no description
Hint: Rejected commit: lyznpkpn 002a74fe main | (empty) (no description set)

Ok, so my main bookmark needs to be pointed at the previous commit, not this new, empty working copy. A bit more digging and I find that I can set a bookmark to point at a reference relative to the "head" (@) -- in this case to one commit back with -r @-.

 jj bookmark set main -r @-
Error: Refusing to move bookmark backwards or sideways: main
Hint: Use --allow-backwards to allow it.

 jj bookmark set main -r @- --allow-backwards
Moved 1 bookmarks to kkozmqrl 37e8029e main* | Add fetch_tag script for posterity

jj continues to be helpful letting me know that I need the --allow-backwards flag for this to work.

Checking jj log I can see the main bookmark showing up in the correct place.

 jj log
@  lyznpkpn [email protected] 2025-11-29 13:59:25 002a74fe
  (empty) (no description set)
  kkozmqrl [email protected] 2025-11-29 13:39:29 main git_head() 37e8029e
  Add fetch_tag script for posterity
  osvwmtyo [email protected] 2025-11-29 13:36:44 198e7323
  Add migration scripts
  vyykzxlt [email protected] 2025-11-29 13:21:25 37bf49bf
  Intial commit, add `.gitignore` file
  zzzzzzzz root() 00000000

Let's try that push again, specifying the --bookmark as well:

 jj git push --bookmark main --allow-new
Changes to push to origin:
  Add bookmark main to 37e8029e5924
remote: Resolving deltas: 100% (1/1), done.

And there I have it. I went from no repo to having a series of commits with specific files in each commit pushed up to GitHub. Along the way I learned about jj split, jj git remote, jj bookmark, moving a bookmark, and jj git push along with various flags.

I'm still selling myself on jj. It seems like it is going to require thinking a bit differently about things than I've learned with git. Many developers that I trust continue to advocate for it, so I trust that it will be worth effort.

Tell us about your project

We build good software through good partnerships. Reach out and we can discuss your business, your goals, and how VisualMode can help.