<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://zerowidth.com/</id>
  <title>zerowidth positive lookahead</title>
  <link href="https://zerowidth.com/atom.xml" rel="self"/>
  <link href="https://zerowidth.com/"/>
  <updated>2026-05-22T17:21:50-06:00</updated>
  <author>
    <name>Nathan Witmer</name>
    <email>nathan@zerowidth.com</email>
  </author>

  
  
  
  <entry>
    <title>An async zsh jujutsu prompt with p10k</title>
    <link href="https://zerowidth.com/2025/async-zsh-jujutsu-prompt-with-p10k/"/>
    <updated>2025-08-16T00:00:00-06:00</updated>
    
    <id>https://zerowidth.com/2025/async-zsh-jujutsu-prompt-with-p10k</id>
    
    
    <summary type="html">How to add an async &lt;code&gt;jujutsu&lt;/code&gt; segment to your powerlevel10k prompt in zsh</summary>
    
    <content type="html">&lt;p&gt;My zsh configuration uses the &lt;a href=&quot;https://github.com/romkatv/powerlevel10k&quot;&gt;powerlevel10k&lt;/a&gt; plugin to render the shell prompt. While the p10k project is no longer maintained, it’s still a good choice due to its easy configuration and performance. The git integration uses a system called &lt;code&gt;gitstatus&lt;/code&gt; to load git status into the prompt asynchronously, which makes a noticeable difference to responsiveness in large repositories. Unfortunately, because of its tight integration and private APIs, the &lt;code&gt;gitstatus&lt;/code&gt; piece of the prompt is difficult to modify or extend for new VCS systems.&lt;/p&gt;
&lt;p&gt;I’ve been using the &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;jujutsu&lt;/a&gt; VCS in colocated mode. This lets my editor continue to use git status to show file changes. However, since &lt;code&gt;jj&lt;/code&gt; leaves git in a “detached HEAD” state, git status prompts are no longer very informative. I’ve figured out a way to add a separate jj-specific prompt segment, including an async version, to show details about the current &lt;code&gt;jj&lt;/code&gt; change:&lt;/p&gt;
&lt;div class=&quot;language-zsh-prompt highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh-prompt&quot;&gt;&lt;span class=&quot;zsh-fg-blue&quot;&gt;~/jj-prompt &lt;/span&gt;&lt;span class=&quot;zsh-fg-magenta&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;zpu&lt;/span&gt; &lt;span class=&quot;zsh-fg-blue&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;97c&lt;/span&gt; &lt;span class=&quot;zsh-fg-yellow&quot;&gt;(no description)&lt;/span&gt; &lt;span class=&quot;zsh-fg-magenta&quot;&gt;a-bookmark&lt;/span&gt; &lt;span class=&quot;zsh-fg-white&quot;&gt;⇡1 &lt;/span&gt;&lt;span class=&quot;zsh-fg-green&quot;&gt;+1&lt;/span&gt; &lt;span class=&quot;zsh-fg-cyan&quot;&gt;±1&lt;/span&gt; &lt;span class=&quot;zsh-fg-red&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;zsh-fg-magenta&quot;&gt;↻1&lt;/span&gt;
&lt;span class=&quot;zsh-fg-green&quot;&gt;❯&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This prompt shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the current working directory&lt;/li&gt;
&lt;li&gt;the current change id&lt;/li&gt;
&lt;li&gt;the current change’s git SHA&lt;/li&gt;
&lt;li&gt;an indicator that there’s no description for the change yet. This shows &lt;code&gt;(empty)&lt;/code&gt; when applicable, and also picks up on &lt;code&gt;wip:&lt;/code&gt; or &lt;code&gt;todo:&lt;/code&gt; in descriptions to show an extra label.&lt;/li&gt;
&lt;li&gt;the nearest bookmark &lt;code&gt;a-bookmark&lt;/code&gt; and the number of changes (1) between &lt;code&gt;@&lt;/code&gt; and the bookmark.&lt;/li&gt;
&lt;li&gt;the number of added, modified, removed, and renamed files as seen by &lt;code&gt;jj&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this information is strictly necessary, but the “(empty)” or “(no description)” in particular are helpful. The rest is fun to see, and I figured out how to make it asynchronous to show these the extra details without affecting the prompt’s performance.&lt;/p&gt;
&lt;h2&gt;Simple synchronous prompt&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;p10k.zsh&lt;/code&gt; (as generated by the &lt;code&gt;p10k configure&lt;/code&gt; command), it’s &lt;a href=&quot;https://github.com/romkatv/powerlevel10k?tab=readme-ov-file#extensible&quot;&gt;easy to define your own segments&lt;/a&gt;. Let’s start with a simple example showing the current change ID:&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;prompt_my_jj&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;current_workspace change_id display

  &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; jj &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;current_workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj workspace root 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return

  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;change_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change_id.shortest(4)&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$change_id&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/\x1b\[[0-9;]*m/%{&amp;amp;%}/g&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
  p10k segment &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$display&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# and add the this jj segment to the powerlevel prompt:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;typeset&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;POWERLEVEL9K_LEFT_PROMPT_ELEMENTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
  context                 &lt;span class=&quot;c&quot;&gt;# user@hostname&lt;/span&gt;
  my_jj                   &lt;span class=&quot;c&quot;&gt;# jj version control status&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Piece by piece, this function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checks that the current working directory is in a &lt;code&gt;jj&lt;/code&gt; workspace&lt;/li&gt;
&lt;li&gt;fetches the current change with &lt;code&gt;jj&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--ignore-working-copy&lt;/code&gt; prevents &lt;code&gt;jj&lt;/code&gt; from taking a new snapshot, it only shows the state of what it currently knows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-graph&lt;/code&gt; disables the visual graph rendering&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--limit 1&lt;/code&gt; ensures we only get the first change in the revisions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--revisions @&lt;/code&gt; gets only the current change&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--color always&lt;/code&gt; forces ANSI colors so we can use jj’s color formatting for the prompt output&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-T &apos;change_id.shortest(4)&apos;&lt;/code&gt; defines a template to fetch the first four characters of the change id.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sed &apos;s/\x1b\[[0-9;]*m/%{&amp;amp;%}/g&apos;&lt;/code&gt;: &lt;code&gt;jj&lt;/code&gt; is emitting colors with &lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI_escape_code#Colors&quot;&gt;ANSI color escape codes&lt;/a&gt;, but the prompt doesn’t know to ignore those extra characters for width calculations. The &lt;code&gt;sed&lt;/code&gt; regex finds &lt;code&gt;\x1b\[0;]&lt;/code&gt; ANSI escapes and wraps them in &lt;code&gt;%{ %}&lt;/code&gt;, telling the &lt;a href=&quot;https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html#Visual-effects&quot;&gt;zsh prompt expansion&lt;/a&gt; to treat them as literal escape sequences and thus zero width, so they don’t affect width calculations.&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;p10k segment -t &amp;quot;$display&amp;quot;&lt;/code&gt; defines the content of the &lt;code&gt;my_jj&lt;/code&gt; segment, rendering the current change:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;language-zsh-prompt highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh-prompt&quot;&gt;&lt;span class=&quot;zsh-fg-blue&quot;&gt;~/jj-prompt &lt;/span&gt;&lt;span class=&quot;zsh-fg-magenta&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;s&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;pxl&lt;/span&gt;
&lt;span class=&quot;zsh-fg-green&quot;&gt;❯&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Making it async&lt;/h2&gt;
&lt;p&gt;We can display basic information quickly, but anything complex will make the prompt unresponsive.&lt;/p&gt;
&lt;p&gt;Fortunately, there is a zsh plugin called &lt;a href=&quot;https://github.com/mafredri/zsh-async&quot;&gt;zsh-async&lt;/a&gt;. This plugin lets us define asynchronous jobs, call them, and handle the callback when the job completes. Here’s &lt;a href=&quot;https://github.com/davidparsson/p10k-ci-status/blob/main/p10k-ci-status.plugin.zsh&quot;&gt;an example of using this for CI status&lt;/a&gt;, and another for &lt;a href=&quot;https://github.com/romkatv/powerlevel10k/issues/609&quot;&gt;docker compose status&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https://github.com/zsh-users/antigen&quot;&gt;antigen&lt;/a&gt; for zsh plugins, so I’ve added it to my &lt;code&gt;.zshrc&lt;/code&gt;. The &lt;code&gt;@main&lt;/code&gt; suffix is important: antigen assumes &lt;code&gt;master&lt;/code&gt; is the default branch name, which is incorrect for this plugin.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;antigen bundle mafredri/zsh-async@main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Initial async setup&lt;/h3&gt;
&lt;p&gt;First we register global variables. The display string is for the prompt segment, and will be updated asynchronously. The current workspace tracks which workspace we’re in so we can clear the segment if we change to a different workspace. If we didn’t do this, we might temporarily show stale information in the prompt from the previous workspace.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nb&quot;&gt;typeset&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;typeset&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_my_jj_workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Next, we define the worker and callbacks.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;c&quot;&gt;# This function is what gets called asynchronously.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# It&apos;s given the current workspace as its argument, and outputs the&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# formatted prompt segment.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# The workspace argument is required, as the async worker can&apos;t see&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# the global $_my_jj_workspace.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# --repository specifies which workspace to operate in.&lt;/span&gt;
_my_jj_async&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;change_id

  &lt;span class=&quot;nv&quot;&gt;change_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change_id.shortest(3)&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$change_id&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/\x1b\[[0-9;]*m/%{&amp;amp;%}/g&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# This callback is called with the result of the async function.&lt;/span&gt;
_my_jj_callback&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;job_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$3&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;execution_time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$4&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$5&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;next_pending&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$6&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exit_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %F{red}&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stderr&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%f&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# This is the dynamic piece: once we have the display data, tell p10k to&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# redraw the prompt in-place.&lt;/span&gt;
  p10k display &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then, initialize the plugin and callbacks. We explicitly stop and restart the worker and re-register the callback so we can handle a full reload of the config.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;async_init
async_stop_worker _my_jj_worker 2&amp;gt;/dev/null
async_start_worker _my_jj_worker
async_unregister_callback _my_jj_worker 2&amp;gt;/dev/null
async_register_callback _my_jj_worker _my_jj_callback
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The prompt itself tracks the current workspace and registers the segment with p10k. The difference from before is that the segment value is in single quotes with a &lt;code&gt;-e&lt;/code&gt; flag: this tells &lt;code&gt;p10k&lt;/code&gt; to reinterpret the segment each time it’s rendered, allowing the dynamic update to get the latest from &lt;code&gt;$_my_jj_display&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;prompt_my_jj&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;workspace

  &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; jj &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj workspace root 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# track current workspace for the async worker&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$_my_jj_workspace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# clear segment if we&apos;ve moved&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;_my_jj_workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# request async job for the current workspace&lt;/span&gt;
  async_job _my_jj_worker _my_jj_async &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# single quotes, we want this to be interpreted each time&lt;/span&gt;
  p10k segment &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$_my_jj_display&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Fancier prompt&lt;/h2&gt;
&lt;p&gt;We’ll use some revset aliases and templates defined in &lt;code&gt;jjconfig.toml&lt;/code&gt; to make things a little more flexible and easier to update. If you’re looking for template examples, check the built-in configs: &lt;code&gt;jj config list --include-defaults --include-overridden&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, the revision data should show the current change, the current git SHA, whether or not it’s hidden or conflicted, and &lt;code&gt;(empty)&lt;/code&gt; and &lt;code&gt;(no description)&lt;/code&gt; when applicable:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[template-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;format_short_change_id(id)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;id.shortest(4)&apos;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;format_short_commit_id(id)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;id.shortest(4)&apos;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&apos;
separate(&quot; &quot;,
  format_short_change_id_with_hidden_and_divergent_info(self),
  format_short_commit_id(commit_id),
  if(empty, label(&quot;empty&quot;, &quot;(empty)&quot;), &quot;&quot;),
  if(description == &quot;&quot;, label(&quot;description placeholder&quot;, &quot;(no description)&quot;), &quot;&quot;),
  if(conflict, label(&quot;conflict&quot;, &quot;(conflict)&quot;), &quot;&quot;)
)
&apos;&apos;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then using this new prompt template:&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;prompt&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We’ll use a revset to find the name and distance to the nearest ancestor bookmark:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[revset-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;closest_bookmark(to)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;heads(::to &amp;amp; bookmarks())&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;bookmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;closest_bookmark(@)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;bookmarks.join(&quot; &quot;)&apos;&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; never &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;closest_bookmark(@)..@&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change_id ++ &quot;\n&quot;&apos;&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Finally, we can use &lt;code&gt;awk&lt;/code&gt; to parse and count file change statuses, and show them with some icons. This uses zsh’s prompt color highlighting syntax to set the colors.&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;file_status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; never &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;self.diff().files().map(|f| f.status()).join(&quot;\n&quot;)&apos;&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;
    /modified/ { parts[++i] = &quot;%F{cyan}±&quot; $1 &quot;%f&quot; }
    /added/ { parts[++i] = &quot;%F{green}+&quot; $1 &quot;%f&quot; }
    /removed/ { parts[++i] = &quot;%F{red}-&quot; $1 &quot;%f&quot; }
    /copied/ { parts[++i] = &quot;%F{yellow}⧉&quot; $1 &quot;%f&quot; }
    /renamed/ { parts[++i] = &quot;%F{magenta}↻&quot; $1 &quot;%f&quot; }
    END { for (j=1; j&amp;lt;=i; j++) printf &quot;%s%s&quot;, parts[j], (j&amp;lt;i ? &quot; &quot; : &quot;&quot;) }
  &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Tying it all together:&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$revision&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bookmark&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bookmark&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$distance&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-gt&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; %7F⇡&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi
fi
if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file_status&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;file_status&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$display&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/\x1b\[[0-9;]*m/%{&amp;amp;%}/g&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This looks like:&lt;/p&gt;
&lt;div class=&quot;language-zsh-prompt highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh-prompt&quot;&gt;&lt;span class=&quot;zsh-fg-blue&quot;&gt;~/jj-prompt &lt;/span&gt;&lt;span class=&quot;zsh-fg-magenta&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;zpu&lt;/span&gt; &lt;span class=&quot;zsh-fg-blue&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;441&lt;/span&gt; &lt;span class=&quot;zsh-fg-yellow&quot;&gt;(no description)&lt;/span&gt; &lt;span class=&quot;zsh-fg-magenta&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;zsh-fg-white&quot;&gt;⇡2 &lt;/span&gt;&lt;span class=&quot;zsh-fg-green&quot;&gt;+1&lt;/span&gt; &lt;span class=&quot;zsh-fg-cyan&quot;&gt;±1&lt;/span&gt; &lt;span class=&quot;zsh-fg-red&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;zsh-fg-magenta&quot;&gt;↻1&lt;/span&gt;
&lt;span class=&quot;zsh-fg-green&quot;&gt;❯&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Disabling the &lt;code&gt;vcs&lt;/code&gt; segment in &lt;code&gt;jj&lt;/code&gt; workspaces&lt;/h2&gt;
&lt;p&gt;The prompt examples above left out the &lt;code&gt;vcs&lt;/code&gt; segment, which shows a &lt;code&gt;detached HEAD&lt;/code&gt; status. If enabled in a colocated jj repository, it looks like this:&lt;/p&gt;
&lt;div class=&quot;language-zsh-prompt highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh-prompt&quot;&gt;&lt;span class=&quot;zsh-fg-blue&quot;&gt;~/jj-prompt &lt;/span&gt;on @&lt;span class=&quot;zsh-fg-green&quot;&gt;397c199a &lt;/span&gt;&lt;span class=&quot;zsh-fg-yellow&quot;&gt;!3 &lt;/span&gt;&lt;span class=&quot;zsh-fg-magenta&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;l&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;vkk&lt;/span&gt; &lt;span class=&quot;zsh-fg-blue&quot;&gt;&lt;span class=&quot;zsh-bold&quot;&gt;a&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;zsh-fg-8&quot;&gt;a10&lt;/span&gt; &lt;span class=&quot;zsh-fg-green&quot;&gt;(empty)&lt;/span&gt; &lt;span class=&quot;zsh-fg-yellow&quot;&gt;(no description)&lt;/span&gt; &lt;span class=&quot;zsh-fg-magenta&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;zsh-fg-white&quot;&gt;⇡3&lt;/span&gt;
&lt;span class=&quot;zsh-fg-green&quot;&gt;❯&lt;/span&gt;              ^^^^^^^^^ ^^
                       |  `- git status shows changed files too
                       `- detached git HEAD SHA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;p10k&lt;/code&gt; gives us an easy way to selectively disable and enable segments. All it takes is a small addition to the “are we in a jj workspace” check:&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj workspace root 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/jj=show&quot;&lt;/span&gt;
  p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/vcs=hide&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else
  &lt;/span&gt;p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/jj=hide&quot;&lt;/span&gt;
  p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/vcs=show&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This can also be accomplished using &lt;code&gt;precmd&lt;/code&gt; and &lt;code&gt;chpwd&lt;/code&gt; hooks.&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Including custom labels for changes described with “wip”, “todo”, and so forth.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jjconfig.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[template-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;format_short_change_id(id)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;id.shortest(4)&apos;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;format_short_commit_id(id)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;id.shortest(4)&apos;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&apos;
separate(&quot; &quot;,
  format_short_change_id_with_hidden_and_divergent_info(self),
  format_short_commit_id(commit_id),
  if(empty, label(&quot;empty&quot;, &quot;(empty)&quot;), &quot;&quot;),
  if(description == &quot;&quot;, label(&quot;description placeholder&quot;, &quot;(no description)&quot;), &quot;&quot;),
  if(description.contains(&quot;megamerge&quot;), label(&quot;mega&quot;, &quot;(mega)&quot;), &quot;&quot;),
  if(description.starts_with(&quot;wip&quot;), label(&quot;wip&quot;, &quot;(wip)&quot;), &quot;&quot;),
  if(description.starts_with(&quot;todo&quot;), label(&quot;todo&quot;, &quot;(todo)&quot;), &quot;&quot;),
  if(description.starts_with(&quot;vibe&quot;), label(&quot;vibe&quot;, &quot;(vibe)&quot;), &quot;&quot;),
  if(description.starts_with(&quot;mega&quot;), label(&quot;mega&quot;, &quot;(mega)&quot;), &quot;&quot;),
  if(conflict, label(&quot;conflict&quot;, &quot;(conflict)&quot;), &quot;&quot;)
)
&apos;&apos;&apos;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[revset-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;closest_bookmark(to)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;heads(::to &amp;amp; bookmarks())&apos;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[colors]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;wip&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;yellow&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;todo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;blue&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;vibe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cyan&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;mega&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;red&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;p10k.zsh&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-zsh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;zsh&quot;&gt;&lt;span class=&quot;nb&quot;&gt;typeset&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;typeset&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_my_jj_workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;

prompt_my_jj&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;workspace

  &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; jj &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return
  if &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj workspace root 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/jj=show&quot;&lt;/span&gt;
    p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/vcs=hide&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else
    &lt;/span&gt;p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/jj=hide&quot;&lt;/span&gt;
    p10k display &lt;span class=&quot;s2&quot;&gt;&quot;*/vcs=show&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return
  fi&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# track current workspace for the async worker&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$_my_jj_workspace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;_my_jj_workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# request async job for the current workspace&lt;/span&gt;
  async_job _my_jj_worker _my_jj_async &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# note the single quotes, we want this to be interpreted each time&lt;/span&gt;
  p10k segment &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$_my_jj_display&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# this function is called by the async worker, and does the work&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# of calculating the jj status.&lt;/span&gt;
_my_jj_async&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;display revision bookmark distance

  &lt;span class=&quot;nv&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;prompt&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;bookmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;closest_bookmark(@)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;bookmarks.join(&quot; &quot;)&apos;&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; never &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;closest_bookmark(@)..@&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;change_id ++ &quot;\n&quot;&apos;&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;file_status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jj log &lt;span class=&quot;nt&quot;&gt;--repository&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$workspace&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ignore-working-copy&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--no-graph&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--color&lt;/span&gt; never &lt;span class=&quot;nt&quot;&gt;--revisions&lt;/span&gt; @ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;self.diff().files().map(|f| f.status()).join(&quot;\n&quot;)&apos;&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;
      /modified/ { parts[++i] = &quot;%F{cyan}±&quot; $1 &quot;%f&quot; }
      /added/ { parts[++i] = &quot;%F{green}+&quot; $1 &quot;%f&quot; }
      /removed/ { parts[++i] = &quot;%F{red}-&quot; $1 &quot;%f&quot; }
      /copied/ { parts[++i] = &quot;%F{yellow}⧉&quot; $1 &quot;%f&quot; }
      /renamed/ { parts[++i] = &quot;%F{magenta}↻&quot; $1 &quot;%f&quot; }
      END { for (j=1; j&amp;lt;=i; j++) printf &quot;%s%s&quot;, parts[j], (j&amp;lt;i ? &quot; &quot; : &quot;&quot;) }
    &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$revision&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bookmark&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bookmark&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$distance&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-gt&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
      &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; %7F⇡&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fi
  fi
  if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file_status&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;display+&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;file_status&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi

  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$display&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s/\x1b\[[0-9;]*m/%{&amp;amp;%}/g&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

_my_jj_callback&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;job_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$3&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;execution_time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$4&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$5&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;next_pending&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$6&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exit_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_my_jj_display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %F{red}&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stderr&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%f&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi
  &lt;/span&gt;p10k display &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# finally, initialize and register the worker and callbacks.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# this unregisters first so we can easily reload everything.&lt;/span&gt;
async_init
async_stop_worker _my_jj_worker 2&amp;gt;/dev/null
async_start_worker _my_jj_worker
async_unregister_callback _my_jj_worker 2&amp;gt;/dev/null
async_register_callback _my_jj_worker _my_jj_callback
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Related work&lt;/h2&gt;
&lt;p&gt;André Arko also used &lt;code&gt;zsh-async&lt;/code&gt; to &lt;a href=&quot;https://andre.arko.net/2025/06/20/a-jj-prompt-for-powerlevel10k/&quot;&gt;implement an async &lt;code&gt;jj&lt;/code&gt; prompt&lt;/a&gt;. I look forward to incorporating some of the different features he’s implemented, including distance from a remote tracked bookmark.&lt;/p&gt;
</content>
  </entry>
  
  
  
  <entry>
    <title>jj tips and tricks</title>
    <link href="https://zerowidth.com/2025/jj-tips-and-tricks/"/>
    <updated>2025-04-26T00:00:00-06:00</updated>
    
    <id>https://zerowidth.com/2025/jj-tips-and-tricks</id>
    
    
    <summary type="html">A collection of tips and tricks for the Jujutsu version control system.</summary>
    
    <content type="html">&lt;p&gt;Here are some of the tips, tricks, guidelines, and recommendations I’ve learned so far for using &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;Jujutsu&lt;/a&gt; well. For more background, see the previous post, &lt;a href=&quot;/2025/what-ive-learned-from-jj/&quot;&gt;what I’ve learned from jj&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m using “commit” and “change” interchangeably here. Remember that a &lt;code&gt;jj&lt;/code&gt; change is durable and evolves over time, while the underlying commit associated with a change might be different.&lt;/p&gt;
&lt;h2&gt;Workflows&lt;/h2&gt;
&lt;h3&gt;All new, all the time&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;jj new&lt;/code&gt; everywhere unless you really mean to change something in place. And even then, &lt;code&gt;jj new&lt;/code&gt; with a &lt;code&gt;jj squash&lt;/code&gt; is just as easy, and safer too because you get to decide if and when to squash those changes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; automatically abandons empty/undescribed changes, so if you move elsewhere in the repo any temporary &lt;code&gt;jj new&lt;/code&gt; change will be cleaned up behind you.&lt;/p&gt;
&lt;h3&gt;Common workflows&lt;/h3&gt;
&lt;p&gt;There are two common workflows for making new changes described by the &lt;code&gt;jj&lt;/code&gt; community, explained in &lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/real-world-workflows/intro.html&quot;&gt;Steve Klabnik’s tutorial&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Squash workflow
&lt;ol&gt;
&lt;li&gt;describe the work&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jj new&lt;/code&gt; to create a new empty change&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jj squash&lt;/code&gt; to push changes into the described change&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Edit workflow
&lt;ol&gt;
&lt;li&gt;Create a new change&lt;/li&gt;
&lt;li&gt;If the change needs to be split up, create a new change &lt;em&gt;before&lt;/em&gt; the current one, then work on that until it’s right, then continue on the main change&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Steve also mentions a “one change per PR” style in &lt;a href=&quot;https://github.com/jj-vcs/jj/discussions/2425#discussioncomment-9193689&quot;&gt;jj-vcs/jj#2425: Working branches and the JJ “way”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These two workflows are somewhat unfamiliar, but I’m curious to see how they might relate to an &lt;a href=&quot;https://understandlegacycode.com/blog/a-process-to-do-safe-changes-in-a-complex-codebase/&quot;&gt;in-repo mikado method&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Hunk-wise style&lt;/h3&gt;
&lt;p&gt;Meanwhile, I’m often using &lt;code&gt;jj&lt;/code&gt; like I used git: hack on some code, then decide where to commit the work. I’d make edits, then use &lt;code&gt;git add -p&lt;/code&gt; to incrementally stage the relevant subset for a new commit. This pattern works fine with &lt;code&gt;jj commit --interactive&lt;/code&gt;, with one exception: the default TUI for interactive staging doesn’t work well for me. I much prefer the hunk-wise &lt;code&gt;yes/no/edit/skip&lt;/code&gt; decisionmaking afforded by &lt;code&gt;git add -p&lt;/code&gt;. Fortunately, &lt;code&gt;@dubi steinkek&lt;/code&gt; in the jj discord implemented this, with a &lt;a href=&quot;https://github.com/zerowidth/dotfiles/pull/3&quot;&gt;later update from @amiryalsa&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[ui]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;diff-editor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;gitpatch&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[merge-tools.gitpatch]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;program&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sh&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;edit-args&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&apos;
  set -eu
  rm -f &quot;$right/JJ-INSTRUCTIONS&quot;
  git -C &quot;$left&quot; init -q
  git -C &quot;$left&quot; add -A
  git -C &quot;$left&quot; commit -q -m baseline --allow-empty
  mv &quot;$left/.git&quot; &quot;$right&quot;
  git -C &quot;$right&quot; add --intent-to-add --ignore-removal .
  git -C &quot;$right&quot; add -p
  git -C &quot;$right&quot; diff-index --quiet --cached HEAD &amp;amp;&amp;amp; { echo &quot;No changes done, aborting split.&quot;; exit 1; }
  git -C &quot;$right&quot; commit -q -m split
  git -C &quot;$right&quot; reset -q --hard
&apos;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This &lt;code&gt;add -p&lt;/code&gt; style also makes &lt;code&gt;jj split --interactive&lt;/code&gt; and &lt;code&gt;jj squash --interactive&lt;/code&gt; a lot easier. I tried to find a tool that would nicely handle 3-way merges with directories (not single files), and failed. This config gave me a familiar comfortable “handle” for the operations &lt;code&gt;jj&lt;/code&gt; allows.&lt;/p&gt;
&lt;h3&gt;Which one first?&lt;/h3&gt;
&lt;p&gt;Start by using &lt;code&gt;jj commit&lt;/code&gt;, or the equivalent, &lt;code&gt;jj describe &amp;amp;&amp;amp; jj new&lt;/code&gt;. Once you’re comfortable with the basics, try out &lt;code&gt;squash&lt;/code&gt; and &lt;code&gt;split&lt;/code&gt;, including their &lt;code&gt;--interactive&lt;/code&gt; versions. Don’t be afraid of things like rebase, either, given &lt;code&gt;jj undo&lt;/code&gt;’s safety net.&lt;/p&gt;
&lt;h2&gt;Git interoperability&lt;/h2&gt;
&lt;h3&gt;Colocated mode&lt;/h3&gt;
&lt;p&gt;I recommend using &lt;code&gt;jj&lt;/code&gt; in &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/working-copy/#ignored-files&quot;&gt;colocated mode&lt;/a&gt;, especially if you’re still dealing with a git upstream. Instead of &lt;code&gt;jj git clone&lt;/code&gt;, use &lt;code&gt;git clone &amp;amp;&amp;amp; jj git init --colocated&lt;/code&gt;, or &lt;code&gt;jj git init --colocated&lt;/code&gt; in an existing repository.&lt;/p&gt;
&lt;p&gt;Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This will let you continue to use &lt;code&gt;git&lt;/code&gt; commands if needed.&lt;/li&gt;
&lt;li&gt;Editors with &lt;code&gt;git&lt;/code&gt; integration will still be able to show you inline git annotations or use other features.&lt;/li&gt;
&lt;li&gt;It’s easy to try &lt;code&gt;jj&lt;/code&gt; without committing fully. You can remove the &lt;code&gt;.jj&lt;/code&gt; folder and go back to git any time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some downsides or complexities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt; sees your current &lt;code&gt;jj&lt;/code&gt; change as a “detached HEAD” state, which makes a shell prompt confusing or useless&lt;/li&gt;
&lt;li&gt;You can do &lt;code&gt;git status&lt;/code&gt; to see that (as far as git is concerned) there might be untracked changes in your repo. This doesn’t mean that &lt;code&gt;jj&lt;/code&gt; hasn’t captured them in the current change, though! You can confirm this with a &lt;code&gt;git diff&lt;/code&gt; and a &lt;code&gt;jj status&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The rougher edges of &lt;code&gt;git&lt;/code&gt; vs. &lt;code&gt;jj&lt;/code&gt; are more visible. You might try going all-in on jj-only instead, as an experiment!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember that if anything ever seems out of sync, especially when dealing with branches and bookmarks, you can resync &lt;code&gt;jj&lt;/code&gt; and the git repo with &lt;code&gt;jj git import&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For more, see &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/github/&quot;&gt;the docs about using jj with GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Bookmarks and branches&lt;/h3&gt;
&lt;p&gt;Bookmarks somewhat of a pain to use because they don’t auto-update like git branches. It’s nice that you don’t need named branches to do things with &lt;code&gt;jj&lt;/code&gt;, but once a git remote is involved, you need bookmarks, and you have to update them yourself. A common configuration sets up a &lt;code&gt;tug&lt;/code&gt; alias to help with this:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[revset-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;closest_bookmark(to)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;heads(::to &amp;amp; bookmarks())&apos;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;tug&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bookmark&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;move&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--from&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;closest_bookmark(@-)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--to&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;@-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Once you’re happy with a series of changes on top of a bookmark, &lt;code&gt;jj tug&lt;/code&gt; moves the nearest bookmark up to &lt;code&gt;@-&lt;/code&gt;. There are some alternatives in the &lt;a href=&quot;https://github.com/jj-vcs/jj/discussions/5568&quot;&gt;discussion post&lt;/a&gt; that make this more flexible or customizable.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/bookmarks/&quot;&gt;jj docs about bookmarks&lt;/a&gt; talks about how to work with bookmarks in much greater depth.&lt;/p&gt;
&lt;h3&gt;Immutable branches&lt;/h3&gt;
&lt;p&gt;GitHub pull requests don’t handle rebases and the resulting force-pushes well: review comments get lost or invalidated, so it’s harder to see what’s changing or what needs to be reviewed. For that reason, you may want to override &lt;code&gt;jj&lt;/code&gt;‘s configuration for what is considered an “immutable change” to include any branch that’s been pushed remotely.&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[revset-aliases]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# set all remote bookmarks (commits pushed to remote branches) to be immutable&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;immutable_heads()&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;builtin_immutable_heads() | remote_bookmarks()&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that if you do this, the default log revset will hide every commit but the latest that you’ve pushed for a bookmark. Using a log revset of something like &lt;code&gt;&apos;default()&apos; = &apos;coalesce(trunk(), root())::present(@) | ancestors(visible_heads(), 2)&apos;&lt;/code&gt; will give you similar results but won’t change what you see when you’ve pushed a bookmark remotely and made those commits immutable.&lt;/p&gt;
&lt;h2&gt;Miscellaneous&lt;/h2&gt;
&lt;h3&gt;Configs&lt;/h3&gt;
&lt;p&gt;To see how &lt;code&gt;jj&lt;/code&gt; is configured, you can look at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jj config list --include-defaults --include-overridden&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jj-vcs/jj/tree/main/cli/src/config&quot;&gt;&lt;code&gt;jj/cli/src/config/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the &lt;code&gt;jj&lt;/code&gt; default configuration is fine, I recommend one change in particular:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[git]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;push-new-bookmarks&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This means you don’t need to specify &lt;code&gt;--allow-new&lt;/code&gt; when pushing a new bookmark.&lt;/p&gt;
&lt;p&gt;The unique prefix for a changeset is highlighted by default, but you can hide the unnecessary bits to make the &lt;code&gt;jj&lt;/code&gt; log more concise:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[template-aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;format_short_change_id(id)&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;id.shortest()&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I figured out how to limit the &lt;code&gt;jj log&lt;/code&gt; output to only show recent bookmarks, for long-lived repos with a lot of branches still around:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[ui]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;default-command&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;log-recent&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;log-recent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;default() &amp;amp; recent()&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[revset-aliases]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# the default log revset defined internally:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# &apos;default()&apos; = &apos;present(@) | ancestors(immutable_heads().., 2) | present(trunk())&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# or a nicer default:&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;default()&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;coalesce(trunk(),root())::present(@) | ancestors(visible_heads() &amp;amp; recent(), 2)&apos;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;&apos;recent()&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;committer_date(after:&quot;1 month ago&quot;)&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Consider tuning the default log output to show what’s most relevant to you: it’s meant to be used for a quick “status” overview.&lt;/p&gt;
&lt;p&gt;And a variety of aliases that I’m trying out to reduce typing:&lt;/p&gt;
&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[aliases]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;commit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;ci&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;commit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--interactive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;init&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--colocate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;nb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bookmark&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;create&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-r @-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# &quot;new bookmark&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;pull&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fetch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;push&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;push&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--allow-new&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rebase&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;squash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;si&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;squash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--interactive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Operation log&lt;/h3&gt;
&lt;p&gt;Remember you have &lt;code&gt;jj op log&lt;/code&gt; to see the state of your repo over time. &lt;code&gt;jj undo&lt;/code&gt; to go back a step means you can try rebase without concern; &lt;code&gt;jj op restore&lt;/code&gt; (&lt;em&gt;not&lt;/em&gt; &lt;code&gt;jj restore&lt;/code&gt;!) to recover to an earlier state.&lt;/p&gt;
&lt;h3&gt;Rebases&lt;/h3&gt;
&lt;p&gt;To understand what rebase can do, &lt;code&gt;jj rebase --help&lt;/code&gt; explains well. You can do a lot, not just rebasing the current branch somewhere new.&lt;/p&gt;
&lt;h3&gt;Working clean&lt;/h3&gt;
&lt;p&gt;When using git, my working copies were often a mess, leaving a bunch of untracked files around. Most of the time that was intentional, except when I forget to &lt;code&gt;git add&lt;/code&gt; a new file I just created too. That’s no longer necessary, but &lt;code&gt;jj&lt;/code&gt; captures everything. To continue to ignore a file in your repository after switching to &lt;code&gt;jj&lt;/code&gt;, make sure it’s in &lt;code&gt;.gitignore&lt;/code&gt; or &lt;code&gt;.git/info/exclude&lt;/code&gt;, and then &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/working-copy/#ignored-files&quot;&gt;tell &lt;code&gt;jj&lt;/code&gt; to forget about the file&lt;/a&gt;: &lt;code&gt;jj file untrack &amp;lt;file&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Replacing &lt;code&gt;git stash&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Any time you’ve made edits to a new, undescribed change, it’s preserved in the repository history. I suggest that any time you’ve made some changes that you might want to keep (and that you’d have previously stored away with &lt;code&gt;git stash&lt;/code&gt;), you at least add a description: &lt;code&gt;jj describe -m &amp;quot;wip for...&amp;quot;&lt;/code&gt; so you have some context when you see it in the history later.&lt;/p&gt;
&lt;h2&gt;Resources and reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;Jujutsu Docs&lt;/a&gt;, the official docs&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/&quot;&gt;Steve’s Jujutsu Tutorial&lt;/a&gt;, the best tutorial&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://old.reddit.com/r/rust/comments/1jrm2uf/jujutsu_vcs_tutorial_that_doesnt_rely_on_git/&quot;&gt;r/rust: Jujutsu VCS tutorial that doesn’t rely on Git&lt;/a&gt;, a quick non-git-specific overview of basic jj concepts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://justinpombrio.net/src/jj-cheat-sheet.pdf&quot;&gt;jj cheat sheet&lt;/a&gt; is an excellent two-pager overview and reference&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v5.chriskrycho.com/essays/jj-init/&quot;&gt;jj init - What if we could actually replace git? Jujutsu might give us a real shot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ofcr.se/jujutsu-merge-workflow&quot;&gt;A Better Merge Workflow with Jujutsu&lt;/a&gt; shows some of the complicated multi-branch and rebase wrangling you can do with &lt;code&gt;jj&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://willhbr.net/2024/08/18/understanding-revsets-for-a-better-jj-log-output/&quot;&gt;Understanding revsets for a better jj log output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jj-vcs/jj/discussions/2425&quot;&gt;jj-vcs/jj#2425: Working branches and the JJ “way”&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jj-vcs/jj/discussions/5568&quot;&gt;jj-vcs/jj#5568: Find the closest bookmark in history to advance it forward&lt;/a&gt;, describes the &lt;code&gt;tug&lt;/code&gt; alias and suggests some other more extensible versions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jj-vcs/jj/discussions/5812&quot;&gt;jj-vcs/jj#5812: How do core Jujutsu developers configure Jujutsu?&lt;/a&gt; as an analog to &lt;a href=&quot;https://blog.gitbutler.com/how-git-core-devs-configure-git/&quot;&gt;How Core Git Developers Config Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;I haven’t tried any of the GUI/TUI tools yet, such as &lt;a href=&quot;https://www.visualjj.com&quot;&gt;VisualJJ for VSCode&lt;/a&gt;, &lt;a href=&quot;https://github.com/keanemind/jjk&quot;&gt;Jujutsu Kaizen for VSCode&lt;/a&gt;, or &lt;a href=&quot;https://github.com/tim-janik/jj-fzf&quot;&gt;jj-fzf&lt;/a&gt; for the terminal. I’m also planning to look at better integration with the &lt;a href=&quot;https://cli.github.com&quot;&gt;GitHub CLI&lt;/a&gt; for creating a bookmark and then opening a pull request related to it in a single command.&lt;/p&gt;
&lt;p&gt;Update: my favorite TUI so far is &lt;a href=&quot;https://github.com/idursun/jjui&quot;&gt;jjui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m not sure yet what a good &lt;a href=&quot;https://github.com/jj-vcs/jj/wiki/Shell-Prompt&quot;&gt;shell prompt&lt;/a&gt; with &lt;code&gt;jj&lt;/code&gt; looks like, but I’d like to try some things out, including integration with my &lt;a href=&quot;https://github.com/romkatv/powerlevel10k&quot;&gt;p10k zsh prompt&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2025-05-02 fixed immutable config, it’s &lt;code&gt;builtin_immutable_heads() | remote_bookmarks()&lt;/code&gt; not &lt;code&gt;&amp;amp;&lt;/code&gt;; added &lt;a href=&quot;https://justinpombrio.net/src/jj-cheat-sheet.pdf&quot;&gt;cheat sheet&lt;/a&gt; link&lt;/li&gt;
&lt;li&gt;2025-06-26
&lt;ul&gt;
&lt;li&gt;added missing &lt;code&gt;default()&lt;/code&gt; revset alias, including my current preferred version&lt;/li&gt;
&lt;li&gt;added note about how to still display pushed commits when making remote bookmarks immutable&lt;/li&gt;
&lt;li&gt;added &lt;code&gt;--include-overridden&lt;/code&gt; to the &lt;code&gt;config list&lt;/code&gt; command, it shows more useful context that way&lt;/li&gt;
&lt;li&gt;recommend &lt;code&gt;jjui&lt;/code&gt; as a TUI&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2025-07-07
&lt;ul&gt;
&lt;li&gt;fixed the &lt;code&gt;recent()&lt;/code&gt; revset usage in the &lt;code&gt;default()&lt;/code&gt; revset alias. I mistakenly only posted the version that doesn’t take an argument. You can use &lt;code&gt;&apos;recent(revset)&apos; = &apos;revset &amp;amp; recent()&apos;&lt;/code&gt; if you want a version that takes a revset as an argument.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2025-07-26
&lt;ul&gt;
&lt;li&gt;updated the hunk-wise &lt;code&gt;gitpatch&lt;/code&gt; merge tool based on an improvement from a PR to my dotfiles repo: &lt;a href=&quot;https://github.com/zerowidth/dotfiles/pull/3&quot;&gt;zerowidth/dotfiles#3&lt;/a&gt;. Thanks &lt;a href=&quot;https://github.com/amiryalsa&quot;&gt;@amiryalsa&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  
  
  <entry>
    <title>What I&apos;ve learned from jj</title>
    <link href="https://zerowidth.com/2025/what-ive-learned-from-jj/"/>
    <updated>2025-04-11T00:00:00-06:00</updated>
    
    <id>https://zerowidth.com/2025/what-ive-learned-from-jj</id>
    
    
    <summary type="html">How Jujutsu&apos;s flexibility and safety changed my approach to version control.</summary>
    
    <content type="html">&lt;p&gt;I recently started using the &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;Jujutsu&lt;/a&gt; version control system, and it’s changed how I think about working with code. As someone who’s been using &lt;a href=&quot;https://git-scm.com&quot;&gt;git&lt;/a&gt; for nearly two decades, it’s refreshing to gain new perspectives on my daily work and get a sense of what might be possible in the future.&lt;/p&gt;
&lt;p&gt;Working with git has been great, especially in contrast to what came before. But despite years of development, it still has sharp edges and presents a steep learning curve. Jujutsu doesn’t fix that, exactly, but it sands off some rough edges and makes some different decisions that result in a much safer and far more flexible workflow.&lt;/p&gt;
&lt;p&gt;See the next post for &lt;a href=&quot;/2025/jj-tips-and-tricks/&quot;&gt;jj tips and tricks&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Everything (is) changes&lt;/h2&gt;
&lt;p&gt;Where git operates solely on commits–snapshots of the codebase explicitly captured with &lt;code&gt;git commit&lt;/code&gt;–Jujutsu treats everything as a unique but flexible “change”. Jujutsu unifies several concepts from git: the working copy, commits of the files you’ve edited, the git index, and the stash. While &lt;code&gt;jj&lt;/code&gt; still uses git’s snapshot-based storage under the hood, it treats everything as a more generic “change”, each identified with a unique revision. The underlying git commit associated with a change may evolve over time but keeps the same revision identifier.&lt;/p&gt;
&lt;p&gt;“Change” is an easily-overloaded term. I’m using “change” here to talk about Jujutsu changes, “commit” for git commits, “edits” or “modifications” for alterations of the codebase, and “revision” to refer to &lt;code&gt;jj&lt;/code&gt;‘s identifier for a specific change. Hopefully that’s clear enough, but be aware that documentation and tutorials often use “change” and “commit” interchangeably–it usually doesn’t matter, but I’m keeping those separate here for precision.&lt;/p&gt;
&lt;p&gt;A change includes the current state of the files on disk. Any modification to your files, whether editing, moving, renaming, or deleting, is automatically&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; captured by &lt;code&gt;jj&lt;/code&gt;. You don’t need to explicitly tell &lt;code&gt;jj&lt;/code&gt; about what you’ve done in your working copy, it’s already tracked. This removes the need for an “index” or staging area, since everything is captured already as part of the current change.&lt;/p&gt;
&lt;p&gt;A change is associated with one or more parents, just like a git commit. The set of edits it captures may be empty. And, in fact, the default for a new change (&lt;code&gt;jj new&lt;/code&gt;) is &lt;code&gt;(empty) (no description)&lt;/code&gt;: basically a no-op, but tracked as a separate revision from its parent or parents.&lt;/p&gt;
&lt;p&gt;Instead of switching branches as you work, you “edit a revision”. When you switch to a different or new change in your repository, that also updates the files on disk to match. This won’t accidentally overwrite anything, because whatever was there would already have been captured in whatever change you were just on. That means switching around to different changes in the repository is safe, and you won’t lose any modifications you’ve made, no matter where you are in the repository’s history.&lt;/p&gt;
&lt;p&gt;Instead of just editing an existing change, though, the recommended pattern is to always use &lt;code&gt;jj new &lt;/code&gt;: this starts a new, empty change on top of whatever revision you wanted to look at or modify. Because the new change starts out empty, you see the files as they were the parent change, which is what you intended. It’s safer, though: any edits you make will be associated with the new change you’re on, rather than updating the preexisting one. Didn’t want to keep those edits you just made? It’s as easy as &lt;code&gt;jj abandon&lt;/code&gt;: the current change goes away and you’re back with a fresh new one. And if you do want to keep them, you can, using &lt;code&gt;jj new&lt;/code&gt; to begin a new empty change after the change you were just working on.&lt;/p&gt;
&lt;p&gt;This pattern, making a new change on an existing revision that you want to modify also covers the “git stash” pattern naturally. It’s trivial to start a new, different change with &lt;code&gt;jj new&lt;/code&gt;: it leaves whatever you were working on right where it was. You can go back to it later if you want, or abandon it if you don’t. These “work in progress” changes have more context than &lt;code&gt;git stash&lt;/code&gt;, too: instead of just a &lt;code&gt;stash{3}: WIP on some-branch&lt;/code&gt;, they exist in the change history alongside everything else.&lt;/p&gt;
&lt;p&gt;While you might think creating new empty changes with &lt;code&gt;jj new&lt;/code&gt; all the time would leave your repository history littered with empty changes, &lt;code&gt;jj&lt;/code&gt; tries to clean up after itself. If you move away from an empty change to somewhere else–&lt;code&gt;jj new&lt;/code&gt;, &lt;code&gt;jj edit&lt;/code&gt;, or whatever–it will automatically discard empty changes that you leave behind. Non-empty changes are preserved.&lt;/p&gt;
&lt;h2&gt;Commits and intentionality&lt;/h2&gt;
&lt;p&gt;So far I’ve talked about edits and making new changes. What about constructing a commit history, like you’d expect with the normal edit/commit git workflow loop?&lt;/p&gt;
&lt;p&gt;A new change has no “description”: it’s like a commit without a commit message. If you’ve made some edits and want to save that work, you can use &lt;code&gt;jj describe&lt;/code&gt; to set a description for the current change. Follow it up with a &lt;code&gt;jj new&lt;/code&gt; to continue, starting a new change on the one you just described. &lt;code&gt;jj commit&lt;/code&gt; is shorthand for this, it’s essentially &lt;code&gt;jj describe &amp;amp;&amp;amp; jj new&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A description on a change is optional, but &lt;code&gt;jj&lt;/code&gt; won’t let you push un-described commits to a remote repository. Describing a change is a way of saying “I’m done with this change, here’s what’s in it” before moving on.&lt;/p&gt;
&lt;p&gt;Along with describing and making new changes, &lt;code&gt;jj squash&lt;/code&gt; allows you to take some or all of the current change and “squash” it into another revision. This is usually the immediate parent, but can be any revision. Being able to pick and choose where modifications go makes it easier to be intentional about where something belongs. Say I had a change history &lt;code&gt;q -&amp;gt; r -&amp;gt; s&lt;/code&gt;, was working on &lt;code&gt;s&lt;/code&gt;, and realized I had something that belongs in &lt;code&gt;q&lt;/code&gt;. With &lt;code&gt;jj&lt;/code&gt;, it’s as easy as &lt;code&gt;jj squash --into q&lt;/code&gt; to push the current changes into &lt;code&gt;q&lt;/code&gt;. With git, I’d have to make a fixup commit and then do an interactive rebase to get things into the right place. In practice, I rarely bothered, because of the friction–having to do an interactive rebase at all–and the risk, having to be mindful of what uncommitted changes might be overwritten or lost in my working copy. With &lt;code&gt;jj squash&lt;/code&gt;, the current change is pushed into whatever target revision you want. And if that change has children, they’ll all be automatically rebased to incorporate the updated code, no additional work is needed.&lt;/p&gt;
&lt;p&gt;It’s also easy to split a change in two with &lt;code&gt;jj split&lt;/code&gt;. Should the edits you’ve made belong in separate changes–maybe even for squashing to new places after–it’s now easy to do. It’s also possible with &lt;code&gt;git&lt;/code&gt;, but as with other operations, it’s more complicated and difficult.&lt;/p&gt;
&lt;p&gt;Rebases with &lt;code&gt;jj&lt;/code&gt; are the same as git, conceptually, but both simpler and more direct. If I had &lt;code&gt;s -&amp;gt; t -&amp;gt; u -&amp;gt; v&lt;/code&gt; and wanted to reorder them, it’s as easy as &lt;code&gt;jj rebase --revision u --after s&lt;/code&gt;, and I’d end up with &lt;code&gt;s -&amp;gt; u -&amp;gt; t -&amp;gt; v&lt;/code&gt;, no interactive reordering needed. &lt;code&gt;jj rebase&lt;/code&gt; can do far, far more, but the main thing is that it’s easy to do.&lt;/p&gt;
&lt;p&gt;These small but important differences from the usual git workflow have meant that I’m more mindful about where any particular code changes “belongs”. It’s easy to put things in the right place, and to build a commit or change series that’s a logical and well-encapsulated  series of discrete steps. I’m a lot less likely now to make a series of commits and then throw a few “whoops” and “fix that thing I missed” commits at the end, because I can effortlessly split, squash, update, and rearrange the history.&lt;/p&gt;
&lt;h2&gt;Flexibility and safety&lt;/h2&gt;
&lt;p&gt;Along with capturing every edit into a change, &lt;code&gt;jj&lt;/code&gt; stores two extra sequences of metadata. The operation log records the state of the repository as it changes, and the &lt;code&gt;evolog&lt;/code&gt; (“evolution log”) tracks an individual revision as it changes.&lt;/p&gt;
&lt;p&gt;Think of the operation log like the git reflog, which tracks the current &lt;code&gt;HEAD&lt;/code&gt; as you commit, switch branches, rebase, and so forth. Going back to the previous state is as easy as &lt;code&gt;jj undo&lt;/code&gt;, or &lt;code&gt;jj op restore&lt;/code&gt; for an earlier state. Whether what I’ve done is as simple as a &lt;code&gt;jj new&lt;/code&gt; or as complex as a multi-headed &lt;code&gt;jj rebase&lt;/code&gt;, going back to the prior state is that easy. It’s far more powerful and useful than the git reflog because of how easy it is to restore the entire repository back to a previous state.&lt;/p&gt;
&lt;p&gt;The operation log has given me confidence to try all kinds of things. Rebasing revisions to different places, temporarily creating multi-parent merges to do work then unwinding them later once I’ve shipped some PRs, or just experimenting with various commands–it’s all risk-free because I can trivially restore my repository and state back to the way it was. Having the backstop of the operation log means &lt;code&gt;jj rebase&lt;/code&gt; is far less scary: if I’m not sure something will work, I can just try it! If it works, great, and if not? &lt;code&gt;jj undo&lt;/code&gt; and I can try something else.&lt;/p&gt;
&lt;p&gt;As I explained earlier, I recommend using &lt;code&gt;jj new&lt;/code&gt; before making edits anywhere. If you forget to do that, any edits you make end up in the current change with whatever was there already. If you didn’t mean to, it would usually be impossible to tease that change back apart to make things “right”, or even to revert back to what the change was before.&lt;/p&gt;
&lt;p&gt;Fortunately, the &lt;code&gt;jj&lt;/code&gt; &lt;code&gt;evolog&lt;/code&gt;, or “evolution log”, tracks the history of a single change over time, like a mini git history just within a single change. Using the evolog, it’s possible to recover the previous version of a change, apply that diff to a new change, and restore the old state: taking what was two separate modifications jammed into one and teasing them apart back into separate changes. Before &lt;code&gt;jj&lt;/code&gt;, I would have had to rely on, at best, the undo history in my editor, or would have simply given up on recovering that part of the code history. This feature is perhaps less useful for day to day work, but I was glad to learn it was there and to be able to successfully try it out. For an example of how to do this, see &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/FAQ/#i-accidentally-changed-files-in-the-wrong-commit-how-do-i-move-the-recent-changes-into-another-commit&quot;&gt;the jj documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conflict resolution&lt;/h2&gt;
&lt;p&gt;One of the consequences of being able to modify changes in-place is that all subsequent changes need to be rebased to account for the updated parent. If there were a sequence &lt;code&gt;s -&amp;gt; t -&amp;gt; u -&amp;gt; v&lt;/code&gt; and you’d modified &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;jj&lt;/code&gt; will automatically rebase the rest: &lt;code&gt;s -&amp;gt; t&apos; -&amp;gt; u&apos; -&amp;gt; v&apos;&lt;/code&gt;. This includes conflicts, if any arise. The difference from &lt;code&gt;git&lt;/code&gt; is that conflicts are not a stop-the-world event! You’ll see in the &lt;code&gt;jj log&lt;/code&gt; output that changes have a conflict, but it won’t prevent a command (like an explicit or implicit rebase) from running to completion. You get to choose when and how to resolve the conflicts afterward. I found this a surprising benefit: rebases are already less stressful because of how easy &lt;code&gt;undo&lt;/code&gt; is, but now I’m no longer interrupted and forced to resolve conflicts immediately.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/conflicts/#conflict-markers&quot;&gt;conflict markers&lt;/a&gt; take some getting used to. But once you’ve resolved a conflict, any child changes will be automatically rebased again. That often clears the conflict for the rest of the branch.&lt;/p&gt;
&lt;h2&gt;Interop&lt;/h2&gt;
&lt;p&gt;Jujutsu doesn’t bother with naming branches for normal operations, nor does it have a “current branch”, but named branches do still exist as “bookmarks”. Like git branches, bookmarks are labels pointing at a change, but unlike git, they don’t move around as you make new changes. Changes in Jujutsu are treated as far more mutable than commits in git, so “move this bookmark to this new change” is a more intentional act. Managing bookmarks is a bit of a hassle still, and has more friction out of the box than I’d prefer. There are some common configs that help, though, which I documented in a &lt;a href=&quot;/2025/jj-tips-and-tricks/#bookmarks-and-branches&quot;&gt;followup post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; encourages a rebase-friendly workflow, given how easy it is to rebase and rearrange things at any time. This is somewhat incompatible with forges&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; like GitHub, where force pushes invalidate review comments in pull requests. So there’s somewhat of a mismatch between &lt;code&gt;jj&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt; models, but it’s not insurmountable. For repositories where I’m collaborating with others, I’m trying a new approach, updating my &lt;code&gt;jj&lt;/code&gt; config to consider any commit that’s been pushed to a remote as “immutable”. If I were to try and modify or rebase one of these commits, I’d get an error message. It’s a more restrictive pattern than &lt;code&gt;jj&lt;/code&gt; allows for, but I’m going to stick with this for now to reduce confusion in pull requests.&lt;/p&gt;
&lt;p&gt;Meanwhile, any change that hasn’t been pushed to a branch yet is fair game! I rebase, split, and squash readily and often.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;jj&lt;/code&gt; in colocated mode, alongside a &lt;code&gt;git&lt;/code&gt; repo, means all the git commands still work. &lt;code&gt;jj git init --colocate&lt;/code&gt; creates a new &lt;code&gt;.jj&lt;/code&gt; directory, and you can now use &lt;code&gt;jj&lt;/code&gt; commands. It’s an easy way to experiment with &lt;code&gt;jj&lt;/code&gt; without committing fully: try it out, and if it doesn’t feel right or help you like you’d hoped, you can &lt;code&gt;rm -r .jj&lt;/code&gt; and go back to git. One interesting note: while &lt;code&gt;jj&lt;/code&gt; captures every file in your working copy (i.e. no “dirty” or “untracked” files), &lt;code&gt;git status&lt;/code&gt; will still show things the “old” way. If you edit a file, &lt;code&gt;git status&lt;/code&gt; will show it as modified but not committed, while &lt;code&gt;jj status&lt;/code&gt; will show that it’s modified &lt;em&gt;and&lt;/em&gt; that it’s been stored as part of the current change.&lt;/p&gt;
&lt;p&gt;Each change is associated with a git commit, or rather, a git commit has a change ID associated with it. When a change is modified, the associated git commit is replaced with a new one with the change’s updated contents. This is irrelevant until you’ve pushed those commits to a remote server. If you edit a change (thereby rewriting the git history), you’ll have to force push to update the remote.&lt;/p&gt;
&lt;h2&gt;“Units of change” and collaboration&lt;/h2&gt;
&lt;p&gt;The flexibility afforded by &lt;code&gt;jj&lt;/code&gt; is philosophically different from the &lt;a href=&quot;https://zachholman.com/posts/git-commit-history/&quot;&gt;commit history doesn’t matter&lt;/a&gt; approach to git branches. The idea, particularly as realized in the &lt;a href=&quot;https://docs.github.com/en/get-started/using-github/github-flow&quot;&gt;GitHub pull request workflow&lt;/a&gt;, is that the real “unit of change” is a pull request, and the individual commits making up a PR are essentially irrelevant.&lt;/p&gt;
&lt;p&gt;I’ve been comfortable with that approach for a long time. But now, with the ease at which I can “put the right things in the right place”, I’ve found myself caring more about individual commits: discrete, standalone units of work that don’t necessarily require the broader context of a pull request. I haven’t gone all the way to “one commit per PR” territory, but each commit feels more important to get right because it’s easier to do it that way.&lt;/p&gt;
&lt;p&gt;My day-to-day work hasn’t changed much. I’m still using the normal vanilla PR-focused, merge-only GitHub flow. I use &lt;code&gt;jj&lt;/code&gt; locally, opening PRs with as clean a history as I can to start. Because of the limitations when force pushes and rebases are involved, I still fall back to “fixup” style commits toward the tail end of a review cycle on a PR.&lt;/p&gt;
&lt;p&gt;What’s different is that I can see new paths and possibilities. I’m curious about things like &lt;a href=&quot;https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2&quot;&gt;interdiff code review&lt;/a&gt; and &lt;a href=&quot;https://benjamincongdon.me/blog/2022/07/17/In-Praise-of-Stacked-PRs/&quot;&gt;stacked PRs&lt;/a&gt;. I’m eagerly watching developments like per-commit code review in tools like &lt;a href=&quot;https://blog.gitbutler.com/gitbutlers-new-patch-based-code-review/&quot;&gt;GitButler&lt;/a&gt; and efforts to add &lt;code&gt;Change-ID&lt;/code&gt; as a supported header in &lt;a href=&quot;https://lore.kernel.org/git/CAESOdVBBeQDtRmRSQeHomuxQubTP5ggKZWGG88n88qKYBHR=+w@mail.gmail.com/T/#t&quot;&gt;git itself&lt;/a&gt; to enable durable change tracking on top of commits. It feels like the pull request as it exists today is fundamentally limited, and I can’t wait to see what’s over the horizon for collaborative tools based on a change-focused iterative model.&lt;/p&gt;
&lt;p&gt;Unfortunately, all of the local advantages of &lt;code&gt;jj&lt;/code&gt; don’t translate to an upstream repository or collaboration tools. Until there’s more direct integration or a collaboration/code review system that supports &lt;code&gt;jj&lt;/code&gt; natively, I’ll have to live with the mismatch in capabilities.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;jj&lt;/code&gt; feels like using &lt;code&gt;git-svn&lt;/code&gt; when the rest of the team is still using &lt;code&gt;svn&lt;/code&gt;. While I still use git and GitHub for collaboration, I get to use a flexible and fun tool locally. I can try new things with confidence, safe in the knowledge that &lt;code&gt;jj&lt;/code&gt; has my back for putting things right if anything goes wrong.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;If you’d like to try &lt;code&gt;jj&lt;/code&gt;, I recommend &lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/&quot;&gt;Steve’s Jujutsu Tutorial&lt;/a&gt; as the best way to get started. The &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;official docs&lt;/a&gt; have all the details including installation instructions.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; hides or improves upon many of the difficult things about day-to-day work with git, but it still really helps if you’re familiar with how git works. If you’re not, I highly recommend Julia Evans’ &lt;a href=&quot;https://wizardzines.com/zines/git/&quot;&gt;How Git Works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I learned &lt;code&gt;jj&lt;/code&gt; as a system building on top of git rather than as a novel standalone utility. This worked well for me because of my experience with git, so I can’t say if it would have an easier learning curve for someone new to version control systems. The underlying git storage abstractions can leak, especially when it comes to working with a remote git repository, so it really helps to know what’s going on.&lt;/p&gt;
&lt;p&gt;As a followup to this post, I’ve written down some of the &lt;a href=&quot;/2025/jj-tips-and-tricks/&quot;&gt;tips and tricks I’ve learned for using &lt;code&gt;jj&lt;/code&gt; effectively&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2025-05-02 &lt;a href=&quot;https://news.ycombinator.com/item?id=43867403&quot;&gt;hackernews discussion&lt;/a&gt;, &lt;a href=&quot;https://lobste.rs/s/ljtwfv/what_i_ve_learned_from_jj&quot;&gt;lobste.rs discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fn1&quot;&gt;
&lt;p&gt;Nearly automatically: snapshots are taken when you run a &lt;code&gt;jj&lt;/code&gt; command. If you’re in the habit of &lt;code&gt;git status&lt;/code&gt; or &lt;code&gt;git diff&lt;/code&gt; every now and again, &lt;code&gt;jj status&lt;/code&gt; will do the same thing with that extra side effect. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot;&gt;
&lt;p&gt;Presumably named after SourceForge, this means any hosting, collaboration, or code review service like GitHub, GitLab, gerritt, etc. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
</feed>
