- can be used as a frontend for git
- has a very smooth CLI UI and workflow
- I can confirm it is a joy to use
I highly recommend watching the first video below.
-
Official
-
Recommended tools:
- lazyjj - a great CLI UI frontend for JJ
-
Introductory materials:
- What if version control was
AWESOME?
[VIDEO]-- highly recommended video to watch - jj init -- great article, see also the video above
- Jujutsu | Ep. 5 Bits and
Booze
[VIDEO]-- a nice intro and walkthrough by GitButler - Jujutsu: a new, Git-compatible version control system - a bit dated but still good, from LWN.net
- What if version control was
AWESOME?
I haven't touched git since using it in a few repos. JJ makes my daily "git" use really effortless, it never gets in the way. Pulling in work from other branches, quickly peeking into just-fetched PR branches, splitting commits, anonymous temp branches, squashing and pushing - have become really effortless.
I expected to miss the time I spent in vim-fugitive, staging, committing, and
pushing directly from inside Neovim—a workflow I was very accustomed to—but
surprisingly, I didn’t. Now, I’ve gotten lazy and often use lazyjj for most
tasks, except for editing multi-line commit messages, where I still prefer
Neovim over lazyjj’s built-in editor.
One thing I hadn’t anticipated: since your working copy is always committed—essentially functioning as an ever-amending commit—you can freely switch to past commits or other Git branches. This includes actions like “checking out” newly fetched PRs, viewing their diffs, and so on, all without any issues. No conflicts, no need to stash away changes temporarily!
I can keep using git! I always work in colocated JJ repositories. That
means, if I feel like it, I can switch to git / vim-fugitive for a while, and
return to my old workflow. The next time I run jj, it will sync the changes.
That really works like a charm.
TEMPORARY FILES! In some repositories, I collected quite an amount of temp
files, artifacts like generated output files when quickly testing something,
which I just kept ignoring when staging. Since JJ always snapshots the working
copy, I had to .gitignore those.
Shoutout to Tom Lord, original author of Tom Lord's then GNU
arch, which I had switched to after
CVS - and probably the VCS that had the most influence on me, before
eventually migrating to mercurial, and a long time later, eventually to git.
To this day I still miss some of TLA's features.
Anyway, I digress. Tom introduced me to prefixing filenames of "temporary" files
with a comma: ,forgetme.txt. I now returned to this practice, and put ,* in
my .gitignore. Yes, jj respects gitignore files!
# we start with git (and show how it's done BETTER with jj a few lines below)
git clone [email protected]...
# init jj alongside git - you do that only in existing git repos
jj git init --colocate
# start tracking your branches
# jj can do that automatically, see jj git clone below
jj bookmark track master@origin
jj bookmark track whatever_branch@origin
# instead of all of the above, we can let jj do the git work, incl. tracking
jj git clone --colocate [email protected]...
# work:
# you `jj new`, `jj describe` for each change
# everytime you run jj after a save, the current change is updated
# you can `jj describe` at any time!
jj new # new change: introduces a new commit to work on
# keep editing files ...
jj st # show status of changed files etc
jj diff # show diff
jj # show log
jj log # same
jj describe # change commit message at any time.
# change a commit message from a past change, identified by shortcut xx
# you see the shortcuts in jj log
# this works even with commits that have been pushed already!
jj desc -r xx
# pushing : catch up bookmark of git branch name to the (second-) most-recent change
# - jj does not move the (git branch name) bookmark along with every change
# - so you need to tell it to update the bookmark to the latest commit
# - but if you `jj new`ed, you're on an empty commit.
# so you want to update to the second-latest commit in that usual case.
# @ denotes the current change
# @- denotes the change before the current change
jj bookmark set master -r @- # provided we jj new'ed before
jj bookmark set master -r @ # provided we did not jj new yet
jj bookmark set whatever_branch -r @- # just a non-master example
jj git push # push the current commit's bookmark if there is one
jj undo # if you think you messed upIt's even easier. j/k to look at commits, d to run describe, n to create a new change, b to open the bookmark menu where you j/k to the bookmark you want to assign to the selected (second-latest/latest) change, p to push.
You can get really advanced, without jj feeling complicated.
For example, when applying above commands like new, describe, etc to a change from the past. You can introduce changes/commits in-between earlier commits, branch off by inserting a series of changes "in the past", merge, split changes into multiple ones, ... etc. etc.
The displayed shortcuts for change and commit IDs are really helpful for the CLI.
And I haven't even mentioned the jj operation log !!!
The YT videos go into some more advanced uses of jj.
See below for an example from me.
So I decide to work on a branch & GitHub PR. In jj, we use bookmarks to work with named git branches.
# I am on the master branch
jj new # to make a new change
jj bookmark create rene/feature_A
# yayyy!I work on my branch:
jj new
# edit files
jj describe -m "blah"
# repeat (editing and describing)...
# ...
# time to push
jj new
jj bookmark set rene/feature_A @-
jj git push
# or lazyjj this effortlessly and press p to pushAt some stage, a colleague of mine informs me that they made changes in a PR that you want in your branch/PR as well. So first, you fetch to get your colleague's branch:
jj git fetchYou then jj log to see the branch or, if you just want the change ID of the PR, you can check out the list of bookmarks:
jj bookmark list
friend/feature_B: ypvwxzwo db1c5ccb fix comment
master: xxqnkzkt 6443fef5 use correct path (#35)
rene/feature_A: mwlxnqmm efb96bea great progressIn jj log you also see that master has moved on with more commits but that
doesn't bother you because you want to keep working on your branch.
So, we see the following. Our branch is at change mwlxnqmm and our friend's branch is at change ypvwxzwo. Perfect!
Optional: Because our working copy is clean, we can check out the new branch and
have a look at it in our editor / IDE: jj edit yp. When done checking it out,
we just return to where we left off: jj edit mw. One tip though: in order to
not change past commits by accident, do a jj new right after switching to
yp. You can abandon that change later if you like: jj abandon. When done, we
switch back to where we were: jj edit mw.
To bring in the changes from our friend's PR, we just create a new change that's based on two parents: our last commit and the last commit from our friend's PR:
jj new mw ypDONE!
We now describe and push to GitHub:
jj describe -m "pulled in PR #xx"
jj bookmark set rene/feature_A @
jj git push
jj newAnd we keep on working ...