Resources:

It’s not “skill issue” to prefer this over git. It’s like how you switch from Windows to Linux. It just… feels better and more intuitive? And you can’t see yourself ever going back to those again.

jj VCS

Workflows

Squash Workflow

Goes something like this:

  1. describe the work you want to do.
  2. create new on top of that.
  3. make changes in that [new] and then use jj squash to move them from @ to the one we just described.

This is a bit similar to normal git stuff—you do things, and then do git add to stage the ones we want in our commit.

  • this is because you can squash individual files—jj squash main.py.
# Git TUI -- <space> to stage; 'f' to show diff

jj squash -i

Can do jj diffedit to compare current changes with that of the previous revision (change id).

Edit workflow

  1. create new for a change/feature
  2. make changes
  3. but you want to add more stuff/break things up into smaller chunks—this is done by: making new -> swap to it -> make the change
  4. go back to main change

New change

jj new -B @: create new change before current. Whenever you make a change onto this, the subsequent [existing] changes automatically get rebased as they depend on it.

Back to main change

jj edit <change-id>

But you can do jj next --edit instead, which moves @ to the child.

  • [Note] jj next by itself will create a new change after the current one

Branching; Merging; Conflicts

Fetching

jj git fetch followed by jj rebase -d [main/bookmark]

Branches

Anonymous branches—don’t need to name your branches.

The idea is that, 2 changes that are “branching”, essentially share the same parent.

After a fresh jj new, do another jj new <parent-commit-id>

jj log 'heads(all())—shows head of every anonymous branch

Merging

Following a similar concept, merging is basically setting a new change’s parent to the anonymous branches.

jj new <p1> <p2> -m "merged features"
  • This creates a merge commit.

[This feels a bit more modular and a cleaner process than git.]

Conflicts

Naturally you may have changes that are incompatible with each other following a rebase.

Booksmarks & Remotes

They basically act as named branches. You can move it around using set, and whenever you push the change that’s tagged with that bookmark, it will be pushed onto a branch with the branch’s name being the bookmark’s name.

jj bookmark create [main]

  • when you create a bookmark, it’s automatically tracked when you set up an empty remote repo. need a bookmark to be able to jj git push

Whenever you abandon a commit that is bookedmarked, the bookmark then moves to the parent.

To make the bookmark point to latest change as it doesn’t automatically go there when you do new – this makes sense bc depending on your workflow, you make changes in a different way, thus better to wait before updating bookmark:

jj bookmark set [main] --allow-backwards to swap @ back to prev change.


When you move a bookmark to a copy that has not been pushed, that bookmark is in a dirty state. And if that bookmark is tracked/linked to the remote branch, then doing jj git fetch will cause a bookmark conflict. Therefore, the bookmark must be on a revision that has had its changed pushed to remote. Only then it’s “safe” to pull remote changes, which would move the local bookmark forward. When it’s moved forward, though, it creates a disconnected revision. Thus if you made any revisions/changes, they must be rebased on this new fetched change to be “merged”. Or just create a merge commit:

jj new <p1> <p2> -m "merged features"

Remotes

jj git remote add origin <link>
# To update the bookmark to latest change
jj bookmark set [main]
jj git push

Fetching changes

Fetched commits are immutable to ensure you don’t accidently re-write shared commits that might impact others.

Then to start working after the fetched commit—jj new [main]

The local bookmark must not be conflicted, otherwise you have to use jj bookmark move, for example, to resolve the conflict

Pull Requests

jj git push -c <@/change-id>: -c creates new branch (bookmark) from revision and automantically names that bookmark so as to create a new branch on remote.

2 types of workflows:

  1. some projects require you to address comments by adding commits to your current PR.
  2. some prefer to just have a 1 whole commit. therefore to address the changes, you’d have to change that revision itself and then force-pushing.

Adding commits to a PR

[Usually done before the maintainer merges the initial req]

# this rev's parent should be the PR branch

jj new -m "respond to feedback"

Then point the PR commit bookmark to point to latest change. Then jj git push

# update bookmark to point to new commit

jj bookmark set <pr-branch-bookmark>

Rebasing a PR (1 change and 1 commit)

Move the bookmark to the initial change if you want to edit something jj bookmark set -r @- --allow-backwards. Move @ to it as well (jj edit).

Make your changes and push.

# jj automatically makes it a force push

jj git push --bookmark <bookmark/branch name>

Misc.

jj split if you want to split up the changes in a revision.

jj undo

  • This switches back and forth. If you want to undo operations from a while ago or series of operations, do jj op log (shows operations history). One operation is in the form:
○  5a14e6a03a23 .....
s
│  new empty commit
│  args: jj new l r px
  • Everything under s is part of that operation id. do jj undo <op-id> to undo that change.

“??” Bookmarks (conflicted bookmarks)

  • set the bookmark to the correct commit jj bookmark move [main] --to <commit ID>.

—Situations

When you edit a few commits before fetching and there’s a bookmark conflict.

  1. Go to one of the conflicted change-ids
  2. jj git push (this will make you lose all your merge/pull request commits)
  3. set main back to the head and push