Skip to content

Instantly share code, notes, and snippets.

@EliahKagan
Last active June 26, 2024 00:14
Show Gist options
  • Save EliahKagan/92cc6aaa7805ca09787f722bb558f8c2 to your computer and use it in GitHub Desktop.
Save EliahKagan/92cc6aaa7805ca09787f722bb558f8c2 to your computer and use it in GitHub Desktop.
gitoxide bug report draft - symlinks to strangely named targets
Checkout fails when Windows symlinks have strangely named targets

Windows permits symbolic links to specify targets with unusual names, such as invalid filenames like ??? and reserved legacy DOS device names like CON. Cloning a repository on Windows with gix clone that contains such a symlink fails, due to the error that occurs when attempting to look up metadata for such a target, which is done to figure out if a file symlink or directory symlink should be created.

gix clone has supported creating dangling symlinks on Windows since 31d02a8 (#1363), which fixed #1354 by special-casing NotFound errors and falling back to creating a file symlink:

https://github.com/Byron/gitoxide/blob/8d89b865c84d1fb153d93343d1ce4e1d64e53541/gix-fs/src/symlink.rs#L44-L48

Most cases of dangling symlinks are covered by NotFound errors. This includes cases where there is no target file at the destination, where there is no destination (e.g., the target path has nonexistent intermediate components), and possibly some other cases where errors prevent the target from being visible. However, this does not cover all errors. When the target is an invalid or reserved name, checkout fails.

Furthermore, it is not only those specific symlinks that are refused; checkout as a whole fails, and gix clone leaves no cloned directory behind. This can be seen when cloning a repository with a symlink to ???:

C:\Users\ek\src> gix clone git@github.com:EliahKagan/symlink-to-qmarks.git
 18:04:17 indexing done 7.0 objects in 0.00s (30.0k objects/s)
 18:04:17 decompressing done 1.5KB in 0.00s (6.3MB/s)
 18:04:17     Resolving done 7.0 objects in 0.05s (136.0 objects/s)
 18:04:17      Decoding done 1.5KB in 0.05s (29.5KB/s)
 18:04:17 writing index file done 1.3KB in 0.00s (8.6MB/s)
 18:04:17  create index file done 7.0 objects in 0.06s (115.0 objects/s)
 18:04:17          read pack done 1.1KB in 0.10s (10.7KB/s)
Error: IO error while writing blob or reading file metadata or changing filetype

Caused by:
    The filename, directory name, or volume label syntax is incorrect. (os error 123)
C:\Users\ek\src> ls symlink-to-qmarks
Get-ChildItem: Cannot find path 'C:\Users\ek\src\symlink-to-qmarks' because it does not exist.

And when cloning a repository with a symlink to CON:

C:\Users\ek\src> gix clone git@github.com:EliahKagan/symlink-to-con.git
 18:09:42 indexing done 7.0 objects in 0.00s (24.6k objects/s)
 18:09:42 decompressing done 1.6KB in 0.00s (5.6MB/s)
 18:09:42     Resolving done 7.0 objects in 0.05s (136.0 objects/s)
 18:09:42      Decoding done 1.6KB in 0.05s (32.2KB/s)
 18:09:42 writing index file done 1.3KB in 0.00s (8.3MB/s)
 18:09:42  create index file done 7.0 objects in 0.06s (116.0 objects/s)
 18:09:42          read pack done 1.2KB in 0.06s (18.5KB/s)
Error: IO error while writing blob or reading file metadata or changing filetype

Caused by:
    The parameter is incorrect. (os error 87)
C:\Users\ek\src> ls symlink-to-con
Get-ChildItem: Cannot find path 'C:\Users\ek\src\symlink-to-con' because it does not exist.

More detailed reproduction is presented below.

This is only relevant when gix operates with a configuration that allows it to create symlinks. When it detects that it cannot create any symlinks at all, or when it is invoked with -c core.symlinks=false, it creates regular files instead of symlinks, so the behavior described here does not occur. Note that gix will usually attempt to create symlinks, though, and as of this writing this bug can be observed even when core.symlinks is set to false globally, due to #1353.

Option 1: Create the symlinks

The strange symlink, which is typically a dangling symlink (see below for details), should probably just be created. This is simple, should be the simplest kind of fix to implement, and would make gix behave more closely to Git. As detailed in the next section on Git behavior, Git creates these without complaint.

It should be feasible to implement this by using an expression like orig_abs.is_dir() in place of the entire existing match logic, because Path::is_dir performs an fs::metadata check, as we are currently doing explicitly, and then converts all errors to false, rather than just NotFound as we are currently doing. I intend to open a PR for this shortly.

(Such a change would leave the behavior invariant with respect to what kind of symlink is created, in cases where a symlink is already being created. Since fs::metadata rather than fs::symlink_metadata is used, this behavior is to attempt to fully dereference the target if possible. If the target is a symlink whose ultimate target does not exist, or cannot be reached—such as due to the wrong kind of symlink, i.e. file vs. directory, appearing somewhere in the chain—then that is treated as an error. So a symlink to a directory symlink that dangles is currently created as a file symlink. This is not necessarily the best possible behavior, but I believe it should be considered independent of this bug and, if it is to be changed, then that can be done separately. Note that Git will sometimes do this as well, and always does so in the straightforward scenario that the ultimate target directory is not also being created.)

Option 2: Refuse with a specific error and check out other files

However, if for some reason it is intentional that symlink creation be refused, then gix should:

  • Report the condition specifically so that the user can understand what failed.
  • Check out other files, unless there is a clear design reason why that shouldn't be done.

I think there is probably no justification for refusing to create symlinks, even unusual ones. I recommend Option 1.

A note on the semantics of such symlinks

Symlinks whose targets are invalid filenames or reserved device names are, for the most part, special cases of dangling symlinks. It seems to me that the behavior of creating dangling symlinks, and making them file symlinks on Windows where the type is unknown because the target does not exist, should apply here.

The following two symlink demonstrations are done in cmd.exe. Usually PowerShell should be preferred, but cmd.exe is easier to use for symlink demonstrations because its mklink builtin creates symlinks and its dir command shows their exact symlink type (file or directory).

It is intuitive that a symlink to an invalid path like ??? is dangling, since no file named ??? exists. This shows that I can create the symlink, and attempting to create the target file by writing through the symlink fails:

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:24 PM    <DIR>          .
06/25/2024  06:24 PM    <DIR>          ..
               0 File(s)              0 bytes
               2 Dir(s)  84,951,973,888 bytes free

C:\Users\ek\tmp>mklink myqmarks ???
symbolic link created for myqmarks <<===>> ???

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:24 PM    <DIR>          .
06/25/2024  06:24 PM    <DIR>          ..
06/25/2024  06:24 PM    <SYMLINK>      myqmarks [???]
               1 File(s)              0 bytes
               2 Dir(s)  84,951,842,816 bytes free

C:\Users\ek\tmp>echo hello >myqmarks
The filename, directory name, or volume label syntax is incorrect.

It is less intuitive that a symlink to a reserved DOS device name like CON would be dangling, since in most file access APIs, such names, except in \\?\ paths, are treated as though they exist in every directory. So isn't a symlink to CON a non-dangling symlink to the CON device?

Although I would not be confident that no application or API treats it that way, the answer is generally no--instead, it refers to a file named CON in the current directory on the actual filesystem, which typically does not exist, but which can be created by writing through the symlink:

C:\Users\ek\tmp>del myqmarks

C:\Users\ek\tmp>echo hello >CON
hello

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:32 PM    <DIR>          .
06/25/2024  06:32 PM    <DIR>          ..
               0 File(s)              0 bytes
               2 Dir(s)  84,944,207,872 bytes free

C:\Users\ek\tmp>mklink mycon CON
symbolic link created for mycon <<===>> CON

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:33 PM    <DIR>          .
06/25/2024  06:33 PM    <DIR>          ..
06/25/2024  06:33 PM    <SYMLINK>      mycon [CON]
               1 File(s)              0 bytes
               2 Dir(s)  84,944,207,872 bytes free

C:\Users\ek\tmp>echo hello >mycon

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:33 PM    <DIR>          .
06/25/2024  06:33 PM    <DIR>          ..
06/25/2024  06:33 PM                 8 CON
06/25/2024  06:33 PM    <SYMLINK>      mycon [CON]
               2 File(s)              8 bytes
               2 Dir(s)  84,944,207,872 bytes free

C:\Users\ek\tmp>type mycon
hello

C:\Users\ek\tmp>type \\?\C:\Users\ek\tmp\CON
hello

C:\Users\ek\tmp>del \\?\C:\Users\ek\tmp\CON

C:\Users\ek\tmp>dir
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\tmp

06/25/2024  06:33 PM    <DIR>          .
06/25/2024  06:33 PM    <DIR>          ..
06/25/2024  06:33 PM    <SYMLINK>      mycon [CON]
               1 File(s)              0 bytes
               2 Dir(s)  84,944,076,800 bytes free

Git creates the symlinks to the strangely named targets, without complaint. Since these are dangling symlinks, Git creates them as file symlinks. I tested with:

C:\Users\ek\src> git version
git version 2.45.2.windows.1

Cloning the repository containing a symlink to ???:

C:\Users\ek\src> git clone git@github.com:EliahKagan/symlink-to-qmarks.git
Cloning into 'symlink-to-qmarks'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7 (delta 0), reused 7 (delta 0), pack-reused 0
Receiving objects: 100% (7/7), done.
C:\Users\ek\src> cmd /c dir symlink-to-qmarks
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\src\symlink-to-qmarks

06/25/2024  07:18 PM    <DIR>          .
06/25/2024  07:18 PM    <DIR>          ..
06/25/2024  07:18 PM               617 COPYING
06/25/2024  07:18 PM               298 README.md
06/25/2024  07:18 PM    <SYMLINK>      symlink [???]
               3 File(s)            915 bytes
               2 Dir(s)  83,582,005,248 bytes free

Cloning the repository containing a symlink to CON:

C:\Users\ek\src> git clone git@github.com:EliahKagan/symlink-to-con.git
Cloning into 'symlink-to-con'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7 (delta 0), reused 7 (delta 0), pack-reused 0
Receiving objects: 100% (7/7), done.
C:\Users\ek\src> cmd /c dir symlink-to-con
 Volume in drive C is OS
 Volume Serial Number is B203-10FB

 Directory of C:\Users\ek\src\symlink-to-con

06/25/2024  07:20 PM    <DIR>          .
06/25/2024  07:20 PM    <DIR>          ..
06/25/2024  07:20 PM               617 COPYING
06/25/2024  07:20 PM               441 README.md
06/25/2024  07:20 PM    <SYMLINK>      symlink [CON]
               3 File(s)          1,058 bytes
               2 Dir(s)  83,575,894,016 bytes free

I showed the output of the dir builtin of cmd.exe because it distinguishes file and directory symlinks, thereby verifying that Git creates them as file symlinks. If they had been directory symlinks, <SYMLINKD> would have been shown instead of <SYMLINK>.

The details of how those repositories were prepared are included below.

1. Create test repositories or examine the ones I made

Either create a repository with a symlink to ??? and a repository with a symlink to CON, or use the preexisting test repositories I created, symlink-to-qmarks and symlink-to-con. To verify that both cases are broken, these need to be two separate repositories, because the current behavior is to fail the checkout entirely and leave no cloned files.

I created both repositories on an Ubuntu system (and WSL is definitely sufficient), though it could be done in native Windows. Note that ln -s may not be sufficient to create symlinks on native Windows, even though ln exists in Git Bash; this may create copies instead.

A repository like symlink-to-qmarks can be created in a Unix-like system like this:

git init symlink-to-qmarks
cd symlink-to-qmarks
ln -s '???' symlink  # Create the dangling symlink.
git add .
git commit -m 'Initial commit'

A repository like symlink-to-con can be created in a Unix-like system analogously:

git init symlink-to-con
cd symlink-to-con
ln -s CON symlink  # Create the dangling symlink.
git add .
git commit -m 'Initial commit'

They can then be uploaded to remotes and used to test gix clone. I made them roughly like that, but I also added more commits with things like readme files, so it would be clear what they were.

All subsequent steps should be done on Windows. I will use SSH URLs here because the output with gix --trace is shorter and more readable, but I have also tested this with HTTPS URLs to verify that the problem is not specific to any transport (though it would be very strange if it were, especially since the specific cause of the bug is known, as detailed above).

2. Optionally, clone the repositories with git clone to verify Git behavior

This is detailed in "Git behavior" above, including specific commands to clone the repositories and to inspect the checked out contents.

This shows that, with Git, the clone succeeds, and the specific dangling symlinks (with strangely named targets) are created.

Before proceeding to test with gix, remove the created directories (or, alternatively, do all subsequent steps in a different directory) to avoid clashes confusing the experiment. They can be removed in PowerShell with rm -r -fo:

rm -r -fo symlink-to-qmarks
rm -r -fo symlink-to-con

3. Clone the repositories with gix clone to observe the bug

Output without --trace is shown above in "Current behavior" demonstrating that:

  • gix clone git@github.com:EliahKagan/symlink-to-qmarks.git fails and reports that the failure is "Caused by":

    The filename, directory name, or volume label syntax is incorrect. (os error 123)
    
  • gix clone git@github.com:EliahKagan/symlink-to-con.git fails and reports that the failure is "Caused by":

    The parameter is incorrect. (os error 87)
    

Inspection reveals that symlink-to-qmarks and symlink-to-con directories do not exist after the operation. Thus the clone as a whole failed, rather than just refusing to check out specific files.

4. Optionally, clone the repositories with gix --trace clone

When passing --trace, the output is very long for HTTPS URLs, which is why SSH URLs have been used throughout, so this output is reasonably short and can be compared directly to non---trace output (and to Git behavior).

Cloning the repository with a symlink to ??? with --trace looks like:

C:\Users\ek\src> gix --trace clone git@github.com:EliahKagan/symlink-to-qmarks.git
 20:03:05 indexing done 7.0 objects in 0.00s (25.4k objects/s)
 20:03:05 decompressing done 1.5KB in 0.00s (5.3MB/s)
 20:03:05     Resolving done 7.0 objects in 0.05s (136.0 objects/s)
 20:03:05      Decoding done 1.5KB in 0.05s (29.5KB/s)
 20:03:05 writing index file done 1.3KB in 0.00s (6.9MB/s)
 20:03:05  create index file done 7.0 objects in 0.06s (126.0 objects/s)
 20:03:05          read pack done 1.1KB in 0.07s (16.2KB/s)
 20:03:05            tracing INFO     run [ 1.09s | 3.73% / 100.00% ]
 20:03:05            tracing INFO     ┝━ open_from_paths() [ 135ms | 7.60% / 12.34% ]
 20:03:05            tracing INFO     │  ┝━ gix_path::git::install_config_path() [ 51.4ms | 4.71% ]
 20:03:05            tracing DEBUG    │  │  ┕━ 🐛 [debug]: invoking git for installation config path | cmd: "git.exe" "config" "-l" "--show-origin"
 20:03:05            tracing DEBUG    │  ┝━ 🐛 [debug]: invoking git to get system prefix/exec path | cmd: "git.exe" "--exec-path"
 20:03:05            tracing INFO     │  ┕━ gix_odb::Store::at() [ 326µs | 0.03% ]
 20:03:05            tracing INFO     ┝━ remote::Connection::ref_map() [ 735ms | 0.00% / 67.32% ]
 20:03:05            tracing INFO     │  ┕━ remote::Connection::fetch_refs() [ 735ms | 0.01% / 67.32% ]
 20:03:05            tracing DEBUG    │     ┝━ gix_protocol::handshake() [ 647ms | 59.26% ] service: UploadPack | extra_parameters: []
 20:03:05            tracing DEBUG    │     │  ┕━ 🐛 [debug]: gix_transport::SpawnProcessOnDemand | command: "C:\\Windows\\System32\\OpenSSH\\ssh.exe" "-o" "SendEnv=GIT_PROTOCOL" "git@github.com" "git-upload-pack" "\'EliahKagan/symlink-to-qmarks.git\'"
 20:03:05            tracing DEBUG    │     ┕━ gix_protocol::ls_refs() [ 87.9ms | 8.05% ] capabilities: Capabilities { data: "agent=git/github-74f58a100f14\nls-refs=unborn\nfetch=shallow wait-for-done filter\nserver-option\nobject-format=sha1", value_sep: 10 }
 20:03:05            tracing INFO     ┝━ fetch::Prepare::receive() [ 179ms | 0.28% / 16.41% ]
 20:03:05            tracing DEBUG    │  ┝━ negotiate [ 87.6ms | 0.09% / 8.03% ] protocol_version: 2
 20:03:05            tracing DEBUG    │  │  ┝━ mark_complete_and_common_ref [ 1.91ms | 0.14% / 0.18% ] mappings: 2
 20:03:05            tracing INFO     │  │  │  ┝━ mark_all_refs [ 333µs | 0.03% ]
 20:03:05            tracing DEBUG    │  │  │  ┝━ mark_alternate_refs [ 200ns | 0.00% ] num_odb: 0
 20:03:05            tracing INFO     │  │  │  ┝━ mark known_common [ 1.50µs | 0.00% ]
 20:03:05            tracing DEBUG    │  │  │  ┕━ mark tips [ 300ns | 0.00% ] num_tips: 0
 20:03:05            tracing DEBUG    │  │  ┕━ negotiate round [ 84.8ms | 7.76% ] round: 1
 20:03:05            tracing INFO     │  ┝━ gix_pack::Bundle::write_to_directory() [ 68.5ms | 6.28% ]
 20:03:05            tracing DEBUG    │  ┕━ update_refs() [ 20.0ms | 0.61% / 1.83% ] mappings: 2
 20:03:05            tracing DEBUG    │     ┕━ apply [ 13.3ms | 1.22% ] edits: 2
 20:03:05            tracing INFO     ┕━ gix::clone::PrepareCheckout::main_worktree() [ 2.16ms | 0.20% / 0.20% ]
 20:03:05            tracing INFO        ┕━ gix_index::State::from_tree() [ 15.3µs | 0.00% ]
Error: IO error while writing blob or reading file metadata or changing filetype

Caused by:
    The filename, directory name, or volume label syntax is incorrect. (os error 123)

Cloning the repository with a symlink to CON with --trace looks like:

C:\Users\ek\src> gix --trace clone git@github.com:EliahKagan/symlink-to-con.git
 20:04:08 indexing done 7.0 objects in 0.00s (12.6k objects/s)
 20:04:08 decompressing done 1.6KB in 0.00s (2.9MB/s)
 20:04:08     Resolving done 7.0 objects in 0.05s (137.0 objects/s)
 20:04:08      Decoding done 1.6KB in 0.05s (32.5KB/s)
 20:04:08 writing index file done 1.3KB in 0.00s (7.5MB/s)
 20:04:08  create index file done 7.0 objects in 0.06s (109.0 objects/s)
 20:04:08          read pack done 1.2KB in 0.07s (17.4KB/s)
 20:04:08            tracing INFO     run [ 1.20s | 4.57% / 100.00% ]
 20:04:08            tracing INFO     ┝━ open_from_paths() [ 143ms | 7.48% / 11.95% ]
 20:04:08            tracing INFO     │  ┝━ gix_path::git::install_config_path() [ 53.1ms | 4.44% ]
 20:04:08            tracing DEBUG    │  │  ┕━ 🐛 [debug]: invoking git for installation config path | cmd: "git.exe" "config" "-l" "--show-origin"
 20:04:08            tracing DEBUG    │  ┝━ 🐛 [debug]: invoking git to get system prefix/exec path | cmd: "git.exe" "--exec-path"
 20:04:08            tracing INFO     │  ┕━ gix_odb::Store::at() [ 326µs | 0.03% ]
 20:04:08            tracing INFO     ┝━ remote::Connection::ref_map() [ 792ms | 0.00% / 66.26% ]
 20:04:08            tracing INFO     │  ┕━ remote::Connection::fetch_refs() [ 792ms | 0.01% / 66.26% ]
 20:04:08            tracing DEBUG    │     ┝━ gix_protocol::handshake() [ 686ms | 57.39% ] service: UploadPack | extra_parameters: []
 20:04:08            tracing DEBUG    │     │  ┕━ 🐛 [debug]: gix_transport::SpawnProcessOnDemand | command: "C:\\Windows\\System32\\OpenSSH\\ssh.exe" "-o" "SendEnv=GIT_PROTOCOL" "git@github.com" "git-upload-pack" "\'EliahKagan/symlink-to-con.git\'"
 20:04:08            tracing DEBUG    │     ┕━ gix_protocol::ls_refs() [ 106ms | 8.86% ] capabilities: Capabilities { data: "agent=git/github-74f58a100f14\nls-refs=unborn\nfetch=shallow wait-for-done filter\nserver-option\nobject-format=sha1", value_sep: 10 }
 20:04:08            tracing INFO     ┝━ fetch::Prepare::receive() [ 204ms | 0.03% / 17.07% ]
 20:04:08            tracing DEBUG    │  ┝━ negotiate [ 91.5ms | 0.06% / 7.65% ] protocol_version: 2
 20:04:08            tracing DEBUG    │  │  ┝━ mark_complete_and_common_ref [ 1.11ms | 0.06% / 0.09% ] mappings: 2
 20:04:08            tracing INFO     │  │  │  ┝━ mark_all_refs [ 342µs | 0.03% ]
 20:04:08            tracing DEBUG    │  │  │  ┝━ mark_alternate_refs [ 200ns | 0.00% ] num_odb: 0
 20:04:08            tracing INFO     │  │  │  ┝━ mark known_common [ 2.00µs | 0.00% ]
 20:04:08            tracing DEBUG    │  │  │  ┕━ mark tips [ 200ns | 0.00% ] num_tips: 0
 20:04:08            tracing DEBUG    │  │  ┕━ negotiate round [ 89.7ms | 7.50% ] round: 1
 20:04:08            tracing INFO     │  ┝━ gix_pack::Bundle::write_to_directory() [ 68.1ms | 5.69% ]
 20:04:08            tracing DEBUG    │  ┕━ update_refs() [ 44.2ms | 1.32% / 3.69% ] mappings: 2
 20:04:08            tracing DEBUG    │     ┕━ apply [ 28.4ms | 2.38% ] edits: 2
 20:04:08            tracing INFO     ┕━ gix::clone::PrepareCheckout::main_worktree() [ 1.81ms | 0.15% / 0.15% ]
 20:04:08            tracing INFO        ┕━ gix_index::State::from_tree() [ 16.4µs | 0.00% ]
Error: IO error while writing blob or reading file metadata or changing filetype

Caused by:
    The parameter is incorrect. (os error 87)

Solution

My forthcoming PR will have tests that fail before the bugfix and pass afterwards. I have already written those tests, verified that they fail with no change to the code under test, written the fix, and verify that the tests pass with it. My pull request is not ready yet because:

  • I want to see if there are any symlink target related errors that are currently misinterpreted as collisions and, if so, to report that and to verify that this fixes that as well. I had originally meant to finish researching that and include it as part of this issue, but, if present, such errors are really a separate bug, due to the additional wrong behavior of being reported as collisions, and due to causing only individual symlinks not to be created rather than an entire clone and checkout to fail.
  • I need to generate some new archives for new fixtures, which I do on a separate machine from the Windows system where I am developing the fix (so all committed archives are generated on Unix-like systems).
  • It looks like there are no tests that directory symlinks are ever created on Windows. I'll look more into that and, if it is the case, I may open a new issue for that and/or include a fix in my PR. The reason this may be appropriate to cover at the same time is that it would facilitate verifying that my solution does not cause file symlinks to be created even in situations where directory symlinks are already intended to be created.

But when that is ready, running its own tests against the current version of the code will also work as verification, as would running the new fixtures manually to produce test repositories and then testing with those repositories.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment