workspace: A Little Less `git2`
Make `git2` an *optional* dependency of `radicle-cob`, and refactor
`radicle` to depend on crates that in turn do not depend on `git2`
*non-optionally*
The main offending dependency of `radicle-cob` is `radicle-git-ext`
from the `radicle-git` workspace in repository
(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on
`git2`.
So, to achieve removal of this dependency:
1. The crate is refactored to depend on the new crates
`radicle-git-ref-format` `radicle-git-metadata`, and
`radicle-oid` introduced in the previous commits, instead of
`radicle-git-ext`.
2. Some code from the `radicle-git-ext` crate in the `radicle-git`
workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
copied. See `crates/radicle-cob/src/backend/git/commit.rs`.
This cascades to `radicle` and its dependents.
Firstly, the there is an
`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made
it very convenient to just deref to obtain the wrapped `git2::Oid`,
so there are many expressions of the shape `*oid` in `radicle` and
its dependents. However `radicle-oid` does not provide
`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,
`Target` is an associated type, and not a type parameter, so an
implementation of `Deref` would tie the new `Oid` too tightly to a
particular implementation.
Instead, work with `impl From<radicle_oid::Oid> for git2::Oid`
(which can be enabled using the feature flag `radicle-oid/git2`).
This explains the changes from `*oid` to `oid.into()` at every
transition to "`git2` land".
Secondly, `radicle` and its dependents are refactored to also depend
on `radicle-git-ref-format` and (much less prominently)
`radicle-git-metadata` instead of `radicle-git-ext`
This is to avoid pulling in `git2` via these dependencies.
Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,
they are at the same time also cleaned up. Notably, the types from
`radicle-git-ref-format` are re-exported under `fmt` only, not "twice".
While overall this obviously is very much a breaking change, these
changes should mostly amount to changing from `Deref` to `Into`, i.e.,
`*oid` → `oid.into()` and rewriting imports for `radicle:
:fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
This commit is contained in:
parent
f4a8908727
commit
b7cfcfff75
|
|
@ -239,12 +239,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
|
|
@ -1119,9 +1113,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-ref-format"
|
name = "git-ref-format"
|
||||||
version = "0.3.1"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1"
|
checksum = "76314f6eb43910ebc5eb89a1f0728724a6b9144dabd18bca4e7cc7c01c3804e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"git-ref-format-core",
|
"git-ref-format-core",
|
||||||
"git-ref-format-macro",
|
"git-ref-format-macro",
|
||||||
|
|
@ -1129,9 +1123,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-ref-format-core"
|
name = "git-ref-format-core"
|
||||||
version = "0.3.1"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
|
checksum = "ae6d9ee666ca7d4ad49cbf7174f785f299a18b37565694f665e8c7df24999cdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bstr",
|
"bstr",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1140,12 +1134,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-ref-format-macro"
|
name = "git-ref-format-macro"
|
||||||
version = "0.3.1"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4"
|
checksum = "2dc2ded12a6ea2b6e63afaf09415e4c15bf4baa74e530acd9daed9ff47e7dd41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"git-ref-format-core",
|
"git-ref-format-core",
|
||||||
"proc-macro-error",
|
"proc-macro-error2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.89",
|
"syn 2.0.89",
|
||||||
]
|
]
|
||||||
|
|
@ -2238,12 +2232,6 @@ dependencies = [
|
||||||
"cyphergraphy",
|
"cyphergraphy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nonempty"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nonempty"
|
name = "nonempty"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -2643,27 +2631,25 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error-attr2"
|
||||||
version = "1.0.4"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error-attr"
|
name = "proc-macro-error2"
|
||||||
version = "1.0.4"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"version_check",
|
"syn 2.0.89",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2751,13 +2737,15 @@ dependencies = [
|
||||||
"localtime",
|
"localtime",
|
||||||
"log",
|
"log",
|
||||||
"multibase",
|
"multibase",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"qcheck",
|
"qcheck",
|
||||||
"qcheck-macros",
|
"qcheck-macros",
|
||||||
"radicle-cob",
|
"radicle-cob",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-git-ext",
|
"radicle-git-metadata",
|
||||||
|
"radicle-git-ref-format",
|
||||||
|
"radicle-oid",
|
||||||
"radicle-ssh",
|
"radicle-ssh",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -2779,19 +2767,18 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dunce",
|
"dunce",
|
||||||
"git-ref-format",
|
|
||||||
"human-panic",
|
"human-panic",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lexopt",
|
"lexopt",
|
||||||
"localtime",
|
"localtime",
|
||||||
"log",
|
"log",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle-cli-test",
|
"radicle-cli-test",
|
||||||
"radicle-cob",
|
"radicle-cob",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-git-ext",
|
"radicle-git-ref-format",
|
||||||
"radicle-node",
|
"radicle-node",
|
||||||
"radicle-surf",
|
"radicle-surf",
|
||||||
"radicle-term",
|
"radicle-term",
|
||||||
|
|
@ -2837,14 +2824,17 @@ name = "radicle-cob"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
"git-ref-format-core",
|
||||||
"git2",
|
"git2",
|
||||||
"log",
|
"log",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"qcheck",
|
"qcheck",
|
||||||
"qcheck-macros",
|
"qcheck-macros",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-dag",
|
"radicle-dag",
|
||||||
"radicle-git-ext",
|
"radicle-git-metadata",
|
||||||
|
"radicle-git-ref-format",
|
||||||
|
"radicle-oid",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
|
|
@ -2894,17 +2884,18 @@ dependencies = [
|
||||||
"gix-protocol",
|
"gix-protocol",
|
||||||
"gix-transport",
|
"gix-transport",
|
||||||
"log",
|
"log",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle-git-ext",
|
"radicle-git-ref-format",
|
||||||
|
"radicle-oid",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radicle-git-ext"
|
name = "radicle-git-ext"
|
||||||
version = "0.8.1"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650"
|
checksum = "fb3de6999a8ff570e0dd92a04ba3132853dd08a3bcfc2f7faf56de7b1bd36053"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"git-ref-format",
|
"git-ref-format",
|
||||||
"git2",
|
"git2",
|
||||||
|
|
@ -2944,13 +2935,12 @@ dependencies = [
|
||||||
"localtime",
|
"localtime",
|
||||||
"log",
|
"log",
|
||||||
"mio 1.0.4",
|
"mio 1.0.4",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"qcheck",
|
"qcheck",
|
||||||
"qcheck-macros",
|
"qcheck-macros",
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-fetch",
|
"radicle-fetch",
|
||||||
"radicle-git-ext",
|
|
||||||
"radicle-protocol",
|
"radicle-protocol",
|
||||||
"radicle-signals",
|
"radicle-signals",
|
||||||
"radicle-systemd",
|
"radicle-systemd",
|
||||||
|
|
@ -2991,14 +2981,13 @@ dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"localtime",
|
"localtime",
|
||||||
"log",
|
"log",
|
||||||
"nonempty 0.9.0",
|
"nonempty",
|
||||||
"paste",
|
"paste",
|
||||||
"qcheck",
|
"qcheck",
|
||||||
"qcheck-macros",
|
"qcheck-macros",
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-fetch",
|
"radicle-fetch",
|
||||||
"radicle-git-ext",
|
|
||||||
"scrypt",
|
"scrypt",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -3015,7 +3004,6 @@ dependencies = [
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle-cli",
|
"radicle-cli",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-git-ext",
|
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3049,22 +3037,22 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radicle-std-ext"
|
name = "radicle-std-ext"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4"
|
checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radicle-surf"
|
name = "radicle-surf"
|
||||||
version = "0.22.0"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb308c3989087f71e43d8c7a2737273fdc7fbcd3e6628af81a42f601ae64f314"
|
checksum = "2f08adc954d4a49287d86bd1ce14ca7e34a11a3acd904044db30551ca8f4e46a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.1",
|
"base64 0.21.7",
|
||||||
"flate2",
|
"flate2",
|
||||||
"git2",
|
"git2",
|
||||||
"log",
|
"log",
|
||||||
"nonempty 0.5.0",
|
"nonempty",
|
||||||
"radicle-git-ext",
|
"radicle-git-ext",
|
||||||
"radicle-std-ext",
|
"radicle-std-ext",
|
||||||
"tar",
|
"tar",
|
||||||
|
|
|
||||||
11
Cargo.toml
11
Cargo.toml
|
|
@ -28,8 +28,6 @@ crossbeam-channel = "0.5.6"
|
||||||
cyphernet = "0.5.2"
|
cyphernet = "0.5.2"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
fastrand = { version = "2.0.0", default-features = false }
|
fastrand = { version = "2.0.0", default-features = false }
|
||||||
git-ref-format-core = { version = "0.3.0", default-features = false }
|
|
||||||
git-ref-format = { version = "0.3.0", default-features = false }
|
|
||||||
git2 = { version = "0.19.0", default-features = false }
|
git2 = { version = "0.19.0", default-features = false }
|
||||||
human-panic = "2"
|
human-panic = "2"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
|
|
@ -49,7 +47,7 @@ radicle-cob = { version = "0.17", path = "crates/radicle-cob" }
|
||||||
radicle-crypto = { version = "0.14", path = "crates/radicle-crypto" }
|
radicle-crypto = { version = "0.14", path = "crates/radicle-crypto" }
|
||||||
radicle-dag = { version = "0.10", path = "crates/radicle-dag" }
|
radicle-dag = { version = "0.10", path = "crates/radicle-dag" }
|
||||||
radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" }
|
radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" }
|
||||||
radicle-git-ext = { version = "0.8", default-features = false }
|
radicle-git-metadata = { version = "0.1.0", path = "crates/radicle-git-metadata", default-features = false }
|
||||||
radicle-git-ref-format = { version = "0.1.0", path = "crates/radicle-git-ref-format", default-features = false }
|
radicle-git-ref-format = { version = "0.1.0", path = "crates/radicle-git-ref-format", default-features = false }
|
||||||
radicle-node = { version = "0.16", path = "crates/radicle-node" }
|
radicle-node = { version = "0.16", path = "crates/radicle-node" }
|
||||||
radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false }
|
radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false }
|
||||||
|
|
@ -70,6 +68,13 @@ thiserror = "1.0"
|
||||||
winpipe = "0.1.1"
|
winpipe = "0.1.1"
|
||||||
zeroize = "1.5.7"
|
zeroize = "1.5.7"
|
||||||
|
|
||||||
|
# Crates from the "radicle-git" workspace. These should be synced manually.
|
||||||
|
# When updating, start from `radicle-surf`:
|
||||||
|
# `radicle-surf` → `radicle-git-ext` → `git-ref-format` → `git-ref-format-core`
|
||||||
|
# Also note that `radicle-surf → git2` so try to also sync with `git2`.
|
||||||
|
git-ref-format-core = { version = "0.5.0", default-features = false }
|
||||||
|
radicle-surf = "0.25.0"
|
||||||
|
|
||||||
[workspace.lints]
|
[workspace.lints]
|
||||||
clippy.type_complexity = "allow"
|
clippy.type_complexity = "allow"
|
||||||
clippy.enum_variant_names = "allow"
|
clippy.enum_variant_names = "allow"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ anyhow = "1"
|
||||||
chrono = { workspace = true, features = ["clock", "std"] }
|
chrono = { workspace = true, features = ["clock", "std"] }
|
||||||
clap = { version = "4.5.44", features = ["derive"] }
|
clap = { version = "4.5.44", features = ["derive"] }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
git-ref-format = { version = "0.3.0", features = ["macro"] }
|
|
||||||
human-panic.workspace = true
|
human-panic.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
lexopt = { workspace = true }
|
lexopt = { workspace = true }
|
||||||
|
|
@ -28,10 +27,8 @@ nonempty = { workspace = true }
|
||||||
radicle = { workspace = true, features = ["logger", "schemars"] }
|
radicle = { workspace = true, features = ["logger", "schemars"] }
|
||||||
radicle-cob = { workspace = true }
|
radicle-cob = { workspace = true }
|
||||||
radicle-crypto = { workspace = true }
|
radicle-crypto = { workspace = true }
|
||||||
# N.b. this is required to use macros, even though it's re-exported
|
radicle-git-ref-format = { workspace = true, features = ["macro"] }
|
||||||
# through radicle
|
radicle-surf = { workspace = true }
|
||||||
radicle-git-ext = { workspace = true, features = ["serde"] }
|
|
||||||
radicle-surf = "0.22.0"
|
|
||||||
radicle-term = { workspace = true }
|
radicle-term = { workspace = true }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -156,9 +156,9 @@ pub fn setup_remotes(
|
||||||
pub fn setup_remote(
|
pub fn setup_remote(
|
||||||
setup: &project::SetupRemote,
|
setup: &project::SetupRemote,
|
||||||
remote_id: &NodeId,
|
remote_id: &NodeId,
|
||||||
remote_name: Option<git::RefString>,
|
remote_name: Option<git::fmt::RefString>,
|
||||||
aliases: &impl AliasStore,
|
aliases: &impl AliasStore,
|
||||||
) -> anyhow::Result<git::RefString> {
|
) -> anyhow::Result<git::fmt::RefString> {
|
||||||
let remote_name = if let Some(name) = remote_name {
|
let remote_name = if let Some(name) = remote_name {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -167,7 +167,7 @@ pub fn setup_remote(
|
||||||
} else {
|
} else {
|
||||||
remote_id.to_human()
|
remote_id.to_human()
|
||||||
};
|
};
|
||||||
git::RefString::try_from(name.as_str())
|
git::fmt::RefString::try_from(name.as_str())
|
||||||
.map_err(|_| anyhow!("invalid remote name: '{name}'"))?
|
.map_err(|_| anyhow!("invalid remote name: '{name}'"))?
|
||||||
};
|
};
|
||||||
let (remote, branch) = setup.run(&remote_name, *remote_id)?;
|
let (remote, branch) = setup.run(&remote_name, *remote_id)?;
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,17 @@ use std::process;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use git_ref_format::Qualified;
|
|
||||||
use localtime::LocalTime;
|
use localtime::LocalTime;
|
||||||
use radicle::cob::TypedId;
|
use radicle::cob::TypedId;
|
||||||
|
use radicle::git::fmt::Qualified;
|
||||||
|
use radicle::git::BranchName;
|
||||||
use radicle::identity::Identity;
|
use radicle::identity::Identity;
|
||||||
use radicle::issue::cache::Issues as _;
|
use radicle::issue::cache::Issues as _;
|
||||||
use radicle::node::notifications;
|
use radicle::node::notifications;
|
||||||
use radicle::node::notifications::*;
|
use radicle::node::notifications::*;
|
||||||
use radicle::patch::cache::Patches as _;
|
use radicle::patch::cache::Patches as _;
|
||||||
use radicle::prelude::{NodeId, Profile, RepoId};
|
use radicle::prelude::{NodeId, Profile, RepoId};
|
||||||
use radicle::storage::{BranchName, ReadRepository, ReadStorage};
|
use radicle::storage::{ReadRepository, ReadStorage};
|
||||||
use radicle::{cob, git, Storage};
|
use radicle::{cob, git, Storage};
|
||||||
|
|
||||||
use term::Element as _;
|
use term::Element as _;
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ use serde_json as json;
|
||||||
|
|
||||||
use radicle::crypto::ssh;
|
use radicle::crypto::ssh;
|
||||||
use radicle::explorer::ExplorerUrl;
|
use radicle::explorer::ExplorerUrl;
|
||||||
|
use radicle::git::fmt::RefString;
|
||||||
use radicle::git::raw;
|
use radicle::git::raw;
|
||||||
use radicle::git::raw::ErrorExt as _;
|
use radicle::git::raw::ErrorExt as _;
|
||||||
use radicle::git::RefString;
|
|
||||||
use radicle::identity::project::ProjectName;
|
use radicle::identity::project::ProjectName;
|
||||||
use radicle::identity::{Doc, RepoId, Visibility};
|
use radicle::identity::{Doc, RepoId, Visibility};
|
||||||
use radicle::node::events::UploadPack;
|
use radicle::node::events::UploadPack;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob::patch::PatchId;
|
use radicle::cob::patch::PatchId;
|
||||||
use radicle::cob::{patch, Label, Reaction};
|
use radicle::cob::{patch, Label, Reaction};
|
||||||
use radicle::git::RefString;
|
use radicle::git::fmt::RefString;
|
||||||
use radicle::patch::cache::Patches as _;
|
use radicle::patch::cache::Patches as _;
|
||||||
use radicle::storage::git::transport;
|
use radicle::storage::git::transport;
|
||||||
use radicle::{prelude::*, Node};
|
use radicle::{prelude::*, Node};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use git_ref_format::Qualified;
|
|
||||||
use radicle::cob::patch;
|
use radicle::cob::patch;
|
||||||
use radicle::cob::patch::RevisionId;
|
use radicle::cob::patch::RevisionId;
|
||||||
|
use radicle::git::fmt::Qualified;
|
||||||
|
use radicle::git::fmt::RefString;
|
||||||
use radicle::git::raw::ErrorExt as _;
|
use radicle::git::raw::ErrorExt as _;
|
||||||
use radicle::git::RefString;
|
|
||||||
use radicle::patch::cache::Patches as _;
|
use radicle::patch::cache::Patches as _;
|
||||||
use radicle::patch::PatchId;
|
use radicle::patch::PatchId;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
@ -25,7 +25,7 @@ impl Options {
|
||||||
Some(refname) => Ok(Qualified::from_refstr(refname)
|
Some(refname) => Ok(Qualified::from_refstr(refname)
|
||||||
.map_or_else(|| refname.clone(), |q| q.to_ref_string())),
|
.map_or_else(|| refname.clone(), |q| q.to_ref_string())),
|
||||||
// SAFETY: Patch IDs are valid refstrings.
|
// SAFETY: Patch IDs are valid refstrings.
|
||||||
None => Ok(git::refname!("patch")
|
None => Ok(git::fmt::refname!("patch")
|
||||||
.join(RefString::try_from(term::format::cob(id).item).unwrap())),
|
.join(RefString::try_from(term::format::cob(id).item).unwrap())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,8 +57,7 @@ pub fn run(
|
||||||
let mut spinner = term::spinner("Performing checkout...");
|
let mut spinner = term::spinner("Performing checkout...");
|
||||||
let patch_branch = opts.branch(patch_id)?;
|
let patch_branch = opts.branch(patch_id)?;
|
||||||
|
|
||||||
let commit =
|
let commit = match working.find_branch(patch_branch.as_str(), git::raw::BranchType::Local) {
|
||||||
match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
|
|
||||||
Ok(branch) if opts.force => {
|
Ok(branch) if opts.force => {
|
||||||
let commit = find_patch_commit(revision, stored, working)?;
|
let commit = find_patch_commit(revision, stored, working)?;
|
||||||
let mut r = branch.into_reference();
|
let mut r = branch.into_reference();
|
||||||
|
|
@ -67,7 +66,7 @@ pub fn run(
|
||||||
}
|
}
|
||||||
Ok(branch) => {
|
Ok(branch) => {
|
||||||
let head = branch.get().peel_to_commit()?;
|
let head = branch.get().peel_to_commit()?;
|
||||||
if head.id() != *revision.head() {
|
if revision.head() != head.id() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"branch '{patch_branch}' already exists (use `--force` to overwrite)"
|
"branch '{patch_branch}' already exists (use `--force` to overwrite)"
|
||||||
);
|
);
|
||||||
|
|
@ -84,7 +83,7 @@ pub fn run(
|
||||||
};
|
};
|
||||||
|
|
||||||
if opts.force {
|
if opts.force {
|
||||||
let mut builder = radicle::git::raw::build::CheckoutBuilder::new();
|
let mut builder = git::raw::build::CheckoutBuilder::new();
|
||||||
builder.force();
|
builder.force();
|
||||||
working.checkout_tree(commit.as_object(), Some(&mut builder))?;
|
working.checkout_tree(commit.as_object(), Some(&mut builder))?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -125,7 +124,7 @@ fn find_patch_commit<'a>(
|
||||||
stored: &Repository,
|
stored: &Repository,
|
||||||
working: &'a git::raw::Repository,
|
working: &'a git::raw::Repository,
|
||||||
) -> anyhow::Result<git::raw::Commit<'a>> {
|
) -> anyhow::Result<git::raw::Commit<'a>> {
|
||||||
let head = *revision.head();
|
let head = revision.head().into();
|
||||||
|
|
||||||
match working.find_commit(head) {
|
match working.find_commit(head) {
|
||||||
Ok(commit) => Ok(commit),
|
Ok(commit) => Ok(commit),
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ use radicle::cob::patch::{PatchId, Revision, Verdict};
|
||||||
use radicle::cob::{CodeLocation, CodeRange};
|
use radicle::cob::{CodeLocation, CodeRange};
|
||||||
use radicle::crypto;
|
use radicle::crypto;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
|
use radicle::git::Oid;
|
||||||
use radicle::node::device::Device;
|
use radicle::node::device::Device;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::{cob::DraftStore, Repository};
|
use radicle::storage::git::{cob::DraftStore, Repository};
|
||||||
use radicle_git_ext::Oid;
|
|
||||||
use radicle_surf::diff::*;
|
use radicle_surf::diff::*;
|
||||||
use radicle_term::{Element, VStack};
|
use radicle_term::{Element, VStack};
|
||||||
|
|
||||||
|
|
@ -196,25 +196,28 @@ impl ReviewItem {
|
||||||
|
|
||||||
fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) {
|
fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) {
|
||||||
match self {
|
match self {
|
||||||
Self::FileAdded { path, new, .. } => (None, Some((path, new.oid))),
|
Self::FileAdded { path, new, .. } => (None, Some((path, Oid::from(*new.oid)))),
|
||||||
Self::FileDeleted { path, old, .. } => (Some((path, old.oid)), None),
|
Self::FileDeleted { path, old, .. } => (Some((path, Oid::from(*old.oid))), None),
|
||||||
Self::FileMoved { moved } => (
|
Self::FileMoved { moved } => (
|
||||||
Some((&moved.old_path, moved.old.oid)),
|
Some((&moved.old_path, Oid::from(*moved.old.oid))),
|
||||||
Some((&moved.new_path, moved.new.oid)),
|
Some((&moved.new_path, Oid::from(*moved.new.oid))),
|
||||||
),
|
),
|
||||||
Self::FileCopied { copied } => (
|
Self::FileCopied { copied } => (
|
||||||
Some((&copied.old_path, copied.old.oid)),
|
Some((&copied.old_path, Oid::from(*copied.old.oid))),
|
||||||
Some((&copied.new_path, copied.new.oid)),
|
Some((&copied.new_path, Oid::from(*copied.new.oid))),
|
||||||
|
),
|
||||||
|
Self::FileModified { path, old, new, .. } => (
|
||||||
|
Some((path, Oid::from(*old.oid))),
|
||||||
|
Some((path, Oid::from(*new.oid))),
|
||||||
|
),
|
||||||
|
Self::FileEofChanged { path, old, new, .. } => (
|
||||||
|
Some((path, Oid::from(*old.oid))),
|
||||||
|
Some((path, Oid::from(*new.oid))),
|
||||||
|
),
|
||||||
|
Self::FileModeChanged { path, old, new, .. } => (
|
||||||
|
Some((path, Oid::from(*old.oid))),
|
||||||
|
Some((path, Oid::from(*new.oid))),
|
||||||
),
|
),
|
||||||
Self::FileModified { path, old, new, .. } => {
|
|
||||||
(Some((path, old.oid)), Some((path, new.oid)))
|
|
||||||
}
|
|
||||||
Self::FileEofChanged { path, old, new, .. } => {
|
|
||||||
(Some((path, old.oid)), Some((path, new.oid)))
|
|
||||||
}
|
|
||||||
Self::FileModeChanged { path, old, new, .. } => {
|
|
||||||
(Some((path, old.oid)), Some((path, new.oid)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,7 +482,7 @@ impl FileReviewBuilder {
|
||||||
/// of changes introduced by a patch.
|
/// of changes introduced by a patch.
|
||||||
pub struct Brain<'a> {
|
pub struct Brain<'a> {
|
||||||
/// Where the review draft is being stored.
|
/// Where the review draft is being stored.
|
||||||
refname: git::Namespaced<'a>,
|
refname: git::fmt::Namespaced<'a>,
|
||||||
/// The commit pointed to by the ref.
|
/// The commit pointed to by the ref.
|
||||||
head: git::raw::Commit<'a>,
|
head: git::raw::Commit<'a>,
|
||||||
/// The tree of accepted changes pointed to by the head commit.
|
/// The tree of accepted changes pointed to by the head commit.
|
||||||
|
|
@ -565,7 +568,7 @@ impl<'a> Brain<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the brain's refname given the patch and remote.
|
/// Get the brain's refname given the patch and remote.
|
||||||
fn refname(patch: &PatchId, remote: &NodeId) -> git::Namespaced<'a> {
|
fn refname(patch: &PatchId, remote: &NodeId) -> git::fmt::Namespaced<'a> {
|
||||||
git::refs::storage::draft::review(remote, patch)
|
git::refs::storage::draft::review(remote, patch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use radicle::cob::patch;
|
use radicle::cob::patch;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
|
use radicle::git::Oid;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
|
|
@ -9,7 +10,7 @@ use crate::terminal::patch::*;
|
||||||
/// Run patch update.
|
/// Run patch update.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
patch_id: patch::PatchId,
|
patch_id: patch::PatchId,
|
||||||
base_id: Option<git::raw::Oid>,
|
base_id: Option<Oid>,
|
||||||
message: term::patch::Message,
|
message: term::patch::Message,
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
repository: &Repository,
|
repository: &Repository,
|
||||||
|
|
@ -27,22 +28,25 @@ pub fn run(
|
||||||
let head_oid = branch_oid(&head_branch)?;
|
let head_oid = branch_oid(&head_branch)?;
|
||||||
let base_oid = match base_id {
|
let base_oid = match base_id {
|
||||||
Some(oid) => oid,
|
Some(oid) => oid,
|
||||||
None => repository.backend.merge_base(*target_oid, *head_oid)?,
|
None => repository
|
||||||
|
.backend
|
||||||
|
.merge_base(target_oid.into(), head_oid.into())?
|
||||||
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// N.b. we don't update if both the head and base are the same as
|
// N.b. we don't update if both the head and base are the same as
|
||||||
// any previous revision
|
// any previous revision
|
||||||
if patch
|
if patch
|
||||||
.revisions()
|
.revisions()
|
||||||
.any(|(_, revision)| revision.head() == head_oid && **revision.base() == base_oid)
|
.any(|(_, revision)| revision.head() == head_oid && *revision.base() == base_oid)
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, revision) = patch.latest();
|
let (_, revision) = patch.latest();
|
||||||
let message = term::patch::get_update_message(message, workdir, revision, &head_oid)?;
|
let message = term::patch::get_update_message(message, workdir, revision, &head_oid.into())?;
|
||||||
let signer = term::signer(profile)?;
|
let signer = term::signer(profile)?;
|
||||||
let revision = patch.update(message, base_oid, *head_oid, &signer)?;
|
let revision = patch.update(message, base_oid, head_oid, &signer)?;
|
||||||
|
|
||||||
term::print(revision);
|
term::print(revision);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::ffi::OsString;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::git::RefString;
|
use radicle::git::fmt::RefString;
|
||||||
use radicle::prelude::NodeId;
|
use radicle::prelude::NodeId;
|
||||||
use radicle::storage::ReadStorage;
|
use radicle::storage::ReadStorage;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::RefString;
|
use radicle::git::fmt::RefString;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::Profile;
|
use radicle::Profile;
|
||||||
use radicle_crypto::PublicKey;
|
use radicle_crypto::PublicKey;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let remote = remote?;
|
let remote = remote?;
|
||||||
let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
|
let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
|
||||||
let mut walk = repo.raw().revwalk()?;
|
let mut walk = repo.raw().revwalk()?;
|
||||||
walk.push(*sigrefs)?;
|
walk.push(sigrefs.into())?;
|
||||||
|
|
||||||
stats.local.pushes += walk.count();
|
stats.local.pushes += walk.count();
|
||||||
stats.local.forks += 1;
|
stats.local.forks += 1;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ Options
|
||||||
|
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
rid: Option<RepoId>,
|
rid: Option<RepoId>,
|
||||||
refstr: git::RefString,
|
refstr: git::fmt::RefString,
|
||||||
target: Option<git::Oid>,
|
target: Option<git::Oid>,
|
||||||
nid: Option<NodeId>,
|
nid: Option<NodeId>,
|
||||||
interval: time::Duration,
|
interval: time::Duration,
|
||||||
|
|
@ -56,7 +56,7 @@ impl Args for Options {
|
||||||
let mut rid = None;
|
let mut rid = None;
|
||||||
let mut nid: Option<NodeId> = None;
|
let mut nid: Option<NodeId> = None;
|
||||||
let mut target: Option<git::Oid> = None;
|
let mut target: Option<git::Oid> = None;
|
||||||
let mut refstr: Option<git::RefString> = None;
|
let mut refstr: Option<git::fmt::RefString> = None;
|
||||||
let mut interval: Option<time::Duration> = None;
|
let mut interval: Option<time::Duration> = None;
|
||||||
let mut timeout: time::Duration = time::Duration::MAX;
|
let mut timeout: time::Duration = time::Duration::MAX;
|
||||||
|
|
||||||
|
|
@ -166,7 +166,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
fn reference<R: ReadRepository>(
|
fn reference<R: ReadRepository>(
|
||||||
repo: &R,
|
repo: &R,
|
||||||
nid: &NodeId,
|
nid: &NodeId,
|
||||||
qual: &git::Qualified,
|
qual: &git::fmt::Qualified,
|
||||||
) -> Result<Option<git::Oid>, git::raw::Error> {
|
) -> Result<Option<git::Oid>, git::raw::Error> {
|
||||||
match repo.reference_oid(nid, qual) {
|
match repo.reference_oid(nid, qual) {
|
||||||
Ok(oid) => Ok(Some(oid)),
|
Ok(oid) => Ok(Some(oid)),
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,15 @@ use thiserror::Error;
|
||||||
|
|
||||||
use radicle::crypto::ssh;
|
use radicle::crypto::ssh;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::raw as git2;
|
|
||||||
use radicle::git::{Version, VERSION_REQUIRED};
|
use radicle::git::{Version, VERSION_REQUIRED};
|
||||||
use radicle::prelude::{NodeId, RepoId};
|
use radicle::prelude::{NodeId, RepoId};
|
||||||
use radicle::storage::git::transport;
|
use radicle::storage::git::transport;
|
||||||
|
|
||||||
|
pub use radicle::git::Oid;
|
||||||
|
|
||||||
pub use radicle::git::raw::{
|
pub use radicle::git::raw::{
|
||||||
build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis,
|
build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, ErrorExt as _,
|
||||||
MergeOptions, Oid, Reference, Repository, Signature,
|
MergeAnalysis, MergeOptions, Reference, Repository, Signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign";
|
pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign";
|
||||||
|
|
@ -46,10 +47,10 @@ impl Rev {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the revision to an [`From<git2::Oid>`].
|
/// Resolve the revision to an [`From<git::raw::Oid>`].
|
||||||
pub fn resolve<T>(&self, repo: &git2::Repository) -> Result<T, git2::Error>
|
pub fn resolve<T>(&self, repo: &Repository) -> Result<T, git::raw::Error>
|
||||||
where
|
where
|
||||||
T: From<git2::Oid>,
|
T: From<git::raw::Oid>,
|
||||||
{
|
{
|
||||||
let object = repo.revparse_single(self.as_str())?;
|
let object = repo.revparse_single(self.as_str())?;
|
||||||
Ok(object.id().into())
|
Ok(object.id().into())
|
||||||
|
|
@ -84,13 +85,13 @@ pub struct Remote<'a> {
|
||||||
pub url: radicle::git::Url,
|
pub url: radicle::git::Url,
|
||||||
pub pushurl: Option<radicle::git::Url>,
|
pub pushurl: Option<radicle::git::Url>,
|
||||||
|
|
||||||
inner: git2::Remote<'a>,
|
inner: git::raw::Remote<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<git2::Remote<'a>> for Remote<'a> {
|
impl<'a> TryFrom<git::raw::Remote<'a>> for Remote<'a> {
|
||||||
type Error = RemoteError;
|
type Error = RemoteError;
|
||||||
|
|
||||||
fn try_from(value: git2::Remote<'a>) -> Result<Self, Self::Error> {
|
fn try_from(value: git::raw::Remote<'a>) -> Result<Self, Self::Error> {
|
||||||
let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| {
|
let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| {
|
||||||
Ok(radicle::git::Url::from_str(url)?)
|
Ok(radicle::git::Url::from_str(url)?)
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -110,7 +111,7 @@ impl<'a> TryFrom<git2::Remote<'a>> for Remote<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for Remote<'a> {
|
impl<'a> Deref for Remote<'a> {
|
||||||
type Target = git2::Remote<'a>;
|
type Target = git::raw::Remote<'a>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.inner
|
&self.inner
|
||||||
|
|
@ -250,7 +251,7 @@ pub fn is_signing_configured(repo: &Path) -> Result<bool, anyhow::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the list of radicle remotes for the given repository.
|
/// Return the list of radicle remotes for the given repository.
|
||||||
pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result<Vec<Remote>> {
|
pub fn rad_remotes(repo: &Repository) -> anyhow::Result<Vec<Remote>> {
|
||||||
let remotes: Vec<_> = repo
|
let remotes: Vec<_> = repo
|
||||||
.remotes()?
|
.remotes()?
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -263,16 +264,16 @@ pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result<Vec<Remote>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the git remote is configured for the `Repository`.
|
/// Check if the git remote is configured for the `Repository`.
|
||||||
pub fn is_remote(repo: &git2::Repository, alias: &str) -> anyhow::Result<bool> {
|
pub fn is_remote(repo: &Repository, alias: &str) -> anyhow::Result<bool> {
|
||||||
match repo.find_remote(alias) {
|
match repo.find_remote(alias) {
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(err) if err.code() == git2::ErrorCode::NotFound => Ok(false),
|
Err(err) if err.is_not_found() => Ok(false),
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the repository's "rad" remote.
|
/// Get the repository's "rad" remote.
|
||||||
pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git2::Remote, RepoId)> {
|
pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git::raw::Remote, RepoId)> {
|
||||||
match radicle::rad::remote(repo) {
|
match radicle::rad::remote(repo) {
|
||||||
Ok((remote, id)) => Ok((remote, id)),
|
Ok((remote, id)) => Ok((remote, id)),
|
||||||
Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!(
|
Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!(
|
||||||
|
|
@ -352,12 +353,12 @@ pub fn parse_remote(refspec: &str) -> Option<(NodeId, &str)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_tag(
|
pub fn add_tag(
|
||||||
repo: &git2::Repository,
|
repo: &Repository,
|
||||||
message: &str,
|
message: &str,
|
||||||
patch_tag_name: &str,
|
patch_tag_name: &str,
|
||||||
) -> anyhow::Result<git2::Oid> {
|
) -> anyhow::Result<git::raw::Oid> {
|
||||||
let head = repo.head()?;
|
let head = repo.head()?;
|
||||||
let commit = head.peel(git2::ObjectType::Commit).unwrap();
|
let commit = head.peel(git::raw::ObjectType::Commit).unwrap();
|
||||||
let oid = repo.tag(patch_tag_name, &commit, &repo.signature()?, message, false)?;
|
let oid = repo.tag(patch_tag_name, &commit, &repo.signature()?, message, false)?;
|
||||||
|
|
||||||
Ok(oid)
|
Ok(oid)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle_git_ext::Oid;
|
use radicle::git::Oid;
|
||||||
use radicle_surf::diff;
|
use radicle_surf::diff;
|
||||||
use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved};
|
use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved};
|
||||||
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
|
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
|
||||||
|
|
@ -33,7 +33,7 @@ pub trait Repo {
|
||||||
|
|
||||||
impl Repo for git::raw::Repository {
|
impl Repo for git::raw::Repository {
|
||||||
fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> {
|
fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> {
|
||||||
let blob = self.find_blob(*oid)?;
|
let blob = self.find_blob(oid.into())?;
|
||||||
|
|
||||||
if blob.is_binary() {
|
if blob.is_binary() {
|
||||||
Ok(Blob::Binary)
|
Ok(Blob::Binary)
|
||||||
|
|
@ -338,7 +338,7 @@ impl ToPretty for Added {
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let old = None;
|
let old = None;
|
||||||
let new = Some((self.path.as_path(), self.new.oid));
|
let new = Some((self.path.as_path(), Oid::from(*self.new.oid)));
|
||||||
|
|
||||||
pretty_modification(header, &self.diff, old, new, repo, hi)
|
pretty_modification(header, &self.diff, old, new, repo, hi)
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +354,7 @@ impl ToPretty for Deleted {
|
||||||
header: &Self::Context,
|
header: &Self::Context,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let old = Some((self.path.as_path(), self.old.oid));
|
let old = Some((self.path.as_path(), Oid::from(*self.old.oid)));
|
||||||
let new = None;
|
let new = None;
|
||||||
|
|
||||||
pretty_modification(header, &self.diff, old, new, repo, hi)
|
pretty_modification(header, &self.diff, old, new, repo, hi)
|
||||||
|
|
@ -371,8 +371,8 @@ impl ToPretty for Modified {
|
||||||
header: &Self::Context,
|
header: &Self::Context,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let old = Some((self.path.as_path(), self.old.oid));
|
let old = Some((self.path.as_path(), Oid::from(*self.old.oid)));
|
||||||
let new = Some((self.path.as_path(), self.new.oid));
|
let new = Some((self.path.as_path(), Oid::from(*self.new.oid)));
|
||||||
|
|
||||||
pretty_modification(header, &self.diff, old, new, repo, hi)
|
pretty_modification(header, &self.diff, old, new, repo, hi)
|
||||||
}
|
}
|
||||||
|
|
@ -595,8 +595,8 @@ mod test {
|
||||||
use term::Element;
|
use term::Element;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use radicle::git::raw::RepositoryOpenFlags;
|
use git::raw::RepositoryOpenFlags;
|
||||||
use radicle::git::raw::{Oid, Repository};
|
use git::raw::{Oid, Repository};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use radicle_surf::diff::FileStats;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::raw::Oid;
|
|
||||||
use radicle_surf::diff;
|
use radicle_surf::diff;
|
||||||
use radicle_surf::diff::{Diff, DiffContent, DiffFile, FileDiff, Hunk, Hunks, Line, Modification};
|
use radicle_surf::diff::{Diff, DiffContent, DiffFile, FileDiff, Hunk, Hunks, Line, Modification};
|
||||||
|
|
||||||
|
|
@ -307,8 +306,8 @@ impl Encode for FileHeader {
|
||||||
if old.mode == new.mode {
|
if old.mode == new.mode {
|
||||||
w.meta(format!(
|
w.meta(format!(
|
||||||
"index {}..{} {:o}",
|
"index {}..{} {:o}",
|
||||||
term::format::oid(old.oid),
|
term::format::oid(*old.oid),
|
||||||
term::format::oid(new.oid),
|
term::format::oid(*new.oid),
|
||||||
u32::from(old.mode.clone()),
|
u32::from(old.mode.clone()),
|
||||||
))?;
|
))?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -316,8 +315,8 @@ impl Encode for FileHeader {
|
||||||
w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?;
|
w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?;
|
||||||
w.meta(format!(
|
w.meta(format!(
|
||||||
"index {}..{}",
|
"index {}..{}",
|
||||||
term::format::oid(old.oid),
|
term::format::oid(*old.oid),
|
||||||
term::format::oid(new.oid)
|
term::format::oid(*new.oid)
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,8 +333,8 @@ impl Encode for FileHeader {
|
||||||
w.meta(format!("new file mode {:o}", u32::from(new.mode.clone())))?;
|
w.meta(format!("new file mode {:o}", u32::from(new.mode.clone())))?;
|
||||||
w.meta(format!(
|
w.meta(format!(
|
||||||
"index {}..{}",
|
"index {}..{}",
|
||||||
term::format::oid(Oid::zero()),
|
term::format::oid(git::Oid::sha1_zero()),
|
||||||
term::format::oid(new.oid),
|
term::format::oid(*new.oid),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
w.meta("--- /dev/null")?;
|
w.meta("--- /dev/null")?;
|
||||||
|
|
@ -355,8 +354,8 @@ impl Encode for FileHeader {
|
||||||
))?;
|
))?;
|
||||||
w.meta(format!(
|
w.meta(format!(
|
||||||
"index {}..{}",
|
"index {}..{}",
|
||||||
term::format::oid(old.oid),
|
term::format::oid(*old.oid),
|
||||||
term::format::oid(Oid::zero())
|
term::format::oid(git::Oid::sha1_zero())
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
w.meta(format!("--- a/{}", path.display()))?;
|
w.meta(format!("--- a/{}", path.display()))?;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use radicle::git::RefStr;
|
use radicle::git::fmt::RefStr;
|
||||||
use radicle::node::NodeId;
|
use radicle::node::NodeId;
|
||||||
|
|
||||||
/// Setup a repository remote and tracking branch.
|
/// Setup a repository remote and tracking branch.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob::{self, issue, patch};
|
use radicle::cob::{self, issue, patch};
|
||||||
use radicle::crypto;
|
use radicle::crypto;
|
||||||
use radicle::git::{Oid, RefString};
|
use radicle::git::{fmt::RefString, Oid};
|
||||||
use radicle::node::{Address, Alias};
|
use radicle::node::{Address, Alias};
|
||||||
use radicle::prelude::{Did, NodeId, RepoId};
|
use radicle::prelude::{Did, NodeId, RepoId};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,8 +188,8 @@ fn message_from_commits(name: &str, commits: Vec<git::raw::Commit>) -> Result<St
|
||||||
/// Return commits between the merge base and a head.
|
/// Return commits between the merge base and a head.
|
||||||
pub fn patch_commits<'a>(
|
pub fn patch_commits<'a>(
|
||||||
repo: &'a git::raw::Repository,
|
repo: &'a git::raw::Repository,
|
||||||
base: &git::Oid,
|
base: &git::raw::Oid,
|
||||||
head: &git::Oid,
|
head: &git::raw::Oid,
|
||||||
) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> {
|
) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> {
|
||||||
let mut commits = Vec::new();
|
let mut commits = Vec::new();
|
||||||
let mut revwalk = repo.revwalk()?;
|
let mut revwalk = repo.revwalk()?;
|
||||||
|
|
@ -205,8 +205,8 @@ pub fn patch_commits<'a>(
|
||||||
/// The message shown in the editor when creating a `Patch`.
|
/// The message shown in the editor when creating a `Patch`.
|
||||||
fn create_display_message(
|
fn create_display_message(
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
base: &git::Oid,
|
base: &git::raw::Oid,
|
||||||
head: &git::Oid,
|
head: &git::raw::Oid,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let commits = patch_commits(repo, base, head)?;
|
let commits = patch_commits(repo, base, head)?;
|
||||||
if commits.is_empty() {
|
if commits.is_empty() {
|
||||||
|
|
@ -226,8 +226,8 @@ fn create_display_message(
|
||||||
pub fn get_create_message(
|
pub fn get_create_message(
|
||||||
message: term::patch::Message,
|
message: term::patch::Message,
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
base: &git::Oid,
|
base: &git::raw::Oid,
|
||||||
head: &git::Oid,
|
head: &git::raw::Oid,
|
||||||
) -> Result<(Title, String), Error> {
|
) -> Result<(Title, String), Error> {
|
||||||
let display_msg = create_display_message(repo, base, head)?;
|
let display_msg = create_display_message(repo, base, head)?;
|
||||||
let message = message.get(&display_msg)?;
|
let message = message.get(&display_msg)?;
|
||||||
|
|
@ -279,10 +279,10 @@ pub fn get_edit_message(
|
||||||
/// The message shown in the editor when updating a `Patch`.
|
/// The message shown in the editor when updating a `Patch`.
|
||||||
fn update_display_message(
|
fn update_display_message(
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
last_rev_head: &git::Oid,
|
last_rev_head: &git::raw::Oid,
|
||||||
head: &git::Oid,
|
head: &git::raw::Oid,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
if !repo.graph_descendant_of(**head, **last_rev_head)? {
|
if !repo.graph_descendant_of(*head, *last_rev_head)? {
|
||||||
return Ok(REVISION_MSG.trim_start().to_string());
|
return Ok(REVISION_MSG.trim_start().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,9 +302,9 @@ pub fn get_update_message(
|
||||||
message: term::patch::Message,
|
message: term::patch::Message,
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
latest: &patch::Revision,
|
latest: &patch::Revision,
|
||||||
head: &git::Oid,
|
head: &git::raw::Oid,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let display_msg = update_display_message(repo, &latest.head(), head)?;
|
let display_msg = update_display_message(repo, &latest.head().into(), head)?;
|
||||||
let message = message.get(&display_msg)?;
|
let message = message.get(&display_msg)?;
|
||||||
let message = message.trim();
|
let message = message.trim();
|
||||||
|
|
||||||
|
|
@ -366,11 +366,8 @@ pub fn show(
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
let ahead_behind = common::ahead_behind(
|
let ahead_behind =
|
||||||
stored.raw(),
|
common::ahead_behind(stored.raw(), revision.head(), patch.target().head(stored)?)?;
|
||||||
*revision.head(),
|
|
||||||
*patch.target().head(stored)?,
|
|
||||||
)?;
|
|
||||||
let author = patch.author();
|
let author = patch.author();
|
||||||
let author = term::format::Author::new(author.id(), profile, verbose);
|
let author = term::format::Author::new(author.id(), profile, verbose);
|
||||||
let labels = patch.labels().map(|l| l.to_string()).collect::<Vec<_>>();
|
let labels = patch.labels().map(|l| l.to_string()).collect::<Vec<_>>();
|
||||||
|
|
@ -468,7 +465,7 @@ fn patch_commit_lines(
|
||||||
let (from, to) = patch.range()?;
|
let (from, to) = patch.range()?;
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
for commit in patch_commits(stored.raw(), &from, &to)? {
|
for commit in patch_commits(stored.raw(), &from.into(), &to.into())? {
|
||||||
lines.push(term::Line::spaced([
|
lines.push(term::Line::spaced([
|
||||||
term::label(term::format::secondary::<String>(
|
term::label(term::format::secondary::<String>(
|
||||||
term::format::oid(commit.id()).into(),
|
term::format::oid(commit.id()).into(),
|
||||||
|
|
@ -484,37 +481,36 @@ fn patch_commit_lines(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use radicle::git::refname;
|
use radicle::git::fmt::refname;
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
fn commit(
|
fn commit(
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
branch: &git::RefStr,
|
branch: &git::fmt::RefStr,
|
||||||
parent: &git::Oid,
|
parent: &git::raw::Oid,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
) -> git::Oid {
|
) -> git::raw::Oid {
|
||||||
let sig = git::raw::Signature::new(
|
let sig = git::raw::Signature::new(
|
||||||
"anonymous",
|
"anonymous",
|
||||||
"anonymous@radicle.example.com",
|
"anonymous@radicle.example.com",
|
||||||
&git::raw::Time::new(0, 0),
|
&git::raw::Time::new(0, 0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let head = repo.find_commit(**parent).unwrap();
|
let head = repo.find_commit(*parent).unwrap();
|
||||||
let tree =
|
let tree =
|
||||||
git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap();
|
git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap();
|
||||||
|
|
||||||
let branch = git::refs::branch(branch);
|
let branch = git::refs::branch(branch);
|
||||||
let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap();
|
let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap();
|
||||||
|
|
||||||
commit.id().into()
|
commit.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_display_message() {
|
fn test_create_display_message() {
|
||||||
let tmpdir = tempfile::tempdir().unwrap();
|
let tmpdir = tempfile::tempdir().unwrap();
|
||||||
let (repo, commit_0) = fixtures::repository(&tmpdir);
|
let (repo, commit_0) = fixtures::repository(&tmpdir);
|
||||||
let commit_0 = commit_0.into();
|
|
||||||
let commit_1 = commit(
|
let commit_1 = commit(
|
||||||
&repo,
|
&repo,
|
||||||
&refname!("feature"),
|
&refname!("feature"),
|
||||||
|
|
@ -625,7 +621,6 @@ mod test {
|
||||||
fn test_update_display_message() {
|
fn test_update_display_message() {
|
||||||
let tmpdir = tempfile::tempdir().unwrap();
|
let tmpdir = tempfile::tempdir().unwrap();
|
||||||
let (repo, commit_0) = fixtures::repository(&tmpdir);
|
let (repo, commit_0) = fixtures::repository(&tmpdir);
|
||||||
let commit_0 = commit_0.into();
|
|
||||||
|
|
||||||
let commit_1 = commit(&repo, &refname!("feature"), &commit_0, "commit 1\n");
|
let commit_1 = commit(&repo, &refname!("feature"), &commit_0, "commit 1\n");
|
||||||
let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n");
|
let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::raw::Oid;
|
use radicle::git::Oid;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::terminal as term;
|
||||||
|
|
||||||
/// Give the oid of the branch or an appropriate error.
|
/// Give the oid of the branch or an appropriate error.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
|
pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<Oid> {
|
||||||
let oid = branch
|
let oid = branch
|
||||||
.get()
|
.get()
|
||||||
.target()
|
.target()
|
||||||
|
|
@ -18,7 +18,7 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_branch(git_ref: git::Qualified) -> git::RefString {
|
fn get_branch(git_ref: git::fmt::Qualified) -> git::fmt::RefString {
|
||||||
let (_, _, head, tail) = git_ref.non_empty_components();
|
let (_, _, head, tail) = git_ref.non_empty_components();
|
||||||
std::iter::once(head).chain(tail).collect()
|
std::iter::once(head).chain(tail).collect()
|
||||||
}
|
}
|
||||||
|
|
@ -28,16 +28,18 @@ fn get_branch(git_ref: git::Qualified) -> git::RefString {
|
||||||
pub fn get_merge_target(
|
pub fn get_merge_target(
|
||||||
storage: &Repository,
|
storage: &Repository,
|
||||||
head_branch: &git::raw::Branch,
|
head_branch: &git::raw::Branch,
|
||||||
) -> anyhow::Result<(git::RefString, git::Oid)> {
|
) -> anyhow::Result<(git::fmt::RefString, git::Oid)> {
|
||||||
let (qualified_ref, target_oid) = storage.canonical_head()?;
|
let (qualified_ref, target_oid) = storage.canonical_head()?;
|
||||||
let head_oid = branch_oid(head_branch)?;
|
let head_oid = branch_oid(head_branch)?;
|
||||||
let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?;
|
let merge_base = storage
|
||||||
|
.raw()
|
||||||
|
.merge_base(head_oid.into(), target_oid.into())?;
|
||||||
|
|
||||||
if head_oid == merge_base.into() {
|
if head_oid == merge_base {
|
||||||
anyhow::bail!("commits are already included in the target branch; nothing to do");
|
anyhow::bail!("commits are already included in the target branch; nothing to do");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((get_branch(qualified_ref), (*target_oid).into()))
|
Ok((get_branch(qualified_ref), (target_oid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the diff stats between two commits.
|
/// Get the diff stats between two commits.
|
||||||
|
|
@ -47,8 +49,8 @@ pub fn diff_stats(
|
||||||
old: &Oid,
|
old: &Oid,
|
||||||
new: &Oid,
|
new: &Oid,
|
||||||
) -> Result<git::raw::DiffStats, git::raw::Error> {
|
) -> Result<git::raw::DiffStats, git::raw::Error> {
|
||||||
let old = repo.find_commit(*old)?;
|
let old = repo.find_commit(old.into())?;
|
||||||
let new = repo.find_commit(*new)?;
|
let new = repo.find_commit(new.into())?;
|
||||||
let old_tree = old.tree()?;
|
let old_tree = old.tree()?;
|
||||||
let new_tree = new.tree()?;
|
let new_tree = new.tree()?;
|
||||||
let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
|
let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
|
||||||
|
|
@ -64,7 +66,7 @@ pub fn ahead_behind(
|
||||||
revision_oid: Oid,
|
revision_oid: Oid,
|
||||||
head_oid: Oid,
|
head_oid: Oid,
|
||||||
) -> anyhow::Result<term::Line> {
|
) -> anyhow::Result<term::Line> {
|
||||||
let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?;
|
let (a, b) = repo.graph_ahead_behind(revision_oid.into(), head_oid.into())?;
|
||||||
if a == 0 && b == 0 {
|
if a == 0 && b == 0 {
|
||||||
return Ok(term::Line::new(term::format::dim("up to date")));
|
return Ok(term::Line::new(term::format::dim("up to date")));
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +90,7 @@ pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
|
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
|
||||||
if oid == target {
|
if target == oid {
|
||||||
branches.push(name.to_string());
|
branches.push(name.to_string());
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use localtime::LocalTime;
|
||||||
use radicle::cob::cache::COBS_DB_FILE;
|
use radicle::cob::cache::COBS_DB_FILE;
|
||||||
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
|
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
|
||||||
use radicle::crypto::{KeyPair, Seed};
|
use radicle::crypto::{KeyPair, Seed};
|
||||||
|
use radicle::git;
|
||||||
use radicle::node::policy::store as policy;
|
use radicle::node::policy::store as policy;
|
||||||
use radicle::node::{self, UserAgent};
|
use radicle::node::{self, UserAgent};
|
||||||
use radicle::node::{Alias, Config, POLICIES_DB_FILE};
|
use radicle::node::{Alias, Config, POLICIES_DB_FILE};
|
||||||
|
|
@ -233,7 +234,7 @@ impl Environment {
|
||||||
pub fn repository(
|
pub fn repository(
|
||||||
&self,
|
&self,
|
||||||
has_alias: &impl HasAlias,
|
has_alias: &impl HasAlias,
|
||||||
) -> (radicle_cli::git::Repository, radicle_cli::git::Oid) {
|
) -> (radicle_cli::git::Repository, git::raw::Oid) {
|
||||||
radicle::test::fixtures::repository(self.work(has_alias).as_path())
|
radicle::test::fixtures::repository(self.work(has_alias).as_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,18 @@ rust-version.workspace = true
|
||||||
default = []
|
default = []
|
||||||
# Only used for testing. Ensures that commit ids are stable.
|
# Only used for testing. Ensures that commit ids are stable.
|
||||||
stable-commit-ids = []
|
stable-commit-ids = []
|
||||||
|
test = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fastrand = { workspace = true }
|
fastrand = { workspace = true }
|
||||||
git2 = { workspace = true, features = ["vendored-libgit2"] }
|
git-ref-format-core = { workspace = true }
|
||||||
|
git2 = { workspace = true, optional = true, features = ["vendored-libgit2"] }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
nonempty = { workspace = true, features = ["serialize"] }
|
nonempty = { workspace = true, features = ["serialize"] }
|
||||||
radicle-crypto = { workspace = true, features = ["ssh"] }
|
radicle-crypto = { workspace = true, features = ["ssh"] }
|
||||||
radicle-dag = { workspace = true }
|
radicle-dag = { workspace = true }
|
||||||
radicle-git-ext = { workspace = true, features = ["serde"] }
|
radicle-git-metadata = { workspace = true }
|
||||||
|
radicle-oid = { workspace = true, features = ["git2", "serde", "std"] }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
signature = { workspace = true }
|
signature = { workspace = true }
|
||||||
|
|
@ -36,4 +39,6 @@ fastrand = { workspace = true }
|
||||||
qcheck = { workspace = true }
|
qcheck = { workspace = true }
|
||||||
qcheck-macros = { workspace = true }
|
qcheck-macros = { workspace = true }
|
||||||
radicle-crypto = { workspace = true, features = ["test", "git-ref-format-core"] }
|
radicle-crypto = { workspace = true, features = ["test", "git-ref-format-core"] }
|
||||||
|
radicle-git-ref-format = { workspace = true, features = ["macro"] }
|
||||||
|
radicle-oid = { workspace = true, features = ["qcheck"] }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
|
||||||
|
#[cfg(feature = "stable-commit-ids")]
|
||||||
|
pub mod stable;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright © 2022 The Radicle Team
|
// Copyright © 2022 The Radicle Team
|
||||||
|
|
||||||
pub mod change;
|
mod commit;
|
||||||
|
|
||||||
#[cfg(feature = "stable-commit-ids")]
|
pub mod change;
|
||||||
pub mod stable;
|
|
||||||
|
|
||||||
/// Environment variable to set to overwrite the commit date for both the author and the committer.
|
/// Environment variable to set to overwrite the commit date for both the author and the committer.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ use std::convert::TryFrom;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use git_ext::author::Author;
|
use metadata::author::Author;
|
||||||
use git_ext::commit::{headers::Headers, Commit};
|
use metadata::commit::headers::Headers;
|
||||||
use git_ext::Oid;
|
use metadata::commit::trailers::OwnedTrailer;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use radicle_git_ext::commit::trailers::OwnedTrailer;
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::change::store::Version;
|
use crate::change::store::Version;
|
||||||
use crate::signatures;
|
use crate::signatures;
|
||||||
|
|
@ -21,6 +21,8 @@ use crate::{
|
||||||
trailers, Embed,
|
trailers, Embed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::commit::Commit;
|
||||||
|
|
||||||
/// Name of the COB manifest file.
|
/// Name of the COB manifest file.
|
||||||
pub const MANIFEST_BLOB_NAME: &str = "manifest";
|
pub const MANIFEST_BLOB_NAME: &str = "manifest";
|
||||||
/// Path under which COB embeds are kept.
|
/// Path under which COB embeds are kept.
|
||||||
|
|
@ -30,8 +32,7 @@ pub mod error {
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
use git_ext::commit;
|
use oid::Oid;
|
||||||
use git_ext::Oid;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::signatures::error::Signatures;
|
use crate::signatures::error::Signatures;
|
||||||
|
|
@ -39,7 +40,7 @@ pub mod error {
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Create {
|
pub enum Create {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
WriteCommit(#[from] commit::error::Write),
|
WriteCommit(#[from] super::super::commit::error::Write),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FromUtf8(#[from] FromUtf8Error),
|
FromUtf8(#[from] FromUtf8Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -55,7 +56,7 @@ pub mod error {
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Load {
|
pub enum Load {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Read(#[from] commit::error::Read),
|
Read(#[from] super::super::commit::error::Read),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Signatures(#[from] Signatures),
|
Signatures(#[from] Signatures),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -123,7 +124,7 @@ impl change::Storage for git2::Repository {
|
||||||
|
|
||||||
let (id, timestamp) = write_commit(
|
let (id, timestamp) = write_commit(
|
||||||
self,
|
self,
|
||||||
resource.map(|o| *o),
|
resource.map(|o| o.into()),
|
||||||
// Commit to tips, extra parents and resource.
|
// Commit to tips, extra parents and resource.
|
||||||
tips.iter()
|
tips.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -134,7 +135,7 @@ impl change::Storage for git2::Repository {
|
||||||
signature.clone(),
|
signature.clone(),
|
||||||
related
|
related
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| trailers::CommitTrailer::Related(**p).into()),
|
.map(|p| trailers::CommitTrailer::Related(*p).into()),
|
||||||
tree,
|
tree,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -153,40 +154,34 @@ impl change::Storage for git2::Repository {
|
||||||
|
|
||||||
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
|
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.find_commit(**id)?
|
.find_commit(id.into())?
|
||||||
.parent_ids()
|
.parent_ids()
|
||||||
.map(Oid::from)
|
.map(Oid::from)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manifest_of(&self, id: &Oid) -> Result<crate::Manifest, Self::LoadError> {
|
fn manifest_of(&self, id: &Oid) -> Result<crate::Manifest, Self::LoadError> {
|
||||||
let commit = self.find_commit(**id)?;
|
let commit = self.find_commit(id.into())?;
|
||||||
let tree = commit.tree()?;
|
let tree = commit.tree()?;
|
||||||
load_manifest(self, &tree)
|
load_manifest(self, &tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
|
fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
|
||||||
let commit = Commit::read(self, id.into())?;
|
let commit = super::commit::Commit::read(self, id.into())?;
|
||||||
let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
|
let timestamp = commit.committer().time.seconds() as u64;
|
||||||
let trailers = parse_trailers(commit.trailers())?;
|
let trailers = parse_trailers(commit.trailers())?;
|
||||||
let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t {
|
let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t {
|
||||||
CommitTrailer::Resource(_) => true,
|
CommitTrailer::Resource(_) => true,
|
||||||
CommitTrailer::Related(_) => false,
|
CommitTrailer::Related(_) => false,
|
||||||
});
|
});
|
||||||
let mut resources = resources
|
let mut resources = resources.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
|
||||||
.into_iter()
|
let related = related.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
|
||||||
.map(|r| r.oid().into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let related = related
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| r.oid().into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let parents = commit
|
let parents = commit
|
||||||
.parents()
|
.parents()
|
||||||
.map(Oid::from)
|
.map(Oid::from)
|
||||||
.filter(|p| !resources.contains(p) && !related.contains(p))
|
.filter(|p| !resources.contains(p) && !related.contains(p))
|
||||||
.collect();
|
.collect();
|
||||||
let mut signatures = Signatures::try_from(&commit)?
|
let mut signatures = Signatures::try_from(&*commit)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let Some((key, sig)) = signatures.pop() else {
|
let Some((key, sig)) = signatures.pop() else {
|
||||||
|
|
@ -285,7 +280,7 @@ fn write_commit(
|
||||||
) -> Result<(Oid, Timestamp), error::Create> {
|
) -> Result<(Oid, Timestamp), error::Create> {
|
||||||
let trailers: Vec<OwnedTrailer> = trailers
|
let trailers: Vec<OwnedTrailer> = trailers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(resource.map(|r| trailers::CommitTrailer::Resource(r).into()))
|
.chain(resource.map(|r| trailers::CommitTrailer::Resource(r.into()).into()))
|
||||||
.collect();
|
.collect();
|
||||||
let author = repo.signature()?;
|
let author = repo.signature()?;
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
|
|
@ -299,29 +294,37 @@ fn write_commit(
|
||||||
.map_err(signatures::error::Signatures::from)?
|
.map_err(signatures::error::Signatures::from)?
|
||||||
.as_str(),
|
.as_str(),
|
||||||
);
|
);
|
||||||
let author = Author::try_from(&author)?;
|
|
||||||
|
let author = Author {
|
||||||
|
name: String::from_utf8(author.name_bytes().to_vec())?,
|
||||||
|
email: String::from_utf8(author.email_bytes().to_vec())?,
|
||||||
|
time: {
|
||||||
|
let when = author.when();
|
||||||
|
metadata::author::Time::new(when.seconds(), when.offset_minutes())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "stable-commit-ids")]
|
#[cfg(feature = "stable-commit-ids")]
|
||||||
// Ensures the commit id doesn't change on every run.
|
// Ensures the commit id doesn't change on every run.
|
||||||
let (author, timestamp) = {
|
let (author, timestamp) = {
|
||||||
let stable = crate::git::stable::read_timestamp();
|
let stable = crate::stable::read_timestamp();
|
||||||
(
|
(
|
||||||
Author {
|
Author {
|
||||||
time: git_ext::author::Time::new(stable, 0),
|
time: metadata::author::Time::new(stable, 0),
|
||||||
..author
|
..author
|
||||||
},
|
},
|
||||||
stable,
|
stable,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let (author, timestamp) = if let Ok(s) = std::env::var(crate::git::GIT_COMMITTER_DATE) {
|
let (author, timestamp) = if let Ok(s) = std::env::var(super::GIT_COMMITTER_DATE) {
|
||||||
let Ok(timestamp) = s.trim().parse::<i64>() else {
|
let Ok(timestamp) = s.trim().parse::<i64>() else {
|
||||||
panic!(
|
panic!(
|
||||||
"Invalid timestamp value {s:?} for `{}`",
|
"Invalid timestamp value {s:?} for `{}`",
|
||||||
crate::git::GIT_COMMITTER_DATE
|
super::GIT_COMMITTER_DATE
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let author = Author {
|
let author = Author {
|
||||||
time: git_ext::author::Time::new(timestamp, 0),
|
time: metadata::author::Time::new(timestamp, 0),
|
||||||
..author
|
..author
|
||||||
};
|
};
|
||||||
(author, timestamp)
|
(author, timestamp)
|
||||||
|
|
@ -380,7 +383,7 @@ fn write_manifest(
|
||||||
let oid = embed.content;
|
let oid = embed.content;
|
||||||
let path = PathBuf::from(embed.name);
|
let path = PathBuf::from(embed.name);
|
||||||
|
|
||||||
embeds_tree.insert(path, *oid, git2::FileMode::Blob.into())?;
|
embeds_tree.insert(path, oid.into(), git2::FileMode::Blob.into())?;
|
||||||
}
|
}
|
||||||
let oid = embeds_tree.write()?;
|
let oid = embeds_tree.write()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
mod trailers;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::{self, FromStr};
|
||||||
|
|
||||||
|
use git2::{ObjectType, Oid};
|
||||||
|
|
||||||
|
use metadata::author::Author;
|
||||||
|
use metadata::commit::headers::Headers;
|
||||||
|
use metadata::commit::trailers::OwnedTrailer;
|
||||||
|
use metadata::commit::CommitData;
|
||||||
|
|
||||||
|
use trailers::Trailers;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(super) struct Commit(metadata::commit::CommitData<Oid, Oid>);
|
||||||
|
|
||||||
|
impl Commit {
|
||||||
|
pub fn new<P, I, T>(
|
||||||
|
tree: git2::Oid,
|
||||||
|
parents: P,
|
||||||
|
author: Author,
|
||||||
|
committer: Author,
|
||||||
|
headers: Headers,
|
||||||
|
message: String,
|
||||||
|
trailers: I,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
P: IntoIterator<Item = Oid>,
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
OwnedTrailer: From<T>,
|
||||||
|
{
|
||||||
|
Self(CommitData::new(
|
||||||
|
tree, parents, author, committer, headers, message, trailers,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Commit {
|
||||||
|
/// Read the [`Commit`] from the `repo` that is expected to be found at
|
||||||
|
/// `oid`.
|
||||||
|
pub fn read(repo: &git2::Repository, oid: Oid) -> Result<Self, error::Read> {
|
||||||
|
let odb = repo.odb()?;
|
||||||
|
let object = odb.read(oid)?;
|
||||||
|
Ok(Commit::try_from(object.data())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the given [`Commit`] to the `repo`. The resulting `Oid`
|
||||||
|
/// is the identifier for this commit.
|
||||||
|
pub fn write(&self, repo: &git2::Repository) -> Result<Oid, error::Write> {
|
||||||
|
let odb = repo.odb().map_err(error::Write::Odb)?;
|
||||||
|
self.verify_for_write(&odb)?;
|
||||||
|
Ok(odb.write(ObjectType::Commit, self.to_string().as_bytes())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_for_write(&self, odb: &git2::Odb) -> Result<(), error::Write> {
|
||||||
|
for parent in self.0.parents() {
|
||||||
|
verify_object(odb, &parent, ObjectType::Commit)?;
|
||||||
|
}
|
||||||
|
verify_object(odb, self.0.tree(), ObjectType::Tree)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_object(odb: &git2::Odb, oid: &Oid, expected: ObjectType) -> Result<(), error::Write> {
|
||||||
|
use git2::{Error, ErrorClass, ErrorCode};
|
||||||
|
|
||||||
|
let (_, kind) = odb
|
||||||
|
.read_header(*oid)
|
||||||
|
.map_err(|err| error::Write::OdbRead { oid: *oid, err })?;
|
||||||
|
if kind != expected {
|
||||||
|
Err(error::Write::NotCommit {
|
||||||
|
oid: *oid,
|
||||||
|
err: Error::new(
|
||||||
|
ErrorCode::NotFound,
|
||||||
|
ErrorClass::Object,
|
||||||
|
format!("Object '{oid}' is not expected object type {expected}"),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod error {
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Write {
|
||||||
|
#[error(transparent)]
|
||||||
|
Git(#[from] git2::Error),
|
||||||
|
#[error("the parent '{oid}' provided is not a commit object")]
|
||||||
|
NotCommit {
|
||||||
|
oid: git2::Oid,
|
||||||
|
#[source]
|
||||||
|
err: git2::Error,
|
||||||
|
},
|
||||||
|
#[error("failed to access git odb")]
|
||||||
|
Odb(#[source] git2::Error),
|
||||||
|
#[error("failed to read '{oid}' from git odb")]
|
||||||
|
OdbRead {
|
||||||
|
oid: git2::Oid,
|
||||||
|
#[source]
|
||||||
|
err: git2::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Read {
|
||||||
|
#[error(transparent)]
|
||||||
|
Git(#[from] git2::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Parse(#[from] Parse),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Parse {
|
||||||
|
#[error(transparent)]
|
||||||
|
Git(#[from] git2::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Header(#[from] metadata::commit::headers::ParseError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Utf8(#[from] str::Utf8Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Commit {
|
||||||
|
type Error = error::Parse;
|
||||||
|
|
||||||
|
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
Commit::from_str(str::from_utf8(data)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Commit {
|
||||||
|
type Err = error::Parse;
|
||||||
|
|
||||||
|
fn from_str(buffer: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (header, message) = buffer
|
||||||
|
.split_once("\n\n")
|
||||||
|
.ok_or(metadata::commit::headers::ParseError::InvalidFormat)?;
|
||||||
|
|
||||||
|
let (tree, parents, author, committer, headers) =
|
||||||
|
metadata::commit::headers::parse_commit_header(header)?;
|
||||||
|
|
||||||
|
let trailers = Trailers::parse(message)?;
|
||||||
|
|
||||||
|
let message = message
|
||||||
|
.strip_suffix(&trailers.to_string(": "))
|
||||||
|
.unwrap_or(message)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Self(CommitData::new(
|
||||||
|
tree,
|
||||||
|
parents,
|
||||||
|
author,
|
||||||
|
committer,
|
||||||
|
headers,
|
||||||
|
message,
|
||||||
|
trailers.iter(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Commit {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Commit {
|
||||||
|
type Target = CommitData<git2::Oid, git2::Oid>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
use std::{borrow::Cow, fmt, fmt::Write, str::FromStr};
|
||||||
|
|
||||||
|
use git2::{MessageTrailersStrs, MessageTrailersStrsIterator};
|
||||||
|
|
||||||
|
use metadata::commit::trailers::Separator;
|
||||||
|
|
||||||
|
/// A Git commit's set of trailers that are left in the commit's
|
||||||
|
/// message.
|
||||||
|
///
|
||||||
|
/// Trailers are key/value pairs in the last paragraph of a message,
|
||||||
|
/// not including any patches or conflicts that may be present.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// To construct `Trailers`, you can use [`Trailers::parse`] or its
|
||||||
|
/// `FromStr` implementation.
|
||||||
|
///
|
||||||
|
/// To iterate over the trailers, you can use [`Trailers::iter`].
|
||||||
|
///
|
||||||
|
/// To render the trailers to a `String`, you can use
|
||||||
|
/// [`Trailers::to_string`] or its `Display` implementation (note that
|
||||||
|
/// it will default to using `": "` as the separator.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// Add new functionality
|
||||||
|
///
|
||||||
|
/// Making code better with new functionality.
|
||||||
|
///
|
||||||
|
/// X-Signed-Off-By: Alex Sellier
|
||||||
|
/// X-Co-Authored-By: Fintan Halpenny
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The trailers in the above example are:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// X-Signed-Off-By: Alex Sellier
|
||||||
|
/// X-Co-Authored-By: Fintan Halpenny
|
||||||
|
/// ```
|
||||||
|
pub struct Trailers {
|
||||||
|
inner: MessageTrailersStrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trailers {
|
||||||
|
pub fn parse(message: &str) -> Result<Self, git2::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: git2::message_trailers_strs(message)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> Iter<'_> {
|
||||||
|
Iter {
|
||||||
|
inner: self.inner.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string<'a, S>(&self, sep: S) -> String
|
||||||
|
where
|
||||||
|
S: Separator<'a>,
|
||||||
|
{
|
||||||
|
let mut buf = String::new();
|
||||||
|
for (i, trailer) in self.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
writeln!(buf).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok();
|
||||||
|
}
|
||||||
|
writeln!(buf).ok();
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Trailers {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.to_string(": "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Trailers {
|
||||||
|
type Err = git2::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::parse(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Iter<'a> {
|
||||||
|
inner: MessageTrailersStrsIterator<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Iter<'a> {
|
||||||
|
type Item = metadata::commit::trailers::Trailer<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let (token, value) = self.inner.next()?;
|
||||||
|
Some(metadata::commit::trailers::Trailer {
|
||||||
|
token: {
|
||||||
|
// This code used to live in the same module with `Token`,
|
||||||
|
// but was separated because it depends on `git2`.
|
||||||
|
// We have no way of directly constructing a `Token`, anymore
|
||||||
|
// but `git2` still guarantees that the trailer is well-formed.
|
||||||
|
metadata::commit::trailers::Token::try_from(token)
|
||||||
|
.expect("token from `git2` must be valid")
|
||||||
|
},
|
||||||
|
value: Cow::Borrowed(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright © 2021 The Radicle Link Contributors
|
// Copyright © 2021 The Radicle Link Contributors
|
||||||
|
|
||||||
use git_ext::Oid;
|
use oid::Oid;
|
||||||
|
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub use store::{Contents, EntryId, Storage, Template, Timestamp};
|
pub use store::{Contents, EntryId, Storage, Template, Timestamp};
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
use std::{error::Error, fmt, num::NonZeroUsize};
|
use std::{error::Error, fmt, num::NonZeroUsize};
|
||||||
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use radicle_git_ext::Oid;
|
use oid::Oid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::object::collaboration::error::{Create, Update};
|
||||||
use crate::{signatures, TypeName};
|
use crate::{signatures, TypeName};
|
||||||
|
|
||||||
/// Change entry storage.
|
/// Change entry storage.
|
||||||
pub trait Storage {
|
pub trait Storage {
|
||||||
type StoreError: Error + Send + Sync + 'static;
|
type StoreError: Error + Send + Sync + 'static + Into<Create> + Into<Update>;
|
||||||
type LoadError: Error + Send + Sync + 'static;
|
type LoadError: Error + Send + Sync + 'static;
|
||||||
|
|
||||||
type ObjectId;
|
type ObjectId;
|
||||||
|
|
@ -194,6 +195,7 @@ pub struct Embed<T = Vec<u8>> {
|
||||||
pub content: T,
|
pub content: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
impl<T: From<Oid>> Embed<T> {
|
impl<T: From<Oid>> Embed<T> {
|
||||||
/// Create a new embed.
|
/// Create a new embed.
|
||||||
pub fn store(
|
pub fn store(
|
||||||
|
|
@ -210,6 +212,7 @@ impl<T: From<Oid>> Embed<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
impl Embed<Vec<u8>> {
|
impl Embed<Vec<u8>> {
|
||||||
/// Get the object id of the embedded content.
|
/// Get the object id of the embedded content.
|
||||||
pub fn oid(&self) -> Oid {
|
pub fn oid(&self) -> Oid {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use std::{cmp::Ordering, collections::BTreeSet};
|
use std::{cmp::Ordering, collections::BTreeSet};
|
||||||
|
|
||||||
use git_ext::Oid;
|
use dag::Dag;
|
||||||
use radicle_dag::Dag;
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
change, object, object::collaboration::Evaluate, signatures::ExtendedSignature,
|
change, object, object::collaboration::Evaluate, signatures::ExtendedSignature,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
|
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
|
||||||
|
|
||||||
use git_ext::Oid;
|
use dag::Dag;
|
||||||
use radicle_dag::Dag;
|
use oid::Oid;
|
||||||
|
|
||||||
pub use crate::change::{Contents, Entry, EntryId, Timestamp};
|
pub use crate::change::{Contents, Entry, EntryId, Timestamp};
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ impl History {
|
||||||
self.graph.node(id, change);
|
self.graph.node(id, change);
|
||||||
|
|
||||||
for tip in tips {
|
for tip in tips {
|
||||||
self.graph.dependency(id, (*tip).into());
|
self.graph.dependency(id, tip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,20 @@ extern crate qcheck;
|
||||||
#[macro_use(quickcheck)]
|
#[macro_use(quickcheck)]
|
||||||
extern crate qcheck_macros;
|
extern crate qcheck_macros;
|
||||||
|
|
||||||
|
extern crate git_ref_format_core as fmt;
|
||||||
extern crate radicle_crypto as crypto;
|
extern crate radicle_crypto as crypto;
|
||||||
extern crate radicle_git_ext as git_ext;
|
extern crate radicle_dag as dag;
|
||||||
|
extern crate radicle_git_metadata as metadata;
|
||||||
|
extern crate radicle_oid as oid;
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
|
|
||||||
|
#[cfg(all(any(test, feature = "test"), feature = "git2"))]
|
||||||
pub use backend::git;
|
pub use backend::git;
|
||||||
|
|
||||||
|
#[cfg(feature = "stable-commit-ids")]
|
||||||
|
pub use backend::stable;
|
||||||
|
|
||||||
mod change_graph;
|
mod change_graph;
|
||||||
mod trailers;
|
mod trailers;
|
||||||
|
|
||||||
|
|
@ -105,19 +113,9 @@ mod tests;
|
||||||
///
|
///
|
||||||
/// * [`object::Storage`]
|
/// * [`object::Storage`]
|
||||||
///
|
///
|
||||||
/// **Note**: [`change::Storage`] is already implemented for
|
|
||||||
/// [`git2::Repository`]. It is expected that the underlying storage
|
|
||||||
/// for `object::Storage` will also be `git2::Repository`, but if not
|
|
||||||
/// please open an issue to change the definition of `Store` :)
|
|
||||||
pub trait Store
|
pub trait Store
|
||||||
where
|
where
|
||||||
Self: object::Storage
|
Self: object::Storage
|
||||||
+ change::Storage<
|
+ change::Storage<ObjectId = oid::Oid, Parent = oid::Oid, Signatures = ExtendedSignature>,
|
||||||
StoreError = git::change::error::Create,
|
|
||||||
LoadError = git::change::error::Load,
|
|
||||||
ObjectId = git_ext::Oid,
|
|
||||||
Parent = git_ext::Oid,
|
|
||||||
Signatures = ExtendedSignature,
|
|
||||||
>,
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
use std::{convert::TryFrom as _, fmt, ops::Deref, str::FromStr};
|
use std::{convert::TryFrom as _, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use git_ext::ref_format::{Component, RefString};
|
use fmt::{Component, RefString};
|
||||||
use git_ext::Oid;
|
use oid::Oid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub use storage::{Commit, Objects, Reference, Storage};
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ParseObjectId {
|
pub enum ParseObjectId {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] oid::str::ParseOidError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The id of an object
|
/// The id of an object
|
||||||
|
|
@ -48,12 +48,14 @@ impl From<&Oid> for ObjectId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
impl From<git2::Oid> for ObjectId {
|
impl From<git2::Oid> for ObjectId {
|
||||||
fn from(oid: git2::Oid) -> Self {
|
fn from(oid: git2::Oid) -> Self {
|
||||||
Oid::from(oid).into()
|
Oid::from(oid).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
impl From<&git2::Oid> for ObjectId {
|
impl From<&git2::Oid> for ObjectId {
|
||||||
fn from(oid: &git2::Oid) -> Self {
|
fn from(oid: &git2::Oid) -> Self {
|
||||||
ObjectId(Oid::from(*oid))
|
ObjectId(Oid::from(*oid))
|
||||||
|
|
@ -68,8 +70,8 @@ impl Deref for ObjectId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ObjectId {
|
impl std::fmt::Display for ObjectId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use git_ext::Oid;
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::change::store::{Manifest, Version};
|
use crate::change::store::{Manifest, Version};
|
||||||
use crate::{change, Entry, History, ObjectId, TypeName};
|
use crate::{change, Entry, History, ObjectId, TypeName};
|
||||||
|
|
@ -101,11 +101,11 @@ impl<R> Evaluate<R> for NonEmpty<Entry> {
|
||||||
/// [`TypeName`] and [`ObjectId`] from it.
|
/// [`TypeName`] and [`ObjectId`] from it.
|
||||||
///
|
///
|
||||||
/// This assumes that the `refname` is in a
|
/// This assumes that the `refname` is in a
|
||||||
/// [`git_ext::ref_format::Qualified`] format. If it has any
|
/// [`fmt::Qualified`] format. If it has any
|
||||||
/// `refs/namespaces`, they will be stripped to access the underlying
|
/// `refs/namespaces`, they will be stripped to access the underlying
|
||||||
/// [`git_ext::ref_format::Qualified`] format.
|
/// [`fmt::Qualified`] format.
|
||||||
///
|
///
|
||||||
/// In the [`git_ext::ref_format::Qualified`] format it assumes that the
|
/// In the [`fmt::Qualified`] format it assumes that the
|
||||||
/// reference name is of the form:
|
/// reference name is of the form:
|
||||||
///
|
///
|
||||||
/// `refs/<category>/<typename>/<object_id>[/<rest>*]`
|
/// `refs/<category>/<typename>/<object_id>[/<rest>*]`
|
||||||
|
|
@ -115,14 +115,14 @@ impl<R> Evaluate<R> for NonEmpty<Entry> {
|
||||||
///
|
///
|
||||||
/// Also note that this will return `None` if:
|
/// Also note that this will return `None` if:
|
||||||
///
|
///
|
||||||
/// * The `refname` is not [`git_ext::ref_format::Qualified`]
|
/// * The `refname` is not [`fmt::Qualified`]
|
||||||
/// * The parsing of the [`ObjectId`] fails
|
/// * The parsing of the [`ObjectId`] fails
|
||||||
/// * The parsing of the [`TypeName`] fails
|
/// * The parsing of the [`TypeName`] fails
|
||||||
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
|
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
|
||||||
where
|
where
|
||||||
R: AsRef<git_ext::ref_format::RefStr>,
|
R: AsRef<fmt::RefStr>,
|
||||||
{
|
{
|
||||||
use git_ext::ref_format::Qualified;
|
use fmt::Qualified;
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let refs_cobs = match name.to_namespaced() {
|
let refs_cobs = match name.to_namespaced() {
|
||||||
None => Qualified::from_refstr(name)?,
|
None => Qualified::from_refstr(name)?,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use nonempty::NonEmpty;
|
||||||
|
|
||||||
use crate::Embed;
|
use crate::Embed;
|
||||||
use crate::Evaluate;
|
use crate::Evaluate;
|
||||||
use crate::Store;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -23,7 +22,7 @@ pub struct Create {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Create {
|
impl Create {
|
||||||
fn template(self) -> change::Template<git_ext::Oid> {
|
fn template(self) -> change::Template<oid::Oid> {
|
||||||
change::Template {
|
change::Template {
|
||||||
type_name: self.type_name,
|
type_name: self.type_name,
|
||||||
tips: Vec::new(),
|
tips: Vec::new(),
|
||||||
|
|
@ -38,7 +37,7 @@ impl Create {
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `signer` is expected to be a cryptographic signing key. This
|
/// The `signer` is expected to be a cryptographic signing key. This
|
||||||
/// ensures that the objects origin is cryptographically verifiable.
|
/// ensures that the objects origin is cryptographically verifiable.
|
||||||
|
|
@ -57,19 +56,24 @@ pub fn create<T, S, G>(
|
||||||
signer: &G,
|
signer: &G,
|
||||||
resource: Option<Oid>,
|
resource: Option<Oid>,
|
||||||
related: Vec<Oid>,
|
related: Vec<Oid>,
|
||||||
identifier: &S::Namespace,
|
identifier: &<S as crate::object::Storage>::Namespace,
|
||||||
args: Create,
|
args: Create,
|
||||||
) -> Result<CollaborativeObject<T>, error::Create>
|
) -> Result<CollaborativeObject<T>, error::Create>
|
||||||
where
|
where
|
||||||
T: Evaluate<S>,
|
T: Evaluate<S>,
|
||||||
S: Store,
|
S: crate::object::Storage
|
||||||
|
+ crate::change::Storage<
|
||||||
|
ObjectId = crate::object::Oid,
|
||||||
|
Parent = crate::object::Oid,
|
||||||
|
Signatures = crate::ExtendedSignature,
|
||||||
|
>,
|
||||||
G: signature::Signer<crate::ExtendedSignature>,
|
G: signature::Signer<crate::ExtendedSignature>,
|
||||||
{
|
{
|
||||||
let type_name = args.type_name.clone();
|
let type_name = args.type_name.clone();
|
||||||
let version = args.version;
|
let version = args.version;
|
||||||
let init_change = storage
|
let init_change = storage
|
||||||
.store(resource, related, signer, args.template())
|
.store(resource, related, signer, args.template())
|
||||||
.map_err(error::Create::from)?;
|
.map_err(Into::<error::Create>::into)?;
|
||||||
let object_id = init_change.id().into();
|
let object_id = init_change.id().into();
|
||||||
let object = T::init(&init_change, storage).map_err(error::Create::evaluate)?;
|
let object = T::init(&init_change, storage).map_err(error::Create::evaluate)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::git;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Create {
|
pub enum Create {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CreateChange(#[from] git::change::error::Create),
|
#[cfg(feature = "git2")]
|
||||||
|
CreateChange(#[from] crate::backend::git::change::error::Create),
|
||||||
#[error("failed to updated references for during object creation: {err}")]
|
#[error("failed to updated references for during object creation: {err}")]
|
||||||
Refs {
|
Refs {
|
||||||
#[source]
|
#[source]
|
||||||
|
|
@ -39,6 +38,7 @@ pub enum Retrieve {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] git2::Error),
|
||||||
#[error("failed to get references during object retrieval: {err}")]
|
#[error("failed to get references during object retrieval: {err}")]
|
||||||
Refs {
|
Refs {
|
||||||
|
|
@ -62,13 +62,15 @@ pub enum Update {
|
||||||
#[error("no object found")]
|
#[error("no object found")]
|
||||||
NoSuchObject,
|
NoSuchObject,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CreateChange(#[from] git::change::error::Create),
|
#[cfg(feature = "git2")]
|
||||||
|
CreateChange(#[from] crate::backend::git::change::error::Create),
|
||||||
#[error("failed to get references during object update: {err}")]
|
#[error("failed to get references during object update: {err}")]
|
||||||
Refs {
|
Refs {
|
||||||
#[source]
|
#[source]
|
||||||
err: Box<dyn std::error::Error + Send + Sync + 'static>,
|
err: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||||
},
|
},
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] git2::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, ObjectId, Store, TypeName};
|
use crypto::ssh::ExtendedSignature;
|
||||||
|
|
||||||
|
use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, ObjectId, TypeName};
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
|
|
||||||
|
|
@ -8,7 +10,7 @@ use super::error;
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `typename` is the type of object to be found, while the
|
/// The `typename` is the type of object to be found, while the
|
||||||
/// `object_id` is the identifier for the particular object under that
|
/// `object_id` is the identifier for the particular object under that
|
||||||
|
|
@ -20,7 +22,12 @@ pub fn get<T, S>(
|
||||||
) -> Result<Option<CollaborativeObject<T>>, error::Retrieve>
|
) -> Result<Option<CollaborativeObject<T>>, error::Retrieve>
|
||||||
where
|
where
|
||||||
T: Evaluate<S>,
|
T: Evaluate<S>,
|
||||||
S: Store,
|
S: crate::object::Storage,
|
||||||
|
S: crate::change::Storage<
|
||||||
|
ObjectId = crate::object::Oid,
|
||||||
|
Parent = crate::object::Oid,
|
||||||
|
Signatures = ExtendedSignature,
|
||||||
|
>,
|
||||||
{
|
{
|
||||||
let tip_refs = storage
|
let tip_refs = storage
|
||||||
.objects(typename, oid)
|
.objects(typename, oid)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use git_ext::Oid;
|
use crypto::ssh::ExtendedSignature;
|
||||||
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::{change_graph::ChangeGraph, ObjectId, Store, TypeName};
|
use crate::{change_graph::ChangeGraph, ObjectId, TypeName};
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ pub struct ChangeGraphInfo {
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `typename` is the type of object to be found, while the `oid`
|
/// The `typename` is the type of object to be found, while the `oid`
|
||||||
/// is the identifier for the particular object under that type.
|
/// is the identifier for the particular object under that type.
|
||||||
|
|
@ -38,7 +39,12 @@ pub fn changegraph<S>(
|
||||||
oid: &ObjectId,
|
oid: &ObjectId,
|
||||||
) -> Result<Option<ChangeGraphInfo>, error::Retrieve>
|
) -> Result<Option<ChangeGraphInfo>, error::Retrieve>
|
||||||
where
|
where
|
||||||
S: Store,
|
S: crate::object::Storage,
|
||||||
|
S: crate::change::Storage<
|
||||||
|
ObjectId = crate::object::Oid,
|
||||||
|
Parent = crate::object::Oid,
|
||||||
|
Signatures = ExtendedSignature,
|
||||||
|
>,
|
||||||
{
|
{
|
||||||
let tip_refs = storage
|
let tip_refs = storage
|
||||||
.objects(typename, oid)
|
.objects(typename, oid)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, Store, TypeName};
|
use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, TypeName};
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ use super::error;
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `typename` is the type of objects to be listed.
|
/// The `typename` is the type of objects to be listed.
|
||||||
pub fn list<T, S>(
|
pub fn list<T, S>(
|
||||||
|
|
@ -17,7 +17,12 @@ pub fn list<T, S>(
|
||||||
) -> Result<Vec<CollaborativeObject<T>>, error::Retrieve>
|
) -> Result<Vec<CollaborativeObject<T>>, error::Retrieve>
|
||||||
where
|
where
|
||||||
T: Evaluate<S>,
|
T: Evaluate<S>,
|
||||||
S: Store,
|
S: crate::object::Storage,
|
||||||
|
S: crate::change::Storage<
|
||||||
|
ObjectId = crate::object::Oid,
|
||||||
|
Parent = crate::object::Oid,
|
||||||
|
Signatures = crate::ExtendedSignature,
|
||||||
|
>,
|
||||||
{
|
{
|
||||||
let references = storage
|
let references = storage
|
||||||
.types(typename)
|
.types(typename)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
use crate::{ObjectId, Store, TypeName};
|
use crate::{ObjectId, TypeName};
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ use super::error;
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `typename` is the type of object to be found, while the
|
/// The `typename` is the type of object to be found, while the
|
||||||
/// `object_id` is the identifier for the particular object under that
|
/// `object_id` is the identifier for the particular object under that
|
||||||
|
|
@ -20,7 +20,7 @@ pub fn remove<S>(
|
||||||
oid: &ObjectId,
|
oid: &ObjectId,
|
||||||
) -> Result<(), error::Remove>
|
) -> Result<(), error::Remove>
|
||||||
where
|
where
|
||||||
S: Store,
|
S: crate::object::Storage,
|
||||||
{
|
{
|
||||||
storage
|
storage
|
||||||
.remove(identifier, typename, oid)
|
.remove(identifier, typename, oid)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use git_ext::Oid;
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
|
change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
|
||||||
ExtendedSignature, ObjectId, Store, TypeName,
|
ExtendedSignature, ObjectId, TypeName,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
|
|
@ -40,7 +40,7 @@ pub struct Update {
|
||||||
///
|
///
|
||||||
/// The `storage` is the backing storage for storing
|
/// The `storage` is the backing storage for storing
|
||||||
/// [`crate::Entry`]s at content-addressable locations. Please see
|
/// [`crate::Entry`]s at content-addressable locations. Please see
|
||||||
/// [`Store`] for further information.
|
/// [`crate::Store`] for further information.
|
||||||
///
|
///
|
||||||
/// The `signer` is expected to be a cryptographic signing key. This
|
/// The `signer` is expected to be a cryptographic signing key. This
|
||||||
/// ensures that the objects origin is cryptographically verifiable.
|
/// ensures that the objects origin is cryptographically verifiable.
|
||||||
|
|
@ -65,7 +65,8 @@ pub fn update<T, S, G>(
|
||||||
) -> Result<Updated<T>, error::Update>
|
) -> Result<Updated<T>, error::Update>
|
||||||
where
|
where
|
||||||
T: Evaluate<S>,
|
T: Evaluate<S>,
|
||||||
S: Store,
|
S: crate::object::Storage,
|
||||||
|
S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
|
||||||
G: signature::Signer<ExtendedSignature>,
|
G: signature::Signer<ExtendedSignature>,
|
||||||
{
|
{
|
||||||
let Update {
|
let Update {
|
||||||
|
|
@ -86,7 +87,8 @@ where
|
||||||
graph.evaluate(storage).map_err(error::Update::evaluate)?;
|
graph.evaluate(storage).map_err(error::Update::evaluate)?;
|
||||||
|
|
||||||
// Create a commit for this change, but don't update any references yet.
|
// Create a commit for this change, but don't update any references yet.
|
||||||
let entry = storage.store(
|
let entry = storage
|
||||||
|
.store(
|
||||||
resource,
|
resource,
|
||||||
related,
|
related,
|
||||||
signer,
|
signer,
|
||||||
|
|
@ -97,7 +99,8 @@ where
|
||||||
type_name: typename.clone(),
|
type_name: typename.clone(),
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
)?;
|
)
|
||||||
|
.map_err(Into::<error::Update>::into)?;
|
||||||
let head = entry.id;
|
let head = entry.id;
|
||||||
let parents = entry.parents.to_vec();
|
let parents = entry.parents.to_vec();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
use std::{collections::BTreeMap, error::Error};
|
use std::{collections::BTreeMap, error::Error};
|
||||||
|
|
||||||
use git_ext::ref_format::RefString;
|
use fmt::RefString;
|
||||||
use git_ext::Oid;
|
use oid::Oid;
|
||||||
|
|
||||||
use crate::change::EntryId;
|
use crate::change::EntryId;
|
||||||
use crate::{ObjectId, TypeName};
|
use crate::{ObjectId, TypeName};
|
||||||
|
|
@ -93,31 +93,32 @@ pub trait Storage {
|
||||||
pub mod convert {
|
pub mod convert {
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use git_ext::ref_format::RefString;
|
use fmt::RefString;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{Commit, Reference};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("the reference '{name}' does not point to a commit object")]
|
#[error("the reference '{name}' does not point to a commit object")]
|
||||||
NotCommit {
|
NotCommit {
|
||||||
name: RefString,
|
name: RefString,
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
#[source]
|
#[source]
|
||||||
err: git2::Error,
|
err: git2::Error,
|
||||||
},
|
},
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Ref(#[from] git_ext::ref_format::Error),
|
Ref(#[from] fmt::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Utf8(#[from] str::Utf8Error),
|
Utf8(#[from] str::Utf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<git2::Reference<'a>> for Reference {
|
#[cfg(feature = "git2")]
|
||||||
|
impl<'a> TryFrom<git2::Reference<'a>> for super::Reference {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(value: git2::Reference<'a>) -> Result<Self, Self::Error> {
|
fn try_from(value: git2::Reference<'a>) -> Result<Self, Self::Error> {
|
||||||
let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?;
|
let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?;
|
||||||
let target = Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit {
|
let target =
|
||||||
|
super::Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
err,
|
err,
|
||||||
})?);
|
})?);
|
||||||
|
|
@ -125,9 +126,10 @@ pub mod convert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<git2::Commit<'a>> for Commit {
|
#[cfg(feature = "git2")]
|
||||||
|
impl<'a> From<git2::Commit<'a>> for super::Commit {
|
||||||
fn from(commit: git2::Commit<'a>) -> Self {
|
fn from(commit: git2::Commit<'a>) -> Self {
|
||||||
Commit {
|
Self {
|
||||||
id: commit.id().into(),
|
id: commit.id().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crypto::{ssh, PublicKey};
|
use crypto::{ssh, PublicKey};
|
||||||
use git_ext::commit::{
|
use metadata::commit::{
|
||||||
headers::Signature::{Pgp, Ssh},
|
headers::Signature::{Pgp, Ssh},
|
||||||
Commit,
|
CommitData,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use ssh::ExtendedSignature;
|
pub use ssh::ExtendedSignature;
|
||||||
|
|
@ -55,10 +55,10 @@ impl From<Signatures> for BTreeMap<PublicKey, crypto::Signature> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Commit> for Signatures {
|
impl<Tree, Parent> TryFrom<&CommitData<Tree, Parent>> for Signatures {
|
||||||
type Error = error::Signatures;
|
type Error = error::Signatures;
|
||||||
|
|
||||||
fn try_from(value: &Commit) -> Result<Self, Self::Error> {
|
fn try_from(value: &CommitData<Tree, Parent>) -> Result<Self, Self::Error> {
|
||||||
value
|
value
|
||||||
.signatures()
|
.signatures()
|
||||||
.filter_map(|signature| {
|
.filter_map(|signature| {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
||||||
|
|
||||||
use radicle_crypto::ssh::ExtendedSignatureError;
|
use crypto::ssh::ExtendedSignatureError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub mod identity;
|
pub mod identity;
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub use identity::{Person, Project, RemoteProject};
|
pub use identity::{Person, Project, RemoteProject};
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub use storage::Storage;
|
pub use storage::Storage;
|
||||||
|
|
||||||
pub mod arbitrary;
|
pub mod arbitrary;
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,7 @@ impl Arbitrary for TypeName {
|
||||||
|
|
||||||
impl Arbitrary for ObjectId {
|
impl Arbitrary for ObjectId {
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||||
let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g));
|
Self::from(oid::Oid::arbitrary(g))
|
||||||
let bytes = iter::repeat_with(|| rng.u8(..))
|
|
||||||
.take(20)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub use project::{Project, RemoteProject};
|
pub use project::{Project, RemoteProject};
|
||||||
|
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub mod person;
|
pub mod person;
|
||||||
|
#[cfg(feature = "git2")]
|
||||||
pub use person::Person;
|
pub use person::Person;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use git_ext::Oid;
|
use oid::Oid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::test::storage::{self, Storage};
|
use crate::test::storage::{self, Storage};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use git_ext::Oid;
|
use oid::Oid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::test;
|
use crate::test;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use std::{collections::BTreeMap, convert::TryFrom as _};
|
use std::{collections::BTreeMap, convert::TryFrom as _};
|
||||||
|
|
||||||
use radicle_git_ext::ref_format::{refname, Component};
|
use radicle_git_ref_format::refname;
|
||||||
|
|
||||||
|
use fmt::Component;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -29,7 +31,7 @@ pub mod error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] git2::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Format(#[from] git_ext::ref_format::Error),
|
Format(#[from] fmt::Error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,16 +95,16 @@ impl change::Storage for Storage {
|
||||||
self.as_raw().load(id)
|
self.as_raw().load(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parents_of(&self, id: &git_ext::Oid) -> Result<Vec<git_ext::Oid>, Self::LoadError> {
|
fn parents_of(&self, id: &oid::Oid) -> Result<Vec<radicle_oid::Oid>, Self::LoadError> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.as_raw()
|
.as_raw()
|
||||||
.find_commit(**id)?
|
.find_commit(id.into())?
|
||||||
.parent_ids()
|
.parent_ids()
|
||||||
.map(git_ext::Oid::from)
|
.map(oid::Oid::from)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manifest_of(&self, id: &git_ext::Oid) -> Result<crate::Manifest, Self::LoadError> {
|
fn manifest_of(&self, id: &oid::Oid) -> Result<crate::Manifest, Self::LoadError> {
|
||||||
self.as_raw().manifest_of(id)
|
self.as_raw().manifest_of(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
use std::ops::ControlFlow;
|
use fmt::{Component, RefString};
|
||||||
|
|
||||||
use crypto::test::signer::MockSigner;
|
use radicle_git_ref_format::refname;
|
||||||
use crypto::{PublicKey, Signer};
|
|
||||||
use git_ext::ref_format::{refname, Component, RefString};
|
|
||||||
use nonempty::{nonempty, NonEmpty};
|
|
||||||
use qcheck::Arbitrary;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{object, test::arbitrary::Invalid, ObjectId, TypeName};
|
||||||
create, get, list, object, test::arbitrary::Invalid, update, Create, Entry, ObjectId, TypeName,
|
|
||||||
Update, Updated, Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::test;
|
#[cfg(feature = "git2")]
|
||||||
|
mod git {
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
#[test]
|
use crypto::test::signer::MockSigner;
|
||||||
fn roundtrip() {
|
use crypto::{PublicKey, Signer};
|
||||||
|
use nonempty::{nonempty, NonEmpty};
|
||||||
|
use qcheck::Arbitrary;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
create, get, list, update, Create, Entry, ObjectId, TypeName, Update, Updated, Version,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::test;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip() {
|
||||||
let storage = test::Storage::new();
|
let storage = test::Storage::new();
|
||||||
let signer = gen::<MockSigner>(1);
|
let signer = gen::<MockSigner>(1);
|
||||||
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
||||||
|
|
@ -45,10 +51,10 @@ fn roundtrip() {
|
||||||
.expect("BUG: cob was missing");
|
.expect("BUG: cob was missing");
|
||||||
|
|
||||||
assert_eq!(cob, expected);
|
assert_eq!(cob, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_cobs() {
|
fn list_cobs() {
|
||||||
let storage = test::Storage::new();
|
let storage = test::Storage::new();
|
||||||
let signer = gen::<MockSigner>(1);
|
let signer = gen::<MockSigner>(1);
|
||||||
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
||||||
|
|
@ -97,10 +103,10 @@ fn list_cobs() {
|
||||||
actual.sort_by(|x, y| x.id().cmp(y.id()));
|
actual.sort_by(|x, y| x.id().cmp(y.id()));
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_cob() {
|
fn update_cob() {
|
||||||
let storage = test::Storage::new();
|
let storage = test::Storage::new();
|
||||||
let signer = gen::<MockSigner>(1);
|
let signer = gen::<MockSigner>(1);
|
||||||
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
|
||||||
|
|
@ -152,10 +158,10 @@ fn update_cob() {
|
||||||
|
|
||||||
assert_ne!(object, not_expected);
|
assert_ne!(object, not_expected);
|
||||||
assert_eq!(object, expected, "{object:#?} {expected:#?}");
|
assert_eq!(object, expected, "{object:#?} {expected:#?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn traverse_cobs() {
|
fn traverse_cobs() {
|
||||||
let storage = test::Storage::new();
|
let storage = test::Storage::new();
|
||||||
let neil_signer = gen::<MockSigner>(2);
|
let neil_signer = gen::<MockSigner>(2);
|
||||||
let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap();
|
let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap();
|
||||||
|
|
@ -233,6 +239,36 @@ fn traverse_cobs() {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
|
assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_to(
|
||||||
|
repo: &git2::Repository,
|
||||||
|
from: &PublicKey,
|
||||||
|
to: &test::RemoteProject,
|
||||||
|
typename: &TypeName,
|
||||||
|
object: ObjectId,
|
||||||
|
) -> Result<(), git2::Error> {
|
||||||
|
let original = {
|
||||||
|
let name = format!("refs/rad/{from}/cobs/{typename}/{object}");
|
||||||
|
let r = repo.find_reference(&name)?;
|
||||||
|
r.target().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = format!(
|
||||||
|
"refs/rad/{}/cobs/{}/{}",
|
||||||
|
to.identifier().to_path(),
|
||||||
|
typename,
|
||||||
|
object
|
||||||
|
);
|
||||||
|
repo.reference(&name, original, false, "copying object reference")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen<T: Arbitrary>(size: usize) -> T {
|
||||||
|
let mut gen = qcheck::Gen::new(size);
|
||||||
|
|
||||||
|
T::arbitrary(&mut gen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
|
|
@ -297,32 +333,3 @@ fn invalid_parse_refstr(oid: Invalid<ObjectId>, typename: TypeName) {
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen<T: Arbitrary>(size: usize) -> T {
|
|
||||||
let mut gen = qcheck::Gen::new(size);
|
|
||||||
|
|
||||||
T::arbitrary(&mut gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_to(
|
|
||||||
repo: &git2::Repository,
|
|
||||||
from: &PublicKey,
|
|
||||||
to: &test::RemoteProject,
|
|
||||||
typename: &TypeName,
|
|
||||||
object: ObjectId,
|
|
||||||
) -> Result<(), git2::Error> {
|
|
||||||
let original = {
|
|
||||||
let name = format!("refs/rad/{from}/cobs/{typename}/{object}");
|
|
||||||
let r = repo.find_reference(&name)?;
|
|
||||||
r.target().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = format!(
|
|
||||||
"refs/rad/{}/cobs/{}/{}",
|
|
||||||
to.identifier().to_path(),
|
|
||||||
typename,
|
|
||||||
object
|
|
||||||
);
|
|
||||||
repo.reference(&name, original, false, "copying object reference")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
||||||
|
|
||||||
use git_ext::commit::trailers::{OwnedTrailer, Token, Trailer};
|
use metadata::commit::trailers::{OwnedTrailer, Token, Trailer};
|
||||||
use std::ops::Deref as _;
|
use std::ops::Deref as _;
|
||||||
|
|
||||||
pub mod error {
|
pub mod error {
|
||||||
|
|
@ -12,21 +12,22 @@ pub mod error {
|
||||||
WrongToken,
|
WrongToken,
|
||||||
#[error("no value for Rad-Resource")]
|
#[error("no value for Rad-Resource")]
|
||||||
NoValue,
|
NoValue,
|
||||||
#[error("invalid git OID")]
|
/// Invalid object ID.
|
||||||
InvalidOid,
|
#[error("invalid oid: {0}")]
|
||||||
|
InvalidOid(#[from] radicle_oid::str::ParseOidError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit trailer for COB commits.
|
/// Commit trailer for COB commits.
|
||||||
pub enum CommitTrailer {
|
pub enum CommitTrailer {
|
||||||
/// Points to the owning resource.
|
/// Points to the owning resource.
|
||||||
Resource(git2::Oid),
|
Resource(oid::Oid),
|
||||||
/// Points to a related change.
|
/// Points to a related change.
|
||||||
Related(git2::Oid),
|
Related(oid::Oid),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommitTrailer {
|
impl CommitTrailer {
|
||||||
pub fn oid(&self) -> git2::Oid {
|
pub fn oid(&self) -> oid::Oid {
|
||||||
match self {
|
match self {
|
||||||
Self::Resource(oid) => *oid,
|
Self::Resource(oid) => *oid,
|
||||||
Self::Related(oid) => *oid,
|
Self::Related(oid) => *oid,
|
||||||
|
|
@ -38,12 +39,11 @@ impl TryFrom<&Trailer<'_>> for CommitTrailer {
|
||||||
type Error = error::InvalidResourceTrailer;
|
type Error = error::InvalidResourceTrailer;
|
||||||
|
|
||||||
fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
|
fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
|
||||||
let ext_oid =
|
let oid = value.as_ref().parse::<oid::Oid>()?;
|
||||||
git_ext::Oid::try_from(value.as_ref()).map_err(|_| Self::Error::InvalidOid)?;
|
|
||||||
if token.deref() == "Rad-Resource" {
|
if token.deref() == "Rad-Resource" {
|
||||||
Ok(CommitTrailer::Resource(ext_oid.into()))
|
Ok(CommitTrailer::Resource(oid))
|
||||||
} else if token.deref() == "Rad-Related" {
|
} else if token.deref() == "Rad-Related" {
|
||||||
Ok(CommitTrailer::Related(ext_oid.into()))
|
Ok(CommitTrailer::Related(oid))
|
||||||
} else {
|
} else {
|
||||||
Err(Self::Error::WrongToken)
|
Err(Self::Error::WrongToken)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Link Contributors
|
||||||
|
|
||||||
use std::{fmt, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use git_ext::ref_format::{Component, RefString};
|
use fmt::{Component, RefString};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -24,8 +24,8 @@ impl TypeName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TypeName {
|
impl std::fmt::Display for TypeName {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.0.as_str())
|
f.write_str(self.0.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,6 @@ gix-transport = { version = "0.44.0", features = ["blocking-client"] }
|
||||||
log = { workspace = true, features = ["std"] }
|
log = { workspace = true, features = ["std"] }
|
||||||
nonempty = { workspace = true }
|
nonempty = { workspace = true }
|
||||||
radicle = { workspace = true }
|
radicle = { workspace = true }
|
||||||
radicle-git-ext = { workspace = true, features = ["bstr"] }
|
radicle-oid = { workspace = true, features = ["gix"] }
|
||||||
|
radicle-git-ref-format = { workspace = true, features = ["bstr"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
@ -3,21 +3,3 @@ pub(crate) mod packfile;
|
||||||
pub(crate) mod repository;
|
pub(crate) mod repository;
|
||||||
|
|
||||||
pub mod refs;
|
pub mod refs;
|
||||||
|
|
||||||
pub(crate) mod oid {
|
|
||||||
//! Helper functions for converting to/from [`radicle::git::Oid`] and
|
|
||||||
//! [`ObjectId`].
|
|
||||||
|
|
||||||
use gix_hash::ObjectId;
|
|
||||||
use radicle::git::Oid;
|
|
||||||
|
|
||||||
/// Convert from an [`ObjectId`] to an [`Oid`].
|
|
||||||
pub fn to_oid(oid: ObjectId) -> Oid {
|
|
||||||
Oid::try_from(oid.as_bytes()).expect("invalid gix Oid")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert from an [`Oid`] to an [`ObjectId`].
|
|
||||||
pub fn to_object_id(oid: Oid) -> ObjectId {
|
|
||||||
ObjectId::from(gix_hash::oid::from_bytes_unchecked(oid.as_ref()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use radicle::git::{Component, Oid, Qualified, RefString};
|
use radicle::git::fmt::{Component, Qualified, RefString};
|
||||||
|
use radicle::git::Oid;
|
||||||
use radicle::prelude::PublicKey;
|
use radicle::prelude::PublicKey;
|
||||||
|
|
||||||
use super::refs::{Applied, RefUpdate, Update};
|
use super::refs::{Applied, RefUpdate, Update};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use radicle::git::{Namespaced, Oid, Qualified};
|
use radicle::git::fmt::{Namespaced, Qualified};
|
||||||
|
use radicle::git::Oid;
|
||||||
use radicle::prelude::PublicKey;
|
use radicle::prelude::PublicKey;
|
||||||
|
|
||||||
pub use radicle::storage::RefUpdate;
|
pub use radicle::storage::RefUpdate;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ pub mod error;
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use radicle::git::raw::ErrorExt as _;
|
use radicle::git::raw::ErrorExt as _;
|
||||||
use radicle::git::{self, Namespaced, Oid, Qualified};
|
use radicle::git::{
|
||||||
|
self,
|
||||||
|
fmt::{Namespaced, Qualified},
|
||||||
|
Oid,
|
||||||
|
};
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
use super::refs::{Applied, Policy, RefUpdate, Update};
|
use super::refs::{Applied, Policy, RefUpdate, Update};
|
||||||
|
|
@ -48,7 +52,7 @@ pub fn contains(repo: &Repository, oid: Oid) -> Result<bool, error::Contains> {
|
||||||
/// - The object does not peel to a commit
|
/// - The object does not peel to a commit
|
||||||
/// - Attempting to find the object fails
|
/// - Attempting to find the object fails
|
||||||
fn find_and_peel(repo: &Repository, oid: Oid) -> Result<Oid, error::Ancestry> {
|
fn find_and_peel(repo: &Repository, oid: Oid) -> Result<Oid, error::Ancestry> {
|
||||||
match repo.backend.find_object(*oid, None) {
|
match repo.backend.find_object(oid.into(), None) {
|
||||||
Ok(object) => Ok(object
|
Ok(object) => Ok(object
|
||||||
.peel(git::raw::ObjectType::Commit)
|
.peel(git::raw::ObjectType::Commit)
|
||||||
.map_err(|err| error::Ancestry::Peel { oid, err })?
|
.map_err(|err| error::Ancestry::Peel { oid, err })?
|
||||||
|
|
@ -80,7 +84,7 @@ pub fn ahead_behind(
|
||||||
|
|
||||||
let (ahead, behind) = repo
|
let (ahead, behind) = repo
|
||||||
.backend
|
.backend
|
||||||
.graph_ahead_behind(*new_commit, *old_commit)
|
.graph_ahead_behind(new_commit.into(), old_commit.into())
|
||||||
.map_err(|err| error::Ancestry::Check {
|
.map_err(|err| error::Ancestry::Check {
|
||||||
old: old_commit,
|
old: old_commit,
|
||||||
new: new_commit,
|
new: new_commit,
|
||||||
|
|
@ -100,7 +104,7 @@ pub fn refname_to_id<'a, N>(repo: &Repository, refname: N) -> Result<Option<Oid>
|
||||||
where
|
where
|
||||||
N: Into<Qualified<'a>>,
|
N: Into<Qualified<'a>>,
|
||||||
{
|
{
|
||||||
use radicle::git::raw::ErrorCode::NotFound;
|
use git::raw::ErrorCode::NotFound;
|
||||||
|
|
||||||
let refname = refname.into();
|
let refname = refname.into();
|
||||||
match repo.backend.refname_to_id(refname.as_ref()) {
|
match repo.backend.refname_to_id(refname.as_ref()) {
|
||||||
|
|
@ -170,7 +174,7 @@ fn direct<'a>(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if prev == *target {
|
if target == prev {
|
||||||
// If the two objects are identical, their ancestry does not matter,
|
// If the two objects are identical, their ancestry does not matter,
|
||||||
// we can always skip the update.
|
// we can always skip the update.
|
||||||
return Ok(RefUpdate::Skipped {
|
return Ok(RefUpdate::Skipped {
|
||||||
|
|
@ -193,7 +197,7 @@ fn direct<'a>(
|
||||||
|
|
||||||
let target = repo
|
let target = repo
|
||||||
.backend
|
.backend
|
||||||
.find_object(*target, ANY_KIND)
|
.find_object(target.into(), ANY_KIND)
|
||||||
.map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?;
|
.map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?;
|
||||||
|
|
||||||
match (prev.kind(), target.kind()) {
|
match (prev.kind(), target.kind()) {
|
||||||
|
|
@ -276,7 +280,7 @@ fn prune<'a>(
|
||||||
name: Namespaced<'a>,
|
name: Namespaced<'a>,
|
||||||
prev: Either<Oid, Qualified<'a>>,
|
prev: Either<Oid, Qualified<'a>>,
|
||||||
) -> Result<Updated<'a>, error::Update> {
|
) -> Result<Updated<'a>, error::Update> {
|
||||||
use radicle::git::raw::ObjectType;
|
use git::raw::ObjectType;
|
||||||
|
|
||||||
match find(repo, &name)? {
|
match find(repo, &name)? {
|
||||||
Some(mut r) => {
|
Some(mut r) => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
use radicle::git::{ext, raw, Namespaced, Oid, Qualified};
|
use radicle::git::{
|
||||||
|
self,
|
||||||
|
fmt::{Namespaced, Qualified},
|
||||||
|
Oid,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("could not open Git ODB")]
|
#[error("could not open Git ODB")]
|
||||||
pub struct Contains(#[source] pub raw::Error);
|
pub struct Contains(#[source] pub git::raw::Error);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Ancestry {
|
pub enum Ancestry {
|
||||||
|
|
@ -14,19 +18,19 @@ pub enum Ancestry {
|
||||||
old: Oid,
|
old: Oid,
|
||||||
new: Oid,
|
new: Oid,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
#[error("failed to peel object to commit {oid}: {err}")]
|
#[error("failed to peel object to commit {oid}: {err}")]
|
||||||
Peel {
|
Peel {
|
||||||
oid: Oid,
|
oid: Oid,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
#[error("failed to find object {oid}: {err}")]
|
#[error("failed to find object {oid}: {err}")]
|
||||||
Object {
|
Object {
|
||||||
oid: Oid,
|
oid: Oid,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,15 +39,15 @@ pub enum Ancestry {
|
||||||
pub struct Resolve {
|
pub struct Resolve {
|
||||||
pub name: Qualified<'static>,
|
pub name: Qualified<'static>,
|
||||||
#[source]
|
#[source]
|
||||||
pub err: raw::Error,
|
pub err: git::raw::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("failed to scan for refs matching {pattern}")]
|
#[error("failed to scan for refs matching {pattern}")]
|
||||||
pub struct Scan {
|
pub struct Scan {
|
||||||
pub pattern: radicle::git::PatternString,
|
pub pattern: radicle::git::fmt::refspec::PatternString,
|
||||||
#[source]
|
#[source]
|
||||||
pub err: ext::Error,
|
pub err: git::raw::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -55,19 +59,19 @@ pub enum Update {
|
||||||
name: Namespaced<'static>,
|
name: Namespaced<'static>,
|
||||||
target: Oid,
|
target: Oid,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
#[error("failed to delete reference {name}")]
|
#[error("failed to delete reference {name}")]
|
||||||
Delete {
|
Delete {
|
||||||
name: Namespaced<'static>,
|
name: Namespaced<'static>,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
#[error("failed to find ref {name}")]
|
#[error("failed to find ref {name}")]
|
||||||
Find {
|
Find {
|
||||||
name: Namespaced<'static>,
|
name: Namespaced<'static>,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
#[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
|
#[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
|
||||||
NonFF {
|
NonFF {
|
||||||
|
|
@ -76,7 +80,7 @@ pub enum Update {
|
||||||
cur: Oid,
|
cur: Oid,
|
||||||
},
|
},
|
||||||
#[error("failed to peel ref to object")]
|
#[error("failed to peel ref to object")]
|
||||||
Peel(#[source] raw::Error),
|
Peel(#[source] git::raw::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Resolve(#[from] Resolve),
|
Resolve(#[from] Resolve),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
use bstr::{BString, ByteSlice};
|
use bstr::{BString, ByteSlice};
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git::{self, Component, Namespaced, Oid, Qualified};
|
use radicle::git::{
|
||||||
|
self,
|
||||||
|
fmt::{Component, Namespaced, Qualified},
|
||||||
|
Oid,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use radicle::git::refs::storage::Special;
|
pub use radicle::git::refs::storage::Special;
|
||||||
|
|
@ -26,7 +30,6 @@ pub enum Error {
|
||||||
pub(crate) fn unpack_ref<'a>(
|
pub(crate) fn unpack_ref<'a>(
|
||||||
r: gix_protocol::handshake::Ref,
|
r: gix_protocol::handshake::Ref,
|
||||||
) -> Result<(ReceivedRefname<'a>, Oid), Error> {
|
) -> Result<(ReceivedRefname<'a>, Oid), Error> {
|
||||||
use crate::git::oid;
|
|
||||||
use gix_protocol::handshake::Ref;
|
use gix_protocol::handshake::Ref;
|
||||||
|
|
||||||
match r {
|
match r {
|
||||||
|
|
@ -43,7 +46,7 @@ pub(crate) fn unpack_ref<'a>(
|
||||||
full_ref_name,
|
full_ref_name,
|
||||||
object,
|
object,
|
||||||
..
|
..
|
||||||
} => ReceivedRefname::try_from(full_ref_name).map(|name| (name, oid::to_oid(object))),
|
} => ReceivedRefname::try_from(full_ref_name).map(|name| (name, object.into())),
|
||||||
Ref::Unborn { full_ref_name, .. } => {
|
Ref::Unborn { full_ref_name, .. } => {
|
||||||
unreachable!("BUG: unborn ref {}", full_ref_name)
|
unreachable!("BUG: unborn ref {}", full_ref_name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ use either::Either;
|
||||||
use gix_protocol::handshake::Ref;
|
use gix_protocol::handshake::Ref;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git::{refname, Component, Namespaced, Qualified};
|
use radicle::git::fmt::{refname, Component, Namespaced, Qualified};
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
use radicle::storage::refs::{RefsAt, Special};
|
use radicle::storage::refs::{RefsAt, Special};
|
||||||
use radicle::storage::ReadRepository;
|
use radicle::storage::ReadRepository;
|
||||||
|
|
@ -52,7 +52,7 @@ use crate::{policy, refs};
|
||||||
|
|
||||||
pub mod error {
|
pub mod error {
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git::RefString;
|
use radicle::git::fmt::RefString;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::transport::WantsHavesError;
|
use crate::transport::WantsHavesError;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::time::Instant;
|
||||||
|
|
||||||
use gix_protocol::handshake;
|
use gix_protocol::handshake;
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git::{Oid, Qualified};
|
use radicle::git::{fmt::Qualified, Oid};
|
||||||
use radicle::identity::{Did, Doc, DocError};
|
use radicle::identity::{Did, Doc, DocError};
|
||||||
|
|
||||||
use radicle::prelude::Verified;
|
use radicle::prelude::Verified;
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,11 @@ use gix_protocol::handshake;
|
||||||
use gix_transport::client;
|
use gix_transport::client;
|
||||||
use gix_transport::Protocol;
|
use gix_transport::Protocol;
|
||||||
use gix_transport::Service;
|
use gix_transport::Service;
|
||||||
|
use radicle::git::fmt::Qualified;
|
||||||
use radicle::git::Oid;
|
use radicle::git::Oid;
|
||||||
use radicle::git::Qualified;
|
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::git::oid;
|
|
||||||
use crate::git::packfile::Keepfile;
|
use crate::git::packfile::Keepfile;
|
||||||
use crate::git::repository;
|
use crate::git::repository;
|
||||||
|
|
||||||
|
|
@ -163,7 +162,7 @@ where
|
||||||
|
|
||||||
let idx = File::at(pack_path, gix_hash::Kind::Sha1)?;
|
let idx = File::at(pack_path, gix_hash::Kind::Sha1)?;
|
||||||
for oid in wants_haves.wants {
|
for oid in wants_haves.wants {
|
||||||
if idx.lookup(oid::to_object_id(oid)).is_none() {
|
if idx.lookup(oid).is_none() {
|
||||||
return Err(Error::NotFound(oid));
|
return Err(Error::NotFound(oid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use gix_protocol::fetch::negotiate::one_round::State;
|
||||||
use gix_protocol::handshake;
|
use gix_protocol::handshake;
|
||||||
use gix_protocol::handshake::Ref;
|
use gix_protocol::handshake::Ref;
|
||||||
|
|
||||||
use crate::git::{oid, packfile};
|
use crate::git::packfile;
|
||||||
|
|
||||||
use super::{agent_name, Connection, WantsHaves};
|
use super::{agent_name, Connection, WantsHaves};
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ impl fetch::Negotiate for Negotiate {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut has_want = false;
|
let mut has_want = false;
|
||||||
for oid in &self.wants_haves.wants {
|
for oid in &self.wants_haves.wants {
|
||||||
arguments.want(oid::to_object_id(*oid));
|
arguments.want(oid);
|
||||||
has_want = true;
|
has_want = true;
|
||||||
}
|
}
|
||||||
has_want
|
has_want
|
||||||
|
|
@ -126,7 +126,7 @@ impl fetch::Negotiate for Negotiate {
|
||||||
_previous_response: Option<&fetch::Response>,
|
_previous_response: Option<&fetch::Response>,
|
||||||
) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> {
|
) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> {
|
||||||
for oid in &self.wants_haves.haves {
|
for oid in &self.wants_haves.haves {
|
||||||
arguments.have(oid::to_object_id(*oid));
|
arguments.have(oid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let round = fetch::negotiate::Round {
|
let round = fetch::negotiate::Round {
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,6 @@ nonempty = { workspace = true, features = ["serialize"] }
|
||||||
qcheck = { workspace = true, optional = true }
|
qcheck = { workspace = true, optional = true }
|
||||||
radicle = { workspace = true, features = ["logger"] }
|
radicle = { workspace = true, features = ["logger"] }
|
||||||
radicle-fetch = { workspace = true }
|
radicle-fetch = { workspace = true }
|
||||||
# N.b. this is required to use macros, even though it's re-exported
|
|
||||||
# through radicle
|
|
||||||
radicle-git-ext = { workspace = true, features = ["serde"] }
|
|
||||||
radicle-protocol = { workspace = true }
|
radicle-protocol = { workspace = true }
|
||||||
radicle-signals = { workspace = true }
|
radicle-signals = { workspace = true }
|
||||||
sqlite = { workspace = true, features = ["bundled"] }
|
sqlite = { workspace = true, features = ["bundled"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git;
|
use radicle::git::Oid;
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::RefsAt;
|
||||||
|
|
||||||
use crate::identity::RepoId;
|
use crate::identity::RepoId;
|
||||||
|
|
@ -108,7 +108,7 @@ impl radicle::node::Handle for Handle {
|
||||||
|
|
||||||
Ok(RefsAt {
|
Ok(RefsAt {
|
||||||
remote: self.nid()?,
|
remote: self.nid()?,
|
||||||
at: git::raw::Oid::zero().into(),
|
at: Oid::sha1_zero(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use radicle::crypto::ssh::keystore::MemorySigner;
|
||||||
use radicle::crypto::test::signer::MockSigner;
|
use radicle::crypto::test::signer::MockSigner;
|
||||||
use radicle::crypto::Signature;
|
use radicle::crypto::Signature;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::refname;
|
use radicle::git::fmt::refname;
|
||||||
use radicle::identity::{RepoId, Visibility};
|
use radicle::identity::{RepoId, Visibility};
|
||||||
use radicle::node::config::ConnectAddress;
|
use radicle::node::config::ConnectAddress;
|
||||||
use radicle::node::policy::store as policy;
|
use radicle::node::policy::store as policy;
|
||||||
|
|
@ -363,7 +363,7 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
|
||||||
/// of the new commit, and the reference will be updated.
|
/// of the new commit, and the reference will be updated.
|
||||||
///
|
///
|
||||||
/// The `rad/sigrefs` are then updated to reflect the new change.
|
/// The `rad/sigrefs` are then updated to reflect the new change.
|
||||||
pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::RefStr>) {
|
pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::fmt::RefStr>) {
|
||||||
use radicle::test::arbitrary;
|
use radicle::test::arbitrary;
|
||||||
|
|
||||||
let refname = refname.as_ref();
|
let refname = refname.as_ref();
|
||||||
|
|
@ -516,14 +516,14 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer<Signature> + Clone> Node<G> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push local branches to storage.
|
// Push local branches to storage.
|
||||||
let mut refs = Vec::<(git::Qualified, git::Qualified)>::new();
|
let mut refs = Vec::<(git::fmt::Qualified, git::fmt::Qualified)>::new();
|
||||||
for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
|
for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
|
||||||
let (branch, _) = branch.unwrap();
|
let (branch, _) = branch.unwrap();
|
||||||
let name = git::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();
|
let name = git::fmt::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();
|
||||||
|
|
||||||
refs.push((
|
refs.push((
|
||||||
git::lit::refs_heads(&name).into(),
|
git::fmt::lit::refs_heads(&name).into(),
|
||||||
git::lit::refs_heads(&name).into(),
|
git::fmt::lit::refs_heads(&name).into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
|
git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
|
||||||
&repo,
|
&repo,
|
||||||
name.try_into().unwrap(),
|
name.try_into().unwrap(),
|
||||||
description,
|
description,
|
||||||
radicle::git::refname!("master"),
|
radicle::git::fmt::refname!("master"),
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
self.signer(),
|
self.signer(),
|
||||||
self.storage(),
|
self.storage(),
|
||||||
|
|
|
||||||
|
|
@ -1567,7 +1567,7 @@ fn test_queued_fetch_from_ann_same_rid() {
|
||||||
let refname = carol
|
let refname = carol
|
||||||
.id()
|
.id()
|
||||||
.to_namespace()
|
.to_namespace()
|
||||||
.join(git::refname!("refs/sigrefs"));
|
.join(git::fmt::refname!("refs/sigrefs"));
|
||||||
|
|
||||||
// Finish the 1st fetch.
|
// Finish the 1st fetch.
|
||||||
// Ensure the ref is in the storage and cache.
|
// Ensure the ref is in the storage and cache.
|
||||||
|
|
@ -1749,7 +1749,7 @@ fn test_init_and_seed() {
|
||||||
&repo,
|
&repo,
|
||||||
"alice".try_into().unwrap(),
|
"alice".try_into().unwrap(),
|
||||||
"alice's repo",
|
"alice's repo",
|
||||||
git::refname!("master"),
|
git::fmt::refname!("master"),
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
alice.signer(),
|
alice.signer(),
|
||||||
alice.storage(),
|
alice.storage(),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::{collections::HashSet, thread, time};
|
||||||
use radicle::cob::Title;
|
use radicle::cob::Title;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
|
||||||
|
use radicle::git::raw::ErrorExt as _;
|
||||||
use radicle::node::device::Device;
|
use radicle::node::device::Device;
|
||||||
use radicle::node::policy::Scope;
|
use radicle::node::policy::Scope;
|
||||||
use radicle::node::Event;
|
use radicle::node::Event;
|
||||||
|
|
@ -248,7 +249,7 @@ fn test_replication_ref_in_sigrefs() {
|
||||||
bob.storage
|
bob.storage
|
||||||
.repository_mut(acme)
|
.repository_mut(acme)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.reference(&bob.id, &git::qualified!("refs/heads/master"))
|
.reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.delete()
|
.delete()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -271,7 +272,7 @@ fn test_replication_ref_in_sigrefs() {
|
||||||
.storage
|
.storage
|
||||||
.repository(acme)
|
.repository(acme)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.reference(&bob.id, &git::qualified!("refs/heads/master"))
|
.reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
|
||||||
.is_ok(),
|
.is_ok(),
|
||||||
"refs/namespaces/{}/refs/heads/master does not exist",
|
"refs/namespaces/{}/refs/heads/master does not exist",
|
||||||
bob.id
|
bob.id
|
||||||
|
|
@ -292,8 +293,8 @@ fn test_replication_invalid() {
|
||||||
// Create some unsigned refs for Carol in Bob's storage.
|
// Create some unsigned refs for Carol in Bob's storage.
|
||||||
repo.raw()
|
repo.raw()
|
||||||
.reference(
|
.reference(
|
||||||
&git::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
|
&git::fmt::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
|
||||||
*head,
|
head.into(),
|
||||||
true,
|
true,
|
||||||
&String::default(),
|
&String::default(),
|
||||||
)
|
)
|
||||||
|
|
@ -579,7 +580,7 @@ fn test_clone() {
|
||||||
.canonical_head()
|
.canonical_head()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(oid, *canonical);
|
assert_eq!(canonical, oid);
|
||||||
|
|
||||||
// Make sure that bob has refs/rad/id set
|
// Make sure that bob has refs/rad/id set
|
||||||
assert!(bob
|
assert!(bob
|
||||||
|
|
@ -1193,7 +1194,6 @@ fn missing_default_branch() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_delegate_default_branch() {
|
fn missing_delegate_default_branch() {
|
||||||
use radicle::git::raw;
|
|
||||||
use radicle::identity::Identity;
|
use radicle::identity::Identity;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -1238,7 +1238,7 @@ fn missing_delegate_default_branch() {
|
||||||
);
|
);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
default_branch,
|
default_branch,
|
||||||
Err(radicle::git::Error::Git(e)) if e.code() == raw::ErrorCode::NotFound
|
Err(e) if e.is_not_found()
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1528,10 +1528,10 @@ fn test_fetch_emits_canonical_ref_update() {
|
||||||
let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
let default_branch: git::Qualified = {
|
let default_branch: git::fmt::Qualified = {
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
let proj = repo.project().unwrap();
|
let proj = repo.project().unwrap();
|
||||||
git::lit::refs_heads(proj.default_branch()).into()
|
git::fmt::lit::refs_heads(proj.default_branch()).into()
|
||||||
};
|
};
|
||||||
alice.commit_to(rid, &default_branch);
|
alice.commit_to(rid, &default_branch);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ fn notify(
|
||||||
// for sigref verification.
|
// for sigref verification.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(rest) = r.strip_prefix(git::refname!("refs/heads/patches")) {
|
if let Some(rest) = r.strip_prefix(git::fmt::refname!("refs/heads/patches")) {
|
||||||
if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() {
|
if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() {
|
||||||
// Don't notify about patch branches, since we already get
|
// Don't notify about patch branches, since we already get
|
||||||
// notifications about patch updates.
|
// notifications about patch updates.
|
||||||
|
|
@ -400,7 +400,7 @@ fn set_canonical_refs(
|
||||||
let oid = object.id();
|
let oid = object.id();
|
||||||
if let Err(e) = repo.backend.reference(
|
if let Err(e) = repo.backend.reference(
|
||||||
refname.clone().as_str(),
|
refname.clone().as_str(),
|
||||||
*oid,
|
oid.into(),
|
||||||
true,
|
true,
|
||||||
"set-canonical-reference from fetch (radicle)",
|
"set-canonical-reference from fetch (radicle)",
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ nonempty = { workspace = true, features = ["serialize"] }
|
||||||
qcheck = { workspace = true, optional = true }
|
qcheck = { workspace = true, optional = true }
|
||||||
radicle = { workspace = true, features = ["logger"] }
|
radicle = { workspace = true, features = ["logger"] }
|
||||||
radicle-fetch = { workspace = true }
|
radicle-fetch = { workspace = true }
|
||||||
radicle-git-ext = { workspace = true, features = ["serde"] }
|
|
||||||
sqlite = { workspace = true, features = ["bundled"] }
|
sqlite = { workspace = true, features = ["bundled"] }
|
||||||
scrypt = { version = "0.11.0", default-features = false }
|
scrypt = { version = "0.11.0", default-features = false }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -186,8 +186,6 @@ pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Git(#[from] radicle::git::raw::Error),
|
Git(#[from] radicle::git::raw::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
GitExt(#[from] radicle::git::ext::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Storage(#[from] storage::Error),
|
Storage(#[from] storage::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Gossip(#[from] gossip::Error),
|
Gossip(#[from] gossip::Error),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use cyphernet::addr::tor;
|
||||||
use radicle::crypto::{PublicKey, Signature, Unverified};
|
use radicle::crypto::{PublicKey, Signature, Unverified};
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::fmt;
|
use radicle::git::fmt;
|
||||||
|
use radicle::git::raw;
|
||||||
use radicle::identity::RepoId;
|
use radicle::identity::RepoId;
|
||||||
use radicle::node;
|
use radicle::node;
|
||||||
use radicle::node::Alias;
|
use radicle::node::Alias;
|
||||||
|
|
@ -285,7 +286,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for git::RefString {
|
impl Encode for git::fmt::RefString {
|
||||||
fn encode(&self, buf: &mut impl BufMut) {
|
fn encode(&self, buf: &mut impl BufMut) {
|
||||||
self.as_str().encode(buf)
|
self.as_str().encode(buf)
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +301,8 @@ impl Encode for Signature {
|
||||||
impl Encode for git::Oid {
|
impl Encode for git::Oid {
|
||||||
fn encode(&self, buf: &mut impl BufMut) {
|
fn encode(&self, buf: &mut impl BufMut) {
|
||||||
// Nb. We use length-encoding here to support future SHA-2 object ids.
|
// Nb. We use length-encoding here to support future SHA-2 object ids.
|
||||||
self.as_bytes().encode(buf)
|
let bytes: &[u8] = self.as_ref();
|
||||||
|
bytes.encode(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,7 +323,7 @@ impl Decode for Refs {
|
||||||
|
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
let name = String::decode(buf)?;
|
let name = String::decode(buf)?;
|
||||||
let name = git::RefString::try_from(name).map_err(Invalid::from)?;
|
let name = git::fmt::RefString::try_from(name).map_err(Invalid::from)?;
|
||||||
let oid = git::Oid::decode(buf)?;
|
let oid = git::Oid::decode(buf)?;
|
||||||
|
|
||||||
refs.insert(name, oid);
|
refs.insert(name, oid);
|
||||||
|
|
@ -330,10 +332,10 @@ impl Decode for Refs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decode for git::RefString {
|
impl Decode for git::fmt::RefString {
|
||||||
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
||||||
let ref_str = String::decode(buf)?;
|
let ref_str = String::decode(buf)?;
|
||||||
Ok(git::RefString::try_from(ref_str).map_err(Invalid::from)?)
|
Ok(git::fmt::RefString::try_from(ref_str).map_err(Invalid::from)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -365,7 +367,7 @@ where
|
||||||
|
|
||||||
impl Decode for git::Oid {
|
impl Decode for git::Oid {
|
||||||
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
||||||
const LEN_EXPECTED: usize = mem::size_of::<git::raw::Oid>();
|
const LEN_EXPECTED: usize = mem::size_of::<raw::Oid>();
|
||||||
|
|
||||||
let len = Size::decode(buf)? as usize;
|
let len = Size::decode(buf)? as usize;
|
||||||
|
|
||||||
|
|
@ -378,7 +380,7 @@ impl Decode for git::Oid {
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf: [u8; LEN_EXPECTED] = Decode::decode(buf)?;
|
let buf: [u8; LEN_EXPECTED] = Decode::decode(buf)?;
|
||||||
let oid = git::raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size");
|
let oid = raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size");
|
||||||
let oid = git::Oid::from(oid);
|
let oid = git::Oid::from(oid);
|
||||||
|
|
||||||
Ok(oid)
|
Ok(oid)
|
||||||
|
|
@ -627,7 +629,7 @@ mod tests {
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn prop_oid(input: [u8; 20]) {
|
fn prop_oid(input: [u8; 20]) {
|
||||||
roundtrip(git::Oid::try_from(input.as_slice()).unwrap());
|
roundtrip(git::Oid::from_sha1(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@ impl FetchResult {
|
||||||
/// corresponding targets.
|
/// corresponding targets.
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct UpdatedCanonicalRefs {
|
pub struct UpdatedCanonicalRefs {
|
||||||
inner: BTreeMap<git::Qualified<'static>, git::Oid>,
|
inner: BTreeMap<git::fmt::Qualified<'static>, git::Oid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for UpdatedCanonicalRefs {
|
impl IntoIterator for UpdatedCanonicalRefs {
|
||||||
type Item = (git::Qualified<'static>, git::Oid);
|
type Item = (git::fmt::Qualified<'static>, git::Oid);
|
||||||
type IntoIter = std::collections::btree_map::IntoIter<git::Qualified<'static>, git::Oid>;
|
type IntoIter = std::collections::btree_map::IntoIter<git::fmt::Qualified<'static>, git::Oid>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.inner.into_iter()
|
self.inner.into_iter()
|
||||||
|
|
@ -51,12 +51,12 @@ impl IntoIterator for UpdatedCanonicalRefs {
|
||||||
impl UpdatedCanonicalRefs {
|
impl UpdatedCanonicalRefs {
|
||||||
/// Insert a new updated entry for the canonical reference identified by
|
/// Insert a new updated entry for the canonical reference identified by
|
||||||
/// `refname` and its new `target.`
|
/// `refname` and its new `target.`
|
||||||
pub fn updated(&mut self, refname: git::Qualified<'static>, target: git::Oid) {
|
pub fn updated(&mut self, refname: git::fmt::Qualified<'static>, target: git::Oid) {
|
||||||
self.inner.insert(refname, target);
|
self.inner.insert(refname, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator of all the updates.
|
/// Return an iterator of all the updates.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&git::Qualified<'static>, &git::Oid)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&git::fmt::Qualified<'static>, &git::Oid)> {
|
||||||
self.inner.iter()
|
self.inner.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::io;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use radicle::{cob, git, identity, storage};
|
use radicle::{cob, git::raw, identity, storage};
|
||||||
use radicle_fetch as fetch;
|
use radicle_fetch as fetch;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -10,7 +10,7 @@ pub enum Fetch {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Run(#[from] fetch::Error),
|
Run(#[from] fetch::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Git(#[from] git::raw::Error),
|
Git(#[from] raw::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Storage(#[from] storage::Error),
|
Storage(#[from] storage::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,4 @@ log = { workspace = true }
|
||||||
radicle = { workspace = true }
|
radicle = { workspace = true }
|
||||||
radicle-cli = { workspace = true }
|
radicle-cli = { workspace = true }
|
||||||
radicle-crypto = { workspace = true }
|
radicle-crypto = { workspace = true }
|
||||||
radicle-git-ext = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
@ -19,9 +19,9 @@ pub enum Error {
|
||||||
/// Invalid reference name.
|
/// Invalid reference name.
|
||||||
#[error("invalid ref: {0}")]
|
#[error("invalid ref: {0}")]
|
||||||
InvalidRef(#[from] radicle::git::fmt::Error),
|
InvalidRef(#[from] radicle::git::fmt::Error),
|
||||||
/// Git error.
|
/// Invalid object ID.
|
||||||
#[error("git: {0}")]
|
#[error("invalid oid: {0}")]
|
||||||
InvalidOid(#[source] git::raw::Error),
|
InvalidOid(#[from] radicle::git::ParseOidError),
|
||||||
|
|
||||||
/// Error fetching pack from storage to working copy.
|
/// Error fetching pack from storage to working copy.
|
||||||
#[error("`git fetch-pack` failed with exit status {status}, stderr and stdout follow:\n{stderr}\n{stdout}")]
|
#[error("`git fetch-pack` failed with exit status {status}, stderr and stdout follow:\n{stderr}\n{stdout}")]
|
||||||
|
|
@ -34,7 +34,7 @@ pub enum Error {
|
||||||
|
|
||||||
/// Run a git fetch command.
|
/// Run a git fetch command.
|
||||||
pub fn run<R: ReadRepository>(
|
pub fn run<R: ReadRepository>(
|
||||||
mut refs: Vec<(git::Oid, git::RefString)>,
|
mut refs: Vec<(git::Oid, git::fmt::RefString)>,
|
||||||
stored: R,
|
stored: R,
|
||||||
stdin: &io::Stdin,
|
stdin: &io::Stdin,
|
||||||
verbosity: Verbosity,
|
verbosity: Verbosity,
|
||||||
|
|
@ -45,8 +45,8 @@ pub fn run<R: ReadRepository>(
|
||||||
let tokens = read_line(stdin, &mut line)?;
|
let tokens = read_line(stdin, &mut line)?;
|
||||||
match tokens.as_slice() {
|
match tokens.as_slice() {
|
||||||
["fetch", oid, refstr] => {
|
["fetch", oid, refstr] => {
|
||||||
let oid = git::Oid::from_str(oid).map_err(Error::InvalidOid)?;
|
let oid = git::Oid::from_str(oid)?;
|
||||||
let refstr = git::RefString::try_from(*refstr)?;
|
let refstr = git::fmt::RefString::try_from(*refstr)?;
|
||||||
|
|
||||||
refs.push((oid, refstr));
|
refs.push((oid, refstr));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub enum Error {
|
||||||
Identity(#[from] radicle::identity::DocError),
|
Identity(#[from] radicle::identity::DocError),
|
||||||
/// Git error.
|
/// Git error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Git(#[from] radicle::git::ext::Error),
|
Git(#[from] radicle::git::raw::Error),
|
||||||
/// Profile error.
|
/// Profile error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Profile(#[from] profile::Error),
|
Profile(#[from] profile::Error),
|
||||||
|
|
@ -56,8 +56,8 @@ pub fn for_fetch<R: ReadRepository + cob::Store<Namespace = NodeId> + 'static>(
|
||||||
// List canonical references.
|
// List canonical references.
|
||||||
// Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy.
|
// Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy.
|
||||||
for glob in [
|
for glob in [
|
||||||
git::refspec::pattern!("refs/heads/*"),
|
git::fmt::pattern!("refs/heads/*"),
|
||||||
git::refspec::pattern!("refs/tags/*"),
|
git::fmt::pattern!("refs/tags/*"),
|
||||||
] {
|
] {
|
||||||
for (name, oid) in stored.references_glob(&glob)? {
|
for (name, oid) in stored.references_glob(&glob)? {
|
||||||
println!("{oid} {name}");
|
println!("{oid} {name}");
|
||||||
|
|
@ -81,8 +81,8 @@ pub fn for_push<R: ReadRepository>(profile: &Profile, stored: &R) -> Result<(),
|
||||||
// Only our own refs can be pushed to.
|
// Only our own refs can be pushed to.
|
||||||
for (name, oid) in stored.references_of(profile.id())? {
|
for (name, oid) in stored.references_of(profile.id())? {
|
||||||
// Only branches and tags can be pushed to.
|
// Only branches and tags can be pushed to.
|
||||||
if name.starts_with(git::refname!("refs/heads").as_str())
|
if name.starts_with(git::fmt::refname!("refs/heads").as_str())
|
||||||
|| name.starts_with(git::refname!("refs/tags").as_str())
|
|| name.starts_with(git::fmt::refname!("refs/tags").as_str())
|
||||||
{
|
{
|
||||||
println!("{oid} {name}");
|
println!("{oid} {name}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@ pub enum Error {
|
||||||
/// List error.
|
/// List error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
List(#[from] list::Error),
|
List(#[from] list::Error),
|
||||||
|
/// Invalid object ID.
|
||||||
|
#[error("invalid oid: {0}")]
|
||||||
|
InvalidOid(#[from] radicle::git::ParseOidError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Models values for the `verbosity` option, see
|
/// Models values for the `verbosity` option, see
|
||||||
|
|
@ -162,13 +165,13 @@ pub enum Branch {
|
||||||
/// Create a branch with the same name as the upstream branch (i.e. `patches/<patch id>`).
|
/// Create a branch with the same name as the upstream branch (i.e. `patches/<patch id>`).
|
||||||
MirrorUpstream,
|
MirrorUpstream,
|
||||||
/// Create a branch with the provided name.
|
/// Create a branch with the provided name.
|
||||||
Provided(git::RefString),
|
Provided(git::fmt::RefString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Branch {
|
impl Branch {
|
||||||
/// Return the branch name to be used for the local branch when creating a
|
/// Return the branch name to be used for the local branch when creating a
|
||||||
/// patch.
|
/// patch.
|
||||||
pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::Qualified> {
|
pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::fmt::Qualified> {
|
||||||
match self {
|
match self {
|
||||||
Self::None => None,
|
Self::None => None,
|
||||||
Self::MirrorUpstream => Some(git::refs::patch(object)),
|
Self::MirrorUpstream => Some(git::refs::patch(object)),
|
||||||
|
|
@ -207,12 +210,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
|
||||||
// module is aware of that.
|
// module is aware of that.
|
||||||
cli::Paint::set_terminal(cli::TerminalFile::Stderr);
|
cli::Paint::set_terminal(cli::TerminalFile::Stderr);
|
||||||
|
|
||||||
let (remote, url): (Option<git::RefString>, Url) = {
|
let (remote, url): (Option<git::fmt::RefString>, Url) = {
|
||||||
let args = env::args().skip(1).take(2).collect::<Vec<_>>();
|
let args = env::args().skip(1).take(2).collect::<Vec<_>>();
|
||||||
|
|
||||||
match args.as_slice() {
|
match args.as_slice() {
|
||||||
[url] => (None, url.parse()?),
|
[url] => (None, url.parse()?),
|
||||||
[remote, url] => (git::RefString::try_from(remote.as_str()).ok(), url.parse()?),
|
[remote, url] => (
|
||||||
|
git::fmt::RefString::try_from(remote.as_str()).ok(),
|
||||||
|
url.parse()?,
|
||||||
|
),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidArguments(args));
|
return Err(Error::InvalidArguments(args));
|
||||||
|
|
@ -274,7 +280,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
["fetch", oid, refstr] => {
|
["fetch", oid, refstr] => {
|
||||||
let oid = git::Oid::from_str(oid)?;
|
let oid = git::Oid::from_str(oid)?;
|
||||||
let refstr = git::RefString::try_from(*refstr)?;
|
let refstr = git::fmt::RefString::try_from(*refstr)?;
|
||||||
|
|
||||||
return Ok(fetch::run(
|
return Ok(fetch::run(
|
||||||
vec![(oid, refstr)],
|
vec![(oid, refstr)],
|
||||||
|
|
@ -347,7 +353,9 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
|
||||||
|
|
||||||
opts.base = Some(git::Oid::from(commit.id()));
|
opts.base = Some(git::Oid::from(commit.id()));
|
||||||
}
|
}
|
||||||
"patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?),
|
"patch.branch" => {
|
||||||
|
opts.branch = Branch::Provided(git::fmt::RefString::try_from(val)?)
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(Error::UnsupportedPushOption(other.to_owned()));
|
return Err(Error::UnsupportedPushOption(other.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub enum Error {
|
||||||
NoKey,
|
NoKey,
|
||||||
/// User tried to delete the canonical branch.
|
/// User tried to delete the canonical branch.
|
||||||
#[error("refusing to delete default branch ref '{0}'")]
|
#[error("refusing to delete default branch ref '{0}'")]
|
||||||
DeleteForbidden(git::RefString),
|
DeleteForbidden(git::fmt::RefString),
|
||||||
/// Identity document error.
|
/// Identity document error.
|
||||||
#[error("doc: {0}")]
|
#[error("doc: {0}")]
|
||||||
Doc(#[from] radicle::identity::doc::DocError),
|
Doc(#[from] radicle::identity::doc::DocError),
|
||||||
|
|
@ -65,9 +65,6 @@ pub enum Error {
|
||||||
/// Git error.
|
/// Git error.
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
Git(#[from] git::raw::Error),
|
Git(#[from] git::raw::Error),
|
||||||
/// Git extension error.
|
|
||||||
#[error("git: {0}")]
|
|
||||||
GitExt(#[from] git::ext::Error),
|
|
||||||
/// Storage error.
|
/// Storage error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Storage(#[from] radicle::storage::Error),
|
Storage(#[from] radicle::storage::Error),
|
||||||
|
|
@ -136,9 +133,9 @@ pub enum Error {
|
||||||
/// Push command.
|
/// Push command.
|
||||||
enum Command {
|
enum Command {
|
||||||
/// Update ref.
|
/// Update ref.
|
||||||
Push(git::Refspec<git::Oid, git::RefString>),
|
Push(git::fmt::refspec::Refspec<git::Oid, git::fmt::RefString>),
|
||||||
/// Delete ref.
|
/// Delete ref.
|
||||||
Delete(git::RefString),
|
Delete(git::fmt::RefString),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
@ -173,7 +170,7 @@ impl Command {
|
||||||
let Some((src, dst)) = s.split_once(':') else {
|
let Some((src, dst)) = s.split_once(':') else {
|
||||||
return Err(CommandError::Empty { rev: s.to_string() });
|
return Err(CommandError::Empty { rev: s.to_string() });
|
||||||
};
|
};
|
||||||
let dst = git::RefString::try_from(dst).map_err(|err| CommandError::Delete {
|
let dst = git::fmt::RefString::try_from(dst).map_err(|err| CommandError::Delete {
|
||||||
rev: dst.to_string(),
|
rev: dst.to_string(),
|
||||||
err,
|
err,
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -195,12 +192,12 @@ impl Command {
|
||||||
.id()
|
.id()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
Ok(Self::Push(git::Refspec { src, dst, force }))
|
Ok(Self::Push(git::fmt::refspec::Refspec { src, dst, force }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the destination refname.
|
/// Return the destination refname.
|
||||||
fn dst(&self) -> &git::RefStr {
|
fn dst(&self) -> &git::fmt::RefStr {
|
||||||
match self {
|
match self {
|
||||||
Self::Push(rs) => rs.dst.as_refstr(),
|
Self::Push(rs) => rs.dst.as_refstr(),
|
||||||
Self::Delete(rs) => rs,
|
Self::Delete(rs) => rs,
|
||||||
|
|
@ -211,30 +208,30 @@ impl Command {
|
||||||
enum PushAction {
|
enum PushAction {
|
||||||
OpenPatch,
|
OpenPatch,
|
||||||
UpdatePatch {
|
UpdatePatch {
|
||||||
dst: git::Qualified<'static>,
|
dst: git::fmt::Qualified<'static>,
|
||||||
patch: patch::PatchId,
|
patch: patch::PatchId,
|
||||||
},
|
},
|
||||||
PushRef {
|
PushRef {
|
||||||
dst: git::Qualified<'static>,
|
dst: git::fmt::Qualified<'static>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PushAction {
|
impl PushAction {
|
||||||
fn new(dst: &git::RefString) -> Result<Self, error::PushAction> {
|
fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
|
||||||
if dst == &*rad::PATCHES_REFNAME {
|
if dst == &*rad::PATCHES_REFNAME {
|
||||||
Ok(Self::OpenPatch)
|
Ok(Self::OpenPatch)
|
||||||
} else {
|
} else {
|
||||||
let dst = git::Qualified::from_refstr(dst)
|
let dst = git::fmt::Qualified::from_refstr(dst)
|
||||||
.ok_or_else(|| error::PushAction::InvalidRef {
|
.ok_or_else(|| error::PushAction::InvalidRef {
|
||||||
refname: dst.clone(),
|
refname: dst.clone(),
|
||||||
})?
|
})?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
if let Some(oid) = dst.strip_prefix(git::refname!("refs/heads/patches")) {
|
if let Some(oid) = dst.strip_prefix(git::fmt::refname!("refs/heads/patches")) {
|
||||||
let patch = git::Oid::from_str(oid)
|
let patch = git::Oid::from_str(oid)
|
||||||
.map_err(|err| error::PushAction::InvalidPatchId {
|
.map_err(|source| error::PushAction::InvalidPatchId {
|
||||||
suffix: oid.to_string(),
|
suffix: oid.to_string(),
|
||||||
source: err,
|
source,
|
||||||
})
|
})
|
||||||
.map(patch::PatchId::from)?;
|
.map(patch::PatchId::from)?;
|
||||||
Ok(Self::UpdatePatch { dst, patch })
|
Ok(Self::UpdatePatch { dst, patch })
|
||||||
|
|
@ -248,7 +245,7 @@ impl PushAction {
|
||||||
/// Run a git push command.
|
/// Run a git push command.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
mut specs: Vec<String>,
|
mut specs: Vec<String>,
|
||||||
remote: Option<git::RefString>,
|
remote: Option<git::fmt::RefString>,
|
||||||
url: Url,
|
url: Url,
|
||||||
stored: &storage::git::Repository,
|
stored: &storage::git::Repository,
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
|
|
@ -290,7 +287,7 @@ pub fn run(
|
||||||
let identity = stored.identity()?;
|
let identity = stored.identity()?;
|
||||||
let project = identity.project()?;
|
let project = identity.project()?;
|
||||||
let canonical_ref = git::refs::branch(project.default_branch());
|
let canonical_ref = git::refs::branch(project.default_branch());
|
||||||
let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> =
|
let mut set_canonical_refs: Vec<(git::fmt::Qualified, git::canonical::Object)> =
|
||||||
Vec::with_capacity(specs.len());
|
Vec::with_capacity(specs.len());
|
||||||
|
|
||||||
// Rely on the environment variable `GIT_DIR`.
|
// Rely on the environment variable `GIT_DIR`.
|
||||||
|
|
@ -317,7 +314,7 @@ pub fn run(
|
||||||
.map(|_| None)
|
.map(|_| None)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
Command::Push(git::Refspec { src, dst, force }) => {
|
Command::Push(git::fmt::refspec::Refspec { src, dst, force }) => {
|
||||||
let patches = crate::patches_mut(profile, stored)?;
|
let patches = crate::patches_mut(profile, stored)?;
|
||||||
let action = PushAction::new(dst)?;
|
let action = PushAction::new(dst)?;
|
||||||
|
|
||||||
|
|
@ -373,7 +370,7 @@ pub fn run(
|
||||||
// canonical branch.
|
// canonical branch.
|
||||||
if let Some(canonical) = rules.canonical(dst.clone(), stored) {
|
if let Some(canonical) = rules.canonical(dst.clone(), stored) {
|
||||||
let object = working
|
let object = working
|
||||||
.find_object(**src, None)
|
.find_object(src.into(), None)
|
||||||
.map(|obj| git::canonical::Object::new(&obj))?
|
.map(|obj| git::canonical::Object::new(&obj))?
|
||||||
.ok_or(Error::UnknownObjectType { oid: *src })?;
|
.ok_or(Error::UnknownObjectType { oid: *src })?;
|
||||||
|
|
||||||
|
|
@ -429,10 +426,10 @@ pub fn run(
|
||||||
}
|
}
|
||||||
|
|
||||||
match stored.backend.refname_to_id(refname.as_str()) {
|
match stored.backend.refname_to_id(refname.as_str()) {
|
||||||
Ok(new) if new != *oid => {
|
Ok(new) if oid != new => {
|
||||||
stored.backend.reference(
|
stored.backend.reference(
|
||||||
refname.as_str(),
|
refname.as_str(),
|
||||||
*oid,
|
oid.into(),
|
||||||
true,
|
true,
|
||||||
"set-canonical-reference from git-push (radicle)",
|
"set-canonical-reference from git-push (radicle)",
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -441,7 +438,7 @@ pub fn run(
|
||||||
Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
|
Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
|
||||||
stored.backend.reference(
|
stored.backend.reference(
|
||||||
refname.as_str(),
|
refname.as_str(),
|
||||||
*oid,
|
oid.into(),
|
||||||
true,
|
true,
|
||||||
"set-canonical-reference from git-push (radicle)",
|
"set-canonical-reference from git-push (radicle)",
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -505,7 +502,7 @@ fn patch_base(
|
||||||
/// [`Drop::drop`].
|
/// [`Drop::drop`].
|
||||||
struct TempPatchRef<'a> {
|
struct TempPatchRef<'a> {
|
||||||
stored: &'a storage::git::Repository,
|
stored: &'a storage::git::Repository,
|
||||||
reference: git::Namespaced<'a>,
|
reference: git::fmt::Namespaced<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TempPatchRef<'a> {
|
impl<'a> TempPatchRef<'a> {
|
||||||
|
|
@ -539,7 +536,7 @@ impl<'a> Drop for TempPatchRef<'a> {
|
||||||
/// Open a new patch.
|
/// Open a new patch.
|
||||||
fn patch_open<G>(
|
fn patch_open<G>(
|
||||||
head: &git::Oid,
|
head: &git::Oid,
|
||||||
upstream: &Option<git::RefString>,
|
upstream: &Option<git::fmt::RefString>,
|
||||||
nid: &NodeId,
|
nid: &NodeId,
|
||||||
working: &git::raw::Repository,
|
working: &git::raw::Repository,
|
||||||
stored: &storage::git::Repository,
|
stored: &storage::git::Repository,
|
||||||
|
|
@ -563,7 +560,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let (title, description) =
|
let (title, description) =
|
||||||
term::patch::get_create_message(opts.message, &stored.backend, &base, head)?;
|
term::patch::get_create_message(opts.message, &stored.backend, &base.into(), &head.into())?;
|
||||||
|
|
||||||
let patch = if opts.draft {
|
let patch = if opts.draft {
|
||||||
patches.draft(
|
patches.draft(
|
||||||
|
|
@ -607,24 +604,29 @@ where
|
||||||
let refname = git::refs::patch(&patch).with_namespace(nid.into());
|
let refname = git::refs::patch(&patch).with_namespace(nid.into());
|
||||||
let _ = stored.raw().reference(
|
let _ = stored.raw().reference(
|
||||||
refname.as_str(),
|
refname.as_str(),
|
||||||
**head,
|
head.into(),
|
||||||
true,
|
true,
|
||||||
"Create reference for patch head",
|
"Create reference for patch head",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(upstream) = upstream {
|
if let Some(upstream) = upstream {
|
||||||
if let Some(local_branch) = opts.branch.to_branch_name(&patch) {
|
if let Some(local_branch) = opts.branch.to_branch_name(&patch) {
|
||||||
fn strip_refs_heads(qualified: git::Qualified) -> git::RefString {
|
fn strip_refs_heads(qualified: git::fmt::Qualified) -> git::fmt::RefString {
|
||||||
let (_refs, _heads, x, xs) = qualified.non_empty_components();
|
let (_refs, _heads, x, xs) = qualified.non_empty_components();
|
||||||
std::iter::once(x).chain(xs).collect()
|
std::iter::once(x).chain(xs).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
working.reference(&local_branch, **head, true, "Create local branch for patch")?;
|
working.reference(
|
||||||
|
&local_branch,
|
||||||
|
head.into(),
|
||||||
|
true,
|
||||||
|
"Create local branch for patch",
|
||||||
|
)?;
|
||||||
|
|
||||||
let remote_branch = git::refs::workdir::patch_upstream(&patch);
|
let remote_branch = git::refs::workdir::patch_upstream(&patch);
|
||||||
let remote_branch = working.reference(
|
let remote_branch = working.reference(
|
||||||
&remote_branch,
|
&remote_branch,
|
||||||
**head,
|
head.into(),
|
||||||
true,
|
true,
|
||||||
"Create remote tracking branch for patch",
|
"Create remote tracking branch for patch",
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -667,7 +669,7 @@ where
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn patch_update<G>(
|
fn patch_update<G>(
|
||||||
head: &git::Oid,
|
head: &git::Oid,
|
||||||
dst: &git::Qualified,
|
dst: &git::fmt::Qualified,
|
||||||
force: bool,
|
force: bool,
|
||||||
patch_id: patch::PatchId,
|
patch_id: patch::PatchId,
|
||||||
nid: &NodeId,
|
nid: &NodeId,
|
||||||
|
|
@ -703,7 +705,8 @@ where
|
||||||
let (latest_id, latest) = patch.latest();
|
let (latest_id, latest) = patch.latest();
|
||||||
let latest = latest.clone();
|
let latest = latest.clone();
|
||||||
|
|
||||||
let message = term::patch::get_update_message(opts.message, &stored.backend, &latest, head)?;
|
let message =
|
||||||
|
term::patch::get_update_message(opts.message, &stored.backend, &latest, &head.into())?;
|
||||||
|
|
||||||
let dst = dst.with_namespace(nid.into());
|
let dst = dst.with_namespace(nid.into());
|
||||||
push_ref(head, &dst, force, stored.raw(), opts.verbosity)?;
|
push_ref(head, &dst, force, stored.raw(), opts.verbosity)?;
|
||||||
|
|
@ -744,7 +747,7 @@ where
|
||||||
|
|
||||||
fn push<G>(
|
fn push<G>(
|
||||||
src: &git::Oid,
|
src: &git::Oid,
|
||||||
dst: &git::Qualified,
|
dst: &git::fmt::Qualified,
|
||||||
force: bool,
|
force: bool,
|
||||||
nid: &NodeId,
|
nid: &NodeId,
|
||||||
working: &git::raw::Repository,
|
working: &git::raw::Repository,
|
||||||
|
|
@ -768,7 +771,7 @@ where
|
||||||
|
|
||||||
if let Some(old) = old {
|
if let Some(old) = old {
|
||||||
let proj = stored.project()?;
|
let proj = stored.project()?;
|
||||||
let master = &*git::Qualified::from(git::lit::refs_heads(proj.default_branch()));
|
let master = &*git::fmt::Qualified::from(git::fmt::lit::refs_heads(proj.default_branch()));
|
||||||
|
|
||||||
// If we're pushing to the project's default branch, we want to see if any patches got
|
// If we're pushing to the project's default branch, we want to see if any patches got
|
||||||
// merged or reverted, and if so, update the patch COB.
|
// merged or reverted, and if so, update the patch COB.
|
||||||
|
|
@ -800,8 +803,8 @@ where
|
||||||
{
|
{
|
||||||
// Find all commits reachable from the old OID but not from the new OID.
|
// Find all commits reachable from the old OID but not from the new OID.
|
||||||
let mut revwalk = stored.revwalk()?;
|
let mut revwalk = stored.revwalk()?;
|
||||||
revwalk.push(*old)?;
|
revwalk.push(old.into())?;
|
||||||
revwalk.hide(*new)?;
|
revwalk.hide(new.into())?;
|
||||||
|
|
||||||
// List of commits that have been dropped.
|
// List of commits that have been dropped.
|
||||||
let dropped = revwalk
|
let dropped = revwalk
|
||||||
|
|
@ -943,7 +946,7 @@ where
|
||||||
/// Push a single reference to storage.
|
/// Push a single reference to storage.
|
||||||
fn push_ref(
|
fn push_ref(
|
||||||
src: &git::Oid,
|
src: &git::Oid,
|
||||||
dst: &git::Namespaced,
|
dst: &git::fmt::Namespaced,
|
||||||
force: bool,
|
force: bool,
|
||||||
stored: &git::raw::Repository,
|
stored: &git::raw::Repository,
|
||||||
verbosity: Verbosity,
|
verbosity: Verbosity,
|
||||||
|
|
@ -951,7 +954,7 @@ fn push_ref(
|
||||||
let path = dunce::canonicalize(stored.path())?.display().to_string();
|
let path = dunce::canonicalize(stored.path())?.display().to_string();
|
||||||
// Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
|
// Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
|
||||||
// This happens during the `list for-push` phase.
|
// This happens during the `list for-push` phase.
|
||||||
let refspec = git::Refspec { src, dst, force };
|
let refspec = git::fmt::refspec::Refspec { src, dst, force };
|
||||||
|
|
||||||
let mut args = vec!["send-pack".to_string()];
|
let mut args = vec!["send-pack".to_string()];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ where
|
||||||
/// copy, and that checks that any two commits are related in the graph.
|
/// copy, and that checks that any two commits are related in the graph.
|
||||||
///
|
///
|
||||||
/// Ensures that the new head and the canonical commit do not diverge.
|
/// Ensures that the new head and the canonical commit do not diverge.
|
||||||
pub fn quorum(self) -> Result<(git::Qualified<'a>, canonical::Object), QuorumError> {
|
pub fn quorum(self) -> Result<(git::fmt::Qualified<'a>, canonical::Object), QuorumError> {
|
||||||
self.canonical
|
self.canonical
|
||||||
.quorum()
|
.quorum()
|
||||||
.map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object))
|
.map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object))
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@ pub struct HeadsDiverge {
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum PushAction {
|
pub enum PushAction {
|
||||||
#[error("invalid reference {refname}, expected qualified reference starting with `refs/`")]
|
#[error("invalid reference {refname}, expected qualified reference starting with `refs/`")]
|
||||||
InvalidRef { refname: git::RefString },
|
InvalidRef { refname: git::fmt::RefString },
|
||||||
#[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID")]
|
#[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID: {source}")]
|
||||||
InvalidPatchId {
|
InvalidPatchId {
|
||||||
suffix: String,
|
suffix: String,
|
||||||
source: git::raw::Error,
|
source: radicle::git::ParseOidError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Re-exports from `git2` at `radicle::git::raw` were limited, using
|
||||||
|
the heartwood workspace as a filter. Dependents that require members that
|
||||||
|
are not exported anymore will have to depend on `git2` directly.
|
||||||
|
- Some re-exports from `git-ref-format-core` were moved from `radicle::git`
|
||||||
|
to `radicle::fmt`.
|
||||||
|
- The crate now re-exports `radicle::git::Oid` from a new `radicle-oid` crate,
|
||||||
|
in an effort to decrease dependence on `git2` via `radicle-git-ext`. This
|
||||||
|
new object identifier type does not implement `Deref` anymore. Use `Into`
|
||||||
|
to convert to a `git2::Oid` as necessary.
|
||||||
|
- Re-exports of `radicle-git-ext` were removed, as this dependency is removed.
|
||||||
|
Instead of `radicle_git_ext::Error`, use `git2::Error` (re-exported as
|
||||||
|
`radicle::git::raw::Error`) together with the new extension trait
|
||||||
|
`radicle::git::raw::ErrorExt`.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
- `radicle::node::Handle::announce_refs` is deprecated in favor of
|
- `radicle::node::Handle::announce_refs` is deprecated in favor of
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ rust-version.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
test = ["tempfile", "qcheck", "radicle-crypto/test"]
|
test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"]
|
||||||
logger = ["colored", "chrono"]
|
logger = ["colored", "chrono"]
|
||||||
|
schemars = ["radicle-oid/schemars", "dep:schemars"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
amplify = { workspace = true, features = ["std"] }
|
amplify = { workspace = true, features = ["std"] }
|
||||||
|
|
@ -32,9 +33,10 @@ log = { workspace = true, features = ["std"] }
|
||||||
multibase = { workspace = true }
|
multibase = { workspace = true }
|
||||||
nonempty = { workspace = true, features = ["serialize"] }
|
nonempty = { workspace = true, features = ["serialize"] }
|
||||||
qcheck = { workspace = true, optional = true }
|
qcheck = { workspace = true, optional = true }
|
||||||
radicle-cob = { workspace = true }
|
radicle-cob = { workspace = true, features = ["git2"] }
|
||||||
radicle-crypto = { workspace = true, features = ["git-ref-format-core", "ssh", "sqlite", "cyphernet"] }
|
radicle-crypto = { workspace = true, features = ["git-ref-format-core", "ssh", "sqlite", "cyphernet"] }
|
||||||
radicle-git-ext = { workspace = true, features = ["serde"] }
|
radicle-git-ref-format = { workspace = true, features = ["macro", "serde"] }
|
||||||
|
radicle-oid = { workspace = true, features = ["git2", "serde", "std", "sha1"] }
|
||||||
radicle-ssh = { workspace = true }
|
radicle-ssh = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true, features = ["derive", "std"] }
|
schemars = { workspace = true, optional = true, features = ["derive", "std"] }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
@ -58,6 +60,7 @@ jsonschema = { version = "0.30", default-features = false }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
qcheck = { workspace = true }
|
qcheck = { workspace = true }
|
||||||
qcheck-macros = { workspace = true }
|
qcheck-macros = { workspace = true }
|
||||||
radicle-cob = { workspace = true, features = ["stable-commit-ids"] }
|
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
|
||||||
radicle-crypto = { workspace = true, features = ["test"] }
|
radicle-crypto = { workspace = true, features = ["test"] }
|
||||||
|
radicle-git-metadata = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ pub mod thread;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub use radicle_cob::stable;
|
||||||
|
|
||||||
pub use cache::{migrate, MigrateCallback};
|
pub use cache::{migrate, MigrateCallback};
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use op::{ActorId, Op};
|
pub use op::{ActorId, Op};
|
||||||
|
|
@ -21,7 +24,7 @@ pub use radicle_cob::{
|
||||||
CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId,
|
CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId,
|
||||||
Store, TypeName, Update, Updated, Version,
|
Store, TypeName, Update, Updated, Version,
|
||||||
};
|
};
|
||||||
pub use radicle_cob::{create, get, git, list, remove, update};
|
pub use radicle_cob::{create, get, list, remove, update};
|
||||||
|
|
||||||
/// The exact identifier for a particular COB.
|
/// The exact identifier for a particular COB.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
|
||||||
|
|
@ -66,17 +69,17 @@ impl TypedId {
|
||||||
self.type_name == *identity::TYPENAME
|
self.type_name == *identity::TYPENAME
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a [`crate::git::Namespaced`] refname into a [`TypedId`].
|
/// Parse a [`crate::git::fmt::Namespaced`] refname into a [`TypedId`].
|
||||||
///
|
///
|
||||||
/// All namespaces are stripped before parsing the suffix for the
|
/// All namespaces are stripped before parsing the suffix for the
|
||||||
/// [`TypedId`] (see [`TypedId::from_qualified`]).
|
/// [`TypedId`] (see [`TypedId::from_qualified`]).
|
||||||
pub fn from_namespaced(
|
pub fn from_namespaced(
|
||||||
n: &crate::git::Namespaced,
|
n: &crate::git::fmt::Namespaced,
|
||||||
) -> Result<Option<Self>, ParseIdentifierError> {
|
) -> Result<Option<Self>, ParseIdentifierError> {
|
||||||
Self::from_qualified(&n.strip_namespace_recursive())
|
Self::from_qualified(&n.strip_namespace_recursive())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a [`crate::git::Qualified`] refname into a [`TypedId`].
|
/// Parse a [`crate::git::fmt::Qualified`] refname into a [`TypedId`].
|
||||||
///
|
///
|
||||||
/// The refname is expected to be of the form:
|
/// The refname is expected to be of the form:
|
||||||
/// `refs/cobs/<type name>/<object id>`
|
/// `refs/cobs/<type name>/<object id>`
|
||||||
|
|
@ -87,7 +90,9 @@ impl TypedId {
|
||||||
///
|
///
|
||||||
/// This will fail if the refname is of the correct form, but the
|
/// This will fail if the refname is of the correct form, but the
|
||||||
/// type name or object id fail to parse.
|
/// type name or object id fail to parse.
|
||||||
pub fn from_qualified(q: &crate::git::Qualified) -> Result<Option<Self>, ParseIdentifierError> {
|
pub fn from_qualified(
|
||||||
|
q: &crate::git::fmt::Qualified,
|
||||||
|
) -> Result<Option<Self>, ParseIdentifierError> {
|
||||||
match q.non_empty_iter() {
|
match q.non_empty_iter() {
|
||||||
("refs", "cobs", type_name, mut id) => {
|
("refs", "cobs", type_name, mut id) => {
|
||||||
let Some(id) = id.next() else {
|
let Some(id) = id.next() else {
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@ impl From<Oid> for Uri {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Uri> for Oid {
|
impl TryFrom<&Uri> for crate::git::raw::Oid {
|
||||||
type Error = Uri;
|
type Error = Uri;
|
||||||
|
|
||||||
fn try_from(value: &Uri) -> Result<Self, Self::Error> {
|
fn try_from(value: &Uri) -> Result<Self, Self::Error> {
|
||||||
|
|
@ -368,6 +368,14 @@ impl TryFrom<&Uri> for Oid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Uri> for crate::git::Oid {
|
||||||
|
type Error = Uri;
|
||||||
|
|
||||||
|
fn try_from(value: &Uri) -> Result<Self, Self::Error> {
|
||||||
|
crate::git::raw::Oid::try_from(value).map(crate::git::Oid::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Uri {
|
impl std::fmt::Display for Uri {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ impl<R: ReadRepository> Evaluate<R> for External {
|
||||||
Self::from_root(Op::try_from(entry)?, store)
|
Self::from_root(Op::try_from(entry)?, store)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply<'a, I: Iterator<Item = (&'a radicle_git_ext::Oid, &'a radicle_cob::Entry)>>(
|
fn apply<'a, I: Iterator<Item = (&'a crate::git::Oid, &'a radicle_cob::Entry)>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &radicle_cob::Entry,
|
entry: &radicle_cob::Entry,
|
||||||
concurrent: I,
|
concurrent: I,
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use crypto::{PublicKey, Signature};
|
use crypto::{PublicKey, Signature};
|
||||||
use radicle_cob::{Embed, ObjectId, TypeName};
|
use radicle_cob::{Embed, ObjectId, TypeName};
|
||||||
use radicle_git_ext as git_ext;
|
|
||||||
use radicle_git_ext::Oid;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::git;
|
||||||
|
use crate::git::Oid;
|
||||||
use crate::identity::doc::Doc;
|
use crate::identity::doc::Doc;
|
||||||
use crate::node::device::Device;
|
use crate::node::device::Device;
|
||||||
use crate::node::NodeId;
|
use crate::node::NodeId;
|
||||||
|
|
@ -126,9 +126,7 @@ pub enum ApplyError {
|
||||||
#[error("document does not contain any changes to current identity")]
|
#[error("document does not contain any changes to current identity")]
|
||||||
DocUnchanged,
|
DocUnchanged,
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
Git(#[from] crate::git::raw::Error),
|
Git(#[from] git::raw::Error),
|
||||||
#[error("git: {0}")]
|
|
||||||
GitExt(#[from] git_ext::Error),
|
|
||||||
#[error("identity document error: {0}")]
|
#[error("identity document error: {0}")]
|
||||||
Doc(#[from] DocError),
|
Doc(#[from] DocError),
|
||||||
}
|
}
|
||||||
|
|
@ -1323,16 +1321,15 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
bob.repo.fetch(alice);
|
bob.repo.fetch(alice);
|
||||||
let a3 = cob::git::stable::with_advanced_timestamp(|| {
|
let a3 = cob::stable::with_advanced_timestamp(|| {
|
||||||
alice_identity.redact(a2, &alice.signer).unwrap()
|
alice_identity.redact(a2, &alice.signer).unwrap()
|
||||||
});
|
});
|
||||||
assert!(alice_identity.revision(&a1).is_some());
|
assert!(alice_identity.revision(&a1).is_some());
|
||||||
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
|
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
|
||||||
|
|
||||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||||
let b1 = cob::git::stable::with_advanced_timestamp(|| {
|
let b1 =
|
||||||
bob_identity.accept(&a2, &bob.signer).unwrap()
|
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(bob_identity.timeline, vec![a0, a1, a2, b1]);
|
assert_eq!(bob_identity.timeline, vec![a0, a1, a2, b1]);
|
||||||
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
|
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
|
||||||
|
|
@ -1381,15 +1378,14 @@ mod test {
|
||||||
eve.repo.fetch(bob);
|
eve.repo.fetch(bob);
|
||||||
|
|
||||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||||
let b1 = cob::git::stable::with_advanced_timestamp(|| {
|
let b1 =
|
||||||
bob_identity.accept(&a2, &bob.signer).unwrap()
|
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||||
});
|
|
||||||
assert_eq!(bob_identity.current, a2);
|
assert_eq!(bob_identity.current, a2);
|
||||||
|
|
||||||
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
||||||
let mut eve_doc = eve_identity.doc().clone().edit();
|
let mut eve_doc = eve_identity.doc().clone().edit();
|
||||||
eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]);
|
eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]);
|
||||||
let e1 = cob::git::stable::with_advanced_timestamp(|| {
|
let e1 = cob::stable::with_advanced_timestamp(|| {
|
||||||
eve_identity
|
eve_identity
|
||||||
.update(
|
.update(
|
||||||
cob::Title::new("Change visibility").unwrap(),
|
cob::Title::new("Change visibility").unwrap(),
|
||||||
|
|
@ -1458,15 +1454,13 @@ mod test {
|
||||||
|
|
||||||
// Bob accepts alice's revision.
|
// Bob accepts alice's revision.
|
||||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||||
let b1 = cob::git::stable::with_advanced_timestamp(|| {
|
let b1 =
|
||||||
bob_identity.accept(&a2, &bob.signer).unwrap()
|
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||||
});
|
|
||||||
|
|
||||||
// Eve rejects the revision, not knowing.
|
// Eve rejects the revision, not knowing.
|
||||||
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
||||||
let e1 = cob::git::stable::with_advanced_timestamp(|| {
|
let e1 =
|
||||||
eve_identity.reject(a2, &eve.signer).unwrap()
|
cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2, &eve.signer).unwrap());
|
||||||
});
|
|
||||||
assert!(eve_identity.revision(&a2).unwrap().is_active());
|
assert!(eve_identity.revision(&a2).unwrap().is_active());
|
||||||
|
|
||||||
// Then she submits a new revision.
|
// Then she submits a new revision.
|
||||||
|
|
@ -1576,7 +1570,7 @@ mod test {
|
||||||
assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);
|
assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);
|
||||||
|
|
||||||
alice_identity.reload().unwrap();
|
alice_identity.reload().unwrap();
|
||||||
let a2 = cob::git::stable::with_advanced_timestamp(|| {
|
let a2 = cob::stable::with_advanced_timestamp(|| {
|
||||||
alice_identity.accept(&b1, &alice.signer).unwrap()
|
alice_identity.accept(&b1, &alice.signer).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1765,7 +1765,7 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Comments require references, so adding two of them to the same transaction errors.
|
// Comments require references, so adding two of them to the same transaction errors.
|
||||||
let mut tx: Transaction<Issue, test::storage::git::Repository> =
|
let mut tx: Transaction<Issue, crate::storage::git::Repository> =
|
||||||
Transaction::<Issue, _>::default();
|
Transaction::<Issue, _>::default();
|
||||||
tx.comment("First reply", *issue.id, vec![]).unwrap();
|
tx.comment("First reply", *issue.id, vec![]).unwrap();
|
||||||
let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err();
|
let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err();
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ use radicle_crypto::PublicKey;
|
||||||
|
|
||||||
use crate::cob;
|
use crate::cob;
|
||||||
use crate::cob::Timestamp;
|
use crate::cob::Timestamp;
|
||||||
|
use crate::git;
|
||||||
|
use crate::identity;
|
||||||
use crate::identity::DocAt;
|
use crate::identity::DocAt;
|
||||||
use crate::storage::ReadRepository;
|
use crate::storage::ReadRepository;
|
||||||
use crate::{git, identity};
|
|
||||||
|
|
||||||
/// The author of an [`Op`].
|
/// The author of an [`Op`].
|
||||||
pub type ActorId = PublicKey;
|
pub type ActorId = PublicKey;
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ pub enum Error {
|
||||||
Payload(#[from] PayloadError),
|
Payload(#[from] PayloadError),
|
||||||
/// Git error.
|
/// Git error.
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
Git(#[from] git::ext::Error),
|
Git(#[from] git::raw::Error),
|
||||||
/// Store error.
|
/// Store error.
|
||||||
#[error("store: {0}")]
|
#[error("store: {0}")]
|
||||||
Store(#[from] store::Error),
|
Store(#[from] store::Error),
|
||||||
|
|
@ -601,12 +601,15 @@ impl Patch {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the merge base of this patch.
|
/// Get the merge base of this patch.
|
||||||
pub fn merge_base<R: ReadRepository>(&self, repo: &R) -> Result<git::Oid, git::ext::Error> {
|
pub fn merge_base<R: ReadRepository>(
|
||||||
|
&self,
|
||||||
|
repo: &R,
|
||||||
|
) -> Result<crate::git::Oid, crate::git::raw::Error> {
|
||||||
repo.merge_base(self.base(), self.head())
|
repo.merge_base(self.base(), self.head())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the commit range of this patch.
|
/// Get the commit range of this patch.
|
||||||
pub fn range(&self) -> Result<(git::Oid, git::Oid), git::ext::Error> {
|
pub fn range(&self) -> Result<(crate::git::Oid, crate::git::Oid), crate::git::raw::Error> {
|
||||||
Ok((*self.base(), *self.head()))
|
Ok((*self.base(), *self.head()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3334,7 +3337,7 @@ mod test {
|
||||||
},
|
},
|
||||||
&alice,
|
&alice,
|
||||||
);
|
);
|
||||||
let patch = Patch::from_history(&h0, &repo).unwrap();
|
let patch: Patch = Patch::from_history(&h0, &repo).unwrap();
|
||||||
assert_eq!(patch.revisions().count(), 2);
|
assert_eq!(patch.revisions().count(), 2);
|
||||||
|
|
||||||
let mut h1 = h0.clone();
|
let mut h1 = h0.clone();
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ pub enum Error {
|
||||||
Git(git::raw::Error),
|
Git(git::raw::Error),
|
||||||
#[error("failed to find reference '{name}': {err}")]
|
#[error("failed to find reference '{name}': {err}")]
|
||||||
RefLookup {
|
RefLookup {
|
||||||
name: git::RefString,
|
name: git::fmt::RefString,
|
||||||
#[source]
|
#[source]
|
||||||
err: git::raw::Error,
|
err: git::raw::Error,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::cob::{Op, TypeName};
|
use crate::cob::{Op, TypeName};
|
||||||
use crate::git::{self, Oid, PatternString};
|
use crate::git;
|
||||||
|
use crate::git::fmt::refspec::PatternString;
|
||||||
|
use crate::git::Oid;
|
||||||
|
|
||||||
use super::error;
|
use super::error;
|
||||||
use super::CobRange;
|
use super::CobRange;
|
||||||
|
|
@ -83,7 +85,7 @@ impl Walk {
|
||||||
match self.until {
|
match self.until {
|
||||||
Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
|
Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
|
||||||
Until::Glob(glob) => {
|
Until::Glob(glob) => {
|
||||||
walk.push(*self.from)?;
|
walk.push(self.from.into())?;
|
||||||
walk.push_glob(glob.as_str())?
|
walk.push_glob(glob.as_str())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +105,7 @@ impl<'a> Iterator for WalkIter<'a> {
|
||||||
// N.b. ensure that we start using the `from` commit and use the revwalk
|
// N.b. ensure that we start using the `from` commit and use the revwalk
|
||||||
// after that.
|
// after that.
|
||||||
if let Some(from) = self.from.take() {
|
if let Some(from) = self.from.take() {
|
||||||
return Some(self.repo.find_commit(*from));
|
return Some(self.repo.find_commit(from.into()));
|
||||||
}
|
}
|
||||||
let oid = self.inner.next()?;
|
let oid = self.inner.next()?;
|
||||||
Some(oid.and_then(|oid| self.repo.find_commit(oid)))
|
Some(oid.and_then(|oid| self.repo.find_commit(oid)))
|
||||||
|
|
@ -132,7 +134,7 @@ where
|
||||||
let commit = self.walk.next()?;
|
let commit = self.walk.next()?;
|
||||||
match commit {
|
match commit {
|
||||||
Ok(commit) => {
|
Ok(commit) => {
|
||||||
let entry = git::Oid::from(commit.id());
|
let entry = crate::git::Oid::from(commit.id());
|
||||||
// N.b. mark this commit as seen, so that it is not walked again
|
// N.b. mark this commit as seen, so that it is not walked again
|
||||||
self.walk.inner.hide(commit.id()).ok();
|
self.walk.inner.hide(commit.id()).ok();
|
||||||
// Skip any Op that do not match the manifest
|
// Skip any Op that do not match the manifest
|
||||||
|
|
@ -155,7 +157,7 @@ impl<'a, A> OpsIter<'a, A> {
|
||||||
|
|
||||||
/// Load the `Op` for the given `entry`, ensuring that manifest matches with
|
/// Load the `Op` for the given `entry`, ensuring that manifest matches with
|
||||||
/// the expected manifest.
|
/// the expected manifest.
|
||||||
fn load(&self, entry: git::Oid) -> Result<Option<Op<A>>, error::Ops>
|
fn load(&self, entry: crate::git::Oid) -> Result<Option<Op<A>>, error::Ops>
|
||||||
where
|
where
|
||||||
A: for<'de> Deserialize<'de>,
|
A: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,6 @@ use crate::cob::store::encoding;
|
||||||
use crate::cob::{patch, Title};
|
use crate::cob::{patch, Title};
|
||||||
use crate::cob::{Entry, History, Manifest, Timestamp, Version};
|
use crate::cob::{Entry, History, Manifest, Timestamp, Version};
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
use crate::git;
|
|
||||||
use crate::git::ext::author::Author;
|
|
||||||
use crate::git::ext::commit::headers::Headers;
|
|
||||||
use crate::git::ext::commit::{trailers::OwnedTrailer, Commit};
|
|
||||||
use crate::git::Oid;
|
use crate::git::Oid;
|
||||||
use crate::node::device::Device;
|
use crate::node::device::Device;
|
||||||
use crate::prelude::Did;
|
use crate::prelude::Did;
|
||||||
|
|
@ -101,7 +97,7 @@ where
|
||||||
self.history.merge(other.history);
|
self.history.merge(other.history);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid {
|
pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> crate::git::Oid {
|
||||||
let timestamp = self.time;
|
let timestamp = self.time;
|
||||||
let tips = self.tips();
|
let tips = self.tips();
|
||||||
let revision = arbitrary::oid();
|
let revision = arbitrary::oid();
|
||||||
|
|
@ -182,7 +178,8 @@ impl<G: Signer> Actor<G> {
|
||||||
"nonce": fastrand::u64(..),
|
"nonce": fastrand::u64(..),
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap();
|
let oid =
|
||||||
|
crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Blob, &data).unwrap();
|
||||||
let id = oid.into();
|
let id = oid.into();
|
||||||
let author = *self.signer.public_key();
|
let author = *self.signer.public_key();
|
||||||
let actions = NonEmpty::from_vec(actions).unwrap();
|
let actions = NonEmpty::from_vec(actions).unwrap();
|
||||||
|
|
@ -226,8 +223,8 @@ impl<G: Signer> Actor<G> {
|
||||||
&mut self,
|
&mut self,
|
||||||
title: Title,
|
title: Title,
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
base: git::Oid,
|
base: crate::git::Oid,
|
||||||
oid: git::Oid,
|
oid: crate::git::Oid,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Patch, patch::Error> {
|
) -> Result<Patch, patch::Error> {
|
||||||
Patch::from_root(
|
Patch::from_root(
|
||||||
|
|
@ -252,21 +249,26 @@ impl<G: Signer> Actor<G> {
|
||||||
///
|
///
|
||||||
/// Doesn't encode in the same way as we do in production, but attempts to include the same data
|
/// Doesn't encode in the same way as we do in production, but attempts to include the same data
|
||||||
/// that feeds into the hash entropy, so that changing any input will change the resulting oid.
|
/// that feeds into the hash entropy, so that changing any input will change the resulting oid.
|
||||||
pub fn encoded<T: Cob, G: Signer>(
|
fn encoded<T: Cob, G: Signer>(
|
||||||
action: &T::Action,
|
action: &T::Action,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
parents: impl IntoIterator<Item = Oid>,
|
parents: impl IntoIterator<Item = Oid>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> (Vec<u8>, git::ext::Oid) {
|
) -> (Vec<u8>, crate::git::Oid) {
|
||||||
|
use radicle_git_metadata::{
|
||||||
|
author::{Author, Time},
|
||||||
|
commit::{headers::Headers, trailers::OwnedTrailer, CommitData},
|
||||||
|
};
|
||||||
|
|
||||||
let data = encoding::encode(action).unwrap();
|
let data = encoding::encode(action).unwrap();
|
||||||
let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap();
|
let oid = crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Blob, &data).unwrap();
|
||||||
let parents = parents.into_iter().map(|o| *o);
|
let parents = parents.into_iter().map(|o| o.into());
|
||||||
let author = Author {
|
let author = Author {
|
||||||
name: "radicle".to_owned(),
|
name: "radicle".to_owned(),
|
||||||
email: signer.public_key().to_human(),
|
email: signer.public_key().to_human(),
|
||||||
time: git_ext::author::Time::new(timestamp.as_secs() as i64, 0),
|
time: Time::new(timestamp.as_secs() as i64, 0),
|
||||||
};
|
};
|
||||||
let commit = Commit::new::<_, _, OwnedTrailer>(
|
let commit = CommitData::<git2::Oid, git2::Oid>::new::<_, _, OwnedTrailer>(
|
||||||
oid,
|
oid,
|
||||||
parents,
|
parents,
|
||||||
author.clone(),
|
author.clone(),
|
||||||
|
|
@ -277,7 +279,9 @@ pub fn encoded<T: Cob, G: Signer>(
|
||||||
)
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let hash = git::raw::Oid::hash_object(git::raw::ObjectType::Commit, commit.as_bytes()).unwrap();
|
let hash =
|
||||||
|
crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Commit, commit.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
(data, hash.into())
|
(data, hash.into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,33 +5,24 @@ use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use git_ext::ref_format as format;
|
pub use radicle_oid::{str::ParseOidError, Oid};
|
||||||
|
|
||||||
|
pub extern crate radicle_git_ref_format as fmt;
|
||||||
|
|
||||||
use crate::collections::RandomMap;
|
use crate::collections::RandomMap;
|
||||||
use crate::crypto::PublicKey;
|
use crate::crypto::PublicKey;
|
||||||
use crate::node::Alias;
|
use crate::node::Alias;
|
||||||
use crate::rad;
|
use crate::rad;
|
||||||
use crate::storage;
|
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::Refs;
|
||||||
use crate::storage::RemoteId;
|
use crate::storage::RemoteId;
|
||||||
|
|
||||||
pub use ext::Error;
|
pub use crate::storage::git::transport::local::Url;
|
||||||
pub use ext::NotFound;
|
|
||||||
pub use ext::Oid;
|
|
||||||
pub use git_ext::ref_format as fmt;
|
|
||||||
pub use git_ext::ref_format::{
|
|
||||||
component, lit, name, qualified, refname, refspec,
|
|
||||||
refspec::{PatternStr, PatternString, Refspec},
|
|
||||||
Component, Namespaced, Qualified, RefStr, RefString,
|
|
||||||
};
|
|
||||||
pub use radicle_git_ext as ext;
|
|
||||||
pub use storage::git::transport::local::Url;
|
|
||||||
pub use storage::BranchName;
|
|
||||||
|
|
||||||
use raw::ErrorExt as _;
|
use raw::ErrorExt as _;
|
||||||
|
|
||||||
|
pub type BranchName = crate::git::fmt::RefString;
|
||||||
|
|
||||||
/// Default port of the `git` transport protocol.
|
/// Default port of the `git` transport protocol.
|
||||||
pub const PROTOCOL_PORT: u16 = 9418;
|
pub const PROTOCOL_PORT: u16 = 9418;
|
||||||
/// Minimum required git version.
|
/// Minimum required git version.
|
||||||
|
|
@ -160,16 +151,16 @@ pub enum RefError {
|
||||||
#[error("ref name is not valid UTF-8")]
|
#[error("ref name is not valid UTF-8")]
|
||||||
InvalidName,
|
InvalidName,
|
||||||
#[error("unexpected unqualified ref: {0}")]
|
#[error("unexpected unqualified ref: {0}")]
|
||||||
Unqualified(RefString),
|
Unqualified(fmt::RefString),
|
||||||
#[error("invalid ref format: {0}")]
|
#[error("invalid ref format: {0}")]
|
||||||
Format(#[from] format::Error),
|
Format(#[from] fmt::Error),
|
||||||
#[error("reference has no target")]
|
#[error("reference has no target")]
|
||||||
NoTarget,
|
NoTarget,
|
||||||
#[error("expected ref to begin with 'refs/namespaces' but found '{0}'")]
|
#[error("expected ref to begin with 'refs/namespaces' but found '{0}'")]
|
||||||
MissingNamespace(format::RefString),
|
MissingNamespace(fmt::RefString),
|
||||||
#[error("ref name contains invalid namespace identifier '{name}'")]
|
#[error("ref name contains invalid namespace identifier '{name}'")]
|
||||||
InvalidNamespace {
|
InvalidNamespace {
|
||||||
name: format::RefString,
|
name: fmt::RefString,
|
||||||
#[source]
|
#[source]
|
||||||
err: Box<dyn std::error::Error + Send + Sync + 'static>,
|
err: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||||
},
|
},
|
||||||
|
|
@ -186,9 +177,13 @@ pub enum ListRefsError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod refs {
|
pub mod refs {
|
||||||
use super::*;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use radicle_cob as cob;
|
use radicle_cob as cob;
|
||||||
|
|
||||||
|
use super::fmt::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Try to get a qualified reference from a generic reference.
|
/// Try to get a qualified reference from a generic reference.
|
||||||
pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> {
|
pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> {
|
||||||
let name = r.name().ok_or(RefError::InvalidName)?;
|
let name = r.name().ok_or(RefError::InvalidName)?;
|
||||||
|
|
@ -214,35 +209,28 @@ pub mod refs {
|
||||||
///
|
///
|
||||||
pub fn patch<'a>(object_id: &cob::ObjectId) -> Qualified<'a> {
|
pub fn patch<'a>(object_id: &cob::ObjectId) -> Qualified<'a> {
|
||||||
Qualified::from_components(
|
Qualified::from_components(
|
||||||
name::component!("heads"),
|
component!("heads"),
|
||||||
name::component!("patches"),
|
component!("patches"),
|
||||||
Some(object_id.into()),
|
Some(object_id.into()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod storage {
|
pub mod storage {
|
||||||
use format::{
|
|
||||||
lit,
|
|
||||||
name::component,
|
|
||||||
refspec::{self, PatternString},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Where the repo's identity document is stored.
|
/// Where the repo's identity document is stored.
|
||||||
///
|
///
|
||||||
/// `refs/rad/id`
|
/// `refs/rad/id`
|
||||||
///
|
///
|
||||||
pub static IDENTITY_BRANCH: LazyLock<Qualified> = LazyLock::new(|| {
|
pub static IDENTITY_BRANCH: LazyLock<Qualified> =
|
||||||
Qualified::from_components(name::component!("rad"), name::component!("id"), None)
|
LazyLock::new(|| Qualified::from_components(component!("rad"), component!("id"), None));
|
||||||
});
|
|
||||||
|
|
||||||
/// Where the repo's identity root document is stored.
|
/// Where the repo's identity root document is stored.
|
||||||
///
|
///
|
||||||
/// `refs/rad/root`
|
/// `refs/rad/root`
|
||||||
///
|
///
|
||||||
pub static IDENTITY_ROOT: LazyLock<Qualified> = LazyLock::new(|| {
|
pub static IDENTITY_ROOT: LazyLock<Qualified> = LazyLock::new(|| {
|
||||||
Qualified::from_components(name::component!("rad"), name::component!("root"), None)
|
Qualified::from_components(component!("rad"), component!("root"), None)
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Where the project's signed references are stored.
|
/// Where the project's signed references are stored.
|
||||||
|
|
@ -250,7 +238,7 @@ pub mod refs {
|
||||||
/// `refs/rad/sigrefs`
|
/// `refs/rad/sigrefs`
|
||||||
///
|
///
|
||||||
pub static SIGREFS_BRANCH: LazyLock<Qualified> = LazyLock::new(|| {
|
pub static SIGREFS_BRANCH: LazyLock<Qualified> = LazyLock::new(|| {
|
||||||
Qualified::from_components(name::component!("rad"), name::component!("sigrefs"), None)
|
Qualified::from_components(component!("rad"), component!("sigrefs"), None)
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The set of special references used in the Heartwood protocol.
|
/// The set of special references used in the Heartwood protocol.
|
||||||
|
|
@ -341,8 +329,8 @@ pub mod refs {
|
||||||
///
|
///
|
||||||
/// `refs/namespaces/*/refs/cobs/<typename>/<object_id>`
|
/// `refs/namespaces/*/refs/cobs/<typename>/<object_id>`
|
||||||
///
|
///
|
||||||
pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString {
|
pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> refspec::PatternString {
|
||||||
refspec::pattern!("refs/namespaces/*")
|
pattern!("refs/namespaces/*")
|
||||||
.join(refname!("refs/cobs"))
|
.join(refname!("refs/cobs"))
|
||||||
.join(Component::from(typename))
|
.join(Component::from(typename))
|
||||||
.join(Component::from(object_id))
|
.join(Component::from(object_id))
|
||||||
|
|
@ -390,8 +378,11 @@ pub mod refs {
|
||||||
///
|
///
|
||||||
/// `refs/namespaces/*/refs/drafts/cobs/<typename>/<object_id>`
|
/// `refs/namespaces/*/refs/drafts/cobs/<typename>/<object_id>`
|
||||||
///
|
///
|
||||||
pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString {
|
pub fn cobs(
|
||||||
refspec::pattern!("refs/namespaces/*")
|
typename: &cob::TypeName,
|
||||||
|
object_id: &cob::ObjectId,
|
||||||
|
) -> refspec::PatternString {
|
||||||
|
pattern!("refs/namespaces/*")
|
||||||
.join(refname!("refs/drafts/cobs"))
|
.join(refname!("refs/drafts/cobs"))
|
||||||
.join(Component::from(typename))
|
.join(Component::from(typename))
|
||||||
.join(Component::from(object_id))
|
.join(Component::from(object_id))
|
||||||
|
|
@ -423,7 +414,6 @@ pub mod refs {
|
||||||
|
|
||||||
pub mod workdir {
|
pub mod workdir {
|
||||||
use super::*;
|
use super::*;
|
||||||
use format::name::component;
|
|
||||||
|
|
||||||
/// Create a [`RefString`] that corresponds to `refs/heads/<branch>`.
|
/// Create a [`RefString`] that corresponds to `refs/heads/<branch>`.
|
||||||
pub fn branch(branch: &RefStr) -> RefString {
|
pub fn branch(branch: &RefStr) -> RefString {
|
||||||
|
|
@ -483,7 +473,7 @@ pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError
|
||||||
Ok(remotes)
|
Ok(remotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a [`format::Qualified`] reference string while expecting the reference
|
/// Parse a [`fmt::Qualified`] reference string while expecting the reference
|
||||||
/// to start with `refs/namespaces`. If the namespace is not present, then an
|
/// to start with `refs/namespaces`. If the namespace is not present, then an
|
||||||
/// error will be returned.
|
/// error will be returned.
|
||||||
///
|
///
|
||||||
|
|
@ -498,7 +488,7 @@ pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError
|
||||||
/// The `T` can be specified when calling the function. For example, if you
|
/// The `T` can be specified when calling the function. For example, if you
|
||||||
/// wanted to parse the namespace as a `PublicKey`, then you would the function
|
/// wanted to parse the namespace as a `PublicKey`, then you would the function
|
||||||
/// like so, `parse_ref_namespaced::<PublicKey>(s)`.
|
/// like so, `parse_ref_namespaced::<PublicKey>(s)`.
|
||||||
pub fn parse_ref_namespaced<T>(s: &str) -> Result<(T, format::Qualified), RefError>
|
pub fn parse_ref_namespaced<T>(s: &str) -> Result<(T, fmt::Qualified), RefError>
|
||||||
where
|
where
|
||||||
T: FromStr,
|
T: FromStr,
|
||||||
T::Err: std::error::Error + Send + Sync + 'static,
|
T::Err: std::error::Error + Send + Sync + 'static,
|
||||||
|
|
@ -510,7 +500,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a [`format::Qualified`] reference string. It will optionally return
|
/// Parse a [`fmt::Qualified`] reference string. It will optionally return
|
||||||
/// the namespace, if present.
|
/// the namespace, if present.
|
||||||
///
|
///
|
||||||
/// The qualified form could be of the form: `refs/heads/main`,
|
/// The qualified form could be of the form: `refs/heads/main`,
|
||||||
|
|
@ -527,15 +517,15 @@ where
|
||||||
/// The `T` can be specified when calling the function. For example, if you
|
/// The `T` can be specified when calling the function. For example, if you
|
||||||
/// wanted to parse the namespace as a `PublicKey`, then you would the function
|
/// wanted to parse the namespace as a `PublicKey`, then you would the function
|
||||||
/// like so, `parse_ref::<PublicKey>(s)`.
|
/// like so, `parse_ref::<PublicKey>(s)`.
|
||||||
pub fn parse_ref<T>(s: &str) -> Result<(Option<T>, format::Qualified), RefError>
|
pub fn parse_ref<T>(s: &str) -> Result<(Option<T>, fmt::Qualified), RefError>
|
||||||
where
|
where
|
||||||
T: FromStr,
|
T: FromStr,
|
||||||
T::Err: std::error::Error + Send + Sync + 'static,
|
T::Err: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let input = format::RefStr::try_from_str(s)?;
|
let input = fmt::RefStr::try_from_str(s)?;
|
||||||
match input.to_namespaced() {
|
match input.to_namespaced() {
|
||||||
None => {
|
None => {
|
||||||
let refname = Qualified::from_refstr(input)
|
let refname = fmt::Qualified::from_refstr(input)
|
||||||
.ok_or_else(|| RefError::Unqualified(input.to_owned()))?;
|
.ok_or_else(|| RefError::Unqualified(input.to_owned()))?;
|
||||||
|
|
||||||
Ok((None, refname))
|
Ok((None, refname))
|
||||||
|
|
@ -573,7 +563,7 @@ pub fn initial_commit<'a>(
|
||||||
pub fn commit<'a>(
|
pub fn commit<'a>(
|
||||||
repo: &'a raw::Repository,
|
repo: &'a raw::Repository,
|
||||||
parent: &'a raw::Commit,
|
parent: &'a raw::Commit,
|
||||||
target: &RefStr,
|
target: &fmt::RefStr,
|
||||||
message: &str,
|
message: &str,
|
||||||
sig: &raw::Signature,
|
sig: &raw::Signature,
|
||||||
tree: &raw::Tree,
|
tree: &raw::Tree,
|
||||||
|
|
@ -588,7 +578,7 @@ pub fn commit<'a>(
|
||||||
pub fn empty_commit<'a>(
|
pub fn empty_commit<'a>(
|
||||||
repo: &'a raw::Repository,
|
repo: &'a raw::Repository,
|
||||||
parent: &'a raw::Commit,
|
parent: &'a raw::Commit,
|
||||||
target: &RefStr,
|
target: &fmt::RefStr,
|
||||||
message: &str,
|
message: &str,
|
||||||
sig: &raw::Signature,
|
sig: &raw::Signature,
|
||||||
) -> Result<raw::Commit<'a>, raw::Error> {
|
) -> Result<raw::Commit<'a>, raw::Error> {
|
||||||
|
|
@ -611,7 +601,7 @@ pub fn write_tree<'r>(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
repo: &'r raw::Repository,
|
repo: &'r raw::Repository,
|
||||||
) -> Result<raw::Tree<'r>, Error> {
|
) -> Result<raw::Tree<'r>, raw::Error> {
|
||||||
let blob_id = repo.blob(bytes)?;
|
let blob_id = repo.blob(bytes)?;
|
||||||
let mut builder = repo.treebuilder(None)?;
|
let mut builder = repo.treebuilder(None)?;
|
||||||
builder.insert(path, blob_id, 0o100_644)?;
|
builder.insert(path, blob_id, 0o100_644)?;
|
||||||
|
|
@ -697,7 +687,7 @@ pub fn fetch(repo: &raw::Repository, remote: &str) -> Result<(), raw::Error> {
|
||||||
pub fn push<'a>(
|
pub fn push<'a>(
|
||||||
repo: &raw::Repository,
|
repo: &raw::Repository,
|
||||||
remote: &str,
|
remote: &str,
|
||||||
refspecs: impl IntoIterator<Item = (&'a Qualified<'a>, &'a Qualified<'a>)>,
|
refspecs: impl IntoIterator<Item = (&'a fmt::Qualified<'a>, &'a fmt::Qualified<'a>)>,
|
||||||
) -> Result<(), raw::Error> {
|
) -> Result<(), raw::Error> {
|
||||||
let refspecs = refspecs
|
let refspecs = refspecs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,12 @@ use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
use git_ext::ref_format::Namespaced;
|
use crate::git::fmt::Namespaced;
|
||||||
|
|
||||||
use crate::prelude::Did;
|
use crate::prelude::Did;
|
||||||
|
|
||||||
use super::{Oid, Qualified};
|
use super::fmt::Qualified;
|
||||||
|
use crate::git::Oid;
|
||||||
|
|
||||||
/// A marker for the initial state of [`Canonical`], after construction using
|
/// A marker for the initial state of [`Canonical`], after construction using
|
||||||
/// [`Canonical::new`].
|
/// [`Canonical::new`].
|
||||||
|
|
@ -510,20 +511,19 @@ mod tests {
|
||||||
|
|
||||||
/// Test helper to construct a Canonical and get the quorum
|
/// Test helper to construct a Canonical and get the quorum
|
||||||
fn quorum(
|
fn quorum(
|
||||||
heads: &[git::raw::Oid],
|
heads: &[crate::git::Oid],
|
||||||
threshold: usize,
|
threshold: usize,
|
||||||
repo: &git::raw::Repository,
|
repo: &crate::git::raw::Repository,
|
||||||
) -> Result<Oid, QuorumError> {
|
) -> Result<Oid, QuorumError> {
|
||||||
let refname =
|
let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap());
|
||||||
git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap());
|
|
||||||
|
|
||||||
let mut delegates = Vec::new();
|
let mut delegates = Vec::new();
|
||||||
for (i, head) in heads.iter().enumerate() {
|
for (i, head) in heads.iter().enumerate() {
|
||||||
let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
|
let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
|
||||||
let did = Did::from(signer.public_key());
|
let did = Did::from(signer.public_key());
|
||||||
delegates.push(did);
|
delegates.push(did);
|
||||||
let ns = git::Component::from(signer.public_key());
|
let ns = git::fmt::Component::from(signer.public_key());
|
||||||
repo.reference(refname.with_namespace(ns).as_str(), *head, true, "")
|
repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,18 +557,18 @@ mod tests {
|
||||||
fn test_quorum_properties() {
|
fn test_quorum_properties() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let (repo, c0) = fixtures::repository(tmp.path());
|
let (repo, c0) = fixtures::repository(tmp.path());
|
||||||
let c0: git::Oid = c0.into();
|
let c0: crate::git::Oid = c0.into();
|
||||||
let a1 = fixtures::commit("A1", &[*c0], &repo);
|
let a1 = fixtures::commit("A1", &[c0.into()], &repo);
|
||||||
let a2 = fixtures::commit("A2", &[*a1], &repo);
|
let a2 = fixtures::commit("A2", &[a1.into()], &repo);
|
||||||
let d1 = fixtures::commit("D1", &[*c0], &repo);
|
let d1 = fixtures::commit("D1", &[c0.into()], &repo);
|
||||||
let c1 = fixtures::commit("C1", &[*c0], &repo);
|
let c1 = fixtures::commit("C1", &[c0.into()], &repo);
|
||||||
let c2 = fixtures::commit("C2", &[*c1], &repo);
|
let c2 = fixtures::commit("C2", &[c1.into()], &repo);
|
||||||
let b2 = fixtures::commit("B2", &[*c1], &repo);
|
let b2 = fixtures::commit("B2", &[c1.into()], &repo);
|
||||||
let a1 = fixtures::commit("A1", &[*c0], &repo);
|
let a1 = fixtures::commit("A1", &[c0.into()], &repo);
|
||||||
let m1 = fixtures::commit("M1", &[*c2, *b2], &repo);
|
let m1 = fixtures::commit("M1", &[c2.into(), b2.into()], &repo);
|
||||||
let m2 = fixtures::commit("M2", &[*a1, *b2], &repo);
|
let m2 = fixtures::commit("M2", &[a1.into(), b2.into()], &repo);
|
||||||
let mut rng = fastrand::Rng::new();
|
let mut rng = fastrand::Rng::new();
|
||||||
let choices = [*c0, *c1, *c2, *b2, *a1, *a2, *d1, *m1, *m2];
|
let choices = [c0, c1, c2, b2, a1, a2, d1, m1, m2];
|
||||||
|
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let count = rng.usize(1..=choices.len());
|
let count = rng.usize(1..=choices.len());
|
||||||
|
|
@ -591,11 +591,10 @@ mod tests {
|
||||||
fn test_quorum_different_types() {
|
fn test_quorum_different_types() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let (repo, c0) = fixtures::repository(tmp.path());
|
let (repo, c0) = fixtures::repository(tmp.path());
|
||||||
let c0: git::Oid = c0.into();
|
let t0 = fixtures::tag("v1", "", c0, &repo);
|
||||||
let t0 = fixtures::tag("v1", "", *c0, &repo);
|
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
quorum(&[*c0, *t0], 1, &repo),
|
quorum(&[c0.into(), t0], 1, &repo),
|
||||||
Err(QuorumError::DifferentTypes { .. })
|
Err(QuorumError::DifferentTypes { .. })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
|
use crate::git::fmt::Qualified;
|
||||||
use crate::git::raw::ErrorExt as _;
|
use crate::git::raw::ErrorExt as _;
|
||||||
use crate::git::{Oid, Qualified};
|
use crate::git::Oid;
|
||||||
use crate::prelude::Did;
|
use crate::prelude::Did;
|
||||||
|
|
||||||
use super::{FoundObjects, GraphAheadBehind, MergeBase, Object};
|
use super::{FoundObjects, GraphAheadBehind, MergeBase, Object};
|
||||||
|
|
@ -40,7 +41,7 @@ pub enum FindObjectsError {
|
||||||
},
|
},
|
||||||
#[error("failed to find reference {refname} due to: {source}")]
|
#[error("failed to find reference {refname} due to: {source}")]
|
||||||
FindReference {
|
FindReference {
|
||||||
refname: git::Namespaced<'static>,
|
refname: git::fmt::Namespaced<'static>,
|
||||||
source: Box<dyn std::error::Error + Send + Sync + 'static>,
|
source: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||||
},
|
},
|
||||||
#[error("failed to find objects")]
|
#[error("failed to find objects")]
|
||||||
|
|
@ -60,7 +61,7 @@ impl FindObjectsError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_reference<E>(refname: git::Namespaced<'static>, err: E) -> Self
|
pub fn find_reference<E>(refname: git::fmt::Namespaced<'static>, err: E) -> Self
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
|
@ -170,7 +171,7 @@ pub struct GraphDescendant {
|
||||||
|
|
||||||
impl FindMergeBase for git::raw::Repository {
|
impl FindMergeBase for git::raw::Repository {
|
||||||
fn merge_base(&self, a: Oid, b: Oid) -> Result<MergeBase, MergeBaseError> {
|
fn merge_base(&self, a: Oid, b: Oid) -> Result<MergeBase, MergeBaseError> {
|
||||||
self.merge_base(*a, *b)
|
self.merge_base(a.into(), b.into())
|
||||||
.map_err(|err| MergeBaseError {
|
.map_err(|err| MergeBaseError {
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
|
|
@ -190,7 +191,7 @@ impl Ancestry for git::raw::Repository {
|
||||||
commit: Oid,
|
commit: Oid,
|
||||||
upstream: Oid,
|
upstream: Oid,
|
||||||
) -> Result<GraphAheadBehind, GraphDescendant> {
|
) -> Result<GraphAheadBehind, GraphDescendant> {
|
||||||
self.graph_ahead_behind(*commit, *upstream)
|
self.graph_ahead_behind(commit.into(), upstream.into())
|
||||||
.map_err(|err| GraphDescendant {
|
.map_err(|err| GraphDescendant {
|
||||||
commit,
|
commit,
|
||||||
upstream,
|
upstream,
|
||||||
|
|
@ -228,7 +229,7 @@ impl FindObjects for git::raw::Repository {
|
||||||
log::warn!(target: "radicle", "Missing target for reference `{name}`");
|
log::warn!(target: "radicle", "Missing target for reference `{name}`");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let object = match self.find_object(*oid, None) {
|
let object = match self.find_object(oid.into(), None) {
|
||||||
Ok(object) => Object::new(&object).ok_or_else(|| {
|
Ok(object) => Object::new(&object).ok_or_else(|| {
|
||||||
FindObjectsError::invalid_object_type(
|
FindObjectsError::invalid_object_type(
|
||||||
*did,
|
*did,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue