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:
describethe work you want to do.- create
newon top of that. - make changes in that [new] and then use
jj squashto 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
- create
newfor a change/feature - make changes
- 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
- 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 nextby 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
bookmarkto be able tojj 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:
- some projects require you to address comments by adding commits to your current PR.
- 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
sis part of that operation id. dojj 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.
- Go to one of the conflicted change-ids
jj git push(this will make you lose all your merge/pull request commits)- set main back to the head and push