<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - devlog</title>
    <link>https://emresahin.net/series/devlog/</link>
    <description>The devlog series</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 15 Sep 2026 19:46:32 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/series/devlog/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>devlog 32</title>
      <published>2025-07-12T16:10:36+00:00</published>
      <updated>2025-07-12T16:10:36+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 12 Jul 2025 16:10:36 +0000</pubDate>
      <link>https://emresahin.net/devlog-32/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-32/</guid>
      <description>This dialog is about converting ColQwen2 to the ONNX format. 🐢 Now, I have two classes. This one is a copy of the ONNX Patcher that’s used to convert ColQwen2 to ONNX format. But this one is valid only for the underlying model, that is Qwen2VLForConditionalGeneration , and ColQwen2 also has some ...</description>
      <category>devlog</category>
      <category>ONNX</category>
      <category>conversions</category>
      <category>ColQwen2</category>
      <category>machine-learning</category>
      <category>python</category>
      <category>torch</category>
      <content:encoded><![CDATA[<p><em>This dialog is about converting ColQwen2 to the ONNX format.</em></p>
<p>🐢 Now, I have two classes. This one is a copy of the ONNX Patcher that’s used to convert ColQwen2 to ONNX format. But this one is valid only for the underlying model, that is <code>Qwen2VLForConditionalGeneration</code>, and <code>ColQwen2</code> also has some modifications to call it.</p>
<p>🐇 What are these modifications?</p>
<p>🐢 The <code>forward</code> method looks something like this:</p>
<h2 id="colqwen2">ColQwen2</h2>
<pre><code class="language-python">    def forward(self, *args, **kwargs) -&gt; torch.Tensor:
        kwargs.pop("output_hidden_states", None)

        # Handle the custom "pixel_values" input obtained with `ColQwen2Processor` through unpadding
        if "pixel_values" in kwargs:
            offsets = kwargs["image_grid_thw"][:, 1] * kwargs["image_grid_thw"][:, 2]  # (batch_size,)
            kwargs["pixel_values"] = torch.cat(
                [pixel_sequence[:offset] for pixel_sequence, offset in zip(kwargs["pixel_values"], offsets)],
                dim=0,
            )

        position_ids, rope_deltas = self.get_rope_index(
            input_ids=kwargs["input_ids"],
            image_grid_thw=kwargs.get("image_grid_thw", None),
            video_grid_thw=None,
            attention_mask=kwargs.get("attention_mask", None),
        )
        last_hidden_states = self.inner_forward(
            *args, **kwargs, position_ids=position_ids, use_cache=False, output_hidden_states=True
        )  # (batch_size, sequence_length, hidden_size)

        proj = self.custom_text_proj(last_hidden_states)  # (batch_size, sequence_length, dim)

        # L2 normalization
        proj = proj / proj.norm(dim=-1, keepdim=True)  # (batch_size, sequence_length, dim)
        proj = proj * kwargs["attention_mask"].unsqueeze(-1)  # (batch_size, sequence_length, dim)

        if "pixel_values" in kwargs and self.mask_non_image_embeddings:
            # Pools only the image embeddings
            image_mask = (kwargs["input_ids"] == self.config.image_token_id).unsqueeze(-1)
            proj = proj * image_mask
        return proj
</code></pre>
<p>🐇 How about the <code>inner_forward</code> method? Does it just directly call <code>Qwen2VLForConditionalGeneration</code>’s <code>forward</code>?</p>
<p>🐢 No, it has some conditionals:</p>
<pre><code class="language-python">        if inputs_embeds is None:
            inputs_embeds = self.model.embed_tokens(input_ids)
            if pixel_values is not None:
                pixel_values = pixel_values.type(self.visual.get_dtype())
                image_embeds = self.visual(pixel_values, grid_thw=image_grid_thw)
                image_mask = (input_ids == self.config.image_token_id).unsqueeze(-1).expand_as(inputs_embeds)
                image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
                inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)

            if pixel_values_videos is not None:
                pixel_values_videos = pixel_values_videos.type(self.visual.get_dtype())
                video_embeds = self.visual(pixel_values_videos, grid_thw=video_grid_thw)
                video_mask = (input_ids == self.config.video_token_id).unsqueeze(-1).expand_as(inputs_embeds)
                video_embeds = video_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
                inputs_embeds = inputs_embeds.masked_scatter(video_mask, video_embeds)

            if attention_mask is not None:
                attention_mask = attention_mask.to(inputs_embeds.device)

        outputs = self.model(
            input_ids=None,
            position_ids=position_ids,
            attention_mask=attention_mask,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
        )

        hidden_states = outputs[0]
        return hidden_states
</code></pre>
<p>🦊 The patcher only modifies the <code>past_key_values_args</code> and moves them to a <code>DynamicCache</code>. There are no other changes for patching.</p>
<p>🐢 But the return types are also different. <code>Qwen2VLForConditionalGeneration.forward</code> returns a <code>Union[Tuple, Qwen2VLCausalLMOutputWithPast]</code>, but <code>ColQwen2.forward</code> returns a <code>torch.Tensor</code>.</p>
<p>🐇 How is that <code>torch.Tensor</code> calculated from the return type of <code>Qwen2VLForConditionalGeneration</code>? ❓</p>
<p>🦊 There are multiple return types in <code>Qwen2VLForConditionalGeneration</code>. It’s determined by the <code>return_dict</code> argument.</p>
<pre><code class="language-python">
        if not return_dict:
            output = (logits,) + outputs[1:]
            return (loss,) + output if loss is not None else output

        return Qwen2VLCausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            past_key_values=outputs.past_key_values,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
            rope_deltas=self.rope_deltas,
        )
</code></pre>
<p>🐇 What’s that argument in <code>ColQwen2</code>?</p>
<p>🐢 It’s passed from the caller; it’s a <code>kwarg</code>.</p>
<p>🐇 Is there any modification to this in the ONNX patcher?</p>
<p>🐢 Nope.</p>
<p>🐇 Then we can consider it as the default. What’s the default?</p>
<p>🐢 The default is <code>None</code>. Hence it returns <code>(logits,) + outputs[1:]</code>.</p>
<p>🐇 Then, <code>logits</code> are the first parameter by default.</p>
<p>🐢 Yes, we can assume this.</p>
<p>🐇 What does <code>ColQwen2</code> do with these logits?</p>
<p>🦊 By the way, it calls <code>inner_forward</code> with <code>use_cache=False</code> and <code>output_hidden_states=True</code>. What do these change in <code>Qwen2VLForConditionalGeneration</code>?</p>
<p>🐢 These hidden states are actually output. In <code>ColQwen2.forward</code>, the <code>inner_forward</code> call is actually:</p>
<pre><code class="language-python">        last_hidden_states = self.inner_forward(
            *args, **kwargs, position_ids=position_ids, use_cache=False, output_hidden_states=True
        )  # (batch_size, sequence_length, hidden_size)
</code></pre>
<p>and the <code>output</code> is the hidden states.</p>
<p>🦊 It then runs:</p>
<pre><code class="language-py">        proj = self.custom_text_proj(last_hidden_states)  # (batch_size, sequence_length, dim)
</code></pre>
<p>and calculates a set of projections.</p>
<p>🐢 <code>custom_set_proj</code> is defined as:</p>
<pre><code class="language-py">        self.custom_text_proj = nn.Linear(self.model.config.hidden_size, self.dim)
</code></pre>
<p>hence these projections are fully connected layer calculations.</p>
<p>🦊 It returns these after L2 normalization and checks if there are <code>pixel_values</code> to consider.</p>
<p>🐢 In this case, the output from <code>ColQwen2</code> is this single tensor.</p>
<p>🐇 Ok. Then it’s actually simpler than wrapping up the whole <code>Qwen2VLForConditionalGeneration</code>.</p>
<p>🐢 It looks so, yes. But the example for <code>ColQwen2</code> also has a post-processing step:</p>
<pre><code class="language-py">scores = processor.score_multi_vector(query_embeddings, image_embeddings)
</code></pre>
<p>🐇 I think we can leave that post-processing for the time being. We only need multi-vector embeddings for FastEmbed.</p>
<p>🐢 Maybe it’s convertible to a <code>forward</code> method that we can use with ONNX.</p>
<p>🐇 Let’s see how this scoring works, then.</p>
<p>🐢 <a href="https://github.com/illuin-tech/colpali/blob/main/colpali_engine/utils/processing_utils.py#L68"><code>score_multi_vector</code></a> receives two tensors or tensor lists and compares query vectors with passage vectors. It’s a rather straightforward implementation that requires Torch, but not Transformers.</p>
<p>🐇 In theory, we can also convert this to an ONNX model.</p>
<p>🦊 We can also write a custom model for this.</p>
<p>🐢 It has two <code>for</code> loops for comparisons. Can we convert all of these?</p>
<p>🦊 The loop indices can be seen as <code>dynamic_axes</code>, and it’s possible to convert the whole thing as a Torch model, then use <code>torch.onnx.export</code> just as we do for the model itself.</p>
<p>🐢 I see, but I don’t think that’s what we must do now.</p>
<p>🐇 Yes, let’s skip that for the time being and convert the model itself. We’ll have multivectors for patches and queries at the end.</p>
<p>🐢 So, we’ll keep the processing part, send <code>BatchFeature</code> objects that are output from the processor, and send this to two models: one for images and one for text.</p>
<p>🐇 Yep, that’s the plan. In the end, we’ll have two ONNX models that require <code>BatchFeature</code>s.</p>
<p>🐢 Then, we’ll modify <code>past_key_values</code> in this argument to use <code>DynamicCache</code>.</p>
<p>🐇 Yes, that’s alright. We can start by moving <code>past_key_values_converter</code> to a method in <code>PatchedColQwen2</code>.</p>
<p>🐢 <code>past_key_values</code> wasn’t used much, so I completely removed it. I also began to use processor outputs as dummy input in the exporter. However, when using <code>BatchFeature</code>, we get:</p>
<blockquote>
<p>RuntimeError: Only tuples, lists and Variables are supported as JIT inputs/outputs. Dictionaries and strings are also accepted, but their usage is not recommended. Here, received an input of unsupported type: BatchFeature</p>
</blockquote>
<p>🐇 So, we can try Dynamo first, I think. If that doesn’t work, we can just collect items as tensors and build up a <code>BatchFeature</code> inside the patcher.</p>
<p>🐢 Let’s try <code>dynamo=True</code> for this first.</p>
<p>🦊 This time, it’s about <code>BatchFeature</code> again, but the error is different: <code>KeyError: 'Indexing with integers is not available when using Python based feature extractors'</code></p>
<p>🐇 In this case, we can just create <code>BatchFeature</code> inside the patcher.</p>
<h2 id="patchedcolqwen2">PatchedColQwen2</h2>
<pre><code class="language-python">
class PatchedColQwen2(ColQwen2):
    def forward(self, *args):
        (
            input_ids,
            inputs_embeds,
            attention_mask,
            position_ids,
            *past_key_values_args,
        ) = args
        # Convert past_key_values list to DynamicCache
        if len(past_key_values_args) == 0:
            past_key_values = None
        else:
            past_key_values = DynamicCache()
            for i in range(self.config.num_hidden_layers):
                key = past_key_values_args.pop(0)
                value = past_key_values_args.pop(0)
                past_key_values.update(key_states=key, value_states=value, layer_idx=i)

        breakpoint()
        o = super().forward(
            input_ids=input_ids,
            inputs_embeds=inputs_embeds,
            attention_mask=attention_mask,
            # position_ids=position_ids,
            past_key_values=past_key_values,
        )

        flattened_past_key_values_outputs = {
            "logits": o.logits,
        }
        output_past_key_values: DynamicCache = o.past_key_values
        for i, (key, value) in enumerate(
            zip(output_past_key_values.key_cache, output_past_key_values.value_cache)
        ):
            flattened_past_key_values_outputs[f"present.{i}.key"] = key
            flattened_past_key_values_outputs[f"present.{i}.value"] = value

        return flattened_past_key_values_outputs

</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog 31</title>
      <published>2025-06-07T10:29:03+00:00</published>
      <updated>2025-06-07T10:29:03+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 07 Jun 2025 10:29:03 +0000</pubDate>
      <link>https://emresahin.net/devlog-31/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-31/</guid>
      <description>🐢 I think sending Alt keys in Ghostty needs some love. Let’s start with it as an easy task for the day. 🐇 While checking macos-option-as-alt as a potential source of the problem, I spent time adjusting the Ghostty icon . I changed the app icon. This is how I spend my time being extremely producti...</description>
      <category>shell</category>
      <category>tmux</category>
      <category>ghostty</category>
      <category>productivity</category>
      <category>macos</category>
      <category>terminal</category>
      <content:encoded><![CDATA[<p>🐢 I think sending Alt keys in Ghostty needs some love. Let’s start with it as an easy task for the day.</p>
<p>🐇 While checking <a href="https://ghostty.org/docs/config/reference#macos-option-as-alt"><code>macos-option-as-alt</code></a> as a potential source of the problem, I spent time <a href="https://ghostty.org/docs/config/reference#macos-icon">adjusting the Ghostty icon</a>. I changed the app icon. This is how I spend my time being extremely productive.</p>
<p>🐢 Weird. The <code>Option as alt</code> key setting is fine.</p>
<p>🐇 Actually, the Alt key works; the reason we cannot make panes larger is that <code>Alt-Right</code> and <code>Alt-Left</code> are not bound to this.</p>
<p>🐢 You may be right, yes. Then, what’s the key to enlarge a pane?</p>
<p>🦊 List all the keys. Check the keys. You should have done this already.</p>
<pre><code class="language-shell">$ tmux list-keys | rg resize

bind-key    -T prefix       &gt;                      display-menu -T "#[align=centre]#{pane_index} (#{pane_id})" -x P -y P "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}" &lt; { send-keys -X history-top } "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}" &gt; { send-keys -X history-bottom } '' "#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}" C-r { if-shell -F "#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}" "copy-mode -t=" ; send-keys -X -t = search-backward "#{q:mouse_word}" } "#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}" C-y { copy-mode -q ; send-keys -l "#{q:mouse_word}" } "#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}" c { copy-mode -q ; set-buffer "#{q:mouse_word}" } "#{?mouse_line,Copy Line,}" l { copy-mode -q ; set-buffer "#{q:mouse_line}" } '' "#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}" C-h { copy-mode -q ; send-keys -l "#{q:mouse_hyperlink}" } "#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}" h { copy-mode -q ; set-buffer "#{q:mouse_hyperlink}" } '' "Horizontal Split" h { split-window -h } "Vertical Split" v { split-window -v } '' "#{?#{&gt;:#{window_panes},1},,-}Swap Up" u { swap-pane -U } "#{?#{&gt;:#{window_panes},1},,-}Swap Down" d { swap-pane -D } "#{?pane_marked_set,,-}Swap Marked" s { swap-pane } '' Kill X { kill-pane } Respawn R { respawn-pane -k } "#{?pane_marked,Unmark,Mark}" m { select-pane -m } "#{?#{&gt;:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}" z { resize-pane -Z }
bind-key    -T prefix       z                      resize-pane -Z
bind-key -r -T prefix       M-Up                   resize-pane -U 5
bind-key -r -T prefix       M-Down                 resize-pane -D 5
bind-key -r -T prefix       M-Left                 resize-pane -L 5
bind-key -r -T prefix       C-Up                   resize-pane -U
bind-key -r -T prefix       C-Down                 resize-pane -D
bind-key -r -T prefix       C-Left                 resize-pane -L
bind-key -r -T prefix       C-Right                resize-pane -R
bind-key    -T root         MouseDown3Pane         if-shell -F -t = "#{||:#{mouse_any_flag},#{&amp;&amp;:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}" { select-pane -t = ; send-keys -M } { display-menu -T "#[align=centre]#{pane_index} (#{pane_id})" -t = -x M -y M "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}" &lt; { send-keys -X history-top } "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}" &gt; { send-keys -X history-bottom } '' "#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}" C-r { if-shell -F "#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}" "copy-mode -t=" ; send-keys -X -t = search-backward "#{q:mouse_word}" } "#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}" C-y { copy-mode -q ; send-keys -l "#{q:mouse_word}" } "#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}" c { copy-mode -q ; set-buffer "#{q:mouse_word}" } "#{?mouse_line,Copy Line,}" l { copy-mode -q ; set-buffer "#{q:mouse_line}" } '' "#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}" C-h { copy-mode -q ; send-keys -l "#{q:mouse_hyperlink}" } "#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}" h { copy-mode -q ; set-buffer "#{q:mouse_hyperlink}" } '' "Horizontal Split" h { split-window -h } "Vertical Split" v { split-window -v } '' "#{?#{&gt;:#{window_panes},1},,-}Swap Up" u { swap-pane -U } "#{?#{&gt;:#{window_panes},1},,-}Swap Down" d { swap-pane -D } "#{?pane_marked_set,,-}Swap Marked" s { swap-pane } '' Kill X { kill-pane } Respawn R { respawn-pane -k } "#{?pane_marked,Unmark,Mark}" m { select-pane -m } "#{?#{&gt;:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}" z { resize-pane -Z } }
bind-key    -T root         MouseDrag1Border       resize-pane -M
bind-key    -T root         M-MouseDown3Pane       display-menu -T "#[align=centre]#{pane_index} (#{pane_id})" -t = -x M -y M "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}" &lt; { send-keys -X history-top } "#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}" &gt; { send-keys -X history-bottom } '' "#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}" C-r { if-shell -F "#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}" "copy-mode -t=" ; send-keys -X -t = search-backward "#{q:mouse_word}" } "#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}" C-y { copy-mode -q ; send-keys -l "#{q:mouse_word}" } "#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}" c { copy-mode -q ; set-buffer "#{q:mouse_word}" } "#{?mouse_line,Copy Line,}" l { copy-mode -q ; set-buffer "#{q:mouse_line}" } '' "#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}" C-h { copy-mode -q ; send-keys -l "#{q:mouse_hyperlink}" } "#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}" h { copy-mode -q ; set-buffer "#{q:mouse_hyperlink}" } '' "Horizontal Split" h { split-window -h } "Vertical Split" v { split-window -v } '' "#{?#{&gt;:#{window_panes},1},,-}Swap Up" u { swap-pane -U } "#{?#{&gt;:#{window_panes},1},,-}Swap Down" d { swap-pane -D } "#{?pane_marked_set,,-}Swap Marked" s { swap-pane } '' Kill X { kill-pane } Respawn R { respawn-pane -k } "#{?pane_marked,Unmark,Mark}" m { select-pane -m } "#{?#{&gt;:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}" z { resize-pane -Z }
</code></pre>
<p>🐇 <code>resize-pane</code> has keys, but they conflict with <code>C-Right</code>, etc., which are macOS display selection keys. I defined <code>M-S-Right</code>, etc., to resize and <code>M-C-Right</code>, etc., to swap the panes. Defining new keys with <code>M-S</code>…</p>
<pre><code class="language-tmux">bind-key -n "M-S-Right" resize-pane -R 10
bind-key -n "M-S-Left" resize-pane -L 10
bind-key -n "M-S-Up" resize-pane -U 10
bind-key -n "M-S-Down" resize-pane -D 10
</code></pre>
<p>🐢 We can set keys to swap windows too, maybe to <code>M-C-PageDown</code>, etc. <code>M-PageDown</code> now moves between windows.</p>
<p>🐇 Let’s take a look at the command options:</p>
<pre><code class="language-text">     swap-window [-d] [-s src-window] [-t dst-window]
                   (alias: swapw)
             This is similar to link-window, except the source and destination windows are swapped.  It is an error if no window exists at src-window.  If -d is given, the new window
             does not become the current window.

             If -s is omitted and a marked pane is present (see select-pane -m), the window containing the marked pane is used rather than the current window.
</code></pre>
<p>🐢 The use case will be limited, though.</p>
<p>🐇 Umm, right. Let’s stop procrastinating here.</p>
<p>🐢 It was productive procrastination, though. Now I can resize my tmux panes.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 30</title>
      <published>2025-05-11T18:17:52+00:00</published>
      <updated>2025-05-11T18:17:52+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 11 May 2025 18:17:52 +0000</pubDate>
      <link>https://emresahin.net/devlog-30/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-30/</guid>
      <description>🐢 Let’s discuss how to move Xvc forward—maybe we can write a post to Reddit and the Rust forum in the meantime. 🐇 I think the next step is rclone remotes. It will allow us to use all remote storages supported by Rclone, which is a nice feature. 🐢 Don’t you think we need to publish the current ver...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>rclone</category>
      <category>rsync</category>
      <category>ecs</category>
      <category>ecs index</category>
      <category>architecture</category>
      <category>storage</category>
      <category>doctor</category>
      <content:encoded><![CDATA[<p>🐢 Let’s discuss how to move Xvc forward—maybe we can write a post to Reddit and the Rust forum in the meantime.
🐇 I think the next step is rclone remotes. It will allow us to use all remote storages supported by Rclone, which is a nice feature.
🐢 Don’t you think we need to publish the current version to Reddit and the forum?
🐇 We can do that as well.
🦊 Adding rclone remote must be a straightforward task.
🐢 We need to understand rclone paths, but overall, yes. We’ll just need to get the remote name, like <code>drive://</code>, and a path, like <code>my-xvc-storage</code>, and build paths with these.
🐇 What are the commands?
🐢 We need to learn how to upload files from local to remote and how to download these files. We can also list the files and get files as well.
🐲 How about adding a <code>paths.txt</code> to folders in remotes to show which paths the files in <code>0.jpg</code> belong to? This will change the remote cache structure a bit. We will have a reverse index of files and they will be findable.
🐢 What’s the reason for this?
🐲 When I upload a file to Drive with only the content hash, I lose track of the actual path. This is not desirable. We can add a file to the directory, called <code>paths.txt</code>, to get the paths for a file.
🐢 This may prove to be a feat, though; adding these <code>XvcPaths</code> to a file requires a lookup.
🐇 Maybe a JSON file? It might be possible to look up a path with a JSON file, and it will be easier to parse.
🐲 I don’t think the issue is about parsing, though. We can just have a plain text file that lists the paths. It’s a text file, which is the most compatible across all storages.
🐢 Storages, you mean.
🐲 Ugh, yeah. If I have a file called <code>Alan Watts</code> but I only have the content, this file will be immensely useful.
🐢 This makes <code>XvcCachePath</code> and <code>XvcPath</code> coupled. Architecture-wise, it may not be a good thing, though.
🐇 Also, there may be common storages for multiple repositories.
🐲 Umm, that’s a good point. I don’t think the architecture will be much compromised, though. We already keep the file paths and their cache paths somewhere.
🐢 Cache paths are generated from the content, but any number of paths can point to a single path in the cache. If I have 1 million copies of the same file, will I add all these files to the <code>paths.txt</code> you mentioned?
🐲 That’s a good point too. We can have a limit, like 1,000 or something, not to make these files too big.
🐢 Instead of this, we can store the output of <code>xvc file list</code> at the storage root and allow looking up the files that way.
🐲 It has the same problem, though; if we have a million files, their list will be too large.
🐢 There can be a manual command, like <code>xvc file index --to storage</code>, that will show content hashes and paths of each file. We can also add URLs to files if possible.
🐲 No one will use it when it’s manual, though.
🐢 We can add functionality to update this index when we send a file, though.<br>🐲 So, after each send, we’ll update the index for the repository on that storage. Is that correct?
🐢 Not after each send. After each send session, maybe.
🐇 We can have an incremental way of updating the index, like we do in ECS?
🐢 It will be overkill for this functionality and add too much noise to the storage.
🐲 Let’s keep this discussion here, but I also want to have an index merge or index cleanup mechanism for the entity generator and the ECS.
🐢 We can have a “merge indices” functionality in ECS. That will remove all older entity-generator files and merge all store files.
🐇 Removing older entity files is easy, but what about merging the store files?
🐢 It’s easy too. We’ll just load all event logs from the directory, remove all other files, and save the event log to a file.
🐇 Will this be manual or automatic?
🐢 I think the first version can be manual, something like <code>xvc fsck merge-store-files</code> or something like that. We can notify the user if the number of files is &gt; 10,000 or something like that. I don’t think we need to make it automatic unless we measure the impact of these files. There is no point in trying to do it at every command.
🐇 Then we’ll have two new commands for the next version?
🐢 I think we can just add rclone remote now and release it, then make changes in the ECS for this new <code>xvc fsck</code> command.
🐇 Can the name be <code>doctor</code> or something? Or <code>util</code>? Or can we add a top-level <code>merge indices</code> command?
🐢 <code>xvc doctor</code> seems like a better alternative. We can have a <code>diagnose</code> subcommand as well to check for possible inconsistencies. <code>xvc doctor merge-store-files</code> is a better command.
🐲 Will we use <code>d</code> for this command?
🐢 No need to add a single-letter command for this, I believe. It shouldn’t be required to run frequently.
🐇 Hmm, ok. What do we need to know for rclone remote?
🐲 I noticed we don’t have the <code>xvc storage remove</code> command implemented yet. Maybe we can start from that.
🐢 Hmm, yeap. Let’s start by implementing that first. We can add the rclone command next.
🐇 Will we use a feature flag for rclone? It will run the command only with an external binary.
🐢 It’s better to have a feature flag. I think we can add a feature flag for rsync remote as well.
🐇 We can use the generic one to update the feature flag.
🐢 I think the only two items of information we need for rclone are the remote name and the remote directory. Will we make these required?</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 29</title>
      <published>2025-04-24T03:29:07+00:00</published>
      <updated>2025-04-24T03:29:07+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:29:07 +0000</pubDate>
      <link>https://emresahin.net/devlog-29/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-29/</guid>
      <description>🐢 As the new version is updated, we can go back to the project. What’s the next step? 🐇 We can continue working on rclone. 🐢 Umm, ok. Let’s try to focus on adding another storage type. ✅ #🌻 ADD rclone storage type (2025-04-24 06:27) 🐢 I think the first option is to run the commands from the comma...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>xvc storage new generic</category>
      <category>rclone alias</category>
      <category>Rust</category>
      <category>Development</category>
      <content:encoded><![CDATA[<p>🐢 As the new version is updated, we can go back to the project. What’s the next step?</p>
<p>🐇 We can continue working on rclone.</p>
<p>🐢 Umm, ok. Let’s try to focus on adding another storage type.</p>
<ul>
<li>✅ #🌻 ADD rclone storage type (2025-04-24 06:27)</li>
</ul>
<p>🐢 I think the first option is to run the commands from the command line. We can just use a modified generic storage type without trying to make it fast.</p>
<p>🐇 Let’s make it run first, you say?</p>
<p>🐢 Yes, let’s make it run first and then we can think about making it run fast.</p>
<p>🐇 You’re right!</p>
<ul>
<li>✅ #🌻 ADD generic rclone tests (2025-04-24 06:27)</li>
</ul>
<p>It’s possible to use the <a href="https://www.reddit.com/r/rclone/comments/qu5l5k/how_to_specify_local_directory_as_a_remote/">alias</a> remote with a local path.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 28</title>
      <published>2025-04-24T03:20:52+00:00</published>
      <updated>2025-04-24T03:20:52+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:20:52 +0000</pubDate>
      <link>https://emresahin.net/devlog-28/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-28/</guid>
      <description>🐢 We can start adding an rclone remote as well. 🐢 Created the PR, waiting for the tests. 🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way. 🐇 It will add another dependency though. 🦊 We can always add a feature flag for this. 🐢 There is a librclone crate that c...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>librclone</category>
      <category>Rust</category>
      <category>celeste</category>
      <category>cloud-storage</category>
      <content:encoded><![CDATA[<p>🐢 We can start adding an rclone remote as well.</p>
<p>🐢 Created the PR, waiting for the tests.</p>
<p>🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way.</p>
<p>🐇 It will add another dependency though.</p>
<p>🦊 We can always add a feature flag for this.</p>
<p>🐢 There is a <a href="https://github.com/trevyn/librclone">librclone</a> crate that can be used to call rclone commands like https://rclone.org/rc/#supported-commands</p>
<p>🦊 We can test it from the command line perhaps.</p>
<pre><code class="language-bash">rclone rc 
2025/03/15 17:41:09 NOTICE: Failed to rc: failed to list: connection failed: Post "http://localhost:5572/rc/list": dial tcp [::1]:5572: connect: connection refused
</code></pre>
<p>🐇 It requires the backend to be running in the background.</p>
<p>🦊 There may be examples in the repository.</p>
<p>🐢 There are none. We can search GH for this crate though.</p>
<p>🦊 It’s also possible to search for dependents in crates.io.</p>
<p>🐢 I think Xvc will be the first dependent of this crate: https://crates.io/crates/librclone/reverse_dependencies</p>
<p>🐇 The following two projects depend on librclone:</p>
<ul>
<li>https://github.com/Sh3mm/WarpDrive/tree/master</li>
<li>https://github.com/hwittenborn/celeste</li>
</ul>
<p>🐢 Let’s clone Celeste. It uses librclone and looks like it’s a user interface for rclone written in Rust.</p>
<p>🐇 The examples are in <code>celeste/src/rclone.rs</code>.</p>
<p>🐢 Cool. Let’s take a look at how commands are run:</p>
<pre><code class="language-rust">    /// Common function for some of the below command.
    fn common(command: &amp;str, remote_name: &amp;str, path: &amp;str) -&gt; Result&lt;(), RcloneError&gt; {
        let resp = run(
            command,
            &amp;json!({
                "fs": get_remote_name(remote_name),
                "remote": util::strip_slashes(path),
            })
            .to_string(),
        );

        match resp {
            Ok(_) =&gt; Ok(()),
            Err(json_str) =&gt; Err(serde_json::from_str(&amp;json_str).unwrap()),
        }
    }</code></pre>
<p>All commands are run like <code>librclone::rpc(method, input))</code> and the commands are like:</p>
<pre><code class="language-rust">    /// make a directory on the remote.
    pub fn mkdir(remote_name: &amp;str, path: &amp;str) -&gt; Result&lt;(), RcloneError&gt; {
        common("operations/mkdir", remote_name, path)
    }</code></pre>
<p>🐇 We have all commands in this file that are relevant to Xvc. Let’s list them here:</p>
<ul>
<li>make directory: <code>common("operations/mkdir", remote_name, path)</code></li>
<li>delete file: <code>common("operations/delete", remote_name, path)</code></li>
<li>remove a dir and all of its contents: <code>common("operations/purge", remote_name, path)</code></li>
<li>copy file:</li>
</ul>
<pre><code class="language-rust">run( "operations/copyfile",
            &amp;json!({
                "srcFs": src_fs,
                "srcRemote": util::strip_slashes(src_remote),
                "dstFs": dst_fs,
                "dstRemote": util::strip_slashes(dst_remote)
            })</code></pre>
<p>and</p>
<pre><code class="language-rust">
    /// Copy a file from the local machine to the remote.
    pub fn copy_to_remote(
        local_file: &amp;str,
        remote_name: &amp;str,
        remote_destination: &amp;str,
    ) -&gt; Result&lt;(), RcloneError&gt; {
        copy(
            "/",
            local_file,
            &amp;get_remote_name(remote_name),
            remote_destination,
        )
    }

    /// Copy a file from the remote to the local machine.
    pub fn copy_to_local(
        local_destination: &amp;str,
        remote_name: &amp;str,
        remote_file: &amp;str,
    ) -&gt; Result&lt;(), RcloneError&gt; {
        copy(
            &amp;get_remote_name(remote_name),
            remote_file,
            "/",
            local_destination,
        )
    }</code></pre>
<p>🐇 It looks like that’s all we need. We can organize the commands differently, but these examples are enough to use <code>librclone</code>. It seems rather straightforward.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 27</title>
      <published>2025-04-24T03:17:59+00:00</published>
      <updated>2025-04-24T03:17:59+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:17:59 +0000</pubDate>
      <link>https://emresahin.net/devlog-27/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-27/</guid>
      <description>🐇 Tests are failing again: ghrl | get url | first https://github.com/iesahin/xvc/actions/runs/13875177382 🐢 We forgot to update the doc tests. Let’s run them again to update storage remove and file untrack commands. 🐇 There are issues with elision. ghpl ╭───┬──────────────────────┬───────────────...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>trycmd</category>
      <category>doc tests</category>
      <category>GitHub Actions</category>
      <category>release checklist</category>
      <category>PyPI</category>
      <content:encoded><![CDATA[<p>🐇 Tests are failing again:</p>
<pre><code class="language-nu">ghrl | get url | first
https://github.com/iesahin/xvc/actions/runs/13875177382
</code></pre>
<p>🐢 We forgot to update the doc tests. Let’s run them again to update <code>storage remove</code> and <code>file untrack</code> commands.</p>
<p>🐇 There are issues with elision.</p>
<pre><code class="language-nu">ghpl

╭───┬──────────────────────┬────────────────────┬──────────────────────────────╮
│ # │     headRefName      │       title        │             url              │
├───┼──────────────────────┼────────────────────┼──────────────────────────────┤
│ 0 │ storage-remove-16674 │ xvc storage remove │ https://github.com/iesahin/x │
│   │                      │                    │ vc/pull/270                  │
╰───┴──────────────────────┴────────────────────┴──────────────────────────────╯
</code></pre>
<p>🐢 Tests are passing; we can merge the PR. But our commit hook to check the CHANGELOG doesn’t work. That’s weird; we don’t get any errors when the CHANGELOG is not in the push set.</p>
<pre><code class="language-nu">tmux new-window -c ($env.HOME | path join github.com iesahin xvc.py)  nvim 
</code></pre>
<p>🐢 Now we can update the Python bindings as well.</p>
<p>🐇 We forgot to bump package versions. We need to create a checklist for releases.</p>
<ul>
<li>✅ #🌻 CREATE a release checklist (2025-03-25 17:52)</li>
</ul>
<p>🐢 Let’s check if the latest version is updated on PyPI.</p>
<pre><code class="language-bash">pypi xvc
</code></pre>
<p>🐇 Yes, it is.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 26</title>
      <published>2025-04-24T03:01:04+00:00</published>
      <updated>2025-04-24T03:01:04+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:01:04 +0000</pubDate>
      <link>https://emresahin.net/devlog-26/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-26/</guid>
      <description>🐢 We can add type checking to xvc.py ’s command-line handler. Currently, it builds a command line manually from the given options and parses it with clap . It’s error-prone. 🐇 What are the options, though? xvc.py is just a wrapper around xvc , and that was the easiest way to get it working. We ca...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>xvc.py</category>
      <category>clap</category>
      <category>command-line</category>
      <category>type-checking</category>
      <category>options</category>
      <content:encoded><![CDATA[<p>🐢 We can add type checking to <code>xvc.py</code>’s command-line handler. Currently, it builds a command line manually from the given options and parses it with <code>clap</code>. It’s error-prone.</p>
<p>🐇 What are the options, though? <code>xvc.py</code> is just a wrapper around <code>xvc</code>, and that was the easiest way to get it working. We can list all options manually in the headers as documentation, but it will be harder to maintain.</p>
<p>🐢 Once we start doing it, we’ll find a good way to simplify and shorten it.</p>
<p>🐇 Let’s start to work on <code>xvc file track</code>, then.</p>
<p>🐢 Worked on it a bit, and I decided it’s not worth it at the moment. We need to supply default values for most of the options. Maintaining a separate list of default values may not be feasible; it will be error-prone in a different way.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 25</title>
      <published>2025-04-24T02:57:26+00:00</published>
      <updated>2025-04-24T02:57:26+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:57:26 +0000</pubDate>
      <link>https://emresahin.net/devlog-25/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-25/</guid>
      <description>🐢 I want to update xvc.py to the latest version. 🐇 It should only be needed to update the dependency versions in Cargo.toml , right? 🐢 Let’s start with that. 🐇 We have interface changes regarding aliases; let’s start using uv for building. 🦊 Added requirements to pyproject.toml by running: uv add...</description>
      <category>XVC</category>
      <category>Python</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Python</category>
      <category>uv</category>
      <category>Package Management</category>
      <category>Requirements</category>
      <content:encoded><![CDATA[<p>🐢 I want to update <code>xvc.py</code> to the latest version.</p>
<p>🐇 It should only be needed to update the dependency versions in <code>Cargo.toml</code>, right?</p>
<p>🐢 Let’s start with that.</p>
<p>🐇 We have interface changes regarding aliases; let’s start using <code>uv</code> for building.</p>
<p>🦊 Added requirements to <code>pyproject.toml</code> by running:</p>
<pre><code class="language-bash">uv add -r requirements.txt
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog 24</title>
      <published>2025-04-24T02:54:54+00:00</published>
      <updated>2025-04-24T02:54:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:54:54 +0000</pubDate>
      <link>https://emresahin.net/devlog-24/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-24/</guid>
      <description>🐇 Is there a way to implement Default for CLI structs? 🐢 There is a way if we start from the configuration, not just files. The default configuration is a TOML document. We can make it an XvcConfiguration struct and load and store it with confy . 🐇 We have a cascading set of configurations, but m...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Configuration</category>
      <category>confy</category>
      <category>CLI</category>
      <content:encoded><![CDATA[<p>🐇 Is there a way to implement <code>Default</code> for CLI structs?</p>
<p>🐢 There is a way if we start from the configuration, not just files. The default
configuration is a TOML document. We can make it an <code>XvcConfiguration</code> struct and
load and store it with <code>confy</code>.</p>
<p>🐇 We have a cascading set of configurations, but maybe we can start from the
struct and serialize/deserialize it on demand.</p>
<p>🐲 I don’t think we need to update <code>xvc-config</code> at the moment. It’s working, and
we don’t need to alter its inner workings in the near future.</p>
<p>🐢 I agree. We can use the <a href="https://docs.rs/config/"><code>config</code></a> crate when we need to update and have
enough time to work on this.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 23</title>
      <published>2025-02-03T10:30:44+00:00</published>
      <updated>2025-02-03T10:30:44+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 10:30:44 +0000</pubDate>
      <link>https://emresahin.net/devlog-23/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-23/</guid>
      <description>🐇 Let’s turn to discussing the JSON output changes. We can add another option to XvcOutputLine , like XvcOutputLine::Json(T: Serialize) , that will output the type using serde_json . It won’t introduce any other type. 🐢 Yes, but what will T be? Although store structures have Serde implementations...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Serde</category>
      <category>JSON</category>
      <category>Serialization</category>
      <category>xvc file list</category>
      <content:encoded><![CDATA[<p>🐇 Let’s turn to discussing the JSON output changes. We can add another option
to <code>XvcOutputLine</code>, like <code>XvcOutputLine::Json(T: Serialize)</code>, that will output
the type using <code>serde_json</code>. It won’t introduce any other type.</p>
<p>🐢 Yes, but what will <code>T</code> be? Although store structures have Serde
implementations, they are not particularly useful for this.</p>
<p>🐇 We can have output types, named like <code>XvcFileListOutput</code>, that will be
converted to strings with serialization.</p>
<p>🦊 We do something similar in <code>xvc pipeline export</code> and <code>import</code> commands. We
use
<a href="https://github.com/iesahin/xvc/blob/main/pipeline/src/pipeline/schema.rs#L41"><code>XvcPipelineSchema</code></a>
and <code>XvcStepSchema</code> just for the import and export commands. We’ll write similar
structs for all JSON output and will use Serde to convert these to strings.</p>
<p>🐢 Unlike the <code>import</code> and <code>export</code> commands, we have optional fields in the
output, though. I don’t want content digests to appear in JSON output if they
are not required.</p>
<p>🐇 Let’s search for optional fields in Serde.</p>
<p>🦊 There is a <a href="https://docs.rs/optional-field/latest/optional_field/attr.serde_optional_fields.html">crate for optional
fields</a>.</p>
<p>🐢 We don’t need another crate for this. Serde has the
<a href="https://serde.rs/attr-skip-serializing.html"><code>skip_serializing_if</code></a> attribute
for fields. We can add <code>Option::is_none</code> as a method to these to skip outputting <code>None</code>
fields. All those fields, in this case, will be optional.</p>
<p>🐇 This is fine. We already use structs to format the <code>xvc file list</code> output. We
can just use them to output JSON.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 22</title>
      <published>2025-02-03T10:24:41+00:00</published>
      <updated>2025-02-03T10:24:41+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 10:24:41 +0000</pubDate>
      <link>https://emresahin.net/devlog-22/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-22/</guid>
      <description>🐢 We need to find a theme for the blog. The current one breaks Nushell output tables because the code blocks are too narrow. 🐇 https://www.getzola.org/themes/pico/ is an option, but I don’t like its header. 🦊 Minimal Dark from the same author looks better: https://kuznetsov17.github.io/minimal-da...</description>
      <category>Digital Garden</category>
      <category>Development</category>
      <category>Theme</category>
      <category>Zola</category>
      <category>Anemone</category>
      <category>mdBook</category>
      <category>Nushell</category>
      <category>Netlify</category>
      <category>Syntax Highlighting</category>
      <category>Static Site Generator</category>
      <content:encoded><![CDATA[<p>🐢 We need to find a theme for the blog. The <a href="https://www.getzola.org/themes/anemone/">current
one</a> breaks Nushell output tables
because the code blocks are too narrow.</p>
<p>🐇 https://www.getzola.org/themes/pico/ is an option, but I don’t like its header.</p>
<p>🦊 Minimal Dark from the same author looks better: https://kuznetsov17.github.io/minimal-dark/notes/note1/</p>
<p>🐇 https://www.getzola.org/themes/no-style-please/ is also an option.</p>
<p>🦊 https://halve-z.netlify.app/posts/information/ looks interesting, but there is too much screen estate for the left bar.</p>
<p>🐢 Let’s start by running the site locally first.</p>
<p>🐇 There are errors in the configuration. That’s weird, but let’s fix these.</p>
<p>🐢 Fixed errors. These are probably related to a newer version. Can we take a look at <code>netlify.toml</code> to see if it downloads the same version?</p>
<p>🐇 There are breaking changes in Zola 0.19. Let’s update and push the Netlify config to see the results.</p>
<p>🐢 We need to add language support for Nushell to prevent warnings. Take a look at how to add a syntax file to Zola.</p>
<p>🐇 I added</p>
<pre><code class="language-toml">extra_syntaxes_and_themes = ["syntaxes"]
</code></pre>
<p>to the <code>[markdown]</code> section and added a <code>syntaxes/nushell.sublime-syntax</code> file copied from https://github.com/kurokirasama/nushell_sublime_syntax. I’m getting:</p>
<pre><code>Error: Reason: Error while compiling regex '\b(?x: 7z | ?
...
s-to-gdrive | usage | ver | verify | weather | wget-all | which-cd | wifi-info | wifi-pass | xls2csv | ydx | yt-api | ytcli | ytm | z | zi)\b'
Oniguruma error: target of repeat operator is not specified
</code></pre>
<p>🐢 The syntax highlighter may be a bit buggy. Let’s try to fix this if it’s a one-off.</p>
<p>🐇 Found the bug. There is a <code>?</code> in the regex that causes it to fail. Now it compiles, and Nushell blocks are colored.</p>
<p>🐢 Cool. Let’s fix the other warnings now. <code>shell</code> and <code>console</code> are not recognized, it looks like.</p>
<p>🐇 There is only Bash listed in https://www.getzola.org/documentation/content/syntax-highlighting/.</p>
<p>🦁 What do you think about migrating to mdBook? We already maintain mdBook for XVC; what about just moving the site to mdBook?</p>
<p>🐢 I thought about this before, and the only downside is the lack of an RSS feed.</p>
<p>🦊 I found this: https://github.com/theowenyoung/mdbook-rss</p>
<p>🐢 Now, this changes everything. We can even move the <code>nedriy.at</code> site to mdBook in this case.</p>
<p>🐇 Then, let’s start working on this. The site will be a technical book site in this case.</p>
<p>🐢 Does mdBook support Nushell syntax?</p>
<p>🐇 Nushell is not in the <a href="https://rust-lang.github.io/mdBook/format/theme/syntax-highlighting.html">listed languages</a>. mdBook uses <a href="https://highlightjs.org/">highlight.js</a>, and in its <a href="https://highlightjs.readthedocs.io/en/latest/supported-languages.html">listed languages</a>, we don’t find Nu either.</p>
<p>🐢 We already added Nu support to Zola, and we can just change the theme. This is a blocker in my opinion.</p>
<p>🐇 I searched for Nushell highlight.js support, and nothing appears. I think we can just postpone until Nu has more support on this front.</p>
<p>🐢 Yes. Let’s first try this change in the non-technical blog, and we can come back to this issue. Now, we’ll update <code>console</code> and <code>shell</code> to <code>bash</code>, I think.</p>
<p>🐇 Replaced <code>shell</code> and <code>console</code> with <code>bash</code>.</p>
<p>🐢 There is a file for <code>ggplot</code> that has warnings from earlier incarnations. We also lack syntax highlighters for Vim and Tmux.</p>
<p>🐇 There is a <code>sublime-syntax</code> file for Tmux at https://raw.githubusercontent.com/gerardroche/sublime-tmux/refs/heads/master/Tmux.sublime-syntax, but do we need it for a single file?</p>
<p>🐢 Let’s set it to plain text.</p>
<p>🐇 Now we only have Mermaid warnings left.</p>
<p>🐢 There should be a diagram at https://emresahin.net/developing-a-gitignore-crate/, but it doesn’t show up. We need a shortcode to show these, like the YouTube shortcode. Now let’s get back to theme selection.</p>
<p>🐇 I tested Karzok, but it doesn’t have category and tags support.</p>
<p>🐢 And I tested https://github.com/micahkepe/radion, but the best so far is the <code>apollo</code> theme. I’m struggling to modify the index page, though. I forgot that I modified the theme’s <code>index.html</code> file. I have content in <code>/content/_index.md</code> and a modified <code>index.html</code> in <code>/themes/anemone/templates/index.html</code> to show the content, tags, categories, etc. It should be fixed now.</p>
<p>🐇 Ah, cool. Can we clean the recent duplicate pages now?</p>
<p>🐢 Yeah, let’s take a look.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 21</title>
      <published>2025-02-03T09:59:28+00:00</published>
      <updated>2025-02-03T09:59:28+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 09:59:28 +0000</pubDate>
      <link>https://emresahin.net/devlog-21/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-21/</guid>
      <description>🐢 Now, the next version will have a --json output for xvc file list . We can start working on it or update the Readme file? 🐇 What about adding at least command completions for Nushell? 🐢 Let’s read a bit about clap_complete_nushell . 🦊 There seems to be a nu-complete command. Let’s check its doc...</description>
      <category>xvc</category>
      <category>Development</category>
      <category>Nushell</category>
      <category>clap_complete_nushell</category>
      <category>carapace</category>
      <category>dynamic completions</category>
      <category>JSON</category>
      <category>Lazygit</category>
      <category>Rust</category>
      <category>XVC</category>
      <category>completions</category>
      <content:encoded><![CDATA[<p>🐢 Now, the next version will have a <code>--json</code> output for <code>xvc file list</code>. We
can start working on it or update the Readme file?</p>
<p>🐇 What about adding at least command completions for Nushell?</p>
<p>🐢 Let’s read a bit about <code>clap_complete_nushell</code>.</p>
<p>🦊 There seems to be a <code>nu-complete</code> command. Let’s check its documentation.</p>
<p>🐇 Nothing was found, and Kagi doesn’t help much either.</p>
<p>🐢 There is a completions document for Nushell: https://www.nushell.sh/book/custom_completions.html</p>
<p>🐇 There is a tool called carapace to provide completions across shells.</p>
<p>🐢 Its <a href="https://carapace-sh.github.io/carapace/carapace.html">documentation</a> is
thin, and I’m not sure if it supports dynamic completions out of the box. I
believe instead of adding a carapace setup, we can just write a Nushell
completion script that will use JSON output from the commands and add some
(maybe hidden) utility commands to support it.</p>
<p>🐇 There are a set of example scripts in the Nushell repo:
https://github.com/nushell/nu_scripts/tree/main/custom-completions</p>
<p>🐢 The reason I want to write custom completions for Nushell is that it will be
an exercise for the scripting language. <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/gh/gh-completions.nu"><code>gh</code>
completions</a>
are not as scary as a Bash script.</p>
<p>🐇 <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/git/git-completions.nu"><code>git</code>
completions</a>
are a better example for XVC. They simply run <code>git</code> whenever necessary. We can
start from a static completions command and update this with dynamic
completions manually. It will teach a lot.</p>
<p>🐢 I <a href="https://github.com/iesahin/nu_scripts">forked</a> the <code>nu_scripts</code> repo and
will add XVC completions script there.</p>
<p>🐇 Then let’s begin by adding Nushell static completions. Shall we add a
command for this?</p>
<p>🦊 Reviving the <code>completion</code> command we removed in 0.6.13?</p>
<p>🐢 We shouldn’t list it. We can make a <code>_comp</code> subcommand for the time being
and generate and distribute completions in the repository. When
<code>clap_complete_nushell</code> has the feature parity to provide dynamic completions,
we can remove these commands.</p>
<p>🐇 What will we use this for other than generating completions?</p>
<p>🐢 Maybe dynamic completions can call this as well.</p>
<p>🐇 Added Nushell static completions to be output using <code>xvc _comp generate-nushell</code>. Let’s bump up the version to 0.6.15.</p>
<pre><code>cargo set-version 0.6.15-alpha.1
   Upgrading xvc from 0.6.14 to 0.6.15-alpha.1
...
</code></pre>
<p>🐢 I noticed we forgot a line in the CLI command handler that asserts <code>xvc_root_opt.is_some()</code>, and this fails when we run <code>xvc</code> outside of repositories. We need to release this version quickly.</p>
<p>🐇 Oops, now, ok, let’s write a static Nushell generator and just release quickly.</p>
<p>🦊 Generating completions with</p>
<pre><code>xvc comp generate-nushell
</code></pre>
<p>🐢 Completion command is run with <code>comp</code> instead of <code>_comp</code>. Should we rename it?</p>
<p>🐇 Renamed it to <code>_comp</code>. It’s not hidden, but at least we can be sure that it won’t be misunderstood as a common command.</p>
<p>🐢 Bumping the version again. Now let’s source the generated script and test it.</p>
<pre><code>cargo set-version 0.6.15-alpha.2
   Upgrading xvc from 0.6.15-alpha.1 to 0.6.15-alpha.2
...
</code></pre>
<p>🦊 Yep, it works. We now have completions for Nushell.</p>
<p>🐢 Let’s update the completions documentation.</p>
<p>🐇 Done. Now, let’s take a look at CI and see what fails.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13070200765 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐢 It fails because of coverage, not the tests. <a href="https://github.com/iesahin/xvc/pull/266#issuecomment-2626794240">Codecov says</a> the new code isn’t tested.</p>
<p>🐇 The added <code>xvc _comp</code> command isn’t tested. We can add a test running those lines and testing if the command outputs a completion script.</p>
<p>🐢 We have a <a href="https://github.com/iesahin/xvc/blob/main/lib/tests/test_completions.rs#L18">test for completions</a>. We can add a test that runs the lines.</p>
<p>🐇 Added a test and bumping up the version.</p>
<pre><code class="language-nu">cargo set-version 0.6.15-alpha.3
   Upgrading xvc from 0.6.15-alpha.2 to 0.6.15-alpha.3
...
</code></pre>
<p>🦊 We can add some more coverage while waiting for the tests.</p>
<p>🐇 <a href="https://app.codecov.io/gh/iesahin/xvc/blob/main/logging%2Fsrc%2Flib.rs#L285"><code>XvcOutputLine</code> implementation</a> seems to have no tests. It’s weird because we use these everywhere.</p>
<p>🐢 I’m not sure we use this particular implementation; we just use <code>XvcOutputLine::Info(s)</code>, not <code>XvcOutputLine::info(s)</code> anywhere. We can delete these methods actually.</p>
<p>🐇 We’ll add JSON output via this particular struct. Can we refactor these to use formatting for JSON, for example? Or use these to output JSON?</p>
<p>🦊 We can add a formatter to <code>XvcOutputLine</code> to output structures.</p>
<p>🐢 The enum is now defined as:</p>
<pre><code class="language-rust">#[derive(Clone, Debug)]
pub enum XvcOutputLine {
    /// The output that we should be reporting to user
    Output(String),
    /// For informational messages
    Info(String),
    /// For debug output to show the internals of Xvc
    Debug(String),
    /// Warnings that are against some usual workflows
    Warn(String),
    /// Errors that interrupts a workflow but may be recoverable
    Error(String),
    /// Panics that interrupts the workflow and ends the program
    /// Note that this doesn't call panic! automatically
    Panic(String),
    /// Progress bar ticks.
    /// Self::Info is also used for Tick(1)
    Tick(usize),
}</code></pre>
<p>Here, these fields can also have a <code>formatter</code> that will render the string in a particular format. For example, the output can be</p>
<pre><code class="language-rust">XvcOutputLine::Output(XvcJsonFormatter, String)</code></pre>
<p>🐇 I’m not sure this is a good idea. <code>Output</code> already specifies this string as output. We can have a wrapper instead, like,</p>
<pre><code class="language-rust">struct XvcJsonOutput(Format&lt;XvcStructuredOutput&gt;, XvcStructuredOutput)</code></pre>
<p>and we can use the supplied format to render <code>XvcStructuredOutput</code> to an output line with <code>XvcOutputLine::Output</code>. If we don’t provide output as structured, it will be too much error-prone work to convert the current outputs to structured.</p>
<p>🦊 The transition will also be gradual. We may not need structured output for most of the commands. We can start with <code>xvc file list</code> and convert others as we go.</p>
<p>🐢 This is sensible. By the way, coverage still didn’t increase. There may be something going on with Codecov or running the test.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13087431038 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐇 Let’s run the test:</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
test test_completions ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.80s
</code></pre>
<p>🐢 Can we make sure the output is a Nushell script and not an error message?</p>
<p>🐇 Let’s print it out.</p>
<p>🐢 It looks like when the <code>COMPLETE</code> environment variable is set, it never calls <code>_comp</code> subcommand and never calls those lines.</p>
<pre><code>cargo set-version 0.6.15-alpha.4
   Upgrading xvc from 0.6.15-alpha.3 to 0.6.15-alpha.4
...
</code></pre>
<p>🐢 Let’s make a release for 0.6.15. Coverage is OK now.</p>
<pre><code class="language-nu">cargo set-version 0.6.15
   Upgrading xvc from 0.6.15-alpha.4 to 0.6.15
...
</code></pre>
<pre><code class="language-nu">gh pr merge --squash --body $"(open CHANGELOG.md | lines | skip 2 | take 5)" --subject "Add static nushell completions"
</code></pre>
<p>🐇 Merged the PR.</p>
<p>🐢 Releases should appear in a few minutes.</p>
<p>🐇 We need to tag the merge commit for this.</p>
<p>🐢 Oh, yep. AFAIK Lazygit doesn’t have something for <code>git push --tags</code>. Let’s push from the CLI.</p>
<pre><code class="language-nu">git push --tags
You are on the main branch. Skipping CHANGELOG.md check.
To github.com:iesahin/xvc
 * [new tag]         v0.6.15 -&gt; v0.6.15
</code></pre>
<p>🦊 These commands, especially tables, are not rendered correctly on the web. We need to change the theme, I think.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 20</title>
      <published>2025-02-01T09:27:50+00:00</published>
      <updated>2025-02-01T09:27:50+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 01 Feb 2025 09:27:50 +0000</pubDate>
      <link>https://emresahin.net/devlog-20/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-20/</guid>
      <description>🐢 We have an error in the publish action. Let’s fix it and rerun it. 🐇 There are two errors. One resulted from forgetting sudo when installing dependencies. The other was the incorrect name for the OpenSSL library. It should be libssl-dev instead of openssl-dev . Maybe we can link the command lis...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>once-cell</category>
      <category>xvc-config</category>
      <category>xvc-root</category>
      <category>rand</category>
      <category>libssl</category>
      <category>rust</category>
      <category>performance</category>
      <content:encoded><![CDATA[<p>🐢 We have an error in the publish action. Let’s fix it and rerun it.</p>
<p>🐇 There are two errors. One resulted from forgetting <code>sudo</code> when installing
dependencies. The other was the incorrect name for the OpenSSL library. It should be
<code>libssl-dev</code> instead of <code>openssl-dev</code>. Maybe we can link the command list in
the <a href="https://docs.rs/openssl/latest/openssl/#automatic">docs</a>.</p>
<p>🐢 Oops, yes, we should at least keep that in mind.</p>
<p>🐇 I’m checking the most popular crates. <a href="https://docs.rs/getrandom">getrandom</a>
retrieves a random number from the system. It looks much lighter than the <code>rand</code>
crate.</p>
<p>🦊 We can use <a href="https://docs.rs/once_cell">once_cell</a> to initialize
<code>XvcEntityCounter</code>. We currently
<a href="https://github.com/iesahin/xvc/blob/main/ecs/src/ecs/mod.rs#L102">use</a> <code>Once</code>
for this purpose.</p>
<p>🐇 I don’t think it will provide any better features.</p>
<p>🦊 For that case, yes, no better features. But the interface is something like:</p>
<pre><code class="language-rust">impl&lt;T&gt; OnceCell&lt;T&gt; {
    const fn new() -&gt; OnceCell&lt;T&gt; { ... }
    fn set(&amp;self, value: T) -&gt; Result&lt;(), T&gt; { ... }
    fn get(&amp;self) -&gt; Option&lt;&amp;T&gt; { ... }
}</code></pre>
<p>And this makes, for example, working with <code>XvcRoot</code> much easier. We are passing
<code>Arc&lt;RwLock&lt;XvcRootInner&gt;&gt;&gt;</code> everywhere. This is a heavy price when we only use
it in a read-only manner. We can prevent most of these, when we use a <code>read</code> lock, by using
<code>OnceCell</code>.</p>
<p>🐇 <code>XvcConfig</code> can benefit from this as well. We don’t update the config during runs.</p>
<p>🐢 Why do we want to assign it, though? We currently have a <code>config</code> field in
<code>XvcRootInner</code>, and we get a reference to it with the <code>config()</code> method.</p>
<p>🐇 Ok. Let’s skip this for now. No need to worry before measuring the performance impact.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 19</title>
      <published>2025-01-29T09:19:56+00:00</published>
      <updated>2025-01-29T09:19:56+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 29 Jan 2025 09:19:56 +0000</pubDate>
      <link>https://emresahin.net/devlog-19/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-19/</guid>
      <description>🐇 Let’s start by checking the GitHub Actions results. $ ghrl | first ╭──────────────┬─────────────────────────────────────────────────────────╮ │ conclusion │ failure │ │ displayTitle │ Add CLI completions │ │ headBranch │ clap-complete-16608 │ │ url │ https://github.com/iesahin/xvc/actions/runs/...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>github-actions</category>
      <category>testing</category>
      <category>tdd</category>
      <category>documentation</category>
      <category>ci-cd</category>
      <category>rust</category>
      <content:encoded><![CDATA[<p>🐇 Let’s start by checking the GitHub Actions results.</p>
<pre><code class="language-nu">$ ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ failure                                                 │
│ displayTitle │ Add CLI completions                                     │
│ headBranch   │ clap-complete-16608                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13007688178 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐢 Although I turned off most of the <code>watch</code>es, logs are still so large that it’s not possible to view them from the interface. Downloaded the log archive.</p>
<p>🦊 We can have different GitHub Actions steps for each test. Claude can help write such a repeating set of steps.</p>
<p>🐇 We can at least separate <code>z_test_docs</code> to see if integration tests or that fails.</p>
<p>🐢 We’ll have to add caching for test artifacts to upload them for coverage. I’m not sure we really need to add that complexity to the process just to avoid downloading the logs.</p>
<p>🐇 We can actually move all to Xvc. We need a GitHub Action to run an Xvc pipeline.</p>
<p>🐢 Eventually yes, we should move our testing to Xvc itself. Now, the logs show that there are differences in <code>z_test_docs</code> actually.</p>
<p>🐇 When I run the command below, it passes. We may have a different config in GitHub Actions.</p>
<pre><code class="language-nu">$ XVC_TRYCMD_TESTS=storage,file,pipeline,core,start TRYCMD=overwrite rws cargo test --features test-ci -p xvc --test z_test_docs
test z_doc_tests ... ok
...
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.34s
</code></pre>
<p>🐢 We don’t have an <code>XVC_TRYCMD_TESTS=storage,file,pipeline,core,start</code> definition in GitHub Actions; let’s add it, bump the version, and try again.</p>
<pre><code class="language-nu">$ cargo set-version "0.6.14-alpha.10"
   Upgrading xvc from 0.6.14-alpha.9 to 0.6.14-alpha.10
...
</code></pre>
<p>🦊 We could also update <code>run-tests.zsh</code> to get a quick response.</p>
<p>🐢 Yep, let’s do that as well.</p>
<p>🐇 Dev tests pass, but there are differences in the documents still. For some reason, the local <code>run-tests.zsh</code> doesn’t update <code>xvc/book/src/ref/xvc-storage.md</code>. It only has the help text as a reference, but it’s not updated with the aliases, and it breaks the CI. This is weird but a small issue. Fixed it manually.</p>
<pre><code class="language-nu">$ ghrl | first 2
╭───┬────────────┬───────────────────┬────────────────────┬────────────────────╮
│ # │ conclusion │   displayTitle    │     headBranch     │        url         │
├───┼────────────┼───────────────────┼────────────────────┼────────────────────┤
│ 0 │            │ Add CLI           │ clap-complete-1660 │ https://github.com │
│   │            │ completions       │ 8                  │ /iesahin/xvc/actio │
│   │            │                   │                    │ ns/runs/1302770108 │
│   │            │                   │                    │ 2                  │
│ 1 │ success    │ Add CLI           │ clap-complete-1660 │ https://github.com │
│   │            │ completions       │ 8                  │ /iesahin/xvc/actio │
│   │            │                   │                    │ ns/runs/1302755462 │
│   │            │                   │                    │ 2                  │
╰───┴────────────┴───────────────────┴────────────────────┴────────────────────╯
</code></pre>
<p>🐢 The earlier one has passed. It looks like we’re ready to merge. Let’s update the <code>CHANGELOG.md</code> for release.</p>
<p>🐇 Setting the release version:</p>
<pre><code class="language-nu">cargo set-version "0.6.14"
   Upgrading xvc from 0.6.14-alpha.10 to 0.6.14
...
</code></pre>
<p>🐢 Let’s check the CI</p>
<pre><code class="language-nu">$ ghrl | first 2

╭───┬────────────┬───────────────────┬────────────────────┬────────────────────╮
│ # │ conclusion │   displayTitle    │     headBranch     │        url         │
├───┼────────────┼───────────────────┼────────────────────┼────────────────────┤
│ 0 │ success    │ Add CLI           │ clap-complete-1660 │ https://github.com │
│   │            │ completions       │ 8                  │ /iesahin/xvc/actio │
│   │            │                   │                    │ ns/runs/1302770108 │
│   │            │                   │                    │ 2                  │
│ 1 │ success    │ Add CLI           │ clap-complete-1660 │ https://github.com │
│   │            │ completions       │ 8                  │ /iesahin/xvc/actio │
│   │            │                   │                    │ ns/runs/1302755462 │
│   │            │                   │                    │ 2                  │
╰───┴────────────┴───────────────────┴────────────────────┴────────────────────╯
</code></pre>
<p>🐇 And merge:</p>
<pre><code class="language-nu">$ ghpM --body $"(open CHANGELOG.md | lines | skip 2 | take 7)" --subject "Add completions" --squash
</code></pre>
<p>🐢 Tagged main and pushed. Packages should be built in a few minutes.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 18</title>
      <published>2025-01-28T10:07:28+00:00</published>
      <updated>2025-01-28T10:07:28+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 28 Jan 2025 10:07:28 +0000</pubDate>
      <link>https://emresahin.net/devlog-18/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-18/</guid>
      <description>🐇 There is no xvc completions command anymore. Let’s remove the test. 🐢 Instead, we can make it test with environment variables for each of these shells. 🐇 It looks like the test fails with wait_status . cargo test -p xvc --test test_completions ... failures: ---- test_completions stdout ---- ......</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>clap</category>
      <category>clap_complete</category>
      <category>testing</category>
      <category>trycmd</category>
      <category>wait_status</category>
      <category>error-handling</category>
      <category>rust</category>
      <content:encoded><![CDATA[<p>🐇 There is no <code>xvc completions</code> command anymore. Let’s remove the test.</p>
<p>🐢 Instead, we can make it test with environment variables for each of these
shells.</p>
<p>🐇 It looks like the test fails with <code>wait_status</code>.</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
failures:

---- test_completions stdout ----
...
ExitStatus(unix_wait_status(512))

thread 'test_completions' panicked at lib/tests/common/mod.rs:46:5:
Command failed: Command { cmd: "/Users/iex/github.com/iesahin/xvc/target/debug/xvc", stdin: None, timeout: None }
...
</code></pre>
<p>🐢 The failure is in the line:</p>
<pre><code class="language-rust">    let mut cmd = Command::cargo_bin("xvc").unwrap();</code></pre>
<p>So it probably waits for user input to complete, but it doesn’t have any and
cannot complete it, so it returns an error.</p>
<p>🦊 Let’s comment the earlier test out and check if other tests work.</p>
<p>🐢 Yes, they fail the same. When there are no commands to run, xvc returns an
error. Maybe we can change this behavior or handle the error in the
<code>Command::cargo_bin</code> line above.</p>
<p>🐇 I checked the code and we don’t handle the “no arguments” case anywhere. It
looks like the behavior to return an error code is inherited from clap.</p>
<p>🐢 Updated the error handling code to report a more descriptive
message from the source.</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
failures:

---- test_completions stdout ----
Output { status: ExitStatus(unix_wait_status(25856)), stdout: "", stderr: "\nthread 'main' panicked at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/debug_asserts.rs:341:13:\nCommand pipeline: command `export` alias `l` is duplicated\nstack backtrace:\n   0: rust_begin_unwind\n             at /rustc/b1a7dfb91106018f47ed9dc9b27aee1977682868/library/std/src/panicking.rs:692:5\n   1: core::panicking::panic_fmt\n             at /rustc/b1a7dfb91106018f47ed9dc9b27aee1977682868/library/core/src/panicking.rs:75:14\n   2: clap_builder::builder::debug_asserts::assert_app\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/debug_asserts.rs:341:13\n   3: clap_builder::builder::command::Command::_build_self\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/command.rs:4173:13\n   4: clap_builder::builder::command::Command::_build_recursive\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/command.rs:4076:9\n   5: clap_builder::builder::command::Command::_build_recursive\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/command.rs:4078:13\n   6: clap_builder::builder::command::Command::build\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_builder-4.5.20/src/builder/command.rs:4071:9\n   7: clap_complete::env::CompleteEnv&lt;F&gt;::try_complete_\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_complete-4.5.40/src/env/mod.rs:229:9\n   8: clap_complete::env::CompleteEnv&lt;F&gt;::try_complete\n             at /Users/iex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/clap_complete-4.5.40/src/env/mod.rs:210:9\n   9: xvc::main\n             at ./src/main.rs:18:30\n  10: core::ops::function::FnOnce::call_once\n             at... [truncated]
ExitStatus(unix_wait_status(25856))

...

error: test failed, to rerun pass `-p xvc --test test_completions`
</code></pre>
<p>🐢 The real issue is when we assert the successful run, at this line:</p>
<pre><code class="language-rust">            assert!(output.status.success(), "Command failed: {:?}", prepared);</code></pre>
<p>🐇 If you look at the error message carefully, you’ll see that this is a
different error. We added an alias to <code>xvc pipeline export</code> with <code>l</code>, which is
a duplicate.</p>
<p>🐢 Oops, yeah.</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
test test_completions ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.81s
</code></pre>
<p>🐇 There are some warnings in compilation. Let’s fix these.</p>
<pre><code>cargo build
   Compiling xvc-storage v0.6.14-alpha.8 (/Users/iex/github.com/iesahin/xvc/storage)
   Compiling xvc-file v0.6.14-alpha.8 (/Users/iex/github.com/iesahin/xvc/file)
   Compiling xvc-pipeline v0.6.14-alpha.8 (/Users/iex/github.com/iesahin/xvc/pipeline)
   Compiling xvc v0.6.14-alpha.8 (/Users/iex/github.com/iesahin/xvc/lib)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.28s
</code></pre>
<p>🐢 Finished the build without warnings. Now we can run the whole test suite again.</p>
<p>🐇 Tests are running fine. Updated some error messages and types, and doc tests
also pass. There is an issue with the Rsync storage ref.</p>
<p>🐢 It looks like we try to elide output with <code>[...]</code>, while the proper format is <code>[..]</code>.</p>
<p>🐇 Replaced them with <code>...</code> that will elide multiple lines.</p>
<p>🐢 Pushed changes to the server.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 17</title>
      <published>2025-01-27T16:07:11+00:00</published>
      <updated>2025-01-27T16:07:11+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 27 Jan 2025 16:07:11 +0000</pubDate>
      <link>https://emresahin.net/devlog-17/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-17/</guid>
      <description>🐢 We are progressing towards adding all store completions. In the meantime, I switched to nushell from zsh. Completions for nushell require a different crate and it doesn’t have dynamic completions yet. 🐇 We can just complete the completions for this version and add JSON output for all commands t...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>clap_complete</category>
      <category>clap</category>
      <category>nushell</category>
      <category>dynamic-completions</category>
      <category>completions</category>
      <category>rust</category>
      <content:encoded><![CDATA[<p>🐢 We are progressing towards adding all store completions. In the meantime, I
switched to nushell from zsh. Completions for
<a href="https://github.com/nushell/nushell">nushell</a> require a different crate and it
doesn’t have dynamic completions yet.</p>
<p>🐇 We can just complete the completions for this version and add JSON output
for all commands to use in nushell. In the meantime, dynamic completions support
for nushell is likely to be added.</p>
<p>🐢 Umm, yep. Then we’ll continue to run tests with zsh for the current version.</p>
<p>🐇 <a href="~/github.com/iesahin/xvc/run-tests.zsh"><code>run-tests</code></a> is a zsh file, and
we will continue to use it. We can switch to nushell eventually as we add more
compatibility with it, but let’s not do this now.</p>
<p>🐢 Where were we the last time for completions?</p>
<p>🐇 We added xvc_path_completers. Let’s check the TODO list now.</p>
<p>🐢 We still need tracked_targets completions in a few places.</p>
<p>🐇 Added those.</p>
<p>🐢 Now we have some strum completers. Let’s finish these up as well.</p>
<p>🐇 Ok. They are done as well.</p>
<p>🐢 Now, let’s start adding a storage_identifier completer. It should read all
<code>XvcStorage</code> records and list their names.</p>
<p>🐇 Added a <code>storage_identifier</code> completer. Let’s add it to all places where we use
storage_identifiers.</p>
<p>🐢 Now, we need completers for pipeline and step names. These are
straightforward.</p>
<p>🐇 Added these. Let’s fill up where they are needed.</p>
<p>🐢 I want to skip some of the fine-grained completions in this version. We can
have a specific completer for <code>--params</code> options for files and params inside,
for example. Or a special completer for directories.</p>
<p>🐇 Let’s not forget these. Reading YAML files and extracting hyperparameter
keys for the prompt would be a really good feature for the user.</p>
<p>🐢 I agree. These are good features in general, but we need to ship the current
version as soon as possible.</p>
<p>🐇 Let’s check the TODO comments once more.</p>
<pre><code class="language-sh">$ rg 'TODO:' 
...
- ✅ pipeline/src/pipeline/api/update.rs:    /// TODO: Add a repository_dirs completer (11:08)
- ✅ file/src/remove/mod.rs:    /// TODO: Add a storage_identifier completer (11:08)
- ✅ file/src/bring/mod.rs:    /// TODO: Add a storage_identifier completer (11:08)
...
</code></pre>
<p>🦊 We can use <code>xvc_path_completer</code> for the tracked directory completer for the
time being. For <code>pipeline update</code>, we can use a <code>ValueHint</code> instead. It’s not
necessary to use a custom completion.</p>
<p>🐢 For the <code>xvc file copy</code> destination, we can have a file or a directory that we
track and is not available, or we don’t track and is available. It’s a similar situation
to xvc_path_completer, but we also need to check the local paths. It requires
some more care.</p>
<p>🐇 Now we can begin to run the tests.</p>
<p>🐢 Is this building?</p>
<p>🐇 It should.</p>
<p>🐢 We also have one bug. xvc shouldn’t print help text when the <code>COMPLETE</code>
environment variable is set.</p>
<p>🐇 Umm, yeah, that’s a blocker.</p>
<p>🐢 Fixed it. We can bump the version and push the changes.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 16</title>
      <published>2025-01-20T12:58:54+00:00</published>
      <updated>2025-01-20T12:58:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 20 Jan 2025 12:58:54 +0000</pubDate>
      <link>https://emresahin.net/devlog-16/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-16/</guid>
      <description>🐢 Good morning. Yesterday we wrote a proxy server for a job application. It was a pleasing experience, but I wasn’t able to write a dialogue. 🐇 It was one of those nice days. I have that feeling in my head that I used it to its full potential. 🐢 So we can do some light work today. 🐇 Like completi...</description>
      <category>xvc</category>
      <category>clap-completion</category>
      <category>xvc-ecs</category>
      <category>architecture</category>
      <category>Rust</category>
      <category>ECS</category>
      <category>data storage</category>
      <category>development log</category>
      <content:encoded><![CDATA[<p>🐢 Good morning. Yesterday we wrote a proxy server for a job application. It was a pleasing experience, but I wasn’t able to write a dialogue.</p>
<p>🐇 It was one of those nice days. I have that feeling in my head that I used it to its full potential.</p>
<p>🐢 So we can do some light work today.</p>
<p>🐇 Like completions?</p>
<p>🐢 Yep, let’s fill in some missing pieces there.</p>
<p>🐇 Let’s see what those missing pieces are.</p>
<p>🐢 We need to clean up the code comments we copied from <code>jj</code>.</p>
<p>🦊 Cleaned up quite a bit and moved everything to <code>main</code>. It’s a single function call.</p>
<p>🐢 I think we completed all <code>strum</code>-related completions. Now we need to discuss store completions a bit.</p>
<p>🐇 They can’t all have the same function like we used for <code>strum</code>-based enums. They will look up different parts of components. For <code>XvcPath</code>, it will be the inner <code>RelativePathBuf</code>; for <code>StorageIdentifier</code>, that will be the names of all recorded storage names and GUIDs. The field we’re going to complete will change from case to case.</p>
<p>🦊 They can depend on the same machinery. It will detect <code>.xvc</code>, load a store, and return a field from the component. The first two are the same for all.</p>
<p>🐢 Yep. Let’s write a few TODOs in the code, then.</p>
<p>🦊 One thing to discuss is whether we should load all config and root. It may be a bit time-consuming. Just loading the stores should be enough.</p>
<p>🐢 I think so. ECS doesn’t depend on <code>XvcRoot</code>, and we can load stores without loading <code>XvcRoot</code>. We have convenience functions in <code>XvcRoot</code>, but they are for convenience. There is nothing that prevents us from loading stores without loading the root.</p>
<p>🦊 Let’s take a look at <code>XvcRoot</code>’s loading of these.</p>
<p>🐢 Tomorrow, hopefully.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 15</title>
      <published>2025-01-20T12:51:56+00:00</published>
      <updated>2025-01-20T12:51:56+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 20 Jan 2025 12:51:56 +0000</pubDate>
      <link>https://emresahin.net/devlog-15/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-15/</guid>
      <description>🐢 Should we go into completions directly, or do we have anything to update in the working scripts? 🐇 Starting with completions is better. One question I have is whether the completions work for subcommands as usual, like in the static completions. 🐢 Yes, they work. We need to add: source &lt;(COMPLE...</description>
      <category>xvc</category>
      <category>clap</category>
      <category>clap-complete</category>
      <category>zsh</category>
      <category>git</category>
      <category>xvc-file</category>
      <category>xvc-pipeline</category>
      <category>Rust</category>
      <category>refactoring</category>
      <category>CLI design</category>
      <category>feature flags</category>
      <content:encoded><![CDATA[<p>🐢 Should we go into completions directly, or do we have anything to update in the working scripts?</p>
<p>🐇 Starting with completions is better. One question I have is whether the completions work for subcommands as usual, like in the static completions.</p>
<p>🐢 Yes, they work. We need to add:</p>
<pre><code class="language-sh">source &lt;(COMPLETE=zsh xvc)
</code></pre>
<p>to <code>.zshrc</code>, though.</p>
<p>🐇 It looks like we can drop the <code>xvc completions</code> command. We’re just checking the <code>COMPLETE</code> environment variable, and there’s no need for a separate command in this case.</p>
<p>🐢 Yes, let’s remove that.</p>
<p>🐇 Maybe we can employ a <code>completions</code> module for all completion-related functionality. It’s a separate thing, you know.</p>
<p>🐢 Which completions do we need?</p>
<p>🐇 We can just mark them with TODOs now.</p>
<p>🐢 Yes, let’s check where we need completions and what.</p>
<p>🐇 I noticed we’re repeating options in <code>XvcFileCLI</code>, and some of the options are missing, e.g., <code>--from-ref</code> and <code>--to-branch</code>.</p>
<p>🐢 Because it can compile into a different binary, and <code>global</code> options don’t work that way. Maybe we can move all these binaries into different files under <code>lib</code>. We can have feature flags to turn off certain features and compile different binaries.</p>
<p>🦊 Let’s search for it; I haven’t seen it before: building different binaries with feature flags using <code>cargo</code>.</p>
<p>🐢 There is no clear-cut solution, but it looks like we can move all binaries to the root with the <a href="https://rustwiki.org/en/cargo/reference/cargo-targets.html#the-required-features-field"><code>required-features</code></a> field.</p>
<p>🐲 We can postpone this to another release.</p>
<p>🐇 Yes, let’s not spend time on this now.</p>
<p>🐢 Should we try making <code>pipeline_name</code> a global option? It’s repeating everywhere.</p>
<p>🐇 That will make it easier to maintain.</p>
<p>🐢 We’ll have to pass <code>pipeline_name</code> to subcommands, though.</p>
<p>🐇 I think we can have a set of global options that we can pass. For the time being, that’s only the <code>pipeline_name</code>.</p>
<p>🐢 Okay. Let’s do this.</p>
<p>🐇 A similar option is the step name for pipeline steps.</p>
<p>🐢 Dependencies will need a revamp in the next version anyway. So let’s keep it for now.</p>
<p>🐲 Also, the semantics of <code>step-name</code> are different for these commands. <code>step new</code> interprets it as a new name, while <code>step dependency</code> interprets it as an existing name. The first can be renamed to <code>--name</code>, and the second can be <code>--to</code>, as one of its aliases suggests.</p>
<p>🐢 Yes, let’s keep it for now, and we’ll continue to work on others.</p>
<p>🦊 I want to post a comment in the <code>clap</code> discussions.</p>
<p>🐢 Wrote it. Now let’s build it after changing the <code>pipeline_name</code>.</p>
<p>🐇 It compiles. Should we test it?</p>
<p>🐢 I think we’ll test after all this completion work is done.</p>
<p>🐇 We did most of the <code>strum</code>-related completion.</p>
<p>🐢 Didn’t test them yet, though.</p>
<p>🐇 This is Rust. It will work if it compiles, and it compiles.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 14</title>
      <published>2025-01-20T10:55:15+00:00</published>
      <updated>2025-01-20T10:55:15+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 20 Jan 2025 10:55:15 +0000</pubDate>
      <link>https://emresahin.net/devlog-14/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-14/</guid>
      <description>🐢 Now, let’s return to clap and its dynamic completions. Last time you said: 🐇 The examples are only found in the tests: https://github.com/clap-rs/clap/blob/master/clap_complete/tests/testsuite/engine.rs#L604 and we can try to create those random strings this way. 🦊 The issue is that we may not ...</description>
      <category>xvc</category>
      <category>jj</category>
      <category>clap</category>
      <category>clap-complete</category>
      <category>zsh</category>
      <category>gitoxide</category>
      <category>git</category>
      <category>Rust</category>
      <category>dynamic completion</category>
      <category>shell</category>
      <category>development</category>
      <content:encoded><![CDATA[<p>🐢 Now, let’s return to <code>clap</code> and its dynamic completions. Last time you said:</p>
<blockquote>
<p>🐇 The examples are only found in the tests: https://github.com/clap-rs/clap/blob/master/clap_complete/tests/testsuite/engine.rs#L604</p>
</blockquote>
<p>and we can try to create those random strings this way.</p>
<p>🦊 The issue is that we may not be able to use the <code>derive</code> method to add these custom completers. All examples are using the builder API.</p>
<p>🐢 We should be able to obtain the end result from derives and modify them to use these custom completers, but let’s try to use <code>add = ArgValueCompleter</code> first.</p>
<p>🐇 It looks like we have another problem. We need to add a <code>rust-toolchain.toml</code> file to the project, you know, to keep it on the stable channel.</p>
<p>🐢 Ah, yeah, we now use multiple channels. Let’s do that.</p>
<p>🐇 I found <a href="https://docs.rs/clap_complete/latest/clap_complete/engine/struct.ArgValueCompleter.html">an example</a>, actually:</p>
<pre><code class="language-rust">#[derive(Debug, Parser)]
struct Cli {
    #[arg(long, add = ArgValueCompleter::new(custom_completer))]
    custom: Option&lt;String&gt;,
}</code></pre>
<p>🐢 I’m trying to add this to the <code>--from-ref</code> option of Xvc. It’s not running anything. The custom completer should return a random string, but it doesn’t.</p>
<p>🦊 Maybe test with a constant first.</p>
<p>🐢 It didn’t work that way either.</p>
<p>🐇 Let’s do a <code>cargo clean</code>.</p>
<p>🐢 Nothing has changed.</p>
<p>🐇 Let’s take a look at the output of <code>xvc completions</code>:</p>
<pre><code class="language-sh">xvc completions
...
'(--skip-git)--from-ref=[Checkout the given Git reference (branch, tag, commit etc.) before performing the Xvc operation. This runs \`git checkout &lt;given-value&gt;\` before running the command]:FROM_REF:_default' \
...
</code></pre>
<p>🐢 This is the only place <code>--from-ref</code> is mentioned and, as far as I can see, there is nothing that calls a dynamic command here.</p>
<p>🐇 Umm, right. There’s something weird here. Maybe we lack the <code>ArgExt</code> trait or something.</p>
<p>🐢 It should be turned on by <code>clap-complete</code> with its <code>unstable-dynamic</code> feature, but who knows.</p>
<p>🐇 It didn’t work.</p>
<p>🐢 <code>ArgExt</code> is available, but it isn’t being used.</p>
<p>🐇 I think I found the answer in https://jj-vcs.github.io/jj/latest/install-and-setup/#command-line-completion:</p>
<pre><code class="language-sh">source &lt;(COMPLETE=zsh xvc)
</code></pre>
<p>is the command we should use.</p>
<p>🐢 It doesn’t produce a completion script, though.</p>
<p>🐇 We have this in <code>jj/cli/src/cli_util.rs</code>:</p>
<pre><code class="language-rust">        if env::var_os("COMPLETE").is_some() {
            return handle_shell_completion(ui, &amp;self.app, &amp;config, &amp;cwd);
        }</code></pre>
<p>🐢 I think the whole <code>handle_shell_completion</code> set is written by them. I’d like to see what <code>jj</code> with <code>COMPLETE=zsh</code> outputs.</p>
<p>🐇 Building it to see now.</p>
<p>🐢 As expected, it calls a function:</p>
<pre><code class="language-sh">❯ COMPLETE=zsh target/debug/jj
#compdef jj
function _clap_dynamic_completer_jj() {
    local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
    local _CLAP_IFS=$'\n'

    local completions=("${(@f)$( \
        _CLAP_IFS="$_CLAP_IFS" \
        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
        COMPLETE="zsh" \
        /Users/iex/github.com/etc/jj/target/debug/jj -- ${words} 2&gt;/dev/null \
    )}")

    if [[ -n $completions ]]; then
        _describe 'values' completions
    fi
}

compdef _clap_dynamic_completer_jj jj
</code></pre>
<p>🐇 Now we should make it the same; the shell must call <code>xvc</code> to get the completions. That’s how all these will work.</p>
<p>🐢 Our goal now is to make a random string output from the <code>--from-ref</code> completion.</p>
<p>🐇 The plan is to make completions work as quickly as possible.</p>
<p>🐢 We have done it. 🎉🥳</p>
<p>🐇 Cool. Now we need to fill up all those completion methods.</p>
<p>🦊 Yeah. We can start with basic ones and move from there.</p>
<p>🐢 There will probably be architectural changes as well. We cannot just run <code>xvc</code> functions directly. We need to run internal functions, but not through commands. At that point, when the user hits tab, we don’t know which command to run.</p>
<p>🐇 Maybe it’s time to move to a Git library. “What is the best Git library for Rust?”</p>
<p>🐲 Should we use a Git library?</p>
<p>🐢 It looks like <a href="https://github.com/GitoxideLabs/gitoxide">Gitoxide</a> is the default Rust way to interact with Git repositories. We can keep our way of using the Git binary and use this as an experimental way to learn.</p>
<p>🦊 Yep. That’s a good idea. We can include it in the lib for the time being and start to use it in completions.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 13</title>
      <published>2025-01-19T09:55:05+00:00</published>
      <updated>2025-01-19T09:55:05+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 19 Jan 2025 09:55:05 +0000</pubDate>
      <link>https://emresahin.net/devlog-13/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-13/</guid>
      <description>🐇 So, what’s next? 🐲 We can work on completions or the GUI. 🐢 Completions are similar to the Homebrew work. It’s in another ecosystem and feels boring. 🐲 We can try to make it in Rust: “Writing shell completions in Rust.” 🐢 There are two solutions: One is clap_complete (https://docs.rs/clap_compl...</description>
      <category>xvc</category>
      <category>completions</category>
      <category>clap_complete</category>
      <category>subcommands</category>
      <category>xvc pipeline step dependency</category>
      <category>xvc aliases</category>
      <category>Rust</category>
      <category>rust-analyzer</category>
      <category>shell completion</category>
      <category>CLI</category>
      <category>auto-save.nvim</category>
      <content:encoded><![CDATA[<p>🐇 So, what’s next?</p>
<p>🐲 We can work on completions or the GUI.</p>
<p>🐢 Completions are similar to the Homebrew work. It’s in another ecosystem and feels boring.</p>
<p>🐲 We can try to make it in Rust: “Writing shell completions in Rust.”</p>
<p>🐢 There are two solutions: One is <code>clap_complete</code> (https://docs.rs/clap_complete/latest/clap_complete/) and the other is <code>shell_completion</code> (https://github.com/JoshMcguigan/shell_completion). The latter is very new but may be what we need: writing completions only with Rust.</p>
<p>🐲 The <code>shell_completion</code> crate is very bare-bones. It doesn’t have anything, actually. https://github.com/JoshMcguigan/shell_completion/issues/1</p>
<p>🐇 The feature that we need is dynamic completion. We’ll write something similar to <a href="https://docs.rs/clap_complete/latest/clap_complete/engine/struct.ArgValueCompleter.html"><code>ArgValueCompleter</code></a> for this.</p>
<p>🦊 I think we can just start a new branch.</p>
<p>🐲 We have a branch for Homebrew; should we merge it?</p>
<p>🐢 I think, yes, we can merge it. We’ll fix it if it breaks. It’s a separate workflow file anyway.</p>
<p>🦊 Created the PR.</p>
<pre><code class="language-sh">ghpl
264	add brew tap	add-brew-tap	OPEN	2025-01-03T05:14:45Z
</code></pre>
<p>🐢 Merged it.</p>
<p>🐇 Now let’s create a new branch and add <code>clap-complete</code> to <code>Cargo.toml</code>.</p>
<p>🐲 There are some packages that we need to take a look at. <code>thiserror</code> now has v2.</p>
<p>🐢 Upgraded packages and compiled. It works.</p>
<p>🐲 Let’s update the version.</p>
<p>🐢 Done. Now we can add <code>clap-complete</code>.</p>
<p>🦊 Added with the <code>unstable-dynamic</code> command. Can we build it again?</p>
<p>🐢 Built it. Now we can add a <code>completions</code> subcommand to Xvc.</p>
<p>🐲 Is this the right name for this?</p>
<p>🐢 It will output a shell script that we can source in the shell.</p>
<p>🐲 Okay, let’s add the subcommand now.</p>
<p>🐢 The example doesn’t work with the <code>clap</code> builder.</p>
<p>🐇 Let’s search for it.</p>
<hr>
<p>🐢 I think we’re extending ourselves a bit when trying to add all dynamic features of completion at once. We can just start by adding completion to <code>xvc-test-helper</code>.</p>
<p>🐇 Yep, that’s a better idea. We can have many more examples, and it’s certainly more straightforward.</p>
<p>🐢 One thing I noticed when working yesterday is that using auto-save was actually preventing many of these swap errors.</p>
<p>🐲 It was a bit slow. Can we take a look at a few others?</p>
<p>🐢 https://github.com/okuuva/auto-save.nvim seems a bit more polished.</p>
<p>🐇 Installed it, but haven’t seen an effect yet.</p>
<p>🐢 We may need to restart this.</p>
<p>🐲 Now, we can get into the <code>test-helper</code> completions.</p>
<p>🐢 Done. Added these quickly as you thought. Now adding this to <code>main</code> seems much easier.</p>
<p>🦊 That was a nice approach. Let’s dive into adding this to <code>main</code>.</p>
<p>🐇 Should we go with a separate command or just an option?</p>
<p>🐲 Our commands have distinct initial letters, allowing them to be used with just those letters. Adding another top-level command for this will make <code>c</code> useless.</p>
<p>🐢 I don’t think that’s a valid concern. We can create aliases for all commands and <code>completions</code> shouldn’t have to have an alias. But I agree that completions should not be a top-level command. It will be run once for installation at most.</p>
<p>🦊 I agree. Let’s call it <code>--completions</code>. It will be run as <code>xvc --completions zsh</code> and will print out the completions.</p>
<p>🐢 Okay. Let’s do this.</p>
<p>🦊 No errors left. Let’s install this version.</p>
<p>🐢 We get:</p>
<pre><code class="language-sh">error: 'xvc' requires a subcommand but one was not provided
  [subcommands: file, init, pipeline, storage, root, check-ignore, aliases, help]

Usage: xvc [OPTIONS] &lt;COMMAND&gt;

For more information, try '--help'.
</code></pre>
<p>🦊 Umm, okay. I think we don’t have an option to create a command like that. Then we’ll have to print completions with a subcommand.</p>
<p>🐲 The above discussion is now moot. ❌</p>
<p>🐢 We can try to push, but probably it’s not worth it.</p>
<p>🐲 Let’s not lose time on this. I think we can remove the <code>aliases</code> command and replace it with <code>completions</code>, and add single-letter aliases to subcommands.</p>
<p>🐢 We can have an option in the <code>completions</code> command to print aliases. That will work.</p>
<p>🦊 I don’t think people will use the <code>aliases</code> command if we have single-letter command aliases.</p>
<p>🐢 You may be right. No need to try to maintain that at this time.</p>
<hr>
<p>🐢 Good morning, and I’m fed up with these <code>blink.cmp</code> errors, you know.</p>
<p>🐇 Reinstalling <code>blink</code> works. It’s interesting to rely on such unreliable software.</p>
<p>🐢 Oh, yeah. It’s <em>interesting</em>. Where were we yesterday?</p>
<p>🐲 We decided to rename the <code>aliases</code> command to <code>completions</code>.</p>
<p>🐇 And waiting for <code>rust-analyzer</code> to complete its analysis.</p>
<p>🐢 Uh, yeah. I see.</p>
<p>🦊 The code itself is very short, actually.</p>
<pre><code class="language-rust">    if let Some(shell) = cli_opts.completions {
        let mut cmd = XvcCLI::command();
        generate(shell, &amp;mut cmd, "xvc", &amp;mut io::stdout());
        return Ok(None);
    }</code></pre>
<p>🐲 We’ll use the <code>output!</code> macro instead of writing to <code>io::stdout()</code>, right?</p>
<p>🐢 Yes, we can rely on the usual output system. It will be slower to create an output thread but shouldn’t matter for outputting a shell script.</p>
<p>🐇 It takes a while for <code>rust-analyzer</code> to scan all the directories, it looks like. When I close the project, it just cannot reload it immediately.</p>
<p>🐢 We can use <code>cargo clean</code> from time to time.</p>
<p>🦊 <code>rust-analyzer</code> finished scanning; let’s try to rename <code>AliasesCLI</code>.</p>
<p>🐲 Let’s keep these here; maybe we’ll need them in our scripts:</p>
<pre><code class="language-sh"># Standard Xvc command aliases for longer commands.
alias xls='xvc file list'
alias pvc='xvc pipeline'
alias fvc='xvc file'
alias xvcf='xvc file'
alias xvcft='xvc file track'
alias xvcfl='xvc file list'
alias xvcfs='xvc file send'
alias xvcfb='xvc file bring'
alias xvcfh='xvc file hash'
alias xvcfco='xvc file checkout'
alias xvcfr='xvc file recheck'
alias xvcp='xvc pipeline'
alias xvcpr='xvc pipeline run'
alias xvcps='xvc pipeline step'
alias xvcpsn='xvc pipeline step new'
alias xvcpsd='xvc pipeline step dependency'
alias xvcpso='xvc pipeline step output'
alias xvcpi='xvc pipeline import'
alias xvcpe='xvc pipeline export'
alias xvcpl='xvc pipeline list'
alias xvcpn='xvc pipeline new'
alias xvcpu='xvc pipeline update'
alias xvcpd='xvc pipeline dag'
alias xvcs='xvc storage'
alias xvcsn='xvc storage new'
alias xvcsl='xvc storage list'
alias xvcsr='xvc storage remove'
</code></pre>
<p>🦊 We can use them when adding single-letter aliases.</p>
<p>🐇 There is a <a href="https://docs.rs/clap_complete/latest/clap_complete/aot/enum.Shell.html#method.from_env"><code>from_env</code></a> method for <code>Shell</code>; will we support it?</p>
<p>🦊 I think we can support it. It’s much easier to use if we omit the shell.</p>
<p>🐲 We had <code>aliases</code> in <code>xvc-core</code>, but we need to access <code>XvcCLI</code> from completions. The completions module must be moved to <code>xvc</code>.</p>
<p>🦊 We added the <code>clap_complete</code> dependency to the <code>xvc-pipeline</code> and <code>xvc-file</code> crates, but I don’t think they are necessary. Let’s remove them now.</p>
<p>🐢 Now only the <code>test-helper</code> and <code>xvc</code> crates have the <code>clap_complete</code> dependency.</p>
<p>🐇 Are we ready to test?</p>
<p>🐢 Completions are working. 🎉</p>
<p>🐲 Now we need to update the docs and doc tests, I believe.</p>
<p>🐢 There are also tests to update.</p>
<p>🐇 Tests are running now. In the meantime, can we take a look at the <code>blink</code> configuration?</p>
<p>🐲 When we removed <code>xvc aliases</code>, we also removed <code>pvc</code>, <code>xls</code>, and other aliases. Maybe we can add these to the docs.</p>
<p>🐢 We can put them in the <code>xvc completions</code> reference for now.</p>
<hr>
<p>🐢 It takes a while for <code>rust-analyzer</code> to finish analyzing the codebase.</p>
<p>🦊 Added aliases for <code>xvc pipeline</code> commands. Do you think we need to repeat root-level flags in <code>xvc-pipeline</code>?</p>
<p>🐢 No need to divert attention, I believe. Also, I still think there is an easier way to do that.</p>
<p>🐇 Okay. Do you think we should add easier subcommands to <code>step</code>? Like, <code>step new</code> becoming <code>xvc p s n</code> instead of <code>xvc p s n</code>? (Wait, that’s the same). I mean, more concise.</p>
<p>🐢 I think we can extend these even to the top-level, but shouldn’t make them visible. It will pollute the help text. We can add <code>xvc fl</code> for <code>file list</code> to avoid the space, but hide these from the help text.</p>
<p>🐲 That’s a good idea, but it will require including sub-crate level modules at the top level. We must test it first.</p>
<p>🐢 Let’s finish up the current changes and release them first.</p>
<p>🐇 By the way, we didn’t add two-letter abbreviations to the <code>xvc storage new</code> subcommands. Do you think we need them?</p>
<p>🐢 When we think about the frequency of these commands, no, I don’t think we need them. Users won’t add a new storage every day.</p>
<p>🐇 By that logic, we shouldn’t need an <code>n</code> for <code>xvc storage new</code>.</p>
<p>🐢 Actually, yeah. Maybe we should remove even <code>s</code>.</p>
<p>🦊 <code>storage list</code> may be useful.</p>
<p>🐢 <code>s</code> is a very common letter, though. We can have other uses for that letter.</p>
<p>🐇  Updating <code>xvc p s dependency</code> options, but it looks like making dependency options subcommands is a better way.</p>
<p>🦊 We didn’t do it because currently we can supply multiple options with a single command. If we go the subcommand route, we’ll have to write all dependencies one by one. Adding multiple subcommands with <code>clap</code> is a bit tricky.</p>
<p>🐢 Umm. Yeah, I see.</p>
<p>🐇 Maybe we can provide a separate command to add multiple dependencies. Like, <code>xvc p s d add 'lines=myfile.csv::10-20; file=myimage.jpg; glob=dir/image-10*'</code></p>
<p>🐲 That looks like the start of a language. We need a parser for those strings. They will be freeform.</p>
<p>🦊 Another option is to keep the current options and add commands with the same names.</p>
<p>🐢 That will be confusing. The user will have both <code>--param</code> and <code>param</code>, and they will work differently.</p>
<p>🐇 We can have an <code>add</code> command that accepts the current options. Parsing will be done by <code>clap</code> just like now, but for a subcommand of <code>dependency</code>.</p>
<p>🐢 The full command will be something like <code>xvc pipeline step --step-name preprocessing dependency add --params 'params.json::batch_size'</code></p>
<p>🐇 With shorter commands, it’s like <code>xvc p s -s preprocessing d a --params 'params.json::batch_size'</code>, and this doesn’t look like <code>ffmpeg</code> monstrosities.</p>
<p>🐲 In any case, this is a backward-incompatible change. This should wait for v0.7 along with ECS changes.</p>
<p>🐢 ECS changes are not user-visible, but these are. Certainly needs a minor version update.</p>
<p>🐇 Then, okay, let’s keep the current ones for this version and think about updating them in a future version.</p>
<p>🐢 Okay. Let’s finish up and we’ll create a separate invisible subcommand to ask questions to the Xvc repo for completions.</p>
<p>🐇 We completed completions for the common command structure. Now, we need a way to show certain info after certain commands. A tab after <code>xvc p r -p</code> should show pipeline names, for example.</p>
<p>🐢 Umm, yeah. And these should be as quick as possible. They shouldn’t check Git or any other things.</p>
<p>🐇 I looked here and there, and the only working example I found is here: https://github.com/clap-rs/clap/blob/master/clap_complete/tests/testsuite/zsh.rs#L248 in <code>clap_complete</code> tests.</p>
<p>🐢 Let’s try to dive in. We can start by cloning the repo, I believe.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
