<?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 🍃 - xvc</title>
    <link>https://emresahin.net/categories/xvc/</link>
    <description>Posts in the xvc 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/xvc/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>devlog 29</title>
      <published>2025-04-24T03:29:07+00:00</published>
      <updated>2025-04-24T03:29:07+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:29:07 +0000</pubDate>
      <link>https://emresahin.net/devlog-29/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-29/</guid>
      <description>🐢 As the new version is updated, we can go back to the project. What’s the next step? 🐇 We can continue working on rclone. 🐢 Umm, ok. Let’s try to focus on adding another storage type. ✅ #🌻 ADD rclone storage type (2025-04-24 06:27) 🐢 I think the first option is to run the commands from the comma...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>xvc storage new generic</category>
      <category>rclone alias</category>
      <category>Rust</category>
      <category>Development</category>
      <content:encoded><![CDATA[<p>🐢 As the new version is updated, we can go back to the project. What’s the next step?</p>
<p>🐇 We can continue working on rclone.</p>
<p>🐢 Umm, ok. Let’s try to focus on adding another storage type.</p>
<ul>
<li>✅ #🌻 ADD rclone storage type (2025-04-24 06:27)</li>
</ul>
<p>🐢 I think the first option is to run the commands from the command line. We can just use a modified generic storage type without trying to make it fast.</p>
<p>🐇 Let’s make it run first, you say?</p>
<p>🐢 Yes, let’s make it run first and then we can think about making it run fast.</p>
<p>🐇 You’re right!</p>
<ul>
<li>✅ #🌻 ADD generic rclone tests (2025-04-24 06:27)</li>
</ul>
<p>It’s possible to use the <a href="https://www.reddit.com/r/rclone/comments/qu5l5k/how_to_specify_local_directory_as_a_remote/">alias</a> remote with a local path.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 28</title>
      <published>2025-04-24T03:20:52+00:00</published>
      <updated>2025-04-24T03:20:52+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:20:52 +0000</pubDate>
      <link>https://emresahin.net/devlog-28/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-28/</guid>
      <description>🐢 We can start adding an rclone remote as well. 🐢 Created the PR, waiting for the tests. 🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way. 🐇 It will add another dependency though. 🦊 We can always add a feature flag for this. 🐢 There is a librclone crate that c...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>librclone</category>
      <category>Rust</category>
      <category>celeste</category>
      <category>cloud-storage</category>
      <content:encoded><![CDATA[<p>🐢 We can start adding an rclone remote as well.</p>
<p>🐢 Created the PR, waiting for the tests.</p>
<p>🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way.</p>
<p>🐇 It will add another dependency though.</p>
<p>🦊 We can always add a feature flag for this.</p>
<p>🐢 There is a <a href="https://github.com/trevyn/librclone">librclone</a> crate that can be used to call rclone commands like https://rclone.org/rc/#supported-commands</p>
<p>🦊 We can test it from the command line perhaps.</p>
<pre><code class="language-bash">rclone rc 
2025/03/15 17:41:09 NOTICE: Failed to rc: failed to list: connection failed: Post "http://localhost:5572/rc/list": dial tcp [::1]:5572: connect: connection refused
</code></pre>
<p>🐇 It requires the backend to be running in the background.</p>
<p>🦊 There may be examples in the repository.</p>
<p>🐢 There are none. We can search GH for this crate though.</p>
<p>🦊 It’s also possible to search for dependents in crates.io.</p>
<p>🐢 I think Xvc will be the first dependent of this crate: https://crates.io/crates/librclone/reverse_dependencies</p>
<p>🐇 The following two projects depend on librclone:</p>
<ul>
<li>https://github.com/Sh3mm/WarpDrive/tree/master</li>
<li>https://github.com/hwittenborn/celeste</li>
</ul>
<p>🐢 Let’s clone Celeste. It uses librclone and looks like it’s a user interface for rclone written in Rust.</p>
<p>🐇 The examples are in <code>celeste/src/rclone.rs</code>.</p>
<p>🐢 Cool. Let’s take a look at how commands are run:</p>
<pre><code class="language-rust">    /// Common function for some of the below command.
    fn common(command: &amp;str, remote_name: &amp;str, path: &amp;str) -&gt; Result&lt;(), RcloneError&gt; {
        let resp = run(
            command,
            &amp;json!({
                "fs": get_remote_name(remote_name),
                "remote": util::strip_slashes(path),
            })
            .to_string(),
        );

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

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

╭───┬──────────────────────┬────────────────────┬──────────────────────────────╮
│ # │     headRefName      │       title        │             url              │
├───┼──────────────────────┼────────────────────┼──────────────────────────────┤
│ 0 │ storage-remove-16674 │ xvc storage remove │ https://github.com/iesahin/x │
│   │                      │                    │ vc/pull/270                  │
╰───┴──────────────────────┴────────────────────┴──────────────────────────────╯
</code></pre>
<p>🐢 Tests are passing; we can merge the PR. But our commit hook to check the CHANGELOG doesn’t work. That’s weird; we don’t get any errors when the CHANGELOG is not in the push set.</p>
<pre><code class="language-nu">tmux new-window -c ($env.HOME | path join github.com iesahin xvc.py)  nvim 
</code></pre>
<p>🐢 Now we can update the Python bindings as well.</p>
<p>🐇 We forgot to bump package versions. We need to create a checklist for releases.</p>
<ul>
<li>✅ #🌻 CREATE a release checklist (2025-03-25 17:52)</li>
</ul>
<p>🐢 Let’s check if the latest version is updated on PyPI.</p>
<pre><code class="language-bash">pypi xvc
</code></pre>
<p>🐇 Yes, it is.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 26</title>
      <published>2025-04-24T03:01:04+00:00</published>
      <updated>2025-04-24T03:01:04+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:01:04 +0000</pubDate>
      <link>https://emresahin.net/devlog-26/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-26/</guid>
      <description>🐢 We can add type checking to xvc.py ’s command-line handler. Currently, it builds a command line manually from the given options and parses it with clap . It’s error-prone. 🐇 What are the options, though? xvc.py is just a wrapper around xvc , and that was the easiest way to get it working. We ca...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>xvc.py</category>
      <category>clap</category>
      <category>command-line</category>
      <category>type-checking</category>
      <category>options</category>
      <content:encoded><![CDATA[<p>🐢 We can add type checking to <code>xvc.py</code>’s command-line handler. Currently, it builds a command line manually from the given options and parses it with <code>clap</code>. It’s error-prone.</p>
<p>🐇 What are the options, though? <code>xvc.py</code> is just a wrapper around <code>xvc</code>, and that was the easiest way to get it working. We can list all options manually in the headers as documentation, but it will be harder to maintain.</p>
<p>🐢 Once we start doing it, we’ll find a good way to simplify and shorten it.</p>
<p>🐇 Let’s start to work on <code>xvc file track</code>, then.</p>
<p>🐢 Worked on it a bit, and I decided it’s not worth it at the moment. We need to supply default values for most of the options. Maintaining a separate list of default values may not be feasible; it will be error-prone in a different way.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 25</title>
      <published>2025-04-24T02:57:26+00:00</published>
      <updated>2025-04-24T02:57:26+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:57:26 +0000</pubDate>
      <link>https://emresahin.net/devlog-25/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-25/</guid>
      <description>🐢 I want to update xvc.py to the latest version. 🐇 It should only be needed to update the dependency versions in Cargo.toml , right? 🐢 Let’s start with that. 🐇 We have interface changes regarding aliases; let’s start using uv for building. 🦊 Added requirements to pyproject.toml by running: uv add...</description>
      <category>XVC</category>
      <category>Python</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Python</category>
      <category>uv</category>
      <category>Package Management</category>
      <category>Requirements</category>
      <content:encoded><![CDATA[<p>🐢 I want to update <code>xvc.py</code> to the latest version.</p>
<p>🐇 It should only be needed to update the dependency versions in <code>Cargo.toml</code>, right?</p>
<p>🐢 Let’s start with that.</p>
<p>🐇 We have interface changes regarding aliases; let’s start using <code>uv</code> for building.</p>
<p>🦊 Added requirements to <code>pyproject.toml</code> by running:</p>
<pre><code class="language-bash">uv add -r requirements.txt
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog 24</title>
      <published>2025-04-24T02:54:54+00:00</published>
      <updated>2025-04-24T02:54:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:54:54 +0000</pubDate>
      <link>https://emresahin.net/devlog-24/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-24/</guid>
      <description>🐇 Is there a way to implement Default for CLI structs? 🐢 There is a way if we start from the configuration, not just files. The default configuration is a TOML document. We can make it an XvcConfiguration struct and load and store it with confy . 🐇 We have a cascading set of configurations, but m...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Configuration</category>
      <category>confy</category>
      <category>CLI</category>
      <content:encoded><![CDATA[<p>🐇 Is there a way to implement <code>Default</code> for CLI structs?</p>
<p>🐢 There is a way if we start from the configuration, not just files. The default
configuration is a TOML document. We can make it an <code>XvcConfiguration</code> struct and
load and store it with <code>confy</code>.</p>
<p>🐇 We have a cascading set of configurations, but maybe we can start from the
struct and serialize/deserialize it on demand.</p>
<p>🐲 I don’t think we need to update <code>xvc-config</code> at the moment. It’s working, and
we don’t need to alter its inner workings in the near future.</p>
<p>🐢 I agree. We can use the <a href="https://docs.rs/config/"><code>config</code></a> crate when we need to update and have
enough time to work on this.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 23</title>
      <published>2025-02-03T10:30:44+00:00</published>
      <updated>2025-02-03T10:30:44+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 10:30:44 +0000</pubDate>
      <link>https://emresahin.net/devlog-23/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-23/</guid>
      <description>🐇 Let’s turn to discussing the JSON output changes. We can add another option to XvcOutputLine , like XvcOutputLine::Json(T: Serialize) , that will output the type using serde_json . It won’t introduce any other type. 🐢 Yes, but what will T be? Although store structures have Serde implementations...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Serde</category>
      <category>JSON</category>
      <category>Serialization</category>
      <category>xvc file list</category>
      <content:encoded><![CDATA[<p>🐇 Let’s turn to discussing the JSON output changes. We can add another option
to <code>XvcOutputLine</code>, like <code>XvcOutputLine::Json(T: Serialize)</code>, that will output
the type using <code>serde_json</code>. It won’t introduce any other type.</p>
<p>🐢 Yes, but what will <code>T</code> be? Although store structures have Serde
implementations, they are not particularly useful for this.</p>
<p>🐇 We can have output types, named like <code>XvcFileListOutput</code>, that will be
converted to strings with serialization.</p>
<p>🦊 We do something similar in <code>xvc pipeline export</code> and <code>import</code> commands. We
use
<a href="https://github.com/iesahin/xvc/blob/main/pipeline/src/pipeline/schema.rs#L41"><code>XvcPipelineSchema</code></a>
and <code>XvcStepSchema</code> just for the import and export commands. We’ll write similar
structs for all JSON output and will use Serde to convert these to strings.</p>
<p>🐢 Unlike the <code>import</code> and <code>export</code> commands, we have optional fields in the
output, though. I don’t want content digests to appear in JSON output if they
are not required.</p>
<p>🐇 Let’s search for optional fields in Serde.</p>
<p>🦊 There is a <a href="https://docs.rs/optional-field/latest/optional_field/attr.serde_optional_fields.html">crate for optional
fields</a>.</p>
<p>🐢 We don’t need another crate for this. Serde has the
<a href="https://serde.rs/attr-skip-serializing.html"><code>skip_serializing_if</code></a> attribute
for fields. We can add <code>Option::is_none</code> as a method to these to skip outputting <code>None</code>
fields. All those fields, in this case, will be optional.</p>
<p>🐇 This is fine. We already use structs to format the <code>xvc file list</code> output. We
can just use them to output JSON.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 21</title>
      <published>2025-02-03T09:59:28+00:00</published>
      <updated>2025-02-03T09:59:28+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 09:59:28 +0000</pubDate>
      <link>https://emresahin.net/devlog-21/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-21/</guid>
      <description>🐢 Now, the next version will have a --json output for xvc file list . We can start working on it or update the Readme file? 🐇 What about adding at least command completions for Nushell? 🐢 Let’s read a bit about clap_complete_nushell . 🦊 There seems to be a nu-complete command. Let’s check its doc...</description>
      <category>xvc</category>
      <category>Development</category>
      <category>Nushell</category>
      <category>clap_complete_nushell</category>
      <category>carapace</category>
      <category>dynamic completions</category>
      <category>JSON</category>
      <category>Lazygit</category>
      <category>Rust</category>
      <category>XVC</category>
      <category>completions</category>
      <content:encoded><![CDATA[<p>🐢 Now, the next version will have a <code>--json</code> output for <code>xvc file list</code>. We
can start working on it or update the Readme file?</p>
<p>🐇 What about adding at least command completions for Nushell?</p>
<p>🐢 Let’s read a bit about <code>clap_complete_nushell</code>.</p>
<p>🦊 There seems to be a <code>nu-complete</code> command. Let’s check its documentation.</p>
<p>🐇 Nothing was found, and Kagi doesn’t help much either.</p>
<p>🐢 There is a completions document for Nushell: https://www.nushell.sh/book/custom_completions.html</p>
<p>🐇 There is a tool called carapace to provide completions across shells.</p>
<p>🐢 Its <a href="https://carapace-sh.github.io/carapace/carapace.html">documentation</a> is
thin, and I’m not sure if it supports dynamic completions out of the box. I
believe instead of adding a carapace setup, we can just write a Nushell
completion script that will use JSON output from the commands and add some
(maybe hidden) utility commands to support it.</p>
<p>🐇 There are a set of example scripts in the Nushell repo:
https://github.com/nushell/nu_scripts/tree/main/custom-completions</p>
<p>🐢 The reason I want to write custom completions for Nushell is that it will be
an exercise for the scripting language. <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/gh/gh-completions.nu"><code>gh</code>
completions</a>
are not as scary as a Bash script.</p>
<p>🐇 <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/git/git-completions.nu"><code>git</code>
completions</a>
are a better example for XVC. They simply run <code>git</code> whenever necessary. We can
start from a static completions command and update this with dynamic
completions manually. It will teach a lot.</p>
<p>🐢 I <a href="https://github.com/iesahin/nu_scripts">forked</a> the <code>nu_scripts</code> repo and
will add XVC completions script there.</p>
<p>🐇 Then let’s begin by adding Nushell static completions. Shall we add a
command for this?</p>
<p>🦊 Reviving the <code>completion</code> command we removed in 0.6.13?</p>
<p>🐢 We shouldn’t list it. We can make a <code>_comp</code> subcommand for the time being
and generate and distribute completions in the repository. When
<code>clap_complete_nushell</code> has the feature parity to provide dynamic completions,
we can remove these commands.</p>
<p>🐇 What will we use this for other than generating completions?</p>
<p>🐢 Maybe dynamic completions can call this as well.</p>
<p>🐇 Added Nushell static completions to be output using <code>xvc _comp generate-nushell</code>. Let’s bump up the version to 0.6.15.</p>
<pre><code>cargo set-version 0.6.15-alpha.1
   Upgrading xvc from 0.6.14 to 0.6.15-alpha.1
...
</code></pre>
<p>🐢 I noticed we forgot a line in the CLI command handler that asserts <code>xvc_root_opt.is_some()</code>, and this fails when we run <code>xvc</code> outside of repositories. We need to release this version quickly.</p>
<p>🐇 Oops, now, ok, let’s write a static Nushell generator and just release quickly.</p>
<p>🦊 Generating completions with</p>
<pre><code>xvc comp generate-nushell
</code></pre>
<p>🐢 Completion command is run with <code>comp</code> instead of <code>_comp</code>. Should we rename it?</p>
<p>🐇 Renamed it to <code>_comp</code>. It’s not hidden, but at least we can be sure that it won’t be misunderstood as a common command.</p>
<p>🐢 Bumping the version again. Now let’s source the generated script and test it.</p>
<pre><code>cargo set-version 0.6.15-alpha.2
   Upgrading xvc from 0.6.15-alpha.1 to 0.6.15-alpha.2
...
</code></pre>
<p>🦊 Yep, it works. We now have completions for Nushell.</p>
<p>🐢 Let’s update the completions documentation.</p>
<p>🐇 Done. Now, let’s take a look at CI and see what fails.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13070200765 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐢 It fails because of coverage, not the tests. <a href="https://github.com/iesahin/xvc/pull/266#issuecomment-2626794240">Codecov says</a> the new code isn’t tested.</p>
<p>🐇 The added <code>xvc _comp</code> command isn’t tested. We can add a test running those lines and testing if the command outputs a completion script.</p>
<p>🐢 We have a <a href="https://github.com/iesahin/xvc/blob/main/lib/tests/test_completions.rs#L18">test for completions</a>. We can add a test that runs the lines.</p>
<p>🐇 Added a test and bumping up the version.</p>
<pre><code class="language-nu">cargo set-version 0.6.15-alpha.3
   Upgrading xvc from 0.6.15-alpha.2 to 0.6.15-alpha.3
...
</code></pre>
<p>🦊 We can add some more coverage while waiting for the tests.</p>
<p>🐇 <a href="https://app.codecov.io/gh/iesahin/xvc/blob/main/logging%2Fsrc%2Flib.rs#L285"><code>XvcOutputLine</code> implementation</a> seems to have no tests. It’s weird because we use these everywhere.</p>
<p>🐢 I’m not sure we use this particular implementation; we just use <code>XvcOutputLine::Info(s)</code>, not <code>XvcOutputLine::info(s)</code> anywhere. We can delete these methods actually.</p>
<p>🐇 We’ll add JSON output via this particular struct. Can we refactor these to use formatting for JSON, for example? Or use these to output JSON?</p>
<p>🦊 We can add a formatter to <code>XvcOutputLine</code> to output structures.</p>
<p>🐢 The enum is now defined as:</p>
<pre><code class="language-rust">#[derive(Clone, Debug)]
pub enum XvcOutputLine {
    /// The output that we should be reporting to user
    Output(String),
    /// For informational messages
    Info(String),
    /// For debug output to show the internals of Xvc
    Debug(String),
    /// Warnings that are against some usual workflows
    Warn(String),
    /// Errors that interrupts a workflow but may be recoverable
    Error(String),
    /// Panics that interrupts the workflow and ends the program
    /// Note that this doesn't call panic! automatically
    Panic(String),
    /// Progress bar ticks.
    /// Self::Info is also used for Tick(1)
    Tick(usize),
}</code></pre>
<p>Here, these fields can also have a <code>formatter</code> that will render the string in a particular format. For example, the output can be</p>
<pre><code class="language-rust">XvcOutputLine::Output(XvcJsonFormatter, String)</code></pre>
<p>🐇 I’m not sure this is a good idea. <code>Output</code> already specifies this string as output. We can have a wrapper instead, like,</p>
<pre><code class="language-rust">struct XvcJsonOutput(Format&lt;XvcStructuredOutput&gt;, XvcStructuredOutput)</code></pre>
<p>and we can use the supplied format to render <code>XvcStructuredOutput</code> to an output line with <code>XvcOutputLine::Output</code>. If we don’t provide output as structured, it will be too much error-prone work to convert the current outputs to structured.</p>
<p>🦊 The transition will also be gradual. We may not need structured output for most of the commands. We can start with <code>xvc file list</code> and convert others as we go.</p>
<p>🐢 This is sensible. By the way, coverage still didn’t increase. There may be something going on with Codecov or running the test.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13087431038 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐇 Let’s run the test:</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
test test_completions ... ok

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

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

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

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

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

...

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

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

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

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

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

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

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

gh pr close 36
✓ Closed pull request iesahin/xvc.py#36 (Bump pyo3 from 0.22.2 to 0.23.3)
</code></pre>
<p>🐢 We have already upgraded to pyo3 0.23. No need for this. Let’s create a PR for the current branch.</p>
<pre><code>git push --set-upstream origin v0.6.13

ghpC --fill
https://github.com/iesahin/xvc.py/pull/37
</code></pre>
<p>🐇 We can also tag and push the tags.</p>
<p>🐢 I’d like to have some more coverage for certain parts of the code. Let’s add some tests.</p>
<p>🐇 It looks like we mainly lack the storage tests. They need configuration to add keys to GitHub, and we already skip some of these even in the Rust code.</p>
<p>🐢 Umm, I see. We also need a way to measure coverage. Could we do this with Codecov, I wonder?</p>
<p>🐇 I found an example here: https://github.com/codecov/example-python/blob/main/.github/workflows/ci.yml. It needs <code>coverage</code> and <code>pytest-cov</code> in the requirements.</p>
<p>🐢 Let’s try this then.</p>
<p>🐇 Added <code>coverage.yml</code> file. It’s simpler than the other GitHub action. Need to update the token now.</p>
<p>🐢 There is an issue installing the requirements.</p>
<p>🐇 I forgot to add <code>sudo</code> to <code>apt-get</code>. Will take care of it now.</p>
<p>🐢 Let’s check the run.</p>
<pre><code>ghrl
in_progress		Release v0.6.13	coverage	v0.6.13	pull_request	12556590550	2m31s	2024-12-31T06:59:58Z
in_progress		Release v0.6.13	publish-to-pypi	v0.6.13	pull_request	12556590548	2m31s	2024-12-31T06:59:58Z
...
</code></pre>
<p>🐇 It takes a while and we still didn’t add the <code>CODECOV_TOKEN</code>.</p>
<p>🐢 Let’s add it, and after that, we need to take a look at this <code>blink</code> configuration. It adds letters after the selection.</p>
<p>🐇 Added the secret and configured <code>xvc.py</code> for coverage. There is an error with the build, though. Maybe the command we should be using is <code>maturin develop</code> instead of <code>build</code> to make Xvc available for the environment.</p>
<p>🐢 Let’s update and try it then.</p>
<p>🐇 <code>maturin develop</code> requires a virtual environment.</p>
<p>🐢 I checked the options for <code>build</code>, and I think there is an option, but let’s search first.</p>
<p>🐇 I searched, but it looks like we can just pass an <code>--out</code> directory or install <code>xvc</code> from the <code>target/wheels/</code> directory. The second option requires less maintenance.</p>
<p>🐢 Okay. Let’s add a step to the action then.</p>
<p>🐇 Now, let’s wait for the run to finish with <code>gh run watch</code>.</p>
<p>🐢 It failed. Let’s take a look at the logs:</p>
<pre><code>ghrl
completed	failure	Release v0.6.13	coverage	v0.6.13	pull_request	12556939836	3m26s	2024-12-31T07:38:54Z

gh run view 12556939836 --log-failed
</code></pre>
<p>🐇 It looks like some of the tests are failing. Let’s run the tests locally.</p>
<p>🐢 We should run with the <code>--forked</code> option, and it looks like we have a test to update with the xvc file list.</p>
<p>🐇 Updated the test, and I noticed we forgot to supply the new <code>--show-directories</code> option in the Python interface.</p>
<p>🐢 Yep. Passing these options as command-line options in strings is not robust. It’s very easy to forget things. I think we should start using CLI structs directly, but it’s not time yet.</p>
<p>🐇 I agree. It’s one of the goals for building a GUI, actually.</p>
<p>🐢 The tests failed again.</p>
<pre><code>ghrl | rg failure
completed	failure	Release v0.6.13	coverage	v0.6.13	pull_request	12557031091	3m49s	2024-12-31T07:51:19Z

gh run view 12557031091 --log-failed
</code></pre>
<p>🐇 There is a Git error now. We need to add a Git user and email to the action.</p>
<p>🐢 Added those and watching the results again now.</p>
<p>🐇 Why do you think the publish action always works? It can only run with the main branch, I think. No need to run it with other pushes.</p>
<p>🐢 Yes, let’s configure it now.</p>
<pre><code>ghrl | rg failure
completed	failure	Release v0.6.13	coverage	v0.6.13	pull_request	12557083393	3m21s	2024-12-31T07:59:18Z

gh run view 12557083393 --log-failed
</code></pre>
<p>🐇 It looks like we also need <code>xvc-test-helper</code> in the path. Let’s <code>cargo install</code> it and add it to the path.</p>
<p>🐢 Added <code>.cargo/bin</code> to the path like:</p>
<pre><code class="language-yaml">- name: Add cargo bin to PATH
  run: echo "$HOME/.cargo/bin" &gt;&gt; $GITHUB_PATH
</code></pre>
<p>and installed the helper with <code>cargo install xvc-test-helper</code>.</p>
<p>🐇 By the way, GitHub Copilot is hallucinating about a method to update the path.</p>
<p>🐢 I searched and it may not be hallucinating. We can use the <code>::add-path::</code> command with <code>echo</code>, it looks like. This is new to me.</p>
<p>🐇 The tests failed again.</p>
<pre><code>ghrl | rg failure
completed	failure	Release v0.6.13	coverage	v0.6.13	pull_request	12557208723	3m34s	2024-12-31T08:12:09Z

gh run view 12557208723 --log-failed
</code></pre>
<p>🐢 <code>file().list()</code> had a mistake, and we need to install <code>rg</code> for the tests.</p>
<p>🐇 Watching the test run. In the meantime, maybe we can review…</p>
<p>🐢 Failed again.</p>
<pre><code>ghrl | rg failure
completed	failure	Release v0.6.13	coverage	v0.6.13	pull_request	12557294105	3m46s	2024-12-31T08:21:17Z

gh run view 12557294105 --log-failed
</code></pre>
<p>🐇 Added <code>db.commit()</code> to two places in the code. This should pass now.</p>
<p>🐢 Yeah!</p>
<pre><code>ghrl | head -n 1
completed	success	Release v0.6.13	coverage	v0.6.13	pull_request	12557509836	3m46s	2024-12-31T08:44:33Z

gh run view 12557509836

✓ v0.6.13 coverage iesahin/xvc.py#37 · 12557509836
Triggered via pull_request about 4 minutes ago

JOBS
✓ linux in 3m36s (ID 35010253915)

ANNOTATIONS
! ubuntu-latest pipelines will use ubuntu-24.04 soon. For more details, see https://github.com/actions/runner-images/issues/10636
linux: .github#1


For more information about the job, try: gh run view --job=35010253915
View this run on GitHub: https://github.com/iesahin/xvc.py/actions/runs/12557509836
</code></pre>
<p>🐇 I can’t see a coverage report on codecov.io, though.</p>
<p>🐢 It says no coverage report is generated. Let’s test to generate XML files locally.</p>
<p>🐇 The option in the docs seems incorrect. <code>--cov-branch</code> doesn’t produce anything.</p>
<p>🐢 Let’s wait for the run again.</p>
<p>🐇 Should we add a badge to the README?</p>
<p>🐢 It won’t show much, but yeah, let’s make it.</p>
<p>🐇 The results are in and it shows 100% coverage. This means it doesn’t actually test anything.</p>
<p>🐢 We need Rust coverage for this. Let’s search for it.</p>
<p>🐇 I found this: https://github.com/cjermain/rust-python-coverage. It runs <code>cargo llvm-cov</code> with the project and measures test coverage. But we don’t have any Rust tests.</p>
<p>🐢 It looks like we don’t need Rust tests. <code>cargo llvm-cov</code> can check coverage with the Python as well.</p>
<pre><code class="language-bash">$ cargo llvm-cov show-env --export-prefix
export RUSTFLAGS=" -C instrument-coverage --cfg coverage --cfg trybuild_no_target"
export LLVM_PROFILE_FILE="/home/.../rust-python-coverage/target/rust-python-coverage-%m.profraw"
export CARGO_INCREMENTAL="0"
export CARGO_LLVM_COV_TARGET_DIR="/home/.../rust-python-coverage/target"
</code></pre>
<hr>
<p>🐢 Let’s take a look at what remained for 0.6.13.</p>
<p>🐇 I think there is nothing left. Python must be published when we merged the PR.</p>
<p>🐢 Let’s take a look by searching xvc python.</p>
<p>🐇 The PyPI page is https://pypi.org/project/xvc/ and it still reports the version as 0.6.11. There must be something.</p>
<p>🐢 Now let’s take a look at</p>
<p>https://github.com/iesahin/xvc.py</p>
<p>🐇 The run seems to be OK though.</p>
<p>https://github.com/iesahin/xvc.py/actions/runs/12557780141/job/35010938462</p>
<p>🐢 Maybe the version in <code>pyproject.toml</code> is still 0.6.11 and we forgot to update it?</p>
<p>tmux new-window -c $HOME/github.com/iesahin/xvc.py/ nvim</p>
<p>🐇 There is no version string in <code>~/github.com/iesahin/xvc.py/pyproject.toml</code>. It’s <em>dynamic</em>.</p>
<p>🐢 Then, let’s try to publish from local now.</p>
<p>🐇 The <code>iex</code> username requires email verification.</p>
<p>🐢 It looks like I publish xvc through the <code>iesahin</code> account, not <code>iex</code>. Maybe I can add both accounts to the project.</p>
<p>🐇 There seem to be no errors on the PyPI site.</p>
<p>🐢 Updating the token. Let’s add the token to pass to run <code>maturin publish</code>.</p>
<p>🐇 We’re receiving invalid or non-existent authentication information. Upgraded <code>maturin</code> to see if it fixes the issue.</p>
<p>🐢 We can also double-check the key.</p>
<p>🐇 Uploaded successfully from local. Let’s check the job again.</p>
<p>🐢 The release job was skipped because we didn’t tag after the merge. https://github.com/iesahin/xvc.py/actions/runs/12557780141/job/35011303045 That looks like the reason.</p>
<p>🐇 I should be more careful which is run and which is skipped.</p>
<p>🐢 Maybe we can relax the condition. We do this rarely. Maybe republishing is alright?</p>
<p>🐇 Yep, removed that. The jobs are running now. Let’s watch them to see what happens when we publish some of the packages.</p>
<p>🐢 In the meantime, let’s experiment with searching commands file with <code>fzf-lua</code>.</p>
<p>🐇 It requires more experimentation, but we can start from https://github.com/ibhagwan/fzf-lua/wiki/Advanced#interactive-shell-command.</p>
<p>🐢 The xvc publish jobs failed, btw.</p>
<pre><code>ghrl
completed	failure	Remove if condition from release	publish-to-pypi	v0.6.13	push	12568863716	15m26s	2025-01-01T08:30:03Z

gh run view 12568863716 --log-failed
...
Release	Run actions/download-artifact@v4.1.7	        Please ensure that your artifact is not expired and the artifact was uploaded using a compatible version of toolkit/upload-artifact.
...
</code></pre>
<p>🐇 It was using an older version of <code>upload-artifact</code>.</p>
<p>🐢 Rerunning the job and it looks like it runs for both push to <code>main</code> and tag with <code>v0.6.13</code>. We can turn off push to <code>main</code>, I believe.</p>
<p>🐇 There was a missing <code>upload-artifact</code> again. Fixed and updated the tags.</p>
<p>ghrl
completed	failure	update upload-artifacts	publish-to-pypi	main	push	12569060915	14m39s	2025-01-01T08:57:22Z</p>
<p>ghrf 12569060915</p>
<p>🐢 There are conflicts with uploaded artifacts now. We may need to clean up the artifacts manually.</p>
<p>🐇 The conflicts were not about inter-workflow names. The names were conflicting because all files were named <code>wheel</code>. I added the platform and target to the names to avoid conflicts.</p>
<p>🐢 Let’s wait then. Maybe it will work this time. Could you search for a Lua console for Neovim?</p>
<p>🐇 Let’s try this one: return {
“yarospace/lua-console.nvim”,
lazy = true, keys = “`”, opts = {},
}</p>
<p>🐢 I couldn’t make it run but won’t spend much time ATM. How about the jobs?</p>
<pre><code>ghrl
completed	failure	update upload artifact names	publish-to-pypi	v0.6.13	push	12569298414	13m57s	2025-01-01T09:27:00Z

ghrf 12569298414
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9180510Z ##[group]Run actions/download-artifact@v4
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9182098Z with:
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9182859Z   name: wheels
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9183843Z   merge-multiple: false
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9184817Z   repository: iesahin/xvc.py
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9185820Z   run-id: 12569298414
Release	Run actions/download-artifact@v4	2025-01-01T09:40:52.9186903Z ##[endgroup]
Release	Run actions/download-artifact@v4	2025-01-01T09:40:53.1937617Z Downloading single artifact
Release	Run actions/download-artifact@v4	2025-01-01T09:40:53.4694769Z ##[error]Unable to download artifact(s): Artifact not found for name: wheels
Release	Run actions/download-artifact@v4	        Please ensure that your artifact is not expired and the artifact was uploaded using a compatible version of toolkit/upload-artifact.
Release	Run actions/download-artifact@v4	        For more information, visit the GitHub Artifacts FAQ: https://github.com/actions/toolkit/blob/main/packages/artifact/docs/faq.md
</code></pre>
<p>🐇 Now the download doesn’t work.</p>
<p>🐢 Update it to download using patterns.</p>
<p>🐇 Did so and let’s take a look at the jobs again.</p>
<pre><code>ghrl
completed	failure	added pattern to download	.github/workflows/publish.yml	main	push	12569456859	0s	2025-01-01T09:49:19Z

ghrv 12569456859

X main .github/workflows/publish.yml · 12569456859
Triggered via push about 3 minutes ago

X This run likely failed because of a workflow file issue.

For more information, see: https://github.com/iesahin/xvc.py/actions/runs/12569456859
</code></pre>
<p>🐢 The line with the pattern was reported as broken.</p>
<p>🐇  Removed it and recommitted.</p>
<pre><code>ghrl
✅ completed	success	remove pattern to download all	publish-to-pypi	v0.6.13	push	12569509435	14m41s	2025-01-01T09:58:49Z
</code></pre>
<p>🐢 And now this completes the <code>v0.6.13</code> release.</p>
<p>🐇 We’ll see how it will work next time.</p>
<p>🐢 Yep. Let’s move on to the GUI for now. Is that okay with you?</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>Differences between DVC and Xvc</title>
      <published>2024-07-17T09:57:44+00:00</published>
      <updated>2024-07-17T09:57:44+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 17 Jul 2024 09:57:44 +0000</pubDate>
      <link>https://emresahin.net/differences-between-dvc-and-xvc/</link>
      <guid isPermaLink="true">https://emresahin.net/differences-between-dvc-and-xvc/</guid>
      <description>I wrote this on Reddit; let’s put it here too. Full list of similarities and differences is rather long. Let me summarize it. Xvc has different commands; xvc file track is used instead of dvc add . Xvc doesn’t add files (like .dvc files) to your repository and keeps all metadata tracking under th...</description>
      <category>xvc</category>
      <category>MLOps</category>
      <category>xvc</category>
      <category>dvc</category>
      <category>mlops</category>
      <category>data-versioning</category>
      <category>rclone</category>
      <category>s5cmd</category>
      <category>data-science</category>
      <content:encoded><![CDATA[<p>I wrote <a href="https://www.reddit.com/r/mlops/comments/1e4vvv3/xvc_a_free_as_in_freedom_cli_and_python_tool_to/">this</a> on Reddit; let’s put it here too.</p>
<p><a href="https://docs.xvc.dev/start/from-dvc">Full list of similarities and differences</a> is rather long. Let me summarize it.</p>
<p>Xvc has different commands; <code>xvc file track</code> is used instead of <code>dvc add</code>. Xvc doesn’t add files (like <code>.dvc</code> files) to your repository and keeps all metadata tracking under the <code>.xvc</code> directory. <a href="https://docs.xvc.dev/ref/xvc-file-recheck"><em>Checkout method</em></a> is per-file, not configured globally, so you can keep track of your data directory with symlinks and your model directory as copies. Xvc uses BLAKE3 as the default hashing algorithm, and you can configure this to be BLAKE2, SHA-2, or SHA-3.</p>
<p>Pipelines are not defined using YAML files. You can write a shell script with <code>xvc pipeline step ...</code> or use Python <code>xvc.pipeline().step().dependency(step_name="preprocess", param="hyperparams.yaml::batch_size")</code> to define pipelines first. Then you can use <code>xvc pipeline export</code> and <code>xvc pipeline import</code> to modify the pipeline in YAML, JSON, or TOML.</p>
<p>There are <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency">more dependency options</a>; e.g., a pipeline step may depend on a text file partially, by <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#regex-item-dependencies">regex</a> or by the <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#line-item-dependencies">line</a> options. There is a <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#generic-command-dependencies">generic dependency</a> option; the output of a shell command can be used as a dependency to a step.</p>
<p><a href="https://docs.xvc.dev/ref/xvc-storage-new">Remote storage options</a> are rather limited for Xvc; local, ssh+rsync, and S3-compatible storages are supported for now. There is also a <code>generic</code> storage option where you can define upload and download commands for the tool you’re using, e.g., <code>rclone</code> or <code>s5cmd</code>, and Xvc can use it. I’ll add Azure and rclone as natively supported storage options eventually, but I don’t like the idea of keeping credentials, so there won’t be any OAuth-required storage options, e.g., Google Drive. (You’ll be able to use these through rclone, though.) All Xvc storages use environment variables for authentication.</p>
<p>Xvc doesn’t have experiment tracking yet. <a href="https://docs.xvc.dev/how-to/git-branches">You can use <code>--from-ref</code> and <code>--to-branch</code> options</a> to store artifacts from the pipeline to different branches. I’ll add features to run pipelines and commands quickly and compare these eventually (I need one too), but it may take some time.</p>
<p>Xvc doesn’t track anything about the user. It shouldn’t make any network connections if you don’t specifically ask it to do so. I’m planning to add binaries that only do file operations or pipeline operations, so if someone doesn’t need pipeline features, they will simply use <code>xvc-file</code>.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
