Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
Handle patch events, fix CI event notion of origin event
Merged liw opened 1 year ago

Change CI event creation to set the from_node field to the node id in the Git ref being updated. Previously we set it to the remote field in the node event, but that is id of the node from which our local node got the RefsUpdated event, not the node where the change was originally made.

12 files changed +888 -439 ac3fcf28 2e2236ae
modified Cargo.lock
@@ -75,9 +75,9 @@ dependencies = [

[[package]]
name = "allocator-api2"
-
version = "0.2.18"
+
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"

[[package]]
name = "amplify"
@@ -207,9 +207,9 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"

[[package]]
name = "atom_syndication"
-
version = "0.12.4"
+
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2a3a5ed3201df5658d1aa45060c5a57dc9dba8a8ada20d696d67cb0c479ee043"
+
checksum = "3ee79fb83c725eae67b55218870813d2fc39fd85e4f1583848ef9f4f823cfe7c"
dependencies = [
 "chrono",
 "derive_builder",
@@ -307,9 +307,9 @@ dependencies = [

[[package]]
name = "bstr"
-
version = "1.10.0"
+
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
+
checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
dependencies = [
 "memchr",
 "serde",
@@ -338,9 +338,9 @@ dependencies = [

[[package]]
name = "cc"
-
version = "1.1.36"
+
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70"
+
checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
dependencies = [
 "jobserver",
 "libc",
@@ -391,9 +391,9 @@ dependencies = [

[[package]]
name = "clap"
-
version = "4.5.20"
+
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
+
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [
 "clap_builder",
 "clap_derive",
@@ -401,9 +401,9 @@ dependencies = [

[[package]]
name = "clap_builder"
-
version = "4.5.20"
+
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
+
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [
 "anstream",
 "anstyle",
@@ -421,14 +421,14 @@ dependencies = [
 "heck",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
name = "clap_lex"
-
version = "0.7.2"
+
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"

[[package]]
name = "colorchoice"
@@ -444,9 +444,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"

[[package]]
name = "cpufeatures"
-
version = "0.2.14"
+
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
 "libc",
]
@@ -524,12 +524,12 @@ checksum = "026ac6ceace6298d2c557ef5ed798894962296469ec7842288ea64674201a2d1"

[[package]]
name = "ctor"
-
version = "0.2.8"
+
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
+
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -558,7 +558,7 @@ checksum = "1234e1717066d3c71dcf89b75e7b586299e41204d361db56ec51e6ded5014279"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -624,7 +624,7 @@ dependencies = [
 "proc-macro2",
 "quote",
 "strsim",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -635,7 +635,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
 "darling_core",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -701,7 +701,7 @@ dependencies = [
 "darling",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -711,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
 "derive_builder_core",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -734,9 +734,9 @@ dependencies = [

[[package]]
name = "diligent-date-parser"
-
version = "0.1.4"
+
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182"
+
checksum = "c8ede7d79366f419921e2e2f67889c12125726692a313bffb474bd5f37a581e9"
dependencies = [
 "chrono",
]
@@ -749,7 +749,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -760,14 +760,14 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"

[[package]]
name = "duration-str"
-
version = "0.11.2"
+
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "709d653e7c92498eb29fb86a2a6f0f3502b97530f33aedb32ef848d4d28b31a3"
+
checksum = "f88959de2d447fd3eddcf1909d1f19fe084e27a056a6904203dc5d8b9e771c1e"
dependencies = [
 "chrono",
 "rust_decimal",
 "serde",
-
 "thiserror",
+
 "thiserror 2.0.3",
 "time",
 "winnow",
]
@@ -865,12 +865,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"

[[package]]
name = "errno"
-
version = "0.3.9"
+
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
 "libc",
-
 "windows-sys 0.52.0",
+
 "windows-sys 0.59.0",
]

[[package]]
@@ -909,9 +909,9 @@ dependencies = [

[[package]]
name = "flate2"
-
version = "1.0.34"
+
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
+
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
 "crc32fast",
 "miniz_oxide",
@@ -1027,7 +1027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
dependencies = [
 "serde",
-
 "thiserror",
+
 "thiserror 1.0.69",
]

[[package]]
@@ -1039,28 +1039,28 @@ dependencies = [
 "git-ref-format-core",
 "proc-macro-error",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
name = "git-testament"
-
version = "0.2.5"
+
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "710c78d2b68e46e62f5ba63ba0a7a2986640f37f9ecc07903b9ad4e7b2dbfc8e"
+
checksum = "5a74999c921479f919c87a9d2e6922a79a18683f18105344df8e067149232e51"
dependencies = [
 "git-testament-derive",
]

[[package]]
name = "git-testament-derive"
-
version = "0.2.0"
+
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9b31494efbbe1a6730f6943759c21b92c8dc431cb4df177e6f2a6429c3c96842"
+
checksum = "bbeac967e71eb3dc1656742fc7521ec7cd3b6b88738face65bf1fddf702bc4c0"
dependencies = [
 "log",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
 "time",
]

@@ -1092,7 +1092,7 @@ dependencies = [
 "aho-corasick",
 "bstr",
 "log",
-
 "regex-automata 0.4.8",
+
 "regex-automata 0.4.9",
 "regex-syntax 0.8.5",
]

@@ -1130,9 +1130,9 @@ dependencies = [

[[package]]
name = "hashbrown"
-
version = "0.15.1"
+
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"

[[package]]
name = "hashlink"
@@ -1315,7 +1315,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -1355,7 +1355,7 @@ dependencies = [
 "globset",
 "log",
 "memchr",
-
 "regex-automata 0.4.8",
+
 "regex-automata 0.4.9",
 "same-file",
 "walkdir",
 "winapi-util",
@@ -1368,7 +1368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
 "equivalent",
-
 "hashbrown 0.15.1",
+
 "hashbrown 0.15.2",
]

[[package]]
@@ -1389,9 +1389,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"

[[package]]
name = "itoa"
-
version = "1.0.11"
+
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"

[[package]]
name = "jobserver"
@@ -1422,9 +1422,9 @@ dependencies = [

[[package]]
name = "libc"
-
version = "0.2.162"
+
version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"

[[package]]
name = "libgit2-sys"
@@ -1487,9 +1487,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"

[[package]]
name = "litemap"
-
version = "0.7.3"
+
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"

[[package]]
name = "localtime"
@@ -1755,7 +1755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
dependencies = [
 "memchr",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "ucd-trie",
]

@@ -1779,7 +1779,7 @@ dependencies = [
 "pest_meta",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -1909,9 +1909,9 @@ dependencies = [

[[package]]
name = "proc-macro2"
-
version = "1.0.89"
+
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
 "unicode-ident",
]
@@ -1946,9 +1946,9 @@ dependencies = [

[[package]]
name = "quick-xml"
-
version = "0.36.2"
+
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
+
checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
dependencies = [
 "encoding_rs",
 "memchr",
@@ -1965,9 +1965,9 @@ dependencies = [

[[package]]
name = "radicle"
-
version = "0.13.0"
+
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4a818569c11f1bac56f38b002d778ce8ec92e312024b9aebcd68bad5dee6a465"
+
checksum = "fd823aeed3ffe73eb82a213e62cb3811f9bdf453844d6e0b14684e0757fb389b"
dependencies = [
 "amplify",
 "base64 0.21.7",
@@ -1991,7 +1991,7 @@ dependencies = [
 "siphasher",
 "sqlite",
 "tempfile",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "unicode-normalization",
]

@@ -2006,6 +2006,7 @@ dependencies = [
 "duration-str",
 "html-page",
 "radicle",
+
 "radicle-crypto",
 "radicle-git-ext",
 "radicle-surf",
 "regex",
@@ -2018,7 +2019,7 @@ dependencies = [
 "subplot-build",
 "subplotlib",
 "tempfile",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "time",
 "tracing",
 "tracing-subscriber",
@@ -2028,9 +2029,9 @@ dependencies = [

[[package]]
name = "radicle-cob"
-
version = "0.12.0"
+
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d4fac94999d8ffb6e88674bee487b080b69bbc9fb1b439ebfa51481ede1a17b3"
+
checksum = "90581a9508ccc310998e991d7acf139d2991297d3fb37d30de07536e10256afb"
dependencies = [
 "fastrand",
 "git2",
@@ -2042,7 +2043,7 @@ dependencies = [
 "radicle-git-ext",
 "serde",
 "serde_json",
-
 "thiserror",
+
 "thiserror 1.0.69",
]

[[package]]
@@ -2062,15 +2063,15 @@ dependencies = [
 "serde",
 "sqlite",
 "ssh-key",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "zeroize",
]

[[package]]
name = "radicle-dag"
-
version = "0.9.0"
+
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c2a678c3049a88ae6a34dd9f52ea9a5f9f066a0af63466b75cf8c48840303067"
+
checksum = "cb41c7e10ada3a4df960190a96bfb4af56d33ada890f917acc8e3b122b614875"
dependencies = [
 "fastrand",
]
@@ -2086,7 +2087,7 @@ dependencies = [
 "percent-encoding",
 "radicle-std-ext",
 "serde",
-
 "thiserror",
+
 "thiserror 1.0.69",
]

[[package]]
@@ -2097,7 +2098,7 @@ checksum = "fbee758010fb64482be4b18591fbeb3cbc15b16450d143edf4edb5484c7366c6"
dependencies = [
 "byteorder",
 "log",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "zeroize",
]

@@ -2123,7 +2124,7 @@ dependencies = [
 "radicle-std-ext",
 "serde",
 "tar",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "url",
]

@@ -2174,7 +2175,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
 "aho-corasick",
 "memchr",
-
 "regex-automata 0.4.8",
+
 "regex-automata 0.4.9",
 "regex-syntax 0.8.5",
]

@@ -2189,9 +2190,9 @@ dependencies = [

[[package]]
name = "regex-automata"
-
version = "0.4.8"
+
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
 "aho-corasick",
 "memchr",
@@ -2246,14 +2247,14 @@ dependencies = [
 "serde",
 "tempfile",
 "textwrap",
-
 "thiserror",
+
 "thiserror 1.0.69",
]

[[package]]
name = "rsa"
-
version = "0.9.6"
+
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
+
checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
dependencies = [
 "const-oid",
 "digest",
@@ -2272,9 +2273,9 @@ dependencies = [

[[package]]
name = "rss"
-
version = "2.0.9"
+
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "27e92048f840d98c6d6dd870af9101610ea9ff413f11f1bcebf4f4c31d96d957"
+
checksum = "531af70fce504d369cf42ac0a9645f5a62a8ea9265de71cfa25087e9f6080c7c"
dependencies = [
 "atom_syndication",
 "derive_builder",
@@ -2294,9 +2295,9 @@ dependencies = [

[[package]]
name = "rustix"
-
version = "0.38.39"
+
version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
+
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
 "bitflags",
 "errno",
@@ -2348,29 +2349,29 @@ dependencies = [

[[package]]
name = "serde"
-
version = "1.0.214"
+
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
+
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
-
version = "1.0.214"
+
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
+
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
name = "serde_json"
-
version = "1.0.132"
+
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
 "indexmap",
 "itoa",
@@ -2646,7 +2647,7 @@ dependencies = [
 "tempfile",
 "tempfile-fast",
 "tera",
-
 "thiserror",
+
 "thiserror 1.0.69",
 "time",
 "walkdir",
]
@@ -2694,7 +2695,7 @@ dependencies = [
 "culpa",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -2716,9 +2717,9 @@ dependencies = [

[[package]]
name = "syn"
-
version = "2.0.87"
+
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
 "proc-macro2",
 "quote",
@@ -2733,7 +2734,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
@@ -2814,22 +2815,42 @@ dependencies = [

[[package]]
name = "thiserror"
-
version = "1.0.68"
+
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
-
 "thiserror-impl",
+
 "thiserror-impl 1.0.69",
+
]
+

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

[[package]]
name = "thiserror-impl"
-
version = "1.0.68"
+
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
+
]
+

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

[[package]]
@@ -2900,9 +2921,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

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

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

[[package]]
name = "tracing-core"
-
version = "0.1.32"
+
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
 "once_cell",
 "valuable",
@@ -3052,9 +3073,9 @@ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"

[[package]]
name = "unicode-ident"
-
version = "1.0.13"
+
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"

[[package]]
name = "unicode-linebreak"
@@ -3089,9 +3110,9 @@ dependencies = [

[[package]]
name = "url"
-
version = "2.5.3"
+
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
 "form_urlencoded",
 "idna",
@@ -3212,7 +3233,7 @@ dependencies = [
 "once_cell",
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
 "wasm-bindgen-shared",
]

@@ -3234,7 +3255,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
 "wasm-bindgen-backend",
 "wasm-bindgen-shared",
]
@@ -3469,9 +3490,9 @@ dependencies = [

[[package]]
name = "yoke"
-
version = "0.7.4"
+
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
 "serde",
 "stable_deref_trait",
@@ -3481,13 +3502,13 @@ dependencies = [

[[package]]
name = "yoke-derive"
-
version = "0.7.4"
+
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
 "synstructure",
]

@@ -3509,27 +3530,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]

[[package]]
name = "zerofrom"
-
version = "0.1.4"
+
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
 "zerofrom-derive",
]

[[package]]
name = "zerofrom-derive"
-
version = "0.1.4"
+
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
 "synstructure",
]

@@ -3558,5 +3579,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
 "proc-macro2",
 "quote",
-
 "syn 2.0.87",
+
 "syn 2.0.89",
]
modified Cargo.toml
@@ -15,6 +15,7 @@ anyhow = "1.0.86"
clap = { version = "4.5.11", features = ["derive", "wrap_help"] }
duration-str = "0.11.2"
html-page = "0.4.0"
+
radicle-crypto = "0.11.0"
radicle-git-ext = "0.8.0"
radicle-surf = { version = "0.22.0", default-features = false, features = ["serde"] }
regex = "1.10.5"
@@ -34,7 +35,7 @@ uuid = { version = "1.10.0", features = ["v4"] }
valuable = { version = "0.1.0", features = ["derive"] }

[dependencies.radicle]
-
version = "0.13.0"
+
version = "0.14.0"
features = ["default", "test"]

[dev-dependencies]
modified ci-broker.md
@@ -314,6 +314,98 @@ then stdout contains ""id": "xyzzy""
~~~


+
## Runs adapter on each type of event
+

+
_Want:_ CI broker runs the adapter for each type of CI event.
+

+
_Why:_ The adapter needs to handle each type of CI event.
+

+
_Who:_ `cib-devs`
+

+
We verify this by adding CI events to the event queue using `cibtool`
+
and checking that `cib` can process those. This is simpler and more
+
direct than emitting node events that result in the desired CI events.
+
We are here not concerned about whether `cib` handles node events or
+
turns those into the correct CI events: we verify that in other ways.
+

+
We first set things up, including creating a repository `xyzzy`, and a
+
Radicle patch in that repository. The id of the patch is in the file
+
`patch-id.txt` so that it can be used.
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given a directory reports
+
when I run ./env.sh env -C xyzzy git switch -c branchy
+
given file create-patch
+
when I run ./env.sh env -C xyzzy bash ../create-patch ../patch-id.txt
+
~~~
+

+
Verify that `cib` can process a branch creation event.
+

+
~~~
+
when I run rm -f ci-broker.db
+
when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind branch-created --id-file id.txt
+
when I run ./env.sh cib --config broker.yaml queued
+
when I run cibtool --db ci-broker.db run list
+
then stdout has one line
+
~~~
+

+
Verify that `cib` can process a branch update event.
+

+
~~~
+
when I run rm -f ci-broker.db
+
when I run cibtool --db ci-broker.db event add --repo xyzzy --ref brancy --commit HEAD --base main --kind branch-updated --id-file id.txt
+
when I run ./env.sh cib --config broker.yaml queued
+
when I run cibtool --db ci-broker.db run list
+
then stdout has one line
+
~~~
+

+
Verify that `cib` can process a branch deletion event.
+

+
~~~
+
when I run rm -f ci-broker.db
+
when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind branch-deleted --id-file id.txt
+
when I run ./env.sh cib --config broker.yaml queued
+
when I run cibtool --db ci-broker.db run list
+
then stdout has one line
+
~~~
+

+
Verify that `cib` can process a patch creation event.
+

+
~~~
+
when I run rm -f ci-broker.db
+
when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind patch-created --patch-id-file patch-id.txt --id-file id.txt
+
when I run ./env.sh cib --config broker.yaml queued
+
when I run cibtool --db ci-broker.db run list
+
then stdout has one line
+
~~~
+

+
Verify that `cib` can process a patch update event.
+

+
~~~
+
when I run rm -f ci-broker.db
+
when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind patch-updated --patch-id-file patch-id.txt --id-file id.txt
+
when I run ./env.sh cib --config broker.yaml queued
+
when I run cibtool --db ci-broker.db run list
+
then stdout has one line
+
~~~
+

+
~~~{#create-patch .file .sh}
+
#!/bin/bash
+
set -euo pipefail
+

+
touch foo
+
git add foo
+
git commit -m foo
+
EDITOR=/bin/true git push rad HEAD:refs/patches
+

+
rad patch list |
+
	awk 'NR == 4 { print $3 }' |
+
	xargs rad patch show |
+
	awk 'NR == 3 { print $3 }' >"$1"
+
~~~
+

## Reports it version

_Want:_ `cib` and `cibtool` report their version, if invoked with the
@@ -752,7 +844,7 @@ down.
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
given a Git repository testy in the Radicle node

-
when I run ./env.sh cibtool --db ci-broker.db event add --repo testy --ref main --commit HEAD
+
when I run ./env.sh cibtool --db ci-broker.db event add --repo testy --ref main --commit HEAD --kind branch-updated --base main
when I run cibtool --db ci-broker.db event shutdown

given a directory reports
@@ -847,6 +939,7 @@ done

echo OK
~~~
+

# Acceptance criteria for management tool

The `cibtool` management tool can be used to examine and change the CI
@@ -874,7 +967,7 @@ given a Git repository xyzzy in the Radicle node
when I run cibtool --db x.db event list
then stdout is empty

-
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref main --commit HEAD --base HEAD --id-file id.txt
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref main --commit HEAD --base HEAD --id-file id.txt --kind branch-updated

when I run cibtool --db x.db event show --id-file id.txt
then stdout contains "rad:"
@@ -903,12 +996,12 @@ given a Git repository testy in the Radicle node
when I run cibtool --db x.db event list
then stdout is empty

-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD --kind branch-updated

when I run cibtool --db x.db event remove --all
when I run cibtool --db x.db event list
@@ -936,6 +1029,106 @@ when I run cibtool --db x.db event shutdown --id-file id.txt
when I run cibtool --db x.db event show --id-file id.txt
then stdout contains "Shutdown"
~~~
+
## Can add a branch creation event to queue
+

+
_Want:_ `cibtool` can add an event for branch being created to the
+
queued events.
+

+
_Why:_ This is needed for testing.
+

+
_Who:_ `cib-devs`
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+

+
when I run ./env.sh env -C xyzzy git switch -c oksa
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref oksa --commit HEAD --kind branch-created
+

+
when I run cibtool --db x.db event list --json
+
then stdout contains "BranchCreated"
+
~~~
+

+
## Can add a branch update event to queue
+

+
_Want:_ `cibtool` can add an event for branch being updated to the
+
queued events.
+

+
_Why:_ This is needed for testing.
+

+
_Who:_ `cib-devs`
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+

+
when I run ./env.sh env -C xyzzy git switch -c oksa
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref oksa --commit HEAD --kind branch-updated --base HEAD
+

+
when I run cibtool --db x.db event list --json
+
then stdout contains "BranchUpdated"
+
~~~
+

+
## Can add a branch deletion event to queue
+

+
_Want:_ `cibtool` can add an event for branch being deleted to the
+
queued events.
+

+
_Why:_ This is needed for testing.
+

+
_Who:_ `cib-devs`
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+

+
when I run ./env.sh env -C xyzzy git switch -c oksa
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref oksa --commit HEAD --kind branch-deleted
+

+
when I run cibtool --db x.db event list --json
+
then stdout contains "BranchDeleted"
+
~~~
+

+
## Can add a patch creation event to queue
+

+
_Want:_ `cibtool` can add an event for a branch being created to the
+
queued events.
+

+
_Why:_ This is needed for testing.
+

+
_Who:_ `cib-devs`
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+

+
when I run ./env.sh env -C xyzzy git switch -c oksa
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref oksa --commit HEAD --kind patch-created --patch-id f863364f6774160607d90811b06a0e401c097466
+

+
when I run cibtool --db x.db event list --json
+
then stdout contains "PatchCreated"
+
~~~
+

+
## Can add a patch update event to queue
+

+
_Want:_ `cibtool` can add an event for a branch being updated to the
+
queued events.
+

+
_Why:_ This is needed for testing.
+

+
_Who:_ `cib-devs`
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+

+
when I run ./env.sh env -C xyzzy git switch -c oksa
+
when I run ./env.sh cibtool --db x.db event add --repo xyzzy --ref oksa --commit HEAD --kind patch-updated --patch-id f863364f6774160607d90811b06a0e401c097466
+

+
when I run cibtool --db x.db event list --json
+
then stdout contains "PatchUpdated"
+
~~~
+

## Can trigger a CI run

_Want:_ The node operator can easily trigger a CI run without
@@ -1113,11 +1306,11 @@ Note that we verify both lookup by name and by repository ID, and by
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
given a Git repository testy in the Radicle node

-
when I try to run ./env.sh cibtool --db x.db event add --repo missing --ref main --commit HEAD --base c0ffee
+
when I try to run ./env.sh cibtool --db x.db event add --repo missing --ref main --commit HEAD --base c0ffee --kind branch-updated
then command fails
then stderr contains "missing"

-
when I try to run ./env.sh cibtool --db x.db event add --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --base c0ffee
+
when I try to run ./env.sh cibtool --db x.db event add --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --base c0ffee --kind branch-updated
then command fails
then stderr contains "rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB"

modified src/bin/cibtool.rs
@@ -352,8 +352,19 @@ enum CibToolError {
    #[error("failed to read filters from YAML file {0}")]
    ReadFilters(PathBuf, #[source] radicle_ci_broker::filter::FilterError),

-
    #[error("failed to construct a CiEvent::BranchCreated")]
-
    BranchCreted(#[source] CiEventError),
+
    #[error("when adding a branch-create event, the --base option is not allowed")]
+
    NoBaseAllowed,
+

+
    #[error("when adding a branch-update event, the --base option is required")]
+
    BaseRequired,
+

+
    #[error(
+
        "when adding a patch-create or patch--update event, the --patch-id option is required"
+
    )]
+
    PatchIdRequired,
+

+
    #[error("failed to construct a CI event")]
+
    CiEvent(#[source] CiEventError),

    #[error("programming error: failed to set up inter-thread notification channel")]
    Notification(#[source] NotificationError),
modified src/bin/cibtoolcmd/event.rs
@@ -1,7 +1,12 @@
use std::io::Write;

+
use clap::ValueEnum;
+

+
use radicle::patch::PatchId;
#[allow(unused_imports)] // FIXME
-
use radicle_ci_broker::{filter::EventFilter, node_event_source::NodeEventSource};
+
use radicle_ci_broker::{
+
    filter::EventFilter, node_event_source::NodeEventSource, util::read_file_as_objectid,
+
};

use super::*;

@@ -81,11 +86,23 @@ pub struct AddEvent {
    #[clap(long)]
    commit: String,

+
    /// Type of event to create.
+
    #[clap(long)]
+
    kind: EventKind,
+

    /// The base commit referred to by the event. The value is parsed
    /// the same way as `--commit` value.
    #[clap(long)]
    base: Option<String>,

+
    // The patch id to use for a patch event.
+
    #[clap(long)]
+
    patch_id: Option<PatchId>,
+

+
    // Read the patch id to use for a patch event from this file.
+
    #[clap(long)]
+
    patch_id_file: Option<PathBuf>,
+

    /// Write the event to this file, as JSON, instead of adding it to
    /// the queue.
    #[clap(long)]
@@ -167,16 +184,54 @@ impl Leaf for AddEvent {
        let name =
            RefString::try_from(name.clone()).map_err(|e| CibToolError::RefString(name, e))?;

-
        let event = if let Some(base) = &self.base {
-
            let base = if let Ok(base) = Oid::from_str(base) {
-
                base
-
            } else {
-
                self.lookup_commit(rid, base)?
-
            };
-
            CiEvent::branch_updated(nid, rid, &name, oid, base)
-
                .map_err(CibToolError::BranchCreted)?
-
        } else {
-
            CiEvent::branch_created(nid, rid, &name, oid).map_err(CibToolError::BranchCreted)?
+
        let event = match &self.kind {
+
            EventKind::BranchCreated => {
+
                if self.base.is_some() {
+
                    return Err(CibToolError::NoBaseAllowed);
+
                } else {
+
                    CiEvent::branch_created(nid, rid, &name, oid).map_err(CibToolError::CiEvent)?
+
                }
+
            }
+
            EventKind::BranchUpdated => {
+
                if let Some(base) = &self.base {
+
                    let base = if let Ok(base) = Oid::from_str(base) {
+
                        base
+
                    } else {
+
                        self.lookup_commit(rid, base)?
+
                    };
+
                    CiEvent::branch_updated(nid, rid, &name, oid, base)
+
                        .map_err(CibToolError::CiEvent)?
+
                } else {
+
                    return Err(CibToolError::BaseRequired);
+
                }
+
            }
+
            EventKind::BranchDeleted => {
+
                CiEvent::branch_deleted(nid, rid, &name, oid).map_err(CibToolError::CiEvent)?
+
            }
+
            EventKind::PatchCreated => {
+
                if let Some(patch_id) = &self.patch_id {
+
                    CiEvent::patch_created(nid, rid, *patch_id, oid)
+
                        .map_err(CibToolError::CiEvent)?
+
                } else if let Some(filename) = &self.patch_id_file {
+
                    let patch_id = read_file_as_objectid(filename)?;
+
                    CiEvent::patch_created(nid, rid, patch_id, oid)
+
                        .map_err(CibToolError::CiEvent)?
+
                } else {
+
                    return Err(CibToolError::PatchIdRequired);
+
                }
+
            }
+
            EventKind::PatchUpdated => {
+
                if let Some(patch_id) = &self.patch_id {
+
                    CiEvent::patch_updated(nid, rid, *patch_id, oid)
+
                        .map_err(CibToolError::CiEvent)?
+
                } else if let Some(filename) = &self.patch_id_file {
+
                    let patch_id = read_file_as_objectid(filename)?;
+
                    CiEvent::patch_created(nid, rid, patch_id, oid)
+
                        .map_err(CibToolError::CiEvent)?
+
                } else {
+
                    return Err(CibToolError::PatchIdRequired);
+
                }
+
            }
        };

        if let Some(output) = &self.output {
@@ -198,6 +253,15 @@ impl Leaf for AddEvent {
    }
}

+
#[derive(Debug, Clone, ValueEnum)]
+
enum EventKind {
+
    BranchCreated,
+
    BranchUpdated,
+
    BranchDeleted,
+
    PatchCreated,
+
    PatchUpdated,
+
}
+

/// Show an event in the queue.
#[derive(Parser)]
pub struct ShowEvent {
modified src/bin/cibtoolcmd/trigger.rs
@@ -35,8 +35,8 @@ impl Leaf for TriggerCmd {
        let name = format!("refs/namespaces/{nid}/refs/heads/{}", self.name.as_str());
        let name =
            RefString::try_from(name.clone()).map_err(|e| CibToolError::RefString(name, e))?;
-
        let event = CiEvent::branch_updated(nid, rid, &name, oid, base)
-
            .map_err(CibToolError::BranchCreted)?;
+
        let event =
+
            CiEvent::branch_updated(nid, rid, &name, oid, base).map_err(CibToolError::CiEvent)?;

        let db = args.open_db()?;
        let id = db.push_queued_ci_event(event)?;
modified src/ci_event.rs
@@ -7,12 +7,15 @@ use radicle_git_ext::Oid;

use radicle::{
    cob::patch::PatchId,
+
    crypto::PublicKey,
    git::RefString,
    node::{Event, NodeId},
    prelude::RepoId,
    storage::RefUpdate,
};

+
use crate::logger;
+

#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum CiEvent {
@@ -58,54 +61,82 @@ pub enum CiEventV1 {

impl CiEvent {
    pub fn branch_created(
-
        node: NodeId,
+
        from_node: NodeId,
        repo: RepoId,
        branch: &str,
        tip: Oid,
    ) -> Result<Self, CiEventError> {
        Ok(Self::V1(CiEventV1::BranchCreated {
-
            from_node: node,
+
            from_node,
            repo,
-
            branch: RefString::try_from(branch)
-
                .map_err(|e| CiEventError::RefString(branch.into(), e))?,
+
            branch: ref_string(branch)?,
            tip,
        }))
    }

    pub fn branch_updated(
-
        node: NodeId,
+
        from_node: NodeId,
        repo: RepoId,
        branch: &str,
        tip: Oid,
        old_tip: Oid,
    ) -> Result<Self, CiEventError> {
-
        let branch =
-
            namespaced_branch(branch).map_err(|_| CiEventError::without_namespace2(branch))?;
        Ok(Self::V1(CiEventV1::BranchUpdated {
-
            from_node: node,
+
            from_node,
            repo,
-
            branch: RefString::try_from(branch.clone())
-
                .map_err(|e| CiEventError::RefString(branch.clone(), e))?,
+
            branch: branch_from_namspeced_ref(branch)?,
            tip,
            old_tip,
        }))
    }

-
    pub fn from_node_event(event: &Event) -> Result<Vec<Self>, CiEventError> {
-
        fn ref_string(s: String) -> Result<RefString, CiEventError> {
-
            RefString::try_from(s.clone()).map_err(|e| CiEventError::ref_string(s, e))
-
        }
+
    pub fn branch_deleted(
+
        from_node: NodeId,
+
        repo: RepoId,
+
        branch: &str,
+
        tip: Oid,
+
    ) -> Result<Self, CiEventError> {
+
        Ok(Self::V1(CiEventV1::BranchDeleted {
+
            from_node,
+
            repo,
+
            branch: branch_from_namspeced_ref(branch)?,
+
            tip,
+
        }))
+
    }

-
        fn branch(ref_name: &str, update: &RefUpdate) -> Result<RefString, CiEventError> {
-
            ref_string(
-
                namespaced_branch(ref_name)
-
                    .map_err(|_| CiEventError::without_namespace(ref_name, update.clone()))?,
-
            )
-
        }
+
    pub fn patch_created(
+
        from_node: NodeId,
+
        repo: RepoId,
+
        patch: PatchId,
+
        tip: Oid,
+
    ) -> Result<Self, CiEventError> {
+
        Ok(Self::V1(CiEventV1::PatchCreated {
+
            from_node,
+
            repo,
+
            patch,
+
            new_tip: tip,
+
        }))
+
    }

+
    pub fn patch_updated(
+
        from_node: NodeId,
+
        repo: RepoId,
+
        patch: PatchId,
+
        new_tip: Oid,
+
    ) -> Result<Self, CiEventError> {
+
        Ok(Self::V1(CiEventV1::PatchUpdated {
+
            from_node,
+
            repo,
+
            patch,
+
            new_tip,
+
        }))
+
    }
+

+
    #[allow(clippy::unwrap_used)]
+
    pub fn from_node_event(event: &Event) -> Result<Vec<Self>, CiEventError> {
        match event {
            Event::RefsFetched {
-
                remote,
+
                remote: _,
                rid,
                updated,
            } => {
@@ -113,53 +144,32 @@ impl CiEvent {
                for update in updated {
                    let e = match update {
                        RefUpdate::Created { name, oid } => {
+
                            let origin = originator(name.to_ref_string())?;
                            if let Ok(patch_id) = patch_id(name) {
-
                                CiEventV1::PatchCreated {
-
                                    from_node: *remote,
-
                                    repo: *rid,
-
                                    patch: patch_id,
-
                                    new_tip: *oid,
-
                                }
+
                                Self::patch_created(origin, *rid, patch_id, *oid)?
                            } else if let Ok(branch) = namespaced_branch(name) {
-
                                CiEventV1::BranchCreated {
-
                                    from_node: *remote,
-
                                    repo: *rid,
-
                                    branch: ref_string(branch)?,
-
                                    tip: *oid,
-
                                }
+
                                Self::branch_created(origin, *rid, &branch, *oid)?
                            } else {
                                continue;
                            }
                        }
                        RefUpdate::Updated { name, old, new } => {
+
                            let origin = originator(name.to_ref_string())?;
                            if let Ok(patch_id) = patch_id(name) {
-
                                CiEventV1::PatchUpdated {
-
                                    from_node: *remote,
-
                                    repo: *rid,
-
                                    patch: patch_id,
-
                                    new_tip: *new,
-
                                }
-
                            } else if let Ok(branch) = namespaced_branch(name) {
-
                                CiEventV1::BranchUpdated {
-
                                    from_node: *remote,
-
                                    repo: *rid,
-
                                    branch: ref_string(branch)?,
-
                                    tip: *new,
-
                                    old_tip: *old,
-
                                }
+
                                Self::patch_updated(origin, *rid, patch_id, *new)?
+
                            } else if namespaced_branch(name).is_ok() {
+
                                Self::branch_updated(origin, *rid, name, *new, *old)?
                            } else {
                                continue;
                            }
                        }
-
                        RefUpdate::Deleted { name, oid } => CiEventV1::BranchDeleted {
-
                            from_node: *remote,
-
                            repo: *rid,
-
                            branch: branch(name, update)?,
-
                            tip: *oid,
-
                        },
+
                        RefUpdate::Deleted { name, oid } => {
+
                            let origin = originator(name.to_ref_string())?;
+
                            Self::branch_deleted(origin, *rid, name, *oid)?
+
                        }
                        RefUpdate::Skipped { .. } => continue,
                    };
-
                    events.push(CiEvent::V1(e));
+
                    events.push(e);
                }
                Ok(events)
            }
@@ -177,6 +187,27 @@ impl CiEvent {
    }
}

+
fn originator(name: RefString) -> Result<PublicKey, CiEventError> {
+
    logger::trace2(format!("originator: name={name:?}"));
+
    if let Some(qualified) = name.clone().into_qualified() {
+
        logger::trace2(format!("originator: qualified={qualified:?}"));
+
        if let Some(namespaced) = qualified.to_namespaced() {
+
            logger::trace2(format!("originator: namespaced={namespaced:?}"));
+
            return PublicKey::from_namespaced(&namespaced)
+
                .map_err(|err| CiEventError::key_from_namespaced(&name, err));
+
        }
+
    }
+
    Err(CiEventError::without_namespace2(name.as_str()))
+
}
+

+
fn ref_string(s: &str) -> Result<RefString, CiEventError> {
+
    RefString::try_from(s).map_err(|e| CiEventError::ref_string(s, e))
+
}
+

+
fn branch_from_namspeced_ref(branch: &str) -> Result<RefString, CiEventError> {
+
    ref_string(&namespaced_branch(branch).map_err(|_| CiEventError::without_namespace2(branch))?)
+
}
+

pub struct CiEvents {
    events: Vec<CiEvent>,
}
@@ -198,9 +229,6 @@ impl CiEvents {

#[derive(Debug, thiserror::Error)]
pub enum CiEventError {
-
    #[error("updated ref name has no name space: {0:?}): from {1:#?}")]
-
    WithoutNamespace(String, RefUpdate),
-

    #[error("updated ref name has no name space: {0:?})")]
    WithoutNamespace2(String),

@@ -215,19 +243,18 @@ pub enum CiEventError {

    #[error("broker events file is not valid JSON: {0}")]
    NotJson(PathBuf, #[source] serde_json::Error),
+

+
    #[error("failed to convert name spaced Git ref into node public key: {0}")]
+
    KeyFromNamespaced(RefString, #[source] radicle_crypto::PublicKeyError),
}

impl CiEventError {
-
    fn without_namespace(refname: &str, update: RefUpdate) -> Self {
-
        Self::WithoutNamespace(refname.into(), update)
-
    }
-

    fn without_namespace2(refname: &str) -> Self {
        Self::WithoutNamespace2(refname.into())
    }

-
    fn ref_string(name: String, err: radicle::git::fmt::Error) -> Self {
-
        Self::RefString(name, err)
+
    fn ref_string(name: &str, err: radicle::git::fmt::Error) -> Self {
+
        Self::RefString(name.into(), err)
    }

    fn read_file(filename: &Path, err: std::io::Error) -> Self {
@@ -241,6 +268,10 @@ impl CiEventError {
    fn not_json(filename: &Path, err: serde_json::Error) -> Self {
        Self::NotJson(filename.into(), err)
    }
+

+
    fn key_from_namespaced(name: &RefString, err: radicle_crypto::PublicKeyError) -> Self {
+
        Self::KeyFromNamespaced(name.to_ref_string(), err)
+
    }
}

#[cfg(test)]
@@ -250,11 +281,12 @@ mod test {
    use radicle::{prelude::NodeId, storage::RefUpdate};
    use std::str::FromStr;

-
    const MAIN_BRANCH_REF_NAME: &str = "refs/namespaces/NID/refs/heads/main";
+
    const MAIN_BRANCH_REF_NAME: &str =
+
        "refs/namespaces/z6MkiB8T5cBEQHnrs2MgjMVqvpSVj42X81HjKfFi2XBoMbtr/refs/heads/main";
    const MAIN_BRANCH_NAME: &str = "main";

    const PATCH_REF_NAME: &str =
-
        "refs/namespaces/NID/refs/heads/patches/f9fa90725474de9002be503ae3cda4670c9a174";
+
        "refs/namespaces/z6MkiB8T5cBEQHnrs2MgjMVqvpSVj42X81HjKfFi2XBoMbtr/refs/heads/patches/f9fa90725474de9002be503ae3cda4670c9a174";
    const PATCH_ID: &str = "f9fa90725474de9002be503ae3cda4670c9a174";

    fn nid() -> NodeId {
modified src/logger.rs
@@ -175,7 +175,7 @@ pub fn ci_event_source_eof(source: &CiEventSource) {
}

pub fn loaded_config(config: &Config) {
-
    debug!(kind = %Kind::Debug, "loaded configuration {config:#?}");
+
    debug!(kind = %Kind::Debug, config=format!("{config:#?}"), "loaded configuration");
}

pub fn adapter_config(config: &Config) {
@@ -478,6 +478,14 @@ pub fn debug2(msg: String) {
    debug!(kind = %Kind::Debug, "{msg}");
}

+
pub fn trace(msg: &str) {
+
    trace!(kind = %Kind::Debug, "{msg}");
+
}
+

+
pub fn trace2(msg: String) {
+
    trace!(kind = %Kind::Debug, "{msg}");
+
}
+

pub fn error(msg: &str, e: &impl std::error::Error) {
    error!("{msg}: {e}");
    let mut e = e.source();
modified src/msg.rs
@@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use radicle::{
+
    cob::patch::PatchId,
    git::Oid,
    prelude::{NodeId, RepoId},
};
@@ -31,7 +32,10 @@ use radicle::{
    Profile,
};

-
use crate::ci_event::{CiEvent, CiEventV1};
+
use crate::{
+
    ci_event::{CiEvent, CiEventV1},
+
    logger,
+
};

// This gets put into every [`Request`] message so the adapter can
// detect its getting a message it knows how to handle.
@@ -128,6 +132,155 @@ impl<'a> RequestBuilder<'a> {

    /// Create a [`Request::Trigger``] message from a [`crate::ci_event::Civet`].
    pub fn build_trigger_from_ci_event(self) -> Result<Request, MessageError> {
+
        fn repository(repo: &RepoId, profile: &Profile) -> Result<Repository, MessageError> {
+
            logger::trace2(format!("build trigger: look up repository {repo}"));
+
            let rad_repo = match profile.storage.repository(*repo) {
+
                Err(err) => {
+
                    logger::trace2(format!("build trigger: repo lookup result {err:?}"));
+
                    return Err(err)?;
+
                }
+
                Ok(rad_repo) => rad_repo,
+
            };
+

+
            logger::trace("build trigger: look up project");
+
            let project_info = match rad_repo.project() {
+
                Err(err) => {
+
                    logger::trace2(format!("build trigger: project lookup result {err:?}"));
+
                    return Err(err)?;
+
                }
+
                Ok(x) => x,
+
            };
+

+
            Ok(Repository {
+
                id: *repo,
+
                name: project_info.name().to_string(),
+
                description: project_info.description().to_string(),
+
                private: !rad_repo.identity()?.visibility().is_public(),
+
                default_branch: project_info.default_branch().to_string(),
+
                delegates: rad_repo.delegates()?.iter().copied().collect(),
+
            })
+
        }
+

+
        fn common_fields(
+
            event_type: EventType,
+
            repo: &RepoId,
+
            profile: &Profile,
+
        ) -> Result<EventCommonFields, MessageError> {
+
            logger::trace2(format!("build trigger: create common fields for {repo}"));
+
            logger::trace("build trigger: look up repository");
+
            let repository = match repository(repo, profile) {
+
                Err(err) => {
+
                    logger::trace2(format!("build trigger: project lookup result {err:?}"));
+
                    return Err(err)?;
+
                }
+
                Ok(x) => x,
+
            };
+
            Ok(EventCommonFields {
+
                version: PROTOCOL_VERSION,
+
                event_type,
+
                repository,
+
            })
+
        }
+

+
        fn author(node: &NodeId, profile: &Profile) -> Result<Author, MessageError> {
+
            logger::trace2(format!("build trigger: look up author {node}"));
+
            let did = Did::from(*node);
+
            let x = did_to_author(profile, &did);
+
            logger::trace2(format!("build trigger: author lookup result {x:?}"));
+
            x
+
        }
+

+
        fn commits(
+
            git_repo: &radicle_surf::Repository,
+
            tip: Oid,
+
            base: Oid,
+
        ) -> Result<Vec<Oid>, radicle_surf::Error> {
+
            logger::trace2(format!("build trigger: look commits {git_repo:?}"));
+
            let x = git_repo
+
                .history(tip)?
+
                .take_while(|c| if let Ok(c) = c { c.id != base } else { false })
+
                .map(|r| r.map(|c| c.id))
+
                .collect::<Result<Vec<Oid>, _>>();
+
            logger::trace2(format!("build trigger: revision lookup result {x:?}"));
+
            x
+
        }
+

+
        fn patch_cob(
+
            rad_repo: &radicle::storage::git::Repository,
+
            patch_id: &PatchId,
+
        ) -> Result<radicle::cob::patch::Patch, MessageError> {
+
            logger::trace2(format!("build trigger: look patch cob {patch_id}"));
+
            let x = match patch::Patches::open(rad_repo) {
+
                Err(err) => {
+
                    logger::trace2(format!("patch repo open => {err:?}"));
+
                    return Err(err)?;
+
                }
+
                Ok(x) => x,
+
            };
+

+
            let x = match x.get(patch_id) {
+
                Err(err) => {
+
                    logger::trace2(format!("get patch => {err:?}"));
+
                    return Err(err)?;
+
                }
+
                Ok(x) => x,
+
            };
+

+
            let x = match x {
+
                None => {
+
                    logger::trace("did not find patch COB from repository");
+
                    return Err(MessageError::PatchCob(*patch_id));
+
                }
+
                Some(x) => x,
+
            };
+

+
            logger::trace2(format!("build trigger: patch cob lookup result {x:?}"));
+
            Ok(x)
+
        }
+

+
        fn revisions(
+
            patch_cob: &radicle::cob::patch::Patch,
+
            author: &Author,
+
        ) -> Result<Vec<Revision>, MessageError> {
+
            logger::trace2(format!("build trigger: look patch revisions by {author:?}"));
+
            let x = patch_cob
+
                .revisions()
+
                .map(|(rid, r)| {
+
                    Ok::<Revision, MessageError>(Revision {
+
                        id: rid.into(),
+
                        author: author.clone(),
+
                        description: r.description().to_string(),
+
                        base: *r.base(),
+
                        oid: r.head(),
+
                        timestamp: r.timestamp().as_secs(),
+
                    })
+
                })
+
                .collect::<Result<Vec<Revision>, MessageError>>();
+
            logger::trace2(format!("build trigger: revision lookup result {x:?}"));
+
            x
+
        }
+

+
        fn patch_base(
+
            patch_cob: &radicle::cob::patch::Patch,
+
            patch_id: &PatchId,
+
            author: &Author,
+
        ) -> Result<Oid, MessageError> {
+
            logger::trace2(format!(
+
                "build trigger: look base commit for patch {patch_id}"
+
            ));
+
            let author_pk = radicle::crypto::PublicKey::from(author.id);
+
            let (_id, revision) = match patch_cob.latest_by(&author_pk) {
+
                None => {
+
                    logger::trace("build trigger: patch base lookup failed: nothing found");
+
                    return Err(MessageError::LatestPatchRevision(*patch_id));
+
                }
+
                Some(x) => x,
+
            };
+
            let base = *revision.base();
+
            logger::trace2(format!("patch base commit is {base}"));
+
            Ok(base)
+
        }
+

        let profile = self.profile.ok_or(MessageError::NoProfile)?;

        match self.ci_event {
@@ -138,35 +291,16 @@ impl<'a> RequestBuilder<'a> {
                branch,
                tip,
            })) => {
-
                let rad_repo = profile.storage.repository(*repo)?;
-
                let project_info = rad_repo.project()?;
-

-
                let common = EventCommonFields {
-
                    version: PROTOCOL_VERSION,
-
                    event_type: EventType::Push,
-
                    repository: Repository {
-
                        id: *repo,
-
                        name: project_info.name().to_string(),
-
                        description: project_info.description().to_string(),
-
                        private: !rad_repo.identity()?.visibility.is_public(),
-
                        default_branch: project_info.default_branch().to_string(),
-
                        delegates: rad_repo.delegates()?.iter().copied().collect(),
-
                    },
-
                };
-

-
                let did = Did::from(*from_node);
-
                let pusher = did_to_author(profile, &did)?;
-

-
                let push = PushEvent {
-
                    pusher,
-
                    before: *tip, // Branch created: we only use the tip
-
                    after: *tip,
-
                    branch: branch.as_str().to_string(),
-
                    commits: vec![*tip], // Branch created, only use tip.
-
                };
+
                logger::trace("build trigger: branch created");
                Ok(Request::Trigger {
-
                    common,
-
                    push: Some(push),
+
                    common: common_fields(EventType::Push, repo, profile)?,
+
                    push: Some(PushEvent {
+
                        pusher: author(from_node, profile)?,
+
                        before: *tip, // Branch created: we only use the tip
+
                        after: *tip,
+
                        branch: branch.as_str().to_string(),
+
                        commits: vec![*tip], // Branch created, only use tip.
+
                    }),
                    patch: None,
                })
            }
@@ -177,53 +311,42 @@ impl<'a> RequestBuilder<'a> {
                tip,
                old_tip,
            })) => {
-
                let rad_repo = profile.storage.repository(*repo)?;
+
                logger::trace("build trigger: branch updated");
                let git_repo =
                    radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
-
                let project_info = rad_repo.project()?;
-

-
                let common = EventCommonFields {
-
                    version: PROTOCOL_VERSION,
-
                    event_type: EventType::Push,
-
                    repository: Repository {
-
                        id: *repo,
-
                        name: project_info.name().to_string(),
-
                        description: project_info.description().to_string(),
-
                        private: !rad_repo.identity()?.visibility.is_public(),
-
                        default_branch: project_info.default_branch().to_string(),
-
                        delegates: rad_repo.delegates()?.iter().copied().collect(),
-
                    },
-
                };
-

-
                let did = Did::from(*from_node);
-
                let pusher = did_to_author(profile, &did)?;
-

-
                let mut commits: Vec<Oid> = git_repo
-
                    .history(tip)?
-
                    .take_while(|c| {
-
                        if let Ok(c) = c {
-
                            c.id != *old_tip
-
                        } else {
-
                            false
-
                        }
-
                    })
-
                    .map(|r| r.map(|c| c.id))
-
                    .collect::<Result<Vec<Oid>, _>>()?;
+
                let mut commits = commits(&git_repo, *tip, *old_tip)?;
                if commits.is_empty() {
                    commits = vec![*old_tip];
                }

-
                let push = PushEvent {
-
                    pusher,
-
                    before: *tip, // Branch created: we only use the tip
-
                    after: *tip,
-
                    branch: branch.as_str().to_string(),
-
                    commits,
-
                };
-

                Ok(Request::Trigger {
-
                    common,
-
                    push: Some(push),
+
                    common: common_fields(EventType::Push, repo, profile)?,
+
                    push: Some(PushEvent {
+
                        pusher: author(from_node, profile)?,
+
                        before: *tip, // Branch created: we only use the tip
+
                        after: *tip,
+
                        branch: branch.as_str().to_string(),
+
                        commits,
+
                    }),
+
                    patch: None,
+
                })
+
            }
+
            Some(CiEvent::V1(CiEventV1::BranchDeleted {
+
                from_node,
+
                repo,
+
                branch,
+
                tip,
+
            })) => {
+
                logger::trace("build trigger: branch deleted");
+
                Ok(Request::Trigger {
+
                    common: common_fields(EventType::Push, repo, profile)?,
+
                    push: Some(PushEvent {
+
                        pusher: author(from_node, profile)?,
+
                        before: *tip, // Branch created: we only use the tip
+
                        after: *tip,
+
                        branch: branch.as_str().to_string(),
+
                        commits: vec![*tip],
+
                    }),
                    patch: None,
                })
            }
@@ -233,87 +356,40 @@ impl<'a> RequestBuilder<'a> {
                patch: patch_id,
                new_tip,
            })) => {
+
                logger::trace("build trigger: patch created");
                let rad_repo = profile.storage.repository(*repo)?;
                let git_repo =
                    radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
-
                let project_info = rad_repo.project()?;
-

-
                let common = EventCommonFields {
-
                    version: PROTOCOL_VERSION,
-
                    event_type: EventType::Patch,
-
                    repository: Repository {
-
                        id: *repo,
-
                        name: project_info.name().to_string(),
-
                        description: project_info.description().to_string(),
-
                        private: !rad_repo.identity()?.visibility.is_public(),
-
                        default_branch: project_info.default_branch().to_string(),
-
                        delegates: rad_repo.delegates()?.iter().copied().collect(),
-
                    },
-
                };
-

-
                let did = Did::from(*from_node);
-
                let author = did_to_author(profile, &did)?;
-

-
                let patch_cob = patch::Patches::open(&rad_repo)?
-
                    .get(patch_id)?
-
                    .ok_or(MessageError::Trigger)?;
-

-
                let revisions: Vec<Revision> = patch_cob
-
                    .revisions()
-
                    .map(|(rid, r)| {
-
                        Ok::<Revision, MessageError>(Revision {
-
                            id: rid.into(),
-
                            author: did_to_author(profile, r.author().id())?,
-
                            description: r.description().to_string(),
-
                            base: *r.base(),
-
                            oid: r.head(),
-
                            timestamp: r.timestamp().as_secs(),
-
                        })
-
                    })
-
                    .collect::<Result<Vec<Revision>, MessageError>>()?;
-
                let patch_author_pk = radicle::crypto::PublicKey::from(author.id);
-
                let patch_latest_revision = patch_cob
-
                    .latest_by(&patch_author_pk)
-
                    .ok_or(MessageError::Trigger)?;
-
                let patch_base = patch_latest_revision.1.base();
-
                let commits: Vec<Oid> = git_repo
-
                    .history(*new_tip)?
-
                    .take_while(|c| {
-
                        if let Ok(c) = c {
-
                            c.id != *patch_base
-
                        } else {
-
                            false
-
                        }
-
                    })
-
                    .map(|r| r.map(|c| c.id))
-
                    .collect::<Result<Vec<Oid>, _>>()?;
-

-
                let patch = Patch {
-
                    id: **patch_id,
-
                    author,
-
                    title: patch_cob.title().to_string(),
-
                    state: State {
-
                        status: patch_cob.state().to_string(),
-
                        conflicts: match patch_cob.state() {
-
                            patch::State::Open { conflicts, .. } => conflicts.to_vec(),
-
                            _ => vec![],
-
                        },
-
                    },
-
                    before: *patch_base,
-
                    after: *new_tip,
-
                    commits,
-
                    target: patch_cob.target().head(&rad_repo)?,
-
                    labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
-
                    assignees: patch_cob.assignees().collect(),
-
                    revisions,
-
                };
+
                let author = author(from_node, profile)?;
+
                let patch_cob = patch_cob(&rad_repo, patch_id)?;
+
                let revisions = revisions(&patch_cob, &author)?;
+
                let patch_base = patch_base(&patch_cob, patch_id, &author)?;
+
                let commits = commits(&git_repo, *new_tip, patch_base)?;

                Ok(Request::Trigger {
-
                    common,
+
                    common: common_fields(EventType::Patch, repo, profile)?,
                    push: None,
                    patch: Some(PatchEvent {
                        action: PatchAction::Created,
-
                        patch,
+
                        patch: Patch {
+
                            id: **patch_id,
+
                            author,
+
                            title: patch_cob.title().to_string(),
+
                            state: State {
+
                                status: patch_cob.state().to_string(),
+
                                conflicts: match patch_cob.state() {
+
                                    patch::State::Open { conflicts, .. } => conflicts.to_vec(),
+
                                    _ => vec![],
+
                                },
+
                            },
+
                            before: patch_base,
+
                            after: *new_tip,
+
                            commits,
+
                            target: patch_cob.target().head(&rad_repo)?,
+
                            labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
+
                            assignees: patch_cob.assignees().collect(),
+
                            revisions,
+
                        },
                    }),
                })
            }
@@ -323,87 +399,40 @@ impl<'a> RequestBuilder<'a> {
                patch: patch_id,
                new_tip,
            })) => {
+
                logger::trace("build trigger: patch updated");
                let rad_repo = profile.storage.repository(*repo)?;
                let git_repo =
                    radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
-
                let project_info = rad_repo.project()?;
-

-
                let common = EventCommonFields {
-
                    version: PROTOCOL_VERSION,
-
                    event_type: EventType::Patch,
-
                    repository: Repository {
-
                        id: *repo,
-
                        name: project_info.name().to_string(),
-
                        description: project_info.description().to_string(),
-
                        private: !rad_repo.identity()?.visibility.is_public(),
-
                        default_branch: project_info.default_branch().to_string(),
-
                        delegates: rad_repo.delegates()?.iter().copied().collect(),
-
                    },
-
                };
-

-
                let did = Did::from(*from_node);
-
                let author = did_to_author(profile, &did)?;
-

-
                let patch_cob = patch::Patches::open(&rad_repo)?
-
                    .get(patch_id)?
-
                    .ok_or(MessageError::Trigger)?;
-

-
                let revisions: Vec<Revision> = patch_cob
-
                    .revisions()
-
                    .map(|(rid, r)| {
-
                        Ok::<Revision, MessageError>(Revision {
-
                            id: rid.into(),
-
                            author: did_to_author(profile, r.author().id())?,
-
                            description: r.description().to_string(),
-
                            base: *r.base(),
-
                            oid: r.head(),
-
                            timestamp: r.timestamp().as_secs(),
-
                        })
-
                    })
-
                    .collect::<Result<Vec<Revision>, MessageError>>()?;
-
                let patch_author_pk = radicle::crypto::PublicKey::from(author.id);
-
                let patch_latest_revision = patch_cob
-
                    .latest_by(&patch_author_pk)
-
                    .ok_or(MessageError::Trigger)?;
-
                let patch_base = patch_latest_revision.1.base();
-
                let commits: Vec<Oid> = git_repo
-
                    .history(*new_tip)?
-
                    .take_while(|c| {
-
                        if let Ok(c) = c {
-
                            c.id != *patch_base
-
                        } else {
-
                            false
-
                        }
-
                    })
-
                    .map(|r| r.map(|c| c.id))
-
                    .collect::<Result<Vec<Oid>, _>>()?;
-

-
                let patch = Patch {
-
                    id: **patch_id,
-
                    author,
-
                    title: patch_cob.title().to_string(),
-
                    state: State {
-
                        status: patch_cob.state().to_string(),
-
                        conflicts: match patch_cob.state() {
-
                            patch::State::Open { conflicts, .. } => conflicts.to_vec(),
-
                            _ => vec![],
-
                        },
-
                    },
-
                    before: *patch_base,
-
                    after: *new_tip,
-
                    commits,
-
                    target: patch_cob.target().head(&rad_repo)?,
-
                    labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
-
                    assignees: patch_cob.assignees().collect(),
-
                    revisions,
-
                };
+
                let author = author(from_node, profile)?;
+
                let patch_cob = patch_cob(&rad_repo, patch_id)?;
+
                let revisions = revisions(&patch_cob, &author)?;
+
                let patch_base = patch_base(&patch_cob, patch_id, &author)?;
+
                let commits = commits(&git_repo, *new_tip, patch_base)?;

                Ok(Request::Trigger {
-
                    common,
+
                    common: common_fields(EventType::Patch, repo, profile)?,
                    push: None,
                    patch: Some(PatchEvent {
                        action: PatchAction::Updated,
-
                        patch,
+
                        patch: Patch {
+
                            id: **patch_id,
+
                            author,
+
                            title: patch_cob.title().to_string(),
+
                            state: State {
+
                                status: patch_cob.state().to_string(),
+
                                conflicts: match patch_cob.state() {
+
                                    patch::State::Open { conflicts, .. } => conflicts.to_vec(),
+
                                    _ => vec![],
+
                                },
+
                            },
+
                            before: patch_base,
+
                            after: *new_tip,
+
                            commits,
+
                            target: patch_cob.target().head(&rad_repo)?,
+
                            labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
+
                            assignees: patch_cob.assignees().collect(),
+
                            revisions,
+
                        },
                    }),
                })
            }
@@ -896,6 +925,14 @@ pub enum MessageError {
    #[error("could not generate trigger from event")]
    Trigger,

+
    /// Error looking up the patch COB.
+
    #[error("could look up patch COB {0}: not found?")]
+
    PatchCob(PatchId),
+

+
    /// Error looking up latest revision for a patch COB.
+
    #[error("failed to look up latest revision for patch {0}")]
+
    LatestPatchRevision(PatchId),
+

    /// Error from Radicle storage.
    #[error(transparent)]
    StorageError(#[from] radicle::storage::Error),
modified src/pages.rs
@@ -810,7 +810,7 @@ impl StatusPage {
    fn is_public_repo(profile: &Profile, rid: &RepoId) -> bool {
        if let Ok(repo) = profile.storage.repository(*rid) {
            if let Ok(id_doc) = repo.canonical_identity_doc() {
-
                if id_doc.doc.visibility.is_public() {
+
                if id_doc.doc.visibility().is_public() {
                    return true;
                }
            }
modified src/queueproc.rs
@@ -116,7 +116,8 @@ impl QueueProcessor {
    }

    fn process_event(&mut self, event: &CiEvent) -> Result<bool, QueueError> {
-
        match event {
+
        logger::debug2(format!("queproc::process_event: called; event={event:#?}"));
+
        let x = match event {
            CiEvent::V1(CiEventV1::Shutdown) => {
                logger::queueproc_action_shutdown();
                Ok(true)
@@ -156,8 +157,64 @@ impl QueueProcessor {
                    .map_err(QueueError::execute_ci)?;
                Ok(false)
            }
-
            _ => unimplemented!("unknown CI event {event:#?}"),
-
        }
+
            CiEvent::V1(CiEventV1::BranchDeleted {
+
                from_node: _,
+
                repo,
+
                branch: _,
+
                tip,
+
            }) => {
+
                logger::queueproc_action_run(repo, tip);
+
                let trigger = RequestBuilder::default()
+
                    .profile(&self.profile)
+
                    .ci_event(event)
+
                    .build_trigger_from_ci_event()
+
                    .map_err(|e| QueueError::build_trigger(event, e))?;
+
                self.broker
+
                    .execute_ci(&trigger, &self.run_tx)
+
                    .map_err(QueueError::execute_ci)?;
+
                Ok(false)
+
            }
+
            CiEvent::V1(CiEventV1::PatchCreated {
+
                from_node: _,
+
                repo,
+
                patch: _,
+
                new_tip,
+
            }) => {
+
                logger::queueproc_action_run(repo, new_tip);
+
                let trigger = RequestBuilder::default()
+
                    .profile(&self.profile)
+
                    .ci_event(event)
+
                    .build_trigger_from_ci_event()
+
                    .map_err(|e| QueueError::build_trigger(event, e))?;
+
                self.broker
+
                    .execute_ci(&trigger, &self.run_tx)
+
                    .map_err(QueueError::execute_ci)?;
+
                Ok(false)
+
            }
+
            CiEvent::V1(CiEventV1::PatchUpdated {
+
                from_node: _,
+
                repo,
+
                patch: _,
+
                new_tip,
+
            }) => {
+
                logger::debug("patch updated");
+
                logger::queueproc_action_run(repo, new_tip);
+
                let trigger = RequestBuilder::default()
+
                    .profile(&self.profile)
+
                    .ci_event(event)
+
                    .build_trigger_from_ci_event()
+
                    .map_err(|e| QueueError::build_trigger(event, e));
+
                logger::debug2(format!("got trigger {trigger:?}"));
+
                let trigger = trigger?;
+
                self.broker
+
                    .execute_ci(&trigger, &self.run_tx)
+
                    .map_err(QueueError::execute_ci)?;
+
                logger::debug("executed ci");
+
                Ok(false)
+
            }
+
        };
+
        logger::debug("queproc::process_event: end");
+
        x
    }

    fn drop_event(&mut self, id: &QueueId) -> Result<(), QueueError> {
modified src/util.rs
@@ -1,4 +1,7 @@
-
use std::str::FromStr;
+
use std::{
+
    path::{Path, PathBuf},
+
    str::FromStr,
+
};

use time::{
    format_description::{well_known::Rfc2822, FormatItem},
@@ -8,6 +11,7 @@ use time::{
};

use radicle::{
+
    cob::ObjectId,
    prelude::{NodeId, RepoId},
    storage::ReadStorage,
    Profile, Storage,
@@ -122,6 +126,18 @@ pub fn rfc822_timestamp(ts: OffsetDateTime) -> Result<String, UtilError> {
    Ok(ts.to_string())
}

+
pub fn read_file_as_string(filename: &Path) -> Result<String, UtilError> {
+
    String::from_utf8(
+
        std::fs::read(filename).map_err(|err| UtilError::Readfile(filename.into(), err))?,
+
    )
+
    .map_err(|err| UtilError::Utf8(filename.into(), err))
+
}
+

+
pub fn read_file_as_objectid(filename: &Path) -> Result<ObjectId, UtilError> {
+
    let s = read_file_as_string(filename)?;
+
    ObjectId::from_str(s.trim()).map_err(|err| UtilError::ReadObjectId(filename.into(), err))
+
}
+

#[derive(Debug, thiserror::Error)]
pub enum UtilError {
    #[error("failed to look up node profile")]
@@ -153,4 +169,13 @@ pub enum UtilError {

    #[error("failed to parse timestamp {0:?}")]
    TimestampParse(String),
+

+
    #[error("failed to read file {0}")]
+
    Readfile(PathBuf, #[source] std::io::Error),
+

+
    #[error("failed to convert file to UTF8: {0}")]
+
    Utf8(PathBuf, #[source] std::string::FromUtf8Error),
+

+
    #[error("failed to read object id from {0}")]
+
    ReadObjectId(PathBuf, #[source] radicle::cob::object::ParseObjectId),
}