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 is useful when I have side experiments or local integration commits around the same branch of thought. 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.
Accounts and SSH
If I need different author identities per folder, I use config scopes:
[[--scope]]
--when.repositories = ["~/dev/work"]
[--scope.user]
email = "you@work.example"
username = "work-handle"
[[--scope]]
--when.repositories = ["~/dev/personal"]
[--scope.user]
email = "you@personal.example"
username = "personal-handle"
SSH keys stay in ~/.ssh/config, because jj uses Git for remote operations:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_oss
IdentitiesOnly yes
Host gerrit.example.com
HostName gerrit.example.com
User my-google-account
IdentityFile ~/.ssh/id_ed25519_google
IdentitiesOnly yes
If two accounts live on the same host, I use host aliases like github-oss and github-work, then put that alias in the remote URL.