jj VCS

jj git init

@ is a special name for “whichever commit the working copy reflects.” Don’t think of it as HEAD, as HEAD is the most recent commit, but @ represents the working copy, which may be “dirty” from git’s point of view. This is indexless workflow.

Log

By default, it omits some commits. Can use revsets.

  • jj log -r ::
  • jj log -r 'all()

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.

Rebasing

Takes a change and moves it to have a different parent, “basing” it again.

# [s]ource; [d]estination

jj rebase -s <c> -d <p>

[use r instead of s if you don’t want to rebase the descendents.]

  • <c> is the child you want to rebase onto the destination—<p> (parent). Works well with branches since they share the same parent anyways. Gives us a linear history.

This also lets you change the commits up and down by changing/assigning new parents.

[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.

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.

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>.

Revsets

Revset is an expression for specifying revisions. Invoked by --revisions/-r to select revision(s) to list. Most commands use this under the hood, but omit it. E.g.:

jj edit @+

+ means the child of @ (working copy).

“Use it in a revset” basically means use the [thing] in functions and stuff.


jj abandon <commit>

  • delete this copy/revision/commit

gitignore

After you add a path to gitignore, run jj file untrack <path>.

—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