<?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/categories/devlog/</link>
    <description>Posts in the devlog category</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/categories/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 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 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 11: Homebrew Taps and Automation</title>
      <published>2025-01-19T09:37:37+00:00</published>
      <updated>2025-01-19T09:37:37+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 19 Jan 2025 09:37:37 +0000</pubDate>
      <link>https://emresahin.net/devlog-11/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-11/</guid>
      <description>🐢 Let’s move slowly. Let’s create a brew project first. 🦊 There is a brew create command, but could we really copy it from someone else? 🐢 I read the espanso example , and I’ll start by adding a repository. Welcome https://github.com/iesahin/homebrew-xvc 🦊 We can search to automate this for Rust ...</description>
      <category>Devlog</category>
      <category>XVC</category>
      <category>Homebrew</category>
      <category>homebrew</category>
      <category>espanso</category>
      <category>ripgrep</category>
      <category>gh</category>
      <category>wget</category>
      <category>github-actions</category>
      <category>rust</category>
      <content:encoded><![CDATA[<p>🐢 Let’s move slowly. Let’s create a brew project first.</p>
<p>🦊 There is a <code>brew create</code> command, but could we really copy it from someone else?</p>
<p>🐢 I read the <a href="https://federicoterzi.com/blog/how-to-publish-your-rust-project-on-homebrew/">espanso example</a>, and I’ll start by adding a repository. Welcome https://github.com/iesahin/homebrew-xvc</p>
<p>🦊 We can search to automate this for Rust stuff first. Maybe it’s easier to automate than to do it manually with examples.</p>
<p>🐢 Let’s search “how to automate homebrew tap formula updates with github actions”</p>
<p>🐲 There is a <a href="https://josh.fail/2023/automate-updating-custom-homebrew-formulae-with-github-actions/">shell script</a> that updates the brew repo with a shell script. It fires the action from the main repository with a command like:</p>
<pre><code>gh workflow run release.yml -f version=${{ env.VERSION }} -R itspriddle/homebrew-slack-notify
</code></pre>
<p>🐢 It looks a little brittle, though.</p>
<p>🐲 There is also a GitHub action: https://github.com/marketplace/actions/homebrew-tap but it doesn’t look very popular. There is another one https://github.com/marketplace/actions/bump-homebrew-formula but this is for formulas, or the default settings are those.</p>
<p>🐢 There is an example https://github.com/marketplace/actions/bump-homebrew-formula#examples for taps as well.</p>
<p>🐲 This one is simpler, and probably we can just add this first: https://github.com/marketplace/actions/homebrew-bump-formula Most of the fields are optional.</p>
<p>🐇 I think this is much easier than the other. Let’s start with this.</p>
<p>🐢 Now let’s add a token to the Xvc repo. ✅</p>
<p>🦊 Add a branch to Xvc and add the release action file.</p>
<p>🐢 Added the release action file <code>~/github.com/iesahin/xvc/.github/workflows/homebrew.yml</code>, but we still don’t have a tap; maybe we can just add one.</p>
<p>🦊 Can you check espanso’s for example?</p>
<p>🐢 It has a cask and is in the core. We need a tap example.</p>
<p>🐲 I believe we need to clone <code>homebrew-xvc</code> and run <code>gh workflow run</code>.</p>
<p>🐢 Yes, let’s clone the repo and try to run it manually.</p>
<p>🐇 We don’t have a formula yet.</p>
<p>🦊 What’s the directory structure of a Homebrew tap project?</p>
<p>🐢 It’s something like this, but we don’t need tests for this, I believe.</p>
<pre><code>.
├── Formula
│   └── &lt;formula_name&gt;.rb
├── LICENSE
├── README.md
└── .github
    └── workflows
        └── tests.yml
</code></pre>
<p>🐇 What goes into the Formula?</p>
<p>🦊 Let’s take a look at ripgrep’s example:</p>
<pre><code class="language-ruby">class Ripgrep &lt; Formula
  desc "Search tool like grep and The Silver Searcher"
  homepage "https://github.com/BurntSushi/ripgrep"
  url "https://github.com/BurntSushi/ripgrep/archive/refs/tags/14.1.1.tar.gz"
  sha256 "4dad02a2f9c8c3c8d89434e47337aa654cb0e2aa50e806589132f186bf5c2b66"
  license "Unlicense"
  head "https://github.com/BurntSushi/ripgrep.git", branch: "master"

  livecheck do
    url :stable
    strategy :github_latest
  end

  bottle do
    sha256 cellar: :any,                 arm64_sequoia:  "b8bf5e73c9c9b441de067ec86ac167b071ecc2078dcb1d89d2cebbb151feab35"
    sha256 cellar: :any,                 arm64_sonoma:   "47b9c3515c866b147f0e98735cab165d6471b9f28fab1ba2c57e59c43da5c10b"
    sha256 cellar: :any,                 arm64_ventura:  "e14a94e84c028ff53c1be3b106fdeb5aca4d7c893a819e7fb967e0719b946a28"
    sha256 cellar: :any,                 arm64_monterey: "ad8dc4ab475c84e2a1e60f5b3107f52dd59e33f84a08284b19681d8b98508fd7"
    sha256 cellar: :any,                 sonoma:         "71d434eeabc2af220285b037f7264563ce9bc77a41af35eabe2213276a37ec2b"
    sha256 cellar: :any,                 ventura:        "0cdb547c696992d08c6613c40934218964f4a061b5413c4b2f013c3f0c3ed253"
    sha256 cellar: :any,                 monterey:       "2ce54302e4524ad28389aca5a16333d4193128e911de2881e6b0e953559d89cd"
    sha256 cellar: :any_skip_relocation, x86_64_linux:   "97d7cbd33b4d0ed09551e3dbc07f830d3df018c2aefbb2222a12ccfb829aae30"
  end

  depends_on "asciidoctor" =&gt; :build
  depends_on "pkgconf" =&gt; :build
  depends_on "rust" =&gt; :build
  depends_on "pcre2"

  def install
    system "cargo", "install", "--features", "pcre2", *std_cargo_args

    generate_completions_from_executable(bin/"rg", "--generate", shell_parameter_format: "complete-")
    (man1/"rg.1").write Utils.safe_popen_read(bin/"rg", "--generate", "man")
  end

  test do
    (testpath/"Hello.txt").write("Hello World!")
    system bin/"rg", "Hello World!", testpath
  end
end
</code></pre>
<p>🐇 What’s in that tar file?</p>
<pre><code>wget https://github.com/BurntSushi/ripgrep/archive/refs/tags/14.1.1.tar.gz 
--2025-01-03 06:28:07--  https://github.com/BurntSushi/ripgrep/archive/refs/tags/14.1.1.tar.gz
Resolving github.com (github.com)... 140.82.121.3
Connecting to github.com (github.com)|140.82.121.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/BurntSushi/ripgrep/tar.gz/refs/tags/14.1.1 [following]
--2025-01-03 06:28:08--  https://codeload.github.com/BurntSushi/ripgrep/tar.gz/refs/tags/14.1.1
Resolving codeload.github.com (codeload.github.com)... 140.82.121.10
Connecting to codeload.github.com (codeload.github.com)|140.82.121.10|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/x-gzip]
Saving to: ‘14.1.1.tar.gz’

     0K .......... .......... .......... .......... ..........  355K
    50K .......... .......... .......... .......... .......... 1.05M
   100K .......... .......... .......... .......... .......... 1.35M
   150K .......... .......... .......... .......... .......... 3.61M
   200K .......... .......... .......... .......... ..........  876K
   250K .......... .......... .......... .......... .......... 6.50M
   300K .......... .......... .......... .......... .......... 1.15M
   350K .......... .......... .......... .......... .......... 1.65M
   400K .......... .......... .......... .......... .......... 4.88M
   450K .......... .......... .......... .......... .......... 1.22M
   500K .......... .......... .......... .......... .......... 1.56M
   550K .......... .......                                     3.62M=0.5s

2025-01-03 06:28:09 (1.21 MB/s) - ‘14.1.1.tar.gz’ saved [581402]

mv 14.1.1.tar.gz $HOME/Downloads/ripgrep-14.1.1.tar.gz

tar xvzf $HOME/Downloads/ripgrep-14.1.1.tar.gz 
</code></pre>
<p>🐢 It’s a source distribution. It doesn’t contain any binaries.</p>
<p>🦊 We can use the same URL format, it looks. Let’s try this:</p>
<pre><code>wget https://github.com/iesahin/xvc/archive/refs/tags/0.6.13.tar.gz 
--2025-01-03 06:34:00--  https://github.com/iesahin/xvc/archive/refs/tags/0.6.13.tar.gz
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/iesahin/xvc/tar.gz/refs/tags/0.6.13 [following]
--2025-01-03 06:34:01--  https://codeload.github.com/iesahin/xvc/tar.gz/refs/tags/0.6.13
Resolving codeload.github.com (codeload.github.com)... 140.82.121.9
Connecting to codeload.github.com (codeload.github.com)|140.82.121.9|:443... connected.
HTTP request sent, awaiting response... 404 Not Found
2025-01-03 06:34:01 ERROR 404: Not Found.
</code></pre>
<p>🐢 Our source should be in another location.</p>
<p>🐇 We had an unpublished release. Didn’t we release the binaries a few days ago?</p>
<p>🐢 Umm, there must be something about the <code>gh</code> command where we set <code>draft=false</code>, but seemingly it didn’t work out.</p>
<p>🐇 Ok, the URL is something like:</p>
<pre><code>wget https://github.com/iesahin/xvc/archive/refs/tags/v0.6.13.tar.gz 
</code></pre>
<p>🐢 Ok, this works. We’ll use this one for the URL.</p>
<p>🐇 What should we put for SHA256?</p>
<p>🦊 Let’s check ripgrep’s again.</p>
<p>🐢 Downloading and using <code>xvc file hash</code> (alias <code>xvcfh</code>):</p>
<pre><code>wget https://github.com/BurntSushi/ripgrep/archive/refs/tags/14.1.1.tar.gz 
...

xvc file hash -a sha2 14.1.1.tar.gz
4dad02a2f9c8c3c8d89434e47337aa654cb0e2aa50e806589132f186bf5c2b66	14.1.1.tar.gz
</code></pre>
<p>🐢 Yep, this matches the source.</p>
<p>🦊 Then we can just use the same.</p>
<p>🐇 I want to learn how to download to <code>$TMPDIR</code> with <code>wget</code>.</p>
<p>🐢 The <code>-P</code> option is used for this.</p>
<pre><code class="language-sh">wget -P $TMPDIR https://github.com/iesahin/xvc/archive/refs/tags/v0.6.13.tar.gz 
--2025-01-03 06:55:02--  https://github.com/iesahin/xvc/archive/refs/tags/v0.6.13.tar.gz
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/iesahin/xvc/tar.gz/refs/tags/v0.6.13 [following]
--2025-01-03 06:55:03--  https://codeload.github.com/iesahin/xvc/tar.gz/refs/tags/v0.6.13
Resolving codeload.github.com (codeload.github.com)... 140.82.121.9
Connecting to codeload.github.com (codeload.github.com)|140.82.121.9|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/x-gzip]
Saving to: ‘/var/folders/gf/18gghtz15_zf0cr8v6dymglw0000gn/T/v0.6.13.tar.gz’
...
2025-01-03 06:55:07 (2.59 MB/s) - ‘/var/folders/gf/18gghtz15_zf0cr8v6dymglw0000gn/T/v0.6.13.tar.gz’ saved [8785449]
</code></pre>
<pre><code class="language-sh">xvcfh -a sha2 $TMPDIR/v0.6.13.tar.gz
01bee5d840eefec7be1f52cc75546e1ffd7e332dfac83d875655f84e61e3a9f6	/var/folders/gf/18gghtz15_zf0cr8v6dymglw0000gn/T//v0.6.13.tar.gz
</code></pre>
<p>🐇 According to documentation, bottles are produced by Brew itself, and the documentation around bottling taps is limited.</p>
<p>🦊 We can just start with the source distribution. We can test it now.</p>
<p>🐢 The source distribution seems to work, but it requires Rust, and Brew downloads everything related to it.</p>
<p>🐇 I think that may be enough for the time being. With 0.6.14, we can work on providing bottles.</p>
<p>🐢 Umm, yes. I’m already bored with this stuff.</p>]]></content:encoded>
    </item>
    <item>
      <title>Devlog 10: Codecov, Doctests, and Cargo Publish Struggles</title>
      <published>2025-01-19T09:30:49+00:00</published>
      <updated>2025-01-19T09:30:49+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 19 Jan 2025 09:30:49 +0000</pubDate>
      <link>https://emresahin.net/devlog-10/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-10/</guid>
      <description>🐢 Now we’re going into the real deep work. The only failure was the Codecov patch in Xvc. Let’s see what needs to be done. 🐇 It looks, from the coverage page , that our additions to HStore don’t have any tests. We can add some unit tests to new joins, maybe. 🐢 I don’t find unit tests particularly...</description>
      <category>Devlog</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>cargo</category>
      <category>ecs</category>
      <category>Claude</category>
      <category>cargo-publish</category>
      <category>crates.io</category>
      <category>sqlite</category>
      <category>codecov</category>
      <category>doctests</category>
      <content:encoded><![CDATA[<p>🐢 Now we’re going into the real deep work. The only failure was the Codecov patch in Xvc. Let’s see what needs to be done.</p>
<p>🐇 It looks, from the <a href="https://app.codecov.io/gh/iesahin/xvc/pull/263?src=pr&amp;el=tree&amp;utm_medium=referral&amp;utm_source=github&amp;utm_content=comment&amp;utm_campaign=pr+comments&amp;utm_term=Emre+Sahin">coverage page</a>, that our additions to HStore don’t have any tests. We can add some unit tests to new joins, maybe.</p>
<p>🐢 I don’t find unit tests particularly useful, but let’s use GitHub Copilot to add tests for us.</p>
<p>🐇 Added a unit test and a doc test for <code>full_join</code>, and I think doc tests have more value. They provide documentation, and we can readily see how to use a function from its docs. Better to increase coverage with doc tests.</p>
<p>🐢 There are points that I should learn while writing doc tests. The imports must use the full path, not <code>crate::</code>. The tested struct also doesn’t have implicit imports.</p>
<p>🐇 The ceremony of adding keys and values is a bit too much. It may be worthwhile to add <code>insert</code> for any <code>Into&lt;XvcEntity&gt;</code>.</p>
<p>🐢 It will certainly save time if we don’t have to type <code>.into()</code> for each key :)</p>
<p>🐇 Pushed to test again. Should we have some means to test coverage locally?</p>
<p>🐢 I don’t think we need to consider coverage locally. It’s not worth our time.</p>
<p>🐇 Now while waiting for tests to be completed, what can we do?</p>
<pre><code>gh -R iesahin/xvc run list
completed	failure	v0.6.13	Rust-CI	v0.6.13	pull_request	12543831360	3m54s	2024-12-30T08:15:06Z
...
</code></pre>
<p>🐢 I don’t think it took too much time. Let’s view the results:</p>
<pre><code>gh -R iesahin/xvc run view 12543831360
...
  X Run Current Dev Tests
...
To see what failed, try: 
View this run on GitHub: https://github.com/iesahin/xvc/actions/runs/12543831360
</code></pre>
<p>🐢 The current dev tests fail for some reason. Let’s run these locally.</p>
<p>🐇 We’re missing <code>llvm-tools-preview</code> locally. How do we install this?</p>
<p>🐢 The command is:</p>
<pre><code>rustup component add llvm-tools-preview
info: component 'llvm-tools' for target 'aarch64-apple-darwin' is up to date
</code></pre>
<p>🐇 It’s already installed. We need to set the environment variables.</p>
<p>🐢 Instead, we can just turn off dev tests for the time being. We don’t need them. Our local tests pass.</p>
<p>🐇 Yeah, ok, we don’t need to solve each and every bit of these issues.</p>
<pre><code>gh -R iesahin/xvc run list
</code></pre>
<p>…
🐇 Ok. Let’s take a look at the run again.</p>
<pre><code>gh -R iesahin/xvc run list
completed	failure	v0.6.13	Rust-CI	v0.6.13	pull_request	12544020064	3m39s	2024-12-30T08:33:25Z

gh -R iesahin/xvc run view 12544020064
...
  X Test and Coverage
...

gh run view 12544020064 --log-failed
...
Test and Coverage (stable)	Test and Coverage	2024-12-30T08:36:58.8911690Z Error: ProcessError { stdout: "", stderr: "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (7) Failed to connect to e1.xvc.dev port 80 after 160 ms: Couldn't connect to server\n" }
...
</code></pre>
<p>🐢 We need to start <code>nginx</code> on the server. We forgot it yesterday.</p>
<p>🐇 Ah, yeah. After adding that dufs installation. Ok.</p>
<p>🐢 We also need to add a reverse proxy to dufs somehow, but this is for later.</p>
<p>🐇 For the use case, I don’t think it’s necessary. We can just adjust the port to a non-standard one if we need to use 443 for another thing, but let’s take a look at the tests again.</p>
<p>🐢 Let’s add another doc test. This time to <code>XvcStore</code>.</p>
<pre><code>cargo test -p xvc-ecs --doc
...
test result: ok. 8 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 2.87s
</code></pre>
<p>🐇 Sent the files again. Waiting for tests to finish.</p>
<p>🐢 Let’s check the keymaps file in the meantime.</p>
<p>🐇 I tried to read some documentation but didn’t see an error. Maybe we should just set it to non-lazy and avoid allowing remaps.</p>
<p>🐢 We already spent too much time with this.</p>
<p>🐇 Yes, let’s take a look at the tests again.</p>
<pre><code>gh -R iesahin/xvc run list
completed	success	v0.6.13	Rust-CI	v0.6.13	pull_request	12544291068	8m10s	2024-12-30T09:00:42Z
...
</code></pre>
<p>🐢 Oh, yeah, the merge is ready.</p>
<p>🐇 Patch coverage is still behind the target, though.</p>
<p>🐢 Yeah, but let’s release this one and make the next better covered. Also, I’m not sure if the doc tests had any effect on coverage.</p>
<p>🐇 If we look at the <a href="https://app.codecov.io/gh/iesahin/xvc/pull/263?src=pr&amp;el=tree&amp;utm_medium=referral&amp;utm_source=github&amp;utm_content=comment&amp;utm_campaign=pr+comments&amp;utm_term=Emre+Sahin">coverage page</a> again, we can see if the doc tests had any effect.</p>
<p>🐢 It seems codecov.io doesn’t consider coverage for doc tests. This is a bit weird, but let’s not spend more time on this.</p>
<p>🐇 Sure, let’s merge.</p>
<p>🐢 I think we forgot to bump the version in <code>Cargo.toml</code>. We’ll have to do that in main.</p>
<p>🐇 Oh, yeah. Let’s bump it and tag as well.</p>
<p>🐢 Now, we can wait for all files to be produced. What will we do next?</p>
<p>🐇 We can just release the Python version as well. It shouldn’t need any changes.</p>
<p>🐢 Umm, right. Maybe we can add a few tests as well.</p>
<p>🐇 Let’s bump the version first and see.</p>
<p>🐢 Bumped versions in <code>Cargo.toml</code> and ran <code>maturin develop</code>.</p>
<p>🐇 It seems ready now.</p>
<p>🐢 The main fails, though. The “Publish Crates” action looks for <code>libsqlite3</code>. Let’s take a look at it.</p>
<pre><code>gh -R iesahin/xvc run list
completed	failure	Release v0.6.13	Publish Crates	v0.6.13	push	12544753359	6m1s	2024-12-30T09:41:56Z
</code></pre>
<p>🐢 The issue is that the VM doesn’t have <code>libsqlite3-dev</code>. Let’s add it.</p>
<p>🐇 We need to start the job manually again. Let’s not tag this time.</p>
<p>🐢 Some of the packages were already published. Now they break. <code>crates.io</code> says they’re already published. Maybe we can check if a package is already published.</p>
<p>🐇 Let’s check if we can make <code>cargo publish</code> more forgiving.</p>
<p>🐢 There doesn’t seem to be an option. Let’s search “how to skip published packages in the workspace to avoid errors with cargo publish”.</p>
<p>🐇 Claude is bullshitting again. Let’s try a manual approach: how to skip already published packages.</p>
<p>🐢 It may be easier to just add a check if the package is published. How do we get the info?</p>
<p>🐇 Or we can just go on to the next package if the package is already available.</p>
<p>🐢 Let’s do this manually this time.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 9</title>
      <published>2025-01-04T13:03:16+00:00</published>
      <updated>2025-01-04T13:03:16+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 04 Jan 2025 13:03:16 +0000</pubDate>
      <link>https://emresahin.net/devlog-9/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-9/</guid>
      <description>🐢 Today I’m planning to add cross-compilation to the Xvc 0.6.13 branch to provide more platform support. 🐇 It looks like you first need to turn off this ghost text from the blink output. It makes writing insufferable. 🐢 Yep, let’s do that first. 🐇 Now, let’s restart Neovim. 🐢 I don’t know why bli...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>blink-cmp</category>
      <category>github-cli</category>
      <category>github-actions</category>
      <category>xvc</category>
      <category>reflinks</category>
      <category>cross-compilation</category>
      <category>rust</category>
      <category>ci-cd</category>
      <content:encoded><![CDATA[<p>🐢 Today I’m planning to add cross-compilation to the Xvc 0.6.13 branch to provide more platform support.</p>
<p>🐇 It looks like you first need to turn off this ghost text from the <code>blink</code> output. It makes writing insufferable.</p>
<p>🐢 Yep, let’s do that first.</p>
<p>🐇 Now, let’s restart Neovim.</p>
<p>🐢 I don’t know why <code>blink.cmp</code> doesn’t prioritize the emojis I use. Maybe we can just use <code>#tor</code> and <code>#rab</code> for ourselves.</p>
<p>🐇 I think over time it will learn that the emojis I defined in the snippets file should have higher priority, but let’s skip this for now. What do we need to do to add cross-compilation?</p>
<p>🐢 Maybe we can just make the completion menu wait a bit longer. It shows up almost instantly, and I want it to wait for a few more milliseconds.</p>
<p>🐇 Okay, let’s look at the config.</p>
<p>🐢 The configuration file doesn’t seem to have a key for this. Let’s search: <code>blink.cmp</code>.</p>
<p>🐇 I think the culprit is typo resistance; it causes better options to be pushed down: <a href="https://cmp.saghen.dev/configuration/reference#fuzzy">https://cmp.saghen.dev/configuration/reference#fuzzy</a></p>
<p>🐢 Let’s turn that off.</p>
<p>🐇 There is an error in the configuration file. I don’t know why it’s failing.</p>
<p>🐢 I added emojis to Espanso and checked the error message. The configuration I copied from their docs seems to be broken. The message is:</p>
<pre><code class="language-text">...share/nvim/lazy/blink.cmp/lua/blink/cmp/config/utils.lua:14: fuzzy.max_items: unexpected field found in configuration
</code></pre>
<p>I’ll just delete that line.</p>
<p>🐇 It still doesn’t prioritize snippets, but we can look into this later. Espanso seems to be a better tool for this anyway.</p>
<p>🐢 Yep. Let’s look into cross-compilation support for Rust.</p>
<p>🐇 The well-known option is <code>cross.rs</code>: <a href="https://github.com/cross-rs/cross">https://github.com/cross-rs/cross</a></p>
<p>🐢 We can start with that. Installation is from the Git repository:</p>
<pre><code class="language-bash">$ cargo install cross --git https://github.com/cross-rs/cross
    Updating git repository `https://github.com/cross-rs/cross`
    Updating git submodule `https://github.com/cross-rs/cross-toolchains.git`
  Installing cross v0.2.5 (https://github.com/cross-rs/cross#4090beca)
...
   Installed package `cross v0.2.5 (https://github.com/cross-rs/cross#4090beca)` (executables `cross`, `cross-util`)
</code></pre>
<p>🐇 Now we have the <code>cross</code> and <code>cross-util</code> commands. Cross-compilation requires Podman on Linux or Docker on macOS. Do we have Docker?</p>
<p>🐢 It looks like we don’t. Maybe we can just set up a remote build using Podman or GitHub Actions. I saw a crate for that yesterday; I remember saving it somewhere but can’t find it now. Searching again seems easier, which says something about my archival and retrieval habits.</p>
<p>🐇 Maybe later you can add some vector search capabilities to your archive—semantic search.</p>
<p>🐢 Yep, <em>sometime</em> later.</p>
<p>🐇 Now let’s search for “adding rust cross compilation to github actions.”</p>
<p>🐢 I found a link to the action: <a href="https://github.com/marketplace/actions/build-rust-projects-with-cross">https://github.com/marketplace/actions/build-rust-projects-with-cross</a></p>
<p>Let’s look at the example:</p>
<pre><code class="language-yaml">jobs:
  release:
    name: Release - ${{ matrix.platform.os-name }}
    strategy:
      matrix:
        platform:
          - os-name: FreeBSD-x86_64
            runs-on: ubuntu-20.04
            target: x86_64-unknown-freebsd
            skip_tests: true

          - os-name: Linux-x86_64
            runs-on: ubuntu-20.04
            target: x86_64-unknown-linux-musl

          - os-name: Linux-aarch64
            runs-on: ubuntu-20.04
            target: aarch64-unknown-linux-musl

          - os-name: Linux-riscv64
            runs-on: ubuntu-20.04
            target: riscv64gc-unknown-linux-gnu

          - os-name: Windows-x86_64
            runs-on: windows-latest
            target: x86_64-pc-windows-msvc

          - os-name: macOS-x86_64
            runs-on: macOS-latest
            target: x86_64-apple-darwin

          # more targets here ...

    runs-on: ${{ matrix.platform.runs-on }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Build binary
        uses: houseabsolute/actions-rust-cross@v0
        with:
           command: ${{ matrix.platform.command }}
          target: ${{ matrix.platform.target }}
          args: "--locked --release"
          strip: true
      - name: Publish artifacts and release
        uses: houseabsolute/actions-rust-release@v0
        with:
          executable-name: ubi
          target: ${{ matrix.platform.target }}
</code></pre>
<p>🐇 We can just convert the current configuration to this and see if it works.</p>
<p>🐢 Let’s do that. I created <code>.github/workflows/release.yml</code> and will update it.</p>
<p>🐇 Let’s check the results with <code>gh</code>:</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run list
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525070531	34s	2024-12-28T08:17:28Z
</code></pre>
<p>🐢 It says the release has completed.</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run view 12525070531

X v0.6.13 Release iesahin/xvc#263 · 12525070531
Triggered via pull_request about 3 minutes ago

JOBS
X Release - FreeBSD-x86_64 in 12s (ID 34936257653)
  ✓ Set up job
  ✓ Checkout
  X Build binary
  - Publish artifacts and release
  ✓ Post Build binary
  ✓ Post Checkout
  ✓ Complete job
...
</code></pre>
<p>Now let’s look at the failure:</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run view 12525070531 --log-failed
</code></pre>
<p>🐇 Change the order and see what happens. Maybe we should first succeed in a non-cross-compilation build.</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run list
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525178664	35s	2024-12-28T08:34:18Z
...
$ gh -R iesahin/xvc run view   12525178664 --log-failed
...
</code></pre>
<p>🐢 It’s the same error. The <a href="https://github.com/houseabsolute/ubi/blob/master/.github/workflows/ci.yml">usage example</a> is actually much more sophisticated.</p>
<p>🐇 The issue is that we’re asking for <code>command</code> from the matrix, but the matrix doesn’t define it. That’s the second time today an example from the documentation has failed. I’ve added <code>command</code> for each platform. We can also add separate features this way to make platform-specific functionality work.</p>
<p>🐢 Agreed. Let’s look at it once more.</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525253982	41s	2024-12-28T08:46:23Z

$ gh -R iesahin/xvc run view 12525253982 --log-failed
...
Release - macOS-x86_64	Build binary	2024-12-28T08:46:44.8464010Z  [1m [31merror [0m [1m: [0m the lock file /Users/runner/work/xvc/xvc/Cargo.lock needs to be updated but --locked was passed to prevent this
</code></pre>
<p>🐇 Ah, that’s a different error. Let’s remove the <code>--locked</code> flag and retry.</p>
<pre><code class="language-bash">$ gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525289176	1m8s	2024-12-28T08:53:23Z
</code></pre>
<p>🐢 We’re finally starting to get some good news. Now we’re getting OpenSSL errors. We need feature flags for these platforms or to specify where OpenSSL is. Let’s add <code>bundled-openssl</code> to the failed ones. We also need <code>bundled-sqlite</code> for Windows binaries.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525354179	3m9s	2024-12-28T09:04:55Z
</code></pre>
<p>🐇 It looks like the <code>Changes.md</code> file is missing, and it can’t upload the binaries as a release because of this. I’ll set the changes file to <code>CHANGELOG.md</code>.</p>
<p>🐢 It’s weird to fail because of that. I think we should report these errors and possibly send a PR to make that file optional.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
in_progress		v0.6.13	Release	v0.6.13	pull_request	12525449368	1m38s	2024-12-28T09:18:40Z
...
✓ Release - macOS-x86_64 in 2m39s (ID 34937017695)
✓ Release - macOS-aarch64 in 2m42s (ID 34937017787)
..
ARTIFACTS
xvc-macOS-x86_64.tar.gz
xvc-macOS-arm64.tar.gz
</code></pre>
<p>🐢 It looks like <code>Linux-riscv64</code> has OpenSSL compilation errors. I think we can skip this platform for now. The goal was to add <code>aarch64</code> for macOS, and that seems to have succeeded.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525504079	3m24s	2024-12-28T09:26:43Z
...
X Release - Linux-x86_64	Build binary	2024-12-28T09:29:16.2262518Z  [0m [1m [38;5;9merror[E0308] [0m [0m [1m: mismatched types [0m
...
</code></pre>
<p>🐢 It looks like <code>Linux-x86_64</code> doesn’t support reflinks. Maybe we can make reflinks an optional feature and add it specifically to macOS and Windows targets.</p>
<p>🐇 I’ve removed <code>reflink</code> from the default features. This is a breaking change, but <em>fortunately</em> we don’t have many users who will be affected by it.</p>
<p>Let’s check the results once more.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525597866	3m39s	2024-12-28T09:42:44Z
</code></pre>
<p>🐢 Now turn off the <code>NetBSD</code> target as well.</p>
<p>🐇 <code>FreeBSD</code> and <code>Linux-x86_64</code> targets are building. Let’s see why the ARM Linux targets are failing.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525643284	4m46s	2024-12-28T09:52:04Z
</code></pre>
<p>🐇 It looks like those targets don’t have <code>libsqlite3</code> installed. Let’s add <code>bundled-sqlite</code> to these targets too.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list | rg Release | head -n 1
completed	failure	v0.6.13	Release	v0.6.13	pull_request	12525734325	4m11s	2024-12-28T10:07:36Z
</code></pre>
<p>🐢 Adding Android as a target didn’t work. Let’s remove it for now; it seems to require more work.</p>
<p>🐇 I think we’ll eventually move to using Xvc to distribute binaries. We could build the Android binary on Termux and link it on the releases page or push it as a release artifact.</p>
<p>🐢 I need to learn more about GitHub releases. If we can add artifacts to the release, maybe we can do some of this work locally.</p>
<p>🐇 We can start by looking at the capabilities of <code>gh</code> commands.</p>
<p>🐢 It looks like it’s possible to upload assets to releases. Let’s check how that works.</p>
<pre><code class="language-bash">gh release upload --help
</code></pre>
<p>🐇 So basically, we can just upload files to tags. We can list releases and work with them like anything else.</p>
<pre><code class="language-bash">gh -R iesahin/xvc release list
</code></pre>
<p>And we can delete releases:</p>
<pre><code class="language-bash">for r in v0.4.2-alpha.8 v0.4.2-alpha.7 v0.4.2-alpha.6 v0.4.2-alpha.5 v0.4.2-alpha.0 v0.4.1-alpha.0; do
  gh -R iesahin/xvc release delete "${r}"
done
</code></pre>
<p>🐢 Now let’s list them again.</p>
<pre><code class="language-bash">gh -R iesahin/xvc release list
</code></pre>
<p>🐇 The latest one has failed again.</p>
<pre><code class="language-bash">X Failed to CreateArtifact: Received non-retryable error: Failed request: (409) Conflict: an artifact with this name already exists on the workflow run
</code></pre>
<p>🐢 It looks like we’re coming to the end of this session. Under what conditions will we create a release?</p>
<p>🐇 I think it’s better to release only non-alpha tags.</p>
<p>🐢 Then the rule in the workflow will be something like:</p>
<pre><code class="language-yaml">on:
  workflow_dispatch:
  push:
    tags:
      - "v*.*.*"
      - "!v.*.*-alpha.*"
</code></pre>
<p>🐇 And now we have this result:</p>
<pre><code class="language-bash">✓ v0.6.13 Release iesahin/xvc#263 · 12533498361
Triggered via pull_request about 21 minutes ago

JOBS
✓ Release - FreeBSD-x86_64 in 4m14s
✓ Release - Linux-x86_64 in 4m3s
✓ Release - Linux-aarch64 in 4m50s
✓ Release - Windows-x86_64 in 8m47s
✓ Release - Windows-aarch64 in 6m42s
✓ Release - macOS-x86_64 in 2m37s
✓ Release - macOS-aarch64 in 2m36s
</code></pre>
<p>🐢 Nice! Let’s check the release list.</p>
<pre><code class="language-bash">gh -R iesahin/xvc release list
</code></pre>
<p>🐇 Why is there no “latest” release?</p>
<p>🐢 We have to tag it first.</p>
<p>🐇 Ah, right. Let’s tag it then.</p>
<p>🐢 I’ve pushed the changes and tagged them with <code>v0.6.13-alpha.5</code>.</p>
<p>🐇 I think it’s possible to make a release today.</p>
<p>🐢 It looks like it, yes.</p>
<p>🐇 We can merge the PR and tag it. Then everything should work.</p>
<p>🐢 We need some cleanup in the YAML files, though.</p>
<p>🐇 We can leave that for the next release.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
in_progress		v0.6.13	Rust-CI	v0.6.13	pull_request	12533690474	9m18s	2024-12-29T08:02:29Z
completed	success	Release	Release	v0.6.13-alpha.5	push	12533689099	9m11s	2024-12-29T08:02:19Z
</code></pre>
<p>🐢 Now we have another failure in the regular CI. Let’s look at it.</p>
<p>🐇 The issue seems to be in the doc tests:</p>
<pre><code class="language-diff">- Total #: 8 Workspace Size:         276 Cached Size:          19
+ Total #: 8 Workspace Size:         278 Cached Size:          19
</code></pre>
<p>🐢 These tests are brittle, but they provide valuable information. Let’s fix it and push again.</p>
<p>🐇 Done. We can also remove some of the watches that produce so many logs.</p>
<p>🐢 I’m a bit ambivalent about them. I thought we could use these watches when debugging, but experience has shown that we need more granular watches during debugging and almost never use these otherwise. Let’s remove some of them.</p>
<p>🐇 We can increase the output for certain commands, but the tracing output doesn’t help much in regular runs. If Xvc gets popular enough that we can’t cope with bug reports, we can always add more watches.</p>
<p>🐢 Another option is to exclude the watch code from the release build, but that won’t change anything for our debug cycles.</p>
<p>🐇 I think removing them is a fair trial. We can always put them back when debugging.</p>
<p>🐢 Watches could also produce regular output instead of tracing. That way we won’t forget to remove them.</p>
<p>🐇 Ah, yep, that’s a good option too.</p>
<p>🐢 We can have a <code>trace!</code> macro similar to the current one for user consumption, and a <code>watch!</code> macro that sends output to <code>stderr</code>.</p>
<p>🐇 Good idea. Let’s do that in the next release.</p>
<p>🐢 Let’s check the tests before pushing this cleanup.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
completed	success	v0.6.13	Rust-CI	v0.6.13	pull_request	12533993607	13m49s	2024-12-29T08:47:50Z
</code></pre>
<p>🐇 CI succeeded, and the release didn’t run. Let’s do some more cleanup.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
in_progress		v0.6.13	Rust-CI	v0.6.13	pull_request	12534416152	43s	2024-12-29T09:57:02Z
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog 8</title>
      <published>2025-01-04T12:41:16+00:00</published>
      <updated>2025-01-04T12:41:16+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 04 Jan 2025 12:41:16 +0000</pubDate>
      <link>https://emresahin.net/devlog-8/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-8/</guid>
      <description>🐢 The only failure was the patch coverage in Xvc. Let’s see what needs to be done. 🐇 It looks, from the coverage page , that our additions to HStore don’t have any tests. Maybe we can add some unit tests to the new joins. 🐢 I don’t find unit tests particularly useful, but let’s use GitHub Copilot...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>xvc</category>
      <category>rust</category>
      <category>coverage</category>
      <category>cargo-publish</category>
      <category>github-copilot</category>
      <category>claude</category>
      <category>dufs</category>
      <category>neovim</category>
      <category>testing</category>
      <content:encoded><![CDATA[<p>🐢 The only failure was the patch coverage in Xvc. Let’s see what needs to be done.</p>
<p>🐇 It looks, from the <a href="https://app.codecov.io/gh/iesahin/xvc/pull/263?src=pr&amp;el=tree&amp;utm_medium=referral&amp;utm_source=github&amp;utm_content=comment&amp;utm_campaign=pr+comments&amp;utm_term=Emre+Sahin">coverage page</a>, that our additions to HStore don’t have any tests. Maybe we can add some unit tests to the new joins.</p>
<p>🐢 I don’t find unit tests particularly useful, but let’s use GitHub Copilot to add some for us.</p>
<p>🐇 I added a unit test and a doc test for <code>full_join</code>. I think doc tests have more value; they provide documentation, and we can readily see how to use a function from its docs. It’s better to increase coverage with doc tests.</p>
<p>🐢 There are things I should keep in mind while writing doc tests. Imports must use the full path, not <code>crate</code>. Also, the tested struct doesn’t have implicit imports.</p>
<p>🐇 The ceremony for adding keys and values is a bit too much. It might be worthwhile to add an <code>insert</code> method for anything that implements <code>Into&lt;XvcEntity&gt;</code>.</p>
<p>🐢 That would certainly save time—no more typing <code>.into()</code> for each key! :)</p>
<p>🐇 Pushed to test again. Should we have some way to test coverage locally?</p>
<p>🐢 I don’t think we need to check coverage locally. It’s not worth our time right now.</p>
<p>🐇 Now, while waiting for the tests to complete, what else can we do?</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
completed failure v0.6.13 Rust-CI v0.6.13 pull_request 12543831360 3m54s 2024-12-30T08:15:06Z
...
</code></pre>
<p>🐢 That didn’t take too long. Let’s view the results:</p>
<pre><code class="language-bash">gh -R iesahin/xvc run view 12543831360
...
  X Run Current Dev Tests
...
To see what failed, try: 
View this run on GitHub: https://github.com/iesahin/xvc/actions/runs/12543831360
</code></pre>
<p>🐢 The current dev tests are failing for some reason. Let’s run them locally.</p>
<p>🐇 We’re missing <code>llvm-tools-preview</code> locally. How do I install this?</p>
<p>🐢 The command is <code>rustup component add llvm-tools-preview</code>:</p>
<pre><code class="language-text">info: component 'llvm-tools' for target 'aarch64-apple-darwin' is up to date
</code></pre>
<p>🐇 It’s already installed. We just need to set the environment variables.</p>
<p>🐢 Instead, we can just turn off dev tests for the time being. We don’t really need them; our local tests pass.</p>
<p>🐇 Yeah, okay. We don’t need to solve every single bit of these issues right now.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
...
</code></pre>
<p>🐇 Okay, let’s take a look at the run again.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
completed failure v0.6.13 Rust-CI v0.6.13 pull_request 12544020064 3m39s 2024-12-30T08:33:25Z

gh -R iesahin/xvc run view 12544020064
...
  X Test and Coverage
...

gh run view 12544020064 --log-failed
...
Test and Coverage (stable) Test and Coverage 2024-12-30T08:36:58.8911690Z Error: ProcessError { stdout: "", stderr: "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (7) Failed to connect to e1.xvc.dev port 80 after 160 ms: Couldn't connect to server\n" }
...
</code></pre>
<p>🐢 We need to start Nginx on the server. We forgot to do that yesterday.</p>
<p>🐇 Ah, right. After that <code>dufs</code> installation. Okay.</p>
<p>🐢 We also need to add a reverse proxy for <code>dufs</code> somehow, but that’s for later.</p>
<p>🐇 For this use case, I don’t think it’s necessary. We can just adjust the port to a non-standard one if we need 443 for something else. Let’s look at the tests again.</p>
<p>🐢 Let’s add another doc test, this time to <code>XvcStore</code>.</p>
<pre><code class="language-bash">cargo test -p xvc-ecs --doc
...
test result: ok. 8 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 2.87s
</code></pre>
<p>🐇 Sent the files again. Waiting for the tests to finish.</p>
<p>🐢 Let’s check the keymaps file in the meantime.</p>
<p>🐇 I tried reading the documentation but didn’t see an error. Maybe we should just set it to non-lazy and disallow remaps.</p>
<p>🐢 We’ve already spent too much time on this.</p>
<p>🐇 Yes, let’s check the tests again.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
completed success v0.6.13 Rust-CI v0.6.13 pull_request 12544291068 8m10s 2024-12-30T09:00:42Z
...
</code></pre>
<p>🐢 Oh, nice, the merge is ready.</p>
<p>🐇 Patch coverage is still behind the target, though.</p>
<p>🐢 Yeah, but let’s release this one and ensure the next one is better covered. Also, I’m not sure if the doc tests actually affected the coverage report.</p>
<p>🐇 If we look at the <a href="https://app.codecov.io/gh/iesahin/xvc/pull/263?src=pr&amp;el=tree&amp;utm_medium=referral&amp;utm_source=github&amp;utm_content=comment&amp;utm_campaign=pr+comments&amp;utm_term=Emre+Sahin">coverage page</a> again, we can see.</p>
<p>🐢 It seems codecov.io doesn’t consider coverage for doc tests. That’s a bit weird, but let’s not spend more time on it.</p>
<p>🐇 Sure, let’s merge.</p>
<p>🐢 I think we forgot to bump the versions in the <code>Cargo.toml</code> files. We’ll have to do that in <code>main</code>.</p>
<p>🐇 Oh, yeah. Let’s bump them and tag the release as well.</p>
<p>🐢 Now we can wait for all the files to be produced. What’s next?</p>
<p>🐇 We can release the Python version too. It shouldn’t need any changes.</p>
<p>🐢 Right. Maybe we can add a few tests there as well.</p>
<p>🐇 Let’s bump the version first and see.</p>
<p>🐢 I’ve bumped the versions in <code>Cargo.toml</code> and run <code>maturin develop</code>.</p>
<p>🐇 It seems ready now.</p>
<p>🐢 The main branch is failing, though. The “Publish crates” action is looking for <code>libsqlite3</code>. Let’s investigate.</p>
<pre><code class="language-bash">gh -R iesahin/xvc run list
completed failure Release v0.6.13 Publish Crates v0.6.13 push 12544753359 6m1s 2024-12-30T09:41:56Z
</code></pre>
<p>🐢 The issue is that the VM doesn’t have <code>libsqlite3-dev</code>. Let’s add it.</p>
<p>🐇 We need to restart the job manually. Let’s skip tagging this time.</p>
<p>🐢 Some of the packages were already published, and now they’re causing failures because crates.io says they already exist. Maybe we can check if a package is already published before trying.</p>
<p>🐇 Let’s see if we can make <code>cargo publish</code> more forgiving.</p>
<p>🐢 There doesn’t seem to be an easy option. Let’s search for “how to skip published packages in workspace to avoid errors with cargo publish.”</p>
<p>🐇 Claude is hallucinating again. Let’s try a manual approach: how to skip already published packages?</p>
<p>🐢 It might be easier to just add a check. How do we get that info?</p>
<p>🐇 Or we can just move on to the next package if one is already available.</p>
<p>🐢 Let’s push the missing packages manually this time.</p>
<p>🐢 We should have a key to open garden files quickly. What does <code>Fzf-Lua files</code> receive as arguments?</p>
<p>🐇 Let’s check the help page: <code>fzf-lua</code>.</p>
<p>🐢 Before that, maybe we can try to fix why selected lines are not searched in Visual-Line mode?</p>
<p>🐇 Yep, let’s fix our search first.</p>
<p>🐢 How do you set a key in visual line mode in Neovim Lua?</p>
<p>🐇 The abbreviation is <code>V</code>. Let’s try that.</p>
<p>🐢 Looks like we need to restart the session.</p>
<p>🐇 It’s still not working. Let’s check <code>:map</code>.</p>
<p>🐢 It seems it needs more care; I’ll check it later.</p>
<p>👨🏾‍🦲
🐢 Bence kendini biraz daha anlamlı bir işle uğraştırmalısın.
🐇 Ne gibi?
🐢 Belki biraz daha yayın yapmalısın, biraz daha işe başvurmalısın.
🐇 “Meli”, “malı” ile geçiyor ömrümüz.</p>
<p>⌚
🐢 It looks like we can move most of the daily template to links or commands. We can call it <code>ref/daily</code>. My idea is to fill the page intentionally, without templates.</p>
<p>🐇 It might be better to start with a blank page, yeah. The template makes me a bit nervous. There are too many things to fill in, and most of them aren’t things I like being pushed into doing.</p>
<p>🐢 Let’s start by moving the daily templates to <code>ref/daily</code>. No more daily template.</p>
<p>🐇 Now we can delete the rest of this page. We’ll use the daily links page and maybe have reminders at the end of these sessions.</p>
<p>👨🏽‍⚕️</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 7</title>
      <published>2024-08-06T06:43:01+00:00</published>
      <updated>2024-08-06T06:43:01+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 06 Aug 2024 06:43:01 +0000</pubDate>
      <link>https://emresahin.net/devlog-7/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-7/</guid>
      <description>I noticed that I often forget to update the Xvc CHANGELOG. To fix this, I added a pre-push hook that checks the files I’m pushing. If the CHANGELOG is not among them and there are changes to Rust files, it prevents the push. I hope this will remind me to update the logs more frequently. #!/bin/ba...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>Git</category>
      <category>git-hooks</category>
      <category>automation</category>
      <category>changelog</category>
      <category>xvc</category>
      <category>bash</category>
      <content:encoded><![CDATA[<p>I noticed that I often forget to update the Xvc CHANGELOG. To fix this, I added
a pre-push hook that checks the files I’m pushing. If the CHANGELOG is not among
them and there are changes to Rust files, it prevents the push. I hope this will
remind me to update the logs more frequently.</p>
<pre><code class="language-bash">#!/bin/bash

# Git pre-push hook to check if CHANGELOG.md is included in the push and if the branch is develop

# Get the current branch name

current_branch=$(git rev-parse --abbrev-ref HEAD)

# Check if the branch is develop

if [ "$current_branch" == "main" ]; then
	echo "You are on the main branch. Skipping CHANGELOG.md check."
	exit 0
fi

# remote="$1"
# url="$2"

# Get the list of commits to be pushed
commits=$(git rev-list '@{u}..HEAD')

has_rust_files=$(false)

# TODO: We can iterate to get file list only once
# Get the list of files that are going to be pushed
for commit in $commits; do
	if git diff-tree --no-commit-id --name-only -r "${commit}" | grep -q "\\.rs$"; then
		has_rust_files=$(true)
	fi
done

if [[ ! $has_rust_files ]]; then
	echo "No .rs files in the push, no need to check CHANGELOG"
	exit 0
fi

# Check if CHANGELOG.md is among the files in the commits
for commit in $commits; do
	if git diff-tree --no-commit-id --name-only -r "${commit}" | grep -q "CHANGELOG.md"; then
		echo "CHANGELOG.md is included in the push."
		exit 0
	fi
done

echo "ERROR: CHANGELOG.md is not included in the push."
exit 1
</code></pre>
<p><strong>Edit (2025-02-08):</strong> Added a check for when only code files are changed.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 6</title>
      <published>2024-07-12T08:53:14+00:00</published>
      <updated>2024-07-12T08:53:14+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Fri, 12 Jul 2024 08:53:14 +0000</pubDate>
      <link>https://emresahin.net/devlog-6/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-6/</guid>
      <description>I have a habit of testing against the CLI’s help string output. It allows me to keep the documentation up to date and makes me aware of any undocumented options. When new features are added, the help text changes and the test fails, which prompts me to add those options to the documentation. I tr...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>xvc</category>
      <category>Python</category>
      <category>pytest</category>
      <category>clap</category>
      <category>CLI</category>
      <category>Testing</category>
      <content:encoded><![CDATA[<p>I have a habit of testing against the CLI’s help string output. It allows me to
keep the documentation up to date and makes me aware of any undocumented options.
When new features are added, the help text changes and the test fails, which
prompts me to add those options to the documentation.</p>
<p>I tried the same approach when testing Python bindings with Pytest:</p>
<pre><code class="language-python">def test_pipeline_step_dependency(empty_xvc_repo):
    dep_help = empty_xvc_repo.pipeline().step().dependency(help=True)
    expected = """
Usage: xvc pipeline step dependency [OPTIONS] --step-name &lt;STEP_NAME&gt;

Options:
  -s, --step-name &lt;STEP_NAME&gt;
          Name of the step to add the dependency to
"""
    assert dep_help == expected
</code></pre>
<p>This doesn’t work because the help text is generated by <a href="https://docs.rs/clap/latest/clap/">clap</a> and skips the usual
thread-based output handler. All command output and errors in Xvc are returned
as strings from the command, except for the help text that’s generated by <a href="https://docs.rs/clap/latest/clap/">clap</a>
automatically.</p>
<p>There are probably workarounds for this, but I won’t pursue them further as I don’t
test the actual functionality in the Python bindings anyway.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 5</title>
      <published>2024-07-08T20:35:24+00:00</published>
      <updated>2024-07-08T20:35:24+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 08 Jul 2024 20:35:24 +0000</pubDate>
      <link>https://emresahin.net/devlog-5/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-5/</guid>
      <description>While writing Xvc tests for Python, I hit an error caused by the ECS single-load protection. The single-loader allows only one instance of Xvc to be run in a single process. This is no problem for the shell, but it looks like it won’t be possible to use multiple Xvc instances in a single Python p...</description>
      <category>devlog</category>
      <category>Xvc</category>
      <category>devlog</category>
      <category>multiprocessing</category>
      <category>Jupyter</category>
      <category>python</category>
      <category>ecs</category>
      <category>testing</category>
      <content:encoded><![CDATA[<p>While writing Xvc tests for Python, I hit an error caused by the ECS single-load protection.</p>
<p>The single-loader allows only one instance of Xvc to be run in a single process. This is no problem for the shell, but it looks like it won’t be possible to use multiple Xvc instances in a single Python process.</p>
<p>It’s possible to overcome this with an elaborate multiprocessing setup in the wrapper, but I won’t bother with it for now.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 4</title>
      <published>2024-06-05T10:04:54+00:00</published>
      <updated>2024-06-05T10:04:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 05 Jun 2024 10:04:54 +0000</pubDate>
      <link>https://emresahin.net/devlog-4/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-4/</guid>
      <description>Let’s start by looking at the debug output issue. We can start by replacing the eprintln! macros with println! , perhaps. I replaced the eprintln! s with println! , but it didn’t make any difference. Maybe we should remove those statements completely. I can’t really find the place that kills the ...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>xvc storage</category>
      <category>python</category>
      <category>clap</category>
      <category>debug</category>
      <category>rust</category>
      <category>s3</category>
      <category>cli</category>
      <content:encoded><![CDATA[<p>Let’s start by looking at the debug output issue. We can start by replacing the <code>eprintln!</code> macros with <code>println!</code>, perhaps.</p>
<p>I replaced the <code>eprintln!</code>s with <code>println!</code>, but it didn’t make any difference. Maybe we should remove those statements completely.</p>
<p>I can’t really find the place that kills the kernel. The last command to run is <code>xvc file list</code>:</p>
<pre><code class="language-python">print(xvc_test_data.file().list("test-data/dir-0002"))
</code></pre>
<p>and the output it produces is:</p>
<pre><code>[src/output.rs:144:13] &amp;output_str = "SS         131 2024-06-05 08:57:11 41e16be7          test-data/dir-0002/file-0003.bin\nSS         131 2024-06-05 08:57:11 27f0
efd0          test-data/dir-0002/file-0002.bin\nSS         131 2024-06-05 08:57:11 66de5084          test-data/dir-0002/file-0001.bin\nTotal #: 3 Workspace Size:
      393 Cached Size:        6006\n"
</code></pre>
<p><code>print</code> may be causing the crash, but the more likely cause is the command that comes after this:</p>
<pre><code>!ls -l test-data/dir-0001/
</code></pre>
<p>I replaced this with <code>lsd</code>, which also failed. Maybe it’s actually a Python crash or bug.</p>
<p>The way to understand is to create a notebook file with only that cell and try to run it.</p>
<p>The <code>ls</code> line runs fine with a new notebook. It even runs on the <code>README</code> file when run at the beginning. The line that makes the kernel crash is:</p>
<pre><code class="language-python">xvc_test_data.storage().new_s3(name="backup", bucket_name="xvc-test", region="eu-central-1", storage_prefix="xvc-storage")
</code></pre>
<p>We can start by removing the <code>new_s3</code> part.</p>
<p>The <code>storage()</code> method runs fine. It returns an <code>XvcStorage()</code> object, as it should.</p>
<p>When I run <code>storage().list()</code>, it takes a very long time. The bug is likely related to <code>storage()</code>.</p>
<p>It looks like the <code>storage</code> object was adding <code>file</code> instead of <code>storage</code> as a subcommand. I’ve fixed it now.</p>
<p>That was the bug. The <code>README</code> notebook now creates the S3 storage.</p>
<p>What was the reason behind this?</p>
<p>Parsing the CLI to the <code>XvcCLI</code> object was perhaps the culprit. Let’s look at it more clearly.</p>
<p>Let’s try <code>xvc file new s3</code> as a command to see how it behaves.</p>
<p>It says <em>unrecognized subcommand</em> for <code>new</code>.</p>
<p>This is how it should be, but I wonder why it doesn’t work for the <code>XvcCLI</code> parser.</p>
<p>Anyway, it’s already 13:00, so let’s stop here for today.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 2</title>
      <published>2024-05-26T17:29:09+00:00</published>
      <updated>2024-05-26T17:29:09+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 26 May 2024 17:29:09 +0000</pubDate>
      <link>https://emresahin.net/devlog-2/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-2/</guid>
      <description>We were debugging Python bindings for Xvc. xvc.file.track enters an infinite loop when given a non-existent path. To make debugging easier, we can add file deletions in the ./start-readme script to the notebook itself. Instead, I’ll run my watcher run-after-commit.sh to ensure that the files are ...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>xvc</category>
      <category>jupyter</category>
      <category>xvc-config</category>
      <category>xvc-file</category>
      <category>repr</category>
      <category>python-bindings</category>
      <category>debugging</category>
      <content:encoded><![CDATA[<p>We were debugging Python bindings for Xvc. <code>xvc.file.track</code> enters an infinite
loop when given a non-existent path.</p>
<p>To make debugging easier, we can add file deletions in the <code>./start-readme</code> script
to the notebook itself.</p>
<p>Instead, I’ll run my watcher <code>run-after-commit.sh</code> to ensure that the files
are deleted after commits.</p>
<p>That may also work; you can use both as well.</p>
<p>I noticed cli-opts pass <code>--no-system-config</code> etc. by default. Let’s deal with
this first.</p>
<p>I fixed that.</p>
<p>I’m testing whether we are in the directory that we should be in with
<code>xvc.root("--absolute")</code>, but it looks like it’s not possible to pass a string
argument to the command.</p>
<p>Let me take a look at this.</p>
<p>It looks like there have been some changes in optional parameter handling in PyO3.
You may need to deal with keyword arguments with decorators.</p>
<p>Now, let’s try <code>xvc.file().track()</code> once more with <code>dir-0001/</code>.</p>
<p>It seems to be working now. With an existing directory, it doesn’t show an
error.</p>
<p>What does <code>xvc.file().list()</code> show?</p>
<p>It shows a single string with <code>\n</code> in it. It looks like we need to handle this in
the output thread.</p>
<p>I added a <code>replace("\\n", "\n")</code> to <code>output_str</code> at the end, but it didn’t make a
difference. I tested in the notebook with <code>list_result.split("\n")</code>, and these
<code>\n</code> characters are indeed CRLF. So Jupyter shows CRLF in strings with <code>\n</code>, and
this is not something we should try to fix, I think.</p>
<p>You can search how to show <code>\n</code> characters in a Jupyter notebook with CRLF.</p>
<p>The output string should be fed into <code>repr</code>, as in:</p>
<pre><code class="language-python"># Define a string with CRLF characters
text_crlf = "Hello\r\nWorld\r\nThis is a test string."

# Use repr to show the \r\n characters explicitly
print(repr(text_crlf))
</code></pre>
<p>I tested this, and GPT misleads. <code>repr</code> is when you <em>want</em> to show <code>\n</code>, not
vice versa. When I <code>print(list_result)</code>, it prints the results properly.</p>
<p>It looks like we don’t need to make this a priority now. We can tell the user in the
notebook that the commands are intentionally returning strings, and they can
process or print them however they want.</p>]]></content:encoded>
    </item>
    <item>
      <title>Devlog 1: XVC Root and Python Bindings Debugging</title>
      <published>2024-05-24T10:12:50+00:00</published>
      <updated>2024-05-24T10:12:50+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Fri, 24 May 2024 10:12:50 +0000</pubDate>
      <link>https://emresahin.net/devlog-1/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-1/</guid>
      <description>I should have a dispatch method that receives an XvcRootOpt and runs a command with it. The dispatcher can also update the XvcRoot from None to Some(XvcRoot) in some cases, so it should receive a mutable XvcRootOpt or return one after receiving ownership. The issue with having a mutable element i...</description>
      <category>Devlog</category>
      <category>XVC</category>
      <category>xvc</category>
      <category>debugging</category>
      <category>shell</category>
      <category>jupyter-lab</category>
      <category>rust</category>
      <category>python-bindings</category>
      <content:encoded><![CDATA[<p>I should have a dispatch method that receives an <code>XvcRootOpt</code> and runs a command
with it. The dispatcher can also update the <code>XvcRoot</code> from <code>None</code> to <code>Some(XvcRoot)</code>
in some cases, so it should receive a mutable <code>XvcRootOpt</code> or return one after
receiving ownership.</p>
<p>The issue with having a mutable element is that we need to update <code>XvcRoot</code> from
<code>Arc&lt;XvcRootInner&gt;</code> to <code>Arc&lt;RwLock&lt;XvcRootInner&gt;&gt;</code>, which will cause almost all
machinery around <code>XvcRoot</code> to require locking the object first. This is too
large a refactoring.</p>
<p>However, I can write an <code>xvc_root!</code> macro to replace <code>xvc_root.read()</code> or
whatever is required to minimize the code changes. <code>XvcRoot</code> can also be a wrapper
object, but that’s too much fuss, and the responsibility may be misplaced.</p>
<p>Let’s update the type and let the dispatcher update <code>XvcRootInner</code> only
when necessary. Let’s see what will require updating.</p>
<hr>
<p>We have a bug in the Python bindings where <code>xvc.file.track()</code> enters an infinite loop.</p>
<p>There can be multiple reasons, but are you sure that the <code>xvc</code> CLI works with the
same command?</p>
<p>Let’s start testing by creating another notebook server.</p>
<p>The notebook creates an Xvc repo successfully and returns the root with
<code>xvc.root()</code>, but <code>xvc.file().track("test-data/dir-0001/")</code> never finishes.</p>
<p>It’s likely that this is caused by something in the background threads.</p>
<p>The CLI command completes successfully, but we may be passing the command
incorrectly. Let’s try <code>dir-0001</code> only.</p>
<p>Even if an incorrect path is provided, it shouldn’t enter an infinite loop.</p>
<p>When I provide the correct path, <code>dir-0001</code>, it returns. There may be something going on with finding the root of the Xvc repo.</p>
<p>It returns, but it’s also giving an error that it cannot find a repository. I should take a closer look.</p>
<p>The <code>Xvc</code> struct wasn’t implementing <code>Debug</code>, so I added it.</p>
<p>Let’s add it to others, <code>XvcFile</code>, etc.</p>
<p>Added it. Recompiling to get more info when we run <code>track</code> with an incorrect path.</p>
<p>You can also make it run after a commit to avoid starting a new server if one is already running.</p>
<p>Added conditionals, and now it runs the server if none is running in the background.</p>
<pre><code class="language-zsh">PORT=7979
if [[ ! -s "$(ps ax | rg -v ps | rg jupyter-lab | rg $PORT)" ]] ; then
  jupyter lab --port=$PORT --notebook-dir=Readme/ &amp;
  open http://localhost:${PORT}/lab/workspaces/auto-H/tree/Readme.ipynb
fi
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog</title>
      <published>2023-06-12T15:04:19+00:00</published>
      <updated>2023-06-12T15:04:19+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 12 Jun 2023 15:04:19 +0000</pubDate>
      <link>https://emresahin.net/devlog/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog/</guid>
      <description>I’ve read a few interesting ideas here . Identifying whether an uploaded document is a template could be a useful feature. This is a basic classification task. We can also find ways to extract named entities from the documents and remove them to create a template. Then, we can ask the user to pro...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>legalops</category>
      <category>contract</category>
      <category>negotiation</category>
      <category>nlp</category>
      <category>document-analysis</category>
      <category>templates</category>
      <content:encoded><![CDATA[<ul>
<li>I’ve read a few interesting ideas <a href="http://sourcinginnovation.com/wordpress/2023/06/06/source-to-pay-is-extensive-p22-time-for-contract-management-but-its-a-nag-lets-start-with-negotiation/">here</a>.</li>
<li>Identifying whether an uploaded document is a template could be a useful feature.
<ul>
<li>This is a basic classification task. We can also find ways to extract named entities from the documents and remove them to create a template. Then, we can ask the user to provide values for these entities to generate a complete document.</li>
</ul>
</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Xvc Devlog - 221109</title>
      <published>2022-11-10T09:26:00+00:00</published>
      <updated>2022-11-10T09:26:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 10 Nov 2022 09:26:00 +0000</pubDate>
      <link>https://emresahin.net/xvc-devlog---221109/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-devlog---221109/</guid>
      <description>🐇 How do you want to proceed from here, Mr. 🐢? 🐢 I think I can implement Rsync today. Looking at ssh2-rs , though, I think we can implement file transfer without relying on rsync . It might be easier to implement everything within the code. 🐇 Then you should rename the issue to new ssh . 🐢 Fair. ...</description>
      <category>devlog</category>
      <category>development</category>
      <category>xvc</category>
      <category>ssh</category>
      <category>rsync</category>
      <category>storage</category>
      <category>feature-flags</category>
      <category>rust</category>
      <category>libssh2</category>
      <content:encoded><![CDATA[<p>🐇 How do you want to proceed from here, Mr. 🐢?</p>
<p>🐢 I think I can implement Rsync today. Looking at <a href="https://docs.rs/ssh2/latest/ssh2/">ssh2-rs</a>, though, I think we can implement file transfer without relying on <code>rsync</code>. It might be easier to implement everything within the code.</p>
<p>🐇 Then you should rename the issue to <code>new ssh</code>.</p>
<p>🐢 Fair. There is also the <a href="https://docs.rs/ssh-rs/0.2.2/ssh_rs/">ssh_rs</a> crate, but it doesn’t have full support for the protocol. Instead, we can have another command, like <code>xvc storage new ssh</code>, that uses <code>libssh2</code> via the crate mentioned above. It has some limitations with OpenSSH on macOS.</p>
<p>🐇 From the <a href="https://github.com/alexcrichton/ssh2-rs">crate’s README</a>, it looks like you can enable the <code>vendored-openssl</code> feature to compile it statically.</p>
<p>🐢 Let’s go ahead then. It’s better to compile it behind a feature flag, though.</p>
<p>🐇 Yup. Rsync can be separate. I think for now you can implement rsync via <code>Exec::cmd</code> and make <code>new ssh</code> a new issue.</p>
<p>🐢 I’ll copy this conversation there.</p>
<hr>
<p>🐇 Now, let’s start implementing <code>rsync</code>.</p>
<p>🐢 Do we really want to hide it behind a feature flag? It doesn’t bring any extra complexity to <code>generic</code>, for example—just using the commands and returning the errors.</p>
<p>🐇 I think so. If the user doesn’t have <code>rsync</code> on their system, they’ll just get errors. We don’t need to make the implementation optional, but the tests might be.</p>
<p>🐢 OK.</p>]]></content:encoded>
    </item>
    <item>
      <title>Xvc Devlog - 221108</title>
      <published>2022-11-09T10:42:00+00:00</published>
      <updated>2022-11-09T10:42:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 09 Nov 2022 10:42:00 +0000</pubDate>
      <link>https://emresahin.net/xvc-devlog---221108/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-devlog---221108/</guid>
      <description>🐇 I think we can begin by checking GitHub PRs . What do you have today? 🐢 The tests for yesterday’s Git integration PR failed. I’ll begin by checking the logs. 🐇 Maybe run the tests locally to see. They might fail on your machine as well. It might be a simple thing. 🐢 Probably, yes. I’ve started ...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>xvc</category>
      <category>git</category>
      <category>testing</category>
      <category>trace</category>
      <category>rsync</category>
      <category>storage</category>
      <category>github actions</category>
      <category>automation</category>
      <content:encoded><![CDATA[<p>🐇 I think we can begin by checking <a href="https://github.com/pulls">GitHub PRs</a>. What do you have today?</p>
<p>🐢 The tests for yesterday’s <a href="https://github.com/iesahin/xvc/pull/105">Git integration PR</a> failed. I’ll begin by checking the logs.</p>
<p>🐇 Maybe run the tests locally to see. They might fail on your machine as well. It might be a simple thing.</p>
<p>🐢 Probably, yes. I’ve started the tests now. I shouldn’t forget to run them before testing the PR; it may save some time.</p>
<p>🐇 Ah, yeah, maybe. Yesterday you were already at the end of the workday, so it didn’t matter much. But you can shorten the testing time by reducing the number of files, etc. I think a separate benchmark suite might be good to have. Tests should be shorter; benchmarks should only run for tags.</p>
<p>🐢 Yup. I should shorten the tests. I should make them use a shorter list of files, maybe.</p>
<p>🐇 Local tests have passed. You’ll have to check the logs.</p>
<p>🐢 It seems <code>git diff --name-only --cached</code> returns files not yet added. That’s causing an error in new repositories with <code>stash</code>. Stashing shouldn’t run at all when there are no staged files.</p>
<p>🐇 Git behavior between the local version and the remote version seems different.</p>
<p>🐢 I think the trace should also show the Git version string.</p>
<p>🐇 You should also require a minimum Git version for the commands. You’re using some newer options, and not all users may have them.</p>
<p>🐢 Yep. Let’s see the version on the CI now.</p>
<hr>
<p>🐢 Ah, I see—the problem is not about versions. It’s that the CI doesn’t configure <code>git config --global user.email</code> and <code>user.name</code>. Commits don’t work without these.</p>
<p>🐇 You should remove the Git version report, then. It’s one more process call for no reason.</p>
<p>🐢 I think it should be in <code>trace!</code>; I can check the verbosity level and call it only if it’s trace.</p>
<hr>
<p>🐢 I completed the xvc-config docs as well. I think I can merge it before the tests finish, as it’s just a documentation update.</p>
<p>🐇 You seem to be rushing for the release.</p>
<p>🐢 Yeah, I want to really start testing this on the servers.</p>
<p>🐇 Then go ahead; let’s release a new version.</p>
<hr>
<p>🐇 It looks like you’re back for an evening session. I think it’s time to start using Xvc on your torrent server.</p>
<p>🐢 Ah, yeah. Let’s see how it goes.</p>
<p>🐇 Create a repository for torrents. Then you can add files to it with cache-type = symlink or cache-type = hardlink.</p>
<p>🐢 I think creating a local storage may also help. I can use it to store files to retrieve them later.</p>
<p>🐇 Umm. We don’t have a “garbage collection” facility yet, you know. There are no file deletions at the moment. It may be better to have a repository-to-repository transfer feature, using SSH.</p>
<p>🐢 Yeah, we don’t have SSH storage either. I think this highlights a lack of features.</p>
<p>🐇 It’s possible to mimic Rsync storage with <code>xvc storage new generic</code>, but it doesn’t feel quite the same.</p>
<p>🐢 Then I’m adding these three tickets.</p>
<p>🐇 I think for 0.3.4, the command we add might be <code>xvc file delete</code>. You can work on this and add <code>rsync</code> support as well.</p>
<p>🐢 There is a <a href="https://docs.rs/librsync/latest/librsync/">librsync</a> bindings library for Rust. But it looks like it doesn’t allow transferring file contents between hosts. There is also <a href="https://github.com/your-tools/rusync">rusync</a>, which is similar to rsync and implemented in Rust. There is also <a href="https://lib.rs/crates/fast_rsync">fast_rsync</a> in pure Rust. It uses MD4 to calculate deltas, though, I think.</p>
<p>🐇 None of these seem to have network capability, though.</p>
<p>🐢 I think it’s better just to use the process for now. We are trying to come up with the simplest solution <em>for now.</em> We are just trying to be more general.</p>
<p>🐇 Yes, I think we can just use the process for the time being.</p>
<p>🐢 Then let’s start by adding it.</p>
<p>🐇 We should also begin to create release notes. GitHub can generate them from PRs. <a href="https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes">Automatically generated release notes</a></p>
<p>🐢 Created an issue for that. Now we’re starting <a href="https://github.com/iesahin/xvc/issues/111"><code>xvc storage new rsync</code> #111</a>.</p>
<p>🐇 Ok. Let’s do this.</p>]]></content:encoded>
    </item>
    <item>
      <title>Xvc Devlog - 221107</title>
      <published>2022-11-08T09:57:00+00:00</published>
      <updated>2022-11-08T09:57:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 08 Nov 2022 09:57:00 +0000</pubDate>
      <link>https://emresahin.net/xvc-devlog---221107/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-devlog---221107/</guid>
      <description>🐇 Welcome to the November 7th issue of Xvc Devlog. In the previous devlog , we began to implement Git integration. How is it going, Mr. Tortoise? 🐢 It looks like we don’t have many architectural problems. 🐇 You’re forgetting how Exec::cmd works, though. You have those kinds of problems. 🐢 Yeah, I...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>xvc</category>
      <category>architecture</category>
      <category>shell</category>
      <category>git</category>
      <category>gitignore</category>
      <category>rust-analyzer</category>
      <category>LSP</category>
      <content:encoded><![CDATA[<p>🐇 Welcome to the November 7th issue of Xvc Devlog. In the <a href="https://emresahin.net/xvc-devlog-221105">previous devlog</a>, we began to implement Git integration. How is it going, Mr. Tortoise?</p>
<p>🐢 It looks like we don’t have many architectural problems.</p>
<p>🐇 You’re forgetting how <code>Exec::cmd</code> works, though. You have those kinds of problems.</p>
<p>🐢 Yeah, I’m figuring that out. <code>Exec::shell</code> requires a string to run on the shell, while <code>Exec::cmd</code> just needs a command name. You have to supply <code>args</code> with another function. I’m writing a closure now to handle this.</p>
<hr>
<p>🐢 It started working, but it revealed a much bigger problem: <code>.xvc/</code> is not added to <code>.gitignore</code> in <code>xvc init</code>.</p>
<p>🐇 Wow, that’s a showstopper.</p>
<p>🐢 Yup. I think I should fix it as well.</p>
<p>🐇 On closer glance, I think the problem is not <code>xvc init</code> <em>not modifying</em> <code>.gitignore</code>; it modifies it incorrectly. Maybe putting certain files on a whitelist is a better idea than trying to blacklist everything.</p>
<p>🐢 Yeah, we should define a set of <em>Git-tracked</em> files and directories, whitelist them, and let all other files be ignored.</p>
<p>🐇 Go ahead, then.</p>
<p>🐢 I think I’ve fixed it. There were two problems: the initial gitignore content was wrong, and it was placed in the root of the repository instead of <code>.xvc</code>.</p>
<p>🐇 Maybe putting it directly in the root is better than hiding it in <code>.xvc</code>. What do you think?</p>
<p>🐢 There seems to be an assumption in <code>file track</code> to have a <code>.gitignore</code> file somewhere. I think we should also handle changes in <code>.gitignore</code> files throughout the repository.</p>
<p>🐇 Umm, yes. We should handle <code>.gitignore</code> files as well. But not all <code>.gitignore</code> files are changed by Xvc. How can we make sure they were modified by Xvc?</p>
<p>🐢 I think there are ways to do that, like tracking them somewhere. But I believe we shouldn’t try. We should just get a list of <code>.gitignore</code> files, <code>git add</code> them, and include them in the commit.</p>
<p>🐇 Ok. Let’s write a test for this as well. No <code>.gitignore</code> should appear in <code>git status -s</code>.</p>
<p>🐢 I wrote the test. It fails now. Do you think we should check the output of Git status to determine which files to add?</p>
<p>🐇 That may be a good idea. Git status should already know which files need to be added. We can use that information.</p>
<p>🐢 <a href="https://css-tricks.com/git-pathspecs-and-how-to-use-them/">It looks like <code>pathspec</code></a> is enough to modify <code>git add</code> behavior. We should be able to write <code>*.gitignore</code> and let all gitignore files be added.</p>
<p>🐇 Let’s try this manually.</p>
<p>🐢 Yep, it works. <code>git add '*.gitignore'</code> adds all <code>.gitignore</code> files in the subdirectories, too.</p>
<p>🐇 Congrats. 👏🥳</p>
<hr>
<p>🐢 I’m writing the missing documentation for the crates. There are <code>proc_macro not expanded</code> errors all over the code.</p>
<p>🐇 I think you should update rust-analyzer and everything. There must be a command for this.</p>
<p>🐢 I reinstalled RA with <code>:LspInstall</code> and restarted the LSP. It seems to work correctly now.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
