My small jj config
The workflow so far works with plain jj.
This is the small config I put on top. It is not a framework. It is just enough personal vocabulary to make the common moves feel natural.
[ui]
paginate = "never"
default-command = "log"
diff-formatter = ["difft", "--color=always", "$left", "$right"]
[user]
name = "Your Name"
email = "you@example.com"
username = "your-handle"
[aliases]
tug = ["bookmark", "advance"]
stack = ["log", "-r", "stack"]
bough = ["log", "-r", "bough"]
wip = ["log", "-r", "wip"]
private = ["log", "-r", "private"]
empty = ["log", "-r", "empty_stack"]
rs = ["rebase", "-b", "bookmarks('push-*')", "-o", "trunk()"]
ps = ["git", "push", "-b", "push-*"]
[revsets]
bookmark-advance-to = "@-"
[revset-aliases]
'stack' = 'trunk()..@'
'bough(x, m)' = 'descendants(ancestors(x) ~ ancestors(m))'
'bough(x)' = 'bough(x, trunk())'
'bough' = 'bough(@)'
'wip' = 'subject("wip:*") & stack'
'private' = 'subject("private:*") & stack'
'empty_stack' = 'empty() & stack ~ @'
[git]
private-commits = "subject('private:*')"
The difft line is optional. If you do not use difft, delete it and keep jj’s default diff.
The plain examples in this book use main. In my config aliases I use trunk() because it follows whatever the repo considers trunk.
Why I like this config
jj by itself becomes my graph view:
jj
That is just default-command = "log". I like it because I look at the graph constantly.
Without my config, I spell the bookmark move explicitly:
jj bookmark advance --to @-
The same explicit command with jj’s short command names is:
jj b a --to @-
With this config:
[revsets]
bookmark-advance-to = "@-"
I can drop --to @-:
jj b a
My alias is:
jj tug
So jj tug is just my name for jj b a with bookmark-advance-to = "@-" set. Since @- is the parent of @, right after jj commit it points at the change I just committed, so tugging moves the bookmark there.
The plain stack maintenance loop is:
jj git fetch
jj rebase -b 'bookmarks("push-*")' -o 'trunk()'
jj git push -b 'push-*'
My aliases are:
jj git fetch
jj rs
jj ps
That is intentionally broad. If I already pushed a generated push-* bookmark for review, I usually want to keep it current on the remote.
I do not alias jj git push --all. It is useful, but it is broad enough that I want to type it deliberately:
jj bookmark list
jj git push --all
--all means all bookmarks. It does not create bookmarks for anonymous local changes.
Logs I actually use
These are just ways to ask “what am I looking at?”
The plain commands are:
jj log -r 'trunk()..@'
jj log -r 'subject("wip:*") & trunk()..@'
jj log -r 'subject("private:*") & trunk()..@'
jj log -r 'empty() & trunk()..@ ~ @'
My aliases are:
jj stack
jj wip
jj private
jj empty
stack is the normal view: work from trunk to @.
For bough, the plain revset is not something I want to type by hand. That is exactly why it is an alias:
jj bough
bough is wider. It shows the full tree of changes beneath a given change, including all parent lines down to trunk. If I have a merge point with two parent branches, bough shows both. I use it for looking around, not as a push target.
wip and private are message-prefix searches inside the current stack.
empty shows empty changes in the stack, excluding the current empty @. That is handy after squash merges or cleanup.
That is the whole point of the config: keep the actual workflow visible, but make the moves I do all day feel good to type.
For setting up different author identities per folder and SSH keys for multiple accounts, see Setting up identities and SSH.