Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Add Nix flake
Merged fintohaps opened 2 years ago

The following series of commits are made to get Nix machinery working.

The majority of the work on the patch was reworking some testing machinery by adding generator for commits so that we can easily populate a repository for testing purposes – avoiding using the native repository which wouldn’t work (easily) with nix flake check.


nix: remove nix tooling in favour of future flake.nix

Removing the Nix tooling managed by niv in favour of a future flake.nix.


fmt: remove .rustfmt.toml

The formatting configuration is not worth the maintenance of a nightly version. Remove it and allow the default formatting to be used instead.


test: improve commit and submodule testing

Two of the tests in radicle-git-ext and radicle-surf relied on using the Git repository itself. This can end up being an issue when sandboxing is involved.

This improves the situation by being able to generate commit data to add to a temporary repository.

It also adds a helper for creating a local, temporary submodule for testing the submodule interactions in the radicle-surf API.


nix: add Nix flake

Add the Nix flake machinery for developing using Nix tooling.

This requires that the following are added:

  • Cargo.lock
  • rust-toolchain

To ensure that versions are pinned.

62 files changed +2242 -607 ce858ec2 a37a40ee
added .envrc
@@ -0,0 +1 @@
+
use flake
modified .gitignore
@@ -1,7 +1,6 @@
target
.vscode
tags
-
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

deleted .rustfmt.toml
@@ -1,29 +0,0 @@
-
max_width = 100
-
# yeap
-
comment_width = 80
-
wrap_comments = true
-
hard_tabs = false
-
tab_spaces = 4
-
imports_layout = "HorizontalVertical"
-
imports_granularity = "Crate"
-

-
newline_style = "Unix"
-
use_small_heuristics = "Default"
-

-
reorder_imports = true
-
reorder_modules = true
-

-
remove_nested_parens = true
-

-
fn_args_layout = "Tall"
-

-
edition = "2018"
-

-
match_block_trailing_comma = true
-

-
merge_derives = true
-

-
use_try_shorthand = false
-
use_field_init_shorthand = false
-

-
force_explicit_abi = true
added Cargo.lock
@@ -0,0 +1,1306 @@
+
# This file is automatically @generated by Cargo.
+
# It is not intended for manual editing.
+
version = 3
+

+
[[package]]
+
name = "adler"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+

+
[[package]]
+
name = "aho-corasick"
+
version = "1.1.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+
dependencies = [
+
 "memchr",
+
]
+

+
[[package]]
+
name = "anstream"
+
version = "0.6.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+
dependencies = [
+
 "anstyle",
+
 "anstyle-parse",
+
 "anstyle-query",
+
 "anstyle-wincon",
+
 "colorchoice",
+
 "utf8parse",
+
]
+

+
[[package]]
+
name = "anstyle"
+
version = "1.0.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+

+
[[package]]
+
name = "anstyle-parse"
+
version = "0.2.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+
dependencies = [
+
 "utf8parse",
+
]
+

+
[[package]]
+
name = "anstyle-query"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+
dependencies = [
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "anstyle-wincon"
+
version = "3.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+
dependencies = [
+
 "anstyle",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "anyhow"
+
version = "1.0.80"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
+

+
[[package]]
+
name = "assert_matches"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+

+
[[package]]
+
name = "autocfg"
+
version = "1.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+

+
[[package]]
+
name = "base64"
+
version = "0.13.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+

+
[[package]]
+
name = "bit-set"
+
version = "0.5.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+
dependencies = [
+
 "bit-vec",
+
]
+

+
[[package]]
+
name = "bit-vec"
+
version = "0.6.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+

+
[[package]]
+
name = "bitflags"
+
version = "1.3.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+

+
[[package]]
+
name = "bitflags"
+
version = "2.4.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+

+
[[package]]
+
name = "bstr"
+
version = "1.9.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
+
dependencies = [
+
 "memchr",
+
 "regex-automata 0.4.6",
+
 "serde",
+
]
+

+
[[package]]
+
name = "cc"
+
version = "1.0.89"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
+
dependencies = [
+
 "jobserver",
+
 "libc",
+
]
+

+
[[package]]
+
name = "cfg-if"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+

+
[[package]]
+
name = "colorchoice"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+

+
[[package]]
+
name = "crc32fast"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+
dependencies = [
+
 "cfg-if",
+
]
+

+
[[package]]
+
name = "diff"
+
version = "0.1.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+

+
[[package]]
+
name = "either"
+
version = "1.10.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+

+
[[package]]
+
name = "env_filter"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
+
dependencies = [
+
 "log",
+
 "regex",
+
]
+

+
[[package]]
+
name = "env_logger"
+
version = "0.11.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d"
+
dependencies = [
+
 "anstream",
+
 "anstyle",
+
 "env_filter",
+
 "humantime",
+
 "log",
+
]
+

+
[[package]]
+
name = "errno"
+
version = "0.3.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+
dependencies = [
+
 "libc",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "fastrand"
+
version = "2.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+

+
[[package]]
+
name = "filetime"
+
version = "0.2.23"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+
dependencies = [
+
 "cfg-if",
+
 "libc",
+
 "redox_syscall",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "flate2"
+
version = "1.0.28"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+
dependencies = [
+
 "crc32fast",
+
 "miniz_oxide",
+
]
+

+
[[package]]
+
name = "fnv"
+
version = "1.0.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+

+
[[package]]
+
name = "form_urlencoded"
+
version = "1.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+
dependencies = [
+
 "percent-encoding",
+
]
+

+
[[package]]
+
name = "getrandom"
+
version = "0.2.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+
dependencies = [
+
 "cfg-if",
+
 "libc",
+
 "wasi",
+
]
+

+
[[package]]
+
name = "git-ref-format"
+
version = "0.3.0"
+
dependencies = [
+
 "git-ref-format-core",
+
 "git-ref-format-macro",
+
]
+

+
[[package]]
+
name = "git-ref-format-core"
+
version = "0.3.0"
+
dependencies = [
+
 "bstr",
+
 "minicbor",
+
 "percent-encoding",
+
 "serde",
+
 "thiserror",
+
]
+

+
[[package]]
+
name = "git-ref-format-macro"
+
version = "0.3.0"
+
dependencies = [
+
 "git-ref-format-core",
+
 "proc-macro-error",
+
 "quote",
+
 "syn 1.0.109",
+
]
+

+
[[package]]
+
name = "git-storage"
+
version = "0.1.0"
+
dependencies = [
+
 "either",
+
 "git2",
+
 "globset",
+
 "libc",
+
 "libgit2-sys",
+
 "parking_lot",
+
 "radicle-git-ext",
+
 "radicle-std-ext",
+
 "thiserror",
+
]
+

+
[[package]]
+
name = "git-storage-test"
+
version = "0.1.0"
+
dependencies = [
+
 "git-storage",
+
 "git2",
+
 "proptest",
+
 "radicle-git-ext",
+
 "radicle-git-ext-test",
+
 "test-helpers",
+
 "uuid",
+
]
+

+
[[package]]
+
name = "git2"
+
version = "0.18.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1b3ba52851e73b46a4c3df1d89343741112003f0f6f13beb0dfac9e457c3fdcd"
+
dependencies = [
+
 "bitflags 2.4.2",
+
 "libc",
+
 "libgit2-sys",
+
 "log",
+
 "url",
+
]
+

+
[[package]]
+
name = "globset"
+
version = "0.4.14"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+
dependencies = [
+
 "aho-corasick",
+
 "bstr",
+
 "log",
+
 "regex-automata 0.4.6",
+
 "regex-syntax 0.8.2",
+
]
+

+
[[package]]
+
name = "humantime"
+
version = "2.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+

+
[[package]]
+
name = "idna"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+
dependencies = [
+
 "unicode-bidi",
+
 "unicode-normalization",
+
]
+

+
[[package]]
+
name = "itoa"
+
version = "1.0.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+

+
[[package]]
+
name = "jobserver"
+
version = "0.1.28"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+
dependencies = [
+
 "libc",
+
]
+

+
[[package]]
+
name = "lazy_static"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+

+
[[package]]
+
name = "libc"
+
version = "0.2.153"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+

+
[[package]]
+
name = "libgit2-sys"
+
version = "0.16.2+1.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
+
dependencies = [
+
 "cc",
+
 "libc",
+
 "libz-sys",
+
 "pkg-config",
+
]
+

+
[[package]]
+
name = "libm"
+
version = "0.2.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+

+
[[package]]
+
name = "libz-sys"
+
version = "1.1.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6"
+
dependencies = [
+
 "cc",
+
 "libc",
+
 "pkg-config",
+
 "vcpkg",
+
]
+

+
[[package]]
+
name = "linux-raw-sys"
+
version = "0.4.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+

+
[[package]]
+
name = "lock_api"
+
version = "0.4.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+
dependencies = [
+
 "autocfg",
+
 "scopeguard",
+
]
+

+
[[package]]
+
name = "log"
+
version = "0.4.21"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+

+
[[package]]
+
name = "matchers"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+
dependencies = [
+
 "regex-automata 0.1.10",
+
]
+

+
[[package]]
+
name = "memchr"
+
version = "2.7.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+

+
[[package]]
+
name = "minicbor"
+
version = "0.13.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "124d887cb82f0b1469bdac3d1b65764a381eed1a54fdab0070e5772b13114521"
+

+
[[package]]
+
name = "miniz_oxide"
+
version = "0.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+
dependencies = [
+
 "adler",
+
]
+

+
[[package]]
+
name = "nonempty"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109"
+

+
[[package]]
+
name = "nu-ansi-term"
+
version = "0.46.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+
dependencies = [
+
 "overload",
+
 "winapi",
+
]
+

+
[[package]]
+
name = "num-traits"
+
version = "0.2.18"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+
dependencies = [
+
 "autocfg",
+
 "libm",
+
]
+

+
[[package]]
+
name = "once_cell"
+
version = "1.19.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+

+
[[package]]
+
name = "overload"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+

+
[[package]]
+
name = "parking_lot"
+
version = "0.12.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+
dependencies = [
+
 "lock_api",
+
 "parking_lot_core",
+
]
+

+
[[package]]
+
name = "parking_lot_core"
+
version = "0.9.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+
dependencies = [
+
 "cfg-if",
+
 "libc",
+
 "redox_syscall",
+
 "smallvec",
+
 "windows-targets 0.48.5",
+
]
+

+
[[package]]
+
name = "percent-encoding"
+
version = "2.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+

+
[[package]]
+
name = "pin-project-lite"
+
version = "0.2.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+

+
[[package]]
+
name = "pkg-config"
+
version = "0.3.30"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+

+
[[package]]
+
name = "ppv-lite86"
+
version = "0.2.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+

+
[[package]]
+
name = "pretty_assertions"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+
dependencies = [
+
 "diff",
+
 "yansi",
+
]
+

+
[[package]]
+
name = "proc-macro-error"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+
dependencies = [
+
 "proc-macro-error-attr",
+
 "proc-macro2",
+
 "quote",
+
 "syn 1.0.109",
+
 "version_check",
+
]
+

+
[[package]]
+
name = "proc-macro-error-attr"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "version_check",
+
]
+

+
[[package]]
+
name = "proc-macro2"
+
version = "1.0.78"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+
dependencies = [
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "proptest"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf"
+
dependencies = [
+
 "bit-set",
+
 "bit-vec",
+
 "bitflags 2.4.2",
+
 "lazy_static",
+
 "num-traits",
+
 "rand",
+
 "rand_chacha",
+
 "rand_xorshift",
+
 "regex-syntax 0.8.2",
+
 "rusty-fork",
+
 "tempfile",
+
 "unarray",
+
]
+

+
[[package]]
+
name = "quick-error"
+
version = "1.2.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+

+
[[package]]
+
name = "quote"
+
version = "1.0.35"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+
dependencies = [
+
 "proc-macro2",
+
]
+

+
[[package]]
+
name = "radicle-git-ext"
+
version = "0.7.0"
+
dependencies = [
+
 "git-ref-format",
+
 "git2",
+
 "percent-encoding",
+
 "radicle-std-ext",
+
 "serde",
+
 "thiserror",
+
]
+

+
[[package]]
+
name = "radicle-git-ext-test"
+
version = "0.1.0"
+
dependencies = [
+
 "assert_matches",
+
 "git2",
+
 "minicbor",
+
 "proptest",
+
 "radicle-git-ext",
+
 "serde",
+
 "serde_json",
+
 "test-helpers",
+
]
+

+
[[package]]
+
name = "radicle-std-ext"
+
version = "0.1.0"
+

+
[[package]]
+
name = "radicle-surf"
+
version = "0.19.0"
+
dependencies = [
+
 "anyhow",
+
 "base64",
+
 "flate2",
+
 "git2",
+
 "log",
+
 "nonempty",
+
 "radicle-git-ext",
+
 "radicle-std-ext",
+
 "serde",
+
 "tar",
+
 "thiserror",
+
 "url",
+
]
+

+
[[package]]
+
name = "radicle-surf-test"
+
version = "0.1.0"
+
dependencies = [
+
 "git2",
+
 "nonempty",
+
 "pretty_assertions",
+
 "proptest",
+
 "radicle-git-ext",
+
 "radicle-git-ext-test",
+
 "radicle-surf",
+
 "serde_json",
+
 "test-helpers",
+
 "url",
+
]
+

+
[[package]]
+
name = "rand"
+
version = "0.8.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+
dependencies = [
+
 "libc",
+
 "rand_chacha",
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "rand_chacha"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+
dependencies = [
+
 "ppv-lite86",
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "rand_core"
+
version = "0.6.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
dependencies = [
+
 "getrandom",
+
]
+

+
[[package]]
+
name = "rand_xorshift"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+
dependencies = [
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "redox_syscall"
+
version = "0.4.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+
dependencies = [
+
 "bitflags 1.3.2",
+
]
+

+
[[package]]
+
name = "regex"
+
version = "1.10.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+
dependencies = [
+
 "aho-corasick",
+
 "memchr",
+
 "regex-automata 0.4.6",
+
 "regex-syntax 0.8.2",
+
]
+

+
[[package]]
+
name = "regex-automata"
+
version = "0.1.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
dependencies = [
+
 "regex-syntax 0.6.29",
+
]
+

+
[[package]]
+
name = "regex-automata"
+
version = "0.4.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+
dependencies = [
+
 "aho-corasick",
+
 "memchr",
+
 "regex-syntax 0.8.2",
+
]
+

+
[[package]]
+
name = "regex-syntax"
+
version = "0.6.29"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+

+
[[package]]
+
name = "regex-syntax"
+
version = "0.8.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+

+
[[package]]
+
name = "rustix"
+
version = "0.38.31"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
+
dependencies = [
+
 "bitflags 2.4.2",
+
 "errno",
+
 "libc",
+
 "linux-raw-sys",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "rusty-fork"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+
dependencies = [
+
 "fnv",
+
 "quick-error",
+
 "tempfile",
+
 "wait-timeout",
+
]
+

+
[[package]]
+
name = "ryu"
+
version = "1.0.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+

+
[[package]]
+
name = "scopeguard"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+

+
[[package]]
+
name = "serde"
+
version = "1.0.197"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+
dependencies = [
+
 "serde_derive",
+
]
+

+
[[package]]
+
name = "serde_derive"
+
version = "1.0.197"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.52",
+
]
+

+
[[package]]
+
name = "serde_json"
+
version = "1.0.114"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+
dependencies = [
+
 "itoa",
+
 "ryu",
+
 "serde",
+
]
+

+
[[package]]
+
name = "sharded-slab"
+
version = "0.1.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+
dependencies = [
+
 "lazy_static",
+
]
+

+
[[package]]
+
name = "smallvec"
+
version = "1.13.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+

+
[[package]]
+
name = "syn"
+
version = "1.0.109"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "syn"
+
version = "2.0.52"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "tar"
+
version = "0.4.40"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
+
dependencies = [
+
 "filetime",
+
 "libc",
+
 "xattr",
+
]
+

+
[[package]]
+
name = "tempfile"
+
version = "3.10.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+
dependencies = [
+
 "cfg-if",
+
 "fastrand",
+
 "rustix",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "test-helpers"
+
version = "0.1.0"
+
dependencies = [
+
 "env_logger",
+
 "log",
+
 "minicbor",
+
 "pretty_assertions",
+
 "proptest",
+
 "serde",
+
 "serde_json",
+
 "tempfile",
+
 "tracing",
+
 "tracing-subscriber",
+
]
+

+
[[package]]
+
name = "tests"
+
version = "0.1.0"
+
dependencies = [
+
 "git-storage-test",
+
 "radicle-git-ext-test",
+
 "radicle-surf-test",
+
]
+

+
[[package]]
+
name = "thiserror"
+
version = "1.0.57"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+
dependencies = [
+
 "thiserror-impl",
+
]
+

+
[[package]]
+
name = "thiserror-impl"
+
version = "1.0.57"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.52",
+
]
+

+
[[package]]
+
name = "thread_local"
+
version = "1.1.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+
dependencies = [
+
 "cfg-if",
+
 "once_cell",
+
]
+

+
[[package]]
+
name = "tinyvec"
+
version = "1.6.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
dependencies = [
+
 "tinyvec_macros",
+
]
+

+
[[package]]
+
name = "tinyvec_macros"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+

+
[[package]]
+
name = "tracing"
+
version = "0.1.40"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+
dependencies = [
+
 "pin-project-lite",
+
 "tracing-attributes",
+
 "tracing-core",
+
]
+

+
[[package]]
+
name = "tracing-attributes"
+
version = "0.1.27"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.52",
+
]
+

+
[[package]]
+
name = "tracing-core"
+
version = "0.1.32"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+
dependencies = [
+
 "once_cell",
+
 "valuable",
+
]
+

+
[[package]]
+
name = "tracing-log"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+
dependencies = [
+
 "log",
+
 "once_cell",
+
 "tracing-core",
+
]
+

+
[[package]]
+
name = "tracing-serde"
+
version = "0.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
+
dependencies = [
+
 "serde",
+
 "tracing-core",
+
]
+

+
[[package]]
+
name = "tracing-subscriber"
+
version = "0.3.18"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+
dependencies = [
+
 "matchers",
+
 "nu-ansi-term",
+
 "once_cell",
+
 "regex",
+
 "serde",
+
 "serde_json",
+
 "sharded-slab",
+
 "smallvec",
+
 "thread_local",
+
 "tracing",
+
 "tracing-core",
+
 "tracing-log",
+
 "tracing-serde",
+
]
+

+
[[package]]
+
name = "unarray"
+
version = "0.1.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+

+
[[package]]
+
name = "unicode-bidi"
+
version = "0.3.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+

+
[[package]]
+
name = "unicode-ident"
+
version = "1.0.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+

+
[[package]]
+
name = "unicode-normalization"
+
version = "0.1.23"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+
dependencies = [
+
 "tinyvec",
+
]
+

+
[[package]]
+
name = "url"
+
version = "2.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+
dependencies = [
+
 "form_urlencoded",
+
 "idna",
+
 "percent-encoding",
+
 "serde",
+
]
+

+
[[package]]
+
name = "utf8parse"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+

+
[[package]]
+
name = "uuid"
+
version = "1.7.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+
dependencies = [
+
 "getrandom",
+
]
+

+
[[package]]
+
name = "valuable"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+

+
[[package]]
+
name = "vcpkg"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+

+
[[package]]
+
name = "version_check"
+
version = "0.9.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+

+
[[package]]
+
name = "wait-timeout"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+
dependencies = [
+
 "libc",
+
]
+

+
[[package]]
+
name = "wasi"
+
version = "0.11.0+wasi-snapshot-preview1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+

+
[[package]]
+
name = "winapi"
+
version = "0.3.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+
dependencies = [
+
 "winapi-i686-pc-windows-gnu",
+
 "winapi-x86_64-pc-windows-gnu",
+
]
+

+
[[package]]
+
name = "winapi-i686-pc-windows-gnu"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+

+
[[package]]
+
name = "winapi-x86_64-pc-windows-gnu"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+

+
[[package]]
+
name = "windows-sys"
+
version = "0.52.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+
dependencies = [
+
 "windows-targets 0.52.4",
+
]
+

+
[[package]]
+
name = "windows-targets"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+
dependencies = [
+
 "windows_aarch64_gnullvm 0.48.5",
+
 "windows_aarch64_msvc 0.48.5",
+
 "windows_i686_gnu 0.48.5",
+
 "windows_i686_msvc 0.48.5",
+
 "windows_x86_64_gnu 0.48.5",
+
 "windows_x86_64_gnullvm 0.48.5",
+
 "windows_x86_64_msvc 0.48.5",
+
]
+

+
[[package]]
+
name = "windows-targets"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+
dependencies = [
+
 "windows_aarch64_gnullvm 0.52.4",
+
 "windows_aarch64_msvc 0.52.4",
+
 "windows_i686_gnu 0.52.4",
+
 "windows_i686_msvc 0.52.4",
+
 "windows_x86_64_gnu 0.52.4",
+
 "windows_x86_64_gnullvm 0.52.4",
+
 "windows_x86_64_msvc 0.52.4",
+
]
+

+
[[package]]
+
name = "windows_aarch64_gnullvm"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+

+
[[package]]
+
name = "windows_aarch64_gnullvm"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+

+
[[package]]
+
name = "windows_aarch64_msvc"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+

+
[[package]]
+
name = "windows_aarch64_msvc"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+

+
[[package]]
+
name = "windows_i686_gnu"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+

+
[[package]]
+
name = "windows_i686_gnu"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+

+
[[package]]
+
name = "windows_i686_msvc"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+

+
[[package]]
+
name = "windows_i686_msvc"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+

+
[[package]]
+
name = "windows_x86_64_gnu"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+

+
[[package]]
+
name = "windows_x86_64_gnu"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+

+
[[package]]
+
name = "windows_x86_64_gnullvm"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+

+
[[package]]
+
name = "windows_x86_64_gnullvm"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+

+
[[package]]
+
name = "windows_x86_64_msvc"
+
version = "0.48.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+

+
[[package]]
+
name = "windows_x86_64_msvc"
+
version = "0.52.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+

+
[[package]]
+
name = "xattr"
+
version = "1.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
+
dependencies = [
+
 "libc",
+
 "linux-raw-sys",
+
 "rustix",
+
]
+

+
[[package]]
+
name = "yansi"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
modified Cargo.toml
@@ -7,3 +7,4 @@ members = [
  "test",
]
resolver = "2"
+
package.version = "0.18.0"

\ No newline at end of file
deleted default.nix
@@ -1,31 +0,0 @@
-
{ sources ? import ./nix/sources.nix
-
, pkgs ? import sources.nixpkgs {
-
    overlays = [ (import sources.rust-overlay) ];
-
  }
-
, rust-overlay ? pkgs.rust-bin.stable.latest.default
-
}:
-
let
-
  # TODO: remove once cargo-nextest is available in nixpkgs stable
-
  cargo-nextest = (pkgs.callPackage ./nix/cargo-nextest/default.nix { });
-
in
-
  with pkgs;
-
  mkShell {
-
    name = "build";
-
    buildInputs = [
-
        # cargo tooling
-
        cargo-deny
-
        cargo-nextest
-
        cargo-watch
-
        pkgs.rust-bin.nightly."2022-07-01".rustfmt
-

-
        # hard dependencies
-
        cmake
-
        openssl
-
        pkgconfig
-
        rust-overlay
-

-
        # testing utilities
-
        gettext # for `envsubst`
-
        socat
-
    ];
-
  }
added flake.lock
@@ -0,0 +1,175 @@
+
{
+
  "nodes": {
+
    "advisory-db": {
+
      "flake": false,
+
      "locked": {
+
        "lastModified": 1710178103,
+
        "narHash": "sha256-Zg8JWAjMWHXtpUI7/nUC8iq3jKONLNSGQSBno5Rho8A=",
+
        "owner": "rustsec",
+
        "repo": "advisory-db",
+
        "rev": "61f79bd5454eb6999417bea4701aa101c0897410",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "rustsec",
+
        "repo": "advisory-db",
+
        "type": "github"
+
      }
+
    },
+
    "crane": {
+
      "inputs": {
+
        "nixpkgs": [
+
          "nixpkgs"
+
        ]
+
      },
+
      "locked": {
+
        "lastModified": 1710003968,
+
        "narHash": "sha256-g8+K+mLiNG5uch35Oy9oDQBAmGSkCcqrd0Jjme7xiG0=",
+
        "owner": "ipetkov",
+
        "repo": "crane",
+
        "rev": "10484f86201bb94bd61ecc5335b1496794fedb78",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "ipetkov",
+
        "repo": "crane",
+
        "type": "github"
+
      }
+
    },
+
    "fenix": {
+
      "inputs": {
+
        "nixpkgs": "nixpkgs",
+
        "rust-analyzer-src": "rust-analyzer-src"
+
      },
+
      "locked": {
+
        "lastModified": 1710310970,
+
        "narHash": "sha256-YREcSnbDH7Es6MDvhmzPBBF3exEg2EP4sd3QCiPyWyI=",
+
        "owner": "nix-community",
+
        "repo": "fenix",
+
        "rev": "df74cae97f59a868ad355af6a703e7845d0ae648",
+
        "type": "github"
+
      },
+
      "original": {
+
        "id": "fenix",
+
        "type": "indirect"
+
      }
+
    },
+
    "flake-utils": {
+
      "inputs": {
+
        "systems": "systems"
+
      },
+
      "locked": {
+
        "lastModified": 1710146030,
+
        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "type": "github"
+
      }
+
    },
+
    "nixpkgs": {
+
      "locked": {
+
        "lastModified": 1709961763,
+
        "narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=",
+
        "owner": "nixos",
+
        "repo": "nixpkgs",
+
        "rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "nixos",
+
        "ref": "nixos-unstable",
+
        "repo": "nixpkgs",
+
        "type": "github"
+
      }
+
    },
+
    "nixpkgs_2": {
+
      "locked": {
+
        "lastModified": 1710307192,
+
        "narHash": "sha256-KZMc4hMS21gfneaMGZpdpMWsCFQdtiS0X9lGHIxERW0=",
+
        "owner": "NixOS",
+
        "repo": "nixpkgs",
+
        "rev": "7e972d239d09f5b7db6a43658d7888f4aa9eea1d",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "NixOS",
+
        "ref": "release-23.11",
+
        "repo": "nixpkgs",
+
        "type": "github"
+
      }
+
    },
+
    "root": {
+
      "inputs": {
+
        "advisory-db": "advisory-db",
+
        "crane": "crane",
+
        "fenix": "fenix",
+
        "flake-utils": "flake-utils",
+
        "nixpkgs": "nixpkgs_2",
+
        "rust-overlay": "rust-overlay"
+
      }
+
    },
+
    "rust-analyzer-src": {
+
      "flake": false,
+
      "locked": {
+
        "lastModified": 1710248332,
+
        "narHash": "sha256-5Ozfx1xSscOrdCQoTLvcJHTtxgQ15YrXpjqce4vdzkQ=",
+
        "owner": "rust-lang",
+
        "repo": "rust-analyzer",
+
        "rev": "a2e274142f35d21fd28d28655f4af8e8531ab649",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "rust-lang",
+
        "ref": "nightly",
+
        "repo": "rust-analyzer",
+
        "type": "github"
+
      }
+
    },
+
    "rust-overlay": {
+
      "inputs": {
+
        "flake-utils": [
+
          "flake-utils"
+
        ],
+
        "nixpkgs": [
+
          "nixpkgs"
+
        ]
+
      },
+
      "locked": {
+
        "lastModified": 1710295923,
+
        "narHash": "sha256-B7wIarZOh5nNnj4GTOOYcxAwVGTO8y0dRSOzd6PtYE8=",
+
        "owner": "oxalica",
+
        "repo": "rust-overlay",
+
        "rev": "a30facbf72f29e5c930f394f637559f46a855e8b",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "oxalica",
+
        "repo": "rust-overlay",
+
        "type": "github"
+
      }
+
    },
+
    "systems": {
+
      "locked": {
+
        "lastModified": 1681028828,
+
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "type": "github"
+
      }
+
    }
+
  },
+
  "root": "root",
+
  "version": 7
+
}
added flake.nix
@@ -0,0 +1,205 @@
+
{
+
  description = "Improved types and functionality for using Git in Radicle";
+

+
  inputs = {
+
    nixpkgs.url = "github:NixOS/nixpkgs/release-23.11";
+

+
    crane = {
+
      url = "github:ipetkov/crane";
+
      inputs.nixpkgs.follows = "nixpkgs";
+
    };
+

+
    rust-overlay = {
+
      url = "github:oxalica/rust-overlay";
+
      inputs = {
+
        nixpkgs.follows = "nixpkgs";
+
        flake-utils.follows = "flake-utils";
+
      };
+
    };
+

+
    flake-utils.url = "github:numtide/flake-utils";
+

+
    advisory-db = {
+
      url = "github:rustsec/advisory-db";
+
      flake = false;
+
    };
+
  };
+

+
  nixConfig = {
+
    keepOutputs = true;
+
  };
+

+
  outputs = {
+
    self,
+
    nixpkgs,
+
    crane,
+
    fenix,
+
    flake-utils,
+
    advisory-db,
+
    rust-overlay,
+
    ...
+
  }:
+
    flake-utils.lib.eachDefaultSystem (system: let
+
      pname = "radicle-git";
+
      pkgs = import nixpkgs {
+
        inherit system;
+
        overlays = [(import rust-overlay)];
+
      };
+

+
      inherit (pkgs) lib;
+

+
      rustToolChain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain;
+
      craneLib = (crane.mkLib pkgs).overrideToolchain rustToolChain;
+

+
      srcFilters = path: type:
+
      # Allow data/git-platinum.tgz
+
        (lib.hasSuffix "\.tgz" path)
+
        ||
+
        # Default filter from crane (allow .rs files)
+
        (craneLib.filterCargoSources path type);
+

+
      src = lib.cleanSourceWith {
+
        src = ./.;
+
        filter = srcFilters;
+
      };
+

+
      # Common arguments can be set here to avoid repeating them later
+
      commonArgs = {
+
        inherit pname;
+
        inherit src;
+
        strictDeps = true;
+

+
        buildInputs =
+
          [
+
            pkgs.git
+
            # Add additional build inputs here
+
          ]
+
          ++ lib.optionals pkgs.stdenv.isDarwin [
+
            # Additional darwin specific inputs can be set here
+
            pkgs.libiconv
+
          ];
+
      };
+

+
      # Build *just* the cargo dependencies, so we can reuse
+
      # all of that work (e.g. via cachix) when running in CI
+
      cargoArtifacts =
+
        craneLib.buildDepsOnly commonArgs;
+

+
      # Build the actual crate itself, reusing the dependency
+
      # artifacts from above.
+

+
      radicle-git-ext = craneLib.buildPackage (commonArgs
+
        // {
+
          inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-git-ext/Cargo.toml;});
+
          doCheck = false;
+
          inherit cargoArtifacts;
+
        });
+
      radicle-std-ext = craneLib.buildPackage (commonArgs
+
        // {
+
          inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-std-ext/Cargo.toml;});
+
          doCheck = false;
+
          inherit cargoArtifacts;
+
        });
+
      radicle-surf = craneLib.buildPackage (commonArgs
+
        // {
+
          inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-surf/Cargo.toml;});
+
          inherit cargoArtifacts;
+
          doCheck = false;
+
        });
+

+
      # Test crates
+
      radicle-git-ext-test = craneLib.buildPackage (commonArgs
+
        // {
+
          inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-git-ext/t/Cargo.toml;});
+
          inherit cargoArtifacts;
+
          doCheck = false;
+
        });
+
      radicle-surf-test = craneLib.buildPackage (commonArgs
+
        // {
+
          inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-surf/t/Cargo.toml;});
+
          inherit cargoArtifacts;
+
          doCheck = false;
+
        });
+
    in {
+
      # Formatter
+
      formatter = pkgs.alejandra;
+

+
      # Set of checks that are run: `nix flake check`
+
      checks = {
+
        # Build the crate as part of `nix flake check` for convenience
+
        inherit radicle-git-ext;
+
        inherit radicle-surf;
+
        inherit radicle-git-ext-test;
+
        inherit radicle-surf-test;
+

+
        # Run clippy (and deny all warnings) on the crate source,
+
        # again, reusing the dependency artifacts from above.
+
        #
+
        # Note that this is done as a separate derivation so that
+
        # we can block the CI if there are issues here, but not
+
        # prevent downstream consumers from building our crate by itself.
+
        clippy = craneLib.cargoClippy (commonArgs
+
          // {
+
            inherit cargoArtifacts;
+
            cargoClippyExtraArgs = "--all-targets -- --deny warnings";
+
          });
+

+
        doc = craneLib.cargoDoc (commonArgs
+
          // {
+
            inherit cargoArtifacts;
+
          });
+

+
        # Check formatting
+
        fmt = craneLib.cargoFmt {
+
          inherit pname;
+
          inherit src;
+
        };
+

+
        # Audit dependencies
+
        audit = craneLib.cargoAudit {
+
          inherit src advisory-db;
+
        };
+

+
        # Audit licenses
+
        deny = craneLib.cargoDeny {
+
          inherit pname;
+
          inherit src;
+
        };
+

+
        # Run tests with cargo-nextest
+
        nextest = craneLib.cargoNextest (commonArgs
+
          // {
+
            inherit cargoArtifacts;
+
            partitions = 1;
+
            partitionType = "count";
+
            nativeBuildInputs = [
+
              # git is required so the sandbox can access it.
+
              pkgs.git
+
            ];
+
            # Ensure dev is used since we rely on env variables being
+
            # set in tests.
+
            buildPhase = ''
+
              export CARGO_PROFILE=dev;
+
            '';
+
          });
+
      };
+

+
      packages = {
+
        inherit radicle-git-ext;
+
        inherit radicle-surf;
+
        inherit radicle-git-ext-test;
+
        inherit radicle-surf-test;
+
      };
+

+
      devShells.default = craneLib.devShell {
+
        # Extra inputs can be added here; cargo and rustc are provided by default.
+
        packages = [
+
          pkgs.cargo-deny
+
          pkgs.cargo-nextest
+
          pkgs.cargo-watch
+
          pkgs.ripgrep
+
          pkgs.rust-analyzer
+
        ];
+
      };
+
    });
+
}
modified git-storage/src/backend/write.rs
@@ -16,12 +16,9 @@ use git_ext::{
use crate::{
    odb,
    refdb::{
-
        self,
-
        resolve,
+
        self, resolve,
        write::{previous, Applied, Policy, SymrefTarget, Update, Updated},
-
        Read as _,
-
        Reference,
-
        Target,
+
        Read as _, Reference, Target,
    },
    signature::UserInfo,
};
@@ -67,7 +64,7 @@ impl Write {
                        .external_template(false),
                )?;
                Ok(backend)
-
            },
+
            }
            Ok(repo) => Ok(repo),
            Err(e) => Err(e),
        }?;
@@ -225,7 +222,7 @@ impl<'a> Transaction<'a> {
                } else {
                    None
                }
-
            },
+
            }
            None => None,
        };

@@ -289,7 +286,7 @@ impl<'a> Transaction<'a> {
            Some(src) => match src.target {
                Target::Direct { .. } if matches!(type_change, Policy::Abort) => {
                    Err(error::Update::TypeChange(name.into()))
-
                },
+
                }
                Target::Direct { .. } if matches!(type_change, Policy::Reject) => {
                    Ok(Either::Left(Update::Symbolic {
                        name,
@@ -298,7 +295,7 @@ impl<'a> Transaction<'a> {
                        previous,
                        reflog,
                    }))
-
                },
+
                }
                _ => {
                    let dst = self.refdb.find_reference(&target.name)?;
                    match dst {
@@ -309,14 +306,14 @@ impl<'a> Transaction<'a> {
                                Ok(Either::Right(
                                    self.symbolic_edit(name, target, prev, &reflog, is_ff)?,
                                ))
-
                            },
+
                            }
                            Target::Symbolic { .. } => Err(error::Update::TargetSymbolic(dst.name)),
                        },
                        None => Ok(Either::Right(
                            self.symbolic_edit(name, target, prev, &reflog, true)?,
                        )),
                    }
-
                },
+
                }
            },
            None => Ok(Either::Right(
                self.symbolic_edit(name, target, prev, &reflog, true)?,
@@ -355,7 +352,7 @@ impl<'a> Transaction<'a> {
            match given {
                None => {
                    panic!("BUG: the previous value for a reference to be removed was not given, but its existence SHOULD be guarded")
-
                },
+
                }
                Some(previous) => Ok(Either::Right(self.remove_edit(name, previous)?)),
            }
        }
modified git-storage/src/refdb.rs
@@ -60,7 +60,7 @@ impl<'a> TryFrom<git2::Reference<'a>> for Reference {
                    .ok_or(ParseReference::InvalidUtf8)
                    .and_then(|name| RefString::try_from(name).map_err(ParseReference::from))?;
                name.into()
-
            },
+
            }
            Some(oid) => oid.into(),
        };

modified git-storage/src/refdb/iter.rs
@@ -37,7 +37,7 @@ impl<'a, G: glob::Pattern + Debug> Iterator for ReferencesGlob<'a, G> {
                Ok(reference) => match reference.name() {
                    Some(name) if self.glob.matches(name) => {
                        return Some(Reference::try_from(reference).map_err(error::Iter::from))
-
                    },
+
                    }
                    _ => continue,
                },

modified git-storage/src/refdb/write/previous.rs
@@ -55,14 +55,14 @@ impl Edit {
                } else {
                    Ok(())
                }
-
            },
+
            }
            Self::MustNotExist => {
                if given.is_some() {
                    Err(DoesExist)
                } else {
                    Ok(())
                }
-
            },
+
            }
            Self::MustExistAndMatch(expected) => match given {
                Some(given) if &given == expected => Ok(()),
                Some(given) => Err(DoesNotMatch {
@@ -118,7 +118,7 @@ impl Remove {
                } else {
                    Ok(())
                }
-
            },
+
            }
            Self::MustExistAndMatch(expected) => match given {
                Some(given) if &given == expected => Ok(()),
                Some(given) => Err(DoesNotMatch {
deleted nix/cargo-nextest/default.nix
@@ -1,30 +0,0 @@
-
{ sources ? import ../sources.nix
-
, pkgs ? import sources.nixpkgs
-
}:
-
with pkgs;
-
rustPlatform.buildRustPackage rec {
-
  pname = "cargo-nextest";
-
  version = "0.9.9";
-

-
  src = fetchFromGitHub {
-
    owner = "nextest-rs";
-
    repo = "nextest";
-
    rev = "cargo-nextest-${version}";
-
    sha256 = "sha256-1s1N126S51kg7aOgAb8oMts1zJcO6QRn1fwbQf6ZaJ8=";
-
  };
-

-
  cargoSha256 = "sha256-JxZyl5Hti3Hh33e7H/pXhM6WkU0kDDml0naBPYzvNy4=";
-

-
  nativeBuildInputs = [
-
    pkg-config
-
  ];
-

-
  buildInputs = [
-
    openssl
-
    libiconv
-
  ] ++ lib.optionals stdenv.isDarwin [
-
    Security
-
  ];
-

-
  doCheck = false;
-
}
deleted nix/ci/fmt
@@ -1,4 +0,0 @@
-
#!/usr/bin/env bash
-
set -eoux pipefail
-

-
cargo fmt -- --check
deleted nix/ci/run
@@ -1,9 +0,0 @@
-
#!/usr/bin/env bash
-
set -eou pipefail
-

-
./nix/ci/fmt
-
./scripts/ci/lint
-
./scripts/ci/build
-
./scripts/ci/test
-
./scripts/ci/docs
-
./scripts/ci/advisory
deleted nix/sources.json
@@ -1,38 +0,0 @@
-
{
-
    "niv": {
-
        "branch": "master",
-
        "description": "Easy dependency management for Nix projects",
-
        "homepage": "https://github.com/nmattia/niv",
-
        "owner": "nmattia",
-
        "repo": "niv",
-
        "rev": "d67c25f29716fd2087e71352783fcce194303a9a",
-
        "sha256": "1813r42sz4pmv1syn38s281lmg2l7h779q4r33nn5azm7wy45yrh",
-
        "type": "tarball",
-
        "url": "https://github.com/nmattia/niv/archive/d67c25f29716fd2087e71352783fcce194303a9a.tar.gz",
-
        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-
    },
-
    "nixpkgs": {
-
        "branch": "release-21.11",
-
        "description": "Nix Packages collection",
-
        "homepage": "",
-
        "owner": "NixOS",
-
        "repo": "nixpkgs",
-
        "rev": "2766f77c32e171a04d59b636a91083bae862274e",
-
        "sha256": "1xk1f62n00z7q5i3pf4c8c4rlv5k4jwpgh0pqgzw1l40vhdkixk9",
-
        "type": "tarball",
-
        "url": "https://github.com/NixOS/nixpkgs/archive/2766f77c32e171a04d59b636a91083bae862274e.tar.gz",
-
        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-
    },
-
    "rust-overlay": {
-
        "branch": "master",
-
        "description": "Pure and reproducible nix overlay of binary distributed rust toolchains",
-
        "homepage": "",
-
        "owner": "oxalica",
-
        "repo": "rust-overlay",
-
        "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d",
-
        "sha256": "138pmz1ypw5j34bywfaffxkffsgfmd0s6889bdc69356cp11zvlm",
-
        "type": "tarball",
-
        "url": "https://github.com/oxalica/rust-overlay/archive/e86c0fb5d3a22a5f30d7f64ecad88643fe26449d.tar.gz",
-
        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-
    }
-
}
deleted nix/sources.nix
@@ -1,198 +0,0 @@
-
# This file has been generated by Niv.
-

-
let
-

-
  #
-
  # The fetchers. fetch_<type> fetches specs of type <type>.
-
  #
-

-
  fetch_file = pkgs: name: spec:
-
    let
-
      name' = sanitizeName name + "-src";
-
    in
-
    if spec.builtin or true then
-
      builtins_fetchurl { inherit (spec) url sha256; name = name'; }
-
    else
-
      pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
-

-
  fetch_tarball = pkgs: name: spec:
-
    let
-
      name' = sanitizeName name + "-src";
-
    in
-
    if spec.builtin or true then
-
      builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
-
    else
-
      pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
-

-
  fetch_git = name: spec:
-
    let
-
      ref =
-
        spec.ref or (
-
          if spec ? branch then "refs/heads/${spec.branch}" else
-
          if spec ? tag then "refs/tags/${spec.tag}" else
-
          abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"
-
        );
-
      submodules = spec.submodules or false;
-
      submoduleArg =
-
        let
-
          nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
-
          emptyArgWithWarning =
-
            if submodules
-
            then
-
              builtins.trace
-
                (
-
                  "The niv input \"${name}\" uses submodules "
-
                  + "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
-
                  + "does not support them"
-
                )
-
                { }
-
            else { };
-
        in
-
        if nixSupportsSubmodules
-
        then { inherit submodules; }
-
        else emptyArgWithWarning;
-
    in
-
    builtins.fetchGit
-
      ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
-

-
  fetch_local = spec: spec.path;
-

-
  fetch_builtin-tarball = name: throw
-
    ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
-
        $ niv modify ${name} -a type=tarball -a builtin=true'';
-

-
  fetch_builtin-url = name: throw
-
    ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
-
        $ niv modify ${name} -a type=file -a builtin=true'';
-

-
  #
-
  # Various helpers
-
  #
-

-
  # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
-
  sanitizeName = name:
-
    (
-
      concatMapStrings (s: if builtins.isList s then "-" else s)
-
        (
-
          builtins.split "[^[:alnum:]+._?=-]+"
-
            ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
-
        )
-
    );
-

-
  # The set of packages used when specs are fetched using non-builtins.
-
  mkPkgs = sources: system:
-
    let
-
      sourcesNixpkgs =
-
        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
-
      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
-
      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
-
    in
-
    if builtins.hasAttr "nixpkgs" sources
-
    then sourcesNixpkgs
-
    else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
-
      import <nixpkgs> { }
-
    else
-
      abort
-
        ''
-
          Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
-
          add a package called "nixpkgs" to your sources.json.
-
        '';
-

-
  # The actual fetching function.
-
  fetch = pkgs: name: spec:
-

-
    if ! builtins.hasAttr "type" spec then
-
      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
-
    else if spec.type == "file" then fetch_file pkgs name spec
-
    else if spec.type == "tarball" then fetch_tarball pkgs name spec
-
    else if spec.type == "git" then fetch_git name spec
-
    else if spec.type == "local" then fetch_local spec
-
    else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
-
    else if spec.type == "builtin-url" then fetch_builtin-url name
-
    else
-
      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
-

-
  # If the environment variable NIV_OVERRIDE_${name} is set, then use
-
  # the path directly as opposed to the fetched source.
-
  replace = name: drv:
-
    let
-
      saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
-
      ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
-
    in
-
    if ersatz == "" then drv else
-
      # this turns the string into an actual Nix path (for both absolute and
-
      # relative paths)
-
    if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
-

-
  # Ports of functions for older nix versions
-

-
  # a Nix version of mapAttrs if the built-in doesn't exist
-
  mapAttrs = builtins.mapAttrs or (
-
    f: set: with builtins;
-
    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
-
  );
-

-
  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
-
  range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
-

-
  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
-
  stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
-

-
  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
-
  stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
-
  concatMapStrings = f: list: concatStrings (map f list);
-
  concatStrings = builtins.concatStringsSep "";
-

-
  # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
-
  optionalAttrs = cond: as: if cond then as else { };
-

-
  # fetchTarball version that is compatible between all the versions of Nix
-
  builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
-
    let
-
      inherit (builtins) lessThan nixVersion fetchTarball;
-
    in
-
    if lessThan nixVersion "1.12" then
-
      fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
-
    else
-
      fetchTarball attrs;
-

-
  # fetchurl version that is compatible between all the versions of Nix
-
  builtins_fetchurl = { url, name ? null, sha256 }@attrs:
-
    let
-
      inherit (builtins) lessThan nixVersion fetchurl;
-
    in
-
    if lessThan nixVersion "1.12" then
-
      fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
-
    else
-
      fetchurl attrs;
-

-
  # Create the final "sources" from the config
-
  mkSources = config:
-
    mapAttrs
-
      (
-
        name: spec:
-
          if builtins.hasAttr "outPath" spec
-
          then
-
            abort
-
              "The values in sources.json should not have an 'outPath' attribute"
-
          else
-
            spec // { outPath = replace name (fetch config.pkgs name spec); }
-
      )
-
      config.sources;
-

-
  # The "config" used by the fetchers
-
  mkConfig =
-
    { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
-
    , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile)
-
    , system ? builtins.currentSystem
-
    , pkgs ? mkPkgs sources system
-
    }: rec {
-
      # The sources, i.e. the attribute set of spec name to spec
-
      inherit sources;
-

-
      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
-
      inherit pkgs;
-
    };
-

-
in
-
mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); }
modified radicle-git-ext/git-ref-format/core/src/cbor.rs
@@ -8,18 +8,12 @@ use std::convert::TryFrom;
use minicbor::{
    decode,
    encode::{self, Write},
-
    Decode,
-
    Decoder,
-
    Encode,
-
    Encoder,
+
    Decode, Decoder, Encode, Encoder,
};

use crate::{
    refspec::{PatternStr, PatternString},
-
    Namespaced,
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    Namespaced, Qualified, RefStr, RefString,
};

impl<'de: 'a, 'a> Decode<'de> for &'a RefStr {
modified radicle-git-ext/git-ref-format/core/src/check.rs
@@ -101,6 +101,6 @@ pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> {
            } else {
                Ok(())
            }
-
        },
+
        }
    }
}
modified radicle-git-ext/git-ref-format/core/src/deriv.rs
@@ -10,12 +10,9 @@ use std::{
};

use crate::{
-
    lit,
-
    name,
+
    lit, name,
    refspec::{PatternStr, QualifiedPattern},
-
    Component,
-
    RefStr,
-
    RefString,
+
    Component, RefStr, RefString,
};

/// A fully-qualified refname.
modified radicle-git-ext/git-ref-format/core/src/name.rs
@@ -14,8 +14,7 @@ use std::{
use crate::{
    check,
    refspec::{PatternStr, PatternString},
-
    Namespaced,
-
    Qualified,
+
    Namespaced, Qualified,
};

mod iter;
@@ -285,7 +284,7 @@ impl RefString {
            Some(idx) => {
                self.0.truncate(idx);
                true
-
            },
+
            }
        }
    }

modified radicle-git-ext/git-ref-format/core/src/refspec.rs
@@ -182,7 +182,7 @@ impl PatternString {
            Some(idx) => {
                self.0.truncate(idx);
                true
-
            },
+
            }
        }
    }
}
modified radicle-git-ext/git-ref-format/core/src/serde.rs
@@ -9,10 +9,7 @@ use ::serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use crate::{
    refspec::{PatternStr, PatternString},
-
    Namespaced,
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    Namespaced, Qualified, RefStr, RefString,
};

impl<'de: 'a, 'a> Deserialize<'de> for &'a RefStr {
modified radicle-git-ext/git-ref-format/macro/src/lib.rs
@@ -38,11 +38,11 @@ pub fn refname(input: TokenStream) -> TokenStream {
                }
            };
            TokenStream::from(expand)
-
        },
+
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
-
        },
+
        }
    }
}

@@ -75,20 +75,20 @@ pub fn qualified(input: TokenStream) -> TokenStream {
                    };

                    TokenStream::from(expand)
-
                },
+
                }

                None => {
                    abort!(
                        lit.span(),
                        "refname is not of the form 'refs/<category>/<name>'"
                    );
-
                },
+
                }
            }
-
        },
+
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
-
        },
+
        }
    }
}

@@ -121,17 +121,17 @@ pub fn component(input: TokenStream) -> TokenStream {
                    };

                    TokenStream::from(expand)
-
                },
+
                }

                None => {
                    abort!(lit.span(), "component contains a '/'");
-
                },
+
                }
            }
-
        },
+
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
-
        },
+
        }
    }
}

@@ -159,10 +159,10 @@ pub fn pattern(input: TokenStream) -> TokenStream {
                }
            };
            TokenStream::from(expand)
-
        },
+
        }

        Err(e) => {
            abort!(lit.span(), "invalid refspec pattern literal: {}", e);
-
        },
+
        }
    }
}
modified radicle-git-ext/git-ref-format/src/lib.rs
@@ -124,17 +124,8 @@
#[cfg(feature = "percent-encoding")]
pub use git_ref_format_core::PercentEncode;
pub use git_ref_format_core::{
-
    check_ref_format,
-
    lit,
-
    name::component,
-
    Component,
-
    DuplicateGlob,
-
    Error,
-
    Namespaced,
-
    Options,
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    check_ref_format, lit, name::component, Component, DuplicateGlob, Error, Namespaced, Options,
+
    Qualified, RefStr, RefString,
};

pub mod name {
modified radicle-git-ext/src/author.rs
@@ -122,7 +122,7 @@ impl FromStr for Author {
                let hours = tz_offset / 100;
                let minutes = tz_offset % 100;
                hours * 60 + minutes
-
            },
+
            }
        };
        let time = match components.next_back() {
            None => return Err(ParseError::Missing("time")),
modified radicle-git-ext/src/blob.rs
@@ -83,13 +83,13 @@ impl<'a> Blob<'a> {
                        git.find_reference(&name).or_matches(is_not_found_err, || {
                            Err(Error::NotFound(NotFound::NoSuchBranch(name.into_owned())))
                        })
-
                    },
+
                    }

                    Branch::Ref(reference) => Ok(reference),
                }?;
                let tree = reference.peel_to_tree()?;
                blob(git, tree, path)
-
            },
+
            }

            Self::Init { branch, path } => {
                let start = match branch {
@@ -100,7 +100,7 @@ impl<'a> Blob<'a> {
                            (_, Some(sym)) => Ok(revwalk::Start::Ref(sym.to_string())),
                            (_, _) => Err(Error::NotFound(NotFound::NoRefTarget)),
                        }
-
                    },
+
                    }
                }?;

                let revwalk = revwalk::FirstParent::new(git, start)?.reverse()?;
@@ -112,9 +112,9 @@ impl<'a> Blob<'a> {
                        let oid = oid?;
                        let tree = git.find_commit(oid)?.tree()?;
                        blob(git, tree, path)
-
                    },
+
                    }
                }
-
            },
+
            }

            Self::At { object, path } => {
                let tree = git
@@ -124,7 +124,7 @@ impl<'a> Blob<'a> {
                    })
                    .and_then(|obj| Ok(obj.peel_to_tree()?))?;
                blob(git, tree, path)
-
            },
+
            }
        }
    }
}
modified radicle-git-ext/src/commit.rs
@@ -26,12 +26,41 @@ use trailers::{OwnedTrailer, Trailer, Trailers};

use crate::author::Author;

+
pub type Commit = CommitData<Oid, Oid>;
+

+
impl Commit {
+
    /// Read the [`Commit`] from the `repo` that is expected to be found at
+
    /// `oid`.
+
    pub fn read(repo: &git2::Repository, oid: Oid) -> Result<Self, error::Read> {
+
        let odb = repo.odb()?;
+
        let object = odb.read(oid)?;
+
        Ok(Commit::try_from(object.data())?)
+
    }
+

+
    /// Write the given [`Commit`] to the `repo`. The resulting `Oid`
+
    /// is the identifier for this commit.
+
    pub fn write(&self, repo: &git2::Repository) -> Result<Oid, error::Write> {
+
        let odb = repo.odb().map_err(error::Write::Odb)?;
+
        self.verify_for_write(&odb)?;
+
        Ok(odb.write(ObjectType::Commit, self.to_string().as_bytes())?)
+
    }
+

+
    fn verify_for_write(&self, odb: &git2::Odb) -> Result<(), error::Write> {
+
        for parent in &self.parents {
+
            verify_object(odb, parent, ObjectType::Commit)?;
+
        }
+
        verify_object(odb, &self.tree, ObjectType::Tree)?;
+

+
        Ok(())
+
    }
+
}
+

/// A git commit in its object description form, i.e. the output of
/// `git cat-file` for a commit object.
#[derive(Debug)]
-
pub struct Commit {
-
    tree: Oid,
-
    parents: Vec<Oid>,
+
pub struct CommitData<Tree, Parent> {
+
    tree: Tree,
+
    parents: Vec<Parent>,
    author: Author,
    committer: Author,
    headers: Headers,
@@ -39,9 +68,9 @@ pub struct Commit {
    trailers: Vec<OwnedTrailer>,
}

-
impl Commit {
+
impl<Tree, Parent> CommitData<Tree, Parent> {
    pub fn new<P, I, T>(
-
        tree: Oid,
+
        tree: Tree,
        parents: P,
        author: Author,
        committer: Author,
@@ -50,7 +79,7 @@ impl Commit {
        trailers: I,
    ) -> Self
    where
-
        P: IntoIterator<Item = Oid>,
+
        P: IntoIterator<Item = Parent>,
        I: IntoIterator<Item = T>,
        OwnedTrailer: From<T>,
    {
@@ -67,30 +96,17 @@ impl Commit {
        }
    }

-
    /// Read the [`Commit`] from the `repo` that is expected to be found at
-
    /// `oid`.
-
    pub fn read(repo: &git2::Repository, oid: Oid) -> Result<Self, error::Read> {
-
        let odb = repo.odb()?;
-
        let object = odb.read(oid)?;
-
        Ok(Commit::try_from(object.data())?)
-
    }
-

-
    /// Write the given [`Commit`] to the `repo`. The resulting `Oid`
-
    /// is the identifier for this commit.
-
    pub fn write(&self, repo: &git2::Repository) -> Result<Oid, error::Write> {
-
        let odb = repo.odb().map_err(error::Write::Odb)?;
-
        self.verify_for_write(&odb)?;
-
        Ok(odb.write(ObjectType::Commit, self.to_string().as_bytes())?)
-
    }
-

-
    /// The tree [`Oid`] this commit points to.
-
    pub fn tree(&self) -> Oid {
-
        self.tree
+
    /// The tree this commit points to.
+
    pub fn tree(&self) -> &Tree {
+
        &self.tree
    }

-
    /// The parent [`Oid`]s of this commit.
-
    pub fn parents(&self) -> impl Iterator<Item = Oid> + '_ {
-
        self.parents.iter().copied()
+
    /// The parents of this commit.
+
    pub fn parents(&self) -> impl Iterator<Item = Parent> + '_
+
    where
+
        Parent: Clone,
+
    {
+
        self.parents.iter().cloned()
    }

    /// The author of this commit, i.e. the header corresponding to `author`.
@@ -136,13 +152,49 @@ impl Commit {
        self.trailers.iter()
    }

-
    fn verify_for_write(&self, odb: &git2::Odb) -> Result<(), error::Write> {
-
        for parent in &self.parents {
-
            verify_object(odb, parent, ObjectType::Commit)?;
-
        }
-
        verify_object(odb, &self.tree, ObjectType::Tree)?;
+
    /// Convert the `CommitData::tree` into a value of type `U`. The
+
    /// conversion function `f` can be fallible.
+
    ///
+
    /// For example, `map_tree` can be used to turn raw tree data into
+
    /// an `Oid` by writing it to a repository.
+
    pub fn map_tree<U, E, F>(self, f: F) -> Result<CommitData<U, Parent>, E>
+
    where
+
        F: FnOnce(Tree) -> Result<U, E>,
+
    {
+
        Ok(CommitData {
+
            tree: f(self.tree)?,
+
            parents: self.parents,
+
            author: self.author,
+
            committer: self.committer,
+
            headers: self.headers,
+
            message: self.message,
+
            trailers: self.trailers,
+
        })
+
    }

-
        Ok(())
+
    /// Convert the `CommitData::parents` into a vector containing
+
    /// values of type `U`. The conversion function `f` can be
+
    /// fallible.
+
    ///
+
    /// For example, `map_parents` can be used to resolve the `Oid`s
+
    /// to their respective `git2::Commit`s.
+
    pub fn map_parents<U, E, F>(self, f: F) -> Result<CommitData<Tree, U>, E>
+
    where
+
        F: FnMut(Parent) -> Result<U, E>,
+
    {
+
        Ok(CommitData {
+
            tree: self.tree,
+
            parents: self
+
                .parents
+
                .into_iter()
+
                .map(f)
+
                .collect::<Result<Vec<_>, _>>()?,
+
            author: self.author,
+
            committer: self.committer,
+
            headers: self.headers,
+
            message: self.message,
+
            trailers: self.trailers,
+
        })
    }
}

modified radicle-git-ext/src/commit/headers.rs
@@ -10,6 +10,7 @@ const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n";
pub struct Headers(pub(super) Vec<(String, String)>);

/// A `gpgsig` signature stored in a [`crate::commit::Commit`].
+
#[derive(Debug)]
pub enum Signature<'a> {
    /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`.
    Pgp(Cow<'a, str>),
modified radicle-git-ext/src/tree.rs
@@ -70,11 +70,11 @@ impl Tree<'_> {
                Blob(data) => {
                    let oid = repo.blob(data)?;
                    builder.insert(name.as_ref(), oid, git2::FileMode::Blob.into())?;
-
                },
+
                }
                Tree(sub) => {
                    let oid = sub.write(repo)?;
                    builder.insert(name.as_ref(), oid, git2::FileMode::Tree.into())?;
-
                },
+
                }
            }
        }

modified radicle-git-ext/t/Cargo.toml
@@ -32,5 +32,5 @@ features = ["vendored-libgit2"]
path = ".."
features = ["serde", "minicbor"]

-
[dev-dependencies.test-helpers]
+
[dependencies.test-helpers]
path = "../../test/test-helpers"
modified radicle-git-ext/t/src/commit.rs
@@ -1,10 +1,13 @@
use std::{io, str::FromStr as _, string::ToString as _};

+
use proptest::proptest;
use radicle_git_ext::{
    author::{self, Author},
    commit::{headers::Headers, trailers::OwnedTrailer, Commit},
};
-
use test_helpers::tempdir::WithTmpDir;
+
use test_helpers::tempdir::{self, WithTmpDir};
+

+
use crate::gen;

const NO_TRAILER: &str = "\
tree 50d6ef440728217febf9e35716d8b0296608d7f8
@@ -152,27 +155,23 @@ fn test_conversion() {
    assert_eq!(Commit::from_str(UNSIGNED).unwrap().to_string(), UNSIGNED);
}

-
#[test]
-
fn valid_commits() {
-
    let radicle_git = format!(
-
        "file://{}",
-
        git2::Repository::discover(".").unwrap().path().display()
-
    );
-
    let repo = WithTmpDir::new(|path| {
-
        let repo = git2::Repository::clone(&radicle_git, path)
-
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
-
        Ok::<_, io::Error>(repo)
-
    })
-
    .unwrap();
-

-
    let mut walk = repo.revwalk().unwrap();
-
    walk.push_head().unwrap();
-

-
    // take the first 20 commits and make sure we can parse them
-
    for oid in walk.take(20) {
-
        let oid = oid.unwrap();
-
        let commit = Commit::read(&repo, oid);
-
        assert!(commit.is_ok(), "Oid: {oid}, Error: {commit:?}")
+
proptest! {
+
    #[test]
+
    fn valid_commits(commits in proptest::collection::vec(gen::commit::commit(), 5..20)) {
+
        let repo = tempdir::WithTmpDir::new(|path| {
+
            git2::Repository::init(path).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
+
        }).unwrap();
+
        let commits = gen::commit::write_commits(&repo, commits).unwrap();
+
        repo.reference("refs/heads/master", *commits.last().unwrap(), true, "").unwrap();
+

+
        let mut walk = repo.revwalk().unwrap();
+
        walk.push_head().unwrap();
+

+
        for oid in walk.take(20) {
+
            let oid = oid.unwrap();
+
            let commit = Commit::read(&repo, oid);
+
            assert!(commit.is_ok(), "Oid: {oid}, Error: {commit:?}");
+
        }
    }
}

@@ -182,6 +181,7 @@ fn write_valid_commit() {
        git2::Repository::init(path).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
    })
    .unwrap();
+

    let author = Author {
        name: "Terry".to_owned(),
        email: "terry.pratchett@proton.mail".to_owned(),
modified radicle-git-ext/t/src/gen.rs
@@ -5,4 +5,15 @@

//! Provides proptest generators

+
use proptest::strategy::Strategy;
+

+
pub mod commit;
pub mod urn;
+

+
pub fn alphanumeric() -> impl Strategy<Value = String> {
+
    "[a-zA-Z0-9_]+"
+
}
+

+
pub fn alpha() -> impl Strategy<Value = String> {
+
    "[a-zA-Z]+"
+
}
added radicle-git-ext/t/src/gen/commit.rs
@@ -0,0 +1,94 @@
+
use std::convert::Infallible;
+

+
use proptest::strategy::Strategy;
+
use radicle_git_ext::commit::{self, CommitData};
+

+
mod author;
+
mod headers;
+
mod trailers;
+

+
pub use author::author;
+
pub use headers::headers;
+
pub use trailers::{trailer, trailers};
+

+
use super::alphanumeric;
+

+
pub fn commit() -> impl Strategy<Value = CommitData<TreeData, Infallible>> {
+
    (
+
        TreeData::gen(),
+
        author(),
+
        author(),
+
        headers(),
+
        alphanumeric(),
+
        trailers(3),
+
    )
+
        .prop_map(|(tree, author, committer, headers, message, trailers)| {
+
            CommitData::new(tree, vec![], author, committer, headers, message, trailers)
+
        })
+
}
+

+
pub fn write_commits(
+
    repo: &git2::Repository,
+
    linear: Vec<CommitData<TreeData, Infallible>>,
+
) -> Result<Vec<git2::Oid>, commit::error::Write> {
+
    let mut parent = None;
+
    let mut commits = Vec::new();
+
    for commit in linear {
+
        let commit = commit.map_tree(|tree| tree.write(repo))?;
+
        let commit = match parent {
+
            Some(parent) => commit
+
                .map_parents::<git2::Oid, Infallible, _>(|_| Ok(parent))
+
                .unwrap(),
+
            None => commit
+
                .map_parents::<git2::Oid, Infallible, _>(|_| unreachable!("no parents"))
+
                .unwrap(),
+
        };
+
        let oid = commit.write(repo)?;
+
        commits.push(oid);
+
        parent = Some(oid);
+
    }
+
    Ok(commits)
+
}
+

+
#[derive(Clone, Debug)]
+
pub enum TreeData {
+
    Blob { name: String, data: String },
+
    Tree { name: String, inner: Vec<TreeData> },
+
}
+

+
impl TreeData {
+
    fn gen() -> impl Strategy<Value = Self> {
+
        let leaf =
+
            (alphanumeric(), alphanumeric()).prop_map(|(name, data)| Self::Blob { name, data });
+
        leaf.prop_recursive(8, 16, 5, |inner| {
+
            (proptest::collection::vec(inner, 1..5), alphanumeric())
+
                .prop_map(|(inner, name)| Self::Tree { name, inner })
+
        })
+
    }
+

+
    fn write(&self, repo: &git2::Repository) -> Result<git2::Oid, git2::Error> {
+
        let mut builder = repo.treebuilder(None)?;
+
        self.write_(repo, &mut builder)?;
+
        builder.write()
+
    }
+

+
    fn write_(
+
        &self,
+
        repo: &git2::Repository,
+
        builder: &mut git2::TreeBuilder,
+
    ) -> Result<git2::Oid, git2::Error> {
+
        match self {
+
            Self::Blob { name, data } => {
+
                let oid = repo.blob(data.as_bytes())?;
+
                builder.insert(name, oid, git2::FileMode::Blob.into())?;
+
            }
+
            Self::Tree { name, inner } => {
+
                for data in inner {
+
                    let oid = data.write_(repo, builder)?;
+
                    builder.insert(name, oid, git2::FileMode::Tree.into())?;
+
                }
+
            }
+
        }
+
        builder.write()
+
    }
+
}
added radicle-git-ext/t/src/gen/commit/author.rs
@@ -0,0 +1,19 @@
+
use proptest::strategy::{Just, Strategy};
+
use radicle_git_ext::author::{Author, Time};
+

+
use crate::gen;
+

+
pub fn author() -> impl Strategy<Value = Author> {
+
    gen::alphanumeric().prop_flat_map(move |name| {
+
        (Just(name), gen::alphanumeric()).prop_flat_map(|(name, domain)| {
+
            (Just(name), Just(domain), (0..1000i64)).prop_map(move |(name, domain, time)| {
+
                let email = format!("{name}@{domain}");
+
                Author {
+
                    name,
+
                    email,
+
                    time: Time::new(time, 0),
+
                }
+
            })
+
        })
+
    })
+
}
added radicle-git-ext/t/src/gen/commit/headers.rs
@@ -0,0 +1,30 @@
+
use proptest::{collection, prop_oneof, strategy::Strategy};
+
use radicle_git_ext::commit::headers::Headers;
+

+
use crate::gen;
+

+
pub fn headers() -> impl Strategy<Value = Headers> {
+
    collection::vec(prop_oneof![header(), signature()], 0..5).prop_map(|hs| {
+
        let mut headers = Headers::new();
+
        for (k, v) in hs {
+
            headers.push(&k, &v);
+
        }
+
        headers
+
    })
+
}
+

+
fn header() -> impl Strategy<Value = (String, String)> {
+
    (prop_oneof!["test", "foo", "foobar"], gen::alphanumeric())
+
}
+

+
pub fn signature() -> impl Strategy<Value = (String, String)> {
+
    ("gpgsig", prop_oneof![pgp(), ssh()])
+
}
+

+
pub fn pgp() -> impl Strategy<Value = String> {
+
    "-----BEGIN PGP SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END PGP SIGNATURE-----"
+
}
+

+
pub fn ssh() -> impl Strategy<Value = String> {
+
    "-----BEGIN SSH SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END SSH SIGNATURE-----"
+
}
added radicle-git-ext/t/src/gen/commit/trailers.rs
@@ -0,0 +1,18 @@
+
use proptest::{collection, strategy::Strategy};
+
use radicle_git_ext::commit::trailers::{OwnedTrailer, Token, Trailer};
+

+
use crate::gen;
+

+
pub fn trailers(n: usize) -> impl Strategy<Value = Vec<OwnedTrailer>> {
+
    collection::vec(trailer(), 0..n)
+
}
+

+
pub fn trailer() -> impl Strategy<Value = OwnedTrailer> {
+
    (gen::alpha(), gen::alphanumeric()).prop_map(|(token, value)| {
+
        Trailer {
+
            token: Token::try_from(format!("X-{}", token).as_str()).unwrap(),
+
            value: value.into(),
+
        }
+
        .to_owned()
+
    })
+
}
modified radicle-git-ext/t/src/git_ref_format/tests.rs
@@ -2,15 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later

use radicle_git_ext::ref_format::{
-
    component,
-
    name,
-
    qualified,
-
    refname,
-
    refspec,
-
    Error,
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    component, name, qualified, refname, refspec, Error, Qualified, RefStr, RefString,
};

#[test]
modified radicle-git-ext/t/src/lib.rs
@@ -12,3 +12,6 @@ mod commit;

#[cfg(any(test, feature = "test"))]
pub mod git_ref_format;
+

+
#[cfg(any(test, feature = "test"))]
+
pub mod repository;
added radicle-git-ext/t/src/repository.rs
@@ -0,0 +1,91 @@
+
use std::{convert::Infallible, io, path::Path};
+

+
use git2::Oid;
+
use radicle_git_ext::{commit::CommitData, ref_format::RefString};
+
use test_helpers::tempdir::{self, WithTmpDir};
+

+
use crate::gen::commit::{self, TreeData};
+

+
pub struct Fixture {
+
    pub inner: WithTmpDir<git2::Repository>,
+
    pub head: Option<git2::Oid>,
+
}
+

+
/// Initialise a [`git2::Repository`] in a temporary directory.
+
///
+
/// The provided `commits` will be added to the repository, and the
+
/// head commit will be returned.
+
pub fn fixture(
+
    refname: &RefString,
+
    commits: Vec<CommitData<TreeData, Infallible>>,
+
) -> io::Result<Fixture> {
+
    let repo = tempdir::WithTmpDir::new(|path| git2::Repository::init(path).map_err(io_other))?;
+
    let commits = commit::write_commits(&repo, commits).map_err(io_other)?;
+
    let head = commits.last().copied();
+

+
    if let Some(head) = head {
+
        repo.reference(refname.as_str(), head, false, "Initialise repository")
+
            .map_err(io_other)?;
+
    }
+

+
    Ok(Fixture { inner: repo, head })
+
}
+

+
pub fn bare_fixture(
+
    refname: &RefString,
+
    commits: Vec<CommitData<TreeData, Infallible>>,
+
) -> io::Result<Fixture> {
+
    let repo =
+
        tempdir::WithTmpDir::new(|path| git2::Repository::init_bare(path).map_err(io_other))?;
+
    let commits = commit::write_commits(&repo, commits).map_err(io_other)?;
+
    let head = commits.last().copied();
+

+
    if let Some(head) = head {
+
        repo.reference(refname.as_str(), head, false, "Initialise repository")
+
            .map_err(io_other)?;
+
    }
+

+
    Ok(Fixture { inner: repo, head })
+
}
+

+
pub fn submodule<'a>(
+
    parent: &'a git2::Repository,
+
    child: &'a git2::Repository,
+
    refname: &RefString,
+
    head: Oid,
+
    author: &git2::Signature,
+
) -> io::Result<git2::Submodule<'a>> {
+
    let url = format!("file://{}", child.path().canonicalize()?.display());
+
    let mut sub = parent
+
        .submodule(url.as_str(), Path::new("submodule"), true)
+
        .map_err(io_other)?;
+
    sub.open().map_err(io_other)?;
+
    sub.clone(Some(&mut git2::SubmoduleUpdateOptions::default()))
+
        .map_err(io_other)?;
+
    sub.add_to_index(true).map_err(io_other)?;
+
    sub.add_finalize().map_err(io_other)?;
+
    {
+
        let mut ix = parent.index().map_err(io_other)?;
+
        let tree = ix.write_tree_to(parent).map_err(io_other)?;
+
        let tree = parent.find_tree(tree).map_err(io_other)?;
+
        let head = parent.find_commit(head).map_err(io_other)?;
+
        parent
+
            .commit(
+
                Some(refname.as_str()),
+
                author,
+
                author,
+
                "Commit submodule",
+
                &tree,
+
                &[&head],
+
            )
+
            .map_err(io_other)?;
+
    }
+
    Ok(sub)
+
}
+

+
fn io_other<E>(e: E) -> io::Error
+
where
+
    E: std::error::Error + Send + Sync + 'static,
+
{
+
    io::Error::new(io::ErrorKind::Other, e)
+
}
modified radicle-surf/Cargo.toml
@@ -33,6 +33,7 @@ base64 = "0.13"
log = "0.4"
nonempty = "0.5"
thiserror = "1.0"
+
url = "2.5"

[dependencies.git2]
version = "0.18.1"
@@ -53,9 +54,6 @@ version = "1"
features = ["serde_derive"]
optional = true

-
[dependencies.url]
-
version = "2.5"
-

[build-dependencies]
anyhow = "1.0"
flate2 = "1"
modified radicle-surf/build.rs
@@ -16,8 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use std::{
-
    env,
-
    fs,
+
    env, fs,
    fs::File,
    io,
    path::{Path, PathBuf},
modified radicle-surf/examples/browsing.rs
@@ -36,7 +36,7 @@ fn main() {
        None => {
            print_usage();
            return;
-
        },
+
        }
    };
    let repo = Repository::discover(repo_path).unwrap();
    let now = Instant::now();
modified radicle-surf/examples/diff.rs
@@ -42,7 +42,7 @@ fn get_options_or_exit() -> Options {
        Err(message) => {
            println!("{message}");
            std::process::exit(1);
-
        },
+
        }
    }
}

@@ -52,7 +52,7 @@ fn init_repository_or_exit(path_to_repo: &str) -> Repository {
        Err(e) => {
            println!("Failed to create repository: {e:?}");
            std::process::exit(1);
-
        },
+
        }
    }
}

modified radicle-surf/src/blob.rs
@@ -138,7 +138,7 @@ where
            Err(_) => {
                let encoded = base64::encode(bytes);
                state.serialize_field("content", &encoded)?
-
            },
+
            }
        };
        state.serialize_field("lastCommit", &self.commit)?;
        state.end()
@@ -162,7 +162,7 @@ impl<'a> Serialize for BlobRef<'a> {
            Err(_) => {
                let encoded = base64::encode(bytes);
                state.serialize_field("content", &encoded)?
-
            },
+
            }
        };
        state.end()
    }
modified radicle-surf/src/branch.rs
@@ -134,7 +134,7 @@ impl Local {
                        name: name.as_ref().to_ref_string(),
                    }
                }
-
            },
+
            }
        }
    }

modified radicle-surf/src/diff.rs
@@ -381,23 +381,23 @@ impl Serialize for FileDiff {
            FileDiff::Added(x) => {
                state.serialize_field("path", &x.path)?;
                state.serialize_field("diff", &x.diff)?;
-
            },
+
            }
            FileDiff::Deleted(x) => {
                state.serialize_field("path", &x.path)?;
                state.serialize_field("diff", &x.diff)?;
-
            },
+
            }
            FileDiff::Modified(x) => {
                state.serialize_field("path", &x.path)?;
                state.serialize_field("diff", &x.diff)?;
-
            },
+
            }
            FileDiff::Moved(x) => {
                state.serialize_field("oldPath", &x.old_path)?;
                state.serialize_field("newPath", &x.new_path)?;
-
            },
+
            }
            FileDiff::Copied(x) => {
                state.serialize_field("oldPath", &x.old_path)?;
                state.serialize_field("newPath", &x.new_path)?;
-
            },
+
            }
        }
        state.end()
    }
@@ -555,14 +555,14 @@ impl Serialize for Modification {
                map.serialize_entry("lineNo", &addition.line_no)?;
                map.serialize_entry("type", "addition")?;
                map.end()
-
            },
+
            }
            Modification::Deletion(deletion) => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("line", &deletion.line)?;
                map.serialize_entry("lineNo", &deletion.line_no)?;
                map.serialize_entry("type", "deletion")?;
                map.end()
-
            },
+
            }
            Modification::Context {
                line,
                line_no_old,
@@ -574,7 +574,7 @@ impl Serialize for Modification {
                map.serialize_entry("lineNoNew", line_no_new)?;
                map.serialize_entry("type", "context")?;
                map.end()
-
            },
+
            }
        }
    }
}
modified radicle-surf/src/diff/git.rs
@@ -18,16 +18,7 @@
use std::convert::TryFrom;

use super::{
-
    Diff,
-
    DiffContent,
-
    DiffFile,
-
    EofNewLine,
-
    FileMode,
-
    FileStats,
-
    Hunk,
-
    Hunks,
-
    Line,
-
    Modification,
+
    Diff, DiffContent, DiffFile, EofNewLine, FileMode, FileStats, Hunk, Hunks, Line, Modification,
    Stats,
};

@@ -168,24 +159,24 @@ impl TryFrom<git2::Patch<'_>> for DiffContent {
                        new_missing_eof = true;
                        old_missing_eof = true;
                        continue;
-
                    },
+
                    }
                    git2::DiffLineType::Addition => {
                        additions += 1;
-
                    },
+
                    }
                    git2::DiffLineType::Deletion => {
                        deletions += 1;
-
                    },
+
                    }
                    git2::DiffLineType::AddEOFNL => {
                        additions += 1;
                        old_missing_eof = true;
                        continue;
-
                    },
+
                    }
                    git2::DiffLineType::DeleteEOFNL => {
                        deletions += 1;
                        new_missing_eof = true;
                        continue;
-
                    },
-
                    _ => {},
+
                    }
+
                    _ => {}
                }
                let line = Modification::try_from(line)?;
                lines.push(line);
@@ -254,7 +245,7 @@ impl<'a> TryFrom<git2::Diff<'a>> for Diff {
                Delta::Copied => copied(&mut diff, &git_diff, idx, &delta)?,
                status => {
                    return Err(error::Diff::DeltaUnhandled(status));
-
                },
+
                }
            }
        }

modified radicle-surf/src/fs.rs
@@ -297,7 +297,7 @@ impl Entry {
                    .then(|| repo.find_submodule(&name))
                    .transpose()?;
                Ok(Self::Submodule(Submodule::new(name, path, submodule, id)?))
-
            },
+
            }
            _ => Err(error::Directory::InvalidType(path, "tree or blob")),
        }
    }
@@ -396,21 +396,21 @@ impl Directory {
                    Entry::File(_) => {
                        entries.insert(entry.name().clone(), entry);
                        git2::TreeWalkResult::Ok
-
                    },
+
                    }
                    Entry::Directory(_) => {
                        entries.insert(entry.name().clone(), entry);
                        // Skip nested directories
                        git2::TreeWalkResult::Skip
-
                    },
+
                    }
                    Entry::Submodule(_) => {
                        entries.insert(entry.name().clone(), entry);
                        git2::TreeWalkResult::Ok
-
                    },
+
                    }
                },
                Err(err) => {
                    error = Some(err);
                    git2::TreeWalkResult::Abort
-
                },
+
                }
            }
        })?;

@@ -520,7 +520,7 @@ impl Directory {
                Entry::Directory(directory) => {
                    let acc = directory.traverse(repo, acc, f)?;
                    f(acc, entry)
-
                },
+
                }
                Entry::Submodule(_) => f(acc, entry),
            })
    }
modified radicle-surf/src/glob.rs
@@ -18,12 +18,9 @@
use std::marker::PhantomData;

use git_ext::ref_format::{
-
    self,
-
    refname,
+
    self, refname,
    refspec::{self, PatternString, QualifiedPattern},
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    Qualified, RefStr, RefString,
};
use thiserror::Error;

modified radicle-surf/src/namespace.rs
@@ -24,11 +24,7 @@ use std::{
use git_ext::ref_format::{
    self,
    refspec::{NamespacedPattern, PatternString, QualifiedPattern},
-
    Component,
-
    Namespaced,
-
    Qualified,
-
    RefStr,
-
    RefString,
+
    Component, Namespaced, Qualified, RefStr, RefString,
};
use nonempty::NonEmpty;
use thiserror::Error;
modified radicle-surf/src/refs.rs
@@ -44,7 +44,7 @@ impl<'a> Iterator for Tags<'a> {
                            res.map_err(error::Tag::from)
                                .and_then(|r| Tag::try_from(&r).map_err(error::Tag::from)),
                        );
-
                    },
+
                    }
                    None => self.current += 1,
                },
                None => break,
@@ -67,7 +67,7 @@ impl<'a> Iterator for TagNames<'a> {
                                .map(|name| lit::refs_tags(name).into())
                                .map_err(error::Tag::from)
                        }))
-
                    },
+
                    }
                    None => self.inner.current += 1,
                },
                None => break,
@@ -111,7 +111,7 @@ impl<'a> Iterator for Branches<'a> {
                            res.map_err(error::Branch::from)
                                .and_then(|r| Branch::try_from(&r).map_err(error::Branch::from)),
                        )
-
                    },
+
                    }
                    None => self.current += 1,
                },
                None => break,
@@ -134,7 +134,7 @@ impl<'a> Iterator for BranchNames<'a> {
                                .map(|branch| branch.refname().into_owned())
                                .map_err(error::Branch::from)
                        }))
-
                    },
+
                    }
                    None => self.inner.current += 1,
                },
                None => break,
@@ -194,7 +194,7 @@ impl<'a> Iterator for Categories<'a> {
                            let (_refs, category, c, cs) = name.non_empty_components();
                            Ok((category.to_ref_string(), refstr_join(c, cs)))
                        }));
-
                    },
+
                    }
                    None => self.current += 1,
                },
                None => break,
modified radicle-surf/src/repo.rs
@@ -33,17 +33,7 @@ use crate::{
    fs::{Directory, File, FileContent},
    refs::{BranchNames, Branches, Categories, Namespaces, TagNames, Tags},
    tree::{Entry, Tree},
-
    Branch,
-
    Commit,
-
    Error,
-
    Glob,
-
    History,
-
    Namespace,
-
    Revision,
-
    Signature,
-
    Stats,
-
    Tag,
-
    ToCommit,
+
    Branch, Commit, Error, Glob, History, Namespace, Revision, Signature, Stats, Tag, ToCommit,
};

/// Enumeration of errors that can occur in repo operations.
@@ -405,7 +395,7 @@ impl Repository {
                } else {
                    Err(error.into())
                }
-
            },
+
            }
            Ok(sig) => Ok(Some(Signature::from(sig.0))),
        }
    }
modified radicle-surf/src/tag.rs
@@ -136,7 +136,7 @@ impl TryFrom<&git2::Reference<'_>> for Tag {
                    id: commit.id().into(),
                    name,
                })
-
            },
+
            }
            Err(err) => Err(err.into()),
        }
    }
modified radicle-surf/t/Cargo.toml
@@ -17,6 +17,7 @@ nonempty = "0.5"
pretty_assertions = "1.3.0"
proptest = "1"
serde_json = "1"
+
url = "2.5"

[dev-dependencies.git2]
version = "0.18.1"
modified radicle-surf/t/src/code_browsing.rs
@@ -3,8 +3,7 @@ use std::path::Path;
use radicle_git_ext::ref_format::refname;
use radicle_surf::{
    fs::{self, Directory},
-
    Branch,
-
    Repository,
+
    Branch, Repository,
};

use super::GIT_PLATINUM;
modified radicle-surf/t/src/diff.rs
@@ -2,23 +2,10 @@ use pretty_assertions::assert_eq;
use radicle_git_ext::{ref_format::refname, Oid};
use radicle_surf::{
    diff::{
-
        Added,
-
        Diff,
-
        DiffContent,
-
        DiffFile,
-
        EofNewLine,
-
        FileDiff,
-
        FileMode,
-
        FileStats,
-
        Hunk,
-
        Line,
-
        Modification,
-
        Modified,
-
        Stats,
+
        Added, Diff, DiffContent, DiffFile, EofNewLine, FileDiff, FileMode, FileStats, Hunk, Line,
+
        Modification, Modified, Stats,
    },
-
    Branch,
-
    Error,
-
    Repository,
+
    Branch, Error, Repository,
};
use std::{path::Path, str::FromStr};

modified radicle-surf/t/src/file_system.rs
@@ -7,8 +7,7 @@ mod directory {
    use radicle_git_ext::ref_format::refname;
    use radicle_surf::{
        fs::{self, Entry},
-
        Branch,
-
        Repository,
+
        Branch, Repository,
    };
    use std::path::Path;

modified radicle-surf/t/src/submodule.rs
@@ -1,21 +1,86 @@
-
use std::path::Path;
+
use std::{convert::Infallible, path::Path};

+
use proptest::{collection, proptest};
+
use radicle_git_ext::commit::CommitData;
+
use radicle_git_ext::ref_format::refname;
+
use radicle_git_ext_test::gen;
use radicle_surf::tree::EntryKind;
+
use radicle_surf::{fs, Branch, Repository};

-
#[test]
-
fn test_submodule() {
-
    use radicle_git_ext::ref_format::refname;
-
    use radicle_surf::{fs, Branch, Repository};
-

-
    let repo = Repository::discover(".").unwrap();
-
    let branch = Branch::local(refname!("surf/submodule-support"));
-
    let dir = repo.root_dir(&branch).unwrap();
-
    let platinum = dir
-
        .find_entry(&Path::new("radicle-surf/data/git-platinum"), &repo)
-
        .unwrap();
-
    assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
-

-
    let surf = repo.tree(&branch, &Path::new("radicle-surf/data")).unwrap();
-
    let kind = EntryKind::from(platinum);
-
    assert!(surf.entries().iter().any(|e| e.entry() == &kind));
+
proptest! {
+
    #[test]
+
    fn test_submodule(
+
        initial in gen::commit::commit(),
+
        commits in collection::vec(gen::commit::commit(), 1..5)
+
    ) {
+
        prop::test_submodule(initial, commits)
+
    }
+

+
    #[ignore = "segfault"]
+
    #[test]
+
    fn test_submodule_bare(
+
        initial in gen::commit::commit(),
+
        commits in collection::vec(gen::commit::commit(), 1..5)
+
    ) {
+
        prop::test_submodule_bare(initial, commits)
+
    }
+

+
}
+

+
mod prop {
+
    use radicle_git_ext_test::{gen::commit, repository};
+

+
    use super::*;
+

+
    pub fn test_submodule(
+
        initial: CommitData<commit::TreeData, Infallible>,
+
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
+
    ) {
+
        let refname = refname!("refs/heads/master");
+
        let author = git2::Signature::try_from(initial.author()).unwrap();
+

+
        let submodule = repository::fixture(&refname, commits).unwrap();
+
        let repo = repository::fixture(&refname, vec![initial]).unwrap();
+

+
        let head = repo.head.expect("missing initial commit");
+
        let sub =
+
            repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap();
+

+
        let repo = Repository::open(repo.inner.path()).unwrap();
+
        let branch = Branch::local(refname);
+
        let dir = repo.root_dir(&branch).unwrap();
+

+
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
+
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
+

+
        let root = repo.tree(&branch, &Path::new("")).unwrap();
+
        let kind = EntryKind::from(platinum);
+
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
+
    }
+

+
    pub fn test_submodule_bare(
+
        initial: CommitData<commit::TreeData, Infallible>,
+
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
+
    ) {
+
        let refname = refname!("refs/heads/master");
+
        let author = git2::Signature::try_from(initial.author()).unwrap();
+

+
        let submodule = repository::fixture(&refname, commits).unwrap();
+
        let repo = repository::bare_fixture(&refname, vec![initial]).unwrap();
+

+
        let head = repo.head.expect("missing initial commit");
+
        let sub =
+
            repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap();
+

+
        let repo = Repository::open(repo.inner.path()).unwrap();
+
        let branch = Branch::local(refname);
+
        let dir = repo.root_dir(&branch).unwrap();
+

+
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
+
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
+

+
        let root = repo.tree(&branch, &Path::new("")).unwrap();
+
        let kind = EntryKind::from(platinum);
+
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
+
    }
}
added rust-toolchain
@@ -0,0 +1 @@
+
1.76
deleted shell.nix
@@ -1,23 +0,0 @@
-
{ sources ? import ./nix/sources.nix
-
, pkgs ? import sources.nixpkgs {
-
    overlays = [ (import sources.rust-overlay) ];
-
  }
-
}:
-
let
-
  stable = pkgs.rust-bin.stable.latest.default;
-
  rust-overlay = stable.override {
-
    extensions = [ "rust-src" "rust-analysis" ];
-
  };
-
  devault = (pkgs.callPackage ./default.nix {});
-
in
-
  with pkgs;
-
  mkShell {
-
    name = "development";
-
    buildInputs = devault.buildInputs ++ [
-
        clang
-
        lld
-

-
        ripgrep
-
        rust-analyzer
-
    ];
-
  }
modified test/test-helpers/src/tempdir.rs
@@ -11,6 +11,7 @@ use std::{

use tempfile::{tempdir, TempDir};

+
#[derive(Debug)]
pub struct WithTmpDir<A> {
    _tmp: TempDir,
    inner: A,