baxter.sh

Cleaner Jujutsu logs

I mentioned a little bit in my previous Jujutsu post that I find the default Jujutsu logs too noisy.

I love syntax highlighting, but I’ve always felt that if everything is highlighted then nothing is highlighted, which is exactly the point that tonsky makes in his recent blog post on syntax highlighting.

This post presumes a certain degree of familiarity with Jujutsu, so if you’re not sure, please check out my previous post, which gives some examples and also links to some introductions.

Here’s how the jj log command looks on my machine without any config changes (output captured with the aha app):

@  vznqvuon [email protected] 2025-10-23 14:16:54 0e6130d7(empty) (no description set)  tsxqxvqs [email protected] 2025-10-23 14:16:34 main ded4b6ab
├─╯  (empty) Merge branch 'baxter/change-wkyuorzppylx' into 'main'
  wkyuorzp [email protected] 2025-10-23 14:11:05 0d2cd7ec
│  Feature description ...
~  (elided revisions)~  (elided revisions)
├─╯
│ ○  ypkklntw [email protected] 2025-10-23 09:53:53 95adb2aa
├─╯  Yet another change
  smquzzqr [email protected] 2025-09-24 16:57:34 fbcd29ad
│  An existing change
~

As you can see, there’s a lot of dense text, and there are many highlights. All of the highlights are meaningful, but not all of them are equally important.

As a reminder, we can edit the current user’s Jujutsu config with jj config edit --user.

We can make the log a bit easier to read by picking a different default template:

[templates]
log = 'builtin_log_comfortable'
@  vznqvuon [email protected] 2025-10-23 14:16:54 0e6130d7(empty) (no description set)
│
│   tsxqxvqs [email protected] 2025-10-23 14:16:34 main ded4b6ab
├─╯  (empty) Merge branch 'baxter/change-wkyuorzppylx' into 'main'
│
  wkyuorzp [email protected] 2025-10-23 14:11:05 0d2cd7ec
│  Feature description ...
│
~  (elided revisions)~  (elided revisions)
├─╯
│ ○  ypkklntw [email protected] 2025-10-23 09:53:53 95adb2aa
├─╯  Yet another change
│
  smquzzqr [email protected] 2025-09-24 16:57:34 fbcd29ad
│  An existing change
~

That’s a bit less dense.

We can also reduce the amount of information shown. As I mentioned in my previous Jujutsu post, we can show the shortest unique ID for changes and commits with:

[template-aliases]
'format_short_id(id)' = "id.shortest()"

And we can remove the email address when it’s your own email address:

[template-aliases]
'format_short_signature(signature)' = '''
  coalesce(
    if(signature.email() == config("user.email").as_string(), label("author me", "(me)")),
    signature.email(),
    email_placeholder
  )
'''

To give a bit more explanation of what this does, coalesce(a, b, c) picks the first non-empty value out of a, b or c. label("foo bar", "hello") outputs the string “hello” with the labels “foo” and “bar”. I’ll explain a little more about labels in a moment.

Using these template aliases leaves us with:

@  v (me) 2025-10-23 14:16:54 0e(empty) (no description set)
│
│   t (me) 2025-10-23 14:16:34 main d
├─╯  (empty) Merge branch 'baxter/change-wkyuorzppylx' into 'main'
│
  w (me) 2025-10-23 14:11:05 0d
│  Feature description ...
│
~  (elided revisions)~  (elided revisions)
├─╯
│ ○  y (me) 2025-10-23 09:53:53 9
├─╯  Yet another change
│
  s (me) 2025-09-24 16:57:34 f
│  An existing change
~

There’s a bit less information now, but in terms of highlighting, there are a lot of things highlighted that I don’t personally feel are that important.

What I am generally interested in when checking jj log is:

  • What’s the Change ID for a change?
  • Which remote branch does this change relate to?

So let’s highlight those two pieces of information.

The standard set of colours used by Jujutsu is defined in Jujutsu’s colors.toml but how do we know which colour is applied to which string in jj log?

Well, you can run the command jj log --color=debug to output a list of all of the labels applied to each string, which in turn determine which colours will be applied to that label.

<<log commit node working_copy mutable::@>>  <<log commit working_copy mutable change_id shortest prefix::v>><<log commit working_copy mutable:: >><<log commit working_copy mutable author me::(me)>><<log commit working_copy mutable:: >><<log commit working_copy mutable committer timestamp local format::2025-10-23 14:16:54>><<log commit working_copy mutable:: >><<log commit working_copy mutable commit_id shortest prefix::0e>><<log commit working_copy mutable::>><<log commit working_copy mutable empty::(empty)>><<log commit working_copy mutable:: >><<log commit working_copy mutable empty description placeholder::(no description set)>><<log commit working_copy mutable::>>
│  <<log commit::>>
│ <<log commit node immutable::◆>>  <<log commit immutable change_id shortest prefix::t>><<log commit immutable:: >><<log commit immutable author me::(me)>><<log commit immutable:: >><<log commit immutable committer timestamp local format::2025-10-23 14:16:34>><<log commit immutable:: >><<log commit immutable bookmarks name::main>><<log commit immutable:: >><<log commit immutable commit_id shortest prefix::d>><<log commit immutable::>>
├─╯  <<log commit immutable empty::(empty)>><<log commit immutable:: >><<log commit immutable description first_line::Merge branch 'baxter/change-wkyuorzppylx' into 'main'>><<log commit immutable::>>
│    <<log commit::>>
<<log commit node immutable::◆>>  <<log commit immutable change_id shortest prefix::w>><<log commit immutable:: >><<log commit immutable author me::(me)>><<log commit immutable:: >><<log commit immutable committer timestamp local format::2025-10-23 14:11:05>><<log commit immutable:: >><<log commit immutable commit_id shortest prefix::0d>><<log commit immutable::>>
│  <<log commit immutable description first_line::Feature description ...>><<log commit immutable::>>
│  <<log commit::>>
<<log commit node elided::~>>  <<elided::(elided revisions)>><<log commit node elided::~>>  <<elided::(elided revisions)>>
├─╯
│ <<log commit node mutable::○>>  <<log commit mutable change_id shortest prefix::y>><<log commit mutable:: >><<log commit mutable author me::(me)>><<log commit mutable:: >><<log commit mutable committer timestamp local format::2025-10-23 09:53:53>><<log commit mutable:: >><<log commit mutable commit_id shortest prefix::9>><<log commit mutable::>>
├─╯  <<log commit mutable description first_line::Yet another change>><<log commit mutable::>>
│    <<log commit::>>
<<log commit node immutable::◆>>  <<log commit immutable change_id shortest prefix::s>><<log commit immutable:: >><<log commit immutable author me::(me)>><<log commit immutable:: >><<log commit immutable committer timestamp local format::2025-09-24 16:57:34>><<log commit immutable:: >><<log commit immutable commit_id shortest prefix::f>><<log commit immutable::>>
│  <<log commit immutable description first_line::An existing change>><<log commit immutable::>>
~  <<log commit::>>

It’s not the easiest thing to read, but if you scroll along the first line, you can see the following string:

<<log commit working_copy mutable author me::(me)>>

This is the string that I added using my template alias above, label("author me", "(me)") — it outputs the string “(me)” with the labels “author” and “(me)”. It also has some other labels, “log”, “commit”, “working_copy”, and “mutable”.

The decision about which colour to apply to which string is based on its labels and the definition in Jujutsu’s colors.toml file.

There’s a section in Jujutsu’s docs on how to style colours which explains how label matching works. It’s basically the same as CSS where more specific label combinations override less specific ones, e.g. “working_copy author” overrides “author”.

Looking at the colours listed in Jujutsu’s colors.toml you can see that a lot of the colours defined there match up with what we see in our jj log output:

[colors]
"commit_id" = "blue"
"change_id" = "magenta"
# ...
"author" = "yellow"
"committer" = "yellow"
"timestamp" = "cyan"
"working_copies" = "green"
"bookmark" = "magenta"

We can remove a lot of the colours with the following change:

[colors]
"author" = "default"
"commit_id" = "default"
"empty" = "default"
"prefix" = "default"
"timestamp" = "default"

Resulting in:

@  v (me) 2025-10-23 14:16:54 0e(empty) (no description set)
│
│   t (me) 2025-10-23 14:16:34 main d
├─╯  (empty) Merge branch 'baxter/change-wkyuorzppylx' into 'main'
│
  w (me) 2025-10-23 14:11:05 0d
│  Feature description ...
│
~  (elided revisions)~  (elided revisions)
├─╯
│ ○  y (me) 2025-10-23 09:53:53 9
├─╯  Yet another change
│
  s (me) 2025-09-24 16:57:34 f
│  An existing change
~

This is less colourful, but there’s still a couple of problems.

  • The working copy is still styled
  • The change ID is now harder to detect at a glance

We can fix the first problem by adding some more specific styling for the working copy:

[colors]
"working_copy author" = "default"
"working_copy commit_id" = "default"
"working_copy empty" = "default"
"working_copy timestamp" = "default"

# We also need to add something more specific for the "no description" message
"empty description placeholder" = "default"

And we can add a colour to the change ID by adding a rule that would match its labels. I went with “change_id prefix”. I also want to make the bookmarks a bit easier to read.

[colors]
"change_id prefix" = { fg = "bright yellow", bold = true }
"bookmarks" = { fg = "bright magenta", bold = true }

Let’s see how all of these changes together look. Here’s the toml:

[colors]
"author" = "default"
"commit_id" = "default"
"empty" = "default"
"prefix" = "default"
"timestamp" = "default"
"empty description placeholder" = "default"

"working_copy author" = "default"
"working_copy commit_id" = "default"
"working_copy empty" = "default"
"working_copy timestamp" = "default"
"working_copy empty description placeholder" = "default"

"change_id prefix" = { fg = "bright yellow", bold = true }
"bookmarks" = { fg = "bright magenta", bold = true }
"working_copy node" = { fg = "bright green", bold = true }

[template-aliases]
'format_short_id(id)' = "id.shortest()"
'format_short_signature(signature)' = '''
  coalesce(
    if(signature.email() == config("user.email").as_string(), label("author me", "(me)")),
    signature.email(),
    email_placeholder
  )
'''

[templates]
log = 'builtin_log_comfortable'

And here’s the result:

@  v (me) 2025-10-23 14:16:54 0e(empty) (no description set)
│
│   t (me) 2025-10-23 14:16:34 main d
├─╯  (empty) Merge branch 'baxter/change-wkyuorzppylx' into 'main'
│
  w (me) 2025-10-23 14:11:05 0d
│  Feature description ...
│
~  (elided revisions)~  (elided revisions)
├─╯
│ ○  y (me) 2025-10-23 09:53:53 9
├─╯  Yet another change
│
  s (me) 2025-09-24 16:57:34 f
│  An existing change
~

I think this is a lot clearer. The change IDs and the bookmarks are highlighted and the other information isn’t too dense.

Jujutsu is very customizable and it’s definitely worth playing around with what’s possible so that you can improve your experience.