rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwoodefe10f95be9fde1cda84f0631ec8f6546ac7228d
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "1cd8044782249b95d577e0678f10eba8c6f32e35",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"title": "A Little Less `git2`",
"state": {
"status": "merged",
"conflicts": []
},
"before": "292befdb36b97e48c5112dbdd6565dd6316969ed",
"after": "efe10f95be9fde1cda84f0631ec8f6546ac7228d",
"commits": [
"efe10f95be9fde1cda84f0631ec8f6546ac7228d",
"b7cfcfff7597a2f3f6cf9f30a348c923f11abf84",
"f4a8908727515ef857e620c0090e8186f196529a",
"793f53b5f0fe2671e57032160faa6806d50f3371",
"3fcf2a86da18fc958d79fd8f435e7c7edcbd2c58"
],
"target": "3fc92f83fe089263c470b328414d923227cf0e4a",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1cd8044782249b95d577e0678f10eba8c6f32e35",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "(I should write up a lengthy description\u2026)",
"base": "5caa7b302a7d4f247aaa7cc688a692aac4486464",
"oid": "d0b815c1715dfd78a7aba632356513370d189eaa",
"timestamp": 1759707360
},
{
"id": "8a36f8eecf18c0e990125564da0f89bbbbb00aa6",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Make `git2` an *optional* dependency of `radicle-cob`, *no* dependency\n(also not transitively) of `radicle-crypto`, and refactor `radicle` to\ndepend on crates that in turn do not depend on `git2`\n*non-optionally*.\n\n### New Crates\n\n`radicle-git-metadata`, `radicle-git-ref-format`, and `radicle-oid` are\nintroduced. Refer to their documentation in the respective `lib.rs`.\n\n### Refactoring\n\nThe main offending dependency of `radicle-cob` is `radicle-git-ext`\nfrom the `radicle-git` workspace in repository\n(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on\n`git2`.\n\nSo, to achieve removal of this dependency:\n\n 1. The crate is refactored to depend on the new crates\n `radicle-git-ref-format` `radicle-git-metadata`, and\n `radicle-oid` introduced in the previous commits, instead of\n `radicle-git-ext`.\n 2. Some code from the `radicle-git-ext` crate in the `radicle-git`\n workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is\n copied. See `crates/radicle-cob/src/backend/git/commit.rs`.\n\nThis cascades to `radicle` and its dependents.\n\n Firstly, the there is an\n`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made\nit very convenient to just deref to obtain the wrapped `git2::Oid`,\nso there are many expressions of the shape `*oid` in `radicle` and\nits dependents. However `radicle-oid` does not provide\n`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,\n`Target` is an associated type, and not a type parameter, so an\nimplementation of `Deref` would tie the new `Oid` too tightly to a\nparticular implementation.\nInstead, work with `impl From<radicle_oid::Oid> for git2::Oid`\n(which can be enabled using the feature flag `radicle-oid/git2`).\nThis explains the changes from `*oid` to `oid.into()` at every\ntransition to \"`git2` land\".\n\n Secondly, `radicle` and its dependents are refactored to also depend\non `radicle-git-ref-format` and (much less prominently)\n`radicle-git-metadata` instead of `radicle-git-ext`\nThis is to avoid pulling in `git2` via these dependencies.\n\n Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,\nthey are at the same time also cleaned up. Notably, the types from\n`radicle-git-ref-format` are re-exported under `fmt` only, not \"twice\".\nAlso, instead of re-exporting all of `git2`, the module `raw` actually\nonly provides the re-exports required to build the workspace\n(potentially breaking dependents outside the workspace, but given just\nhow many types are re-exported, this seems unlikely) and injects an\nextension trait for more ergonomic error handling. Uses of\n`radicle_git_ext::Error` are replaced with uses of the extension trait.\n\nWhile overall this obviously is very much a breaking change, these\nchanges should mostly amount to changing from `Deref` to `Into`, i.e.,\n`*oid` \u2192 `oid.into()`, importing the extension trait as necessary, and\nrewriting imports for `radicle::git::fmt`. This is indicated by the\nmostly mechanical nature of the changes to\n`crates/radicle-{cli,node,remote-helper}`.",
"base": "2e77d5ef4df526a8453d38d6d2d735fe9ce7e423",
"oid": "7fe1584106be32b9f2c5c153bd0d6052016dd4eb",
"timestamp": 1759877418
},
{
"id": "8c293dee6c08d53311abff02f3af729e9103cb6f",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase onto spun out patches:\n - `222bc576b7ffb7b9650be00525a1b66087c83b44`\n - `a1b9d9c92737f12b5b24405042e2432cc9730a5e`",
"base": "ac572e64e5029cb1db16f05158915b6ef55ab7e7",
"oid": "435a75304663802c85d7083ffb35a8d78eb3ab65",
"timestamp": 1760045529
},
{
"id": "8a2ca52af6004ddee5fcce8802f22a7d79f1f83d",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase",
"base": "633059040cf1aae705172e7dac24c1fd863d44e9",
"oid": "1d81f96ffbe3ea878181df42a3b699defc687e0d",
"timestamp": 1760089036
},
{
"id": "01334a94976a3191e59fba481c17abd724ccf3ba",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Linearize",
"base": "633059040cf1aae705172e7dac24c1fd863d44e9",
"oid": "3e505578b948a7541d0d88e286c0a8f1a953a115",
"timestamp": 1760089170
},
{
"id": "35ede635a75beb42ffcb629344b0e2b8de31b6b0",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Edit commit messages",
"base": "ea562215eaa163568c3f1061e4cbbd907e9913db",
"oid": "a1bece58c85c29d948e25abfb851814924bd9141",
"timestamp": 1760090217
},
{
"id": "af50a86347e8f3cc13d5f4d03142ba9523d7aa75",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "REVIEW",
"base": "ea562215eaa163568c3f1061e4cbbd907e9913db",
"oid": "0db6602c1bc79c30d3351e43384d37f33e7b1bef",
"timestamp": 1760099394
},
{
"id": "deb3610cffa58908132e39c3c42324bdbf4023b9",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Work in Fintan's feedback, clean up, rebase on top of test fixes.",
"base": "bb68fdad2307a3768197d56cfcfeafa95c79e78b",
"oid": "c324810fa3cb49710eccb22db2f5c17754be69cf",
"timestamp": 1760206797
},
{
"id": "ae9d102a7930c1551d4f602d3dfb85f776d82e25",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase, small fixes to `radicle-oid`.",
"base": "3e98589a767d9a17a3da90e52ae4abb198fa9ada",
"oid": "0469700d83ec46eaccede2d30d36da924a7c0e3d",
"timestamp": 1760351277
},
{
"id": "e3b938904c9d624bbd005dfa33f1faed62007094",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase",
"base": "292befdb36b97e48c5112dbdd6565dd6316969ed",
"oid": "2958823742b2bb5ec2afead2c71352ac0d56b3c2",
"timestamp": 1760355334
},
{
"id": "1707b5d159e409e1c8f4b2f869710e9c67300db6",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fix wording.",
"base": "292befdb36b97e48c5112dbdd6565dd6316969ed",
"oid": "ec0781bd63e0131b2cfdbac5b8c2af666a008315",
"timestamp": 1760360445
},
{
"id": "bc04eddfe4e476cfe19987a9854096186f5595cf",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fix doc-comments.",
"base": "292befdb36b97e48c5112dbdd6565dd6316969ed",
"oid": "efe10f95be9fde1cda84f0631ec8f6546ac7228d",
"timestamp": 1760364911
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "5c0c2ed0-094e-47af-94b9-5059541936f0"
},
"info_url": "https://cci.rad.levitte.org//5c0c2ed0-094e-47af-94b9-5059541936f0.html"
}
Started at: 2026-09-08 08:59:23.032214+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/5c0c2ed0-094e-47af-94b9-5059541936f0/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 38 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 1cd8044782249b95d577e0678f10eba8c6f32e35
✓ Switched to branch patch/1cd8044 at revision bc04edd
✓ Branch patch/1cd8044 setup to track rad/patches/1cd8044782249b95d577e0678f10eba8c6f32e35
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout efe10f95be9fde1cda84f0631ec8f6546ac7228d
HEAD is now at efe10f95 workspace: Enable vendored `libgit2`
Exit code: 0
$ rad patch show 1cd8044782249b95d577e0678f10eba8c6f32e35 -p
╭──────────────────────────────────────────────────────────────────────────────────╮
│ Title A Little Less `git2` │
│ Patch 1cd8044782249b95d577e0678f10eba8c6f32e35 │
│ Author lorenz z6MkkPv…WX5sTEz │
│ Head efe10f95be9fde1cda84f0631ec8f6546ac7228d │
│ Base 292befdb36b97e48c5112dbdd6565dd6316969ed │
│ Target master │
│ Branches patch/1cd8044 │
│ Commits ahead 0, behind 715 │
│ Status merged │
│ │
│ (I should write up a lengthy description…) │
├──────────────────────────────────────────────────────────────────────────────────┤
│ efe10f9 workspace: Enable vendored `libgit2` │
│ b7cfcff workspace: A Little Less `git2` │
│ f4a8908 git-metadata: New crate extract from `radicle-git` │
│ 793f53b oid: New crate │
│ 3fcf2a8 git-ref-format: New crate │
├──────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 1cd8044 @ 5caa7b3..d0b815c by lorenz z6MkkPv…WX5sTEz 11 months ago │
│ ↑ Revision 8a36f8e @ 2e77d5e..7fe1584 by lorenz z6MkkPv…WX5sTEz 11 months ago │
│ ↑ Revision 8c293de @ ac572e6..435a753 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision 8a2ca52 @ 6330590..1d81f96 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision 01334a9 @ 6330590..3e50557 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision 35ede63 @ ea56221..a1bece5 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision af50a86 @ ea56221..0db6602 by fintohaps z6Mkire…SQZ3voM 10 months ago │
│ ↑ Revision deb3610 @ bb68fda..c324810 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision ae9d102 @ 3e98589..0469700 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision e3b9389 @ 292befd..2958823 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision 1707b5d @ 292befd..ec0781b by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ ↑ Revision bc04edd @ 292befd..efe10f9 by lorenz z6MkkPv…WX5sTEz 10 months ago │
│ └─ ✓ merged by fintohaps z6Mkire…SQZ3voM │
╰──────────────────────────────────────────────────────────────────────────────────╯
commit efe10f95be9fde1cda84f0631ec8f6546ac7228d
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sun Oct 5 14:10:45 2025 +0200
workspace: Enable vendored `libgit2`
Multiple crates are enabling this feature. Do that at the workspace
level.
diff --git a/Cargo.toml b/Cargo.toml
index 3e910b574..f8a23539f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,7 +28,7 @@ crossbeam-channel = "0.5.6"
cyphernet = "0.5.2"
dunce = "1.0.5"
fastrand = { version = "2.0.0", default-features = false }
-git2 = { version = "0.19.0", default-features = false }
+git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] }
human-panic = "2"
itertools = "0.14"
lexopt = "0.3.0"
diff --git a/crates/radicle-cob/Cargo.toml b/crates/radicle-cob/Cargo.toml
index a598acb5c..59c216e58 100644
--- a/crates/radicle-cob/Cargo.toml
+++ b/crates/radicle-cob/Cargo.toml
@@ -22,7 +22,7 @@ test = []
[dependencies]
fastrand = { workspace = true }
git-ref-format-core = { workspace = true }
-git2 = { workspace = true, optional = true, features = ["vendored-libgit2"] }
+git2 = { workspace = true, optional = true }
log = { workspace = true }
nonempty = { workspace = true, features = ["serialize"] }
radicle-crypto = { workspace = true, features = ["ssh"] }
diff --git a/crates/radicle-term/Cargo.toml b/crates/radicle-term/Cargo.toml
index 2d5ddc286..512e43583 100644
--- a/crates/radicle-term/Cargo.toml
+++ b/crates/radicle-term/Cargo.toml
@@ -24,7 +24,7 @@ thiserror = { workspace = true }
unicode-display-width = "0.3.0"
unicode-segmentation = "1.7.1"
zeroize = { workspace = true }
-git2 = { workspace = true, features = ["vendored-libgit2"], optional = true }
+git2 = { workspace = true, optional = true }
shlex = { workspace = true }
[target.'cfg(unix)'.dependencies]
commit b7cfcfff7597a2f3f6cf9f30a348c923f11abf84
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sat Oct 4 11:59:26 2025 +0200
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::git::fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
diff --git a/Cargo.lock b/Cargo.lock
index 237f85e6c..3c3eb9fda 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -239,12 +239,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
-[[package]]
-name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
[[package]]
name = "base64"
version = "0.21.7"
@@ -1119,9 +1113,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git-ref-format"
-version = "0.3.1"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1"
+checksum = "76314f6eb43910ebc5eb89a1f0728724a6b9144dabd18bca4e7cc7c01c3804e3"
dependencies = [
"git-ref-format-core",
"git-ref-format-macro",
@@ -1129,9 +1123,9 @@ dependencies = [
[[package]]
name = "git-ref-format-core"
-version = "0.3.1"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
+checksum = "ae6d9ee666ca7d4ad49cbf7174f785f299a18b37565694f665e8c7df24999cdd"
dependencies = [
"bstr",
"serde",
@@ -1140,12 +1134,12 @@ dependencies = [
[[package]]
name = "git-ref-format-macro"
-version = "0.3.1"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4"
+checksum = "2dc2ded12a6ea2b6e63afaf09415e4c15bf4baa74e530acd9daed9ff47e7dd41"
dependencies = [
"git-ref-format-core",
- "proc-macro-error",
+ "proc-macro-error2",
"quote",
"syn 2.0.89",
]
@@ -2238,12 +2232,6 @@ dependencies = [
"cyphergraphy",
]
-[[package]]
-name = "nonempty"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109"
-
[[package]]
name = "nonempty"
version = "0.9.0"
@@ -2643,27 +2631,25 @@ dependencies = [
]
[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "proc-macro-error-attr2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
- "proc-macro-error-attr",
"proc-macro2",
"quote",
- "syn 1.0.109",
- "version_check",
]
[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+name = "proc-macro-error2"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
+ "proc-macro-error-attr2",
"proc-macro2",
"quote",
- "version_check",
+ "syn 2.0.89",
]
[[package]]
@@ -2751,13 +2737,15 @@ dependencies = [
"localtime",
"log",
"multibase",
- "nonempty 0.9.0",
+ "nonempty",
"pretty_assertions",
"qcheck",
"qcheck-macros",
"radicle-cob",
"radicle-crypto",
- "radicle-git-ext",
+ "radicle-git-metadata",
+ "radicle-git-ref-format",
+ "radicle-oid",
"radicle-ssh",
"schemars",
"serde",
@@ -2779,19 +2767,18 @@ dependencies = [
"chrono",
"clap",
"dunce",
- "git-ref-format",
"human-panic",
"itertools",
"lexopt",
"localtime",
"log",
- "nonempty 0.9.0",
+ "nonempty",
"pretty_assertions",
"radicle",
"radicle-cli-test",
"radicle-cob",
"radicle-crypto",
- "radicle-git-ext",
+ "radicle-git-ref-format",
"radicle-node",
"radicle-surf",
"radicle-term",
@@ -2837,14 +2824,17 @@ name = "radicle-cob"
version = "0.17.0"
dependencies = [
"fastrand",
+ "git-ref-format-core",
"git2",
"log",
- "nonempty 0.9.0",
+ "nonempty",
"qcheck",
"qcheck-macros",
"radicle-crypto",
"radicle-dag",
- "radicle-git-ext",
+ "radicle-git-metadata",
+ "radicle-git-ref-format",
+ "radicle-oid",
"serde",
"serde_json",
"signature 2.2.0",
@@ -2894,17 +2884,18 @@ dependencies = [
"gix-protocol",
"gix-transport",
"log",
- "nonempty 0.9.0",
+ "nonempty",
"radicle",
- "radicle-git-ext",
+ "radicle-git-ref-format",
+ "radicle-oid",
"thiserror 1.0.69",
]
[[package]]
name = "radicle-git-ext"
-version = "0.8.1"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650"
+checksum = "fb3de6999a8ff570e0dd92a04ba3132853dd08a3bcfc2f7faf56de7b1bd36053"
dependencies = [
"git-ref-format",
"git2",
@@ -2944,13 +2935,12 @@ dependencies = [
"localtime",
"log",
"mio 1.0.4",
- "nonempty 0.9.0",
+ "nonempty",
"qcheck",
"qcheck-macros",
"radicle",
"radicle-crypto",
"radicle-fetch",
- "radicle-git-ext",
"radicle-protocol",
"radicle-signals",
"radicle-systemd",
@@ -2991,14 +2981,13 @@ dependencies = [
"fastrand",
"localtime",
"log",
- "nonempty 0.9.0",
+ "nonempty",
"paste",
"qcheck",
"qcheck-macros",
"radicle",
"radicle-crypto",
"radicle-fetch",
- "radicle-git-ext",
"scrypt",
"serde",
"serde_json",
@@ -3015,7 +3004,6 @@ dependencies = [
"radicle",
"radicle-cli",
"radicle-crypto",
- "radicle-git-ext",
"thiserror 1.0.69",
]
@@ -3049,22 +3037,22 @@ dependencies = [
[[package]]
name = "radicle-std-ext"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4"
+checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a"
[[package]]
name = "radicle-surf"
-version = "0.22.0"
+version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb308c3989087f71e43d8c7a2737273fdc7fbcd3e6628af81a42f601ae64f314"
+checksum = "2f08adc954d4a49287d86bd1ce14ca7e34a11a3acd904044db30551ca8f4e46a"
dependencies = [
"anyhow",
- "base64 0.13.1",
+ "base64 0.21.7",
"flate2",
"git2",
"log",
- "nonempty 0.5.0",
+ "nonempty",
"radicle-git-ext",
"radicle-std-ext",
"tar",
diff --git a/Cargo.toml b/Cargo.toml
index 185eedb0b..3e910b574 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,8 +28,6 @@ crossbeam-channel = "0.5.6"
cyphernet = "0.5.2"
dunce = "1.0.5"
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 }
human-panic = "2"
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-dag = { version = "0.10", path = "crates/radicle-dag" }
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-node = { version = "0.16", path = "crates/radicle-node" }
radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false }
@@ -70,6 +68,13 @@ thiserror = "1.0"
winpipe = "0.1.1"
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]
clippy.type_complexity = "allow"
clippy.enum_variant_names = "allow"
diff --git a/crates/radicle-cli/Cargo.toml b/crates/radicle-cli/Cargo.toml
index 4517bbed5..878f3601f 100644
--- a/crates/radicle-cli/Cargo.toml
+++ b/crates/radicle-cli/Cargo.toml
@@ -18,7 +18,6 @@ anyhow = "1"
chrono = { workspace = true, features = ["clock", "std"] }
clap = { version = "4.5.44", features = ["derive"] }
dunce = { workspace = true }
-git-ref-format = { version = "0.3.0", features = ["macro"] }
human-panic.workspace = true
itertools.workspace = true
lexopt = { workspace = true }
@@ -28,10 +27,8 @@ nonempty = { workspace = true }
radicle = { workspace = true, features = ["logger", "schemars"] }
radicle-cob = { workspace = true }
radicle-crypto = { 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-surf = "0.22.0"
+radicle-git-ref-format = { workspace = true, features = ["macro"] }
+radicle-surf = { workspace = true }
radicle-term = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
diff --git a/crates/radicle-cli/src/commands/checkout.rs b/crates/radicle-cli/src/commands/checkout.rs
index 265a86115..1f25ee221 100644
--- a/crates/radicle-cli/src/commands/checkout.rs
+++ b/crates/radicle-cli/src/commands/checkout.rs
@@ -156,9 +156,9 @@ pub fn setup_remotes(
pub fn setup_remote(
setup: &project::SetupRemote,
remote_id: &NodeId,
- remote_name: Option<git::RefString>,
+ remote_name: Option<git::fmt::RefString>,
aliases: &impl AliasStore,
-) -> anyhow::Result<git::RefString> {
+) -> anyhow::Result<git::fmt::RefString> {
let remote_name = if let Some(name) = remote_name {
name
} else {
@@ -167,7 +167,7 @@ pub fn setup_remote(
} else {
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}'"))?
};
let (remote, branch) = setup.run(&remote_name, *remote_id)?;
diff --git a/crates/radicle-cli/src/commands/inbox.rs b/crates/radicle-cli/src/commands/inbox.rs
index 2b7cfa00f..81a7bd67e 100644
--- a/crates/radicle-cli/src/commands/inbox.rs
+++ b/crates/radicle-cli/src/commands/inbox.rs
@@ -4,16 +4,17 @@ use std::process;
use anyhow::anyhow;
-use git_ref_format::Qualified;
use localtime::LocalTime;
use radicle::cob::TypedId;
+use radicle::git::fmt::Qualified;
+use radicle::git::BranchName;
use radicle::identity::Identity;
use radicle::issue::cache::Issues as _;
use radicle::node::notifications;
use radicle::node::notifications::*;
use radicle::patch::cache::Patches as _;
use radicle::prelude::{NodeId, Profile, RepoId};
-use radicle::storage::{BranchName, ReadRepository, ReadStorage};
+use radicle::storage::{ReadRepository, ReadStorage};
use radicle::{cob, git, Storage};
use term::Element as _;
diff --git a/crates/radicle-cli/src/commands/init.rs b/crates/radicle-cli/src/commands/init.rs
index 0d74ccf74..d44ea66a2 100644
--- a/crates/radicle-cli/src/commands/init.rs
+++ b/crates/radicle-cli/src/commands/init.rs
@@ -16,9 +16,9 @@ use serde_json as json;
use radicle::crypto::ssh;
use radicle::explorer::ExplorerUrl;
+use radicle::git::fmt::RefString;
use radicle::git::raw;
use radicle::git::raw::ErrorExt as _;
-use radicle::git::RefString;
use radicle::identity::project::ProjectName;
use radicle::identity::{Doc, RepoId, Visibility};
use radicle::node::events::UploadPack;
diff --git a/crates/radicle-cli/src/commands/patch.rs b/crates/radicle-cli/src/commands/patch.rs
index 67f03ac88..187abfc93 100644
--- a/crates/radicle-cli/src/commands/patch.rs
+++ b/crates/radicle-cli/src/commands/patch.rs
@@ -24,7 +24,7 @@ use anyhow::anyhow;
use radicle::cob::patch::PatchId;
use radicle::cob::{patch, Label, Reaction};
-use radicle::git::RefString;
+use radicle::git::fmt::RefString;
use radicle::patch::cache::Patches as _;
use radicle::storage::git::transport;
use radicle::{prelude::*, Node};
diff --git a/crates/radicle-cli/src/commands/patch/checkout.rs b/crates/radicle-cli/src/commands/patch/checkout.rs
index 9fdec248a..ef1b400ab 100644
--- a/crates/radicle-cli/src/commands/patch/checkout.rs
+++ b/crates/radicle-cli/src/commands/patch/checkout.rs
@@ -1,10 +1,10 @@
use anyhow::anyhow;
-use git_ref_format::Qualified;
use radicle::cob::patch;
use radicle::cob::patch::RevisionId;
+use radicle::git::fmt::Qualified;
+use radicle::git::fmt::RefString;
use radicle::git::raw::ErrorExt as _;
-use radicle::git::RefString;
use radicle::patch::cache::Patches as _;
use radicle::patch::PatchId;
use radicle::storage::git::Repository;
@@ -25,7 +25,7 @@ impl Options {
Some(refname) => Ok(Qualified::from_refstr(refname)
.map_or_else(|| refname.clone(), |q| q.to_ref_string())),
// 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())),
}
}
@@ -57,34 +57,33 @@ pub fn run(
let mut spinner = term::spinner("Performing checkout...");
let patch_branch = opts.branch(patch_id)?;
- let commit =
- match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
- Ok(branch) if opts.force => {
- let commit = find_patch_commit(revision, stored, working)?;
- let mut r = branch.into_reference();
- r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?;
- commit
- }
- Ok(branch) => {
- let head = branch.get().peel_to_commit()?;
- if head.id() != *revision.head() {
- anyhow::bail!(
- "branch '{patch_branch}' already exists (use `--force` to overwrite)"
- );
- }
- head
- }
- Err(e) if e.is_not_found() => {
- let commit = find_patch_commit(revision, stored, working)?;
- // Create patch branch and switch to it.
- working.branch(patch_branch.as_str(), &commit, true)?;
- commit
+ let commit = match working.find_branch(patch_branch.as_str(), git::raw::BranchType::Local) {
+ Ok(branch) if opts.force => {
+ let commit = find_patch_commit(revision, stored, working)?;
+ let mut r = branch.into_reference();
+ r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?;
+ commit
+ }
+ Ok(branch) => {
+ let head = branch.get().peel_to_commit()?;
+ if revision.head() != head.id() {
+ anyhow::bail!(
+ "branch '{patch_branch}' already exists (use `--force` to overwrite)"
+ );
}
- Err(e) => return Err(e.into()),
- };
+ head
+ }
+ Err(e) if e.is_not_found() => {
+ let commit = find_patch_commit(revision, stored, working)?;
+ // Create patch branch and switch to it.
+ working.branch(patch_branch.as_str(), &commit, true)?;
+ commit
+ }
+ Err(e) => return Err(e.into()),
+ };
if opts.force {
- let mut builder = radicle::git::raw::build::CheckoutBuilder::new();
+ let mut builder = git::raw::build::CheckoutBuilder::new();
builder.force();
working.checkout_tree(commit.as_object(), Some(&mut builder))?;
} else {
@@ -125,7 +124,7 @@ fn find_patch_commit<'a>(
stored: &Repository,
working: &'a git::raw::Repository,
) -> anyhow::Result<git::raw::Commit<'a>> {
- let head = *revision.head();
+ let head = revision.head().into();
match working.find_commit(head) {
Ok(commit) => Ok(commit),
diff --git a/crates/radicle-cli/src/commands/patch/review/builder.rs b/crates/radicle-cli/src/commands/patch/review/builder.rs
index fe7f24484..c7f0a339f 100644
--- a/crates/radicle-cli/src/commands/patch/review/builder.rs
+++ b/crates/radicle-cli/src/commands/patch/review/builder.rs
@@ -23,10 +23,10 @@ use radicle::cob::patch::{PatchId, Revision, Verdict};
use radicle::cob::{CodeLocation, CodeRange};
use radicle::crypto;
use radicle::git;
+use radicle::git::Oid;
use radicle::node::device::Device;
use radicle::prelude::*;
use radicle::storage::git::{cob::DraftStore, Repository};
-use radicle_git_ext::Oid;
use radicle_surf::diff::*;
use radicle_term::{Element, VStack};
@@ -196,25 +196,28 @@ impl ReviewItem {
fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) {
match self {
- Self::FileAdded { path, new, .. } => (None, Some((path, new.oid))),
- Self::FileDeleted { path, old, .. } => (Some((path, old.oid)), None),
+ Self::FileAdded { path, new, .. } => (None, Some((path, Oid::from(*new.oid)))),
+ Self::FileDeleted { path, old, .. } => (Some((path, Oid::from(*old.oid))), None),
Self::FileMoved { moved } => (
- Some((&moved.old_path, moved.old.oid)),
- Some((&moved.new_path, moved.new.oid)),
+ Some((&moved.old_path, Oid::from(*moved.old.oid))),
+ Some((&moved.new_path, Oid::from(*moved.new.oid))),
),
Self::FileCopied { copied } => (
- Some((&copied.old_path, copied.old.oid)),
- Some((&copied.new_path, copied.new.oid)),
+ Some((&copied.old_path, Oid::from(*copied.old.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.
pub struct Brain<'a> {
/// Where the review draft is being stored.
- refname: git::Namespaced<'a>,
+ refname: git::fmt::Namespaced<'a>,
/// The commit pointed to by the ref.
head: git::raw::Commit<'a>,
/// 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.
- 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)
}
}
diff --git a/crates/radicle-cli/src/commands/patch/update.rs b/crates/radicle-cli/src/commands/patch/update.rs
index b2cc6f7ea..52cac82a4 100644
--- a/crates/radicle-cli/src/commands/patch/update.rs
+++ b/crates/radicle-cli/src/commands/patch/update.rs
@@ -1,5 +1,6 @@
use radicle::cob::patch;
use radicle::git;
+use radicle::git::Oid;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@@ -9,7 +10,7 @@ use crate::terminal::patch::*;
/// Run patch update.
pub fn run(
patch_id: patch::PatchId,
- base_id: Option<git::raw::Oid>,
+ base_id: Option<Oid>,
message: term::patch::Message,
profile: &Profile,
repository: &Repository,
@@ -27,22 +28,25 @@ pub fn run(
let head_oid = branch_oid(&head_branch)?;
let base_oid = match base_id {
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
// any previous revision
if patch
.revisions()
- .any(|(_, revision)| revision.head() == head_oid && **revision.base() == base_oid)
+ .any(|(_, revision)| revision.head() == head_oid && *revision.base() == base_oid)
{
return Ok(());
}
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 revision = patch.update(message, base_oid, *head_oid, &signer)?;
+ let revision = patch.update(message, base_oid, head_oid, &signer)?;
term::print(revision);
diff --git a/crates/radicle-cli/src/commands/remote.rs b/crates/radicle-cli/src/commands/remote.rs
index 3cae74410..777054700 100644
--- a/crates/radicle-cli/src/commands/remote.rs
+++ b/crates/radicle-cli/src/commands/remote.rs
@@ -8,7 +8,7 @@ use std::ffi::OsString;
use anyhow::anyhow;
-use radicle::git::RefString;
+use radicle::git::fmt::RefString;
use radicle::prelude::NodeId;
use radicle::storage::ReadStorage;
diff --git a/crates/radicle-cli/src/commands/remote/add.rs b/crates/radicle-cli/src/commands/remote/add.rs
index 89562e86b..7634eff3c 100644
--- a/crates/radicle-cli/src/commands/remote/add.rs
+++ b/crates/radicle-cli/src/commands/remote/add.rs
@@ -1,7 +1,7 @@
use std::str::FromStr;
use radicle::git;
-use radicle::git::RefString;
+use radicle::git::fmt::RefString;
use radicle::prelude::*;
use radicle::Profile;
use radicle_crypto::PublicKey;
diff --git a/crates/radicle-cli/src/commands/stats.rs b/crates/radicle-cli/src/commands/stats.rs
index 87ac5cd2b..386cee665 100644
--- a/crates/radicle-cli/src/commands/stats.rs
+++ b/crates/radicle-cli/src/commands/stats.rs
@@ -71,7 +71,7 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let remote = remote?;
let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
let mut walk = repo.raw().revwalk()?;
- walk.push(*sigrefs)?;
+ walk.push(sigrefs.into())?;
stats.local.pushes += walk.count();
stats.local.forks += 1;
diff --git a/crates/radicle-cli/src/commands/watch.rs b/crates/radicle-cli/src/commands/watch.rs
index c854ccb9d..b5084d8fa 100644
--- a/crates/radicle-cli/src/commands/watch.rs
+++ b/crates/radicle-cli/src/commands/watch.rs
@@ -41,7 +41,7 @@ Options
pub struct Options {
rid: Option<RepoId>,
- refstr: git::RefString,
+ refstr: git::fmt::RefString,
target: Option<git::Oid>,
nid: Option<NodeId>,
interval: time::Duration,
@@ -56,7 +56,7 @@ impl Args for Options {
let mut rid = None;
let mut nid: Option<NodeId> = 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 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>(
repo: &R,
nid: &NodeId,
- qual: &git::Qualified,
+ qual: &git::fmt::Qualified,
) -> Result<Option<git::Oid>, git::raw::Error> {
match repo.reference_oid(nid, qual) {
Ok(oid) => Ok(Some(oid)),
diff --git a/crates/radicle-cli/src/git.rs b/crates/radicle-cli/src/git.rs
index 199f60d54..1d885a498 100644
--- a/crates/radicle-cli/src/git.rs
+++ b/crates/radicle-cli/src/git.rs
@@ -20,14 +20,15 @@ use thiserror::Error;
use radicle::crypto::ssh;
use radicle::git;
-use radicle::git::raw as git2;
use radicle::git::{Version, VERSION_REQUIRED};
use radicle::prelude::{NodeId, RepoId};
use radicle::storage::git::transport;
+pub use radicle::git::Oid;
+
pub use radicle::git::raw::{
- build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis,
- MergeOptions, Oid, Reference, Repository, Signature,
+ build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, ErrorExt as _,
+ MergeAnalysis, MergeOptions, Reference, Repository, Signature,
};
pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign";
@@ -46,10 +47,10 @@ impl Rev {
&self.0
}
- /// Resolve the revision to an [`From<git2::Oid>`].
- pub fn resolve<T>(&self, repo: &git2::Repository) -> Result<T, git2::Error>
+ /// Resolve the revision to an [`From<git::raw::Oid>`].
+ pub fn resolve<T>(&self, repo: &Repository) -> Result<T, git::raw::Error>
where
- T: From<git2::Oid>,
+ T: From<git::raw::Oid>,
{
let object = repo.revparse_single(self.as_str())?;
Ok(object.id().into())
@@ -84,13 +85,13 @@ pub struct Remote<'a> {
pub url: 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;
- 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| {
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> {
- type Target = git2::Remote<'a>;
+ type Target = git::raw::Remote<'a>;
fn deref(&self) -> &Self::Target {
&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.
-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
.remotes()?
.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`.
-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) {
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()),
}
}
/// 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) {
Ok((remote, id)) => Ok((remote, id)),
Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!(
@@ -352,12 +353,12 @@ pub fn parse_remote(refspec: &str) -> Option<(NodeId, &str)> {
}
pub fn add_tag(
- repo: &git2::Repository,
+ repo: &Repository,
message: &str,
patch_tag_name: &str,
-) -> anyhow::Result<git2::Oid> {
+) -> anyhow::Result<git::raw::Oid> {
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)?;
Ok(oid)
diff --git a/crates/radicle-cli/src/git/pretty_diff.rs b/crates/radicle-cli/src/git/pretty_diff.rs
index dd5d77bcd..a5319cfa8 100644
--- a/crates/radicle-cli/src/git/pretty_diff.rs
+++ b/crates/radicle-cli/src/git/pretty_diff.rs
@@ -2,7 +2,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use radicle::git;
-use radicle_git_ext::Oid;
+use radicle::git::Oid;
use radicle_surf::diff;
use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved};
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
@@ -33,7 +33,7 @@ pub trait Repo {
impl Repo for git::raw::Repository {
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() {
Ok(Blob::Binary)
@@ -338,7 +338,7 @@ impl ToPretty for Added {
repo: &R,
) -> Self::Output {
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)
}
@@ -354,7 +354,7 @@ impl ToPretty for Deleted {
header: &Self::Context,
repo: &R,
) -> 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;
pretty_modification(header, &self.diff, old, new, repo, hi)
@@ -371,8 +371,8 @@ impl ToPretty for Modified {
header: &Self::Context,
repo: &R,
) -> Self::Output {
- let old = Some((self.path.as_path(), self.old.oid));
- let new = Some((self.path.as_path(), self.new.oid));
+ let old = Some((self.path.as_path(), Oid::from(*self.old.oid)));
+ let new = Some((self.path.as_path(), Oid::from(*self.new.oid)));
pretty_modification(header, &self.diff, old, new, repo, hi)
}
@@ -595,8 +595,8 @@ mod test {
use term::Element;
use super::*;
- use radicle::git::raw::RepositoryOpenFlags;
- use radicle::git::raw::{Oid, Repository};
+ use git::raw::RepositoryOpenFlags;
+ use git::raw::{Oid, Repository};
#[test]
#[ignore]
diff --git a/crates/radicle-cli/src/git/unified_diff.rs b/crates/radicle-cli/src/git/unified_diff.rs
index 666ad4fbe..d37cdcbff 100644
--- a/crates/radicle-cli/src/git/unified_diff.rs
+++ b/crates/radicle-cli/src/git/unified_diff.rs
@@ -7,7 +7,6 @@ use radicle_surf::diff::FileStats;
use thiserror::Error;
use radicle::git;
-use radicle::git::raw::Oid;
use radicle_surf::diff;
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 {
w.meta(format!(
"index {}..{} {:o}",
- term::format::oid(old.oid),
- term::format::oid(new.oid),
+ term::format::oid(*old.oid),
+ term::format::oid(*new.oid),
u32::from(old.mode.clone()),
))?;
} else {
@@ -316,8 +315,8 @@ impl Encode for FileHeader {
w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?;
w.meta(format!(
"index {}..{}",
- term::format::oid(old.oid),
- term::format::oid(new.oid)
+ term::format::oid(*old.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!(
"index {}..{}",
- term::format::oid(Oid::zero()),
- term::format::oid(new.oid),
+ term::format::oid(git::Oid::sha1_zero()),
+ term::format::oid(*new.oid),
))?;
w.meta("--- /dev/null")?;
@@ -355,8 +354,8 @@ impl Encode for FileHeader {
))?;
w.meta(format!(
"index {}..{}",
- term::format::oid(old.oid),
- term::format::oid(Oid::zero())
+ term::format::oid(*old.oid),
+ term::format::oid(git::Oid::sha1_zero())
))?;
w.meta(format!("--- a/{}", path.display()))?;
diff --git a/crates/radicle-cli/src/project.rs b/crates/radicle-cli/src/project.rs
index 37708d139..c7df9945e 100644
--- a/crates/radicle-cli/src/project.rs
+++ b/crates/radicle-cli/src/project.rs
@@ -1,7 +1,7 @@
use radicle::prelude::*;
use crate::git;
-use radicle::git::RefStr;
+use radicle::git::fmt::RefStr;
use radicle::node::NodeId;
/// Setup a repository remote and tracking branch.
diff --git a/crates/radicle-cli/src/terminal/args.rs b/crates/radicle-cli/src/terminal/args.rs
index 90f74d4e0..c3c511d10 100644
--- a/crates/radicle-cli/src/terminal/args.rs
+++ b/crates/radicle-cli/src/terminal/args.rs
@@ -7,7 +7,7 @@ use anyhow::anyhow;
use radicle::cob::{self, issue, patch};
use radicle::crypto;
-use radicle::git::{Oid, RefString};
+use radicle::git::{fmt::RefString, Oid};
use radicle::node::{Address, Alias};
use radicle::prelude::{Did, NodeId, RepoId};
diff --git a/crates/radicle-cli/src/terminal/patch.rs b/crates/radicle-cli/src/terminal/patch.rs
index 67d149a2c..daf823a8b 100644
--- a/crates/radicle-cli/src/terminal/patch.rs
+++ b/crates/radicle-cli/src/terminal/patch.rs
@@ -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.
pub fn patch_commits<'a>(
repo: &'a git::raw::Repository,
- base: &git::Oid,
- head: &git::Oid,
+ base: &git::raw::Oid,
+ head: &git::raw::Oid,
) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> {
let mut commits = Vec::new();
let mut revwalk = repo.revwalk()?;
@@ -205,8 +205,8 @@ pub fn patch_commits<'a>(
/// The message shown in the editor when creating a `Patch`.
fn create_display_message(
repo: &git::raw::Repository,
- base: &git::Oid,
- head: &git::Oid,
+ base: &git::raw::Oid,
+ head: &git::raw::Oid,
) -> Result<String, Error> {
let commits = patch_commits(repo, base, head)?;
if commits.is_empty() {
@@ -226,8 +226,8 @@ fn create_display_message(
pub fn get_create_message(
message: term::patch::Message,
repo: &git::raw::Repository,
- base: &git::Oid,
- head: &git::Oid,
+ base: &git::raw::Oid,
+ head: &git::raw::Oid,
) -> Result<(Title, String), Error> {
let display_msg = create_display_message(repo, base, head)?;
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`.
fn update_display_message(
repo: &git::raw::Repository,
- last_rev_head: &git::Oid,
- head: &git::Oid,
+ last_rev_head: &git::raw::Oid,
+ head: &git::raw::Oid,
) -> 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());
}
@@ -302,9 +302,9 @@ pub fn get_update_message(
message: term::patch::Message,
repo: &git::raw::Repository,
latest: &patch::Revision,
- head: &git::Oid,
+ head: &git::raw::Oid,
) -> 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.trim();
@@ -366,11 +366,8 @@ pub fn show(
} else {
vec![]
};
- let ahead_behind = common::ahead_behind(
- stored.raw(),
- *revision.head(),
- *patch.target().head(stored)?,
- )?;
+ let ahead_behind =
+ common::ahead_behind(stored.raw(), revision.head(), patch.target().head(stored)?)?;
let author = patch.author();
let author = term::format::Author::new(author.id(), profile, verbose);
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 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([
term::label(term::format::secondary::<String>(
term::format::oid(commit.id()).into(),
@@ -484,37 +481,36 @@ fn patch_commit_lines(
#[cfg(test)]
mod test {
use super::*;
- use radicle::git::refname;
+ use radicle::git::fmt::refname;
use radicle::test::fixtures;
use std::path;
fn commit(
repo: &git::raw::Repository,
- branch: &git::RefStr,
- parent: &git::Oid,
+ branch: &git::fmt::RefStr,
+ parent: &git::raw::Oid,
msg: &str,
- ) -> git::Oid {
+ ) -> git::raw::Oid {
let sig = git::raw::Signature::new(
"anonymous",
"anonymous@radicle.example.com",
&git::raw::Time::new(0, 0),
)
.unwrap();
- let head = repo.find_commit(**parent).unwrap();
+ let head = repo.find_commit(*parent).unwrap();
let tree =
git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap();
let branch = git::refs::branch(branch);
let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap();
- commit.id().into()
+ commit.id()
}
#[test]
fn test_create_display_message() {
let tmpdir = tempfile::tempdir().unwrap();
let (repo, commit_0) = fixtures::repository(&tmpdir);
- let commit_0 = commit_0.into();
let commit_1 = commit(
&repo,
&refname!("feature"),
@@ -625,7 +621,6 @@ mod test {
fn test_update_display_message() {
let tmpdir = tempfile::tempdir().unwrap();
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_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n");
diff --git a/crates/radicle-cli/src/terminal/patch/common.rs b/crates/radicle-cli/src/terminal/patch/common.rs
index 35e1dcf38..2e9e42174 100644
--- a/crates/radicle-cli/src/terminal/patch/common.rs
+++ b/crates/radicle-cli/src/terminal/patch/common.rs
@@ -1,7 +1,7 @@
use anyhow::anyhow;
use radicle::git;
-use radicle::git::raw::Oid;
+use radicle::git::Oid;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@@ -9,7 +9,7 @@ use crate::terminal as term;
/// Give the oid of the branch or an appropriate error.
#[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
.get()
.target()
@@ -18,7 +18,7 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
}
#[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();
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(
storage: &Repository,
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 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");
}
- Ok((get_branch(qualified_ref), (*target_oid).into()))
+ Ok((get_branch(qualified_ref), (target_oid)))
}
/// Get the diff stats between two commits.
@@ -47,8 +49,8 @@ pub fn diff_stats(
old: &Oid,
new: &Oid,
) -> Result<git::raw::DiffStats, git::raw::Error> {
- let old = repo.find_commit(*old)?;
- let new = repo.find_commit(*new)?;
+ let old = repo.find_commit(old.into())?;
+ let new = repo.find_commit(new.into())?;
let old_tree = old.tree()?;
let new_tree = new.tree()?;
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,
head_oid: Oid,
) -> 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 {
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;
}
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
- if oid == target {
+ if target == oid {
branches.push(name.to_string());
};
};
diff --git a/crates/radicle-cli/tests/util/environment.rs b/crates/radicle-cli/tests/util/environment.rs
index 61f032a9b..67a2f8606 100644
--- a/crates/radicle-cli/tests/util/environment.rs
+++ b/crates/radicle-cli/tests/util/environment.rs
@@ -5,6 +5,7 @@ use localtime::LocalTime;
use radicle::cob::cache::COBS_DB_FILE;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::{KeyPair, Seed};
+use radicle::git;
use radicle::node::policy::store as policy;
use radicle::node::{self, UserAgent};
use radicle::node::{Alias, Config, POLICIES_DB_FILE};
@@ -233,7 +234,7 @@ impl Environment {
pub fn repository(
&self,
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())
}
diff --git a/crates/radicle-cob/Cargo.toml b/crates/radicle-cob/Cargo.toml
index 15bffd7c6..a598acb5c 100644
--- a/crates/radicle-cob/Cargo.toml
+++ b/crates/radicle-cob/Cargo.toml
@@ -17,15 +17,18 @@ rust-version.workspace = true
default = []
# Only used for testing. Ensures that commit ids are stable.
stable-commit-ids = []
+test = []
[dependencies]
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 }
nonempty = { workspace = true, features = ["serialize"] }
radicle-crypto = { workspace = true, features = ["ssh"] }
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_json = { workspace = true }
signature = { workspace = true }
@@ -36,4 +39,6 @@ fastrand = { workspace = true }
qcheck = { workspace = true }
qcheck-macros = { workspace = true }
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 }
diff --git a/crates/radicle-cob/src/backend.rs b/crates/radicle-cob/src/backend.rs
index 8baeba422..22ab4ad94 100644
--- a/crates/radicle-cob/src/backend.rs
+++ b/crates/radicle-cob/src/backend.rs
@@ -1,3 +1,7 @@
// Copyright © 2022 The Radicle Link Contributors
+#[cfg(feature = "git2")]
pub mod git;
+
+#[cfg(feature = "stable-commit-ids")]
+pub mod stable;
diff --git a/crates/radicle-cob/src/backend/git.rs b/crates/radicle-cob/src/backend/git.rs
index 0cf18802b..b4a374890 100644
--- a/crates/radicle-cob/src/backend/git.rs
+++ b/crates/radicle-cob/src/backend/git.rs
@@ -1,9 +1,8 @@
// Copyright © 2022 The Radicle Team
-pub mod change;
+mod commit;
-#[cfg(feature = "stable-commit-ids")]
-pub mod stable;
+pub mod change;
/// Environment variable to set to overwrite the commit date for both the author and the committer.
///
diff --git a/crates/radicle-cob/src/backend/git/change.rs b/crates/radicle-cob/src/backend/git/change.rs
index 85fff00c9..9d36dfe7d 100644
--- a/crates/radicle-cob/src/backend/git/change.rs
+++ b/crates/radicle-cob/src/backend/git/change.rs
@@ -5,11 +5,11 @@ use std::convert::TryFrom;
use std::path::PathBuf;
use std::sync::LazyLock;
-use git_ext::author::Author;
-use git_ext::commit::{headers::Headers, Commit};
-use git_ext::Oid;
+use metadata::author::Author;
+use metadata::commit::headers::Headers;
+use metadata::commit::trailers::OwnedTrailer;
use nonempty::NonEmpty;
-use radicle_git_ext::commit::trailers::OwnedTrailer;
+use oid::Oid;
use crate::change::store::Version;
use crate::signatures;
@@ -21,6 +21,8 @@ use crate::{
trailers, Embed,
};
+use super::commit::Commit;
+
/// Name of the COB manifest file.
pub const MANIFEST_BLOB_NAME: &str = "manifest";
/// Path under which COB embeds are kept.
@@ -30,8 +32,7 @@ pub mod error {
use std::str::Utf8Error;
use std::string::FromUtf8Error;
- use git_ext::commit;
- use git_ext::Oid;
+ use oid::Oid;
use thiserror::Error;
use crate::signatures::error::Signatures;
@@ -39,7 +40,7 @@ pub mod error {
#[derive(Debug, Error)]
pub enum Create {
#[error(transparent)]
- WriteCommit(#[from] commit::error::Write),
+ WriteCommit(#[from] super::super::commit::error::Write),
#[error(transparent)]
FromUtf8(#[from] FromUtf8Error),
#[error(transparent)]
@@ -55,7 +56,7 @@ pub mod error {
#[derive(Debug, Error)]
pub enum Load {
#[error(transparent)]
- Read(#[from] commit::error::Read),
+ Read(#[from] super::super::commit::error::Read),
#[error(transparent)]
Signatures(#[from] Signatures),
#[error(transparent)]
@@ -123,7 +124,7 @@ impl change::Storage for git2::Repository {
let (id, timestamp) = write_commit(
self,
- resource.map(|o| *o),
+ resource.map(|o| o.into()),
// Commit to tips, extra parents and resource.
tips.iter()
.cloned()
@@ -134,7 +135,7 @@ impl change::Storage for git2::Repository {
signature.clone(),
related
.iter()
- .map(|p| trailers::CommitTrailer::Related(**p).into()),
+ .map(|p| trailers::CommitTrailer::Related(*p).into()),
tree,
)?;
@@ -153,40 +154,34 @@ impl change::Storage for git2::Repository {
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
Ok(self
- .find_commit(**id)?
+ .find_commit(id.into())?
.parent_ids()
.map(Oid::from)
.collect::<Vec<_>>())
}
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()?;
load_manifest(self, &tree)
}
fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
- let commit = Commit::read(self, id.into())?;
- let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
+ let commit = super::commit::Commit::read(self, id.into())?;
+ let timestamp = commit.committer().time.seconds() as u64;
let trailers = parse_trailers(commit.trailers())?;
let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t {
CommitTrailer::Resource(_) => true,
CommitTrailer::Related(_) => false,
});
- let mut resources = resources
- .into_iter()
- .map(|r| r.oid().into())
- .collect::<Vec<_>>();
- let related = related
- .into_iter()
- .map(|r| r.oid().into())
- .collect::<Vec<_>>();
+ let mut resources = resources.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
+ let related = related.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
let parents = commit
.parents()
.map(Oid::from)
.filter(|p| !resources.contains(p) && !related.contains(p))
.collect();
- let mut signatures = Signatures::try_from(&commit)?
+ let mut signatures = Signatures::try_from(&*commit)?
.into_iter()
.collect::<Vec<_>>();
let Some((key, sig)) = signatures.pop() else {
@@ -285,7 +280,7 @@ fn write_commit(
) -> Result<(Oid, Timestamp), error::Create> {
let trailers: Vec<OwnedTrailer> = trailers
.into_iter()
- .chain(resource.map(|r| trailers::CommitTrailer::Resource(r).into()))
+ .chain(resource.map(|r| trailers::CommitTrailer::Resource(r.into()).into()))
.collect();
let author = repo.signature()?;
#[allow(unused_variables)]
@@ -299,29 +294,37 @@ fn write_commit(
.map_err(signatures::error::Signatures::from)?
.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")]
// Ensures the commit id doesn't change on every run.
let (author, timestamp) = {
- let stable = crate::git::stable::read_timestamp();
+ let stable = crate::stable::read_timestamp();
(
Author {
- time: git_ext::author::Time::new(stable, 0),
+ time: metadata::author::Time::new(stable, 0),
..author
},
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 {
panic!(
"Invalid timestamp value {s:?} for `{}`",
- crate::git::GIT_COMMITTER_DATE
+ super::GIT_COMMITTER_DATE
);
};
let author = Author {
- time: git_ext::author::Time::new(timestamp, 0),
+ time: metadata::author::Time::new(timestamp, 0),
..author
};
(author, timestamp)
@@ -380,7 +383,7 @@ fn write_manifest(
let oid = embed.content;
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()?;
diff --git a/crates/radicle-cob/src/backend/git/commit.rs b/crates/radicle-cob/src/backend/git/commit.rs
new file mode 100644
index 000000000..462b455b7
--- /dev/null
+++ b/crates/radicle-cob/src/backend/git/commit.rs
@@ -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
+ }
+}
diff --git a/crates/radicle-cob/src/backend/git/commit/trailers.rs b/crates/radicle-cob/src/backend/git/commit/trailers.rs
new file mode 100644
index 000000000..b1b5238f7
--- /dev/null
+++ b/crates/radicle-cob/src/backend/git/commit/trailers.rs
@@ -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),
+ })
+ }
+}
diff --git a/crates/radicle-cob/src/backend/git/stable.rs b/crates/radicle-cob/src/backend/stable.rs
similarity index 100%
rename from crates/radicle-cob/src/backend/git/stable.rs
rename to crates/radicle-cob/src/backend/stable.rs
diff --git a/crates/radicle-cob/src/change.rs b/crates/radicle-cob/src/change.rs
index 8458064da..70b23bdb8 100644
--- a/crates/radicle-cob/src/change.rs
+++ b/crates/radicle-cob/src/change.rs
@@ -1,6 +1,6 @@
// Copyright © 2021 The Radicle Link Contributors
-use git_ext::Oid;
+use oid::Oid;
pub mod store;
pub use store::{Contents, EntryId, Storage, Template, Timestamp};
diff --git a/crates/radicle-cob/src/change/store.rs b/crates/radicle-cob/src/change/store.rs
index 7e8f147cf..feffd98b8 100644
--- a/crates/radicle-cob/src/change/store.rs
+++ b/crates/radicle-cob/src/change/store.rs
@@ -3,14 +3,15 @@
use std::{error::Error, fmt, num::NonZeroUsize};
use nonempty::NonEmpty;
-use radicle_git_ext::Oid;
+use oid::Oid;
use serde::{Deserialize, Serialize};
+use crate::object::collaboration::error::{Create, Update};
use crate::{signatures, TypeName};
/// Change entry 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 ObjectId;
@@ -194,6 +195,7 @@ pub struct Embed<T = Vec<u8>> {
pub content: T,
}
+#[cfg(feature = "git2")]
impl<T: From<Oid>> Embed<T> {
/// Create a new embed.
pub fn store(
@@ -210,6 +212,7 @@ impl<T: From<Oid>> Embed<T> {
}
}
+#[cfg(feature = "git2")]
impl Embed<Vec<u8>> {
/// Get the object id of the embedded content.
pub fn oid(&self) -> Oid {
diff --git a/crates/radicle-cob/src/change_graph.rs b/crates/radicle-cob/src/change_graph.rs
index 4db6e1204..488521a3b 100644
--- a/crates/radicle-cob/src/change_graph.rs
+++ b/crates/radicle-cob/src/change_graph.rs
@@ -3,8 +3,8 @@
use std::ops::ControlFlow;
use std::{cmp::Ordering, collections::BTreeSet};
-use git_ext::Oid;
-use radicle_dag::Dag;
+use dag::Dag;
+use oid::Oid;
use crate::{
change, object, object::collaboration::Evaluate, signatures::ExtendedSignature,
diff --git a/crates/radicle-cob/src/history.rs b/crates/radicle-cob/src/history.rs
index cfcc37483..e130d367d 100644
--- a/crates/radicle-cob/src/history.rs
+++ b/crates/radicle-cob/src/history.rs
@@ -2,8 +2,8 @@
#![allow(clippy::too_many_arguments)]
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
-use git_ext::Oid;
-use radicle_dag::Dag;
+use dag::Dag;
+use oid::Oid;
pub use crate::change::{Contents, Entry, EntryId, Timestamp};
@@ -77,7 +77,7 @@ impl History {
self.graph.node(id, change);
for tip in tips {
- self.graph.dependency(id, (*tip).into());
+ self.graph.dependency(id, tip);
}
}
diff --git a/crates/radicle-cob/src/lib.rs b/crates/radicle-cob/src/lib.rs
index 198b0977e..d49135b74 100644
--- a/crates/radicle-cob/src/lib.rs
+++ b/crates/radicle-cob/src/lib.rs
@@ -59,12 +59,20 @@ extern crate qcheck;
#[macro_use(quickcheck)]
extern crate qcheck_macros;
+extern crate git_ref_format_core as fmt;
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;
+
+#[cfg(all(any(test, feature = "test"), feature = "git2"))]
pub use backend::git;
+#[cfg(feature = "stable-commit-ids")]
+pub use backend::stable;
+
mod change_graph;
mod trailers;
@@ -105,19 +113,9 @@ mod tests;
///
/// * [`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
where
Self: object::Storage
- + change::Storage<
- StoreError = git::change::error::Create,
- LoadError = git::change::error::Load,
- ObjectId = git_ext::Oid,
- Parent = git_ext::Oid,
- Signatures = ExtendedSignature,
- >,
+ + change::Storage<ObjectId = oid::Oid, Parent = oid::Oid, Signatures = ExtendedSignature>,
{
}
diff --git a/crates/radicle-cob/src/object.rs b/crates/radicle-cob/src/object.rs
index f368ed9d2..dfb50a0c1 100644
--- a/crates/radicle-cob/src/object.rs
+++ b/crates/radicle-cob/src/object.rs
@@ -1,9 +1,9 @@
// 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 git_ext::Oid;
+use fmt::{Component, RefString};
+use oid::Oid;
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -19,7 +19,7 @@ pub use storage::{Commit, Objects, Reference, Storage};
#[derive(Debug, Error)]
pub enum ParseObjectId {
#[error(transparent)]
- Git(#[from] git2::Error),
+ Git(#[from] oid::str::ParseOidError),
}
/// The id of an object
@@ -48,12 +48,14 @@ impl From<&Oid> for ObjectId {
}
}
+#[cfg(feature = "git2")]
impl From<git2::Oid> for ObjectId {
fn from(oid: git2::Oid) -> Self {
Oid::from(oid).into()
}
}
+#[cfg(feature = "git2")]
impl From<&git2::Oid> for ObjectId {
fn from(oid: &git2::Oid) -> Self {
ObjectId(Oid::from(*oid))
@@ -68,8 +70,8 @@ impl Deref for ObjectId {
}
}
-impl fmt::Display for ObjectId {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+impl std::fmt::Display for ObjectId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
diff --git a/crates/radicle-cob/src/object/collaboration.rs b/crates/radicle-cob/src/object/collaboration.rs
index 2236f97eb..11cc2dbc1 100644
--- a/crates/radicle-cob/src/object/collaboration.rs
+++ b/crates/radicle-cob/src/object/collaboration.rs
@@ -2,8 +2,8 @@
use std::convert::Infallible;
use std::fmt::Debug;
-use git_ext::Oid;
use nonempty::NonEmpty;
+use oid::Oid;
use crate::change::store::{Manifest, Version};
use crate::{change, Entry, History, ObjectId, TypeName};
@@ -101,11 +101,11 @@ impl<R> Evaluate<R> for NonEmpty<Entry> {
/// [`TypeName`] and [`ObjectId`] from it.
///
/// 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
-/// [`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:
///
/// `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:
///
-/// * 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 [`TypeName`] fails
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
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 refs_cobs = match name.to_namespaced() {
None => Qualified::from_refstr(name)?,
diff --git a/crates/radicle-cob/src/object/collaboration/create.rs b/crates/radicle-cob/src/object/collaboration/create.rs
index c2a2b5ea5..7d194a4a2 100644
--- a/crates/radicle-cob/src/object/collaboration/create.rs
+++ b/crates/radicle-cob/src/object/collaboration/create.rs
@@ -4,7 +4,6 @@ use nonempty::NonEmpty;
use crate::Embed;
use crate::Evaluate;
-use crate::Store;
use super::*;
@@ -23,7 +22,7 @@ pub struct Create {
}
impl Create {
- fn template(self) -> change::Template<git_ext::Oid> {
+ fn template(self) -> change::Template<oid::Oid> {
change::Template {
type_name: self.type_name,
tips: Vec::new(),
@@ -38,7 +37,7 @@ impl Create {
///
/// The `storage` is the backing storage for storing
/// [`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
/// ensures that the objects origin is cryptographically verifiable.
@@ -57,19 +56,24 @@ pub fn create<T, S, G>(
signer: &G,
resource: Option<Oid>,
related: Vec<Oid>,
- identifier: &S::Namespace,
+ identifier: &<S as crate::object::Storage>::Namespace,
args: Create,
) -> Result<CollaborativeObject<T>, error::Create>
where
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>,
{
let type_name = args.type_name.clone();
let version = args.version;
let init_change = storage
.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 = T::init(&init_change, storage).map_err(error::Create::evaluate)?;
diff --git a/crates/radicle-cob/src/object/collaboration/error.rs b/crates/radicle-cob/src/object/collaboration/error.rs
index 4b85b4c90..24ddf5478 100644
--- a/crates/radicle-cob/src/object/collaboration/error.rs
+++ b/crates/radicle-cob/src/object/collaboration/error.rs
@@ -2,14 +2,13 @@
use thiserror::Error;
-use crate::git;
-
#[derive(Debug, Error)]
pub enum Create {
#[error(transparent)]
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
#[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}")]
Refs {
#[source]
@@ -39,6 +38,7 @@ pub enum Retrieve {
#[error(transparent)]
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)]
+ #[cfg(feature = "git2")]
Git(#[from] git2::Error),
#[error("failed to get references during object retrieval: {err}")]
Refs {
@@ -62,13 +62,15 @@ pub enum Update {
#[error("no object found")]
NoSuchObject,
#[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}")]
Refs {
#[source]
err: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error(transparent)]
+ #[cfg(feature = "git2")]
Git(#[from] git2::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
diff --git a/crates/radicle-cob/src/object/collaboration/get.rs b/crates/radicle-cob/src/object/collaboration/get.rs
index 281f86c99..ab7beddbb 100644
--- a/crates/radicle-cob/src/object/collaboration/get.rs
+++ b/crates/radicle-cob/src/object/collaboration/get.rs
@@ -1,6 +1,8 @@
// 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;
@@ -8,7 +10,7 @@ use super::error;
///
/// The `storage` is the backing storage for storing
/// [`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
/// `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>
where
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
.objects(typename, oid)
diff --git a/crates/radicle-cob/src/object/collaboration/info.rs b/crates/radicle-cob/src/object/collaboration/info.rs
index 5e885c9f8..08822cde6 100644
--- a/crates/radicle-cob/src/object/collaboration/info.rs
+++ b/crates/radicle-cob/src/object/collaboration/info.rs
@@ -6,9 +6,10 @@
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;
@@ -28,7 +29,7 @@ pub struct ChangeGraphInfo {
///
/// The `storage` is the backing storage for storing
/// [`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`
/// is the identifier for the particular object under that type.
@@ -38,7 +39,12 @@ pub fn changegraph<S>(
oid: &ObjectId,
) -> Result<Option<ChangeGraphInfo>, error::Retrieve>
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
.objects(typename, oid)
diff --git a/crates/radicle-cob/src/object/collaboration/list.rs b/crates/radicle-cob/src/object/collaboration/list.rs
index 5f3a0ccae..33d9c9bbf 100644
--- a/crates/radicle-cob/src/object/collaboration/list.rs
+++ b/crates/radicle-cob/src/object/collaboration/list.rs
@@ -1,6 +1,6 @@
// 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;
@@ -8,7 +8,7 @@ use super::error;
///
/// The `storage` is the backing storage for storing
/// [`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.
pub fn list<T, S>(
@@ -17,7 +17,12 @@ pub fn list<T, S>(
) -> Result<Vec<CollaborativeObject<T>>, error::Retrieve>
where
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
.types(typename)
diff --git a/crates/radicle-cob/src/object/collaboration/remove.rs b/crates/radicle-cob/src/object/collaboration/remove.rs
index 0b0a798d7..d3b96d8fb 100644
--- a/crates/radicle-cob/src/object/collaboration/remove.rs
+++ b/crates/radicle-cob/src/object/collaboration/remove.rs
@@ -1,6 +1,6 @@
// Copyright © 2022 The Radicle Link Contributors
-use crate::{ObjectId, Store, TypeName};
+use crate::{ObjectId, TypeName};
use super::error;
@@ -8,7 +8,7 @@ use super::error;
///
/// The `storage` is the backing storage for storing
/// [`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
/// `object_id` is the identifier for the particular object under that
@@ -20,7 +20,7 @@ pub fn remove<S>(
oid: &ObjectId,
) -> Result<(), error::Remove>
where
- S: Store,
+ S: crate::object::Storage,
{
storage
.remove(identifier, typename, oid)
diff --git a/crates/radicle-cob/src/object/collaboration/update.rs b/crates/radicle-cob/src/object/collaboration/update.rs
index 3c6a7c431..d05a34733 100644
--- a/crates/radicle-cob/src/object/collaboration/update.rs
+++ b/crates/radicle-cob/src/object/collaboration/update.rs
@@ -1,12 +1,12 @@
// Copyright © 2022 The Radicle Link Contributors
use std::iter;
-use git_ext::Oid;
use nonempty::NonEmpty;
+use oid::Oid;
use crate::{
change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
- ExtendedSignature, ObjectId, Store, TypeName,
+ ExtendedSignature, ObjectId, TypeName,
};
use super::error;
@@ -40,7 +40,7 @@ pub struct Update {
///
/// The `storage` is the backing storage for storing
/// [`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
/// ensures that the objects origin is cryptographically verifiable.
@@ -65,7 +65,8 @@ pub fn update<T, S, G>(
) -> Result<Updated<T>, error::Update>
where
T: Evaluate<S>,
- S: Store,
+ S: crate::object::Storage,
+ S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
G: signature::Signer<ExtendedSignature>,
{
let Update {
@@ -86,18 +87,20 @@ where
graph.evaluate(storage).map_err(error::Update::evaluate)?;
// Create a commit for this change, but don't update any references yet.
- let entry = storage.store(
- resource,
- related,
- signer,
- change::Template {
- tips: object.history.tips().into_iter().collect(),
- embeds,
- contents: changes,
- type_name: typename.clone(),
- message,
- },
- )?;
+ let entry = storage
+ .store(
+ resource,
+ related,
+ signer,
+ change::Template {
+ tips: object.history.tips().into_iter().collect(),
+ embeds,
+ contents: changes,
+ type_name: typename.clone(),
+ message,
+ },
+ )
+ .map_err(Into::<error::Update>::into)?;
let head = entry.id;
let parents = entry.parents.to_vec();
diff --git a/crates/radicle-cob/src/object/storage.rs b/crates/radicle-cob/src/object/storage.rs
index 6779f5a5b..a23d468a7 100644
--- a/crates/radicle-cob/src/object/storage.rs
+++ b/crates/radicle-cob/src/object/storage.rs
@@ -2,8 +2,8 @@
use std::{collections::BTreeMap, error::Error};
-use git_ext::ref_format::RefString;
-use git_ext::Oid;
+use fmt::RefString;
+use oid::Oid;
use crate::change::EntryId;
use crate::{ObjectId, TypeName};
@@ -93,41 +93,43 @@ pub trait Storage {
pub mod convert {
use std::str;
- use git_ext::ref_format::RefString;
+ use fmt::RefString;
use thiserror::Error;
- use super::{Commit, Reference};
-
#[derive(Debug, Error)]
pub enum Error {
#[error("the reference '{name}' does not point to a commit object")]
NotCommit {
name: RefString,
+ #[cfg(feature = "git2")]
#[source]
err: git2::Error,
},
#[error(transparent)]
- Ref(#[from] git_ext::ref_format::Error),
+ Ref(#[from] fmt::Error),
#[error(transparent)]
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;
fn try_from(value: git2::Reference<'a>) -> Result<Self, Self::Error> {
let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?;
- let target = Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit {
- name: name.clone(),
- err,
- })?);
+ let target =
+ super::Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit {
+ name: name.clone(),
+ err,
+ })?);
Ok(Self { name, target })
}
}
- 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 {
- Commit {
+ Self {
id: commit.id().into(),
}
}
diff --git a/crates/radicle-cob/src/signatures.rs b/crates/radicle-cob/src/signatures.rs
index 967eb1e70..643cdd210 100644
--- a/crates/radicle-cob/src/signatures.rs
+++ b/crates/radicle-cob/src/signatures.rs
@@ -8,9 +8,9 @@ use std::{
};
use crypto::{ssh, PublicKey};
-use git_ext::commit::{
+use metadata::commit::{
headers::Signature::{Pgp, Ssh},
- Commit,
+ CommitData,
};
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;
- fn try_from(value: &Commit) -> Result<Self, Self::Error> {
+ fn try_from(value: &CommitData<Tree, Parent>) -> Result<Self, Self::Error> {
value
.signatures()
.filter_map(|signature| {
diff --git a/crates/radicle-cob/src/signatures/error.rs b/crates/radicle-cob/src/signatures/error.rs
index 45e5cacca..e10e8e66c 100644
--- a/crates/radicle-cob/src/signatures/error.rs
+++ b/crates/radicle-cob/src/signatures/error.rs
@@ -1,6 +1,6 @@
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-use radicle_crypto::ssh::ExtendedSignatureError;
+use crypto::ssh::ExtendedSignatureError;
use thiserror::Error;
#[derive(Debug, Error)]
diff --git a/crates/radicle-cob/src/test.rs b/crates/radicle-cob/src/test.rs
index 2c1164d3c..7562ae42f 100644
--- a/crates/radicle-cob/src/test.rs
+++ b/crates/radicle-cob/src/test.rs
@@ -1,7 +1,11 @@
+#[cfg(feature = "git2")]
pub mod identity;
+#[cfg(feature = "git2")]
pub use identity::{Person, Project, RemoteProject};
+#[cfg(feature = "git2")]
pub mod storage;
+#[cfg(feature = "git2")]
pub use storage::Storage;
pub mod arbitrary;
diff --git a/crates/radicle-cob/src/test/arbitrary.rs b/crates/radicle-cob/src/test/arbitrary.rs
index 123166a9a..4a85f063d 100644
--- a/crates/radicle-cob/src/test/arbitrary.rs
+++ b/crates/radicle-cob/src/test/arbitrary.rs
@@ -26,11 +26,7 @@ impl Arbitrary for TypeName {
impl Arbitrary for ObjectId {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
- let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g));
- let bytes = iter::repeat_with(|| rng.u8(..))
- .take(20)
- .collect::<Vec<_>>();
- Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap())
+ Self::from(oid::Oid::arbitrary(g))
}
}
diff --git a/crates/radicle-cob/src/test/identity.rs b/crates/radicle-cob/src/test/identity.rs
index 6ce679bdc..3df7b869b 100644
--- a/crates/radicle-cob/src/test/identity.rs
+++ b/crates/radicle-cob/src/test/identity.rs
@@ -1,7 +1,9 @@
pub mod project;
pub use project::{Project, RemoteProject};
+#[cfg(feature = "git2")]
pub mod person;
+#[cfg(feature = "git2")]
pub use person::Person;
#[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/crates/radicle-cob/src/test/identity/person.rs b/crates/radicle-cob/src/test/identity/person.rs
index 2ec89c02c..5e85176fa 100644
--- a/crates/radicle-cob/src/test/identity/person.rs
+++ b/crates/radicle-cob/src/test/identity/person.rs
@@ -1,4 +1,4 @@
-use git_ext::Oid;
+use oid::Oid;
use serde::{Deserialize, Serialize};
use crate::test::storage::{self, Storage};
diff --git a/crates/radicle-cob/src/test/identity/project.rs b/crates/radicle-cob/src/test/identity/project.rs
index 1b813ee0a..06ec1f256 100644
--- a/crates/radicle-cob/src/test/identity/project.rs
+++ b/crates/radicle-cob/src/test/identity/project.rs
@@ -1,6 +1,6 @@
use std::collections::BTreeSet;
-use git_ext::Oid;
+use oid::Oid;
use serde::{Deserialize, Serialize};
use crate::test;
diff --git a/crates/radicle-cob/src/test/storage.rs b/crates/radicle-cob/src/test/storage.rs
index af084c403..78f1f6974 100644
--- a/crates/radicle-cob/src/test/storage.rs
+++ b/crates/radicle-cob/src/test/storage.rs
@@ -1,6 +1,8 @@
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 crate::{
@@ -29,7 +31,7 @@ pub mod error {
#[error(transparent)]
Git(#[from] git2::Error),
#[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)
}
- 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
.as_raw()
- .find_commit(**id)?
+ .find_commit(id.into())?
.parent_ids()
- .map(git_ext::Oid::from)
+ .map(oid::Oid::from)
.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)
}
}
diff --git a/crates/radicle-cob/src/tests.rs b/crates/radicle-cob/src/tests.rs
index 683e23f2a..8e08166a4 100644
--- a/crates/radicle-cob/src/tests.rs
+++ b/crates/radicle-cob/src/tests.rs
@@ -1,238 +1,274 @@
-use std::ops::ControlFlow;
-
-use crypto::test::signer::MockSigner;
-use crypto::{PublicKey, Signer};
-use git_ext::ref_format::{refname, Component, RefString};
-use nonempty::{nonempty, NonEmpty};
-use qcheck::Arbitrary;
-
-use crate::{
- create, get, list, object, test::arbitrary::Invalid, update, Create, Entry, ObjectId, TypeName,
- Update, Updated, Version,
-};
-
-use super::test;
-
-#[test]
-fn roundtrip() {
- let storage = test::Storage::new();
- let signer = gen::<MockSigner>(1);
- let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
- let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
- let proj = test::RemoteProject {
- project: proj,
- person: terry,
- };
- let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
- let cob = create::<NonEmpty<Entry>, _, _>(
- &storage,
- &signer,
- Some(proj.project.content_id),
- vec![],
- signer.public_key(),
- Create {
- contents: nonempty!(Vec::new()),
- type_name: typename.clone(),
- message: "creating xyz.rad.issue".to_string(),
- embeds: vec![],
- version: Version::default(),
- },
- )
- .unwrap();
-
- let expected = get(&storage, &typename, cob.id())
- .unwrap()
- .expect("BUG: cob was missing");
-
- assert_eq!(cob, expected);
-}
+use fmt::{Component, RefString};
-#[test]
-fn list_cobs() {
- let storage = test::Storage::new();
- let signer = gen::<MockSigner>(1);
- let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
- let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
- let proj = test::RemoteProject {
- project: proj,
- person: terry,
- };
- let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
- let issue_1 = create::<NonEmpty<Entry>, _, _>(
- &storage,
- &signer,
- Some(proj.project.content_id),
- vec![],
- signer.public_key(),
- Create {
- contents: nonempty!(b"issue 1".to_vec()),
- type_name: typename.clone(),
- message: "creating xyz.rad.issue".to_string(),
- embeds: vec![],
- version: Version::default(),
- },
- )
- .unwrap();
-
- let issue_2 = create(
- &storage,
- &signer,
- Some(proj.project.content_id),
- vec![],
- signer.public_key(),
- Create {
- contents: nonempty!(b"issue 2".to_vec()),
- type_name: typename.clone(),
- message: "commenting xyz.rad.issue".to_string(),
- embeds: vec![],
- version: Version::default(),
- },
- )
- .unwrap();
-
- let mut expected = list(&storage, &typename).unwrap();
- expected.sort_by(|x, y| x.id().cmp(y.id()));
-
- let mut actual = vec![issue_1, issue_2];
- actual.sort_by(|x, y| x.id().cmp(y.id()));
-
- assert_eq!(actual, expected);
-}
+use radicle_git_ref_format::refname;
-#[test]
-fn update_cob() {
- let storage = test::Storage::new();
- let signer = gen::<MockSigner>(1);
- let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
- let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
- let proj = test::RemoteProject {
- project: proj,
- person: terry,
- };
- let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
- let cob = create::<NonEmpty<Entry>, _, _>(
- &storage,
- &signer,
- Some(proj.project.content_id),
- vec![],
- signer.public_key(),
- Create {
- contents: nonempty!(Vec::new()),
- type_name: typename.clone(),
- message: "creating xyz.rad.issue".to_string(),
- embeds: vec![],
- version: Version::default(),
- },
- )
- .unwrap();
-
- let not_expected = get::<NonEmpty<Entry>, _>(&storage, &typename, cob.id())
- .unwrap()
- .expect("BUG: cob was missing");
-
- let Updated { object, .. } = update(
- &storage,
- &signer,
- Some(proj.project.content_id),
- vec![],
- signer.public_key(),
- Update {
- changes: nonempty!(b"issue 1".to_vec()),
- object_id: *cob.id(),
- type_name: typename.clone(),
- embeds: vec![],
- message: "commenting xyz.rad.issue".to_string(),
- },
- )
- .unwrap();
-
- let expected = get(&storage, &typename, object.id())
- .unwrap()
- .expect("BUG: cob was missing");
-
- assert_ne!(object, not_expected);
- assert_eq!(object, expected, "{object:#?} {expected:#?}");
-}
+use crate::{object, test::arbitrary::Invalid, ObjectId, TypeName};
-#[test]
-fn traverse_cobs() {
- let storage = test::Storage::new();
- let neil_signer = gen::<MockSigner>(2);
- let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap();
- let terry_signer = gen::<MockSigner>(1);
- let terry = test::Person::new(&storage, "pratchett", *terry_signer.public_key()).unwrap();
- let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap();
- let terry_proj = test::RemoteProject {
- project: proj.clone(),
- person: terry,
- };
- let neil_proj = test::RemoteProject {
- project: proj,
- person: neil,
- };
- let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
- let cob = create::<NonEmpty<Entry>, _, _>(
- &storage,
- &terry_signer,
- Some(terry_proj.project.content_id),
- vec![],
- terry_signer.public_key(),
- Create {
- contents: nonempty!(b"issue 1".to_vec()),
- type_name: typename.clone(),
- message: "creating xyz.rad.issue".to_string(),
- embeds: vec![],
- version: Version::default(),
- },
- )
- .unwrap();
- copy_to(
- storage.as_raw(),
- terry_signer.public_key(),
- &neil_proj,
- &typename,
- *cob.id(),
- )
- .unwrap();
-
- let Updated { object, .. } = update::<NonEmpty<Entry>, _, _>(
- &storage,
- &neil_signer,
- Some(neil_proj.project.content_id),
- vec![],
- neil_signer.public_key(),
- Update {
- changes: nonempty!(b"issue 2".to_vec()),
- object_id: *cob.id(),
- type_name: typename,
- embeds: vec![],
- message: "commenting on xyz.rad.issue".to_string(),
- },
- )
- .unwrap();
-
- let root = object.history.root().id;
- // traverse over the history and filter by changes that were only authorized by terry
- let contents = object
- .history()
- .traverse(Vec::new(), &[root], |mut acc, _, entry| {
- if entry.author() == terry_signer.public_key() {
- acc.push(entry.contents().head.clone());
- }
- ControlFlow::Continue(acc)
- });
+#[cfg(feature = "git2")]
+mod git {
+ use std::ops::ControlFlow;
- assert_eq!(contents, vec![b"issue 1".to_vec()]);
+ use crypto::test::signer::MockSigner;
+ use crypto::{PublicKey, Signer};
+ use nonempty::{nonempty, NonEmpty};
+ use qcheck::Arbitrary;
- // traverse over the history and filter by changes that were only authorized by neil
- let contents = object
- .history()
- .traverse(Vec::new(), &[root], |mut acc, _, entry| {
- acc.push(entry.contents().head.clone());
- ControlFlow::Continue(acc)
- });
+ use crate::{
+ create, get, list, update, Create, Entry, ObjectId, TypeName, Update, Updated, Version,
+ };
- assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
+ use crate::test;
+
+ #[test]
+ fn roundtrip() {
+ let storage = test::Storage::new();
+ let signer = gen::<MockSigner>(1);
+ let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
+ let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
+ let proj = test::RemoteProject {
+ project: proj,
+ person: terry,
+ };
+ let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
+ let cob = create::<NonEmpty<Entry>, _, _>(
+ &storage,
+ &signer,
+ Some(proj.project.content_id),
+ vec![],
+ signer.public_key(),
+ Create {
+ contents: nonempty!(Vec::new()),
+ type_name: typename.clone(),
+ message: "creating xyz.rad.issue".to_string(),
+ embeds: vec![],
+ version: Version::default(),
+ },
+ )
+ .unwrap();
+
+ let expected = get(&storage, &typename, cob.id())
+ .unwrap()
+ .expect("BUG: cob was missing");
+
+ assert_eq!(cob, expected);
+ }
+
+ #[test]
+ fn list_cobs() {
+ let storage = test::Storage::new();
+ let signer = gen::<MockSigner>(1);
+ let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
+ let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
+ let proj = test::RemoteProject {
+ project: proj,
+ person: terry,
+ };
+ let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
+ let issue_1 = create::<NonEmpty<Entry>, _, _>(
+ &storage,
+ &signer,
+ Some(proj.project.content_id),
+ vec![],
+ signer.public_key(),
+ Create {
+ contents: nonempty!(b"issue 1".to_vec()),
+ type_name: typename.clone(),
+ message: "creating xyz.rad.issue".to_string(),
+ embeds: vec![],
+ version: Version::default(),
+ },
+ )
+ .unwrap();
+
+ let issue_2 = create(
+ &storage,
+ &signer,
+ Some(proj.project.content_id),
+ vec![],
+ signer.public_key(),
+ Create {
+ contents: nonempty!(b"issue 2".to_vec()),
+ type_name: typename.clone(),
+ message: "commenting xyz.rad.issue".to_string(),
+ embeds: vec![],
+ version: Version::default(),
+ },
+ )
+ .unwrap();
+
+ let mut expected = list(&storage, &typename).unwrap();
+ expected.sort_by(|x, y| x.id().cmp(y.id()));
+
+ let mut actual = vec![issue_1, issue_2];
+ actual.sort_by(|x, y| x.id().cmp(y.id()));
+
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn update_cob() {
+ let storage = test::Storage::new();
+ let signer = gen::<MockSigner>(1);
+ let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
+ let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
+ let proj = test::RemoteProject {
+ project: proj,
+ person: terry,
+ };
+ let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
+ let cob = create::<NonEmpty<Entry>, _, _>(
+ &storage,
+ &signer,
+ Some(proj.project.content_id),
+ vec![],
+ signer.public_key(),
+ Create {
+ contents: nonempty!(Vec::new()),
+ type_name: typename.clone(),
+ message: "creating xyz.rad.issue".to_string(),
+ embeds: vec![],
+ version: Version::default(),
+ },
+ )
+ .unwrap();
+
+ let not_expected = get::<NonEmpty<Entry>, _>(&storage, &typename, cob.id())
+ .unwrap()
+ .expect("BUG: cob was missing");
+
+ let Updated { object, .. } = update(
+ &storage,
+ &signer,
+ Some(proj.project.content_id),
+ vec![],
+ signer.public_key(),
+ Update {
+ changes: nonempty!(b"issue 1".to_vec()),
+ object_id: *cob.id(),
+ type_name: typename.clone(),
+ embeds: vec![],
+ message: "commenting xyz.rad.issue".to_string(),
+ },
+ )
+ .unwrap();
+
+ let expected = get(&storage, &typename, object.id())
+ .unwrap()
+ .expect("BUG: cob was missing");
+
+ assert_ne!(object, not_expected);
+ assert_eq!(object, expected, "{object:#?} {expected:#?}");
+ }
+
+ #[test]
+ fn traverse_cobs() {
+ let storage = test::Storage::new();
+ let neil_signer = gen::<MockSigner>(2);
+ let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap();
+ let terry_signer = gen::<MockSigner>(1);
+ let terry = test::Person::new(&storage, "pratchett", *terry_signer.public_key()).unwrap();
+ let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap();
+ let terry_proj = test::RemoteProject {
+ project: proj.clone(),
+ person: terry,
+ };
+ let neil_proj = test::RemoteProject {
+ project: proj,
+ person: neil,
+ };
+ let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
+ let cob = create::<NonEmpty<Entry>, _, _>(
+ &storage,
+ &terry_signer,
+ Some(terry_proj.project.content_id),
+ vec![],
+ terry_signer.public_key(),
+ Create {
+ contents: nonempty!(b"issue 1".to_vec()),
+ type_name: typename.clone(),
+ message: "creating xyz.rad.issue".to_string(),
+ embeds: vec![],
+ version: Version::default(),
+ },
+ )
+ .unwrap();
+ copy_to(
+ storage.as_raw(),
+ terry_signer.public_key(),
+ &neil_proj,
+ &typename,
+ *cob.id(),
+ )
+ .unwrap();
+
+ let Updated { object, .. } = update::<NonEmpty<Entry>, _, _>(
+ &storage,
+ &neil_signer,
+ Some(neil_proj.project.content_id),
+ vec![],
+ neil_signer.public_key(),
+ Update {
+ changes: nonempty!(b"issue 2".to_vec()),
+ object_id: *cob.id(),
+ type_name: typename,
+ embeds: vec![],
+ message: "commenting on xyz.rad.issue".to_string(),
+ },
+ )
+ .unwrap();
+
+ let root = object.history.root().id;
+ // traverse over the history and filter by changes that were only authorized by terry
+ let contents = object
+ .history()
+ .traverse(Vec::new(), &[root], |mut acc, _, entry| {
+ if entry.author() == terry_signer.public_key() {
+ acc.push(entry.contents().head.clone());
+ }
+ ControlFlow::Continue(acc)
+ });
+
+ assert_eq!(contents, vec![b"issue 1".to_vec()]);
+
+ // traverse over the history and filter by changes that were only authorized by neil
+ let contents = object
+ .history()
+ .traverse(Vec::new(), &[root], |mut acc, _, entry| {
+ acc.push(entry.contents().head.clone());
+ ControlFlow::Continue(acc)
+ });
+
+ 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]
@@ -297,32 +333,3 @@ fn invalid_parse_refstr(oid: Invalid<ObjectId>, typename: TypeName) {
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(())
-}
diff --git a/crates/radicle-cob/src/trailers.rs b/crates/radicle-cob/src/trailers.rs
index 2403a8b5a..ed65b4b57 100644
--- a/crates/radicle-cob/src/trailers.rs
+++ b/crates/radicle-cob/src/trailers.rs
@@ -1,6 +1,6 @@
// 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 _;
pub mod error {
@@ -12,21 +12,22 @@ pub mod error {
WrongToken,
#[error("no value for Rad-Resource")]
NoValue,
- #[error("invalid git OID")]
- InvalidOid,
+ /// Invalid object ID.
+ #[error("invalid oid: {0}")]
+ InvalidOid(#[from] radicle_oid::str::ParseOidError),
}
}
/// Commit trailer for COB commits.
pub enum CommitTrailer {
/// Points to the owning resource.
- Resource(git2::Oid),
+ Resource(oid::Oid),
/// Points to a related change.
- Related(git2::Oid),
+ Related(oid::Oid),
}
impl CommitTrailer {
- pub fn oid(&self) -> git2::Oid {
+ pub fn oid(&self) -> oid::Oid {
match self {
Self::Resource(oid) => *oid,
Self::Related(oid) => *oid,
@@ -38,12 +39,11 @@ impl TryFrom<&Trailer<'_>> for CommitTrailer {
type Error = error::InvalidResourceTrailer;
fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
- let ext_oid =
- git_ext::Oid::try_from(value.as_ref()).map_err(|_| Self::Error::InvalidOid)?;
+ let oid = value.as_ref().parse::<oid::Oid>()?;
if token.deref() == "Rad-Resource" {
- Ok(CommitTrailer::Resource(ext_oid.into()))
+ Ok(CommitTrailer::Resource(oid))
} else if token.deref() == "Rad-Related" {
- Ok(CommitTrailer::Related(ext_oid.into()))
+ Ok(CommitTrailer::Related(oid))
} else {
Err(Self::Error::WrongToken)
}
diff --git a/crates/radicle-cob/src/type_name.rs b/crates/radicle-cob/src/type_name.rs
index 20a503145..3e548aa2f 100644
--- a/crates/radicle-cob/src/type_name.rs
+++ b/crates/radicle-cob/src/type_name.rs
@@ -1,8 +1,8 @@
// 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 thiserror::Error;
@@ -24,8 +24,8 @@ impl TypeName {
}
}
-impl fmt::Display for TypeName {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+impl std::fmt::Display for TypeName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str())
}
}
diff --git a/crates/radicle-fetch/Cargo.toml b/crates/radicle-fetch/Cargo.toml
index deaab2541..2385bd288 100644
--- a/crates/radicle-fetch/Cargo.toml
+++ b/crates/radicle-fetch/Cargo.toml
@@ -20,5 +20,6 @@ gix-transport = { version = "0.44.0", features = ["blocking-client"] }
log = { workspace = true, features = ["std"] }
nonempty = { 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 }
\ No newline at end of file
diff --git a/crates/radicle-fetch/src/git.rs b/crates/radicle-fetch/src/git.rs
index 7b721b959..5d4bf04b9 100644
--- a/crates/radicle-fetch/src/git.rs
+++ b/crates/radicle-fetch/src/git.rs
@@ -3,21 +3,3 @@ pub(crate) mod packfile;
pub(crate) mod repository;
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()))
- }
-}
diff --git a/crates/radicle-fetch/src/git/mem.rs b/crates/radicle-fetch/src/git/mem.rs
index 598c93c31..8849b9731 100644
--- a/crates/radicle-fetch/src/git/mem.rs
+++ b/crates/radicle-fetch/src/git/mem.rs
@@ -1,6 +1,7 @@
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 super::refs::{Applied, RefUpdate, Update};
diff --git a/crates/radicle-fetch/src/git/refs/update.rs b/crates/radicle-fetch/src/git/refs/update.rs
index e3f54e2e5..46e2ede68 100644
--- a/crates/radicle-fetch/src/git/refs/update.rs
+++ b/crates/radicle-fetch/src/git/refs/update.rs
@@ -18,7 +18,8 @@
use std::collections::BTreeMap;
use either::Either;
-use radicle::git::{Namespaced, Oid, Qualified};
+use radicle::git::fmt::{Namespaced, Qualified};
+use radicle::git::Oid;
use radicle::prelude::PublicKey;
pub use radicle::storage::RefUpdate;
diff --git a/crates/radicle-fetch/src/git/repository.rs b/crates/radicle-fetch/src/git/repository.rs
index e4e8ca8fe..92f1a49a9 100644
--- a/crates/radicle-fetch/src/git/repository.rs
+++ b/crates/radicle-fetch/src/git/repository.rs
@@ -2,7 +2,11 @@ pub mod error;
use either::Either;
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 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
/// - Attempting to find the object fails
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
.peel(git::raw::ObjectType::Commit)
.map_err(|err| error::Ancestry::Peel { oid, err })?
@@ -80,7 +84,7 @@ pub fn ahead_behind(
let (ahead, behind) = repo
.backend
- .graph_ahead_behind(*new_commit, *old_commit)
+ .graph_ahead_behind(new_commit.into(), old_commit.into())
.map_err(|err| error::Ancestry::Check {
old: old_commit,
new: new_commit,
@@ -100,7 +104,7 @@ pub fn refname_to_id<'a, N>(repo: &Repository, refname: N) -> Result<Option<Oid>
where
N: Into<Qualified<'a>>,
{
- use radicle::git::raw::ErrorCode::NotFound;
+ use git::raw::ErrorCode::NotFound;
let refname = refname.into();
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,
// we can always skip the update.
return Ok(RefUpdate::Skipped {
@@ -193,7 +197,7 @@ fn direct<'a>(
let target = repo
.backend
- .find_object(*target, ANY_KIND)
+ .find_object(target.into(), ANY_KIND)
.map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?;
match (prev.kind(), target.kind()) {
@@ -276,7 +280,7 @@ fn prune<'a>(
name: Namespaced<'a>,
prev: Either<Oid, Qualified<'a>>,
) -> Result<Updated<'a>, error::Update> {
- use radicle::git::raw::ObjectType;
+ use git::raw::ObjectType;
match find(repo, &name)? {
Some(mut r) => {
diff --git a/crates/radicle-fetch/src/git/repository/error.rs b/crates/radicle-fetch/src/git/repository/error.rs
index 268e8f6f3..49f5f3d00 100644
--- a/crates/radicle-fetch/src/git/repository/error.rs
+++ b/crates/radicle-fetch/src/git/repository/error.rs
@@ -1,9 +1,13 @@
-use radicle::git::{ext, raw, Namespaced, Oid, Qualified};
+use radicle::git::{
+ self,
+ fmt::{Namespaced, Qualified},
+ Oid,
+};
use thiserror::Error;
#[derive(Debug, Error)]
#[error("could not open Git ODB")]
-pub struct Contains(#[source] pub raw::Error);
+pub struct Contains(#[source] pub git::raw::Error);
#[derive(Debug, Error)]
pub enum Ancestry {
@@ -14,19 +18,19 @@ pub enum Ancestry {
old: Oid,
new: Oid,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
#[error("failed to peel object to commit {oid}: {err}")]
Peel {
oid: Oid,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
#[error("failed to find object {oid}: {err}")]
Object {
oid: Oid,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
}
@@ -35,15 +39,15 @@ pub enum Ancestry {
pub struct Resolve {
pub name: Qualified<'static>,
#[source]
- pub err: raw::Error,
+ pub err: git::raw::Error,
}
#[derive(Debug, Error)]
#[error("failed to scan for refs matching {pattern}")]
pub struct Scan {
- pub pattern: radicle::git::PatternString,
+ pub pattern: radicle::git::fmt::refspec::PatternString,
#[source]
- pub err: ext::Error,
+ pub err: git::raw::Error,
}
#[derive(Debug, Error)]
@@ -55,19 +59,19 @@ pub enum Update {
name: Namespaced<'static>,
target: Oid,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
#[error("failed to delete reference {name}")]
Delete {
name: Namespaced<'static>,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
#[error("failed to find ref {name}")]
Find {
name: Namespaced<'static>,
#[source]
- err: raw::Error,
+ err: git::raw::Error,
},
#[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
NonFF {
@@ -76,7 +80,7 @@ pub enum Update {
cur: Oid,
},
#[error("failed to peel ref to object")]
- Peel(#[source] raw::Error),
+ Peel(#[source] git::raw::Error),
#[error(transparent)]
Resolve(#[from] Resolve),
diff --git a/crates/radicle-fetch/src/refs.rs b/crates/radicle-fetch/src/refs.rs
index f24ef3af3..504730bb6 100644
--- a/crates/radicle-fetch/src/refs.rs
+++ b/crates/radicle-fetch/src/refs.rs
@@ -1,7 +1,11 @@
use bstr::{BString, ByteSlice};
use either::Either;
use radicle::crypto::PublicKey;
-use radicle::git::{self, Component, Namespaced, Oid, Qualified};
+use radicle::git::{
+ self,
+ fmt::{Component, Namespaced, Qualified},
+ Oid,
+};
use thiserror::Error;
pub use radicle::git::refs::storage::Special;
@@ -26,7 +30,6 @@ pub enum Error {
pub(crate) fn unpack_ref<'a>(
r: gix_protocol::handshake::Ref,
) -> Result<(ReceivedRefname<'a>, Oid), Error> {
- use crate::git::oid;
use gix_protocol::handshake::Ref;
match r {
@@ -43,7 +46,7 @@ pub(crate) fn unpack_ref<'a>(
full_ref_name,
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, .. } => {
unreachable!("BUG: unborn ref {}", full_ref_name)
}
diff --git a/crates/radicle-fetch/src/stage.rs b/crates/radicle-fetch/src/stage.rs
index 88b7cfbd3..d5ff17537 100644
--- a/crates/radicle-fetch/src/stage.rs
+++ b/crates/radicle-fetch/src/stage.rs
@@ -37,7 +37,7 @@ use either::Either;
use gix_protocol::handshake::Ref;
use nonempty::NonEmpty;
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::refs::{RefsAt, Special};
use radicle::storage::ReadRepository;
@@ -52,7 +52,7 @@ use crate::{policy, refs};
pub mod error {
use radicle::crypto::PublicKey;
- use radicle::git::RefString;
+ use radicle::git::fmt::RefString;
use thiserror::Error;
use crate::transport::WantsHavesError;
diff --git a/crates/radicle-fetch/src/state.rs b/crates/radicle-fetch/src/state.rs
index e8c4c9482..2449b6039 100644
--- a/crates/radicle-fetch/src/state.rs
+++ b/crates/radicle-fetch/src/state.rs
@@ -3,7 +3,7 @@ use std::time::Instant;
use gix_protocol::handshake;
use radicle::crypto::PublicKey;
-use radicle::git::{Oid, Qualified};
+use radicle::git::{fmt::Qualified, Oid};
use radicle::identity::{Did, Doc, DocError};
use radicle::prelude::Verified;
diff --git a/crates/radicle-fetch/src/transport.rs b/crates/radicle-fetch/src/transport.rs
index 8f7bf2d28..d46c7ec57 100644
--- a/crates/radicle-fetch/src/transport.rs
+++ b/crates/radicle-fetch/src/transport.rs
@@ -13,12 +13,11 @@ use gix_protocol::handshake;
use gix_transport::client;
use gix_transport::Protocol;
use gix_transport::Service;
+use radicle::git::fmt::Qualified;
use radicle::git::Oid;
-use radicle::git::Qualified;
use radicle::storage::git::Repository;
use thiserror::Error;
-use crate::git::oid;
use crate::git::packfile::Keepfile;
use crate::git::repository;
@@ -163,7 +162,7 @@ where
let idx = File::at(pack_path, gix_hash::Kind::Sha1)?;
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));
}
}
diff --git a/crates/radicle-fetch/src/transport/fetch.rs b/crates/radicle-fetch/src/transport/fetch.rs
index 5a038b34d..61538ffa0 100644
--- a/crates/radicle-fetch/src/transport/fetch.rs
+++ b/crates/radicle-fetch/src/transport/fetch.rs
@@ -9,7 +9,7 @@ use gix_protocol::fetch::negotiate::one_round::State;
use gix_protocol::handshake;
use gix_protocol::handshake::Ref;
-use crate::git::{oid, packfile};
+use crate::git::packfile;
use super::{agent_name, Connection, WantsHaves};
@@ -109,7 +109,7 @@ impl fetch::Negotiate for Negotiate {
) -> bool {
let mut has_want = false;
for oid in &self.wants_haves.wants {
- arguments.want(oid::to_object_id(*oid));
+ arguments.want(oid);
has_want = true;
}
has_want
@@ -126,7 +126,7 @@ impl fetch::Negotiate for Negotiate {
_previous_response: Option<&fetch::Response>,
) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> {
for oid in &self.wants_haves.haves {
- arguments.have(oid::to_object_id(*oid));
+ arguments.have(oid);
}
let round = fetch::negotiate::Round {
diff --git a/crates/radicle-node/Cargo.toml b/crates/radicle-node/Cargo.toml
index b059dbe19..910607dae 100644
--- a/crates/radicle-node/Cargo.toml
+++ b/crates/radicle-node/Cargo.toml
@@ -31,9 +31,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] }
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-signals = { workspace = true }
sqlite = { workspace = true, features = ["bundled"] }
diff --git a/crates/radicle-node/src/test/handle.rs b/crates/radicle-node/src/test/handle.rs
index 12cb6735d..0c5b6e233 100644
--- a/crates/radicle-node/src/test/handle.rs
+++ b/crates/radicle-node/src/test/handle.rs
@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
use std::time;
use radicle::crypto::PublicKey;
-use radicle::git;
+use radicle::git::Oid;
use radicle::storage::refs::RefsAt;
use crate::identity::RepoId;
@@ -108,7 +108,7 @@ impl radicle::node::Handle for Handle {
Ok(RefsAt {
remote: self.nid()?,
- at: git::raw::Oid::zero().into(),
+ at: Oid::sha1_zero(),
})
}
diff --git a/crates/radicle-node/src/test/node.rs b/crates/radicle-node/src/test/node.rs
index 0b6ef0934..f15109d73 100644
--- a/crates/radicle-node/src/test/node.rs
+++ b/crates/radicle-node/src/test/node.rs
@@ -16,7 +16,7 @@ use radicle::crypto::ssh::keystore::MemorySigner;
use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::Signature;
use radicle::git;
-use radicle::git::refname;
+use radicle::git::fmt::refname;
use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress;
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.
///
/// 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;
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.
- 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() {
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((
- git::lit::refs_heads(&name).into(),
- git::lit::refs_heads(&name).into(),
+ git::fmt::lit::refs_heads(&name).into(),
+ git::fmt::lit::refs_heads(&name).into(),
));
}
git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
diff --git a/crates/radicle-node/src/test/peer.rs b/crates/radicle-node/src/test/peer.rs
index 59ffe1e82..72f449931 100644
--- a/crates/radicle-node/src/test/peer.rs
+++ b/crates/radicle-node/src/test/peer.rs
@@ -138,7 +138,7 @@ impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
&repo,
name.try_into().unwrap(),
description,
- radicle::git::refname!("master"),
+ radicle::git::fmt::refname!("master"),
Visibility::default(),
self.signer(),
self.storage(),
diff --git a/crates/radicle-node/src/tests.rs b/crates/radicle-node/src/tests.rs
index bb232db73..5b6ffff5f 100644
--- a/crates/radicle-node/src/tests.rs
+++ b/crates/radicle-node/src/tests.rs
@@ -1567,7 +1567,7 @@ fn test_queued_fetch_from_ann_same_rid() {
let refname = carol
.id()
.to_namespace()
- .join(git::refname!("refs/sigrefs"));
+ .join(git::fmt::refname!("refs/sigrefs"));
// Finish the 1st fetch.
// Ensure the ref is in the storage and cache.
@@ -1749,7 +1749,7 @@ fn test_init_and_seed() {
&repo,
"alice".try_into().unwrap(),
"alice's repo",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
alice.signer(),
alice.storage(),
diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs
index 6a8b68075..3082ffe1e 100644
--- a/crates/radicle-node/src/tests/e2e.rs
+++ b/crates/radicle-node/src/tests/e2e.rs
@@ -3,6 +3,7 @@ use std::{collections::HashSet, thread, time};
use radicle::cob::Title;
use test_log::test;
+use radicle::git::raw::ErrorExt as _;
use radicle::node::device::Device;
use radicle::node::policy::Scope;
use radicle::node::Event;
@@ -248,7 +249,7 @@ fn test_replication_ref_in_sigrefs() {
bob.storage
.repository_mut(acme)
.unwrap()
- .reference(&bob.id, &git::qualified!("refs/heads/master"))
+ .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
.unwrap()
.delete()
.unwrap();
@@ -271,7 +272,7 @@ fn test_replication_ref_in_sigrefs() {
.storage
.repository(acme)
.unwrap()
- .reference(&bob.id, &git::qualified!("refs/heads/master"))
+ .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
.is_ok(),
"refs/namespaces/{}/refs/heads/master does not exist",
bob.id
@@ -292,8 +293,8 @@ fn test_replication_invalid() {
// Create some unsigned refs for Carol in Bob's storage.
repo.raw()
.reference(
- &git::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
- *head,
+ &git::fmt::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
+ head.into(),
true,
&String::default(),
)
@@ -579,7 +580,7 @@ fn test_clone() {
.canonical_head()
.unwrap();
- assert_eq!(oid, *canonical);
+ assert_eq!(canonical, oid);
// Make sure that bob has refs/rad/id set
assert!(bob
@@ -1193,7 +1194,6 @@ fn missing_default_branch() {
#[test]
fn missing_delegate_default_branch() {
- use radicle::git::raw;
use radicle::identity::Identity;
use radicle::storage::git::Repository;
let tmp = tempfile::tempdir().unwrap();
@@ -1238,7 +1238,7 @@ fn missing_delegate_default_branch() {
);
assert!(matches!(
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();
assert!(result.is_success());
- let default_branch: git::Qualified = {
+ let default_branch: git::fmt::Qualified = {
let repo = alice.storage.repository(rid).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);
diff --git a/crates/radicle-node/src/worker/fetch.rs b/crates/radicle-node/src/worker/fetch.rs
index a52f255a3..da4256a35 100644
--- a/crates/radicle-node/src/worker/fetch.rs
+++ b/crates/radicle-node/src/worker/fetch.rs
@@ -196,7 +196,7 @@ fn notify(
// for sigref verification.
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() {
// Don't notify about patch branches, since we already get
// notifications about patch updates.
@@ -400,7 +400,7 @@ fn set_canonical_refs(
let oid = object.id();
if let Err(e) = repo.backend.reference(
refname.clone().as_str(),
- *oid,
+ oid.into(),
true,
"set-canonical-reference from fetch (radicle)",
) {
diff --git a/crates/radicle-protocol/Cargo.toml b/crates/radicle-protocol/Cargo.toml
index 7d306d04a..d8aeff0ef 100644
--- a/crates/radicle-protocol/Cargo.toml
+++ b/crates/radicle-protocol/Cargo.toml
@@ -23,7 +23,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] }
radicle-fetch = { workspace = true }
-radicle-git-ext = { workspace = true, features = ["serde"] }
sqlite = { workspace = true, features = ["bundled"] }
scrypt = { version = "0.11.0", default-features = false }
serde = { workspace = true, features = ["derive"] }
diff --git a/crates/radicle-protocol/src/service.rs b/crates/radicle-protocol/src/service.rs
index 39ddaa909..c0202a453 100644
--- a/crates/radicle-protocol/src/service.rs
+++ b/crates/radicle-protocol/src/service.rs
@@ -186,8 +186,6 @@ pub enum Error {
#[error(transparent)]
Git(#[from] radicle::git::raw::Error),
#[error(transparent)]
- GitExt(#[from] radicle::git::ext::Error),
- #[error(transparent)]
Storage(#[from] storage::Error),
#[error(transparent)]
Gossip(#[from] gossip::Error),
diff --git a/crates/radicle-protocol/src/wire.rs b/crates/radicle-protocol/src/wire.rs
index 577218e8d..9f949536c 100644
--- a/crates/radicle-protocol/src/wire.rs
+++ b/crates/radicle-protocol/src/wire.rs
@@ -20,6 +20,7 @@ use cyphernet::addr::tor;
use radicle::crypto::{PublicKey, Signature, Unverified};
use radicle::git;
use radicle::git::fmt;
+use radicle::git::raw;
use radicle::identity::RepoId;
use radicle::node;
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) {
self.as_str().encode(buf)
}
@@ -300,7 +301,8 @@ impl Encode for Signature {
impl Encode for git::Oid {
fn encode(&self, buf: &mut impl BufMut) {
// 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 {
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)?;
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> {
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 {
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;
@@ -378,7 +380,7 @@ impl Decode for git::Oid {
}
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);
Ok(oid)
@@ -627,7 +629,7 @@ mod tests {
#[quickcheck]
fn prop_oid(input: [u8; 20]) {
- roundtrip(git::Oid::try_from(input.as_slice()).unwrap());
+ roundtrip(git::Oid::from_sha1(input));
}
#[test]
diff --git a/crates/radicle-protocol/src/worker/fetch.rs b/crates/radicle-protocol/src/worker/fetch.rs
index 1ca13f4b2..f91c54bb2 100644
--- a/crates/radicle-protocol/src/worker/fetch.rs
+++ b/crates/radicle-protocol/src/worker/fetch.rs
@@ -36,12 +36,12 @@ impl FetchResult {
/// corresponding targets.
#[derive(Clone, Default, Debug)]
pub struct UpdatedCanonicalRefs {
- inner: BTreeMap<git::Qualified<'static>, git::Oid>,
+ inner: BTreeMap<git::fmt::Qualified<'static>, git::Oid>,
}
impl IntoIterator for UpdatedCanonicalRefs {
- type Item = (git::Qualified<'static>, git::Oid);
- type IntoIter = std::collections::btree_map::IntoIter<git::Qualified<'static>, git::Oid>;
+ type Item = (git::fmt::Qualified<'static>, git::Oid);
+ type IntoIter = std::collections::btree_map::IntoIter<git::fmt::Qualified<'static>, git::Oid>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
@@ -51,12 +51,12 @@ impl IntoIterator for UpdatedCanonicalRefs {
impl UpdatedCanonicalRefs {
/// Insert a new updated entry for the canonical reference identified by
/// `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);
}
/// 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()
}
}
diff --git a/crates/radicle-protocol/src/worker/fetch/error.rs b/crates/radicle-protocol/src/worker/fetch/error.rs
index 6c9d753f3..20fd310af 100644
--- a/crates/radicle-protocol/src/worker/fetch/error.rs
+++ b/crates/radicle-protocol/src/worker/fetch/error.rs
@@ -2,7 +2,7 @@ use std::io;
use thiserror::Error;
-use radicle::{cob, git, identity, storage};
+use radicle::{cob, git::raw, identity, storage};
use radicle_fetch as fetch;
#[derive(Debug, Error)]
@@ -10,7 +10,7 @@ pub enum Fetch {
#[error(transparent)]
Run(#[from] fetch::Error),
#[error(transparent)]
- Git(#[from] git::raw::Error),
+ Git(#[from] raw::Error),
#[error(transparent)]
Storage(#[from] storage::Error),
#[error(transparent)]
diff --git a/crates/radicle-remote-helper/Cargo.toml b/crates/radicle-remote-helper/Cargo.toml
index b33bdce2c..418956853 100644
--- a/crates/radicle-remote-helper/Cargo.toml
+++ b/crates/radicle-remote-helper/Cargo.toml
@@ -19,5 +19,4 @@ log = { workspace = true }
radicle = { workspace = true }
radicle-cli = { workspace = true }
radicle-crypto = { workspace = true }
-radicle-git-ext = { workspace = true }
thiserror = { workspace = true }
\ No newline at end of file
diff --git a/crates/radicle-remote-helper/src/fetch.rs b/crates/radicle-remote-helper/src/fetch.rs
index 06e432995..1c3829dc7 100644
--- a/crates/radicle-remote-helper/src/fetch.rs
+++ b/crates/radicle-remote-helper/src/fetch.rs
@@ -19,9 +19,9 @@ pub enum Error {
/// Invalid reference name.
#[error("invalid ref: {0}")]
InvalidRef(#[from] radicle::git::fmt::Error),
- /// Git error.
- #[error("git: {0}")]
- InvalidOid(#[source] git::raw::Error),
+ /// Invalid object ID.
+ #[error("invalid oid: {0}")]
+ InvalidOid(#[from] radicle::git::ParseOidError),
/// 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}")]
@@ -34,7 +34,7 @@ pub enum Error {
/// Run a git fetch command.
pub fn run<R: ReadRepository>(
- mut refs: Vec<(git::Oid, git::RefString)>,
+ mut refs: Vec<(git::Oid, git::fmt::RefString)>,
stored: R,
stdin: &io::Stdin,
verbosity: Verbosity,
@@ -45,8 +45,8 @@ pub fn run<R: ReadRepository>(
let tokens = read_line(stdin, &mut line)?;
match tokens.as_slice() {
["fetch", oid, refstr] => {
- let oid = git::Oid::from_str(oid).map_err(Error::InvalidOid)?;
- let refstr = git::RefString::try_from(*refstr)?;
+ let oid = git::Oid::from_str(oid)?;
+ let refstr = git::fmt::RefString::try_from(*refstr)?;
refs.push((oid, refstr));
}
diff --git a/crates/radicle-remote-helper/src/list.rs b/crates/radicle-remote-helper/src/list.rs
index 4d435a26e..3a34698c0 100644
--- a/crates/radicle-remote-helper/src/list.rs
+++ b/crates/radicle-remote-helper/src/list.rs
@@ -19,7 +19,7 @@ pub enum Error {
Identity(#[from] radicle::identity::DocError),
/// Git error.
#[error(transparent)]
- Git(#[from] radicle::git::ext::Error),
+ Git(#[from] radicle::git::raw::Error),
/// Profile error.
#[error(transparent)]
Profile(#[from] profile::Error),
@@ -56,8 +56,8 @@ pub fn for_fetch<R: ReadRepository + cob::Store<Namespace = NodeId> + 'static>(
// List canonical references.
// Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy.
for glob in [
- git::refspec::pattern!("refs/heads/*"),
- git::refspec::pattern!("refs/tags/*"),
+ git::fmt::pattern!("refs/heads/*"),
+ git::fmt::pattern!("refs/tags/*"),
] {
for (name, oid) in stored.references_glob(&glob)? {
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.
for (name, oid) in stored.references_of(profile.id())? {
// Only branches and tags can be pushed to.
- if name.starts_with(git::refname!("refs/heads").as_str())
- || name.starts_with(git::refname!("refs/tags").as_str())
+ if name.starts_with(git::fmt::refname!("refs/heads").as_str())
+ || name.starts_with(git::fmt::refname!("refs/tags").as_str())
{
println!("{oid} {name}");
}
diff --git a/crates/radicle-remote-helper/src/main.rs b/crates/radicle-remote-helper/src/main.rs
index 3f5cd923a..e5240fa74 100644
--- a/crates/radicle-remote-helper/src/main.rs
+++ b/crates/radicle-remote-helper/src/main.rs
@@ -117,6 +117,9 @@ pub enum Error {
/// List error.
#[error(transparent)]
List(#[from] list::Error),
+ /// Invalid object ID.
+ #[error("invalid oid: {0}")]
+ InvalidOid(#[from] radicle::git::ParseOidError),
}
/// 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>`).
MirrorUpstream,
/// Create a branch with the provided name.
- Provided(git::RefString),
+ Provided(git::fmt::RefString),
}
impl Branch {
/// Return the branch name to be used for the local branch when creating a
/// 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 {
Self::None => None,
Self::MirrorUpstream => Some(git::refs::patch(object)),
@@ -207,12 +210,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
// module is aware of that.
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<_>>();
match args.as_slice() {
[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));
@@ -274,7 +280,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
}
["fetch", oid, refstr] => {
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(
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()));
}
- "patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?),
+ "patch.branch" => {
+ opts.branch = Branch::Provided(git::fmt::RefString::try_from(val)?)
+ }
other => {
return Err(Error::UnsupportedPushOption(other.to_owned()));
}
diff --git a/crates/radicle-remote-helper/src/push.rs b/crates/radicle-remote-helper/src/push.rs
index 0a86d5196..b45f38df2 100644
--- a/crates/radicle-remote-helper/src/push.rs
+++ b/crates/radicle-remote-helper/src/push.rs
@@ -43,7 +43,7 @@ pub enum Error {
NoKey,
/// User tried to delete the canonical branch.
#[error("refusing to delete default branch ref '{0}'")]
- DeleteForbidden(git::RefString),
+ DeleteForbidden(git::fmt::RefString),
/// Identity document error.
#[error("doc: {0}")]
Doc(#[from] radicle::identity::doc::DocError),
@@ -65,9 +65,6 @@ pub enum Error {
/// Git error.
#[error("git: {0}")]
Git(#[from] git::raw::Error),
- /// Git extension error.
- #[error("git: {0}")]
- GitExt(#[from] git::ext::Error),
/// Storage error.
#[error(transparent)]
Storage(#[from] radicle::storage::Error),
@@ -136,9 +133,9 @@ pub enum Error {
/// Push command.
enum Command {
/// Update ref.
- Push(git::Refspec<git::Oid, git::RefString>),
+ Push(git::fmt::refspec::Refspec<git::Oid, git::fmt::RefString>),
/// Delete ref.
- Delete(git::RefString),
+ Delete(git::fmt::RefString),
}
#[derive(Debug, thiserror::Error)]
@@ -173,7 +170,7 @@ impl Command {
let Some((src, dst)) = s.split_once(':') else {
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(),
err,
})?;
@@ -195,12 +192,12 @@ impl Command {
.id()
.into();
- Ok(Self::Push(git::Refspec { src, dst, force }))
+ Ok(Self::Push(git::fmt::refspec::Refspec { src, dst, force }))
}
}
/// Return the destination refname.
- fn dst(&self) -> &git::RefStr {
+ fn dst(&self) -> &git::fmt::RefStr {
match self {
Self::Push(rs) => rs.dst.as_refstr(),
Self::Delete(rs) => rs,
@@ -211,30 +208,30 @@ impl Command {
enum PushAction {
OpenPatch,
UpdatePatch {
- dst: git::Qualified<'static>,
+ dst: git::fmt::Qualified<'static>,
patch: patch::PatchId,
},
PushRef {
- dst: git::Qualified<'static>,
+ dst: git::fmt::Qualified<'static>,
},
}
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 {
Ok(Self::OpenPatch)
} else {
- let dst = git::Qualified::from_refstr(dst)
+ let dst = git::fmt::Qualified::from_refstr(dst)
.ok_or_else(|| error::PushAction::InvalidRef {
refname: dst.clone(),
})?
.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)
- .map_err(|err| error::PushAction::InvalidPatchId {
+ .map_err(|source| error::PushAction::InvalidPatchId {
suffix: oid.to_string(),
- source: err,
+ source,
})
.map(patch::PatchId::from)?;
Ok(Self::UpdatePatch { dst, patch })
@@ -248,7 +245,7 @@ impl PushAction {
/// Run a git push command.
pub fn run(
mut specs: Vec<String>,
- remote: Option<git::RefString>,
+ remote: Option<git::fmt::RefString>,
url: Url,
stored: &storage::git::Repository,
profile: &Profile,
@@ -290,7 +287,7 @@ pub fn run(
let identity = stored.identity()?;
let project = identity.project()?;
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());
// Rely on the environment variable `GIT_DIR`.
@@ -317,7 +314,7 @@ pub fn run(
.map(|_| None)
.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 action = PushAction::new(dst)?;
@@ -373,7 +370,7 @@ pub fn run(
// canonical branch.
if let Some(canonical) = rules.canonical(dst.clone(), stored) {
let object = working
- .find_object(**src, None)
+ .find_object(src.into(), None)
.map(|obj| git::canonical::Object::new(&obj))?
.ok_or(Error::UnknownObjectType { oid: *src })?;
@@ -429,10 +426,10 @@ pub fn run(
}
match stored.backend.refname_to_id(refname.as_str()) {
- Ok(new) if new != *oid => {
+ Ok(new) if oid != new => {
stored.backend.reference(
refname.as_str(),
- *oid,
+ oid.into(),
true,
"set-canonical-reference from git-push (radicle)",
)?;
@@ -441,7 +438,7 @@ pub fn run(
Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
stored.backend.reference(
refname.as_str(),
- *oid,
+ oid.into(),
true,
"set-canonical-reference from git-push (radicle)",
)?;
@@ -505,7 +502,7 @@ fn patch_base(
/// [`Drop::drop`].
struct TempPatchRef<'a> {
stored: &'a storage::git::Repository,
- reference: git::Namespaced<'a>,
+ reference: git::fmt::Namespaced<'a>,
}
impl<'a> TempPatchRef<'a> {
@@ -539,7 +536,7 @@ impl<'a> Drop for TempPatchRef<'a> {
/// Open a new patch.
fn patch_open<G>(
head: &git::Oid,
- upstream: &Option<git::RefString>,
+ upstream: &Option<git::fmt::RefString>,
nid: &NodeId,
working: &git::raw::Repository,
stored: &storage::git::Repository,
@@ -563,7 +560,7 @@ where
}
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 {
patches.draft(
@@ -607,24 +604,29 @@ where
let refname = git::refs::patch(&patch).with_namespace(nid.into());
let _ = stored.raw().reference(
refname.as_str(),
- **head,
+ head.into(),
true,
"Create reference for patch head",
)?;
if let Some(upstream) = upstream {
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();
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 = working.reference(
&remote_branch,
- **head,
+ head.into(),
true,
"Create remote tracking branch for patch",
)?;
@@ -667,7 +669,7 @@ where
#[allow(clippy::too_many_arguments)]
fn patch_update<G>(
head: &git::Oid,
- dst: &git::Qualified,
+ dst: &git::fmt::Qualified,
force: bool,
patch_id: patch::PatchId,
nid: &NodeId,
@@ -703,7 +705,8 @@ where
let (latest_id, latest) = patch.latest();
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());
push_ref(head, &dst, force, stored.raw(), opts.verbosity)?;
@@ -744,7 +747,7 @@ where
fn push<G>(
src: &git::Oid,
- dst: &git::Qualified,
+ dst: &git::fmt::Qualified,
force: bool,
nid: &NodeId,
working: &git::raw::Repository,
@@ -768,7 +771,7 @@ where
if let Some(old) = old {
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
// 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.
let mut revwalk = stored.revwalk()?;
- revwalk.push(*old)?;
- revwalk.hide(*new)?;
+ revwalk.push(old.into())?;
+ revwalk.hide(new.into())?;
// List of commits that have been dropped.
let dropped = revwalk
@@ -943,7 +946,7 @@ where
/// Push a single reference to storage.
fn push_ref(
src: &git::Oid,
- dst: &git::Namespaced,
+ dst: &git::fmt::Namespaced,
force: bool,
stored: &git::raw::Repository,
verbosity: Verbosity,
@@ -951,7 +954,7 @@ fn push_ref(
let path = dunce::canonicalize(stored.path())?.display().to_string();
// Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
// 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()];
diff --git a/crates/radicle-remote-helper/src/push/canonical.rs b/crates/radicle-remote-helper/src/push/canonical.rs
index 4073a0901..09ab7cc92 100644
--- a/crates/radicle-remote-helper/src/push/canonical.rs
+++ b/crates/radicle-remote-helper/src/push/canonical.rs
@@ -40,7 +40,7 @@ where
/// 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.
- 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
.quorum()
.map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object))
diff --git a/crates/radicle-remote-helper/src/push/error.rs b/crates/radicle-remote-helper/src/push/error.rs
index e65be9e1b..47222f7d9 100644
--- a/crates/radicle-remote-helper/src/push/error.rs
+++ b/crates/radicle-remote-helper/src/push/error.rs
@@ -37,10 +37,10 @@ pub struct HeadsDiverge {
#[derive(Debug, Error)]
pub enum PushAction {
#[error("invalid reference {refname}, expected qualified reference starting with `refs/`")]
- InvalidRef { refname: git::RefString },
- #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID")]
+ InvalidRef { refname: git::fmt::RefString },
+ #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID: {source}")]
InvalidPatchId {
suffix: String,
- source: git::raw::Error,
+ source: radicle::git::ParseOidError,
},
}
diff --git a/crates/radicle/CHANGELOG.md b/crates/radicle/CHANGELOG.md
index e4a0623e8..7e1b130f5 100644
--- a/crates/radicle/CHANGELOG.md
+++ b/crates/radicle/CHANGELOG.md
@@ -18,6 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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
- `radicle::node::Handle::announce_refs` is deprecated in favor of
diff --git a/crates/radicle/Cargo.toml b/crates/radicle/Cargo.toml
index 4135fc491..ef646bd6d 100644
--- a/crates/radicle/Cargo.toml
+++ b/crates/radicle/Cargo.toml
@@ -11,8 +11,9 @@ rust-version.workspace = true
[features]
default = []
-test = ["tempfile", "qcheck", "radicle-crypto/test"]
+test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"]
logger = ["colored", "chrono"]
+schemars = ["radicle-oid/schemars", "dep:schemars"]
[dependencies]
amplify = { workspace = true, features = ["std"] }
@@ -32,9 +33,10 @@ log = { workspace = true, features = ["std"] }
multibase = { workspace = true }
nonempty = { workspace = true, features = ["serialize"] }
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-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 }
schemars = { workspace = true, optional = true, features = ["derive", "std"] }
serde = { workspace = true, features = ["derive"] }
@@ -58,6 +60,7 @@ jsonschema = { version = "0.30", default-features = false }
pretty_assertions = { workspace = true }
qcheck = { 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-git-metadata = { workspace = true }
tempfile = { workspace = true }
diff --git a/crates/radicle/src/cob.rs b/crates/radicle/src/cob.rs
index 64cae77ce..7892bd416 100644
--- a/crates/radicle/src/cob.rs
+++ b/crates/radicle/src/cob.rs
@@ -13,6 +13,9 @@ pub mod thread;
#[cfg(test)]
pub mod test;
+#[cfg(test)]
+pub use radicle_cob::stable;
+
pub use cache::{migrate, MigrateCallback};
pub use common::*;
pub use op::{ActorId, Op};
@@ -21,7 +24,7 @@ pub use radicle_cob::{
CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId,
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.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
@@ -66,17 +69,17 @@ impl TypedId {
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
/// [`TypedId`] (see [`TypedId::from_qualified`]).
pub fn from_namespaced(
- n: &crate::git::Namespaced,
+ n: &crate::git::fmt::Namespaced,
) -> Result<Option<Self>, ParseIdentifierError> {
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:
/// `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
/// 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() {
("refs", "cobs", type_name, mut id) => {
let Some(id) = id.next() else {
diff --git a/crates/radicle/src/cob/common.rs b/crates/radicle/src/cob/common.rs
index 7bb7289b8..91df7396f 100644
--- a/crates/radicle/src/cob/common.rs
+++ b/crates/radicle/src/cob/common.rs
@@ -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;
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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
diff --git a/crates/radicle/src/cob/external.rs b/crates/radicle/src/cob/external.rs
index 69bd49ec1..9014b46fb 100644
--- a/crates/radicle/src/cob/external.rs
+++ b/crates/radicle/src/cob/external.rs
@@ -225,7 +225,7 @@ impl<R: ReadRepository> Evaluate<R> for External {
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,
entry: &radicle_cob::Entry,
concurrent: I,
diff --git a/crates/radicle/src/cob/identity.rs b/crates/radicle/src/cob/identity.rs
index 37eab9543..1d0e81fde 100644
--- a/crates/radicle/src/cob/identity.rs
+++ b/crates/radicle/src/cob/identity.rs
@@ -4,11 +4,11 @@ use std::{fmt, ops::Deref, str::FromStr};
use crypto::{PublicKey, Signature};
use radicle_cob::{Embed, ObjectId, TypeName};
-use radicle_git_ext as git_ext;
-use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};
use thiserror::Error;
+use crate::git;
+use crate::git::Oid;
use crate::identity::doc::Doc;
use crate::node::device::Device;
use crate::node::NodeId;
@@ -126,9 +126,7 @@ pub enum ApplyError {
#[error("document does not contain any changes to current identity")]
DocUnchanged,
#[error("git: {0}")]
- Git(#[from] crate::git::raw::Error),
- #[error("git: {0}")]
- GitExt(#[from] git_ext::Error),
+ Git(#[from] git::raw::Error),
#[error("identity document error: {0}")]
Doc(#[from] DocError),
}
@@ -1323,16 +1321,15 @@ mod test {
.unwrap();
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()
});
assert!(alice_identity.revision(&a1).is_some());
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
- let b1 = cob::git::stable::with_advanced_timestamp(|| {
- bob_identity.accept(&a2, &bob.signer).unwrap()
- });
+ let b1 =
+ 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.revision(&a2).unwrap().state, State::Accepted);
@@ -1381,15 +1378,14 @@ mod test {
eve.repo.fetch(bob);
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
- let b1 = cob::git::stable::with_advanced_timestamp(|| {
- bob_identity.accept(&a2, &bob.signer).unwrap()
- });
+ let b1 =
+ cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
assert_eq!(bob_identity.current, a2);
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
let mut eve_doc = eve_identity.doc().clone().edit();
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
.update(
cob::Title::new("Change visibility").unwrap(),
@@ -1458,15 +1454,13 @@ mod test {
// Bob accepts alice's revision.
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
- let b1 = cob::git::stable::with_advanced_timestamp(|| {
- bob_identity.accept(&a2, &bob.signer).unwrap()
- });
+ let b1 =
+ cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
// Eve rejects the revision, not knowing.
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
- let e1 = cob::git::stable::with_advanced_timestamp(|| {
- eve_identity.reject(a2, &eve.signer).unwrap()
- });
+ let e1 =
+ cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2, &eve.signer).unwrap());
assert!(eve_identity.revision(&a2).unwrap().is_active());
// Then she submits a new revision.
@@ -1576,7 +1570,7 @@ mod test {
assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);
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()
});
diff --git a/crates/radicle/src/cob/issue.rs b/crates/radicle/src/cob/issue.rs
index f771301c7..1958bbf9f 100644
--- a/crates/radicle/src/cob/issue.rs
+++ b/crates/radicle/src/cob/issue.rs
@@ -1765,7 +1765,7 @@ mod test {
.unwrap();
// 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();
tx.comment("First reply", *issue.id, vec![]).unwrap();
let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err();
diff --git a/crates/radicle/src/cob/op.rs b/crates/radicle/src/cob/op.rs
index bd6cd02eb..b9cbbd3b5 100644
--- a/crates/radicle/src/cob/op.rs
+++ b/crates/radicle/src/cob/op.rs
@@ -8,9 +8,10 @@ use radicle_crypto::PublicKey;
use crate::cob;
use crate::cob::Timestamp;
+use crate::git;
+use crate::identity;
use crate::identity::DocAt;
use crate::storage::ReadRepository;
-use crate::{git, identity};
/// The author of an [`Op`].
pub type ActorId = PublicKey;
diff --git a/crates/radicle/src/cob/patch.rs b/crates/radicle/src/cob/patch.rs
index 64422550b..65f6304bf 100644
--- a/crates/radicle/src/cob/patch.rs
+++ b/crates/radicle/src/cob/patch.rs
@@ -130,7 +130,7 @@ pub enum Error {
Payload(#[from] PayloadError),
/// Git error.
#[error("git: {0}")]
- Git(#[from] git::ext::Error),
+ Git(#[from] git::raw::Error),
/// Store error.
#[error("store: {0}")]
Store(#[from] store::Error),
@@ -601,12 +601,15 @@ impl 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())
}
/// 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()))
}
@@ -3334,7 +3337,7 @@ mod test {
},
&alice,
);
- let patch = Patch::from_history(&h0, &repo).unwrap();
+ let patch: Patch = Patch::from_history(&h0, &repo).unwrap();
assert_eq!(patch.revisions().count(), 2);
let mut h1 = h0.clone();
diff --git a/crates/radicle/src/cob/store.rs b/crates/radicle/src/cob/store.rs
index cc3b1c367..c1695b0c5 100644
--- a/crates/radicle/src/cob/store.rs
+++ b/crates/radicle/src/cob/store.rs
@@ -125,7 +125,7 @@ pub enum Error {
Git(git::raw::Error),
#[error("failed to find reference '{name}': {err}")]
RefLookup {
- name: git::RefString,
+ name: git::fmt::RefString,
#[source]
err: git::raw::Error,
},
diff --git a/crates/radicle/src/cob/stream/iter.rs b/crates/radicle/src/cob/stream/iter.rs
index 66d3d7ae7..767c0cabb 100644
--- a/crates/radicle/src/cob/stream/iter.rs
+++ b/crates/radicle/src/cob/stream/iter.rs
@@ -3,7 +3,9 @@ use std::marker::PhantomData;
use serde::Deserialize;
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::CobRange;
@@ -83,7 +85,7 @@ impl Walk {
match self.until {
Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
Until::Glob(glob) => {
- walk.push(*self.from)?;
+ walk.push(self.from.into())?;
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
// after that.
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()?;
Some(oid.and_then(|oid| self.repo.find_commit(oid)))
@@ -132,7 +134,7 @@ where
let commit = self.walk.next()?;
match 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
self.walk.inner.hide(commit.id()).ok();
// 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
/// 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
A: for<'de> Deserialize<'de>,
{
diff --git a/crates/radicle/src/cob/test.rs b/crates/radicle/src/cob/test.rs
index 30fb221fe..8ecad448b 100644
--- a/crates/radicle/src/cob/test.rs
+++ b/crates/radicle/src/cob/test.rs
@@ -12,10 +12,6 @@ use crate::cob::store::encoding;
use crate::cob::{patch, Title};
use crate::cob::{Entry, History, Manifest, Timestamp, Version};
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::node::device::Device;
use crate::prelude::Did;
@@ -101,7 +97,7 @@ where
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 tips = self.tips();
let revision = arbitrary::oid();
@@ -182,7 +178,8 @@ impl<G: Signer> Actor<G> {
"nonce": fastrand::u64(..),
}))
.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 author = *self.signer.public_key();
let actions = NonEmpty::from_vec(actions).unwrap();
@@ -226,8 +223,8 @@ impl<G: Signer> Actor<G> {
&mut self,
title: Title,
description: impl ToString,
- base: git::Oid,
- oid: git::Oid,
+ base: crate::git::Oid,
+ oid: crate::git::Oid,
repo: &R,
) -> Result<Patch, patch::Error> {
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
/// 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,
timestamp: Timestamp,
parents: impl IntoIterator<Item = Oid>,
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 oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap();
- let parents = parents.into_iter().map(|o| *o);
+ let oid = crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Blob, &data).unwrap();
+ let parents = parents.into_iter().map(|o| o.into());
let author = Author {
name: "radicle".to_owned(),
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,
parents,
author.clone(),
@@ -277,7 +279,9 @@ pub fn encoded<T: Cob, G: Signer>(
)
.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())
}
diff --git a/crates/radicle/src/git.rs b/crates/radicle/src/git.rs
index 676e990fd..45f1b9171 100644
--- a/crates/radicle/src/git.rs
+++ b/crates/radicle/src/git.rs
@@ -5,33 +5,24 @@ use std::io;
use std::path::Path;
use std::process::Command;
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::crypto::PublicKey;
use crate::node::Alias;
use crate::rad;
-use crate::storage;
use crate::storage::refs::Refs;
use crate::storage::RemoteId;
-pub use ext::Error;
-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;
+pub use crate::storage::git::transport::local::Url;
use raw::ErrorExt as _;
+pub type BranchName = crate::git::fmt::RefString;
+
/// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418;
/// Minimum required git version.
@@ -160,16 +151,16 @@ pub enum RefError {
#[error("ref name is not valid UTF-8")]
InvalidName,
#[error("unexpected unqualified ref: {0}")]
- Unqualified(RefString),
+ Unqualified(fmt::RefString),
#[error("invalid ref format: {0}")]
- Format(#[from] format::Error),
+ Format(#[from] fmt::Error),
#[error("reference has no target")]
NoTarget,
#[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}'")]
InvalidNamespace {
- name: format::RefString,
+ name: fmt::RefString,
#[source]
err: Box<dyn std::error::Error + Send + Sync + 'static>,
},
@@ -186,9 +177,13 @@ pub enum ListRefsError {
}
pub mod refs {
- use super::*;
+ use std::sync::LazyLock;
+
use radicle_cob as cob;
+ use super::fmt::*;
+ use super::*;
+
/// Try to get a qualified reference from a generic reference.
pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> {
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> {
Qualified::from_components(
- name::component!("heads"),
- name::component!("patches"),
+ component!("heads"),
+ component!("patches"),
Some(object_id.into()),
)
}
pub mod storage {
- use format::{
- lit,
- name::component,
- refspec::{self, PatternString},
- };
-
use super::*;
/// Where the repo's identity document is stored.
///
/// `refs/rad/id`
///
- pub static IDENTITY_BRANCH: LazyLock<Qualified> = LazyLock::new(|| {
- Qualified::from_components(name::component!("rad"), name::component!("id"), None)
- });
+ pub static IDENTITY_BRANCH: LazyLock<Qualified> =
+ LazyLock::new(|| Qualified::from_components(component!("rad"), component!("id"), None));
/// Where the repo's identity root document is stored.
///
/// `refs/rad/root`
///
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.
@@ -250,7 +238,7 @@ pub mod refs {
/// `refs/rad/sigrefs`
///
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.
@@ -341,8 +329,8 @@ pub mod refs {
///
/// `refs/namespaces/*/refs/cobs/<typename>/<object_id>`
///
- pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString {
- refspec::pattern!("refs/namespaces/*")
+ pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> refspec::PatternString {
+ pattern!("refs/namespaces/*")
.join(refname!("refs/cobs"))
.join(Component::from(typename))
.join(Component::from(object_id))
@@ -390,8 +378,11 @@ pub mod refs {
///
/// `refs/namespaces/*/refs/drafts/cobs/<typename>/<object_id>`
///
- pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString {
- refspec::pattern!("refs/namespaces/*")
+ pub fn cobs(
+ typename: &cob::TypeName,
+ object_id: &cob::ObjectId,
+ ) -> refspec::PatternString {
+ pattern!("refs/namespaces/*")
.join(refname!("refs/drafts/cobs"))
.join(Component::from(typename))
.join(Component::from(object_id))
@@ -423,7 +414,6 @@ pub mod refs {
pub mod workdir {
use super::*;
- use format::name::component;
/// Create a [`RefString`] that corresponds to `refs/heads/<branch>`.
pub fn branch(branch: &RefStr) -> RefString {
@@ -483,7 +473,7 @@ pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError
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
/// 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
/// wanted to parse the namespace as a `PublicKey`, then you would the function
/// 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
T: FromStr,
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 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
/// wanted to parse the namespace as a `PublicKey`, then you would the function
/// 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
T: FromStr,
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() {
None => {
- let refname = Qualified::from_refstr(input)
+ let refname = fmt::Qualified::from_refstr(input)
.ok_or_else(|| RefError::Unqualified(input.to_owned()))?;
Ok((None, refname))
@@ -573,7 +563,7 @@ pub fn initial_commit<'a>(
pub fn commit<'a>(
repo: &'a raw::Repository,
parent: &'a raw::Commit,
- target: &RefStr,
+ target: &fmt::RefStr,
message: &str,
sig: &raw::Signature,
tree: &raw::Tree,
@@ -588,7 +578,7 @@ pub fn commit<'a>(
pub fn empty_commit<'a>(
repo: &'a raw::Repository,
parent: &'a raw::Commit,
- target: &RefStr,
+ target: &fmt::RefStr,
message: &str,
sig: &raw::Signature,
) -> Result<raw::Commit<'a>, raw::Error> {
@@ -611,7 +601,7 @@ pub fn write_tree<'r>(
path: &Path,
bytes: &[u8],
repo: &'r raw::Repository,
-) -> Result<raw::Tree<'r>, Error> {
+) -> Result<raw::Tree<'r>, raw::Error> {
let blob_id = repo.blob(bytes)?;
let mut builder = repo.treebuilder(None)?;
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>(
repo: &raw::Repository,
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> {
let refspecs = refspecs
.into_iter()
diff --git a/crates/radicle/src/git/canonical.rs b/crates/radicle/src/git/canonical.rs
index aab1d20ee..56702310b 100644
--- a/crates/radicle/src/git/canonical.rs
+++ b/crates/radicle/src/git/canonical.rs
@@ -19,11 +19,12 @@ use std::fmt;
use std::marker::PhantomData;
use std::ops::ControlFlow;
-use git_ext::ref_format::Namespaced;
+use crate::git::fmt::Namespaced;
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
/// [`Canonical::new`].
@@ -510,20 +511,19 @@ mod tests {
/// Test helper to construct a Canonical and get the quorum
fn quorum(
- heads: &[git::raw::Oid],
+ heads: &[crate::git::Oid],
threshold: usize,
- repo: &git::raw::Repository,
+ repo: &crate::git::raw::Repository,
) -> Result<Oid, QuorumError> {
- let refname =
- git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap());
+ let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap());
let mut delegates = Vec::new();
for (i, head) in heads.iter().enumerate() {
let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
let did = Did::from(signer.public_key());
delegates.push(did);
- let ns = git::Component::from(signer.public_key());
- repo.reference(refname.with_namespace(ns).as_str(), *head, true, "")
+ let ns = git::fmt::Component::from(signer.public_key());
+ repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "")
.unwrap();
}
@@ -557,18 +557,18 @@ mod tests {
fn test_quorum_properties() {
let tmp = tempfile::tempdir().unwrap();
let (repo, c0) = fixtures::repository(tmp.path());
- let c0: git::Oid = c0.into();
- let a1 = fixtures::commit("A1", &[*c0], &repo);
- let a2 = fixtures::commit("A2", &[*a1], &repo);
- let d1 = fixtures::commit("D1", &[*c0], &repo);
- let c1 = fixtures::commit("C1", &[*c0], &repo);
- let c2 = fixtures::commit("C2", &[*c1], &repo);
- let b2 = fixtures::commit("B2", &[*c1], &repo);
- let a1 = fixtures::commit("A1", &[*c0], &repo);
- let m1 = fixtures::commit("M1", &[*c2, *b2], &repo);
- let m2 = fixtures::commit("M2", &[*a1, *b2], &repo);
+ let c0: crate::git::Oid = c0.into();
+ let a1 = fixtures::commit("A1", &[c0.into()], &repo);
+ let a2 = fixtures::commit("A2", &[a1.into()], &repo);
+ let d1 = fixtures::commit("D1", &[c0.into()], &repo);
+ let c1 = fixtures::commit("C1", &[c0.into()], &repo);
+ let c2 = fixtures::commit("C2", &[c1.into()], &repo);
+ let b2 = fixtures::commit("B2", &[c1.into()], &repo);
+ let a1 = fixtures::commit("A1", &[c0.into()], &repo);
+ let m1 = fixtures::commit("M1", &[c2.into(), b2.into()], &repo);
+ let m2 = fixtures::commit("M2", &[a1.into(), b2.into()], &repo);
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 {
let count = rng.usize(1..=choices.len());
@@ -591,11 +591,10 @@ mod tests {
fn test_quorum_different_types() {
let tmp = tempfile::tempdir().unwrap();
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!(
- quorum(&[*c0, *t0], 1, &repo),
+ quorum(&[c0.into(), t0], 1, &repo),
Err(QuorumError::DifferentTypes { .. })
);
}
diff --git a/crates/radicle/src/git/canonical/effects.rs b/crates/radicle/src/git/canonical/effects.rs
index 54ca20d57..de70f9d55 100644
--- a/crates/radicle/src/git/canonical/effects.rs
+++ b/crates/radicle/src/git/canonical/effects.rs
@@ -1,8 +1,9 @@
use std::collections::{BTreeMap, BTreeSet};
use crate::git;
+use crate::git::fmt::Qualified;
use crate::git::raw::ErrorExt as _;
-use crate::git::{Oid, Qualified};
+use crate::git::Oid;
use crate::prelude::Did;
use super::{FoundObjects, GraphAheadBehind, MergeBase, Object};
@@ -40,7 +41,7 @@ pub enum FindObjectsError {
},
#[error("failed to find reference {refname} due to: {source}")]
FindReference {
- refname: git::Namespaced<'static>,
+ refname: git::fmt::Namespaced<'static>,
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[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
E: std::error::Error + Send + Sync + 'static,
{
@@ -170,7 +171,7 @@ pub struct GraphDescendant {
impl FindMergeBase for git::raw::Repository {
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 {
a,
b,
@@ -190,7 +191,7 @@ impl Ancestry for git::raw::Repository {
commit: Oid,
upstream: Oid,
) -> Result<GraphAheadBehind, GraphDescendant> {
- self.graph_ahead_behind(*commit, *upstream)
+ self.graph_ahead_behind(commit.into(), upstream.into())
.map_err(|err| GraphDescendant {
commit,
upstream,
@@ -228,7 +229,7 @@ impl FindObjects for git::raw::Repository {
log::warn!(target: "radicle", "Missing target for reference `{name}`");
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(|| {
FindObjectsError::invalid_object_type(
*did,
diff --git a/crates/radicle/src/git/canonical/rules.rs b/crates/radicle/src/git/canonical/rules.rs
index abfbb12a2..9e32928c4 100644
--- a/crates/radicle/src/git/canonical/rules.rs
+++ b/crates/radicle/src/git/canonical/rules.rs
@@ -22,9 +22,9 @@ use thiserror::Error;
use crate::git;
use crate::git::canonical;
use crate::git::canonical::Canonical;
+use crate::git::fmt::refspec::QualifiedPattern;
+use crate::git::fmt::Qualified;
use crate::git::fmt::{refname, RefString};
-use crate::git::refspec::QualifiedPattern;
-use crate::git::Qualified;
use crate::identity::{doc, Did};
const ASTERISK: char = '*';
@@ -214,7 +214,7 @@ impl Ord for Pattern {
}
}
- use git::refspec::Component;
+ use git::fmt::refspec::Component;
fn cmp_component(lhs: Component<'_>, rhs: Component<'_>) -> ComponentOrdering {
let (l, r) = (lhs.as_str(), rhs.as_str());
@@ -401,7 +401,10 @@ impl ValidRule {
/// # Errors
///
/// If the `name` reference begins with `refs/rad`.
- pub fn default_branch(did: Did, name: &git::RefStr) -> Result<(Pattern, Self), PatternError> {
+ pub fn default_branch(
+ did: Did,
+ name: &git::fmt::RefStr,
+ ) -> Result<(Pattern, Self), PatternError> {
let pattern = Pattern::try_from(git::refs::branch(name).to_owned())?;
let rule = Self {
allow: ResolvedDelegates::Delegates(doc::Delegates::from(did)),
@@ -732,9 +735,7 @@ pub enum ValidationError {
#[derive(Debug, Error)]
pub enum CanonicalError {
#[error(transparent)]
- Git(#[from] git::raw::Error),
- #[error(transparent)]
- References(#[from] git::ext::Error),
+ Git(#[from] crate::git::raw::Error),
}
#[cfg(test)]
@@ -746,8 +747,8 @@ mod tests {
use crate::crypto::{test::signer::MockSigner, Signer};
use crate::git;
- use crate::git::refspec::qualified_pattern;
- use crate::git::RefString;
+ use crate::git::fmt::qualified_pattern;
+ use crate::git::fmt::RefString;
use crate::identity::doc::Doc;
use crate::identity::Visibility;
use crate::node::device::Device;
@@ -782,7 +783,7 @@ mod tests {
fn tag(name: RefString, head: git::raw::Oid, repo: &git::raw::Repository) -> git::Oid {
let commit = fixtures::commit(name.as_str(), &[head], repo);
- let target = repo.find_object(*commit, None).unwrap();
+ let target = repo.find_object(commit.into(), None).unwrap();
let tagger = repo.signature().unwrap();
repo.tag(name.as_str(), &target, &tagger, name.as_str(), false)
.unwrap()
@@ -1118,7 +1119,7 @@ mod tests {
&repo,
"heartwood".try_into().unwrap(),
"Radicle Heartwood Protocol & Stack",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&delegate,
&storage,
@@ -1132,20 +1133,20 @@ mod tests {
// Create tags and keep track of their OIDs
//
// follows the `refs/tags/release/candidates/*` rule
- let failing_tag = git::refname!("release/candidates/v1.0");
+ let failing_tag = git::fmt::refname!("release/candidates/v1.0");
let tags = [
// follows the `refs/tags/*` rule
- git::refname!("v1.0"),
+ git::fmt::refname!("v1.0"),
// follows the `refs/tags/release/*` rule
- git::refname!("release/v1.0"),
+ git::fmt::refname!("release/v1.0"),
failing_tag.clone(),
// follows the `refs/tags/*` rule
- git::refname!("qa/v1.0"),
+ git::fmt::refname!("qa/v1.0"),
]
.into_iter()
.map(|name| {
(
- git::lit::refs_tags(name.clone()).into(),
+ git::fmt::lit::refs_tags(name.clone()).into(),
tag(name, head, &repo),
)
})
@@ -1156,20 +1157,20 @@ mod tests {
&rad::REMOTE_NAME,
[
(
- &git::qualified!("refs/tags/v1.0"),
- &git::qualified!("refs/tags/v1.0"),
+ &git::fmt::qualified!("refs/tags/v1.0"),
+ &git::fmt::qualified!("refs/tags/v1.0"),
),
(
- &git::qualified!("refs/tags/release/v1.0"),
- &git::qualified!("refs/tags/release/v1.0"),
+ &git::fmt::qualified!("refs/tags/release/v1.0"),
+ &git::fmt::qualified!("refs/tags/release/v1.0"),
),
(
- &git::qualified!("refs/tags/release/candidates/v1.0"),
- &git::qualified!("refs/tags/release/candidates/v1.0"),
+ &git::fmt::qualified!("refs/tags/release/candidates/v1.0"),
+ &git::fmt::qualified!("refs/tags/release/candidates/v1.0"),
),
(
- &git::qualified!("refs/tags/qa/v1.0"),
- &git::qualified!("refs/tags/qa/v1.0"),
+ &git::fmt::qualified!("refs/tags/qa/v1.0"),
+ &git::fmt::qualified!("refs/tags/qa/v1.0"),
),
],
)
@@ -1200,7 +1201,7 @@ mod tests {
// All tags should succeed at getting their canonical commit other than the
// candidates tag.
let stored = storage.repository(rid).unwrap();
- let failing = git::Qualified::from(git::lit::refs_tags(failing_tag));
+ let failing = git::fmt::Qualified::from(git::fmt::lit::refs_tags(failing_tag));
for (refname, oid) in tags.into_iter() {
let canonical = rules
.canonical(refname.clone(), &stored)
diff --git a/crates/radicle/src/git/raw.rs b/crates/radicle/src/git/raw.rs
index 35b882280..1246d071f 100644
--- a/crates/radicle/src/git/raw.rs
+++ b/crates/radicle/src/git/raw.rs
@@ -49,14 +49,3 @@ impl ErrorExt for git2::Error {
self.code() == git2::ErrorCode::NotFound
}
}
-
-impl ErrorExt for git_ext::Error {
- fn is_not_found(&self) -> bool {
- use git_ext::Error::*;
- match self {
- Git(e) => e.is_not_found(),
- NotFound(_) => true,
- _ => false,
- }
- }
-}
diff --git a/crates/radicle/src/identity/doc.rs b/crates/radicle/src/identity/doc.rs
index 6617265e8..a28d37269 100644
--- a/crates/radicle/src/identity/doc.rs
+++ b/crates/radicle/src/identity/doc.rs
@@ -10,9 +10,9 @@ use std::path::Path;
use std::str::FromStr;
use std::sync::LazyLock;
+use crate::git::Oid;
use nonempty::NonEmpty;
use radicle_cob::type_name::{TypeName, TypeNameParse};
-use radicle_git_ext::Oid;
use serde::{de, Deserialize, Serialize};
use thiserror::Error;
@@ -53,8 +53,6 @@ pub enum DocError {
#[error(transparent)]
Threshold(#[from] ThresholdError),
#[error("git: {0}")]
- GitExt(#[from] git::Error),
- #[error("git: {0}")]
Git(#[from] git::raw::Error),
#[error("missing identity document")]
Missing,
@@ -72,7 +70,6 @@ impl DocError {
/// Whether this error is caused by the document not being found.
pub fn is_not_found(&self) -> bool {
match self {
- Self::GitExt(e) => e.is_not_found(),
Self::Git(e) => e.is_not_found(),
_ => false,
}
@@ -810,7 +807,7 @@ impl Doc {
if !self.is_delegate(&key.into()) {
return Err(*key);
}
- if key.verify(blob.as_bytes(), signature).is_err() {
+ if key.verify(AsRef::<[u8]>::as_ref(&blob), signature).is_err() {
return Err(*key);
}
Ok(())
@@ -855,7 +852,7 @@ impl Doc {
G: crypto::signature::Signer<crypto::Signature>,
{
let (oid, bytes) = self.encode()?;
- let sig = signer.sign(oid.as_bytes());
+ let sig = signer.sign(oid.as_ref());
Ok((oid, bytes, sig))
}
@@ -1132,7 +1129,7 @@ mod test {
&repo,
"heartwood".try_into().unwrap(),
"Radicle Heartwood Protocol & Stack",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&delegate,
&storage,
@@ -1182,7 +1179,7 @@ mod test {
&working,
"heartwood".try_into().unwrap(),
"Radicle Heartwood Protocol & Stack",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&delegate,
&storage,
diff --git a/crates/radicle/src/identity/doc/id.rs b/crates/radicle/src/identity/doc/id.rs
index 46eb0c87f..9a2071708 100644
--- a/crates/radicle/src/identity/doc/id.rs
+++ b/crates/radicle/src/identity/doc/id.rs
@@ -1,7 +1,7 @@
use std::ops::Deref;
use std::{ffi::OsString, fmt, str::FromStr};
-use git_ext::ref_format::{Component, RefString};
+use crate::git::fmt::{Component, RefString};
use thiserror::Error;
use crate::git;
@@ -12,10 +12,10 @@ pub const RAD_PREFIX: &str = "rad:";
#[derive(Error, Debug)]
pub enum IdError {
- #[error("invalid git object id: {0}")]
- InvalidOid(#[from] git::raw::Error),
#[error(transparent)]
Multibase(#[from] multibase::Error),
+ #[error("invalid length: expected {expected} bytes, got {actual} bytes")]
+ Length { expected: usize, actual: usize },
}
/// A repository identifier.
@@ -68,14 +68,18 @@ impl RepoId {
/// Eg. `z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
///
pub fn canonical(&self) -> String {
- multibase::encode(multibase::Base::Base58Btc, self.0.as_bytes())
+ multibase::encode(multibase::Base::Base58Btc, AsRef::<[u8]>::as_ref(&self.0))
}
pub fn from_canonical(input: &str) -> Result<Self, IdError> {
+ const EXPECTED_LEN: usize = 20;
let (_, bytes) = multibase::decode(input)?;
- let array: git::Oid = bytes.as_slice().try_into()?;
-
- Ok(Self(array))
+ let bytes: [u8; EXPECTED_LEN] =
+ bytes.try_into().map_err(|bytes: Vec<u8>| IdError::Length {
+ expected: EXPECTED_LEN,
+ actual: bytes.len(),
+ })?;
+ Ok(Self(crate::git::Oid::from_sha1(bytes)))
}
}
diff --git a/crates/radicle/src/identity/doc/update.rs b/crates/radicle/src/identity/doc/update.rs
index 5fd2755c8..5f3484b94 100644
--- a/crates/radicle/src/identity/doc/update.rs
+++ b/crates/radicle/src/identity/doc/update.rs
@@ -208,7 +208,8 @@ pub fn verify(raw: RawDoc) -> Result<Doc, error::DocVerification> {
.map(|rcrefs| rcrefs.and_then(|c| project.map(|p| (c, p))))
{
Ok(Some((crefs, project))) => {
- let default = git::Qualified::from(git::lit::refs_heads(project.default_branch()));
+ let default =
+ git::fmt::Qualified::from(git::fmt::lit::refs_heads(project.default_branch()));
let matches = crefs
.raw_rules()
.matches(&default)
@@ -300,7 +301,7 @@ mod test {
#[test]
fn test_cannot_include_default_branch_rule() {
let raw = arbitrary::gen::<RawDoc>(1);
- let branch = git::Qualified::from(git::lit::refs_heads(
+ let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(
raw.project().unwrap().default_branch(),
));
let raw = super::payload(
@@ -333,7 +334,7 @@ mod test {
#[test]
fn test_default_branch_rule_exists_after_verification() {
let raw = arbitrary::gen::<RawDoc>(1);
- let branch = git::Qualified::from(git::lit::refs_heads(
+ let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(
raw.project().unwrap().default_branch(),
));
let raw = super::payload(
diff --git a/crates/radicle/src/identity/doc/update/error.rs b/crates/radicle/src/identity/doc/update/error.rs
index a9e664f4b..05dd37036 100644
--- a/crates/radicle/src/identity/doc/update/error.rs
+++ b/crates/radicle/src/identity/doc/update/error.rs
@@ -1,7 +1,7 @@
use thiserror::Error;
use crate::git;
-use crate::git::RefString;
+use crate::git::fmt::RefString;
use crate::identity::{doc::PayloadId, Did, DocError};
#[derive(Debug, Error)]
@@ -31,7 +31,7 @@ pub enum DocVerification {
#[error("incompatible payloads: The rule(s) xyz.radicle.crefs.rules.{matches:?} matches the value of xyz.radicle.project.defaultBranch ('{default}'). Possible resolutions: Change the name of the default branch or remove the rule(s).")]
DisallowDefault {
matches: Vec<String>,
- default: git::Qualified<'static>,
+ default: git::fmt::Qualified<'static>,
},
}
diff --git a/crates/radicle/src/identity/project.rs b/crates/radicle/src/identity/project.rs
index 242288efc..2b90cc6a3 100644
--- a/crates/radicle/src/identity/project.rs
+++ b/crates/radicle/src/identity/project.rs
@@ -7,9 +7,9 @@ use serde::{
use thiserror::Error;
use crate::crypto;
+use crate::git::BranchName;
use crate::identity::doc;
use crate::identity::doc::Payload;
-use crate::storage::BranchName;
pub use crypto::PublicKey;
diff --git a/crates/radicle/src/lib.rs b/crates/radicle/src/lib.rs
index c2be087c5..a83ba98dd 100644
--- a/crates/radicle/src/lib.rs
+++ b/crates/radicle/src/lib.rs
@@ -7,7 +7,6 @@ pub extern crate radicle_crypto as crypto;
#[macro_use]
extern crate amplify;
-extern crate radicle_git_ext as git_ext;
mod canonical;
@@ -42,10 +41,9 @@ pub mod prelude {
use super::*;
pub use crypto::{PublicKey, Verified};
+ pub use git::BranchName;
pub use identity::{project::Project, Did, Doc, RawDoc, RepoId};
pub use node::{Alias, NodeId, Timestamp};
pub use profile::Profile;
- pub use storage::{
- BranchName, ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage,
- };
+ pub use storage::{ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage};
}
diff --git a/crates/radicle/src/node.rs b/crates/radicle/src/node.rs
index c71de57ff..4b108a8cf 100644
--- a/crates/radicle/src/node.rs
+++ b/crates/radicle/src/node.rs
@@ -729,7 +729,7 @@ impl FetchResult {
}
}
- pub fn find_updated(&self, name: &git::RefStr) -> Option<RefUpdate> {
+ pub fn find_updated(&self, name: &git::fmt::RefStr) -> Option<RefUpdate> {
let updated = match self {
Self::Success { updated, .. } => Some(updated),
_ => None,
diff --git a/crates/radicle/src/node/events.rs b/crates/radicle/src/node/events.rs
index de63491ee..a8434c7b1 100644
--- a/crates/radicle/src/node/events.rs
+++ b/crates/radicle/src/node/events.rs
@@ -9,7 +9,8 @@ use std::time;
use crossbeam_channel as chan;
-use crate::git::{Oid, Qualified};
+use crate::git::fmt::Qualified;
+use crate::git::Oid;
use crate::node;
use crate::prelude::*;
use crate::storage::{refs, RefUpdate};
diff --git a/crates/radicle/src/node/notifications.rs b/crates/radicle/src/node/notifications.rs
index 23f77d9bc..704a2f946 100644
--- a/crates/radicle/src/node/notifications.rs
+++ b/crates/radicle/src/node/notifications.rs
@@ -7,7 +7,7 @@ use thiserror::Error;
use crate::cob;
use crate::cob::TypedId;
-use crate::git::{BranchName, Qualified};
+use crate::git::{fmt::Qualified, BranchName};
use crate::prelude::RepoId;
use crate::storage::{RefUpdate, RemoteId};
@@ -73,7 +73,7 @@ pub enum NotificationKindError {
TypedId(#[from] cob::ParseIdentifierError),
/// Invalid Git ref format.
#[error("invalid ref format: {0}")]
- RefFormat(#[from] radicle_git_ext::ref_format::Error),
+ RefFormat(#[from] crate::git::fmt::Error),
}
impl TryFrom<Qualified<'_>> for NotificationKind {
diff --git a/crates/radicle/src/node/notifications/store.rs b/crates/radicle/src/node/notifications/store.rs
index 6fe298705..4700e86e4 100644
--- a/crates/radicle/src/node/notifications/store.rs
+++ b/crates/radicle/src/node/notifications/store.rs
@@ -10,7 +10,9 @@ use sqlite as sql;
use thiserror::Error;
use crate::git;
-use crate::git::{Oid, RefError, RefString};
+use crate::git::fmt::RefString;
+use crate::git::Oid;
+use crate::git::RefError;
use crate::prelude::RepoId;
use crate::sql::transaction;
use crate::storage::RefUpdate;
@@ -40,7 +42,7 @@ pub enum Error {
RefName(#[from] RefError),
/// Invalid Git ref format.
#[error("invalid ref format: {0}")]
- RefFormat(#[from] git_ext::ref_format::Error),
+ RefFormat(#[from] crate::git::fmt::Error),
/// Invalid notification kind.
#[error("invalid notification kind: {0}")]
NotificationKind(#[from] NotificationKindError),
@@ -414,10 +416,10 @@ mod parse {
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
- use radicle_git_ext::ref_format::{qualified, refname};
+ use crate::git::fmt::{qualified, refname};
+ use crate::{cob, node::NodeId, test::arbitrary};
use super::*;
- use crate::{cob, node::NodeId, test::arbitrary};
#[test]
fn test_clear() {
diff --git a/crates/radicle/src/node/refs/store.rs b/crates/radicle/src/node/refs/store.rs
index deae273f2..c9fe0402d 100644
--- a/crates/radicle/src/node/refs/store.rs
+++ b/crates/radicle/src/node/refs/store.rs
@@ -6,7 +6,8 @@ use localtime::LocalTime;
use sqlite as sql;
use thiserror::Error;
-use crate::git::{Oid, Qualified};
+use crate::git::fmt::Qualified;
+use crate::git::Oid;
use crate::node::Database;
use crate::node::NodeId;
use crate::prelude::RepoId;
@@ -176,7 +177,7 @@ impl Store for Database {
#[allow(clippy::unwrap_used)]
mod test {
use super::*;
- use crate::git::qualified;
+ use crate::git::fmt::qualified;
use crate::test::arbitrary;
use localtime::{LocalDuration, LocalTime};
diff --git a/crates/radicle/src/node/seed.rs b/crates/radicle/src/node/seed.rs
index 5e2ae4bfc..642872e87 100644
--- a/crates/radicle/src/node/seed.rs
+++ b/crates/radicle/src/node/seed.rs
@@ -3,7 +3,6 @@ pub use store::{Error, Store};
use localtime::LocalTime;
-use crate::git;
use crate::node::KnownAddress;
use crate::prelude::NodeId;
use crate::storage::{refs::RefsAt, ReadRepository, RemoteId};
@@ -14,8 +13,7 @@ use crate::storage::{refs::RefsAt, ReadRepository, RemoteId};
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SyncedAt {
/// Head of `rad/sigrefs`.
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
- pub oid: git_ext::Oid,
+ pub oid: crate::git::Oid,
/// When these refs were synced.
#[serde(with = "crate::serde_ext::localtime::time")]
#[cfg_attr(
@@ -27,7 +25,10 @@ pub struct SyncedAt {
impl SyncedAt {
/// Load a new [`SyncedAt`] for the given remote.
- pub fn load<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, git::ext::Error> {
+ pub fn load<S: ReadRepository>(
+ repo: &S,
+ remote: RemoteId,
+ ) -> Result<Self, crate::git::raw::Error> {
let refs = RefsAt::new(repo, remote)?;
let oid = refs.at;
@@ -35,7 +36,10 @@ impl SyncedAt {
}
/// Create a new [`SyncedAt`] given an OID, by looking up the timestamp in the repo.
- pub fn new<S: ReadRepository>(oid: git::ext::Oid, repo: &S) -> Result<Self, git::ext::Error> {
+ pub fn new<S: ReadRepository>(
+ oid: crate::git::Oid,
+ repo: &S,
+ ) -> Result<Self, crate::git::raw::Error> {
let timestamp = repo.commit(oid)?.time();
let timestamp = LocalTime::from_secs(timestamp.seconds() as u64);
diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs
index 505a0d642..7b1086a5e 100644
--- a/crates/radicle/src/rad.rs
+++ b/crates/radicle/src/rad.rs
@@ -9,6 +9,7 @@ use thiserror::Error;
use crate::cob::ObjectId;
use crate::crypto::Verified;
use crate::git;
+use crate::git::BranchName;
use crate::identity::doc;
use crate::identity::doc::{DocError, RepoId, Visibility};
use crate::identity::project::{Project, ProjectName};
@@ -17,18 +18,18 @@ use crate::storage::git::transport;
use crate::storage::git::Repository;
use crate::storage::refs::SignedRefs;
use crate::storage::RepositoryError;
-use crate::storage::{BranchName, ReadRepository as _, RemoteId, SignRepository as _};
+use crate::storage::{ReadRepository as _, RemoteId, SignRepository as _};
use crate::storage::{WriteRepository, WriteStorage};
use crate::{identity, storage};
/// Name of the radicle storage remote.
-pub static REMOTE_NAME: LazyLock<git::RefString> = LazyLock::new(|| git::refname!("rad"));
+pub static REMOTE_NAME: LazyLock<git::fmt::RefString> = LazyLock::new(|| git::fmt::refname!("rad"));
/// Name of the radicle storage remote.
-pub static REMOTE_COMPONENT: LazyLock<git::Component> =
- LazyLock::new(|| git::fmt::name::component!("rad"));
+pub static REMOTE_COMPONENT: LazyLock<git::fmt::Component> =
+ LazyLock::new(|| git::fmt::component!("rad"));
/// Refname used for pushing patches.
-pub static PATCHES_REFNAME: LazyLock<git::RefString> =
- LazyLock::new(|| git::refname!("refs/patches"));
+pub static PATCHES_REFNAME: LazyLock<git::fmt::RefString> =
+ LazyLock::new(|| git::fmt::refname!("refs/patches"));
#[derive(Error, Debug)]
pub enum InitError {
@@ -110,16 +111,16 @@ where
git::configure_repository(repo)?;
git::configure_remote(repo, &REMOTE_NAME, url, &url.clone().with_namespace(*pk))?;
- let branch = git::Qualified::from(git::fmt::lit::refs_heads(default_branch));
+ let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(default_branch));
{
// Push branch to storage.
let stored = dunce::canonicalize(stored.path())?.display().to_string();
// Pushes to default branch to the namespace of the `signer`.
- let pushspec = git::Refspec {
+ let pushspec = git::fmt::refspec::Refspec {
src: branch.clone(),
- dst: branch.with_namespace(git::Component::from(pk)),
+ dst: branch.with_namespace(git::fmt::Component::from(pk)),
force: false,
}
.to_string();
@@ -128,8 +129,8 @@ where
}
// N.b. we need to create the remote branch for the default branch
- let rad_remote =
- git::Qualified::from(git::lit::refs_remotes(&*REMOTE_COMPONENT)).join(default_branch);
+ let rad_remote = git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_COMPONENT))
+ .join(default_branch);
let oid = repo.refname_to_id(branch.as_str())?;
repo.reference(
rad_remote.as_str(),
@@ -215,7 +216,7 @@ where
raw.reference(
&canonical_branch.with_namespace(me.into()),
- *canonical_head,
+ canonical_head.into(),
true,
&format!("creating default branch for {me}"),
)?;
@@ -281,10 +282,10 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
{
// Fetch remote head to working copy.
- let fetchspec = git::Refspec {
- src: git::refspec::pattern!("refs/heads/*"),
- dst: git::Qualified::from(git::lit::refs_remotes(&*REMOTE_NAME))
- .to_pattern(git::refspec::STAR)
+ let fetchspec = git::fmt::refspec::Refspec {
+ src: git::fmt::pattern!("refs/heads/*"),
+ dst: git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_NAME))
+ .to_pattern(git::fmt::refspec::STAR)
.into_patternstring(),
force: false,
}
@@ -456,15 +457,15 @@ pub fn repo_jj_git_root() -> Result<git::raw::Repository, JujutsuGitRootError> {
/// Setup patch upstream branch such that `git push` updates the patch.
pub fn setup_patch_upstream<'a>(
patch: &ObjectId,
- patch_head: git::Oid,
- working: &'a git::raw::Repository,
- remote: &git::RefString,
+ patch_head: crate::git::Oid,
+ working: &'a crate::git::raw::Repository,
+ remote: &git::fmt::RefString,
force: bool,
-) -> Result<Option<git::raw::Branch<'a>>, git::ext::Error> {
+) -> Result<Option<crate::git::raw::Branch<'a>>, crate::git::raw::Error> {
let head = working.head()?;
// Don't do anything in case we're not on the patch branch.
- if head.peel_to_commit()?.id() != *patch_head {
+ if patch_head != head.peel_to_commit()?.id() {
return Ok(None);
}
let Ok(r) = head.resolve() else {
@@ -476,18 +477,18 @@ pub fn setup_patch_upstream<'a>(
return Ok(None);
}
- let branch = git::raw::Branch::wrap(r);
+ let branch = crate::git::raw::Branch::wrap(r);
// Only set the upstream if it's missing or `force` is `true`
if branch.upstream().is_ok() && !force {
return Ok(None);
}
- let name: Option<git::RefString> = branch.name()?.and_then(|b| b.try_into().ok());
+ let name: Option<git::fmt::RefString> = branch.name()?.and_then(|b| b.try_into().ok());
let remote_branch = git::refs::workdir::patch_upstream(patch);
let remote_branch = working.reference(
&remote_branch,
- *patch_head,
+ patch_head.into(),
true,
"Create remote tracking branch for patch",
)?;
@@ -498,7 +499,7 @@ pub fn setup_patch_upstream<'a>(
git::set_upstream(working, remote, name.as_str(), git::refs::patch(patch))?;
}
}
- Ok(Some(git::raw::Branch::wrap(remote_branch)))
+ Ok(Some(crate::git::raw::Branch::wrap(remote_branch)))
}
#[cfg(test)]
@@ -508,12 +509,12 @@ mod tests {
use pretty_assertions::assert_eq;
- use crate::git::{name::component, qualified};
use crate::identity::Did;
use crate::storage::git::transport;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, RemoteRepository as _};
use crate::test::fixtures;
+ use git::fmt::{component, qualified};
use super::*;
@@ -531,7 +532,7 @@ mod tests {
&repo,
"acme".try_into().unwrap(),
"Acme's repo",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&signer,
&storage,
@@ -553,19 +554,19 @@ mod tests {
// Test canonical refs.
assert_eq!(refs.head(component!("master")).unwrap(), head);
- assert_eq!(project_repo.raw().refname_to_id("HEAD").unwrap(), *head);
+ assert_eq!(head, project_repo.raw().refname_to_id("HEAD").unwrap());
assert_eq!(
+ head,
project_repo
.raw()
.refname_to_id("refs/heads/master")
.unwrap(),
- *head
);
assert_eq!(remotes[&public_key].refs, refs);
assert_eq!(project.name(), "acme");
assert_eq!(project.description(), "Acme's repo");
- assert_eq!(project.default_branch(), &git::refname!("master"));
+ assert_eq!(project.default_branch(), &git::fmt::refname!("master"));
assert_eq!(doc.delegates().first(), &Did::from(public_key));
}
@@ -586,7 +587,7 @@ mod tests {
&original,
"acme".try_into().unwrap(),
"Acme's repo",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&alice,
&storage,
@@ -622,7 +623,7 @@ mod tests {
&original,
"acme".try_into().unwrap(),
"Acme's repo",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
&signer,
&storage,
diff --git a/crates/radicle/src/schemars_ext.rs b/crates/radicle/src/schemars_ext.rs
index 07b125f2b..bac10a98d 100644
--- a/crates/radicle/src/schemars_ext.rs
+++ b/crates/radicle/src/schemars_ext.rs
@@ -90,23 +90,9 @@ pub(crate) mod localtime {
}
pub(crate) mod git {
- use super::*;
-
- /// See [`crate::git::Oid`]
- /// See [`::git_ext::Oid`]
- /// See [`::git::raw::Oid`]
- ///
- /// A Git Object Identifier in hexadecimal encoding.
- #[derive(JsonSchema)]
- #[schemars(
- remote = "git::raw::Oid",
- description = "A Git Object Identifier (SHA-1 or SHA-256 hash) in hexadecimal encoding."
- )]
- pub(crate) struct Oid(
- #[schemars(regex(pattern = r"^([0-9a-fA-F]{64}|[0-9a-fA-F]{40})$"))] String,
- );
-
- /// See [`crate::git::RefString`]
- #[derive(JsonSchema)]
- pub(crate) struct RefString(String);
+ pub(crate) mod fmt {
+ /// See [`crate::git::fmt::RefString`]
+ #[derive(schemars::JsonSchema)]
+ pub(crate) struct RefString(String);
+ }
}
diff --git a/crates/radicle/src/storage.rs b/crates/radicle/src/storage.rs
index 3e9a830f6..37acaf17b 100644
--- a/crates/radicle/src/storage.rs
+++ b/crates/radicle/src/storage.rs
@@ -10,15 +10,16 @@ use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
use thiserror::Error;
+pub use crate::git::Oid;
use crypto::{PublicKey, Unverified, Verified};
pub use git::{Validation, Validations};
-pub use radicle_git_ext::Oid;
use crate::cob;
use crate::collections::RandomMap;
+use crate::git::canonical;
+use crate::git::fmt::{refspec::PatternString, refspec::Refspec, Qualified, RefStr, RefString};
use crate::git::raw::ErrorExt as _;
-use crate::git::{canonical, ext as git_ext};
-use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefStr, RefString};
+use crate::git::RefError;
use crate::identity::{doc, Did, PayloadError};
use crate::identity::{Doc, DocAt, DocError};
use crate::identity::{Identity, RepoId};
@@ -27,10 +28,8 @@ use crate::node::SyncedAt;
use crate::storage::git::NAMESPACES_GLOB;
use crate::storage::refs::Refs;
-use self::git::UserInfo;
use self::refs::{RefsAt, SignedRefs};
-
-pub type BranchName = git::RefString;
+use crate::git::UserInfo;
/// Basic repository information.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -69,7 +68,9 @@ impl Namespaces {
Namespaces::Followed(pks) => pks
.iter()
.map(|pk| {
- let ns = pk.to_namespace().with_pattern(git::refspec::STAR);
+ let ns = pk
+ .to_namespace()
+ .with_pattern(crate::git::fmt::refspec::STAR);
Refspec {
src: ns.clone(),
dst: ns,
@@ -114,9 +115,7 @@ pub enum RepositoryError {
#[error(transparent)]
Payload(#[from] PayloadError),
#[error(transparent)]
- Git(#[from] git::raw::Error),
- #[error(transparent)]
- GitExt(#[from] git_ext::Error),
+ Git(#[from] crate::git::raw::Error),
#[error(transparent)]
Quorum(#[from] canonical::error::QuorumError),
#[error(transparent)]
@@ -135,7 +134,6 @@ impl RepositoryError {
pub fn is_not_found(&self) -> bool {
match self {
Self::Storage(e) => e.is_not_found(),
- Self::GitExt(e) => e.is_not_found(),
Self::Git(e) => e.is_not_found(),
_ => false,
}
@@ -154,9 +152,7 @@ pub enum Error {
#[error(transparent)]
Refs(#[from] refs::Error),
#[error("git: {0}")]
- Git(#[from] git::raw::Error),
- #[error("git: {0}")]
- Ext(#[from] git::ext::Error),
+ Git(#[from] crate::git::raw::Error),
#[error("invalid repository identifier {0:?}")]
InvalidId(std::ffi::OsString),
#[error("i/o: {0}")]
@@ -180,7 +176,7 @@ impl Error {
#[allow(clippy::large_enum_variant)]
pub enum FetchError {
#[error("git: {0}")]
- Git(#[from] git::raw::Error),
+ Git(#[from] crate::git::raw::Error),
#[error("i/o: {0}")]
Io(#[from] io::Error),
#[error(transparent)]
@@ -205,36 +201,31 @@ pub enum RefUpdate {
Updated {
#[cfg_attr(
feature = "schemars",
- schemars(with = "crate::schemars_ext::git::RefString")
+ schemars(with = "crate::schemars_ext::git::fmt::RefString")
)]
name: RefString,
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
old: Oid,
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
new: Oid,
},
Created {
#[cfg_attr(
feature = "schemars",
- schemars(with = "crate::schemars_ext::git::RefString")
+ schemars(with = "crate::schemars_ext::git::fmt::RefString")
)]
name: RefString,
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
Deleted {
#[cfg_attr(
feature = "schemars",
- schemars(with = "crate::schemars_ext::git::RefString")
+ schemars(with = "crate::schemars_ext::git::fmt::RefString")
)]
name: RefString,
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
Skipped {
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
name: RefString,
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
}
@@ -504,7 +495,7 @@ pub trait ReadRepository: Sized + ValidateRepository {
fn id(&self) -> RepoId;
/// Returns `true` if there are no references in the repository.
- fn is_empty(&self) -> Result<bool, git::raw::Error>;
+ fn is_empty(&self) -> Result<bool, crate::git::raw::Error>;
/// The [`Path`] to the git repository.
fn path(&self) -> &Path;
@@ -514,10 +505,10 @@ pub trait ReadRepository: Sized + ValidateRepository {
&self,
commit: Oid,
path: P,
- ) -> Result<git::raw::Blob, git_ext::Error>;
+ ) -> Result<crate::git::raw::Blob, crate::git::raw::Error>;
/// Get a blob in this repository, given its id.
- fn blob(&self, oid: Oid) -> Result<git::raw::Blob, git_ext::Error>;
+ fn blob(&self, oid: Oid) -> Result<crate::git::raw::Blob, crate::git::raw::Error>;
/// Get the head of this repository.
///
@@ -541,7 +532,7 @@ pub trait ReadRepository: Sized + ValidateRepository {
fn identity_head(&self) -> Result<Oid, RepositoryError>;
/// Get the identity head of a specific remote.
- fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, git::ext::Error>;
+ fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git::raw::Error>;
/// Get the root commit of the canonical identity branch.
fn identity_root(&self) -> Result<Oid, RepositoryError>;
@@ -577,28 +568,28 @@ pub trait ReadRepository: Sized + ValidateRepository {
&self,
remote: &RemoteId,
reference: &Qualified,
- ) -> Result<git::raw::Reference, git_ext::Error>;
+ ) -> Result<crate::git::raw::Reference, crate::git::raw::Error>;
- /// Get the [`git::raw::Commit`] found using its `oid`.
+ /// Get the [`crate::git::raw::Commit`] found using its `oid`.
///
/// Returns `Err` if the commit did not exist.
- fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git::ext::Error>;
+ fn commit(&self, oid: Oid) -> Result<crate::git::raw::Commit, crate::git::raw::Error>;
/// Perform a revision walk of a commit history starting from the given head.
- fn revwalk(&self, head: Oid) -> Result<git::raw::Revwalk, git::raw::Error>;
+ fn revwalk(&self, head: Oid) -> Result<crate::git::raw::Revwalk, crate::git::raw::Error>;
/// Check if the underlying ODB contains the given `oid`.
- fn contains(&self, oid: Oid) -> Result<bool, git::raw::Error>;
+ fn contains(&self, oid: Oid) -> Result<bool, crate::git::raw::Error>;
/// Check whether the given commit is an ancestor of another commit.
- fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git::ext::Error>;
+ fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git::raw::Error>;
/// Get the object id of a reference under the given remote.
fn reference_oid(
&self,
remote: &RemoteId,
reference: &Qualified,
- ) -> Result<Oid, git::raw::Error>;
+ ) -> Result<Oid, crate::git::raw::Error>;
/// Get all references of the given remote.
fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error>;
@@ -610,8 +601,8 @@ pub trait ReadRepository: Sized + ValidateRepository {
/// commit pointed to by the tag is returned, and not the [`Oid`] of the tag itsself.
fn references_glob(
&self,
- pattern: &git::PatternStr,
- ) -> Result<Vec<(Qualified, Oid)>, git::ext::Error>;
+ pattern: &crate::git::fmt::refspec::PatternStr,
+ ) -> Result<Vec<(Qualified, Oid)>, crate::git::raw::Error>;
/// Get repository delegates.
fn delegates(&self) -> Result<NonEmpty<Did>, RepositoryError> {
@@ -632,7 +623,7 @@ pub trait ReadRepository: Sized + ValidateRepository {
fn identity_doc_at(&self, head: Oid) -> Result<DocAt, DocError>;
/// Get the merge base of two commits.
- fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error>;
+ fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error>;
}
/// Access the remotes of a repository.
@@ -697,7 +688,7 @@ pub trait WriteRepository: ReadRepository + SignRepository {
/// Set the user info of the Git repository.
fn set_user(&self, info: &UserInfo) -> Result<(), Error>;
/// Get the underlying git repository.
- fn raw(&self) -> &git::raw::Repository;
+ fn raw(&self) -> &crate::git::raw::Repository;
}
/// Allows signing refs.
diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs
index bdfea41e3..faf5f29e7 100644
--- a/crates/radicle/src/storage/git.rs
+++ b/crates/radicle/src/storage/git.rs
@@ -27,24 +27,26 @@ use crate::storage::{
ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository,
WriteRepository, WriteStorage,
};
-use crate::{git, node};
+use crate::{git, git::Oid, node};
-pub use crate::git::{
- ext, raw, refname, refspec, Oid, PatternStr, Qualified, RefError, RefString, UserInfo,
+use crate::git::fmt::{
+ refname, refspec, refspec::PatternStr, refspec::PatternString, Qualified, RefString,
};
+use crate::git::RefError;
+use crate::git::UserInfo;
pub use crate::storage::{Error, RepositoryError};
use super::refs::RefsAt;
use super::{RemoteId, RemoteRepository, ValidateRepository};
-pub static NAMESPACES_GLOB: LazyLock<git::refspec::PatternString> =
- LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*"));
+pub static NAMESPACES_GLOB: LazyLock<PatternString> =
+ LazyLock::new(|| git::fmt::pattern!("refs/namespaces/*"));
pub static SIGREFS_GLOB: LazyLock<refspec::PatternString> =
- LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*/rad/sigrefs"));
-pub static CANONICAL_IDENTITY: LazyLock<git::Qualified> = LazyLock::new(|| {
- git::Qualified::from_components(
- git::name::component!("rad"),
- git::name::component!("id"),
+ LazyLock::new(|| git::fmt::pattern!("refs/namespaces/*/rad/sigrefs"));
+pub static CANONICAL_IDENTITY: LazyLock<git::fmt::Qualified> = LazyLock::new(|| {
+ git::fmt::Qualified::from_components(
+ git::fmt::component!("rad"),
+ git::fmt::component!("id"),
None,
)
});
@@ -52,7 +54,7 @@ pub static CANONICAL_IDENTITY: LazyLock<git::Qualified> = LazyLock::new(|| {
/// A parsed Git reference.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ref {
- pub oid: git::Oid,
+ pub oid: crate::git::Oid,
pub name: RefString,
pub namespace: Option<RemoteId>,
}
@@ -446,9 +448,9 @@ impl Repository {
continue;
}
- let glob = git::refname!("refs/namespaces")
- .join(git::Component::from(&id))
- .with_pattern(git::refspec::STAR);
+ let glob = git::fmt::refname!("refs/namespaces")
+ .join(git::fmt::Component::from(&id))
+ .with_pattern(git::fmt::refspec::STAR);
let refs = match self.references_glob(&glob) {
Ok(refs) => refs,
Err(e) => {
@@ -476,7 +478,7 @@ impl Repository {
doc: &Doc,
storage: &S,
signer: &Device<G>,
- ) -> Result<(Self, git::Oid), RepositoryError>
+ ) -> Result<(Self, crate::git::Oid), RepositoryError>
where
G: crypto::signature::Signer<crypto::Signature>,
S: WriteStorage,
@@ -486,7 +488,7 @@ impl Repository {
let repo = Self::create(paths::repository(storage, &id), id, storage.info())?;
let oid = repo.backend.blob(&doc_bytes)?; // Store document blob in repository.
- debug_assert_eq!(oid, *doc_oid);
+ debug_assert_eq!(doc_oid, oid);
let commit = doc.init(&repo, signer)?;
@@ -661,48 +663,52 @@ impl ReadRepository for Repository {
self.backend.path()
}
- fn blob_at<P: AsRef<Path>>(&self, commit: Oid, path: P) -> Result<git::raw::Blob, git::Error> {
- let commit = self.backend.find_commit(*commit)?;
+ fn blob_at<P: AsRef<Path>>(
+ &self,
+ commit_id: Oid,
+ path: P,
+ ) -> Result<git::raw::Blob, git::raw::Error> {
+ let commit = self.backend.find_commit(git::raw::Oid::from(commit_id))?;
let tree = commit.tree()?;
let entry = tree.get_path(path.as_ref())?;
let obj = entry.to_object(&self.backend)?;
- let blob = obj.into_blob().map_err(|_| {
- git::Error::NotFound(git::NotFound::NoSuchBlob(
- path.as_ref().display().to_string(),
- ))
- })?;
+ let blob = obj.into_blob().map_err(|_|
+ crate::git::raw::Error::new(
+ crate::git::raw::ErrorCode::NotFound,
+ crate::git::raw::ErrorClass::None,
+ format!("Path '{}' in tree of commit {commit_id} was expected to be a blob, but is not.", path.as_ref().display()),
+ )
+ )?;
Ok(blob)
}
- fn blob(&self, oid: Oid) -> Result<git::raw::Blob, git::Error> {
- self.backend.find_blob(oid.into()).map_err(git::Error::from)
+ fn blob(&self, oid: Oid) -> Result<git::raw::Blob, git::raw::Error> {
+ self.backend.find_blob(oid.into())
}
fn reference(
&self,
remote: &RemoteId,
- name: &git::Qualified,
- ) -> Result<git::raw::Reference, git::Error> {
+ name: &git::fmt::Qualified,
+ ) -> Result<git::raw::Reference, git::raw::Error> {
let name = name.with_namespace(remote.into());
- self.backend.find_reference(&name).map_err(git::Error::from)
+ self.backend.find_reference(&name)
}
fn reference_oid(
&self,
remote: &RemoteId,
- reference: &git::Qualified,
- ) -> Result<Oid, git::raw::Error> {
+ reference: &git::fmt::Qualified,
+ ) -> Result<Oid, crate::git::raw::Error> {
let name = reference.with_namespace(remote.into());
let oid = self.backend.refname_to_id(&name)?;
Ok(oid.into())
}
- fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git::Error> {
- self.backend
- .find_commit(oid.into())
- .map_err(git::Error::from)
+ fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git::raw::Error> {
+ self.backend.find_commit(oid.into())
}
fn revwalk(&self, head: Oid) -> Result<git::raw::Revwalk, git::raw::Error> {
@@ -712,14 +718,13 @@ impl ReadRepository for Repository {
Ok(revwalk)
}
- fn contains(&self, oid: Oid) -> Result<bool, raw::Error> {
+ fn contains(&self, oid: Oid) -> Result<bool, crate::git::raw::Error> {
self.backend.odb().map(|odb| odb.exists(oid.into()))
}
- fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git::Error> {
+ fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git::raw::Error> {
self.backend
.graph_descendant_of(head.into(), ancestor.into())
- .map_err(git::Error::from)
}
fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error> {
@@ -735,12 +740,14 @@ impl ReadRepository for Repository {
let oid = e.resolve()?.target().ok_or(Error::InvalidRef)?;
let (_, category, _, _) = refname.non_empty_components();
+ use git::fmt::{component, name};
+
if [
- git::name::HEADS,
- git::name::TAGS,
- git::name::NOTES,
- &git::name::component!("rad"),
- &git::name::component!("cobs"),
+ name::HEADS,
+ name::TAGS,
+ name::NOTES,
+ &component!("rad"),
+ &component!("cobs"),
]
.contains(&category.as_ref())
{
@@ -753,7 +760,7 @@ impl ReadRepository for Repository {
fn references_glob(
&self,
pattern: &PatternStr,
- ) -> Result<Vec<(Qualified, Oid)>, git::ext::Error> {
+ ) -> Result<Vec<(Qualified, Oid)>, crate::git::raw::Error> {
let mut refs = Vec::new();
for r in self.backend.references_glob(pattern)? {
@@ -765,8 +772,8 @@ impl ReadRepository for Repository {
if let Some(name) = r
.name()
- .and_then(|n| git::RefStr::try_from_str(n).ok())
- .and_then(git::Qualified::from_refstr)
+ .and_then(|n| git::fmt::RefStr::try_from_str(n).ok())
+ .and_then(git::fmt::Qualified::from_refstr)
{
refs.push((name.to_owned(), oid.into()));
}
@@ -823,9 +830,8 @@ impl ReadRepository for Repository {
}
}
- fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, git::ext::Error> {
+ fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git::raw::Error> {
self.reference_oid(remote, &git::refs::storage::IDENTITY_BRANCH)
- .map_err(git::ext::Error::from)
}
fn identity_root(&self) -> Result<Oid, RepositoryError> {
@@ -864,7 +870,7 @@ impl ReadRepository for Repository {
let blob = Doc::blob_at(root, self)?;
// We've got an identity that goes back to the correct root.
- if blob.id() == **self.id {
+ if *self.id == blob.id() {
let identity = Identity::get(&root.into(), self)?;
return Ok(identity.head());
@@ -873,11 +879,10 @@ impl ReadRepository for Repository {
Err(DocError::Missing.into())
}
- fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error> {
+ fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error> {
self.backend
- .merge_base(**left, **right)
+ .merge_base(left.into(), right.into())
.map(Oid::from)
- .map_err(git::ext::Error::from)
}
}
@@ -897,7 +902,7 @@ impl WriteRepository for Repository {
}
log::debug!(target: "storage", "Setting ref: {} -> {}", &branch_ref, new);
self.raw()
- .reference(&branch_ref, *new, true, "set-local-branch (radicle)")?;
+ .reference(&branch_ref, new.into(), true, "set-local-branch (radicle)")?;
log::debug!(target: "storage", "Setting ref: {head_ref} -> {branch_ref}");
self.raw()
@@ -910,7 +915,7 @@ impl WriteRepository for Repository {
log::debug!(target: "storage", "Setting ref: {} -> {}", *CANONICAL_IDENTITY, commit);
self.raw().reference(
CANONICAL_IDENTITY.as_str(),
- *commit,
+ commit.into(),
true,
"set-local-branch (radicle)",
)?;
@@ -925,7 +930,7 @@ impl WriteRepository for Repository {
let refname = git::refs::storage::id_root(remote);
self.raw()
- .reference(refname.as_str(), *root, true, "set-id-root (radicle)")?;
+ .reference(refname.as_str(), root.into(), true, "set-id-root (radicle)")?;
Ok(())
}
@@ -1104,7 +1109,7 @@ mod tests {
git::commit(
&working,
&head,
- &git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
+ &git::fmt::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
"Second commit",
&sig,
&head.tree().unwrap(),
diff --git a/crates/radicle/src/storage/git/cob.rs b/crates/radicle/src/storage/git/cob.rs
index f35a45097..1c612bd2d 100644
--- a/crates/radicle/src/storage/git/cob.rs
+++ b/crates/radicle/src/storage/git/cob.rs
@@ -12,6 +12,7 @@ use storage::SignRepository;
use storage::ValidateRepository;
use crate::git;
+use crate::git::fmt::*;
use crate::git::*;
use crate::identity;
use crate::identity::doc::DocError;
@@ -34,8 +35,6 @@ pub enum ObjectsError {
Convert(#[from] cob::object::storage::convert::Error),
#[error(transparent)]
Git(#[from] git::raw::Error),
- #[error(transparent)]
- GitExt(#[from] git_ext::Error),
}
#[derive(Error, Debug)]
@@ -290,7 +289,7 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
self.repo.path()
}
- fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git_ext::Error> {
+ fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git::raw::Error> {
self.repo.commit(oid)
}
@@ -298,39 +297,39 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
self.repo.revwalk(head)
}
- fn contains(&self, oid: Oid) -> Result<bool, raw::Error> {
+ fn contains(&self, oid: Oid) -> Result<bool, crate::git::raw::Error> {
self.repo.contains(oid)
}
- fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git_ext::Error> {
+ fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git::raw::Error> {
self.repo.is_ancestor_of(ancestor, head)
}
fn blob_at<P: AsRef<Path>>(
&self,
- oid: git_ext::Oid,
+ oid: Oid,
path: P,
- ) -> Result<git::raw::Blob, git_ext::Error> {
+ ) -> Result<git::raw::Blob, git::raw::Error> {
self.repo.blob_at(oid, path)
}
- fn blob(&self, oid: git_ext::Oid) -> Result<raw::Blob, ext::Error> {
+ fn blob(&self, oid: Oid) -> Result<crate::git::raw::Blob, crate::git::raw::Error> {
self.repo.blob(oid)
}
fn reference(
&self,
remote: &RemoteId,
- reference: &git::Qualified,
- ) -> Result<git::raw::Reference, git_ext::Error> {
+ reference: &git::fmt::Qualified,
+ ) -> Result<git::raw::Reference, git::raw::Error> {
self.repo.reference(remote, reference)
}
fn reference_oid(
&self,
remote: &RemoteId,
- reference: &git::Qualified,
- ) -> Result<git_ext::Oid, git::raw::Error> {
+ reference: &git::fmt::Qualified,
+ ) -> Result<Oid, crate::git::raw::Error> {
self.repo.reference_oid(remote, reference)
}
@@ -340,8 +339,8 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
fn references_glob(
&self,
- pattern: &git::PatternStr,
- ) -> Result<Vec<(fmt::Qualified, Oid)>, git::ext::Error> {
+ pattern: &git::fmt::refspec::PatternStr,
+ ) -> Result<Vec<(fmt::Qualified, Oid)>, crate::git::raw::Error> {
self.repo.references_glob(pattern)
}
@@ -357,7 +356,7 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
self.repo.identity_head()
}
- fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, super::ext::Error> {
+ fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git::raw::Error> {
self.repo.identity_head_of(remote)
}
@@ -373,14 +372,14 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
self.repo.canonical_identity_head()
}
- fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error> {
+ fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error> {
self.repo.merge_base(left, right)
}
}
impl<R: storage::WriteRepository> cob::object::Storage for DraftStore<'_, R> {
type ObjectsError = ObjectsError;
- type TypesError = git::ext::Error;
+ type TypesError = git::raw::Error;
type UpdateError = git::raw::Error;
type RemoveError = git::raw::Error;
diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs
index 9a109d71a..585e36beb 100644
--- a/crates/radicle/src/storage/refs.rs
+++ b/crates/radicle/src/storage/refs.rs
@@ -13,7 +13,6 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::git;
-use crate::git::ext as git_ext;
use crate::git::raw::ErrorExt as _;
use crate::git::Oid;
use crate::node::device::Device;
@@ -48,7 +47,7 @@ pub enum Error {
#[error("invalid reference")]
InvalidRef,
#[error("missing identity root reference '{0}'")]
- MissingIdentityRoot(git::RefString),
+ MissingIdentityRoot(git::fmt::RefString),
#[error("missing identity object '{0}'")]
MissingIdentity(Oid),
#[error("mismatched identity: local {local}, remote {remote}")]
@@ -57,26 +56,23 @@ pub enum Error {
Ref(#[from] git::RefError),
#[error(transparent)]
Git(#[from] git::raw::Error),
- #[error(transparent)]
- GitExt(#[from] git_ext::Error),
}
impl Error {
/// Whether this error is caused by a reference not being found.
pub fn is_not_found(&self) -> bool {
match self {
- Self::GitExt(e) => e.is_not_found(),
Self::Git(e) => e.is_not_found(),
_ => false,
}
}
}
-// TODO(finto): we should turn `git::RefString` to `git::Qualified`,
+// TODO(finto): we should turn `git::fmt::RefString` to `git::fmt::Qualified`,
// since all these refs SHOULD be `Qualified`.
/// The published state of a local repository.
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
-pub struct Refs(BTreeMap<git::RefString, Oid>);
+pub struct Refs(BTreeMap<git::fmt::RefString, Oid>);
impl Refs {
/// Verify the given signature on these refs, and return [`SignedRefs`] on success.
@@ -102,13 +98,13 @@ impl Refs {
}
/// Get a particular ref.
- pub fn get(&self, name: &git::Qualified) -> Option<Oid> {
+ pub fn get(&self, name: &git::fmt::Qualified) -> Option<Oid> {
self.0.get(name.to_ref_string().as_refstr()).copied()
}
/// Get a particular head ref.
- pub fn head(&self, name: impl AsRef<git::RefStr>) -> Option<Oid> {
- let branch = git::refname!("refs/heads").join(name);
+ pub fn head(&self, name: impl AsRef<git::fmt::RefStr>) -> Option<Oid> {
+ let branch = git::fmt::refname!("refs/heads").join(name);
self.0.get(&branch).copied()
}
@@ -123,8 +119,8 @@ impl Refs {
.split_once(' ')
.ok_or(canonical::Error::InvalidFormat)?;
- let name = git::RefString::try_from(name)?;
- let oid = Oid::from_str(oid)?;
+ let name = git::fmt::RefString::try_from(name)?;
+ let oid = Oid::from_str(oid).map_err(|_| canonical::Error::InvalidFormat)?;
if oid.is_zero() {
continue;
@@ -148,15 +144,15 @@ impl Refs {
}
impl IntoIterator for Refs {
- type Item = (git::RefString, Oid);
- type IntoIter = std::collections::btree_map::IntoIter<git::RefString, Oid>;
+ type Item = (git::fmt::RefString, Oid);
+ type IntoIter = std::collections::btree_map::IntoIter<git::fmt::RefString, Oid>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
-impl From<Refs> for BTreeMap<git::RefString, Oid> {
+impl From<Refs> for BTreeMap<git::fmt::RefString, Oid> {
fn from(refs: Refs) -> Self {
refs.0
}
@@ -168,14 +164,14 @@ impl<V> From<SignedRefs<V>> for Refs {
}
}
-impl From<BTreeMap<git::RefString, Oid>> for Refs {
- fn from(refs: BTreeMap<git::RefString, Oid>) -> Self {
+impl From<BTreeMap<git::fmt::RefString, Oid>> for Refs {
+ fn from(refs: BTreeMap<git::fmt::RefString, Oid>) -> Self {
Self(refs)
}
}
impl Deref for Refs {
- type Target = BTreeMap<git::RefString, Oid>;
+ type Target = BTreeMap<git::fmt::RefString, Oid>;
fn deref(&self) -> &Self::Target {
&self.0
@@ -297,7 +293,7 @@ impl SignedRefs<Verified> {
Some(SignedRefsAt { sigrefs, at }) if sigrefs.signature == self.signature => {
return Ok(Updated::Unchanged { oid: at });
}
- Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(*at)?),
+ Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(at.into())?),
None => None,
};
@@ -386,12 +382,14 @@ pub struct RefsAt {
)]
pub remote: RemoteId,
/// The commit SHA that `rad/sigrefs` points to.
- #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
pub at: Oid,
}
impl RefsAt {
- pub fn new<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, git::raw::Error> {
+ pub fn new<S: ReadRepository>(
+ repo: &S,
+ remote: RemoteId,
+ ) -> Result<Self, crate::git::raw::Error> {
let at = repo.reference_oid(&remote, &storage::refs::SIGREFS_BRANCH)?;
Ok(RefsAt { remote, at })
}
@@ -400,7 +398,7 @@ impl RefsAt {
SignedRefsAt::load_at(self.at, self.remote, repo)
}
- pub fn path(&self) -> &git::Qualified {
+ pub fn path(&self) -> &git::fmt::Qualified {
&SIGREFS_BRANCH
}
}
@@ -446,7 +444,7 @@ impl SignedRefsAt {
})
}
- pub fn iter(&self) -> impl Iterator<Item = (&git::RefString, &Oid)> {
+ pub fn iter(&self) -> impl Iterator<Item = (&git::fmt::RefString, &Oid)> {
self.sigrefs.refs.iter()
}
}
@@ -515,7 +513,7 @@ mod tests {
&paris_repo,
"paris".try_into().unwrap(),
"Paris repository",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Default::default(),
&alice,
storage,
@@ -528,7 +526,7 @@ mod tests {
&london_repo,
"london".try_into().unwrap(),
"London repository",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Default::default(),
&alice,
storage,
@@ -585,7 +583,7 @@ mod tests {
let bob_head = git::empty_commit(
&bob_working,
&paris_head,
- git::refname!("refs/heads/master").as_refstr(),
+ git::fmt::refname!("refs/heads/master").as_refstr(),
"Bob's commit",
&bob_sig,
)
@@ -602,9 +600,9 @@ mod tests {
assert_eq!(
sigrefs
- .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+ .get(&crate::git::fmt::qualified!("refs/heads/master"))
.unwrap(),
- bob_head.id().into()
+ bob_head.id()
);
(sigrefs, bob_head.id())
};
@@ -616,10 +614,10 @@ mod tests {
.unwrap();
assert_ne!(
alice_paris_sigrefs
- .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+ .get(&crate::git::fmt::qualified!("refs/heads/master"))
.unwrap(),
bob_paris_sigrefs
- .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+ .get(&crate::git::fmt::qualified!("refs/heads/master"))
.unwrap()
);
}
@@ -651,7 +649,8 @@ mod tests {
london
.raw()
.reference(
- git::refs::storage::branch_of(bob.public_key(), &git::refname!("master")).as_str(),
+ git::refs::storage::branch_of(bob.public_key(), &git::fmt::refname!("master"))
+ .as_str(),
bob_head,
false,
"",
diff --git a/crates/radicle/src/test.rs b/crates/radicle/src/test.rs
index b21ee03e5..e6b40ed2a 100644
--- a/crates/radicle/src/test.rs
+++ b/crates/radicle/src/test.rs
@@ -34,7 +34,7 @@ pub fn fetch<W: WriteRepository>(
};
callbacks.update_tips(|name, old, new| {
- if let Ok(name) = crate::git::RefString::try_from(name) {
+ if let Ok(name) = git::fmt::RefString::try_from(name) {
if name.to_namespaced().is_some() {
updates.push(RefUpdate::from(name, old, new));
// Returning `true` ensures the process is not aborted.
@@ -77,13 +77,8 @@ pub mod setup {
use crate::crypto::test::signer::MockSigner;
use crate::node::device::Device;
use crate::storage::git::transport::remote;
- use crate::{
- git,
- profile::Home,
- rad::REMOTE_NAME,
- test::{fixtures, storage::git::Repository},
- Storage,
- };
+ use crate::storage::git::Repository;
+ use crate::{git, profile::Home, rad::REMOTE_NAME, test::fixtures, Storage};
use crate::{prelude::*, rad};
/// A node.
@@ -188,7 +183,8 @@ pub mod setup {
&self,
blobs: impl IntoIterator<Item = (S, T)>,
) -> BranchWith {
- let refname = git::Qualified::from(git::lit::refs_heads(git::refname!("master")));
+ let refname =
+ git::fmt::Qualified::from(git::fmt::lit::refs_heads(git::fmt::refname!("master")));
let base = self.checkout.refname_to_id(refname.as_str()).unwrap();
let parent = self.checkout.find_commit(base).unwrap();
let oid = commit(&self.checkout, &refname, blobs, &[&parent]);
@@ -297,10 +293,10 @@ pub mod setup {
pub fn commit<S: AsRef<Path>, T: AsRef<[u8]>>(
repo: &git::raw::Repository,
- refname: &git::Qualified,
+ refname: &git::fmt::Qualified,
blobs: impl IntoIterator<Item = (S, T)>,
parents: &[&git::raw::Commit<'_>],
- ) -> git::Oid {
+ ) -> crate::git::Oid {
let tree = {
let mut tb = repo.treebuilder(None).unwrap();
for (name, blob) in blobs.into_iter() {
diff --git a/crates/radicle/src/test/arbitrary.rs b/crates/radicle/src/test/arbitrary.rs
index 88cd2b2d3..1df2cd9db 100644
--- a/crates/radicle/src/test/arbitrary.rs
+++ b/crates/radicle/src/test/arbitrary.rs
@@ -27,14 +27,14 @@ use crate::{cob, git};
pub fn oid() -> storage::Oid {
let oid_bytes: [u8; 20] = gen(1);
- storage::Oid::try_from(oid_bytes.as_slice()).unwrap()
+ storage::Oid::from_sha1(oid_bytes)
}
pub fn entry_id() -> cob::EntryId {
self::oid()
}
-pub fn refstring(len: usize) -> git::RefString {
+pub fn refstring(len: usize) -> git::fmt::RefString {
let mut buf = Vec::<u8>::new();
for _ in 0..len {
buf.push(fastrand::u8(0x61..0x7a));
@@ -135,7 +135,7 @@ impl Arbitrary for Project {
let description = iter::repeat_with(|| rng.alphanumeric())
.take(length * 2)
.collect();
- let default_branch: git::RefString = iter::repeat_with(|| rng.alphanumeric())
+ let default_branch: git::fmt::RefString = iter::repeat_with(|| rng.alphanumeric())
.take(length)
.collect::<String>()
.try_into()
@@ -207,7 +207,7 @@ impl Arbitrary for SignedRefs<Unverified> {
impl Arbitrary for Refs {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
- let mut refs: BTreeMap<git::RefString, storage::Oid> = BTreeMap::new();
+ let mut refs: BTreeMap<git::fmt::RefString, storage::Oid> = BTreeMap::new();
let mut bytes: [u8; 20] = [0; 20];
let names = &[
"heads/master",
@@ -225,8 +225,8 @@ impl Arbitrary for Refs {
for byte in &mut bytes {
*byte = u8::arbitrary(g);
}
- let oid = storage::Oid::try_from(&bytes[..]).unwrap();
- let name = git::RefString::try_from(*name).unwrap();
+ let oid = storage::Oid::from_sha1(bytes);
+ let name = git::fmt::RefString::try_from(*name).unwrap();
refs.insert(name, oid);
}
@@ -273,7 +273,7 @@ impl Arbitrary for storage::Remote<crypto::Unverified> {
impl Arbitrary for RepoId {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let bytes = <[u8; 20]>::arbitrary(g);
- let oid = git::Oid::try_from(bytes.as_slice()).unwrap();
+ let oid = crate::git::Oid::from_sha1(bytes);
RepoId::from(oid)
}
diff --git a/crates/radicle/src/test/fixtures.rs b/crates/radicle/src/test/fixtures.rs
index f30d92e7c..66fc00ae9 100644
--- a/crates/radicle/src/test/fixtures.rs
+++ b/crates/radicle/src/test/fixtures.rs
@@ -57,7 +57,7 @@ where
&repo,
name.try_into().unwrap(),
desc,
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
signer,
&storage,
@@ -92,7 +92,7 @@ where
&working,
"acme".try_into().unwrap(),
"Acme's repository",
- git::refname!("master"),
+ git::fmt::refname!("master"),
Visibility::default(),
signer,
storage,
@@ -131,19 +131,21 @@ fn repository_with<P: AsRef<Path>>(
config.set_str("user.name", USER_NAME).unwrap();
config.set_str("user.email", USER_EMAIL).unwrap();
}
+
let sig = git::raw::Signature::new(
USER_NAME,
USER_EMAIL,
&git::raw::Time::new(RADICLE_EPOCH, 0),
)
.unwrap();
+
let head = git::initial_commit(&repo, &sig).unwrap();
let tree = git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
let oid = {
let commit = git::commit(
&repo,
&head,
- git::refname!("refs/heads/master").as_refstr(),
+ git::fmt::refname!("refs/heads/master").as_refstr(),
"Second commit",
&sig,
&tree,
@@ -203,7 +205,7 @@ pub fn tag(
}
/// Populate a repository with commits, branches and blobs.
-pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec<git::Qualified> {
+pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec<git::fmt::Qualified> {
assert!(
scale <= 8,
"Scale parameter must be less than or equal to 8"
@@ -221,8 +223,8 @@ pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec<git::Qualified
.take(7)
.collect::<String>()
.to_lowercase();
- let name =
- git::refname!("feature").join(git::RefString::try_from(random.as_str()).unwrap());
+ let name = git::fmt::refname!("feature")
+ .join(git::fmt::RefString::try_from(random.as_str()).unwrap());
let signature = git::raw::Signature::now("Radicle", "radicle@radicle.xyz").unwrap();
rng.fill(&mut buffer);
@@ -244,7 +246,7 @@ pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec<git::Qualified
)
.unwrap();
- refs.push(git::Qualified::from_refstr(refstr).unwrap());
+ refs.push(git::fmt::Qualified::from_refstr(refstr).unwrap());
}
refs
}
@@ -279,7 +281,7 @@ pub mod gen {
let oid = git::commit(
&repo,
&head,
- git::refname!("refs/heads/master").as_refstr(),
+ git::fmt::refname!("refs/heads/master").as_refstr(),
string(16).as_str(),
&sig,
&tree,
diff --git a/crates/radicle/src/test/storage.rs b/crates/radicle/src/test/storage.rs
index 54c02f367..11f320778 100644
--- a/crates/radicle/src/test/storage.rs
+++ b/crates/radicle/src/test/storage.rs
@@ -4,7 +4,8 @@ use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use git_ext::ref_format as fmt;
+pub use crate::git;
+use crate::git::fmt;
use crate::crypto::Verified;
use crate::identity::doc::{Doc, DocAt, DocError, RawDoc, RepoId};
@@ -18,7 +19,7 @@ use super::{arbitrary, fixtures};
#[derive(Clone, Debug)]
pub struct MockStorage {
pub path: PathBuf,
- pub info: git::UserInfo,
+ pub info: crate::git::UserInfo,
/// All refs keyed by RID.
/// Each value is a map of refs keyed by node Id (public key).
@@ -67,7 +68,7 @@ impl MockStorage {
impl ReadStorage for MockStorage {
type Repository = MockRepository;
- fn info(&self) -> &git::UserInfo {
+ fn info(&self) -> &crate::git::UserInfo {
&self.info
}
@@ -215,10 +216,12 @@ impl ReadRepository for MockRepository {
todo!()
}
- fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git_ext::Error> {
- Err(git_ext::Error::NotFound(git_ext::NotFound::NoSuchObject(
- *oid,
- )))
+ fn commit(&self, oid: Oid) -> Result<git::raw::Commit, git::raw::Error> {
+ Err(git::raw::Error::new(
+ git::raw::ErrorCode::NotFound,
+ git::raw::ErrorClass::None,
+ format!("commit {oid} not found"),
+ ))
}
fn revwalk(&self, _head: Oid) -> Result<git::raw::Revwalk, git::raw::Error> {
@@ -232,39 +235,39 @@ impl ReadRepository for MockRepository {
.any(|sigrefs| sigrefs.at == oid || sigrefs.refs.values().any(|oid_| *oid_ == oid)))
}
- fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result<bool, git_ext::Error> {
+ fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result<bool, crate::git::raw::Error> {
Ok(true)
}
- fn blob(&self, _oid: Oid) -> Result<git::raw::Blob, git_ext::Error> {
+ fn blob(&self, _oid: Oid) -> Result<git::raw::Blob, git::raw::Error> {
todo!()
}
fn blob_at<P: AsRef<std::path::Path>>(
&self,
- _oid: git_ext::Oid,
+ _oid: Oid,
_path: P,
- ) -> Result<git::raw::Blob, git_ext::Error> {
+ ) -> Result<git::raw::Blob, git::raw::Error> {
todo!()
}
fn reference(
&self,
_remote: &RemoteId,
- _reference: &git::Qualified,
- ) -> Result<git::raw::Reference, git_ext::Error> {
+ _reference: &git::fmt::Qualified,
+ ) -> Result<git::raw::Reference, git::raw::Error> {
todo!()
}
fn reference_oid(
&self,
remote: &RemoteId,
- reference: &git::Qualified,
- ) -> Result<git_ext::Oid, git::raw::Error> {
+ reference: &crate::git::fmt::Qualified,
+ ) -> Result<Oid, crate::git::raw::Error> {
let not_found = || {
- git::raw::Error::new(
- git::raw::ErrorCode::NotFound,
- git::raw::ErrorClass::Reference,
+ crate::git::raw::Error::new(
+ crate::git::raw::ErrorCode::NotFound,
+ crate::git::raw::ErrorClass::Reference,
format!("could not find {reference} for {remote}"),
)
};
@@ -283,8 +286,8 @@ impl ReadRepository for MockRepository {
fn references_glob(
&self,
- _pattern: &git::PatternStr,
- ) -> Result<Vec<(fmt::Qualified, Oid)>, git::ext::Error> {
+ _pattern: &crate::git::fmt::refspec::PatternStr,
+ ) -> Result<Vec<(fmt::Qualified, Oid)>, crate::git::raw::Error> {
todo!()
}
@@ -300,7 +303,7 @@ impl ReadRepository for MockRepository {
self.canonical_identity_head()
}
- fn identity_head_of(&self, _remote: &RemoteId) -> Result<Oid, git::ext::Error> {
+ fn identity_head_of(&self, _remote: &RemoteId) -> Result<Oid, crate::git::raw::Error> {
Ok(self.doc.commit)
}
@@ -316,7 +319,7 @@ impl ReadRepository for MockRepository {
Ok(self.doc.commit)
}
- fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result<Oid, git::ext::Error> {
+ fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result<Oid, crate::git::raw::Error> {
todo!()
}
}
@@ -342,7 +345,7 @@ impl WriteRepository for MockRepository {
todo!()
}
- fn set_user(&self, _info: &git::UserInfo) -> Result<(), Error> {
+ fn set_user(&self, _info: &crate::git::UserInfo) -> Result<(), Error> {
todo!()
}
}
commit f4a8908727515ef857e620c0090e8186f196529a
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sun Oct 5 12:22:47 2025 +0200
git-metadata: New crate extract from `radicle-git`
This is just enough to get heartwood going.
Some code from the `radicle-git-ext` crate in the `radicle-git`
workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
copied.
diff --git a/Cargo.lock b/Cargo.lock
index f3be1efd9..237f85e6c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2914,6 +2914,13 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "radicle-git-metadata"
+version = "0.1.0"
+dependencies = [
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "radicle-git-ref-format"
version = "0.1.0"
diff --git a/crates/radicle-git-metadata/Cargo.toml b/crates/radicle-git-metadata/Cargo.toml
new file mode 100644
index 000000000..ab58b3f08
--- /dev/null
+++ b/crates/radicle-git-metadata/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "radicle-git-metadata"
+description = "Radicle structs that carry Git commit metadata"
+homepage.workspace = true
+repository.workspace = true
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+keywords = ["radicle", "git", "metadata"]
+rust-version.workspace = true
+
+[dependencies]
+thiserror = { workspace = true }
\ No newline at end of file
diff --git a/crates/radicle-git-metadata/src/author.rs b/crates/radicle-git-metadata/src/author.rs
new file mode 100644
index 000000000..e0c133658
--- /dev/null
+++ b/crates/radicle-git-metadata/src/author.rs
@@ -0,0 +1,125 @@
+use std::{
+ fmt,
+ num::ParseIntError,
+ str::{self, FromStr},
+};
+
+use thiserror::Error;
+
+/// The data for indicating authorship of an action within
+/// [`crate::commit::CommitData`].
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub struct Author {
+ /// Name corresponding to `user.name` in the git config.
+ ///
+ /// Note: this must not contain `<` or `>`.
+ pub name: String,
+ /// Email corresponding to `user.email` in the git config.
+ ///
+ /// Note: this must not contain `<` or `>`.
+ pub email: String,
+ /// The time of this author's action.
+ pub time: Time,
+}
+
+/// The time of a [`Author`]'s action.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct Time {
+ seconds: i64,
+ offset: i32,
+}
+
+impl Time {
+ pub fn new(seconds: i64, offset: i32) -> Self {
+ Self { seconds, offset }
+ }
+
+ /// Return the time, in seconds, since the epoch.
+ pub fn seconds(&self) -> i64 {
+ self.seconds
+ }
+
+ /// Return the timezone offset, in minutes.
+ pub fn offset(&self) -> i32 {
+ self.offset
+ }
+
+ fn from_components<'a>(cs: &mut impl Iterator<Item = &'a str>) -> Result<Self, ParseError> {
+ let offset = match cs.next() {
+ None => Err(ParseError::Missing("offset")),
+ Some(offset) => Self::parse_offset(offset).map_err(ParseError::Offset),
+ }?;
+ let time = match cs.next() {
+ None => return Err(ParseError::Missing("time")),
+ Some(time) => time.parse::<i64>().map_err(ParseError::Time)?,
+ };
+ Ok(Self::new(time, offset))
+ }
+
+ fn parse_offset(offset: &str) -> Result<i32, ParseIntError> {
+ // The offset is in the form of timezone offset,
+ // e.g. +0200, -0100. This needs to be converted into
+ // minutes. The first two digits in the offset are the
+ // number of hours in the offset, while the latter two
+ // digits are the number of minutes in the offset.
+ let tz_offset = offset.parse::<i32>()?;
+ let hours = tz_offset / 100;
+ let minutes = tz_offset % 100;
+ Ok(hours * 60 + minutes)
+ }
+}
+
+impl FromStr for Time {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Self::from_components(&mut s.split(' ').rev())
+ }
+}
+
+impl fmt::Display for Time {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let sign = if self.offset.is_negative() { '-' } else { '+' };
+ let hours = self.offset.abs() / 60;
+ let minutes = self.offset.abs() % 60;
+ write!(f, "{} {}{:0>2}{:0>2}", self.seconds, sign, hours, minutes)
+ }
+}
+
+impl fmt::Display for Author {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} <{}> {}", self.name, self.email, self.time,)
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum ParseError {
+ #[error("missing '{0}' while parsing person signature")]
+ Missing(&'static str),
+ #[error("offset was incorrect format while parsing person signature")]
+ Offset(#[source] ParseIntError),
+ #[error("time was incorrect format while parsing person signature")]
+ Time(#[source] ParseIntError),
+}
+
+impl FromStr for Author {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ // Splitting the string in 4 subcomponents is expected to give back the
+ // following iterator entries: timezone offset, time, email, and name
+ let mut components = s.rsplitn(4, ' ');
+ let time = Time::from_components(&mut components)?;
+ let email = components
+ .next()
+ .ok_or(ParseError::Missing("email"))?
+ .trim_matches(|c| c == '<' || c == '>')
+ .to_owned();
+ let name = components.next().ok_or(ParseError::Missing("name"))?;
+ Ok(Self {
+ name: name.to_owned(),
+ email: email.to_owned(),
+ time,
+ })
+ }
+}
diff --git a/crates/radicle-git-metadata/src/commit.rs b/crates/radicle-git-metadata/src/commit.rs
new file mode 100644
index 000000000..628729608
--- /dev/null
+++ b/crates/radicle-git-metadata/src/commit.rs
@@ -0,0 +1,179 @@
+pub mod headers;
+pub mod trailers;
+
+use core::fmt;
+use std::str;
+
+use headers::{Headers, Signature};
+use trailers::{OwnedTrailer, Trailer};
+
+use crate::author::Author;
+
+/// A git commit in its object description form, i.e. the output of
+/// `git cat-file` for a commit object.
+#[derive(Debug)]
+pub struct CommitData<Tree, Parent> {
+ tree: Tree,
+ parents: Vec<Parent>,
+ author: Author,
+ committer: Author,
+ headers: Headers,
+ message: String,
+ trailers: Vec<OwnedTrailer>,
+}
+
+impl<Tree, Parent> CommitData<Tree, Parent> {
+ pub fn new<P, I, T>(
+ tree: Tree,
+ parents: P,
+ author: Author,
+ committer: Author,
+ headers: Headers,
+ message: String,
+ trailers: I,
+ ) -> Self
+ where
+ P: IntoIterator<Item = Parent>,
+ I: IntoIterator<Item = T>,
+ OwnedTrailer: From<T>,
+ {
+ let trailers = trailers.into_iter().map(OwnedTrailer::from).collect();
+ let parents = parents.into_iter().collect();
+ Self {
+ tree,
+ parents,
+ author,
+ committer,
+ headers,
+ message,
+ trailers,
+ }
+ }
+
+ /// The tree this commit points to.
+ pub fn tree(&self) -> &Tree {
+ &self.tree
+ }
+
+ /// The parents of this commit.
+ pub fn parents(&self) -> impl Iterator<Item = Parent> + '_
+ where
+ Parent: Clone,
+ {
+ self.parents.iter().cloned()
+ }
+
+ /// The author of this commit, i.e. the header corresponding to `author`.
+ pub fn author(&self) -> &Author {
+ &self.author
+ }
+
+ /// The committer of this commit, i.e. the header corresponding to
+ /// `committer`.
+ pub fn committer(&self) -> &Author {
+ &self.committer
+ }
+
+ /// The message body of this commit.
+ pub fn message(&self) -> &str {
+ &self.message
+ }
+
+ /// The [`Signature`]s found in this commit, i.e. the headers corresponding
+ /// to `gpgsig`.
+ pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
+ self.headers.signatures()
+ }
+
+ /// The [`Headers`] found in this commit.
+ ///
+ /// Note: these do not include `tree`, `parent`, `author`, and `committer`.
+ pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
+ self.headers.iter()
+ }
+
+ /// Iterate over the [`Headers`] values that match the provided `name`.
+ pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + 'a {
+ self.headers.values(name)
+ }
+
+ /// Push a header to the end of the headers section.
+ pub fn push_header(&mut self, name: &str, value: &str) {
+ self.headers.push(name, value.trim());
+ }
+
+ pub fn trailers(&self) -> impl Iterator<Item = &OwnedTrailer> {
+ self.trailers.iter()
+ }
+
+ /// Convert the `CommitData::tree` into a value of type `U`. The
+ /// conversion function `f` can be fallible.
+ ///
+ /// For example, `map_tree` can be used to turn raw tree data into
+ /// an `Oid` by writing it to a repository.
+ pub fn map_tree<U, E, F>(self, f: F) -> Result<CommitData<U, Parent>, E>
+ where
+ F: FnOnce(Tree) -> Result<U, E>,
+ {
+ Ok(CommitData {
+ tree: f(self.tree)?,
+ parents: self.parents,
+ author: self.author,
+ committer: self.committer,
+ headers: self.headers,
+ message: self.message,
+ trailers: self.trailers,
+ })
+ }
+
+ /// Convert the [`CommitData::parents`] into a vector containing
+ /// values of type `U`. The conversion function `f` can be
+ /// fallible.
+ ///
+ /// For example, this can be used to resolve the object identifiers
+ /// to their respective full commits.
+ pub fn map_parents<U, E, F>(self, f: F) -> Result<CommitData<Tree, U>, E>
+ where
+ F: FnMut(Parent) -> Result<U, E>,
+ {
+ Ok(CommitData {
+ tree: self.tree,
+ parents: self
+ .parents
+ .into_iter()
+ .map(f)
+ .collect::<Result<Vec<_>, _>>()?,
+ author: self.author,
+ committer: self.committer,
+ headers: self.headers,
+ message: self.message,
+ trailers: self.trailers,
+ })
+ }
+}
+
+impl<Tree: fmt::Display, Parent: fmt::Display> fmt::Display for CommitData<Tree, Parent> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "tree {}", self.tree)?;
+ for parent in self.parents.iter() {
+ writeln!(f, "parent {parent}")?;
+ }
+ writeln!(f, "author {}", self.author)?;
+ writeln!(f, "committer {}", self.committer)?;
+
+ for (name, value) in self.headers.iter() {
+ writeln!(f, "{name} {}", value.replace('\n', "\n "))?;
+ }
+ writeln!(f)?;
+ write!(f, "{}", self.message.trim())?;
+ writeln!(f)?;
+
+ if !self.trailers.is_empty() {
+ writeln!(f)?;
+ }
+ for trailer in self.trailers.iter() {
+ writeln!(f, "{}", Trailer::from(trailer).display(": "))?;
+ }
+ Ok(())
+ }
+}
diff --git a/crates/radicle-git-metadata/src/commit/headers.rs b/crates/radicle-git-metadata/src/commit/headers.rs
new file mode 100644
index 000000000..172a884d4
--- /dev/null
+++ b/crates/radicle-git-metadata/src/commit/headers.rs
@@ -0,0 +1,168 @@
+use core::fmt;
+use std::borrow::Cow;
+
+const BEGIN_SSH: &str = "-----BEGIN SSH SIGNATURE-----\n";
+const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n";
+
+/// A collection of headers stored in [`super::CommitData`].
+///
+/// Note: these do not include `tree`, `parent`, `author`, and `committer`.
+#[derive(Clone, Debug, Default)]
+pub struct Headers(pub(super) Vec<(String, String)>);
+
+/// A `gpgsig` signature stored in [`super::CommitData`].
+#[derive(Debug)]
+pub enum Signature<'a> {
+ /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`.
+ Pgp(Cow<'a, str>),
+ /// A SSH signature, i.e. starts with `-----BEGIN SSH SIGNATURE-----`.
+ Ssh(Cow<'a, str>),
+}
+
+impl<'a> Signature<'a> {
+ fn from_str(s: &'a str) -> Result<Self, UnknownScheme> {
+ if s.starts_with(BEGIN_SSH) {
+ Ok(Signature::Ssh(Cow::Borrowed(s)))
+ } else if s.starts_with(BEGIN_PGP) {
+ Ok(Signature::Pgp(Cow::Borrowed(s)))
+ } else {
+ Err(UnknownScheme)
+ }
+ }
+}
+
+impl fmt::Display for Signature<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Signature::Pgp(pgp) => f.write_str(pgp.as_ref()),
+ Signature::Ssh(ssh) => f.write_str(ssh.as_ref()),
+ }
+ }
+}
+
+pub struct UnknownScheme;
+
+impl Headers {
+ pub fn new() -> Self {
+ Headers(Vec::new())
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
+ self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
+ }
+
+ pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + 'a {
+ self.iter()
+ .filter_map(move |(k, v)| (k == name).then_some(v))
+ }
+
+ pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
+ self.0.iter().filter_map(|(k, v)| {
+ if k == "gpgsig" {
+ Signature::from_str(v).ok()
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Push a header to the end of the headers section.
+ pub fn push(&mut self, name: &str, value: &str) {
+ self.0.push((name.to_owned(), value.trim().to_owned()));
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ParseError {
+ #[error("missing tree")]
+ MissingTree,
+ #[error("invalid tree")]
+ InvalidTree,
+ #[error("invalid format")]
+ InvalidFormat,
+ #[error("invalid parent")]
+ InvalidParent,
+ #[error("invalid header")]
+ InvalidHeader,
+ #[error("invalid author")]
+ InvalidAuthor,
+ #[error("missing author")]
+ MissingAuthor,
+ #[error("invalid committer")]
+ InvalidCommitter,
+ #[error("missing committer")]
+ MissingCommitter,
+}
+
+pub fn parse_commit_header<
+ Tree: std::str::FromStr,
+ Parent: std::str::FromStr,
+ Signature: std::str::FromStr,
+>(
+ header: &str,
+) -> Result<(Tree, Vec<Parent>, Signature, Signature, Headers), ParseError> {
+ let mut lines = header.lines();
+
+ let tree = match lines.next() {
+ Some(tree) => tree
+ .strip_prefix("tree ")
+ .map(Tree::from_str)
+ .transpose()
+ .map_err(|_| ParseError::InvalidTree)?
+ .ok_or(ParseError::MissingTree)?,
+ None => return Err(ParseError::MissingTree),
+ };
+
+ let mut parents = Vec::new();
+ let mut author: Option<Signature> = None;
+ let mut committer: Option<Signature> = None;
+ let mut headers = Headers::new();
+
+ for line in lines {
+ // Check if a signature is still being parsed
+ if let Some(rest) = line.strip_prefix(' ') {
+ let value: &mut String = headers
+ .0
+ .last_mut()
+ .map(|(_, v)| v)
+ .ok_or(ParseError::InvalidFormat)?;
+ value.push('\n');
+ value.push_str(rest);
+ continue;
+ }
+
+ if let Some((name, value)) = line.split_once(' ') {
+ match name {
+ "parent" => parents.push(
+ value
+ .parse::<Parent>()
+ .map_err(|_| ParseError::InvalidParent)?,
+ ),
+ "author" => {
+ author = Some(
+ value
+ .parse::<Signature>()
+ .map_err(|_| ParseError::InvalidAuthor)?,
+ )
+ }
+ "committer" => {
+ committer = Some(
+ value
+ .parse::<Signature>()
+ .map_err(|_| ParseError::InvalidCommitter)?,
+ )
+ }
+ _ => headers.push(name, value),
+ }
+ continue;
+ }
+ }
+
+ Ok((
+ tree,
+ parents,
+ author.ok_or(ParseError::MissingAuthor)?,
+ committer.ok_or(ParseError::MissingCommitter)?,
+ headers,
+ ))
+}
diff --git a/crates/radicle-git-metadata/src/commit/trailers.rs b/crates/radicle-git-metadata/src/commit/trailers.rs
new file mode 100644
index 000000000..797d56dda
--- /dev/null
+++ b/crates/radicle-git-metadata/src/commit/trailers.rs
@@ -0,0 +1,127 @@
+use std::{borrow::Cow, fmt, ops::Deref};
+
+pub trait Separator<'a> {
+ fn sep_for(&self, token: &Token) -> &'a str;
+}
+
+impl<'a> Separator<'a> for &'a str {
+ fn sep_for(&self, _: &Token) -> &'a str {
+ self
+ }
+}
+
+impl<'a, F> Separator<'a> for F
+where
+ F: Fn(&Token) -> &'a str,
+{
+ fn sep_for(&self, token: &Token) -> &'a str {
+ self(token)
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Token<'a>(&'a str);
+
+impl Deref for Token<'_> {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.0
+ }
+}
+
+impl<'a> TryFrom<&'a str> for Token<'a> {
+ type Error = &'static str;
+
+ fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+ let is_token = s.chars().all(|c| c.is_alphanumeric() || c == '-');
+ if is_token {
+ Ok(Token(s))
+ } else {
+ Err("token contains invalid characters")
+ }
+ }
+}
+
+pub struct Display<'a> {
+ trailer: &'a Trailer<'a>,
+ separator: &'a str,
+}
+
+impl fmt::Display for Display<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}{}{}",
+ self.trailer.token.deref(),
+ self.separator,
+ self.trailer.value,
+ )
+ }
+}
+
+/// A trailer is a key/value pair found in the last paragraph of a Git
+/// commit message, not including any patches or conflicts that may be
+/// present.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Trailer<'a> {
+ pub token: Token<'a>,
+ pub value: Cow<'a, str>,
+}
+
+impl<'a> Trailer<'a> {
+ pub fn display(&'a self, separator: &'a str) -> Display<'a> {
+ Display {
+ trailer: self,
+ separator,
+ }
+ }
+
+ pub fn to_owned(&self) -> OwnedTrailer {
+ OwnedTrailer::from(self)
+ }
+}
+
+/// A version of the [`Trailer`] which owns its token and
+/// value. Useful for when you need to carry trailers around in a long
+/// lived data structure.
+#[derive(Debug)]
+pub struct OwnedTrailer {
+ pub token: OwnedToken,
+ pub value: String,
+}
+
+#[derive(Debug)]
+pub struct OwnedToken(String);
+
+impl Deref for OwnedToken {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'a> From<&Trailer<'a>> for OwnedTrailer {
+ fn from(t: &Trailer<'a>) -> Self {
+ OwnedTrailer {
+ token: OwnedToken(t.token.0.to_string()),
+ value: t.value.to_string(),
+ }
+ }
+}
+
+impl<'a> From<Trailer<'a>> for OwnedTrailer {
+ fn from(t: Trailer<'a>) -> Self {
+ (&t).into()
+ }
+}
+
+impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
+ fn from(t: &'a OwnedTrailer) -> Self {
+ Trailer {
+ token: Token(t.token.0.as_str()),
+ value: Cow::from(&t.value),
+ }
+ }
+}
diff --git a/crates/radicle-git-metadata/src/lib.rs b/crates/radicle-git-metadata/src/lib.rs
new file mode 100644
index 000000000..3e183770f
--- /dev/null
+++ b/crates/radicle-git-metadata/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod author;
+pub mod commit;
commit 793f53b5f0fe2671e57032160faa6806d50f3371
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Thu Oct 2 23:42:58 2025 +0200
oid: New crate
For further details on the crate see its documentation in
`crates/radicle-oid/src/lib.rs`.
Changes to `crates/radicle{,-crypto}/Cargo.toml` are to preserve the
default features of their dependencies `serde` and `schemars`,
which are disabled to support `radicle-oid` being `no_std`.
diff --git a/Cargo.lock b/Cargo.lock
index 77c0662b1..f3be1efd9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2960,6 +2960,19 @@ dependencies = [
"winpipe",
]
+[[package]]
+name = "radicle-oid"
+version = "0.1.0"
+dependencies = [
+ "git2",
+ "gix-hash",
+ "qcheck",
+ "qcheck-macros",
+ "radicle-git-ref-format",
+ "schemars",
+ "serde",
+]
+
[[package]]
name = "radicle-protocol"
version = "0.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 56580781a..185eedb0b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ cyphernet = "0.5.2"
dunce = "1.0.5"
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 }
human-panic = "2"
itertools = "0.14"
@@ -49,14 +50,16 @@ radicle-crypto = { version = "0.14", path = "crates/radicle-crypto" }
radicle-dag = { version = "0.10", path = "crates/radicle-dag" }
radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" }
radicle-git-ext = { version = "0.8", 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-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false }
radicle-protocol = { version = "0.4", path = "crates/radicle-protocol" }
radicle-signals = { version = "0.11", path = "crates/radicle-signals" }
radicle-ssh = { version = "0.10", path = "crates/radicle-ssh", default-features = false }
radicle-systemd = { version = "0.11", path = "crates/radicle-systemd" }
radicle-term = { version = "0.16", path = "crates/radicle-term" }
-schemars = { version = "1.0.4" }
-serde = "1.0"
+schemars = { version = "1.0.4", default-features = false }
+serde = { version = "1.0", default-features = false }
serde_json = "1.0"
shlex = "1.1.0"
signature = "2.2"
diff --git a/crates/radicle-crypto/Cargo.toml b/crates/radicle-crypto/Cargo.toml
index e3533d23d..2315128f3 100644
--- a/crates/radicle-crypto/Cargo.toml
+++ b/crates/radicle-crypto/Cargo.toml
@@ -25,7 +25,7 @@ multibase = { workspace = true }
qcheck = { workspace = true, optional = true }
git-ref-format-core = { workspace = true, optional = true }
radicle-ssh = { workspace = true, optional = true }
-serde = { workspace = true, features = ["derive"] }
+serde = { workspace = true, features = ["derive", "std"] }
signature = { workspace = true }
sqlite = { workspace = true, features = ["bundled"], optional = true }
ssh-key = { version = "0.6.3", default-features = false, features = ["std", "encryption", "getrandom"], optional = true }
diff --git a/crates/radicle-oid/Cargo.toml b/crates/radicle-oid/Cargo.toml
new file mode 100644
index 000000000..da2ad4958
--- /dev/null
+++ b/crates/radicle-oid/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "radicle-oid"
+description = "Radicle representation of Git object identifiers"
+homepage.workspace = true
+repository.workspace = true
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+keywords = ["radicle", "git", "oid"]
+rust-version.workspace = true
+
+# For documentation of features refer to the module documentation in `./lib.rs`
+[features]
+default = ["sha1", "std"]
+gix = ["dep:gix-hash"]
+std = []
+sha1 = []
+
+[dependencies]
+git2 = { workspace = true, optional = true, default-features = false }
+gix-hash = { version = "0.15.1", optional = true, default-features = false }
+qcheck = { workspace = true, optional = true, default-features = false }
+radicle-git-ref-format = { workspace = true, optional = true, default-features = false }
+schemars = { workspace = true, optional = true, default-features = false }
+serde = { workspace = true, optional = true, default-features = false }
+
+[dev-dependencies]
+git2 = { workspace = true }
+gix-hash = { version = "0.15.1" }
+qcheck = { workspace = true }
+qcheck-macros = { workspace = true }
\ No newline at end of file
diff --git a/crates/radicle-oid/src/lib.rs b/crates/radicle-oid/src/lib.rs
new file mode 100644
index 000000000..e01fc7dd8
--- /dev/null
+++ b/crates/radicle-oid/src/lib.rs
@@ -0,0 +1,575 @@
+#![no_std]
+
+//! This is a `no_std` crate which carries the struct [`Oid`] that represents
+//! Git object identifiers. Currently, only SHA-1 digests are supported.
+//!
+//! # Feature Flags
+//!
+//! The default features are `sha1` and `std`.
+//!
+//! ## `sha1`
+//!
+//! Enabled by default, since SHA-1 is commonly used. Currently, this feature is
+//! also *required* to build the crate. In the future, after support for other
+//! hashes is added, it might become possible to build the crate without support
+//! for SHA-1.
+//!
+//! ## `std`
+//!
+//! [`Hash`]: ::doc_std::hash::Hash
+//!
+//! Enabled by default, since it is expected that most dependents will use the
+//! standard library.
+//!
+//! Provides an implementation of [`Hash`].
+//!
+//! ## `git2`
+//!
+//! [`git2::Oid`]: ::git2::Oid
+//!
+//! Provides conversions to/from [`git2::Oid`].
+//!
+//! Note that as of version 0.19.0,
+//!
+//! ## `gix`
+//!
+//! [`ObjectId`]: ::gix_hash::ObjectId
+//!
+//! Provides conversions to/from [`ObjectId`].
+//!
+//! ## `schemars`
+//!
+//! [`JsonSchema`]: ::schemars::JsonSchema
+//!
+//! Provides an implementation of [`JsonSchema`].
+//!
+//! ## `serde`
+//!
+//! [`Serialize`]: ::serde::ser::Serialize
+//! [`Deserialize`]: ::serde::de::Deserialize
+//!
+//! Provides implementations of [`Serialize`] and [`Deserialize`].
+//!
+//! ## `qcheck`
+//!
+//! [`qcheck::Arbitrary`]: ::qcheck::Arbitrary
+//!
+//! Provides an implementation of [`qcheck::Arbitrary`].
+//!
+//! ## `radicle-git-ref-format`
+//!
+//! [`radicle_git_ref_format::Component`]: ::radicle_git_ref_format::Component
+//! [`radicle_git_ref_format::RefString`]: ::radicle_git_ref_format::RefString
+//!
+//! Conversion to [`radicle_git_ref_format::Component`]
+//! (and also [`radicle_git_ref_format::RefString`]).
+
+#[cfg(doc)]
+extern crate std as doc_std;
+
+extern crate alloc;
+
+// Remove this once other hashes (e.g., SHA-256, and potentially others)
+// are supported, and this crate can build without [`Oid::Sha1`].
+#[cfg(not(feature = "sha1"))]
+compile_error!("The `sha1` feature is required.");
+
+const SHA1_DIGEST_LEN: usize = 20;
+
+#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
+#[non_exhaustive]
+pub enum Oid {
+ Sha1([u8; SHA1_DIGEST_LEN]),
+}
+
+/// Conversions to/from SHA-1.
+// Note that we deliberately do not implement `From<[u8; 20]>` and `Into<[u8; 20]>`,
+// for forwards compatibility: What if another hash with digests of the same
+// length becomes popular?
+impl Oid {
+ pub fn from_sha1(digest: [u8; SHA1_DIGEST_LEN]) -> Self {
+ Self::Sha1(digest)
+ }
+
+ pub fn into_sha1(&self) -> Option<[u8; SHA1_DIGEST_LEN]> {
+ match self {
+ Oid::Sha1(digest) => Some(*digest),
+ }
+ }
+
+ pub fn sha1_zero() -> Self {
+ Self::Sha1([0u8; SHA1_DIGEST_LEN])
+ }
+}
+
+/// Interaction with zero.
+impl Oid {
+ /// Test whether all bytes in this object identifier are zero.
+ /// See also [`::git2::Oid::is_zero`].
+ pub fn is_zero(&self) -> bool {
+ match self {
+ Oid::Sha1(ref array) => array.iter().all(|b| *b == 0),
+ }
+ }
+}
+
+impl AsRef<[u8]> for Oid {
+ fn as_ref(&self) -> &[u8] {
+ match self {
+ Oid::Sha1(ref array) => array,
+ }
+ }
+}
+
+impl From<Oid> for alloc::boxed::Box<[u8]> {
+ fn from(oid: Oid) -> Self {
+ match oid {
+ Oid::Sha1(array) => alloc::boxed::Box::new(array),
+ }
+ }
+}
+
+pub mod str {
+ use super::{Oid, SHA1_DIGEST_LEN};
+ use core::str;
+
+ /// Length of the string representation of a SHA-1 digest in hexadecimal notation.
+ pub(super) const SHA1_DIGEST_STR_LEN: usize = SHA1_DIGEST_LEN * 2;
+
+ impl str::FromStr for Oid {
+ type Err = error::ParseOidError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use error::ParseOidError::*;
+
+ let len = s.len();
+ if len != SHA1_DIGEST_STR_LEN {
+ return Err(Len(len));
+ }
+
+ let mut bytes = [0u8; SHA1_DIGEST_LEN];
+ for i in 0..SHA1_DIGEST_LEN {
+ bytes[i] = u8::from_str_radix(&s[i * 2..=i * 2 + 1], 16)
+ .map_err(|source| At { index: i, source })?;
+ }
+
+ Ok(Self::Sha1(bytes))
+ }
+ }
+
+ pub mod error {
+ use core::{fmt, num};
+
+ use super::SHA1_DIGEST_STR_LEN;
+
+ pub enum ParseOidError {
+ Len(usize),
+ At {
+ index: usize,
+ source: num::ParseIntError,
+ },
+ }
+
+ impl fmt::Display for ParseOidError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use ParseOidError::*;
+ match self {
+ Len(len) => {
+ write!(f, "invalid length (have {len}, want {SHA1_DIGEST_STR_LEN})")
+ }
+ At { index, source } => write!(
+ f,
+ "parse error at byte {index} (characters {} and {}): {source}",
+ index * 2,
+ index * 2 + 1
+ ),
+ }
+ }
+ }
+
+ impl fmt::Debug for ParseOidError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+ }
+
+ impl core::error::Error for ParseOidError {
+ fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
+ match self {
+ ParseOidError::At { source, .. } => Some(source),
+ _ => None,
+ }
+ }
+ }
+ }
+
+ pub use error::ParseOidError;
+
+ #[cfg(test)]
+ mod test {
+ use super::*;
+ use alloc::string::ToString;
+ use qcheck_macros::quickcheck;
+
+ #[test]
+ fn fixture() {
+ assert_eq!(
+ "123456789abcdef0123456789abcdef012345678"
+ .parse::<Oid>()
+ .unwrap(),
+ Oid::from_sha1([
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a,
+ 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78,
+ ])
+ );
+ }
+
+ #[test]
+ fn zero() {
+ assert_eq!(
+ "0000000000000000000000000000000000000000"
+ .parse::<Oid>()
+ .unwrap(),
+ Oid::sha1_zero()
+ );
+ }
+
+ #[quickcheck]
+ fn git2_roundtrip(oid: Oid) {
+ let other = git2::Oid::from(oid);
+ let other = other.to_string();
+ let other = other.parse::<Oid>().unwrap();
+ assert_eq!(oid, other);
+ }
+
+ #[quickcheck]
+ fn gix_roundrip(oid: Oid) {
+ let other = gix_hash::ObjectId::from(oid);
+ let other = other.to_string();
+ let other = other.parse::<Oid>().unwrap();
+ assert_eq!(oid, other);
+ }
+ }
+}
+
+mod fmt {
+ use alloc::format;
+ use core::fmt;
+
+ use super::Oid;
+
+ impl fmt::Display for Oid {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Oid::Sha1(digest) =>
+ // SAFETY (for all 20 blocks below): The length of `digest` is
+ // known to be `SHA1_DIGEST_LEN`, which is 20.
+ // The indices below are manually verified to not be out of bounds.
+ format!(
+ "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
+ unsafe { digest.get_unchecked(0) },
+ unsafe { digest.get_unchecked(1) },
+ unsafe { digest.get_unchecked(2) },
+ unsafe { digest.get_unchecked(3) },
+ unsafe { digest.get_unchecked(4) },
+ unsafe { digest.get_unchecked(5) },
+ unsafe { digest.get_unchecked(6) },
+ unsafe { digest.get_unchecked(7) },
+ unsafe { digest.get_unchecked(8) },
+ unsafe { digest.get_unchecked(9) },
+ unsafe { digest.get_unchecked(10) },
+ unsafe { digest.get_unchecked(11) },
+ unsafe { digest.get_unchecked(12) },
+ unsafe { digest.get_unchecked(13) },
+ unsafe { digest.get_unchecked(14) },
+ unsafe { digest.get_unchecked(15) },
+ unsafe { digest.get_unchecked(16) },
+ unsafe { digest.get_unchecked(17) },
+ unsafe { digest.get_unchecked(18) },
+ unsafe { digest.get_unchecked(19) },
+ ).fmt(f)
+ }
+ }
+ }
+
+ impl fmt::Debug for Oid {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+ }
+
+ #[cfg(test)]
+ mod test {
+ use super::*;
+ use alloc::string::ToString;
+ use qcheck_macros::quickcheck;
+
+ #[test]
+ fn fixture() {
+ assert_eq!(
+ Oid::from_sha1([
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a,
+ 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78,
+ ])
+ .to_string(),
+ "123456789abcdef0123456789abcdef012345678"
+ );
+ }
+
+ #[test]
+ fn zero() {
+ assert_eq!(
+ Oid::sha1_zero().to_string(),
+ "0000000000000000000000000000000000000000"
+ );
+ }
+
+ #[quickcheck]
+ fn git2(oid: Oid) {
+ assert_eq!(oid.to_string(), git2::Oid::from(oid).to_string());
+ }
+
+ #[quickcheck]
+ fn gix(oid: Oid) {
+ assert_eq!(oid.to_string(), gix_hash::ObjectId::from(oid).to_string());
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+mod std {
+ extern crate std;
+
+ use super::Oid;
+
+ mod hash {
+ use std::hash;
+
+ use super::*;
+
+ #[allow(clippy::derived_hash_with_manual_eq)]
+ impl hash::Hash for Oid {
+ fn hash<H: hash::Hasher>(&self, state: &mut H) {
+ state.write(self.as_ref());
+ }
+ }
+ }
+}
+
+#[cfg(any(feature = "gix", test))]
+mod gix {
+ use gix_hash::ObjectId as Other;
+
+ use super::Oid;
+
+ impl From<Other> for Oid {
+ fn from(other: Other) -> Self {
+ match other {
+ Other::Sha1(digest) => Self::Sha1(digest),
+ }
+ }
+ }
+
+ impl From<Oid> for Other {
+ fn from(oid: Oid) -> Other {
+ match oid {
+ Oid::Sha1(digest) => Other::Sha1(digest),
+ }
+ }
+ }
+
+ impl core::cmp::PartialEq<Other> for Oid {
+ fn eq(&self, other: &Other) -> bool {
+ match (self, other) {
+ (Oid::Sha1(a), Other::Sha1(b)) => a == b,
+ }
+ }
+ }
+
+ impl AsRef<gix_hash::oid> for Oid {
+ fn as_ref(&self) -> &gix_hash::oid {
+ match self {
+ Oid::Sha1(digest) => gix_hash::oid::from_bytes_unchecked(digest),
+ }
+ }
+ }
+
+ #[cfg(test)]
+ mod test {
+ use super::*;
+ use gix_hash::Kind;
+
+ #[test]
+ fn zero() {
+ assert!(Oid::sha1_zero() == Other::null(Kind::Sha1));
+ }
+ }
+}
+
+#[cfg(any(feature = "git2", test))]
+mod git2 {
+ use ::git2::Oid as Other;
+
+ use super::*;
+
+ const EXPECT: &str = "git2::Oid must be exactly 20 bytes long";
+
+ impl From<Other> for Oid {
+ fn from(other: Other) -> Self {
+ Self::Sha1(other.as_bytes().try_into().expect(EXPECT))
+ }
+ }
+
+ impl From<Oid> for Other {
+ fn from(oid: Oid) -> Self {
+ match oid {
+ Oid::Sha1(array) => Other::from_bytes(&array).expect(EXPECT),
+ }
+ }
+ }
+
+ impl From<&Oid> for Other {
+ fn from(oid: &Oid) -> Self {
+ match oid {
+ Oid::Sha1(array) => Other::from_bytes(array).expect(EXPECT),
+ }
+ }
+ }
+
+ impl core::cmp::PartialEq<Other> for Oid {
+ fn eq(&self, other: &Other) -> bool {
+ other.as_bytes() == AsRef::<[u8]>::as_ref(&self)
+ }
+ }
+
+ #[cfg(test)]
+ mod test {
+ use super::*;
+
+ #[test]
+ fn zero() {
+ assert!(Oid::sha1_zero() == Other::zero());
+ }
+ }
+}
+
+#[cfg(any(test, feature = "qcheck"))]
+mod test {
+ mod qcheck {
+ use ::qcheck::{Arbitrary, Gen};
+
+ use crate::*;
+
+ impl Arbitrary for Oid {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let slice = [0u8; SHA1_DIGEST_LEN];
+ g.fill(slice);
+ Self::Sha1(slice)
+ }
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+mod serde {
+ mod ser {
+ use ::serde::ser;
+
+ use crate::*;
+
+ impl ser::Serialize for Oid {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ serializer.collect_str(self)
+ }
+ }
+ }
+
+ mod de {
+ use core::fmt;
+
+ use ::serde::de;
+
+ use crate::*;
+
+ impl<'de> de::Deserialize<'de> for Oid {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ struct OidVisitor;
+
+ impl<'de> de::Visitor<'de> for OidVisitor {
+ type Value = Oid;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use crate::str::SHA1_DIGEST_STR_LEN;
+ write!(f, "a Git object identifier (SHA-1 digest in hexadecimal notation; {SHA1_DIGEST_STR_LEN} characters; {SHA1_DIGEST_LEN} bytes)")
+ }
+
+ fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ s.parse().map_err(de::Error::custom)
+ }
+ }
+
+ deserializer.deserialize_str(OidVisitor)
+ }
+ }
+ }
+}
+
+#[cfg(feature = "radicle-git-ref-format")]
+mod radicle_git_ref_format {
+ use ::radicle_git_ref_format::{Component, RefString};
+
+ use super::*;
+
+ impl From<&Oid> for Component<'_> {
+ fn from(id: &Oid) -> Self {
+ Component::from_refstr(RefString::from(id))
+ .expect("Git object identifiers are valid component strings")
+ }
+ }
+
+ impl From<&Oid> for RefString {
+ fn from(id: &Oid) -> Self {
+ RefString::try_from(alloc::format!("{id}"))
+ .expect("Git object identifiers are valid reference strings")
+ }
+ }
+}
+
+#[cfg(feature = "schemars")]
+mod schemars {
+ use alloc::{borrow::Cow, format};
+
+ use ::schemars::{json_schema, JsonSchema, Schema, SchemaGenerator};
+
+ use super::Oid;
+
+ impl JsonSchema for Oid {
+ fn schema_name() -> Cow<'static, str> {
+ "Oid".into()
+ }
+
+ fn schema_id() -> Cow<'static, str> {
+ concat!(module_path!(), "::Oid").into()
+ }
+
+ fn json_schema(_: &mut SchemaGenerator) -> Schema {
+ use crate::{str::SHA1_DIGEST_STR_LEN, SHA1_DIGEST_LEN};
+ json_schema!({
+ "description": format!("A Git object identifier (SHA-1 digest in hexadecimal notation; {SHA1_DIGEST_STR_LEN} characters; {SHA1_DIGEST_LEN} bytes)"),
+ "type": "string",
+ "maxLength": SHA1_DIGEST_STR_LEN,
+ "minLength": SHA1_DIGEST_STR_LEN,
+ "pattern": format!("^[0-9a-fA-F]{{{SHA1_DIGEST_STR_LEN}}}$"),
+ })
+ }
+ }
+}
diff --git a/crates/radicle/Cargo.toml b/crates/radicle/Cargo.toml
index 0eb904808..4135fc491 100644
--- a/crates/radicle/Cargo.toml
+++ b/crates/radicle/Cargo.toml
@@ -36,7 +36,7 @@ radicle-cob = { workspace = true }
radicle-crypto = { workspace = true, features = ["git-ref-format-core", "ssh", "sqlite", "cyphernet"] }
radicle-git-ext = { workspace = true, features = ["serde"] }
radicle-ssh = { workspace = true }
-schemars = { workspace = true, optional = true }
+schemars = { workspace = true, optional = true, features = ["derive", "std"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
serde-untagged = "0.1.7"
commit 3fcf2a86da18fc958d79fd8f435e7c7edcbd2c58
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sun Oct 5 11:53:21 2025 +0200
git-ref-format: New crate
For further details on the crate see its documentation in
`crates/radicle-git-ref-format/src/lib.rs`.
diff --git a/Cargo.lock b/Cargo.lock
index 7db4a9b91..77c0662b1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2914,6 +2914,13 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "radicle-git-ref-format"
+version = "0.1.0"
+dependencies = [
+ "git-ref-format-core",
+]
+
[[package]]
name = "radicle-node"
version = "0.16.0"
diff --git a/crates/radicle-git-ref-format/Cargo.toml b/crates/radicle-git-ref-format/Cargo.toml
new file mode 100644
index 000000000..8a00a7c3d
--- /dev/null
+++ b/crates/radicle-git-ref-format/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "radicle-git-ref-format"
+description = "Radicle re-exports and macros for `git-ref-format-core`"
+homepage.workspace = true
+repository.workspace = true
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+keywords = ["radicle", "git", "refname", "ref", "references"]
+rust-version.workspace = true
+
+[features]
+macro = []
+bstr = ["git-ref-format-core/bstr"]
+serde = ["git-ref-format-core/serde"]
+
+[dependencies]
+git-ref-format-core.workspace = true
diff --git a/crates/radicle-git-ref-format/src/lib.rs b/crates/radicle-git-ref-format/src/lib.rs
new file mode 100644
index 000000000..e7df4b253
--- /dev/null
+++ b/crates/radicle-git-ref-format/src/lib.rs
@@ -0,0 +1,298 @@
+#![no_std]
+
+//! [`git_ref_format`]: https://crates.io/crates/git-ref-format
+//! [`radicle-git-ext`]: https://crates.io/crates/radicle-git-ext
+//!
+//! This crate depends on and re-exports from [`git_ref_format_core`].
+//!
+//! ## Macros
+//!
+//! Instead of providing procedural macros, like [`git_ref_format`]
+//! it just provides much simpler declarative macros, guarded by the feature
+//! flag `macro`.
+//!
+//! ### Benefits
+//!
+//! - Does not depend on [`radicle-git-ext`].
+//! - Does not pull in procedural macro dependencies.
+//! - Has much smaller compile-time overhead than [`git_ref_format`].
+//!
+//! ### Drawback
+//!
+//! The main drawback is that the macros in this crate cannot provide compile
+//! time validation of the argument. Thus, these macros must be used in
+//! conjunction with testing: If all generated objects are used in tests, and
+//! these tests are run, then the guarantees are equally strong. Consumers that
+//! do not or cannot test their code should not use the macros then.
+
+pub use git_ref_format_core::*;
+
+/// Create a [`git_ref_format_core::RefString`] from a string literal.
+///
+/// Similar to [`core::debug_assert`], an optimized build will not validate
+/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
+/// passed to the compiler.
+#[cfg(any(feature = "macro", test))]
+#[macro_export]
+macro_rules! refname {
+ ($arg:literal) => {{
+ use $crate::RefString;
+
+ #[cfg(debug_assertions)]
+ {
+ RefString::try_from($arg).expect(core::concat!(
+ "literal `",
+ $arg,
+ "` must be a valid reference name"
+ ))
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ extern crate alloc;
+
+ use alloc::string::String;
+
+ let s: String = $arg.to_owned();
+ unsafe { core::mem::transmute::<_, RefString>(s) }
+ }
+ }};
+}
+
+/// Create a [`git_ref_format_core::Qualified`] from a string literal.
+///
+/// Similar to [`core::debug_assert`], an optimized build will not validate
+/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
+/// passed to the compiler.
+#[cfg(any(feature = "macro", test))]
+#[macro_export]
+macro_rules! qualified {
+ ($arg:literal) => {{
+ use $crate::Qualified;
+
+ #[cfg(debug_assertions)]
+ {
+ Qualified::from_refstr($crate::refname!($arg)).expect(core::concat!(
+ "literal `",
+ $arg,
+ "` must be of the form 'refs/<category>/<name>'"
+ ))
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ extern crate alloc;
+
+ use core::mem::transmute;
+
+ use alloc::borrow::Cow;
+ use alloc::string::String;
+
+ use $crate::{RefStr, RefString};
+
+ let s: String = $arg.to_owned();
+ let refstring: RefString = unsafe { transmute(s) };
+ let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
+ let qualified: Qualified = unsafe { transmute(cow) };
+
+ qualified
+ }
+ }};
+}
+
+/// Create a [`git_ref_format_core::Component`] from a string literal.
+///
+/// Similar to [`core::debug_assert`], an optimized build will not validate
+/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
+/// passed to the compiler.
+#[cfg(any(feature = "macro", test))]
+#[macro_export]
+macro_rules! component {
+ ($arg:literal) => {{
+ use $crate::Component;
+
+ #[cfg(debug_assertions)]
+ {
+ Component::from_refstr($crate::refname!($arg)).expect(core::concat!(
+ "literal `",
+ $arg,
+ "` must be a valid component (cannot contain '/')"
+ ))
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ extern crate alloc;
+
+ use core::mem::transmute;
+
+ use alloc::borrow::Cow;
+ use alloc::string::String;
+
+ use $crate::{RefStr, RefString};
+
+ let s: String = $arg.to_owned();
+ let refstring: RefString = unsafe { transmute(s) };
+ let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
+ let component: Component = unsafe { transmute(cow) };
+
+ component
+ }
+ }};
+}
+
+/// Create a [`git_ref_format_core::refspec::PatternString`] from a string literal.
+///
+/// Similar to [`core::debug_assert`], an optimized build will not validate
+/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
+/// passed to the compiler.
+#[cfg(any(feature = "macro", test))]
+#[macro_export]
+macro_rules! pattern {
+ ($arg:literal) => {{
+ use $crate::refspec::PatternString;
+
+ #[cfg(debug_assertions)]
+ {
+ PatternString::try_from($arg).expect(core::concat!(
+ "literal `",
+ $arg,
+ "` must be a valid refspec pattern"
+ ))
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ extern crate alloc;
+
+ use alloc::string::String;
+
+ let s: String = $arg.to_owned();
+ unsafe { core::mem::transmute::<_, PatternString>(s) }
+ }
+ }};
+}
+
+/// Create a [`git_ref_format_core::refspec::QualifiedPattern`] from a string literal.
+///
+/// Similar to [`core::debug_assert`], an optimized build will not validate
+/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
+/// passed to the compiler.
+#[cfg(any(feature = "macro", test))]
+#[macro_export]
+macro_rules! qualified_pattern {
+ ($arg:literal) => {{
+ use $crate::refspec::QualifiedPattern;
+
+ #[cfg(debug_assertions)]
+ {
+ use core::concat;
+
+ use $crate::refspec::PatternStr;
+
+ let pattern = PatternStr::try_from_str($arg).expect(concat!(
+ "literal `",
+ $arg,
+ "` must be a valid refspec pattern"
+ ));
+
+ QualifiedPattern::from_patternstr(pattern).expect(concat!(
+ "literal `",
+ $arg,
+ "` must be a valid qualified refspec pattern"
+ ))
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ extern crate alloc;
+
+ use core::mem::transmute;
+
+ use alloc::borrow::Cow;
+ use alloc::string::String;
+
+ use $crate::refspec::{PatternStr, PatternString};
+
+ let s: String = $arg.to_owned();
+ let pattern: PatternString = unsafe { transmute(s) };
+ let cow: Cow<'_, PatternStr> = Cow::Owned(pattern);
+ let qualified: QualifiedPattern = unsafe { transmute(cow) };
+
+ qualified
+ }
+ }};
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn refname() {
+ let _ = crate::refname!("refs/heads/main");
+ let _ = crate::refname!("refs/tags/v1.0.0");
+ let _ = crate::refname!("refs/remotes/origin/main");
+ let _ = crate::refname!("a");
+ }
+
+ #[test]
+ #[should_panic]
+ fn refname_invalid() {
+ let _ = crate::refname!("a~b");
+ }
+
+ #[test]
+ fn qualified() {
+ let _ = crate::qualified!("refs/heads/main");
+ let _ = crate::qualified!("refs/tags/v1.0.0");
+ let _ = crate::qualified!("refs/remotes/origin/main");
+ }
+
+ #[test]
+ #[should_panic]
+ fn qualified_invalid() {
+ let _ = crate::qualified!("a");
+ }
+
+ #[test]
+ fn component() {
+ let _ = crate::component!("a");
+ }
+
+ #[test]
+ #[should_panic]
+ fn component_invalid() {
+ let _ = crate::component!("a/b");
+ }
+
+ #[test]
+ fn pattern() {
+ let _ = crate::pattern!("refs/heads/main");
+ let _ = crate::pattern!("refs/tags/v1.0.0");
+ let _ = crate::pattern!("refs/remotes/origin/main");
+
+ let _ = crate::pattern!("a");
+ let _ = crate::pattern!("a/*");
+ let _ = crate::pattern!("*");
+ let _ = crate::pattern!("a/b*");
+ let _ = crate::pattern!("a/b*/c");
+ let _ = crate::pattern!("a/*/c");
+ }
+
+ #[test]
+ fn qualified_pattern() {
+ let _ = crate::qualified_pattern!("refs/heads/main");
+ let _ = crate::qualified_pattern!("refs/tags/v1.0.0");
+ let _ = crate::qualified_pattern!("refs/remotes/origin/main");
+
+ let _ = crate::qualified_pattern!("refs/heads/main/*");
+ let _ = crate::qualified_pattern!("refs/tags/v*");
+ let _ = crate::qualified_pattern!("refs/remotes/origin/main");
+ let _ = crate::qualified_pattern!("refs/remotes/origin/department/*/person");
+ }
+
+ #[test]
+ #[should_panic]
+ fn qualified_pattern_invalid() {
+ let _ = crate::qualified_pattern!("a/*/b");
+ }
+}
Exit code: 0
shell: 'export RUSTDOCFLAGS=''-D warnings'' cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny warnings cargo build --all-targets --workspace cargo doc --workspace --no-deps cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 5c0c2ed0-094e-47af-94b9-5059541936f0 -v /opt/radcis/ci.rad.levitte.org/cci/state/5c0c2ed0-094e-47af-94b9-5059541936f0/s:/5c0c2ed0-094e-47af-94b9-5059541936f0/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/5c0c2ed0-094e-47af-94b9-5059541936f0/w:/5c0c2ed0-094e-47af-94b9-5059541936f0/w -w /5c0c2ed0-094e-47af-94b9-5059541936f0/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /5c0c2ed0-094e-47af-94b9-5059541936f0/s/script.sh
+ export 'RUSTDOCFLAGS=-D warnings'
+ RUSTDOCFLAGS='-D warnings'
+ cargo --version
info: syncing channel updates for '1.88-x86_64-unknown-linux-gnu'
info: latest update on 2025-06-26, rust version 1.88.0 (6b00bc388 2025-06-23)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.88.0 (873a06493 2025-05-10)
+ rustc --version
rustc 1.88.0 (6b00bc388 2025-06-23)
+ cargo fmt --check
+ cargo clippy --all-targets --workspace -- --deny warnings
Updating crates.io index
Downloading crates ...
Downloaded aead v0.5.2
Downloaded adler2 v2.0.0
Downloaded ahash v0.8.11
Downloaded num-cmp v0.1.0
Downloaded addr2line v0.24.2
Downloaded filetime v0.2.23
Downloaded crc32fast v1.4.0
Downloaded displaydoc v0.2.5
Downloaded num-bigint v0.4.6
Downloaded pkcs1 v0.7.5
Downloaded parking_lot v0.12.3
Downloaded pin-project-lite v0.2.16
Downloaded pkcs8 v0.10.2
Downloaded escargot v0.5.10
Downloaded git-ref-format-macro v0.5.0
Downloaded pkg-config v0.3.30
Downloaded fast-glob v0.3.3
Downloaded gix-shallow v0.1.0
Downloaded gix-sec v0.10.12
Downloaded scrypt v0.11.0
Downloaded scopeguard v1.2.0
Downloaded rfc6979 v0.4.0
Downloaded gix-revwalk v0.17.0
Downloaded icu_locid_transform v1.5.0
Downloaded rand_chacha v0.3.1
Downloaded snapbox-macros v0.3.8
Downloaded matchers v0.1.0
Downloaded parking_lot_core v0.9.9
Downloaded newline-converter v0.3.0
Downloaded value-bag-serde1 v1.11.1
Downloaded zerofrom-derive v0.1.6
Downloaded gix-quote v0.4.15
Downloaded itoa v1.0.11
Downloaded gix-trace v0.1.12
Downloaded der v0.7.9
Downloaded strsim v0.11.1
Downloaded icu_locid v1.5.0
Downloaded sval_serde v2.14.1
Downloaded value-bag v1.11.1
Downloaded synstructure v0.13.1
Downloaded human-panic v2.0.3
Downloaded tracing-core v0.1.34
Downloaded icu_provider_macros v1.5.0
Downloaded gix-lock v15.0.1
Downloaded unit-prefix v0.5.1
Downloaded icu_normalizer_data v1.5.1
Downloaded sval_dynamic v2.14.1
Downloaded generic-array v0.14.7
Downloaded home v0.5.9
Downloaded vsimd v0.8.0
Downloaded utf8parse v0.2.1
Downloaded ssh-cipher v0.2.0
Downloaded ssh-encoding v0.2.0
Downloaded tree-sitter-json v0.24.8
Downloaded tree-sitter-language v0.1.2
Downloaded yansi v0.5.1
Downloaded gix-url v0.28.2
Downloaded gix-utils v0.1.14
Downloaded tinyvec_macros v0.1.1
Downloaded thiserror-impl v2.0.12
Downloaded xattr v1.3.1
Downloaded stable_deref_trait v1.2.0
Downloaded gix-command v0.4.1
Downloaded getrandom v0.2.15
Downloaded test-log v0.2.18
Downloaded uuid v1.16.0
Downloaded gix-actor v0.33.2
Downloaded writeable v0.5.5
Downloaded git-ref-format-core v0.5.0
Downloaded lazy_static v1.5.0
Downloaded keccak v0.1.5
Downloaded gix-object v0.46.1
Downloaded gix-prompt v0.9.1
Downloaded systemd-journal-logger v2.2.2
Downloaded gix-traverse v0.43.1
Downloaded unicode-segmentation v1.11.0
Downloaded icu_provider v1.5.0
Downloaded tar v0.4.40
Downloaded fraction v0.15.3
Downloaded gix-chunk v0.4.11
Downloaded tree-sitter v0.24.4
Downloaded ssh-key v0.6.6
Downloaded thiserror v2.0.12
Downloaded lexopt v0.3.0
Downloaded icu_properties_data v1.5.1
Downloaded winnow v0.6.26
Downloaded gix-protocol v0.47.0
Downloaded vcpkg v0.2.15
Downloaded syn v2.0.89
Downloaded gix-pack v0.56.0
Downloaded schemars v1.0.4
Downloaded jsonschema v0.30.0
Downloaded p521 v0.13.3
Downloaded tree-sitter-md v0.3.2
Downloaded tree-sitter-python v0.23.4
Downloaded p384 v0.13.0
Downloaded serde_json v1.0.140
Downloaded tree-sitter-bash v0.23.3
Downloaded regex v1.11.1
Downloaded gimli v0.31.1
Downloaded regex-syntax v0.6.29
Downloaded tree-sitter-rust v0.23.2
Downloaded rustix v0.38.34
Downloaded regex-syntax v0.8.5
Downloaded object v0.36.7
Downloaded jiff v0.2.1
Downloaded rustix v1.0.7
Downloaded portable-atomic v1.11.0
Downloaded libm v0.2.8
Downloaded zerovec v0.10.4
Downloaded radicle-surf v0.25.0
Downloaded num-bigint-dig v0.8.4
Downloaded libc v0.2.174
Downloaded unicode-width v0.2.1
Downloaded tree-sitter-ruby v0.23.1
Downloaded regex-automata v0.1.10
Downloaded regex-automata v0.4.9
Downloaded rand v0.8.5
Downloaded tree-sitter-typescript v0.23.2
Downloaded pretty_assertions v1.4.0
Downloaded bstr v1.9.1
Downloaded flate2 v1.1.1
Downloaded prodash v29.0.2
Downloaded num-traits v0.2.19
Downloaded getrandom v0.3.3
Downloaded toml v0.9.5
Downloaded serde v1.0.219
Downloaded syn v1.0.109
Downloaded tokio v1.47.1
Downloaded socket2 v0.5.7
Downloaded sha3 v0.10.8
Downloaded snapbox v0.4.17
Downloaded similar v2.5.0
Downloaded sem_safe v0.2.0
Downloaded rsa v0.9.6
Downloaded proc-macro2 v1.0.92
Downloaded tree-sitter-c v0.23.2
Downloaded sharded-slab v0.1.7
Downloaded bloomy v1.2.0
Downloaded hashbrown v0.14.3
Downloaded tinyvec v1.6.0
Downloaded thiserror v1.0.69
Downloaded gix-path v0.10.15
Downloaded gix-packetline v0.18.4
Downloaded gix-date v0.9.4
Downloaded spin v0.9.8
Downloaded smallvec v1.13.2
Downloaded signal-hook v0.3.18
Downloaded serde_derive v1.0.219
Downloaded once_cell v1.21.3
Downloaded idna v1.0.3
Downloaded unicode-normalization v0.1.23
Downloaded fluent-uri v0.3.2
Downloaded tracing-subscriber v0.3.19
Downloaded linux-raw-sys v0.4.13
Downloaded jobserver v0.1.31
Downloaded serde-untagged v0.1.7
Downloaded ryu v1.0.17
Downloaded referencing v0.30.0
Downloaded proc-macro-error2 v2.0.1
Downloaded indexmap v2.2.6
Downloaded zerocopy v0.7.35
Downloaded toml_datetime v0.7.0
Downloaded litemap v0.7.5
Downloaded indicatif v0.18.0
Downloaded idna_adapter v1.2.0
Downloaded git2 v0.19.0
Downloaded p256 v0.13.2
Downloaded signals_receipts v0.2.0
Downloaded zerovec-derive v0.10.3
Downloaded libgit2-sys v0.17.0+1.8.1
Downloaded spki v0.7.3
Downloaded sval_nested v2.14.1
Downloaded git-ref-format v0.5.0
Downloaded tempfile v3.10.1
Downloaded hmac v0.12.1
Downloaded gix-odb v0.66.0
Downloaded tree-sitter-html v0.23.2
Downloaded overload v0.1.1
Downloaded chrono v0.4.38
Downloaded itertools v0.14.0
Downloaded paste v1.0.15
Downloaded walkdir v2.5.0
Downloaded tree-sitter-toml-ng v0.6.0
Downloaded thiserror-impl v1.0.69
Downloaded gix-diff v0.49.0
Downloaded schemars_derive v1.0.4
Downloaded write16 v1.0.0
Downloaded gix-commitgraph v0.25.1
Downloaded url v2.5.4
Downloaded tree-sitter-highlight v0.24.4
Downloaded serde_derive_internals v0.29.1
Downloaded quote v1.0.36
Downloaded qcheck v1.0.0
Downloaded poly1305 v0.8.0
Downloaded num-rational v0.4.2
Downloaded sec1 v0.7.3
Downloaded pbkdf2 v0.12.2
Downloaded sval_json v2.14.1
Downloaded sval_fmt v2.14.1
Downloaded subtle v2.5.0
Downloaded gix-credentials v0.26.0
Downloaded gix-validate v0.9.4
Downloaded rand_core v0.6.4
Downloaded nu-ansi-term v0.46.0
Downloaded tree-sitter-go v0.23.4
Downloaded mio v0.8.11
Downloaded linux-raw-sys v0.9.4
Downloaded sha2 v0.10.8
Downloaded rustc-demangle v0.1.26
Downloaded primeorder v0.13.6
Downloaded inout v0.1.3
Downloaded group v0.13.0
Downloaded gix-features v0.39.1
Downloaded toml_writer v1.0.2
Downloaded sval v2.14.1
Downloaded clap_builder v4.5.44
Downloaded test-log-macros v0.2.18
Downloaded unicode-ident v1.0.12
Downloaded typenum v1.17.0
Downloaded signal-hook-registry v1.4.5
Downloaded sqlite3-src v0.5.1
Downloaded num-integer v0.1.46
Downloaded tinystr v0.7.6
Downloaded thread_local v1.1.9
Downloaded form_urlencoded v1.2.1
Downloaded mio v1.0.4
Downloaded heck v0.5.0
Downloaded timeago v0.4.2
Downloaded shlex v1.3.0
Downloaded sqlite3-sys v0.15.2
Downloaded polyval v0.6.2
Downloaded sval_buffer v2.14.1
Downloaded memchr v2.7.2
Downloaded value-bag-sval2 v1.11.1
Downloaded gix-negotiate v0.17.0
Downloaded gix-transport v0.44.0
Downloaded unicode-width v0.1.11
Downloaded icu_properties v1.5.1
Downloaded inquire v0.7.5
Downloaded utf16_iter v1.0.5
Downloaded sqlite v0.32.0
Downloaded salsa20 v0.10.2
Downloaded proc-macro-error-attr2 v2.0.0
Downloaded crossterm v0.29.0
Downloaded icu_normalizer v1.5.0
Downloaded tracing v0.1.41
Downloaded gix-config-value v0.14.12
Downloaded emojis v0.6.4
Downloaded version_check v0.9.4
Downloaded utf8_iter v1.0.4
Downloaded universal-hash v0.5.1
Downloaded unicode-display-width v0.3.0
Downloaded typeid v1.0.3
Downloaded miniz_oxide v0.8.8
Downloaded siphasher v1.0.1
Downloaded signature v1.6.4
Downloaded same-file v1.0.6
Downloaded sval_ref v2.14.1
Downloaded structured-logger v1.0.4
Downloaded streaming-iterator v0.1.9
Downloaded log v0.4.27
Downloaded siphasher v0.3.11
Downloaded radicle-std-ext v0.1.1
Downloaded zeroize v1.7.0
Downloaded tree-sitter-css v0.23.1
Downloaded ref-cast-impl v1.0.24
Downloaded radicle-git-ext v0.10.0
Downloaded percent-encoding v2.3.1
Downloaded icu_collections v1.5.0
Downloaded gix-tempfile v15.0.0
Downloaded yoke v0.7.5
Downloaded memmap2 v0.9.4
Downloaded maybe-async v0.2.10
Downloaded lock_api v0.4.11
Downloaded litrs v0.4.1
Downloaded shell-words v1.1.0
Downloaded serde_fmt v1.0.3
Downloaded crypto-bigint v0.5.5
Downloaded uuid-simd v0.8.0
Downloaded signature v2.2.0
Downloaded qcheck-macros v1.0.0
Downloaded num-iter v0.1.45
Downloaded fancy-regex v0.14.0
Downloaded base64 v0.21.7
Downloaded icu_locid_transform_data v1.5.1
Downloaded gix-ref v0.49.1
Downloaded signal-hook-mio v0.2.4
Downloaded cc v1.2.2
Downloaded yoke-derive v0.7.5
Downloaded tracing-log v0.2.0
Downloaded socks5-client v0.4.1
Downloaded zerofrom v0.1.6
Downloaded noise-framework v0.4.0
Downloaded serde_spanned v1.0.0
Downloaded nonempty v0.9.0
Downloaded localtime v1.3.1
Downloaded ref-cast v1.0.24
Downloaded chacha20poly1305 v0.10.1
Downloaded multibase v0.9.1
Downloaded ppv-lite86 v0.2.17
Downloaded pem-rfc7468 v0.7.0
Downloaded derive_more v2.0.1
Downloaded clap v4.5.44
Downloaded cyphernet v0.5.2
Downloaded bytes v1.10.1
Downloaded sha1_smol v1.0.0
Downloaded crossbeam-utils v0.8.19
Downloaded elliptic-curve v0.13.8
Downloaded anyhow v1.0.82
Downloaded iana-time-zone v0.1.60
Downloaded gix-revision v0.31.1
Downloaded gix-refspec v0.27.0
Downloaded gix-hashtable v0.6.0
Downloaded env_logger v0.11.8
Downloaded email_address v0.2.9
Downloaded ecdsa v0.16.9
Downloaded ec25519 v0.1.0
Downloaded diff v0.1.13
Downloaded ctr v0.9.2
Downloaded crossterm v0.25.0
Downloaded const-oid v0.9.6
Downloaded console v0.16.0
Downloaded cipher v0.4.4
Downloaded chacha20 v0.9.1
Downloaded arc-swap v1.7.1
Downloaded anstream v0.6.13
Downloaded amplify_syn v2.0.1
Downloaded gix-hash v0.15.1
Downloaded gix-fs v0.12.1
Downloaded ed25519 v1.5.3
Downloaded base64ct v1.6.0
Downloaded num-complex v0.4.6
Downloaded clap_lex v0.7.5
Downloaded anstyle-query v1.0.2
Downloaded ghash v0.5.1
Downloaded fxhash v0.2.1
Downloaded cyphergraphy v0.3.0
Downloaded autocfg v1.2.0
Downloaded either v1.11.0
Downloaded dyn-clone v1.0.17
Downloaded cpufeatures v0.2.12
Downloaded phf_shared v0.11.3
Downloaded phf v0.11.3
Downloaded outref v0.5.2
Downloaded normalize-line-endings v0.3.0
Downloaded faster-hex v0.9.0
Downloaded data-encoding-macro-internal v0.1.12
Downloaded cfg-if v1.0.0
Downloaded block-padding v0.3.3
Downloaded bcrypt-pbkdf v0.10.0
Downloaded base-x v0.2.11
Downloaded erased-serde v0.4.6
Downloaded bitflags v1.3.2
Downloaded bit-vec v0.8.0
Downloaded os_info v3.12.0
Downloaded equivalent v1.0.1
Downloaded env_filter v0.1.3
Downloaded dunce v1.0.5
Downloaded derive_more-impl v2.0.1
Downloaded data-encoding-macro v0.1.14
Downloaded cypheraddr v0.4.0
Downloaded colorchoice v1.0.0
Downloaded block-buffer v0.10.4
Downloaded backtrace v0.3.75
Downloaded opaque-debug v0.3.1
Downloaded num v0.4.3
Downloaded ff v0.13.0
Downloaded fastrand v2.1.0
Downloaded document-features v0.2.11
Downloaded data-encoding v2.5.0
Downloaded ct-codecs v1.1.1
Downloaded crypto-common v0.1.6
Downloaded crossbeam-channel v0.5.15
Downloaded convert_case v0.7.1
Downloaded clap_derive v4.5.41
Downloaded byteorder v1.5.0
Downloaded bytecount v0.6.8
Downloaded borrow-or-share v0.2.2
Downloaded blowfish v0.9.1
Downloaded bit-set v0.8.0
Downloaded ascii v1.1.0
Downloaded anstyle v1.0.11
Downloaded errno v0.3.13
Downloaded bytesize v2.0.1
Downloaded bitflags v2.9.1
Downloaded base64 v0.22.1
Downloaded digest v0.10.7
Downloaded cbc v0.1.2
Downloaded anstyle-parse v0.2.3
Downloaded aes-gcm v0.10.3
Downloaded base32 v0.4.0
Downloaded base16ct v0.2.0
Downloaded amplify_num v0.5.2
Downloaded amplify_derive v4.0.0
Downloaded aes v0.8.4
Downloaded colored v2.1.0
Downloaded amplify v4.6.0
Downloaded aho-corasick v1.1.3
Downloaded libz-sys v1.1.16
Compiling libc v0.2.174
Compiling proc-macro2 v1.0.92
Compiling unicode-ident v1.0.12
Checking cfg-if v1.0.0
Compiling serde v1.0.219
Compiling shlex v1.3.0
Checking memchr v2.7.2
Compiling typeid v1.0.3
Compiling quote v1.0.36
Compiling syn v2.0.89
Compiling jobserver v0.1.31
Compiling autocfg v1.2.0
Checking smallvec v1.13.2
Compiling version_check v0.9.4
Compiling cc v1.2.2
Checking getrandom v0.2.15
Checking aho-corasick v1.1.3
Checking regex-syntax v0.8.5
Compiling typenum v1.17.0
Compiling generic-array v0.14.7
Checking rand_core v0.6.4
Compiling thiserror v2.0.12
Checking regex-automata v0.4.9
Checking fastrand v2.1.0
Checking bitflags v2.9.1
Compiling lock_api v0.4.11
Compiling parking_lot_core v0.9.9
Checking scopeguard v1.2.0
Checking crypto-common v0.1.6
Checking once_cell v1.21.3
Compiling synstructure v0.13.1
Checking parking_lot v0.12.3
Checking stable_deref_trait v1.2.0
Checking subtle v2.5.0
Checking tinyvec_macros v0.1.1
Checking itoa v1.0.11
Checking tinyvec v1.6.0
Checking hashbrown v0.14.3
Checking bstr v1.9.1
Checking zeroize v1.7.0
Checking unicode-normalization v0.1.23
Checking litemap v0.7.5
Compiling crc32fast v1.4.0
Checking writeable v0.5.5
Checking cpufeatures v0.2.12
Compiling serde_derive v1.0.219
Compiling thiserror-impl v2.0.12
Compiling zerofrom-derive v0.1.6
Compiling yoke-derive v0.7.5
Compiling zerovec-derive v0.10.3
Checking zerofrom v0.1.6
Compiling displaydoc v0.2.5
Checking yoke v0.7.5
Compiling icu_locid_transform_data v1.5.1
Compiling pkg-config v0.3.30
Checking zerovec v0.10.4
Compiling thiserror v1.0.69
Compiling thiserror-impl v1.0.69
Compiling icu_provider_macros v1.5.0
Checking tinystr v0.7.6
Checking icu_locid v1.5.0
Compiling icu_properties_data v1.5.1
Checking icu_provider v1.5.0
Checking block-padding v0.3.3
Compiling icu_normalizer_data v1.5.1
Checking icu_locid_transform v1.5.0
Checking inout v0.1.3
Checking icu_collections v1.5.0
Checking block-buffer v0.10.4
Compiling syn v1.0.109
Checking digest v0.10.7
Checking cipher v0.4.4
Checking utf8_iter v1.0.4
Checking write16 v1.0.0
Checking icu_properties v1.5.1
Checking utf16_iter v1.0.5
Checking adler2 v2.0.0
Checking miniz_oxide v0.8.8
Checking erased-serde v0.4.6
Checking serde_fmt v1.0.3
Checking faster-hex v0.9.0
Checking value-bag-serde1 v1.11.1
Checking value-bag v1.11.1
Checking gix-hash v0.15.1
Checking log v0.4.27
Checking percent-encoding v2.3.1
Compiling rustix v0.38.34
Checking linux-raw-sys v0.4.13
Checking icu_normalizer v1.5.0
Checking gix-trace v0.1.12
Checking form_urlencoded v1.2.1
Checking idna_adapter v1.2.0
Checking sha2 v0.10.8
Checking idna v1.0.3
Compiling vcpkg v0.2.15
Checking url v2.5.4
Checking universal-hash v0.5.1
Checking opaque-debug v0.3.1
Checking gix-utils v0.1.14
Compiling libz-sys v1.1.16
Compiling amplify_syn v2.0.1
Checking tempfile v3.10.1
Compiling libgit2-sys v0.17.0+1.8.1
Compiling num-traits v0.2.19
Compiling serde_json v1.0.140
Checking same-file v1.0.6
Checking signature v1.6.4
Checking equivalent v1.0.1
Checking byteorder v1.5.0
Checking ed25519 v1.5.3
Checking indexmap v2.2.6
Checking walkdir v2.5.0
Compiling amplify_derive v4.0.0
Checking flate2 v1.1.1
Checking prodash v29.0.2
Checking aead v0.5.2
Checking ascii v1.1.0
Compiling ref-cast v1.0.24
Checking ct-codecs v1.1.1
Checking sha1_smol v1.0.0
Checking amplify_num v0.5.2
Checking ryu v1.0.17
Checking gix-features v0.39.1
Checking amplify v4.6.0
Checking ec25519 v0.1.0
Checking poly1305 v0.8.0
Checking git-ref-format-core v0.5.0
Checking chacha20 v0.9.1
Compiling ref-cast-impl v1.0.24
Checking cyphergraphy v0.3.0
Checking polyval v0.6.2
Checking hmac v0.12.1
Compiling sqlite3-src v0.5.1
Checking home v0.5.9
Checking keccak v0.1.5
Checking base64ct v1.6.0
Checking dyn-clone v1.0.17
Checking pem-rfc7468 v0.7.0
Checking sha3 v0.10.8
Checking gix-path v0.10.15
Checking ghash v0.5.1
Checking pbkdf2 v0.12.2
Checking ctr v0.9.2
Checking aes v0.8.4
Checking rand v0.8.5
Compiling crossbeam-utils v0.8.19
Checking base32 v0.4.0
Compiling data-encoding v2.5.0
Checking cypheraddr v0.4.0
Checking qcheck v1.0.0
Compiling data-encoding-macro-internal v0.1.12
Checking aes-gcm v0.10.3
Checking ssh-encoding v0.2.0
Checking chacha20poly1305 v0.10.1
Checking blowfish v0.9.1
Checking cbc v0.1.2
Compiling serde_derive_internals v0.29.1
Checking data-encoding-macro v0.1.14
Checking ssh-cipher v0.2.0
Checking bcrypt-pbkdf v0.10.0
Checking noise-framework v0.4.0
Checking socks5-client v0.4.1
Compiling schemars_derive v1.0.4
Checking base-x v0.2.11
Checking signature v2.2.0
Checking ssh-key v0.6.6
Checking multibase v0.9.1
Checking crossbeam-channel v0.5.15
Checking cyphernet v0.5.2
Checking radicle-ssh v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-ssh)
Checking lazy_static v1.5.0
Checking nonempty v0.9.0
Checking siphasher v1.0.1
Checking schemars v1.0.4
Checking jiff v0.2.1
Checking radicle-git-metadata v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-metadata)
Checking radicle-dag v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-dag)
Checking winnow v0.6.26
Checking anstyle-query v1.0.2
Checking radicle-git-ref-format v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-ref-format)
Checking gix-hashtable v0.6.0
Checking gix-validate v0.9.4
Checking base64 v0.21.7
Checking gix-date v0.9.4
Checking gix-actor v0.33.2
Checking utf8parse v0.2.1
Checking iana-time-zone v0.1.60
Checking chrono v0.4.38
Checking gix-object v0.46.1
Checking anstyle-parse v0.2.3
Checking colored v2.1.0
Checking serde-untagged v0.1.7
Checking bytesize v2.0.1
Checking localtime v1.3.1
Checking memmap2 v0.9.4
Checking tree-sitter-language v0.1.2
Checking fast-glob v0.3.3
Checking colorchoice v1.0.0
Checking anstyle v1.0.11
Checking dunce v1.0.5
Checking gix-chunk v0.4.11
Checking anstream v0.6.13
Checking gix-commitgraph v0.25.1
Compiling rustix v1.0.7
Checking gix-revwalk v0.17.0
Checking gix-fs v0.12.1
Checking linux-raw-sys v0.9.4
Checking gix-tempfile v15.0.0
Checking mio v1.0.4
Checking errno v0.3.13
Checking sem_safe v0.2.0
Checking shell-words v1.1.0
Checking either v1.11.0
Checking gix-command v0.4.1
Checking signals_receipts v0.2.0
Compiling signal-hook v0.3.18
Compiling object v0.36.7
Checking gix-lock v15.0.1
Checking gix-url v0.28.2
Checking gix-config-value v0.14.12
Checking gix-quote v0.4.15
Checking gix-sec v0.10.12
Checking signal-hook-registry v1.4.5
Compiling getrandom v0.3.3
Checking gimli v0.31.1
Checking gix-prompt v0.9.1
Checking addr2line v0.24.2
Checking radicle-signals v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-signals)
Checking gix-revision v0.31.1
Checking gix-traverse v0.43.1
Checking gix-diff v0.49.0
Checking gix-packetline v0.18.4
Checking mio v0.8.11
Compiling tree-sitter v0.24.4
Compiling anyhow v1.0.82
Compiling unicode-segmentation v1.11.0
Checking rustc-demangle v0.1.26
Checking backtrace v0.3.75
Compiling convert_case v0.7.1
Checking signal-hook-mio v0.2.4
Checking gix-transport v0.44.0
Checking gix-pack v0.56.0
Checking gix-refspec v0.27.0
Checking gix-credentials v0.26.0
Checking gix-shallow v0.1.0
Checking gix-ref v0.49.1
Checking gix-negotiate v0.17.0
Compiling maybe-async v0.2.10
Checking regex v1.11.1
Compiling proc-macro-error-attr2 v2.0.0
Compiling portable-atomic v1.11.0
Checking arc-swap v1.7.1
Checking gix-odb v0.66.0
Checking sqlite3-sys v0.15.2
Checking sqlite v0.32.0
Checking radicle-crypto v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-crypto)
Compiling proc-macro-error2 v2.0.1
Checking gix-protocol v0.47.0
Checking uuid v1.16.0
Compiling xattr v1.3.1
Compiling derive_more-impl v2.0.1
Compiling filetime v0.2.23
Compiling litrs v0.4.1
Checking unicode-width v0.2.1
Checking lexopt v0.3.0
Checking bitflags v1.3.2
Checking bytes v1.10.1
Checking crossterm v0.25.0
Checking derive_more v2.0.1
Compiling document-features v0.2.11
Checking console v0.16.0
Compiling tar v0.4.40
Compiling git-ref-format-macro v0.5.0
Checking newline-converter v0.3.0
Checking snapbox-macros v0.3.8
Checking fxhash v0.2.1
Checking salsa20 v0.10.2
Checking unicode-width v0.1.11
Checking unit-prefix v0.5.1
Checking streaming-iterator v0.1.9
Checking siphasher v0.3.11
Checking similar v2.5.0
Checking normalize-line-endings v0.3.0
Checking bloomy v1.2.0
Checking indicatif v0.18.0
Checking snapbox v0.4.17
Checking inquire v0.7.5
Checking scrypt v0.11.0
Compiling radicle-surf v0.25.0
Checking git-ref-format v0.5.0
Checking crossterm v0.29.0
Checking unicode-display-width v0.3.0
Checking systemd-journal-logger v2.2.2
Checking toml_datetime v0.7.0
Checking serde_spanned v1.0.0
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-rust v0.23.2
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-css v0.23.1
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-json v0.24.8
Checking clap_lex v0.7.5
Compiling heck v0.5.0
Checking strsim v0.11.1
Checking pin-project-lite v0.2.16
Checking toml_writer v1.0.2
Checking radicle-std-ext v0.1.1
Checking clap_builder v4.5.44
Checking tokio v1.47.1
Checking toml v0.9.5
Compiling clap_derive v4.5.41
Checking os_info v3.12.0
Checking diff v0.1.13
Checking yansi v0.5.1
Compiling radicle-cli v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cli)
Compiling radicle-node v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-node)
Checking human-panic v2.0.3
Checking pretty_assertions v1.4.0
Checking clap v4.5.44
Checking structured-logger v1.0.4
Checking radicle-systemd v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-systemd)
Checking tree-sitter-highlight v0.24.4
Checking itertools v0.14.0
Checking num-integer v0.1.46
Compiling qcheck-macros v1.0.0
Checking socket2 v0.5.7
Checking timeago v0.4.2
Compiling escargot v0.5.10
Checking num-bigint v0.4.6
Compiling ahash v0.8.11
Checking num-iter v0.1.45
Checking num-complex v0.4.6
Checking env_filter v0.1.3
Checking bit-vec v0.8.0
Checking borrow-or-share v0.2.2
Checking zerocopy v0.7.35
Checking bit-set v0.8.0
Checking num-rational v0.4.2
Checking fluent-uri v0.3.2
Checking num v0.4.3
Checking env_logger v0.11.8
Checking phf_shared v0.11.3
Compiling test-log-macros v0.2.18
Compiling paste v1.0.15
Checking outref v0.5.2
Checking vsimd v0.8.0
Compiling radicle-remote-helper v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-remote-helper)
Checking test-log v0.2.18
Checking uuid-simd v0.8.0
Checking phf v0.11.3
Checking referencing v0.30.0
Checking fraction v0.15.3
Checking fancy-regex v0.14.0
Checking email_address v0.2.9
Checking base64 v0.22.1
Checking num-cmp v0.1.0
Checking bytecount v0.6.8
Checking emojis v0.6.4
Checking jsonschema v0.30.0
Checking git2 v0.19.0
Checking radicle-oid v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-oid)
Checking radicle-git-ext v0.10.0
Checking radicle-term v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-term)
Checking radicle-cob v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cob)
Checking radicle v0.20.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle)
Checking radicle-fetch v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-fetch)
Checking radicle-cli-test v0.13.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cli-test)
Checking radicle-protocol v0.4.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-protocol)
Checking radicle-schemars v0.6.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-schemars)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 52.56s
+ cargo build --all-targets --workspace
Compiling cfg-if v1.0.0
Compiling libc v0.2.174
Compiling shlex v1.3.0
Compiling serde v1.0.219
Compiling memchr v2.7.2
Compiling typeid v1.0.3
Compiling smallvec v1.13.2
Compiling aho-corasick v1.1.3
Compiling jobserver v0.1.31
Compiling getrandom v0.2.15
Compiling regex-syntax v0.8.5
Compiling cc v1.2.2
Compiling erased-serde v0.4.6
Compiling serde_fmt v1.0.3
Compiling regex-automata v0.4.9
Compiling typenum v1.17.0
Compiling value-bag-serde1 v1.11.1
Compiling value-bag v1.11.1
Compiling rand_core v0.6.4
Compiling log v0.4.27
Compiling generic-array v0.14.7
Compiling bitflags v2.9.1
Compiling thiserror v2.0.12
Compiling crypto-common v0.1.6
Compiling fastrand v2.1.0
Compiling scopeguard v1.2.0
Compiling lock_api v0.4.11
Compiling parking_lot_core v0.9.9
Compiling faster-hex v0.9.0
Compiling once_cell v1.21.3
Compiling parking_lot v0.12.3
Compiling zerofrom v0.1.6
Compiling stable_deref_trait v1.2.0
Compiling yoke v0.7.5
Compiling gix-hash v0.15.1
Compiling subtle v2.5.0
Compiling tinyvec_macros v0.1.1
Compiling zerovec v0.10.4
Compiling tinyvec v1.6.0
Compiling hashbrown v0.14.3
Compiling unicode-normalization v0.1.23
Compiling bstr v1.9.1
Compiling zeroize v1.7.0
Compiling itoa v1.0.11
Compiling tinystr v0.7.6
Compiling adler2 v2.0.0
Compiling writeable v0.5.5
Compiling litemap v0.7.5
Compiling cpufeatures v0.2.12
Compiling icu_locid v1.5.0
Compiling miniz_oxide v0.8.8
Compiling icu_locid_transform_data v1.5.1
Compiling block-padding v0.3.3
Compiling thiserror v1.0.69
Compiling inout v0.1.3
Compiling crc32fast v1.4.0
Compiling icu_provider v1.5.0
Compiling icu_properties_data v1.5.1
Compiling icu_locid_transform v1.5.0
Compiling icu_collections v1.5.0
Compiling block-buffer v0.10.4
Compiling digest v0.10.7
Compiling cipher v0.4.4
Compiling icu_properties v1.5.1
Compiling icu_normalizer_data v1.5.1
Compiling write16 v1.0.0
Compiling utf16_iter v1.0.5
Compiling utf8_iter v1.0.4
Compiling percent-encoding v2.3.1
Compiling linux-raw-sys v0.4.13
Compiling gix-trace v0.1.12
Compiling form_urlencoded v1.2.1
Compiling sha2 v0.10.8
Compiling libz-sys v1.1.16
Compiling rustix v0.38.34
Compiling universal-hash v0.5.1
Compiling opaque-debug v0.3.1
Compiling gix-utils v0.1.14
Compiling libgit2-sys v0.17.0+1.8.1
Compiling same-file v1.0.6
Compiling signature v1.6.4
Compiling equivalent v1.0.1
Compiling icu_normalizer v1.5.0
Compiling byteorder v1.5.0
Compiling indexmap v2.2.6
Compiling idna_adapter v1.2.0
Compiling idna v1.0.3
Compiling walkdir v2.5.0
Compiling ed25519 v1.5.3
Compiling url v2.5.4
Compiling tempfile v3.10.1
Compiling flate2 v1.1.1
Compiling prodash v29.0.2
Compiling aead v0.5.2
Compiling ryu v1.0.17
Compiling ascii v1.1.0
Compiling amplify_num v0.5.2
Compiling ct-codecs v1.1.1
Compiling sha1_smol v1.0.0
Compiling ec25519 v0.1.0
Compiling gix-features v0.39.1
Compiling serde_json v1.0.140
Compiling amplify v4.6.0
Compiling num-traits v0.2.19
Compiling poly1305 v0.8.0
Compiling chacha20 v0.9.1
Compiling git-ref-format-core v0.5.0
Compiling ref-cast v1.0.24
Compiling cyphergraphy v0.3.0
Compiling polyval v0.6.2
Compiling hmac v0.12.1
Compiling sqlite3-src v0.5.1
Compiling keccak v0.1.5
Compiling dyn-clone v1.0.17
Compiling home v0.5.9
Compiling base64ct v1.6.0
Compiling gix-path v0.10.15
Compiling pem-rfc7468 v0.7.0
Compiling sha3 v0.10.8
Compiling pbkdf2 v0.12.2
Compiling ghash v0.5.1
Compiling ctr v0.9.2
Compiling aes v0.8.4
Compiling rand v0.8.5
Compiling base32 v0.4.0
Compiling cypheraddr v0.4.0
Compiling aes-gcm v0.10.3
Compiling qcheck v1.0.0
Compiling ssh-encoding v0.2.0
Compiling chacha20poly1305 v0.10.1
Compiling blowfish v0.9.1
Compiling cbc v0.1.2
Compiling data-encoding v2.5.0
Compiling data-encoding-macro v0.1.14
Compiling ssh-cipher v0.2.0
Compiling bcrypt-pbkdf v0.10.0
Compiling noise-framework v0.4.0
Compiling socks5-client v0.4.1
Compiling crossbeam-utils v0.8.19
Compiling signature v2.2.0
Compiling base-x v0.2.11
Compiling crossbeam-channel v0.5.15
Compiling multibase v0.9.1
Compiling ssh-key v0.6.6
Compiling cyphernet v0.5.2
Compiling schemars v1.0.4
Compiling radicle-ssh v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-ssh)
Compiling lazy_static v1.5.0
Compiling nonempty v0.9.0
Compiling jiff v0.2.1
Compiling siphasher v1.0.1
Compiling radicle-git-metadata v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-metadata)
Compiling radicle-dag v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-dag)
Compiling winnow v0.6.26
Compiling anstyle-query v1.0.2
Compiling radicle-git-ref-format v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-ref-format)
Compiling gix-validate v0.9.4
Compiling gix-hashtable v0.6.0
Compiling iana-time-zone v0.1.60
Compiling gix-date v0.9.4
Compiling gix-actor v0.33.2
Compiling utf8parse v0.2.1
Compiling base64 v0.21.7
Compiling anstyle-parse v0.2.3
Compiling gix-object v0.46.1
Compiling chrono v0.4.38
Compiling colored v2.1.0
Compiling serde-untagged v0.1.7
Compiling bytesize v2.0.1
Compiling localtime v1.3.1
Compiling memmap2 v0.9.4
Compiling anstyle v1.0.11
Compiling colorchoice v1.0.0
Compiling dunce v1.0.5
Compiling tree-sitter-language v0.1.2
Compiling fast-glob v0.3.3
Compiling anstream v0.6.13
Compiling gix-chunk v0.4.11
Compiling gix-commitgraph v0.25.1
Compiling gix-fs v0.12.1
Compiling linux-raw-sys v0.9.4
Compiling gix-revwalk v0.17.0
Compiling gix-tempfile v15.0.0
Compiling mio v1.0.4
Compiling rustix v1.0.7
Compiling sem_safe v0.2.0
Compiling errno v0.3.13
Compiling unicode-segmentation v1.11.0
Compiling either v1.11.0
Compiling shell-words v1.1.0
Compiling gix-command v0.4.1
Compiling signals_receipts v0.2.0
Compiling gix-lock v15.0.1
Compiling gix-config-value v0.14.12
Compiling gix-url v0.28.2
Compiling gix-quote v0.4.15
Compiling gix-sec v0.10.12
Compiling signal-hook-registry v1.4.5
Compiling gimli v0.31.1
Compiling signal-hook v0.3.18
Compiling gix-prompt v0.9.1
Compiling object v0.36.7
Compiling addr2line v0.24.2
Compiling sqlite3-sys v0.15.2
Compiling sqlite v0.32.0
Compiling radicle-signals v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-signals)
Compiling radicle-crypto v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-crypto)
Compiling gix-traverse v0.43.1
Compiling gix-revision v0.31.1
Compiling gix-diff v0.49.0
Compiling gix-packetline v0.18.4
Compiling mio v0.8.11
Compiling tree-sitter v0.24.4
Compiling rustc-demangle v0.1.26
Compiling backtrace v0.3.75
Compiling signal-hook-mio v0.2.4
Compiling gix-transport v0.44.0
Compiling gix-refspec v0.27.0
Compiling gix-pack v0.56.0
Compiling gix-credentials v0.26.0
Compiling getrandom v0.3.3
Compiling gix-ref v0.49.1
Compiling gix-shallow v0.1.0
Compiling convert_case v0.7.1
Compiling gix-negotiate v0.17.0
Compiling regex v1.11.1
Compiling arc-swap v1.7.1
Compiling gix-odb v0.66.0
Compiling gix-protocol v0.47.0
Compiling derive_more-impl v2.0.1
Compiling uuid v1.16.0
Compiling xattr v1.3.1
Compiling filetime v0.2.23
Compiling lexopt v0.3.0
Compiling bytes v1.10.1
Compiling unicode-width v0.2.1
Compiling bitflags v1.3.2
Compiling crossterm v0.25.0
Compiling console v0.16.0
Compiling git-ref-format-macro v0.5.0
Compiling tar v0.4.40
Compiling derive_more v2.0.1
Compiling anyhow v1.0.82
Compiling portable-atomic v1.11.0
Compiling newline-converter v0.3.0
Compiling snapbox-macros v0.3.8
Compiling fxhash v0.2.1
Compiling salsa20 v0.10.2
Compiling unicode-width v0.1.11
Compiling streaming-iterator v0.1.9
Compiling similar v2.5.0
Compiling unit-prefix v0.5.1
Compiling normalize-line-endings v0.3.0
Compiling siphasher v0.3.11
Compiling indicatif v0.18.0
Compiling bloomy v1.2.0
Compiling snapbox v0.4.17
Compiling radicle-surf v0.25.0
Compiling inquire v0.7.5
Compiling scrypt v0.11.0
Compiling crossterm v0.29.0
Compiling git-ref-format v0.5.0
Compiling systemd-journal-logger v2.2.2
Compiling unicode-display-width v0.3.0
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-rust v0.23.2
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-css v0.23.1
Compiling serde_spanned v1.0.0
Compiling toml_datetime v0.7.0
Compiling radicle-std-ext v0.1.1
Compiling toml_writer v1.0.2
Compiling pin-project-lite v0.2.16
Compiling strsim v0.11.1
Compiling clap_lex v0.7.5
Compiling toml v0.9.5
Compiling clap_builder v4.5.44
Compiling tokio v1.47.1
Compiling os_info v3.12.0
Compiling radicle-cli v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cli)
Compiling radicle-node v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-node)
Compiling diff v0.1.13
Compiling yansi v0.5.1
Compiling pretty_assertions v1.4.0
Compiling human-panic v2.0.3
Compiling structured-logger v1.0.4
Compiling clap v4.5.44
Compiling radicle-systemd v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-systemd)
Compiling tree-sitter-highlight v0.24.4
Compiling itertools v0.14.0
Compiling num-integer v0.1.46
Compiling socket2 v0.5.7
Compiling timeago v0.4.2
Compiling escargot v0.5.10
Compiling num-bigint v0.4.6
Compiling num-iter v0.1.45
Compiling num-complex v0.4.6
Compiling env_filter v0.1.3
Compiling num-rational v0.4.2
Compiling zerocopy v0.7.35
Compiling borrow-or-share v0.2.2
Compiling bit-vec v0.8.0
Compiling fluent-uri v0.3.2
Compiling bit-set v0.8.0
Compiling num v0.4.3
Compiling ahash v0.8.11
Compiling env_logger v0.11.8
Compiling phf_shared v0.11.3
Compiling vsimd v0.8.0
Compiling outref v0.5.2
Compiling radicle-remote-helper v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-remote-helper)
Compiling phf v0.11.3
Compiling test-log v0.2.18
Compiling referencing v0.30.0
Compiling uuid-simd v0.8.0
Compiling fraction v0.15.3
Compiling fancy-regex v0.14.0
Compiling email_address v0.2.9
Compiling num-cmp v0.1.0
Compiling base64 v0.22.1
Compiling bytecount v0.6.8
Compiling jsonschema v0.30.0
Compiling emojis v0.6.4
Compiling git2 v0.19.0
Compiling radicle-oid v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-oid)
Compiling radicle-git-ext v0.10.0
Compiling radicle-cob v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cob)
Compiling radicle-term v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-term)
Compiling radicle v0.20.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle)
Compiling radicle-fetch v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-fetch)
Compiling radicle-protocol v0.4.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-protocol)
Compiling radicle-cli-test v0.13.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cli-test)
Compiling radicle-schemars v0.6.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-schemars)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 13s
+ cargo doc --workspace --no-deps
Checking regex-automata v0.4.9
Compiling syn v1.0.109
Checking idna v1.0.3
Checking radicle-ssh v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-ssh)
Compiling num-traits v0.2.19
Checking url v2.5.4
Checking radicle-git-metadata v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-metadata)
Checking radicle-dag v0.10.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-dag)
Checking git2 v0.19.0
Checking chrono v0.4.38
Checking bstr v1.9.1
Compiling amplify_syn v2.0.1
Checking gix-path v0.10.15
Checking git-ref-format-core v0.5.0
Checking gix-date v0.9.4
Checking gix-validate v0.9.4
Checking gix-actor v0.33.2
Checking radicle-oid v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-oid)
Compiling data-encoding-macro-internal v0.1.12
Checking radicle-git-ref-format v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-ref-format)
Checking gix-object v0.46.1
Compiling amplify_derive v4.0.0
Checking gix-commitgraph v0.25.1
Checking gix-command v0.4.1
Checking gix-config-value v0.14.12
Checking data-encoding-macro v0.1.14
Checking gix-url v0.28.2
Checking multibase v0.9.1
Checking gix-revwalk v0.17.0
Checking gix-quote v0.4.15
Checking gix-diff v0.49.0
Checking gix-traverse v0.43.1
Checking gix-revision v0.31.1
Checking gix-prompt v0.9.1
Checking radicle-signals v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-signals)
Checking gix-packetline v0.18.4
Checking regex v1.11.1
Checking gix-pack v0.56.0
Checking gix-transport v0.44.0
Checking tree-sitter v0.24.4
Checking gix-credentials v0.26.0
Checking git-ref-format v0.5.0
Checking gix-refspec v0.27.0
Checking gix-negotiate v0.17.0
Checking gix-ref v0.49.1
Checking gix-shallow v0.1.0
Checking radicle-git-ext v0.10.0
Checking gix-odb v0.66.0
Checking amplify v4.6.0
Checking uuid v1.16.0
Checking cyphergraphy v0.3.0
Checking gix-protocol v0.47.0
Compiling radicle-cli v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cli)
Checking human-panic v2.0.3
Checking cypheraddr v0.4.0
Checking noise-framework v0.4.0
Checking socks5-client v0.4.1
Checking radicle-surf v0.25.0
Checking tree-sitter-toml-ng v0.6.0
Checking cyphernet v0.5.2
Checking radicle-term v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-term)
Checking radicle-crypto v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-crypto)
Checking tree-sitter-highlight v0.24.4
Checking radicle-cob v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cob)
Compiling radicle-node v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-node)
Checking radicle-systemd v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-systemd)
Documenting radicle-systemd v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-systemd)
Documenting radicle-cob v0.17.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-cob)
Checking radicle v0.20.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle)
Documenting radicle v0.20.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle)
Documenting radicle-crypto v0.14.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-crypto)
Documenting radicle-term v0.16.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-term)
Documenting radicle-signals v0.11.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-signals)
Documenting radicle-git-ref-format v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-git-ref-format)
Documenting radicle-oid v0.1.0 (/5c0c2ed0-094e-47af-94b9-5059541936f0/w/crates/radicle-oid)
error: unresolved link to `::qcheck::Arbitrary`
--> crates/radicle-oid/src/lib.rs:55:28
|
55 | //! [`qcheck::Arbitrary`]: ::qcheck::Arbitrary
| ^^^^^^^^^^^^^^^^^^^ no item named `` in scope
|
= note: `-D rustdoc::broken-intra-doc-links` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(rustdoc::broken_intra_doc_links)]`
error: unresolved link to `::radicle_git_ref_format::Component`
--> crates/radicle-oid/src/lib.rs:61:44
|
61 | //! [`radicle_git_ref_format::Component`]: ::radicle_git_ref_format::Component
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `` in scope
error: unresolved link to `::radicle_git_ref_format::RefString`
--> crates/radicle-oid/src/lib.rs:62:44
|
62 | //! [`radicle_git_ref_format::RefString`]: ::radicle_git_ref_format::RefString
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `` in scope
error: could not document `radicle-oid`
warning: build failed, waiting for other jobs to finish...
Exit code: 101
{
"response": "finished",
"result": "failure"
}