Make `Doc` type payload generic

This commit is contained in:
Alexis Sellier 2022-12-08 15:38:09 +01:00
parent e7f2637126
commit 98c1566109
No known key found for this signature in database
20 changed files with 336 additions and 224 deletions

131
Cargo.lock generated
View File

@ -30,9 +30,9 @@ dependencies = [
[[package]] [[package]]
name = "amplify" name = "amplify"
version = "4.0.0-beta.4" version = "4.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c3b7fe483c11f7a434ec6923a68749b3415c1328857f1a8e37bb2c792a25c0" checksum = "4ff4d466b0cb5124991d3a2085c28f2aed6d86264c3c0bdbd7d1b64a49b4726b"
dependencies = [ dependencies = [
"amplify_derive", "amplify_derive",
"amplify_num", "amplify_num",
@ -543,9 +543,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [ dependencies = [
"cc", "cc",
"cxxbridge-flags", "cxxbridge-flags",
@ -555,9 +555,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx-build" name = "cxx-build"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [ dependencies = [
"cc", "cc",
"codespan-reporting", "codespan-reporting",
@ -570,15 +570,15 @@ dependencies = [
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-flags"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
[[package]] [[package]]
name = "cxxbridge-macro" name = "cxxbridge-macro"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -597,9 +597,9 @@ dependencies = [
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.3.2" version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]] [[package]]
name = "data-encoding-macro" name = "data-encoding-macro"
@ -623,9 +623,9 @@ dependencies = [
[[package]] [[package]]
name = "der" name = "der"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"pem-rfc7468", "pem-rfc7468",
@ -704,9 +704,9 @@ dependencies = [
[[package]] [[package]]
name = "ed25519-compact" name = "ed25519-compact"
version = "2.0.2" version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2d21333b679bbbac680b3eb45c86937e42f69277028f4e97b599b80b86c253" checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c"
dependencies = [ dependencies = [
"ct-codecs", "ct-codecs",
"getrandom 0.2.8", "getrandom 0.2.8",
@ -873,9 +873,9 @@ dependencies = [
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.18" version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -1020,13 +1020,31 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "git-ref-format"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
]
[[package]] [[package]]
name = "git-ref-format" name = "git-ref-format"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d" source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [ dependencies = [
"git-ref-format-core", "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git-ref-format-macro", "git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
]
[[package]]
name = "git-ref-format-core"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"serde",
"thiserror",
] ]
[[package]] [[package]]
@ -1038,12 +1056,23 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "git-ref-format-macro"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
dependencies = [
"git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"proc-macro-error",
"quote",
"syn",
]
[[package]] [[package]]
name = "git-ref-format-macro" name = "git-ref-format-macro"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d" source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [ dependencies = [
"git-ref-format-core", "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"proc-macro-error", "proc-macro-error",
"quote", "quote",
"syn", "syn",
@ -1467,9 +1496,9 @@ checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]] [[package]]
name = "libgit2-sys" name = "libgit2-sys"
@ -2067,7 +2096,7 @@ dependencies = [
"cyphernet", "cyphernet",
"ed25519-compact", "ed25519-compact",
"fastrand", "fastrand",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git2", "git2",
"log", "log",
"multibase", "multibase",
@ -2126,7 +2155,7 @@ dependencies = [
"ed25519-compact", "ed25519-compact",
"fastrand", "fastrand",
"git-commit", "git-commit",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"git-trailers", "git-trailers",
"git2", "git2",
"log", "log",
@ -2164,7 +2193,7 @@ dependencies = [
"cyphernet", "cyphernet",
"ed25519-compact", "ed25519-compact",
"fastrand", "fastrand",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"multibase", "multibase",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
@ -2183,7 +2212,7 @@ name = "radicle-git-ext"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d" source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [ dependencies = [
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git2", "git2",
"percent-encoding", "percent-encoding",
"radicle-std-ext", "radicle-std-ext",
@ -2229,7 +2258,7 @@ dependencies = [
"colored", "colored",
"crossbeam-channel", "crossbeam-channel",
"fastrand", "fastrand",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"lexopt", "lexopt",
"log", "log",
"nakamoto-net", "nakamoto-net",
@ -2281,7 +2310,7 @@ dependencies = [
"base64", "base64",
"either", "either",
"flate2", "flate2",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
"git2", "git2",
"nom 6.1.2", "nom 6.1.2",
"nonempty 0.5.0", "nonempty 0.5.0",
@ -2297,7 +2326,7 @@ name = "radicle-tools"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"git-ref-format", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"radicle", "radicle",
] ]
@ -2489,9 +2518,9 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.4" version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -2524,9 +2553,9 @@ dependencies = [
[[package]] [[package]]
name = "scale-info" name = "scale-info"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"derive_more", "derive_more",
@ -2536,9 +2565,9 @@ dependencies = [
[[package]] [[package]]
name = "scale-info-derive" name = "scale-info-derive"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@ -2580,18 +2609,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.148" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.148" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2901,9 +2930,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.104" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3077,9 +3106,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.22.0" version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -3090,7 +3119,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "windows-sys",
] ]
[[package]] [[package]]
@ -3145,9 +3174,9 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes",
@ -3258,9 +3287,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.15.0" version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]] [[package]]
name = "uint" name = "uint"
@ -3556,9 +3585,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize_derive" name = "zeroize_derive"
version = "1.3.2" version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -42,4 +42,4 @@ rev = "d3115a22158c8395705babefdc89049f7510d32d"
[patch.crates-io.git-ref-format] [patch.crates-io.git-ref-format]
git = "https://github.com/radicle-dev/radicle-git" git = "https://github.com/radicle-dev/radicle-git"
rev = "d3115a22158c8395705babefdc89049f7510d32d" rev = "39ce2f934915a563f9420ac9c85480df8a591481"

View File

@ -15,7 +15,7 @@ ok Project heartwood created
} }
Your project id is rad:z2TBtGrJKGsremYAPec6vN4n77Ba7. You can show it any time by running: Your project id is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji. You can show it any time by running:
rad . rad .
To publish your project to the network, run: To publish your project to the network, run:
@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:
``` ```
$ rad ls $ rad ls
heartwood rad:z2TBtGrJKGsremYAPec6vN4n77Ba7 cdf76ce Radicle Heartwood Protocol & Stack heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji cdf76ce Radicle Heartwood Protocol & Stack
``` ```

View File

@ -79,12 +79,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> { pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
let id = options.id; let id = options.id;
let storage = &profile.storage; let storage = &profile.storage;
let Doc { let doc = storage
payload, delegates, ..
} = storage
.repository(id)? .repository(id)?
.project_of(profile.id()) .identity_of(profile.id())
.context("project could not be found in local storage")?; .context("project could not be found in local storage")?;
let payload = doc.project()?;
let path = PathBuf::from(payload.name.clone()); let path = PathBuf::from(payload.name.clone());
if path.exists() { if path.exists() {
@ -109,7 +108,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
}; };
spinner.finish(); spinner.finish();
let remotes = delegates let remotes = doc
.delegates
.into_iter() .into_iter()
.map(|did| *did) .map(|did| *did)
.filter(|id| id != profile.id()) .filter(|id| id != profile.id())

View File

@ -93,10 +93,11 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
let doc = profile let doc = profile
.storage .storage
.repository(id)? .repository(id)?
.project_of(profile.id()) .identity_of(profile.id())
.map_err(|_e| anyhow!("couldn't load project {} from local state", id))?; .map_err(|_e| anyhow!("couldn't load project {} from local state", id))?;
let proj = doc.project()?;
let path = Path::new(&doc.name); let path = Path::new(&proj.name);
let repo = rad::checkout(id, profile.id(), path, &profile.storage)?; let repo = rad::checkout(id, profile.id(), path, &profile.storage)?;
let delegates = doc let delegates = doc
.delegates .delegates
@ -104,7 +105,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
.map(|d| **d) .map(|d| **d)
.filter(|id| id != profile.id()) .filter(|id| id != profile.id())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let default_branch = doc.payload.default_branch.clone(); let default_branch = proj.default_branch.clone();
// Setup tracking for project delegates. // Setup tracking for project delegates.
setup_remotes( setup_remotes(

View File

@ -201,24 +201,26 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
&profile.storage, &profile.storage,
) { ) {
Ok((id, doc, _)) => { Ok((id, doc, _)) => {
let proj = doc.project()?;
spinner.message(format!( spinner.message(format!(
"Project {} created", "Project {} created",
term::format::highlight(&doc.name) term::format::highlight(&proj.name)
)); ));
spinner.finish(); spinner.finish();
if interactive.no() { if interactive.no() {
term::blob(json::to_string_pretty(&doc.payload)?); term::blob(json::to_string_pretty(&proj)?);
term::blank(); term::blank();
} }
if options.set_upstream || git::branch_remote(&repo, &doc.default_branch).is_err() { if options.set_upstream || git::branch_remote(&repo, &proj.default_branch).is_err() {
// Setup eg. `master` -> `rad/master` // Setup eg. `master` -> `rad/master`
radicle::git::set_upstream( radicle::git::set_upstream(
&repo, &repo,
&radicle::rad::REMOTE_NAME, &radicle::rad::REMOTE_NAME,
&doc.default_branch, &proj.default_branch,
&radicle::git::refs::workdir::branch(&doc.default_branch), &radicle::git::refs::workdir::branch(&proj.default_branch),
)?; )?;
} }

View File

@ -50,13 +50,13 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
storage.projects()?.into_iter().for_each(|id| { storage.projects()?.into_iter().for_each(|id| {
let Ok(repo) = storage.repository(id) else { return }; let Ok(repo) = storage.repository(id) else { return };
let Ok((_, head)) = repo.head() else { return }; let Ok((_, head)) = repo.head() else { return };
let Ok(Doc { payload, .. }) = repo.project_of(profile.id()) else { return }; let Ok(Project { name, description, .. }) = repo.project_of(profile.id()) else { return };
let head = term::format::oid(head); let head = term::format::oid(head);
table.push([ table.push([
term::format::bold(payload.name), term::format::bold(name),
term::format::tertiary(id), term::format::tertiary(id),
term::format::secondary(head), term::format::secondary(head),
term::format::italic(payload.description), term::format::italic(description),
]); ]);
}); });
table.render(); table.render();

View File

@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let signer = term::signer(&profile)?; let signer = term::signer(&profile)?;
let repository = profile.storage.repository(id)?; let repository = profile.storage.repository(id)?;
let _project = repository let _project = repository
.project_of(profile.id()) .identity_of(profile.id())
.context(format!("couldn't load project {} from local state", id))?; .context(format!("couldn't load project {} from local state", id))?;
let repository = profile.storage.repository(id)?; let repository = profile.storage.repository(id)?;
let mut patches = Patches::open(*profile.id(), &repository)?; let mut patches = Patches::open(*profile.id(), &repository)?;

View File

@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let signer = term::signer(&profile)?; let signer = term::signer(&profile)?;
let repository = profile.storage.repository(id)?; let repository = profile.storage.repository(id)?;
let _project = repository let _project = repository
.project_of(profile.id()) .identity_of(profile.id())
.context(format!("couldn't load project {} from local state", id))?; .context(format!("couldn't load project {} from local state", id))?;
let mut patches = Patches::open(*profile.id(), &repository)?; let mut patches = Patches::open(*profile.id(), &repository)?;

View File

@ -3,8 +3,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::node::Handle; use radicle::node::{Handle, NodeId};
use radicle::prelude::*;
use radicle::storage::WriteStorage; use radicle::storage::WriteStorage;
use crate::terminal as term; use crate::terminal as term;
@ -94,12 +93,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?; let profile = ctx.profile()?;
let storage = &profile.storage; let storage = &profile.storage;
let (_, rid) = radicle::rad::cwd().context("this command must be run within a project")?; let (_, rid) = radicle::rad::cwd().context("this command must be run within a project")?;
let Doc { payload, .. } = storage.repository(rid)?.project_of(profile.id())?; let project = storage.repository(rid)?.project_of(profile.id())?;
let mut node = radicle::node::connect(&profile.node())?; let mut node = radicle::node::connect(&profile.node())?;
term::info!( term::info!(
"Establishing 🌱 tracking relationship for {}", "Establishing 🌱 tracking relationship for {}",
term::format::highlight(&payload.name) term::format::highlight(&project.name)
); );
term::blank(); term::blank();

View File

@ -68,18 +68,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.context("current directory is not a git repository; please supply an `<id>`")?; .context("current directory is not a git repository; please supply an `<id>`")?;
let profile = ctx.profile()?; let profile = ctx.profile()?;
let storage = &profile.storage; let storage = &profile.storage;
let Doc { payload, .. } = storage.repository(id)?.project_of(profile.id())?; let project = storage.repository(id)?.project_of(profile.id())?;
if untrack(id, &profile)? { if untrack(id, &profile)? {
term::success!( term::success!(
"Tracking relationships for {} ({}) removed", "Tracking relationships for {} ({}) removed",
term::format::highlight(payload.name), term::format::highlight(project.name),
&id.to_human() &id.to_human()
); );
} else { } else {
term::info!( term::info!(
"Tracking relationships for {} ({}) doesn't exist", "Tracking relationships for {} ({}) doesn't exist",
term::format::highlight(payload.name), term::format::highlight(project.name),
&id.to_human() &id.to_human()
); );
} }

View File

@ -19,7 +19,7 @@ git-trailers = { version = "0.1" }
log = { version = "0.4.17" } log = { version = "0.4.17" }
nonempty = { version = "0.8.1", features = ["serialize"] } nonempty = { version = "0.8.1", features = ["serialize"] }
petgraph = { version = "0.5" } petgraph = { version = "0.5" }
radicle-git-ext = { version = "0.2" } radicle-git-ext = { version = "0" }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
thiserror = { version = "1.0" } thiserror = { version = "1.0" }

View File

@ -17,7 +17,7 @@ use tower_http::trace::TraceLayer;
use tracing::Span; use tracing::Span;
use radicle::cob::issue::Issues; use radicle::cob::issue::Issues;
use radicle::identity::{Doc, Id}; use radicle::identity::Id;
use radicle::storage::{ReadRepository, WriteStorage}; use radicle::storage::{ReadRepository, WriteStorage};
use radicle::Profile; use radicle::Profile;
@ -49,7 +49,7 @@ impl Context {
let storage = &self.profile.storage; let storage = &self.profile.storage;
let repo = storage.repository(id)?; let repo = storage.repository(id)?;
let (_, head) = repo.head()?; let (_, head) = repo.head()?;
let Doc { payload, .. } = repo.project_of(self.profile.id())?; let payload = repo.project_of(self.profile.id())?;
let issues = (Issues::open(self.profile.public_key, &repo)?).count()?; let issues = (Issues::open(self.profile.public_key, &repo)?).count()?;
Ok(project::Info { Ok(project::Info {
@ -142,7 +142,7 @@ pub struct PaginationQuery {
mod project { mod project {
use radicle::git::Oid; use radicle::git::Oid;
use radicle::identity::project::Payload; use radicle::identity::project::Project;
use radicle::identity::Id; use radicle::identity::Id;
use serde::Serialize; use serde::Serialize;
@ -152,7 +152,7 @@ mod project {
pub struct Info { pub struct Info {
/// Project metadata. /// Project metadata.
#[serde(flatten)] #[serde(flatten)]
pub payload: Payload, pub payload: Project,
pub head: Oid, pub head: Oid,
pub patches: usize, pub patches: usize,
pub issues: usize, pub issues: usize,

View File

@ -3,7 +3,7 @@ use axum::routing::get;
use axum::{Extension, Json, Router}; use axum::{Extension, Json, Router};
use radicle::cob::issue::Issues; use radicle::cob::issue::Issues;
use radicle::identity::{Did, Doc}; use radicle::identity::Did;
use radicle::storage::{ReadRepository, WriteStorage}; use radicle::storage::{ReadRepository, WriteStorage};
use crate::api::axum_extra::{Path, Query}; use crate::api::axum_extra::{Path, Query};
@ -38,9 +38,10 @@ async fn delegates_projects_handler(
.filter_map(|id| { .filter_map(|id| {
let Ok(repo) = storage.repository(id) else { return None }; let Ok(repo) = storage.repository(id) else { return None };
let Ok((_, head)) = repo.head() else { return None }; let Ok((_, head)) = repo.head() else { return None };
let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) else { return None }; let Ok(doc) = repo.identity_of(ctx.profile.id()) else { return None };
let Ok(payload) = doc.project() else { return None };
if !delegates.iter().any(|d| *d == delegate) { if !doc.delegates.iter().any(|d| *d == delegate) {
return None; return None;
} }

View File

@ -14,7 +14,7 @@ use radicle::cob::issue::Issues;
use radicle::cob::thread::{self, CommentId}; use radicle::cob::thread::{self, CommentId};
use radicle::cob::Timestamp; use radicle::cob::Timestamp;
use radicle::git::raw::BranchType; use radicle::git::raw::BranchType;
use radicle::identity::{Doc, Id, PublicKey}; use radicle::identity::{Id, PublicKey};
use radicle::node::NodeId; use radicle::node::NodeId;
use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage}; use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage};
use radicle_surf::git::History; use radicle_surf::git::History;
@ -68,7 +68,7 @@ async fn project_root_handler(
.filter_map(|id| { .filter_map(|id| {
let Ok(repo) = storage.repository(id) else { return None }; let Ok(repo) = storage.repository(id) else { return None };
let Ok((_, head)) = repo.head() else { return None }; let Ok((_, head)) = repo.head() else { return None };
let Ok(Doc { payload, .. }) = repo.project_of(ctx.profile.id()) else { return None }; let Ok(payload) = repo.project_of(ctx.profile.id()) else { return None };
let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None }; let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None };
let Ok(issues) = (*issues).count() else { return None }; let Ok(issues) = (*issues).count() else { return None };

View File

@ -1,7 +1,7 @@
mod id; mod id;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _; use std::fmt::{self, Write as _};
use std::io; use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
@ -46,6 +46,8 @@ pub enum DocError {
Io(#[from] io::Error), Io(#[from] io::Error),
#[error("verification: {0}")] #[error("verification: {0}")]
Verification(#[from] VerificationError), Verification(#[from] VerificationError),
#[error("payload: {0}")]
Payload(#[from] PayloadError),
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] git::Error), Git(#[from] git::Error),
#[error("git: {0}")] #[error("git: {0}")]
@ -68,26 +70,120 @@ impl DocError {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Error)]
#[serde(rename_all = "camelCase")] pub enum ProjectError {
pub struct Payload { #[error("invalid name: {0}")]
pub name: String, Name(&'static str),
pub description: String, #[error("invalid description: {0}")]
pub default_branch: git::RefString, Description(&'static str),
#[error("invalid default branch: {0}")]
DefaultBranch(&'static str),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("project payload not found in identity document")]
NotFound,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Project {
pub name: String,
pub description: String,
pub default_branch: BranchName,
}
impl From<Project> for Payload {
fn from(proj: Project) -> Self {
let value = serde_json::to_value(proj)
.expect("Payload::from: could not convert project into value");
Self { value }
}
}
impl Project {
/// Validate the project data.
pub fn validate(&self) -> Result<(), ProjectError> {
if self.name.is_empty() {
return Err(ProjectError::Name("name cannot be empty"));
}
if self.name.len() > MAX_STRING_LENGTH {
return Err(ProjectError::Name("name cannot exceed 255 bytes"));
}
if self.description.len() > MAX_STRING_LENGTH {
return Err(ProjectError::Description(
"description cannot exceed 255 bytes",
));
}
if self.default_branch.is_empty() {
return Err(ProjectError::DefaultBranch(
"default branch cannot be empty",
));
}
if self.default_branch.len() > MAX_STRING_LENGTH {
return Err(ProjectError::DefaultBranch(
"default branch cannot exceed 255 bytes",
));
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum PayloadError {
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("payload '{0}' not found in identity document")]
NotFound(PayloadId),
}
/// Identifies an identity document payload type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
// TODO: Restrict values. // TODO: Restrict values.
pub struct Namespace(String); pub struct PayloadId(String);
impl fmt::Display for PayloadId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl PayloadId
where
PayloadId: Clone,
{
/// Project payload type.
pub fn project() -> Self {
Self(String::from("xyz.radicle.project"))
}
}
/// Payload value.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Payload {
value: serde_json::Value,
}
impl From<serde_json::Value> for Payload {
fn from(value: serde_json::Value) -> Self {
Self { value }
}
}
impl Deref for Payload {
type Target = serde_json::Value;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Doc<V> { pub struct Doc<V> {
#[serde(rename = "xyz.radicle.project")] pub payload: BTreeMap<PayloadId, Payload>,
pub payload: Payload,
#[serde(flatten)]
pub extensions: BTreeMap<Namespace, serde_json::Value>,
pub delegates: NonEmpty<Did>, pub delegates: NonEmpty<Did>,
pub threshold: usize, pub threshold: usize,
@ -117,6 +213,17 @@ impl Doc<Verified> {
false false
} }
/// Get the project payload, if it exists and is valid, out of this document.
pub fn project(&self) -> Result<Project, PayloadError> {
let value = self
.payload
.get(&PayloadId::project())
.ok_or_else(|| PayloadError::NotFound(PayloadId::project()))?;
let proj: Project = serde_json::from_value((**value).clone())?;
Ok(proj)
}
pub fn sign<G: crypto::Signer>(&self, signer: &G) -> Result<(git::Oid, Signature), DocError> { pub fn sign<G: crypto::Signer>(&self, signer: &G) -> Result<(git::Oid, Signature), DocError> {
let (oid, bytes) = self.encode()?; let (oid, bytes) = self.encode()?;
let sig = signer.sign(&bytes); let sig = signer.sign(&bytes);
@ -181,66 +288,27 @@ impl Doc<Verified> {
} }
} }
impl<V> Deref for Doc<V> {
type Target = Payload;
fn deref(&self) -> &Self::Target {
&self.payload
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum VerificationError { pub enum VerificationError {
#[error("invalid name: {0}")]
Name(&'static str),
#[error("invalid description: {0}")]
Description(&'static str),
#[error("invalid default branch: {0}")]
DefaultBranch(&'static str),
#[error("invalid delegates: {0}")] #[error("invalid delegates: {0}")]
Delegates(&'static str), Delegates(&'static str),
#[error("invalid version `{0}`")] #[error("invalid version `{0}`")]
Version(u32), Version(u32),
#[error("invalid parent: {0}")]
Parent(&'static str),
#[error("invalid threshold `{0}`: {1}")] #[error("invalid threshold `{0}`: {1}")]
Threshold(usize, &'static str), Threshold(usize, &'static str),
} }
impl Doc<Unverified> { impl Doc<Unverified> {
pub fn initial( pub fn initial(project: Project, delegate: Did) -> Self {
name: String, Self::new(project, NonEmpty::new(delegate), 1)
description: String,
default_branch: BranchName,
delegate: Did,
) -> Self {
Self {
payload: Payload {
name,
description,
default_branch,
},
extensions: BTreeMap::new(),
delegates: NonEmpty::new(delegate),
threshold: 1,
verified: PhantomData,
}
} }
pub fn new( pub fn new(project: Project, delegates: NonEmpty<Did>, threshold: usize) -> Self {
name: String, let project =
description: String, serde_json::to_value(project).expect("Doc::initial: payload must be serializable");
default_branch: BranchName,
delegates: NonEmpty<Did>,
threshold: usize,
) -> Self {
Self { Self {
payload: Payload { payload: BTreeMap::from_iter([(PayloadId::project(), Payload::from(project))]),
name,
description,
default_branch,
},
extensions: BTreeMap::new(),
delegates, delegates,
threshold, threshold,
verified: PhantomData, verified: PhantomData,
@ -252,17 +320,6 @@ impl Doc<Unverified> {
} }
pub fn verified(self) -> Result<Doc<Verified>, VerificationError> { pub fn verified(self) -> Result<Doc<Verified>, VerificationError> {
if self.name.is_empty() {
return Err(VerificationError::Name("name cannot be empty"));
}
if self.name.len() > MAX_STRING_LENGTH {
return Err(VerificationError::Name("name cannot exceed 255 bytes"));
}
if self.description.len() > MAX_STRING_LENGTH {
return Err(VerificationError::Description(
"description cannot exceed 255 bytes",
));
}
if self.delegates.len() > MAX_DELEGATES { if self.delegates.len() > MAX_DELEGATES {
return Err(VerificationError::Delegates( return Err(VerificationError::Delegates(
"number of delegates cannot exceed 255", "number of delegates cannot exceed 255",
@ -273,16 +330,6 @@ impl Doc<Unverified> {
"delegate list cannot be empty", "delegate list cannot be empty",
)); ));
} }
if self.default_branch.is_empty() {
return Err(VerificationError::DefaultBranch(
"default branch cannot be empty",
));
}
if self.default_branch.len() > MAX_STRING_LENGTH {
return Err(VerificationError::DefaultBranch(
"default branch cannot exceed 255 bytes",
));
}
if self.threshold > self.delegates.len() { if self.threshold > self.delegates.len() {
return Err(VerificationError::Threshold( return Err(VerificationError::Threshold(
self.threshold, self.threshold,
@ -298,7 +345,6 @@ impl Doc<Unverified> {
Ok(Doc { Ok(Doc {
payload: self.payload, payload: self.payload,
extensions: self.extensions,
delegates: self.delegates, delegates: self.delegates,
threshold: self.threshold, threshold: self.threshold,
verified: PhantomData, verified: PhantomData,
@ -500,9 +546,13 @@ mod test {
delegate.public_key().to_human(), delegate.public_key().to_human(),
String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi") String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi")
); );
assert_eq!(
(*id).to_string(),
"d96f425412c9f8ad5d9a9a05c9831d0728e2338d"
);
assert_eq!( assert_eq!(
id.to_human(), id.to_human(),
String::from("rad:z2TBtGrJKGsremYAPec6vN4n77Ba7") String::from("rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji")
); );
} }
@ -542,14 +592,16 @@ mod test {
// TODO: In some cases we want to get the repo and the project, but don't // TODO: In some cases we want to get the repo and the project, but don't
// want to have to create a repository object twice. Perhaps there should // want to have to create a repository object twice. Perhaps there should
// be a way of getting a project from a repo. // be a way of getting a project from a repo.
let mut proj = storage.get(alice.public_key(), id).unwrap().unwrap(); let mut doc = storage.get(alice.public_key(), id).unwrap().unwrap();
let mut prj = doc.project().unwrap();
let repo = storage.repository(id).unwrap(); let repo = storage.repository(id).unwrap();
// Make a change to the description and sign it. // Make a change to the description and sign it.
proj.payload.description += "!"; prj.description += "!";
proj.sign(&alice) doc.payload.insert(PayloadId::project(), prj.clone().into());
doc.sign(&alice)
.and_then(|(_, sig)| { .and_then(|(_, sig)| {
proj.update( doc.update(
alice.public_key(), alice.public_key(),
"Update description", "Update description",
&[(alice.public_key(), sig)], &[(alice.public_key(), sig)],
@ -559,11 +611,11 @@ mod test {
.unwrap(); .unwrap();
// Add Bob as a delegate, and sign it. // Add Bob as a delegate, and sign it.
proj.delegate(*bob.public_key()); doc.delegate(*bob.public_key());
proj.threshold = 2; doc.threshold = 2;
proj.sign(&alice) doc.sign(&alice)
.and_then(|(_, sig)| { .and_then(|(_, sig)| {
proj.update( doc.update(
alice.public_key(), alice.public_key(),
"Add bob", "Add bob",
&[(alice.public_key(), sig)], &[(alice.public_key(), sig)],
@ -573,11 +625,11 @@ mod test {
.unwrap(); .unwrap();
// Add Eve as a delegate, and sign it. // Add Eve as a delegate, and sign it.
proj.delegate(*eve.public_key()); doc.delegate(*eve.public_key());
proj.sign(&alice) doc.sign(&alice)
.and_then(|(_, alice_sig)| { .and_then(|(_, alice_sig)| {
proj.sign(&bob).and_then(|(_, bob_sig)| { doc.sign(&bob).and_then(|(_, bob_sig)| {
proj.update( doc.update(
alice.public_key(), alice.public_key(),
"Add eve", "Add eve",
&[(alice.public_key(), alice_sig), (bob.public_key(), bob_sig)], &[(alice.public_key(), alice_sig), (bob.public_key(), bob_sig)],
@ -588,12 +640,13 @@ mod test {
.unwrap(); .unwrap();
// Update description again with signatures by Eve and Bob. // Update description again with signatures by Eve and Bob.
proj.payload.description += "?"; prj.description += "?";
let (current, head) = proj doc.payload.insert(PayloadId::project(), prj.into());
let (current, head) = doc
.sign(&bob) .sign(&bob)
.and_then(|(_, bob_sig)| { .and_then(|(_, bob_sig)| {
proj.sign(&eve).and_then(|(blob_id, eve_sig)| { doc.sign(&eve).and_then(|(blob_id, eve_sig)| {
proj.update( doc.update(
alice.public_key(), alice.public_key(),
"Update description", "Update description",
&[(bob.public_key(), bob_sig), (eve.public_key(), eve_sig)], &[(bob.public_key(), bob_sig), (eve.public_key(), eve_sig)],
@ -614,10 +667,10 @@ mod test {
assert_eq!(identity.root, id); assert_eq!(identity.root, id);
assert_eq!(identity.current, current); assert_eq!(identity.current, current);
assert_eq!(identity.head, head); assert_eq!(identity.head, head);
assert_eq!(identity.doc, proj); assert_eq!(identity.doc, doc);
let proj = storage.get(alice.public_key(), id).unwrap().unwrap(); let doc = storage.get(alice.public_key(), id).unwrap().unwrap();
assert_eq!(proj.description, "Acme's repository!?"); assert_eq!(doc.project().unwrap().description, "Acme's repository!?");
} }
#[quickcheck] #[quickcheck]

View File

@ -26,7 +26,7 @@ pub mod prelude {
use super::*; use super::*;
pub use crypto::{PublicKey, Signer, Verified}; pub use crypto::{PublicKey, Signer, Verified};
pub use identity::{Did, Doc, Id}; pub use identity::{project::Project, Did, Doc, Id};
pub use node::NodeId; pub use node::NodeId;
pub use profile::Profile; pub use profile::Profile;
pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage}; pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage};

View File

@ -8,7 +8,7 @@ use thiserror::Error;
use crate::crypto::{Signer, Verified}; use crate::crypto::{Signer, Verified};
use crate::git; use crate::git;
use crate::identity::project::DocError; use crate::identity::project::{self, DocError, Project};
use crate::identity::Id; use crate::identity::Id;
use crate::node; use crate::node;
use crate::node::NodeId; use crate::node::NodeId;
@ -60,13 +60,12 @@ pub fn init<G: Signer>(
// TODO: Better error when project id already exists in storage, but remote doesn't. // TODO: Better error when project id already exists in storage, but remote doesn't.
let pk = signer.public_key(); let pk = signer.public_key();
let delegate = identity::Did::from(*pk); let delegate = identity::Did::from(*pk);
let doc = identity::Doc::initial( let proj = Project {
name.to_owned(), name: name.to_owned(),
description.to_owned(), description: description.to_owned(),
default_branch.clone(), default_branch: default_branch.clone(),
delegate, };
) let doc = identity::Doc::initial(proj, delegate).verified()?;
.verified()?;
let (id, _, project) = doc.create(pk, "Initialize Radicle\n", storage)?; let (id, _, project) = doc.create(pk, "Initialize Radicle\n", storage)?;
let url = git::Url::from(id).with_namespace(*pk); let url = git::Url::from(id).with_namespace(*pk);
@ -96,6 +95,8 @@ pub enum ForkError {
Git(#[from] git::Error), Git(#[from] git::Error),
#[error("storage: {0}")] #[error("storage: {0}")]
Storage(#[from] storage::Error), Storage(#[from] storage::Error),
#[error("payload: {0}")]
Payload(#[from] project::PayloadError),
#[error("project `{0}` was not found in storage")] #[error("project `{0}` was not found in storage")]
NotFound(Id), NotFound(Id),
#[error("project identity error: {0}")] #[error("project identity error: {0}")]
@ -123,9 +124,10 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
// refs/namespaces/<pk>/refs/tags/* // refs/namespaces/<pk>/refs/tags/*
let me = signer.public_key(); let me = signer.public_key();
let project = storage let doc = storage
.get(remote, proj)? .get(remote, proj)?
.ok_or(ForkError::NotFound(proj))?; .ok_or(ForkError::NotFound(proj))?;
let project = doc.project()?;
let repository = storage.repository(proj)?; let repository = storage.repository(proj)?;
let raw = repository.raw(); let raw = repository.raw();
@ -247,6 +249,8 @@ pub enum CheckoutError {
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error("storage: {0}")] #[error("storage: {0}")]
Storage(#[from] storage::Error), Storage(#[from] storage::Error),
#[error("payload: {0}")]
Payload(#[from] project::PayloadError),
#[error("project `{0}` was not found in storage")] #[error("project `{0}` was not found in storage")]
NotFound(Id), NotFound(Id),
#[error("project error: {0}")] #[error("project error: {0}")]
@ -263,9 +267,10 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
) -> Result<git2::Repository, CheckoutError> { ) -> Result<git2::Repository, CheckoutError> {
// TODO: Decide on whether we can use `clone_local` // TODO: Decide on whether we can use `clone_local`
// TODO: Look into sharing object databases. // TODO: Look into sharing object databases.
let project = storage let doc = storage
.get(remote, proj)? .get(remote, proj)?
.ok_or(CheckoutError::NotFound(proj))?; .ok_or(CheckoutError::NotFound(proj))?;
let project = doc.project()?;
let mut opts = git2::RepositoryInitOptions::new(); let mut opts = git2::RepositoryInitOptions::new();
opts.no_reinit(true).description(&project.description); opts.no_reinit(true).description(&project.description);
@ -375,7 +380,8 @@ mod tests {
) )
.unwrap(); .unwrap();
let project = storage.get(&public_key, proj).unwrap().unwrap(); let doc = storage.get(&public_key, proj).unwrap().unwrap();
let project = doc.project().unwrap();
let remotes: HashMap<_, _> = storage let remotes: HashMap<_, _> = storage
.repository(proj) .repository(proj)
.unwrap() .unwrap()
@ -402,7 +408,7 @@ mod tests {
assert_eq!(project.name, "acme"); assert_eq!(project.name, "acme");
assert_eq!(project.description, "Acme's repo"); assert_eq!(project.description, "Acme's repo");
assert_eq!(project.default_branch, git::refname!("master")); assert_eq!(project.default_branch, git::refname!("master"));
assert_eq!(project.delegates.first(), &Did::from(public_key)); assert_eq!(doc.delegates.first(), &Did::from(public_key));
} }
#[test] #[test]

View File

@ -11,7 +11,7 @@ use radicle_cob::{self as cob, change};
use crate::git; use crate::git;
use crate::identity; use crate::identity;
use crate::identity::project::{Identity, IdentityError}; use crate::identity::project::{Identity, IdentityError, Project};
use crate::identity::{Doc, Id}; use crate::identity::{Doc, Id};
use crate::storage::refs; use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs}; use crate::storage::refs::{Refs, SignedRefs};
@ -41,6 +41,8 @@ pub enum ProjectError {
Storage(#[from] Error), Storage(#[from] Error),
#[error("identity document error: {0}")] #[error("identity document error: {0}")]
Doc(#[from] identity::project::DocError), Doc(#[from] identity::project::DocError),
#[error("payload error: {0}")]
Payload(#[from] identity::project::PayloadError),
#[error("identity verification error: {0}")] #[error("identity verification error: {0}")]
Verify(#[from] identity::project::VerificationError), Verify(#[from] identity::project::VerificationError),
#[error("git: {0}")] #[error("git: {0}")]
@ -96,7 +98,7 @@ impl ReadStorage for Storage {
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> { fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> {
// TODO: Don't create a repo here if it doesn't exist? // TODO: Don't create a repo here if it doesn't exist?
// Perhaps for checking we could have a `contains` method? // Perhaps for checking we could have a `contains` method?
match self.repository(proj)?.project_of(remote) { match self.repository(proj)?.identity_of(remote) {
Ok(doc) => Ok(Some(doc)), Ok(doc) => Ok(Some(doc)),
Err(err) if err.is_not_found() => Ok(None), Err(err) if err.is_not_found() => Ok(None),
@ -271,7 +273,14 @@ impl Repository {
Identity::load(remote, self) Identity::load(remote, self)
} }
pub fn project_of(&self, remote: &RemoteId) -> Result<identity::Doc<Verified>, ProjectError> { pub fn project_of(&self, remote: &RemoteId) -> Result<Project, ProjectError> {
let doc = self.identity_of(remote)?;
let proj = doc.project()?;
Ok(proj)
}
pub fn identity_of(&self, remote: &RemoteId) -> Result<Doc<Verified>, ProjectError> {
let (doc, _) = identity::Doc::load(remote, self)?; let (doc, _) = identity::Doc::load(remote, self)?;
let verified = doc.verified()?; let verified = doc.verified()?;
@ -279,7 +288,7 @@ impl Repository {
} }
/// Return the canonical identity [`git::Oid`] and document. /// Return the canonical identity [`git::Oid`] and document.
pub fn project(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> { pub fn identity_doc(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
let mut heads = Vec::new(); let mut heads = Vec::new();
for remote in self.remote_ids()? { for remote in self.remote_ids()? {
let remote = remote?; let remote = remote?;
@ -478,7 +487,7 @@ impl ReadRepository for Repository {
} }
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> { fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
Repository::project(self) Repository::identity_doc(self)
} }
fn head(&self) -> Result<(Qualified, Oid), ProjectError> { fn head(&self) -> Result<(Qualified, Oid), ProjectError> {
@ -494,12 +503,14 @@ impl ReadRepository for Repository {
fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> { fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> {
// TODO: In the `fork` function for example, we call Repository::project_identity again, // TODO: In the `fork` function for example, we call Repository::project_identity again,
// This should only be necessary once. // This should only be necessary once.
let (_, project) = self.project_identity()?; let (_, doc) = self.project_identity()?;
let doc = doc.verified()?;
let project = doc.project()?;
let branch_ref = Qualified::from(lit::refs_heads(&project.default_branch)); let branch_ref = Qualified::from(lit::refs_heads(&project.default_branch));
let raw = self.raw(); let raw = self.raw();
let mut heads = Vec::new(); let mut heads = Vec::new();
for delegate in project.delegates.iter() { for delegate in doc.delegates.iter() {
let r = self.reference_oid(delegate, &branch_ref)?.into(); let r = self.reference_oid(delegate, &branch_ref)?.into();
heads.push(r); heads.push(r);

View File

@ -10,7 +10,7 @@ use qcheck::Arbitrary;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::git; use crate::git;
use crate::identity::{project::Doc, Did, Id}; use crate::identity::{project::Doc, project::Project, Did, Id};
use crate::storage; use crate::storage;
use crate::storage::refs::{Refs, SignedRefs}; use crate::storage::refs::{Refs, SignedRefs};
use crate::test::storage::MockStorage; use crate::test::storage::MockStorage;
@ -74,18 +74,7 @@ impl Arbitrary for Did {
} }
} }
impl Arbitrary for Doc<Unverified> { impl Arbitrary for Project {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let name = String::arbitrary(g);
let description = String::arbitrary(g);
let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
let delegate = Did::arbitrary(g);
Self::initial(name, description, default_branch, delegate)
}
}
impl Arbitrary for Doc<Verified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self { fn arbitrary(g: &mut qcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let name = iter::repeat_with(|| rng.alphanumeric()) let name = iter::repeat_with(|| rng.alphanumeric())
@ -99,14 +88,35 @@ impl Arbitrary for Doc<Verified> {
.collect::<String>() .collect::<String>()
.try_into() .try_into()
.unwrap(); .unwrap();
Project {
name,
description,
default_branch,
}
}
}
impl Arbitrary for Doc<Unverified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let proj = Project::arbitrary(g);
let delegate = Did::arbitrary(g);
Self::initial(proj, delegate)
}
}
impl Arbitrary for Doc<Verified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let project = Project::arbitrary(g);
let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g)) let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g))
.take(rng.usize(1..6)) .take(rng.usize(1..6))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.try_into() .try_into()
.unwrap(); .unwrap();
let threshold = delegates.len() / 2 + 1; let threshold = delegates.len() / 2 + 1;
let doc: Doc<Unverified> = let doc: Doc<Unverified> = Doc::new(project, delegates, threshold);
Doc::new(name, description, default_branch, delegates, threshold);
doc.verified().unwrap() doc.verified().unwrap()
} }