radicle: update radicle-git-ext

This change updates to the latest radicle-git-ext, which required a
few changes within the radicle family of crates.

One set of changes is that the radicle-git-ext crate subsumes
git-commit, git-trailers, and git-ref-format -- so all those imports
go through radicle-git-ext.

The commit code requires that parent `Oid`s point to commits, which
made some tests fail. One of the reasons for these failures is that
the patch tests used fabricated `Oid`s and so the verification would
fail. This is fixed by allowing the test context code to create
`PatchRequest`s that create valid commits.
The other reason for failure was that the identity `Action` passed an
`Oid` that pointed to a blob. This is fixed by removing that code and
making a note of it in documentation.

The final major change was moving the Refspec type into git-ref-format
and removing the AsRefspecs trait. The trait was not required and
could its usage could be replaced by simpler code.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-05-05 12:59:59 +01:00
parent 293a76948f
commit 8508eab8fa
No known key found for this signature in database
GPG Key ID: 2552FB6F64066CB7
37 changed files with 400 additions and 394 deletions

78
Cargo.lock generated
View File

@ -912,22 +912,11 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "git-commit"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42480682cb6d05a34304eaea858ff0008fe3ac8167e31eda360931c0b51a35c1"
dependencies = [
"git-trailers",
"git2",
"thiserror",
]
[[package]]
name = "git-ref-format"
version = "0.2.0"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c0eb2e3e4837359b843ab8cc5ad482bb84d4aa528153dc882dbbba44a2f4f47"
checksum = "dee7296543085890905b49ee720902b25133a8d878b7221488d0450bbd965741"
dependencies = [
"git-ref-format-core",
"git-ref-format-macro",
@ -935,19 +924,20 @@ dependencies = [
[[package]]
name = "git-ref-format-core"
version = "0.2.0"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cbf4ccf1d4cd0ac87a1461d6e59be34aec975bc2097850d7c3e164e3ff66436"
checksum = "54d2d2c6b5f0b38d20c51883abe9ee5879fb50e56a8255bb35ac0b0c679504c5"
dependencies = [
"minicbor",
"serde",
"thiserror",
]
[[package]]
name = "git-ref-format-macro"
version = "0.2.0"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3058716dac23a95ef68bd44c855c8e8368ee36feefc875bd8bdcb42ae582d98e"
checksum = "f6b7367435d968adc55ba8cd0bb3c12a3cb517270f110b5300df9c6f0726c762"
dependencies = [
"git-ref-format-core",
"proc-macro-error",
@ -955,21 +945,11 @@ dependencies = [
"syn",
]
[[package]]
name = "git-trailers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0934f57135449b88bea0e28efd80aab0c1b53692f8207c7e232086db824c7a8"
dependencies = [
"nom",
"thiserror",
]
[[package]]
name = "git2"
version = "0.16.1"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc"
checksum = "8b7905cdfe33d31a88bb2e8419ddd054451f5432d1da9eaf2ac7804ee1ea12d5"
dependencies = [
"bitflags",
"libc",
@ -1345,9 +1325,9 @@ checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "libgit2-sys"
version = "0.14.2+1.5.1"
version = "0.15.1+1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
checksum = "fb4577bde8cdfc7d6a2a4bcb7b049598597de33ffd337276e9c7db6cd4a2cee7"
dependencies = [
"cc",
"libc",
@ -1437,10 +1417,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
name = "minicbor"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
checksum = "124d887cb82f0b1469bdac3d1b65764a381eed1a54fdab0070e5772b13114521"
[[package]]
name = "miniz_oxide"
@ -1508,16 +1488,6 @@ dependencies = [
"cyphergraphy",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nonempty"
version = "0.5.0"
@ -1872,7 +1842,6 @@ dependencies = [
"crossbeam-channel",
"cyphernet",
"fastrand",
"git-ref-format",
"git2",
"localtime",
"log",
@ -1912,6 +1881,7 @@ dependencies = [
"radicle-cli-test",
"radicle-cob",
"radicle-crypto",
"radicle-git-ext",
"radicle-node",
"radicle-term",
"serde",
@ -1941,9 +1911,6 @@ name = "radicle-cob"
version = "0.1.0"
dependencies = [
"fastrand",
"git-commit",
"git-ref-format",
"git-trailers",
"git2",
"log",
"nonempty 0.8.1",
@ -1980,10 +1947,10 @@ dependencies = [
"cyphernet",
"ec25519",
"fastrand",
"git-ref-format",
"multibase",
"qcheck",
"qcheck-macros",
"radicle-git-ext",
"radicle-ssh",
"serde",
"sqlite",
@ -2002,9 +1969,9 @@ dependencies = [
[[package]]
name = "radicle-git-ext"
version = "0.2.1"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c68edd5604b96b0ede289513ce523b9cef82a9c4ab07f9e059a24036fc77a2da"
checksum = "69d38277eb376789c3ed50a673c99b7fead98c172dd976060b029b527e605148"
dependencies = [
"git-ref-format",
"git2",
@ -2060,7 +2027,6 @@ dependencies = [
"crossbeam-channel",
"cyphernet",
"fastrand",
"git-ref-format",
"io-reactor",
"lexopt",
"libc",
@ -2072,6 +2038,7 @@ dependencies = [
"qcheck-macros",
"radicle",
"radicle-crypto",
"radicle-git-ext",
"radicle-term",
"scrypt",
"serde",
@ -2110,14 +2077,13 @@ checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4"
[[package]]
name = "radicle-surf"
version = "0.9.0"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10168a9ee60ee17511fe0062cc2fa3115654f0c1f2d1692d8109a3f254c855de"
checksum = "7607a6c93d4cc399479cc0da0d259a42a0ed0b7ae2b051ed80ea8ed35f1246b1"
dependencies = [
"anyhow",
"base64",
"flate2",
"git-ref-format",
"git2",
"log",
"nonempty 0.5.0",
@ -2149,9 +2115,9 @@ name = "radicle-tools"
version = "0.2.0"
dependencies = [
"anyhow",
"git-ref-format",
"radicle",
"radicle-cli",
"radicle-git-ext",
]
[[package]]

View File

@ -18,6 +18,9 @@ json-color = { version = "0.7" }
lexopt = { version = "0.2" }
log = { version = "0.4", features = ["std"] }
nonempty = { version = "0.8" }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
radicle-git-ext = { version = "0", features = ["serde"] }
serde = { version = "1.0" }
serde_json = { version = "1" }
serde_yaml = { version = "0.8" }

View File

@ -6,7 +6,7 @@ delegates creating proposals concurrently.
```
$ rad id edit --title "Add Alice" --description "Add Alice as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
✓ Identity proposal 'f4579d0a9a068453cb24da1ad048ca1bfdcb6d98' created
✓ Identity proposal '04603c0d3ea4d137487024a51c9360adfc511114' created
title: Add Alice
description: Add Alice as a delegate
status: open
@ -48,7 +48,7 @@ Quorum Reached
```
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --no-confirm
✓ Identity proposal '5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f' created
✓ Identity proposal '3f6ae4f8645c8b0cbcd35ea924df7b13aca52774' created
title: Add Bob
description: Add Bob as a delegate
status: open
@ -93,7 +93,7 @@ second proposal, then the identity would be out of date. So let's run
through that and see what happens.
```
$ rad id accept f4579d0a9a068453cb24da1ad048ca1bfdcb6d98 --no-confirm
$ rad id accept 04603c0d3ea4d137487024a51c9360adfc511114 --no-confirm
✓ Accepted proposal ✓
title: Add Alice
description: Add Alice as a delegate
@ -137,7 +137,7 @@ Quorum Reached
```
```
$ rad id commit f4579d0a9a068453cb24da1ad048ca1bfdcb6d98 --no-confirm
$ rad id commit 04603c0d3ea4d137487024a51c9360adfc511114 --no-confirm
✓ Committed new identity '29ae4b72f5a315328f06fbd68dc1c396a2d5c45e'
title: Add Alice
description: Add Alice as a delegate
@ -183,7 +183,7 @@ Quorum Reached
Now, when we go to accept the second proposal:
```
$ rad id accept 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --no-confirm
$ rad id accept 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --no-confirm
! Warning: Revision is out of date
! Warning: d96f425412c9f8ad5d9a9a05c9831d0728e2338d =/= 475cdfbc8662853dd132ec564e4f5eb0f152dd7f
👉 Consider using 'rad id rebase' to update the proposal to the latest identity
@ -238,19 +238,19 @@ Note that a warning was emitted:
If we attempt to commit this revision, the command will fail:
```
$ rad id commit 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --no-confirm
$ rad id commit 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --no-confirm
! Warning: Revision is out of date
! Warning: d96f425412c9f8ad5d9a9a05c9831d0728e2338d =/= 475cdfbc8662853dd132ec564e4f5eb0f152dd7f
👉 Consider using 'rad id rebase' to update the proposal to the latest identity
✗ Id failed: the identity hashes do match 'd96f425412c9f8ad5d9a9a05c9831d0728e2338d =/= 475cdfbc8662853dd132ec564e4f5eb0f152dd7f' for the revision '5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f'
✗ Id failed: the identity hashes do match 'd96f425412c9f8ad5d9a9a05c9831d0728e2338d =/= 475cdfbc8662853dd132ec564e4f5eb0f152dd7f' for the revision '3f6ae4f8645c8b0cbcd35ea924df7b13aca52774'
```
So, let's fix this by running a rebase on the proposal's revision:
```
$ rad id rebase 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --no-confirm
✓ Identity proposal '5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f' rebased
✓ Revision '45477572abd02ea6fc3d3e710e034bb60c74038b'
$ rad id rebase 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --no-confirm
✓ Identity proposal '3f6ae4f8645c8b0cbcd35ea924df7b13aca52774' rebased
✓ Revision '42b9428df59ad349f706b1397750b75ea3b42574'
title: Add Bob
description: Add Bob as a delegate
status: open
@ -293,9 +293,9 @@ Quorum Reached
We can now update the proposal to have both keys in the delegates set:
```
$ rad id update 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --rev 45477572abd02ea6fc3d3e710e034bb60c74038b --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
✓ Identity proposal '5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f' updated
✓ Revision '66f68597d031f9678e704204bea5ce7f020c98e7'
$ rad id update 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --rev 42b9428df59ad349f706b1397750b75ea3b42574 --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
✓ Identity proposal '3f6ae4f8645c8b0cbcd35ea924df7b13aca52774' updated
✓ Revision '1b4ded759249e4f76d19c3e580b4736bf2a2d1c4'
title: Add Bob
description: Add Bob as a delegate
status: open
@ -338,10 +338,10 @@ Quorum Reached
Finally, we can accept and commit this proposal, creating the final
state of our new Radicle identity:
$ rad id show 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --revisions
$ rad id show 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --revisions
```
$ rad id accept 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --rev 66f68597d031f9678e704204bea5ce7f020c98e7 --no-confirm
$ rad id accept 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --rev 1b4ded759249e4f76d19c3e580b4736bf2a2d1c4 --no-confirm
✓ Accepted proposal ✓
title: Add Bob
description: Add Bob as a delegate
@ -385,7 +385,7 @@ Quorum Reached
```
```
$ rad id commit 5d6c0faeeb5ed5e0d66c72d853a6131a35228d3f --rev 66f68597d031f9678e704204bea5ce7f020c98e7 --no-confirm
$ rad id commit 3f6ae4f8645c8b0cbcd35ea924df7b13aca52774 --rev 1b4ded759249e4f76d19c3e580b4736bf2a2d1c4 --no-confirm
✓ Committed new identity '60de897bc24898f6908fd1272633c0b15aa4096f'
title: Add Bob
description: Add Bob as a delegate

View File

@ -14,7 +14,7 @@ Let's add Bob as a delegate using their DID
```
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
✓ Identity proposal 'c2fe23ff387d5e040d0ed8eb2651942f5d0c2aa5' created
✓ Identity proposal '0d396a83a5e1dda2b8929f7dc401d19dd1a79fb8' created
title: Add Bob
description: Add Bob as a delegate
status: open
@ -89,7 +89,7 @@ Finally, we can see whether the `Quorum` was reached:
Let's see what happens when we reject the change:
```
$ rad id reject c2fe23ff387d5e040d0ed8eb2651942f5d0c2aa5 --no-confirm
$ rad id reject 0d396a --no-confirm
✓ Rejected proposal 👎
title: Add Bob
description: Add Bob as a delegate
@ -145,7 +145,7 @@ increased to `1`.
Instead, let's accept the proposal:
```
$ rad id accept c2fe23f --no-confirm
$ rad id accept 0d396a --no-confirm
✓ Accepted proposal ✓
title: Add Bob
description: Add Bob as a delegate
@ -207,7 +207,7 @@ As well as that, the `Quorum` has now been reached:
At this point, we can commit the proposal and update the identity:
```
$ rad id commit c2fe23ff387d5e040d0ed8eb2651942f5d0c2aa5 --no-confirm
$ rad id commit 0d396a --no-confirm
✓ Committed new identity 'c96e764965aaeff1c6ea3e5b97e2b9828773c8b0'
title: Add Bob
description: Add Bob as a delegate
@ -255,7 +255,7 @@ the `--threshold` option:
```
$ rad id edit --title "Update threshold" --description "Update to safer threshold" --threshold 2 --no-confirm
✓ Identity proposal '27864167e392d63e77df2a7413c988857bd32839' created
✓ Identity proposal 'f435d6e89c8f922ede691287c0d8b7f82afa591e' created
title: Update threshold
description: Update to safer threshold
status: open
@ -298,8 +298,8 @@ Quorum Reached
But we change our minds and decide to close the proposal instead:
```
$ rad id close 27864167e392d63e77df2a7413c988857bd32839 --no-confirm
✓ Closed identity proposal '27864167e392d63e77df2a7413c988857bd32839'
$ rad id close f435d6 --no-confirm
✓ Closed identity proposal 'f435d6e89c8f922ede691287c0d8b7f82afa591e'
title: Update threshold
description: Update to safer threshold
status: closed
@ -348,15 +348,15 @@ Radicle identity, then we can use the list command:
```
$ rad id list
27864167e392d63e77df2a7413c988857bd32839 "Update threshold" closed
c2fe23ff387d5e040d0ed8eb2651942f5d0c2aa5 "Add Bob" committed
0d396a83a5e1dda2b8929f7dc401d19dd1a79fb8 "Add Bob" committed
f435d6e89c8f922ede691287c0d8b7f82afa591e "Update threshold" closed
```
And if we want to view the latest state of any proposal we can use the
show command:
```
$ rad id show 27864167e392d63e77df2a7413c988857bd32839
$ rad id show f435d6
title: Update threshold
description: Update to safer threshold
status: closed

View File

@ -513,6 +513,8 @@ fn test_clone_without_seeds() {
#[test]
fn test_cob_replication() {
logger::init(log::Level::Debug);
let mut environment = Environment::new();
let working = tempfile::tempdir().unwrap();
let mut alice = environment.node("alice");

View File

@ -14,17 +14,14 @@ keywords = ["radicle", "collaborative objects", "cob", "cobs"]
[dependencies]
fastrand = { version = "1.9.0" }
git-commit = { version = "0.3" }
git-ref-format = { version = "0.2" }
git-trailers = { version = "0.1" }
log = { version = "0.4.17" }
nonempty = { version = "0.8.1", features = ["serialize"] }
radicle-git-ext = { version = "0" }
radicle-git-ext = { version = "0", features = ["serde"] }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }
[dependencies.git2]
version = "0.16.1"
version = "0.17.0"
default-features = false
features = ["vendored-libgit2"]
@ -43,7 +40,6 @@ features = ["derive"]
[dev-dependencies]
fastrand = { version = "1.9.0", default-features = false }
git-ref-format = { version = "0.2", features = ["macro"] }
tempfile = { version = "3" }
qcheck = { version = "1", default-features = false }
qcheck-macros = { version = "1", default-features = false }

View File

@ -3,10 +3,12 @@
use std::collections::BTreeMap;
use std::convert::TryFrom;
use git_commit::{self as commit, Commit};
use git_ext::author;
use git_ext::author::Author;
use git_ext::commit::{headers::Headers, Commit};
use git_ext::Oid;
use git_trailers::OwnedTrailer;
use nonempty::NonEmpty;
use radicle_git_ext::commit::trailers::OwnedTrailer;
use crate::history::entry::Timestamp;
use crate::signatures;
@ -23,14 +25,16 @@ pub mod error {
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use git_ext::commit;
use git_ext::Oid;
use git_trailers::Error as TrailerError;
use thiserror::Error;
use crate::signatures::error::Signatures;
#[derive(Debug, Error)]
pub enum Create {
#[error(transparent)]
WriteCommit(#[from] commit::error::Write),
#[error(transparent)]
FromUtf8(#[from] FromUtf8Error),
#[error(transparent)]
@ -46,7 +50,7 @@ pub mod error {
#[derive(Debug, Error)]
pub enum Load {
#[error(transparent)]
Read(#[from] git_commit::error::Read),
Read(#[from] commit::error::Read),
#[error(transparent)]
Signatures(#[from] Signatures),
#[error(transparent)]
@ -73,8 +77,6 @@ pub mod error {
ResourceTrailer(#[from] super::trailers::error::InvalidResourceTrailer),
#[error("non utf-8 characters in commit message")]
Utf8(#[from] FromUtf8Error),
#[error(transparent)]
Trailer(#[from] TrailerError),
}
}
@ -262,7 +264,7 @@ where
let author = repo.signature()?;
let timestamp = author.when().seconds();
let mut headers = commit::Headers::new();
let mut headers = Headers::new();
headers.push(
"gpgsig",
signature
@ -270,13 +272,13 @@ where
.map_err(signatures::error::Signatures::from)?
.as_str(),
);
let author = commit::Author::try_from(&author)?;
let author = Author::try_from(&author)?;
#[cfg(debug_assertions)]
let (author, timestamp) = if let Ok(s) = std::env::var(crate::git::RAD_COMMIT_TIME) {
let timestamp = s.trim().parse::<i64>().unwrap();
let author = commit::Author {
time: git_commit::author::Time::new(timestamp, 0),
let author = Author {
time: author::Time::new(timestamp, 0),
..author
};
(author, timestamp)

View File

@ -2,8 +2,8 @@
use std::{convert::TryFrom as _, fmt, ops::Deref, str::FromStr};
use git_ext::ref_format::{Component, RefString};
use git_ext::Oid;
use git_ref_format::{Component, RefString};
use serde::{Deserialize, Serialize};
use thiserror::Error;

View File

@ -82,9 +82,9 @@ impl CollaborativeObject {
/// * The parsing of the [`TypeName`] fails
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
where
R: AsRef<git_ref_format::RefStr>,
R: AsRef<git_ext::ref_format::RefStr>,
{
use git_ref_format::Qualified;
use git_ext::ref_format::Qualified;
let name = name.as_ref();
let refs_cobs = match name.to_namespaced() {
None => Qualified::from_refstr(name)?,

View File

@ -2,8 +2,8 @@
use std::{collections::HashMap, error::Error};
use git_ext::ref_format::RefString;
use git_ext::Oid;
use git_ref_format::RefString;
use crate::change::Change;
use crate::{ObjectId, TypeName};
@ -95,7 +95,7 @@ pub trait Storage {
pub mod convert {
use std::str;
use git_ref_format::RefString;
use git_ext::ref_format::RefString;
use thiserror::Error;
use super::{Commit, Reference};
@ -109,7 +109,7 @@ pub mod convert {
err: git2::Error,
},
#[error(transparent)]
Ref(#[from] git_ref_format::Error),
Ref(#[from] git_ext::ref_format::Error),
#[error(transparent)]
Utf8(#[from] str::Utf8Error),
}

View File

@ -8,9 +8,9 @@ use std::{
};
use crypto::{ssh, PublicKey};
use git_commit::{
use git_ext::commit::{
headers::Signature::{Pgp, Ssh},
Commit,
Signature::{Pgp, Ssh},
};
pub use ssh::ExtendedSignature;

View File

@ -30,7 +30,7 @@ pub mod error {
#[error(transparent)]
Git(#[from] git2::Error),
#[error(transparent)]
Format(#[from] git_ref_format::Error),
Format(#[from] git_ext::ref_format::Error),
}
}

View File

@ -1,7 +1,7 @@
use std::ops::ControlFlow;
use crypto::test::signer::MockSigner;
use git_ref_format::{refname, Component, RefString};
use git_ext::ref_format::{refname, Component, RefString};
use nonempty::nonempty;
use qcheck::Arbitrary;
use radicle_crypto::Signer;

View File

@ -1,7 +1,7 @@
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
use git_trailers::{OwnedTrailer, Token, Trailer};
use radicle_git_ext as ext;
use git_ext::commit::trailers::{OwnedTrailer, Token, Trailer};
use std::ops::Deref as _;
pub mod error {
use thiserror::Error;
@ -30,11 +30,10 @@ impl ResourceCommitTrailer {
impl TryFrom<&Trailer<'_>> for ResourceCommitTrailer {
type Error = error::InvalidResourceTrailer;
fn try_from(Trailer { values, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
let val = values.first().ok_or(Self::Error::NoValue)?;
fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
let ext_oid =
radicle_git_ext::Oid::try_from(val.as_ref()).map_err(|_| Self::Error::InvalidOid)?;
if Some(token) == Token::try_from("Rad-Resource").ok().as_ref() {
git_ext::Oid::try_from(value.as_ref()).map_err(|_| Self::Error::InvalidOid)?;
if token.deref() == "Rad-Resource" {
Ok(ResourceCommitTrailer(ext_oid.into()))
} else {
Err(Self::Error::WrongToken)
@ -60,7 +59,7 @@ impl From<ResourceCommitTrailer> for Trailer<'_> {
fn from(containing: ResourceCommitTrailer) -> Self {
Trailer {
token: Token::try_from("Rad-Resource").unwrap(),
values: vec![containing.0.to_string().into()],
value: containing.0.to_string().into(),
}
}
}
@ -71,8 +70,8 @@ impl From<ResourceCommitTrailer> for OwnedTrailer {
}
}
impl From<ext::Oid> for ResourceCommitTrailer {
fn from(oid: ext::Oid) -> Self {
impl From<git_ext::Oid> for ResourceCommitTrailer {
fn from(oid: git_ext::Oid) -> Self {
Self(oid.into())
}
}

View File

@ -2,7 +2,7 @@
use std::{fmt, str::FromStr};
use git_ref_format::{Component, RefString};
use git_ext::ref_format::{Component, RefString};
use serde::{Deserialize, Serialize};
use thiserror::Error;

View File

@ -27,8 +27,8 @@ version = "1.8.0"
default-features = false
optional = true
[dependencies.git-ref-format]
version = "0.2"
[dependencies.radicle-git-ext]
version = "0.4"
optional = true
[dependencies.ssh-key]

View File

@ -360,13 +360,16 @@ impl PublicKey {
multibase::encode(multibase::Base::Base58Btc, buf)
}
#[cfg(feature = "git-ref-format")]
pub fn to_namespace(&self) -> git_ref_format::RefString {
git_ref_format::refname!("refs/namespaces").join(git_ref_format::Component::from(self))
#[cfg(feature = "radicle-git-ext")]
pub fn to_namespace(&self) -> radicle_git_ext::ref_format::RefString {
use radicle_git_ext::ref_format::{refname, Component};
refname!("refs/namespaces").join(Component::from(self))
}
#[cfg(feature = "git-ref-format")]
pub fn from_namespaced(refstr: &git_ref_format::Namespaced) -> Result<Self, PublicKeyError> {
#[cfg(feature = "radicle-git-ext")]
pub fn from_namespaced(
refstr: &radicle_git_ext::ref_format::Namespaced,
) -> Result<Self, PublicKeyError> {
let name = refstr.namespace().into_inner();
Self::from_str(name.deref().as_str())
@ -405,10 +408,10 @@ impl Deref for PublicKey {
}
}
#[cfg(feature = "git-ref-format")]
impl<'a> From<&PublicKey> for git_ref_format::Component<'a> {
#[cfg(feature = "radicle-git-ext")]
impl<'a> From<&PublicKey> for radicle_git_ext::ref_format::Component<'a> {
fn from(id: &PublicKey) -> Self {
use git_ref_format::{Component, RefString};
use radicle_git_ext::ref_format::{Component, RefString};
let refstr =
RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
Component::from_refstr(refstr).expect("encoded public keys are valid refname components")

View File

@ -25,7 +25,7 @@ flate2 = { version = "1" }
hyper = { version = "0.14.17", default-features = false }
lexopt = { version = "0.2.1" }
nonempty = { version = "0.8.1", features = ["serialize"] }
radicle-surf = { version = "0.9.0", default-features = false, features = ["serde"] }
radicle-surf = { version = "0.10.3", default-features = false, features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
thiserror = { version = "1" }

View File

@ -19,7 +19,6 @@ colored = { version = "1.9.0" }
crossbeam-channel = { version = "0.5.6" }
cyphernet = { version = "0.2.0", features = ["tor", "dns", "ed25519", "p2p-ed25519"] }
fastrand = { version = "1.9.0" }
git-ref-format = { version = "0.2", features = ["serde", "macro"] }
io-reactor = { version = "0.1.2", features = ["popol"] }
lexopt = { version = "0.2.1" }
libc = { version = "0.2.137" }
@ -28,6 +27,9 @@ localtime = { version = "1.2.0" }
netservices = { version = "0.2.2", features = ["io-reactor", "socket2"] }
nonempty = { version = "0.8.1", features = ["serialize"] }
qcheck = { version = "1", default-features = false, optional = true }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
radicle-git-ext = { version = "0", features = ["serde"] }
sqlite = { version = "0.30.3" }
sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking
scrypt = { version = "0.10.0", default-features = false }

View File

@ -478,7 +478,7 @@ impl Worker {
channels: &mut Channels,
) -> Result<(), FetchError>
where
S: fetch::AsRefspecs,
S: IntoIterator<Item = fetch::Refspec>,
{
let mut tunnel = Tunnel::with(channels, stream, self.nid, remote, self.handle.clone())?;
let tunnel_addr = tunnel.local_addr();
@ -498,7 +498,6 @@ impl Worker {
let namespace = self.nid.to_namespace();
let mut fetchspecs = specs
.into_refspecs()
.into_iter()
// Filter out our own refs, if we aren't cloning.
.filter(|fs| is_cloning || !fs.dst.starts_with(namespace.as_str()))

View File

@ -1,5 +1,5 @@
mod refspecs;
pub use refspecs::{AsRefspecs, Refspec, SpecialRefs};
pub use refspecs::SpecialRefs;
pub mod error;
@ -7,6 +7,7 @@ use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::ops::Deref;
use radicle::crypto::{PublicKey, Unverified, Verified};
use radicle::git::refspec;
use radicle::git::{url, Namespaced};
use radicle::prelude::{Doc, Id, NodeId};
use radicle::storage::git::Repository;
@ -15,6 +16,8 @@ use radicle::storage::{Namespaces, RefUpdate, Remote, RemoteId};
use radicle::storage::{ReadRepository, ReadStorage, WriteRepository, WriteStorage};
use radicle::{git, Storage};
pub type Refspec = refspec::Refspec<git::PatternString, git::PatternString>;
/// The initial phase of staging a fetch from a remote.
///
/// The [`StagingPhaseInitial::refpsecs`] generated are to fetch the
@ -146,15 +149,14 @@ impl<'a> StagingPhaseInitial<'a> {
/// Return the fetch refspecs for fetching the necessary `rad`
/// references.
pub fn refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
pub fn refspecs(&self) -> Vec<Refspec> {
let id = git::PatternString::from(IDENTITY_BRANCH.clone().into_refstring());
match self.repo {
StagedRepository::Cloning(_) => Refspec {
StagedRepository::Cloning(_) => vec![Refspec {
src: id.clone(),
dst: id,
force: false,
}
.into_refspecs(),
}],
StagedRepository::Fetching(_) => SpecialRefs(self.namespaces.clone()).into_refspecs(),
}
}
@ -229,7 +231,7 @@ impl<'a> StagingPhaseInitial<'a> {
// don't need to be re-fetched from the remote.
let mut remote = copy.remote_anonymous(&url)?;
let refspecs: Vec<_> = Namespaces::All
.into_refspecs()
.to_refspecs()
.into_iter()
.map(|s| s.to_string())
.collect();
@ -256,12 +258,19 @@ impl<'a> StagingPhaseInitial<'a> {
impl<'a> StagingPhaseFinal<'a> {
/// Return the fetch refspecs for fetching the necessary
/// references.
pub fn refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
pub fn refspecs(&self) -> Vec<Refspec> {
match &self.repo {
FinalStagedRepository::Cloning { trusted, .. } => {
Namespaces::Trusted(trusted.clone()).as_refspecs()
Namespaces::Trusted(trusted.clone()).to_refspecs()
}
FinalStagedRepository::Fetching { refs, .. } => refs.as_refspecs(),
FinalStagedRepository::Fetching { refs, .. } => refs
.iter()
.map(|r| Refspec {
src: r.clone().to_ref_string().into(),
dst: r.clone().to_ref_string().into(),
force: true,
})
.collect(),
}
}
@ -358,8 +367,8 @@ impl<'a> StagingPhaseFinal<'a> {
refspecs.push((
remote.id,
Refspec {
src: id.clone(),
dst: id,
src: id.clone().into(),
dst: id.into(),
// Nb. The identity branch is allowed to be force-updated.
force: true,
}
@ -368,8 +377,8 @@ impl<'a> StagingPhaseFinal<'a> {
refspecs.push((
remote.id,
Refspec {
src: sigrefs.clone(),
dst: sigrefs,
src: sigrefs.clone().into(),
dst: sigrefs.into(),
// Nb. Sigrefs are never force-updated.
force: false,
}

View File

@ -1,43 +1,16 @@
use std::collections::BTreeSet;
use std::fmt;
use std::fmt::Write as _;
use radicle::crypto::PublicKey;
use radicle::git;
use radicle::git::refs::storage::{IDENTITY_BRANCH, SIGREFS_BRANCH};
use radicle::storage;
use radicle::storage::git::NAMESPACES_GLOB;
use radicle::storage::{Namespaces, Remote};
use radicle::storage::Namespaces;
/// A Git [refspec].
///
/// [refspec]: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// TODO(finto): this should go into radicle-git-ext/git-ref-format
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Refspec<T, U> {
pub src: T,
pub dst: U,
pub force: bool,
}
impl<T, U> fmt::Display for Refspec<T, U>
where
T: fmt::Display,
U: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.force {
f.write_char('+')?;
}
write!(f, "{}:{}", self.src, self.dst)
}
}
use super::Refspec;
/// Radicle special refs, i.e. `refs/rad/*`.
pub struct SpecialRefs(pub(super) Namespaces);
impl AsRefspecs for SpecialRefs {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
impl SpecialRefs {
pub fn into_refspecs(self) -> Vec<Refspec> {
match &self.0 {
Namespaces::All => {
let id = NAMESPACES_GLOB.join(&*IDENTITY_BRANCH);
@ -55,13 +28,9 @@ impl AsRefspecs for SpecialRefs {
Namespaces::Trusted(pks) => pks.iter().flat_map(rad_refs).collect(),
}
}
fn into_refspecs(self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
self.as_refspecs()
}
}
fn rad_refs(pk: &PublicKey) -> Vec<Refspec<git::PatternString, git::PatternString>> {
fn rad_refs(pk: &PublicKey) -> Vec<Refspec> {
let ns = pk.to_namespace();
let id = git::PatternString::from(ns.join(&*IDENTITY_BRANCH));
let id = Refspec {
@ -77,95 +46,3 @@ fn rad_refs(pk: &PublicKey) -> Vec<Refspec<git::PatternString, git::PatternStrin
};
vec![id, sigrefs]
}
/// A conversion trait for producing a set of Git [`Refspec`]s.
pub trait AsRefspecs
where
Self: Sized,
{
/// Convert the borrowed data into a set of [`Refspec`]s.
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>>;
/// Convert the owned data into a set of [`Refspec`]s.
///
/// Nb. The default implementation uses
/// [`AsRefspecs::as_refspecs`], which may clone data.
fn into_refspecs(self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
self.as_refspecs()
}
}
impl AsRefspecs for Namespaces {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
match self {
Namespaces::All => vec![Refspec {
src: (*storage::git::NAMESPACES_GLOB).clone(),
dst: (*storage::git::NAMESPACES_GLOB).clone(),
force: true,
}],
Namespaces::Trusted(pks) => pks
.iter()
.map(|pk| {
let ns = pk.to_namespace().with_pattern(git::refspec::STAR);
Refspec {
src: ns.clone(),
dst: ns,
force: true,
}
})
.collect(),
}
}
}
impl AsRefspecs for Refspec<git::PatternString, git::PatternString> {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
vec![self.clone()]
}
fn into_refspecs(self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
vec![self]
}
}
impl<T: AsRefspecs> AsRefspecs for Vec<T> {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
self.iter().flat_map(AsRefspecs::as_refspecs).collect()
}
fn into_refspecs(self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
self.into_iter()
.flat_map(AsRefspecs::into_refspecs)
.collect()
}
}
impl AsRefspecs for Remote {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
let ns = self.id.to_namespace();
// Nb. the references in Refs are expected to be Qualified
self.refs
.iter()
.map(|(name, _)| {
let name = git::PatternString::from(ns.join(name));
Refspec {
src: name.clone(),
dst: name,
force: true,
}
})
.collect()
}
}
impl<'a> AsRefspecs for BTreeSet<git::Namespaced<'a>> {
fn as_refspecs(&self) -> Vec<Refspec<git::PatternString, git::PatternString>> {
self.iter()
.map(|r| Refspec {
src: r.clone().to_ref_string().into(),
dst: r.clone().to_ref_string().into(),
force: true,
})
.collect()
}
}

View File

@ -7,7 +7,9 @@ edition = "2021"
[dependencies]
anyhow = { version = "1" }
git-ref-format = { version = "0", features = ["serde", "macro"] }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
radicle-git-ext = { version = "0", features = ["serde"] }
[dependencies.radicle]
version = "0"

View File

@ -14,7 +14,6 @@ amplify = { version = "4.0.0-beta.7", default-features = false, features = ["std
crossbeam-channel = { version = "0.5.6" }
cyphernet = { version = "0.2.0", features = ["tor", "dns", "ed25519"] }
fastrand = { version = "1.9.0" }
git-ref-format = { version = "0", features = ["serde", "macro"] }
multibase = { version = "0.9.1" }
localtime = { version = "1.2.0" }
log = { version = "0.4.17", features = ["std"] }
@ -30,7 +29,7 @@ thiserror = { version = "1" }
unicode-normalization = { version = "0.1" }
[dependencies.git2]
version = "0.16.1"
version = "0.17.0"
default-features = false
features = ["vendored-libgit2"]
@ -45,7 +44,7 @@ version = "0"
[dependencies.radicle-crypto]
path = "../radicle-crypto"
version = "0"
features = ["git-ref-format", "ssh", "sqlite", "cyphernet"]
features = ["radicle-git-ext", "ssh", "sqlite", "cyphernet"]
[dependencies.radicle-ssh]
path = "../radicle-ssh"

View File

@ -59,6 +59,8 @@ pub enum Action {
revision: RevisionId,
},
Revision {
// N.b. the `Oid` is a blob identifier and not a commit, so we
// do not need to propagate it via HistoryAction.
current: Oid,
proposed: Doc<Verified>,
},
@ -68,16 +70,7 @@ pub enum Action {
},
}
impl HistoryAction for Action {
fn parents(&self) -> Vec<Oid> {
match self {
Self::Revision { current, .. } => {
vec![*current]
}
_ => vec![],
}
}
}
impl HistoryAction for Action {}
/// Error applying an operation onto a state.
#[derive(Error, Debug)]

View File

@ -575,7 +575,9 @@ mod test {
#[test]
fn test_issue_create_and_assign() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let assignee: ActorId = arbitrary::gen(1);
@ -612,7 +614,9 @@ mod test {
#[test]
fn test_issue_create_and_reassign() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let assignee: ActorId = arbitrary::gen(1);
@ -642,7 +646,9 @@ mod test {
#[test]
fn test_issue_create_and_get() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let created = issues
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
@ -664,7 +670,9 @@ mod test {
#[test]
fn test_issue_create_and_change_state() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
@ -698,7 +706,9 @@ mod test {
#[test]
fn test_issue_create_and_unassign() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let assignee: ActorId = arbitrary::gen(1);
@ -726,7 +736,9 @@ mod test {
#[test]
fn test_issue_edit_title() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
@ -744,7 +756,9 @@ mod test {
#[test]
fn test_issue_react() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
@ -767,7 +781,9 @@ mod test {
#[test]
fn test_issue_reply() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
@ -805,7 +821,9 @@ mod test {
#[test]
fn test_issue_tag() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let bug_tag = Tag::new("bug").unwrap();
let ux_tag = Tag::new("ux").unwrap();
@ -835,7 +853,9 @@ mod test {
#[test]
fn test_issue_comment() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let author = *signer.public_key();
let mut issues = Issues::open(&project).unwrap();
let mut issue = issues
@ -886,7 +906,9 @@ mod test {
#[test]
fn test_issue_all() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
issues.create("First", "Blah", &[], &[], &signer).unwrap();
@ -910,7 +932,9 @@ mod test {
#[test]
fn test_issue_multilines() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let test::setup::Context {
signer, project, ..
} = test::setup::Context::new(&tmp);
let mut issues = Issues::open(&project).unwrap();
let created = issues
.create(

View File

@ -1391,21 +1391,21 @@ mod test {
#[test]
fn test_patch_create_and_get() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let author: Did = signer.public_key().into();
let target = MergeTarget::Delegates;
let oid = git::Oid::from_str("e2a85016a458cd809c0ecee81f8c99613b0b0945").unwrap();
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let patch = patches
.create(
"My first patch",
"Blah blah blah.",
target,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1426,8 +1426,8 @@ mod test {
assert_eq!(revision.author.id(), &author);
assert_eq!(revision.description(), "");
assert_eq!(revision.discussion.len(), 0);
assert_eq!(revision.oid, oid);
assert_eq!(revision.base, base);
assert_eq!(revision.oid, pr.oid);
assert_eq!(revision.base, pr.base);
let (id, _, _) = patches.find_by_revision(rev_id).unwrap().unwrap();
assert_eq!(id, patch_id);
@ -1436,17 +1436,19 @@ mod test {
#[test]
fn test_patch_discussion() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
git::Oid::try_from("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap(),
git::Oid::try_from("e2a85016a458cd809c0ecee81f8c99613b0b0945").unwrap(),
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1455,7 +1457,7 @@ mod test {
let (revision_id, _) = patch.revisions().last().unwrap();
assert!(
patch
.comment(*revision_id, "patch comment", None, &signer)
.comment(*revision_id, "patch comment", None, signer)
.is_ok(),
"can comment on patch"
);
@ -1468,25 +1470,25 @@ mod test {
#[test]
fn test_patch_merge() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let oid = git::Oid::from_str("e2a85016a458cd809c0ecee81f8c99613b0b0945").unwrap();
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
let id = patch.id;
let (rid, _) = patch.revisions().next().unwrap();
let _merge = patch.merge(*rid, base, &signer).unwrap();
let _merge = patch.merge(*rid, pr.base, signer).unwrap();
let patch = patches.get(&id).unwrap().unwrap();
@ -1496,25 +1498,25 @@ mod test {
let merge = merges.first().unwrap();
assert_eq!(merge.node, *signer.public_key());
assert_eq!(merge.commit, base);
assert_eq!(merge.commit, pr.base);
}
#[test]
fn test_patch_review() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1525,7 +1527,7 @@ mod test {
Some(Verdict::Accept),
Some("LGTM".to_owned()),
vec![],
&signer,
signer,
)
.unwrap();
@ -1626,20 +1628,20 @@ mod test {
#[test]
fn test_patch_review_edit() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let blob = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d133999").unwrap();
let mut patches = Patches::open(&project).unwrap();
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1650,7 +1652,7 @@ mod test {
location: CodeLocation {
blob,
path: Path::new("file.rs").to_path_buf(),
commit: oid,
commit: pr.oid,
lines: 1..3,
},
comment: "Nice!".to_owned(),
@ -1662,7 +1664,7 @@ mod test {
Some(Verdict::Accept),
Some("LGTM".to_owned()),
inline.clone(),
&signer,
signer,
)
.unwrap();
patch
@ -1671,7 +1673,7 @@ mod test {
Some(Verdict::Reject),
Some("LGTM".to_owned()),
vec![],
&signer,
signer,
)
.unwrap(); // Overwrite the verdict.
@ -1691,7 +1693,7 @@ mod test {
Some(Verdict::Reject),
Some("Whoops!".to_owned()),
vec![],
&signer,
signer,
)
.unwrap(); // Overwrite the comment.
let (_, revision) = patch.latest().unwrap();
@ -1704,19 +1706,19 @@ mod test {
#[test]
fn test_patch_reject_to_accept() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1729,7 +1731,7 @@ mod test {
Some(Verdict::Reject),
Some("Nah".to_owned()),
vec![],
&signer,
signer,
)
.unwrap();
patch
@ -1738,7 +1740,7 @@ mod test {
Some(Verdict::Accept),
Some("LGTM".to_owned()),
vec![],
&signer,
signer,
)
.unwrap();
@ -1755,19 +1757,19 @@ mod test {
#[test]
fn test_patch_review_remove_fields() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1780,10 +1782,10 @@ mod test {
Some(Verdict::Reject),
Some("Nah".to_owned()),
vec![],
&signer,
signer,
)
.unwrap();
patch.review(rid, None, None, vec![], &signer).unwrap();
patch.review(rid, None, None, vec![], signer).unwrap();
let id = patch.id;
let patch = patches.get_mut(&id).unwrap();
@ -1797,20 +1799,19 @@ mod test {
#[test]
fn test_patch_update() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp);
let base = git::Oid::from_str("af08e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let rev0_oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let rev1_oid = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let mut patches = Patches::open(&project).unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
base,
rev0_oid,
pr.base,
pr.oid,
&[],
&signer,
signer,
)
.unwrap();
@ -1818,8 +1819,9 @@ mod test {
assert_eq!(patch.description(), "Blah blah blah.");
assert_eq!(patch.version(), 0);
let update = ctx.branch_with(test::setup::update_blobs());
let _ = patch
.update("I've made changes.", base, rev1_oid, &signer)
.update("I've made changes.", pr.base, update.oid, signer)
.unwrap();
assert_eq!(patch.clock.get(), 2);
@ -1837,7 +1839,7 @@ mod test {
let (_, revision) = patch.latest().unwrap();
assert_eq!(patch.version(), 1);
assert_eq!(revision.oid, rev1_oid);
assert_eq!(revision.oid, update.oid);
assert_eq!(revision.description(), "I've made changes.");
}
}

View File

@ -497,7 +497,7 @@ mod tests {
#[test]
fn test_redact_comment() {
let tmp = tempfile::tempdir().unwrap();
let (_, signer, _) = radicle::test::setup::context(&tmp);
let radicle::test::setup::Context { signer, .. } = radicle::test::setup::Context::new(&tmp);
let repo = gen::<MockRepository>(1);
let mut alice = Actor::new(signer);
let mut thread = Thread::default();

View File

@ -3,7 +3,7 @@ use std::path::Path;
use std::process::Command;
use std::str::FromStr;
use git_ref_format as format;
use git_ext::ref_format as format;
use once_cell::sync::Lazy;
use crate::collections::HashMap;
@ -17,8 +17,8 @@ pub use ext::Error;
pub use ext::NotFound;
pub use ext::Oid;
pub use git2 as raw;
pub use git_ref_format as fmt;
pub use git_ref_format::{
pub use git_ext::ref_format as fmt;
pub use git_ext::ref_format::{
component, lit, name, qualified, refname, refspec,
refspec::{PatternStr, PatternString},
Component, Namespaced, Qualified, RefStr, RefString,
@ -152,6 +152,7 @@ pub mod refs {
pub mod storage {
use format::{
lit,
name::component,
refspec::{self, PatternString},
};
@ -181,7 +182,7 @@ pub mod refs {
/// `refs/namespaces/<remote>/refs/heads/<branch>`
///
pub fn branch<'a>(remote: &RemoteId, branch: &RefStr) -> Namespaced<'a> {
Qualified::from(git_ref_format::lit::refs_heads(branch)).with_namespace(remote.into())
Qualified::from(lit::refs_heads(branch)).with_namespace(remote.into())
}
/// Get the branch where the project's identity document is stored.

View File

@ -1,7 +1,7 @@
use std::ops::Deref;
use std::{ffi::OsString, fmt, str::FromStr};
use git_ref_format::{Component, RefString};
use git_ext::ref_format::{Component, RefString};
use thiserror::Error;
use crate::crypto;

View File

@ -7,6 +7,7 @@ pub extern crate radicle_crypto as crypto;
#[macro_use]
extern crate amplify;
extern crate radicle_git_ext as git_ext;
mod canonical;
pub mod cob;

View File

@ -16,11 +16,12 @@ pub use radicle_git_ext::Oid;
use crate::collections::HashMap;
use crate::git::ext as git_ext;
use crate::git::{Qualified, RefError, RefString};
use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefString};
use crate::identity;
use crate::identity::doc::DocError;
use crate::identity::Did;
use crate::identity::{Id, IdentityError};
use crate::storage::git::NAMESPACES_GLOB;
use crate::storage::refs::Refs;
use self::refs::SignedRefs;
@ -38,6 +39,29 @@ pub enum Namespaces {
Trusted(HashSet<PublicKey>),
}
impl Namespaces {
pub fn to_refspecs(&self) -> Vec<Refspec<PatternString, PatternString>> {
match self {
Namespaces::All => vec![Refspec {
src: (*NAMESPACES_GLOB).clone(),
dst: (*NAMESPACES_GLOB).clone(),
force: true,
}],
Namespaces::Trusted(pks) => pks
.iter()
.map(|pk| {
let ns = pk.to_namespace().with_pattern(git::refspec::STAR);
Refspec {
src: ns.clone(),
dst: ns,
force: true,
}
})
.collect(),
}
}
}
impl FromIterator<PublicKey> for Namespaces {
fn from_iter<T: IntoIterator<Item = PublicKey>>(iter: T) -> Self {
Self::Trusted(iter.into_iter().collect())
@ -236,6 +260,22 @@ impl Remote<Verified> {
refs: self.refs.unverified(),
}
}
pub fn to_refspecs(&self) -> Vec<Refspec<PatternString, PatternString>> {
let ns = self.id.to_namespace();
// Nb. the references in Refs are expected to be Qualified
self.refs
.iter()
.map(|(name, _)| {
let name = PatternString::from(ns.join(name));
Refspec {
src: name.clone(),
dst: name,
force: true,
}
})
.collect()
}
}
impl<V> Deref for Remote<V> {

View File

@ -6,7 +6,6 @@ use std::path::{Path, PathBuf};
use std::{fs, io};
use crypto::{Signer, Unverified, Verified};
use git_ref_format::refspec;
use once_cell::sync::Lazy;
use crate::git;
@ -25,10 +24,10 @@ pub use crate::storage::Error;
use super::RemoteId;
pub static NAMESPACES_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*"));
pub static NAMESPACES_GLOB: Lazy<git::refspec::PatternString> =
Lazy::new(|| git::refspec::pattern!("refs/namespaces/*"));
pub static SIGREFS_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*/rad/sigrefs"));
Lazy::new(|| git::refspec::pattern!("refs/namespaces/*/rad/sigrefs"));
pub static CANONICAL_IDENTITY: Lazy<git::Qualified> = Lazy::new(|| {
git::Qualified::from_components(
git::name::component!("rad"),

View File

@ -31,7 +31,7 @@ pub enum TypesError {
#[error(transparent)]
ParseObjectId(#[from] cob::object::ParseObjectId),
#[error(transparent)]
RefFormat(#[from] git_ref_format::Error),
RefFormat(#[from] git::fmt::Error),
}
impl cob::Store for Repository {}

View File

@ -361,7 +361,7 @@ pub mod canonical {
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
InvalidRef(#[from] git_ref_format::Error),
InvalidRef(#[from] git::fmt::Error),
#[error("invalid canonical format")]
InvalidFormat,
#[error(transparent)]

View File

@ -10,20 +10,108 @@ pub mod setup {
use crate::crypto::test::signer::MockSigner;
use crate::prelude::*;
use crate::{
git,
profile::Home,
rad::REMOTE_NAME,
test::{fixtures, storage::git::Repository},
Storage,
};
pub fn context(tmp: &TempDir) -> (Storage, MockSigner, Repository) {
let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng);
let home = tmp.path().join("home");
let paths = Home::new(home.as_path()).unwrap();
let storage = Storage::open(paths.storage()).unwrap();
let (id, _, _, _) = fixtures::project(tmp.path().join("copy"), &storage, &signer).unwrap();
let project = storage.repository(id).unwrap();
#[derive(Debug)]
pub struct BranchWith {
pub base: git::Oid,
pub oid: git::Oid,
}
(storage, signer, project)
pub struct Context {
pub storage: Storage,
pub signer: MockSigner,
pub project: Repository,
pub working: git2::Repository,
}
impl Context {
pub fn new(tmp: &TempDir) -> Self {
let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng);
let home = tmp.path().join("home");
let paths = Home::new(home.as_path()).unwrap();
let storage = Storage::open(paths.storage()).unwrap();
let (id, _, working, _) =
fixtures::project(tmp.path().join("copy"), &storage, &signer).unwrap();
let project = storage.repository(id).unwrap();
Self {
storage,
signer,
project,
working,
}
}
pub fn branch_with(
&self,
blobs: impl IntoIterator<Item = (String, Vec<u8>)>,
) -> BranchWith {
let refname = git::Qualified::from(git::lit::refs_heads(git::refname!("master")));
let base = self.working.refname_to_id(refname.as_str()).unwrap();
let parent = self.working.find_commit(base).unwrap();
let oid = commit(&self.working, &refname, blobs, &[&parent]);
git::push(&self.working, &REMOTE_NAME, [(&refname, &refname)]).unwrap();
BranchWith {
base: base.into(),
oid,
}
}
}
pub fn initial_blobs() -> Vec<(String, Vec<u8>)> {
vec![
("README.md".to_string(), b"Hello, World!".to_vec()),
(
"CONTRIBUTING".to_string(),
b"Please follow the rules".to_vec(),
),
]
}
pub fn update_blobs() -> Vec<(String, Vec<u8>)> {
vec![
("README.md".to_string(), b"Hello, Radicle!".to_vec()),
(
"CONTRIBUTING".to_string(),
b"Please follow the rules".to_vec(),
),
]
}
pub fn commit(
repo: &git2::Repository,
refname: &git::Qualified,
blobs: impl IntoIterator<Item = (String, Vec<u8>)>,
parents: &[&git2::Commit<'_>],
) -> git::Oid {
let tree = {
let mut tb = repo.treebuilder(None).unwrap();
for (name, blob) in blobs.into_iter() {
let oid = repo.blob(&blob).unwrap();
tb.insert(name, oid, git2::FileMode::Blob.into()).unwrap();
}
tb.write().unwrap()
};
let tree = repo.find_tree(tree).unwrap();
let author = git2::Signature::now("anonymous", "anonymous@example.com").unwrap();
repo.commit(
Some(refname.as_str()),
&author,
&author,
"test commit",
&tree,
parents,
)
.unwrap()
.into()
}
}

View File

@ -2,8 +2,7 @@ use std::collections::HashMap;
use std::io;
use std::path::{Path, PathBuf};
use git_ref_format as fmt;
use radicle_git_ext as git_ext;
use git_ext::ref_format as fmt;
use crate::crypto::{Signer, Verified};
use crate::identity::doc::{Doc, DocError, Id};