├── .cargo └── config ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── demo.png └── src ├── context.rs ├── id.rs ├── lib.rs ├── main.rs ├── patch.rs ├── state ├── commit.rs ├── graph.rs ├── id_lookups.rs ├── load_patch.rs ├── merge.rs ├── mod.rs ├── process_patch.rs └── total_state.rs └── version.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "uuid_unstable", "--cfg", "feature=\"tinyvec_alloc\""] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | patches/ 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "1.0.2" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 21 | dependencies = [ 22 | "memchr", 23 | ] 24 | 25 | [[package]] 26 | name = "android-tzdata" 27 | version = "0.1.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 30 | 31 | [[package]] 32 | name = "android_system_properties" 33 | version = "0.1.5" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 36 | dependencies = [ 37 | "libc", 38 | ] 39 | 40 | [[package]] 41 | name = "anstream" 42 | version = "0.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 45 | dependencies = [ 46 | "anstyle", 47 | "anstyle-parse", 48 | "anstyle-query", 49 | "anstyle-wincon", 50 | "colorchoice", 51 | "is-terminal", 52 | "utf8parse", 53 | ] 54 | 55 | [[package]] 56 | name = "anstyle" 57 | version = "1.0.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 60 | 61 | [[package]] 62 | name = "anstyle-parse" 63 | version = "0.2.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 66 | dependencies = [ 67 | "utf8parse", 68 | ] 69 | 70 | [[package]] 71 | name = "anstyle-query" 72 | version = "1.0.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 75 | dependencies = [ 76 | "windows-sys 0.48.0", 77 | ] 78 | 79 | [[package]] 80 | name = "anstyle-wincon" 81 | version = "1.0.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 84 | dependencies = [ 85 | "anstyle", 86 | "windows-sys 0.48.0", 87 | ] 88 | 89 | [[package]] 90 | name = "arrayvec" 91 | version = "0.5.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 94 | 95 | [[package]] 96 | name = "atomic" 97 | version = "0.5.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" 100 | 101 | [[package]] 102 | name = "autocfg" 103 | version = "1.1.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 106 | 107 | [[package]] 108 | name = "base64ct" 109 | version = "1.6.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 112 | 113 | [[package]] 114 | name = "bitflags" 115 | version = "1.3.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 118 | 119 | [[package]] 120 | name = "bitvec" 121 | version = "1.0.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 124 | dependencies = [ 125 | "funty", 126 | "radium", 127 | "tap", 128 | "wyz", 129 | ] 130 | 131 | [[package]] 132 | name = "bumpalo" 133 | version = "3.13.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 136 | 137 | [[package]] 138 | name = "bytecheck" 139 | version = "0.6.11" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" 142 | dependencies = [ 143 | "bytecheck_derive", 144 | "ptr_meta", 145 | "simdutf8", 146 | "uuid", 147 | ] 148 | 149 | [[package]] 150 | name = "bytecheck_derive" 151 | version = "0.6.11" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" 154 | dependencies = [ 155 | "proc-macro2", 156 | "quote", 157 | "syn 1.0.109", 158 | ] 159 | 160 | [[package]] 161 | name = "bytemuck" 162 | version = "1.13.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" 165 | 166 | [[package]] 167 | name = "byteorder" 168 | version = "1.4.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 171 | 172 | [[package]] 173 | name = "cc" 174 | version = "1.0.79" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 177 | 178 | [[package]] 179 | name = "cfg-if" 180 | version = "1.0.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 183 | 184 | [[package]] 185 | name = "chrono" 186 | version = "0.4.26" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" 189 | dependencies = [ 190 | "android-tzdata", 191 | "iana-time-zone", 192 | "js-sys", 193 | "num-traits", 194 | "time", 195 | "wasm-bindgen", 196 | "winapi", 197 | ] 198 | 199 | [[package]] 200 | name = "clap" 201 | version = "4.3.8" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "d9394150f5b4273a1763355bd1c2ec54cc5a2593f790587bcd6b2c947cfa9211" 204 | dependencies = [ 205 | "clap_builder", 206 | ] 207 | 208 | [[package]] 209 | name = "clap_builder" 210 | version = "4.3.8" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717" 213 | dependencies = [ 214 | "anstream", 215 | "anstyle", 216 | "bitflags", 217 | "clap_lex", 218 | "strsim", 219 | ] 220 | 221 | [[package]] 222 | name = "clap_lex" 223 | version = "0.5.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 226 | 227 | [[package]] 228 | name = "colorchoice" 229 | version = "1.0.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 232 | 233 | [[package]] 234 | name = "core-foundation-sys" 235 | version = "0.8.4" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 238 | 239 | [[package]] 240 | name = "crossterm" 241 | version = "0.26.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" 244 | dependencies = [ 245 | "bitflags", 246 | "crossterm_winapi", 247 | "libc", 248 | "mio", 249 | "parking_lot", 250 | "serde", 251 | "signal-hook", 252 | "signal-hook-mio", 253 | "winapi", 254 | ] 255 | 256 | [[package]] 257 | name = "crossterm_winapi" 258 | version = "0.9.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 261 | dependencies = [ 262 | "winapi", 263 | ] 264 | 265 | [[package]] 266 | name = "either" 267 | version = "1.8.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 270 | 271 | [[package]] 272 | name = "equivalent" 273 | version = "1.0.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" 276 | 277 | [[package]] 278 | name = "errno" 279 | version = "0.3.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 282 | dependencies = [ 283 | "errno-dragonfly", 284 | "libc", 285 | "windows-sys 0.48.0", 286 | ] 287 | 288 | [[package]] 289 | name = "errno-dragonfly" 290 | version = "0.1.2" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 293 | dependencies = [ 294 | "cc", 295 | "libc", 296 | ] 297 | 298 | [[package]] 299 | name = "fd-lock" 300 | version = "3.0.12" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "39ae6b3d9530211fb3b12a95374b8b0823be812f53d09e18c5675c0146b09642" 303 | dependencies = [ 304 | "cfg-if", 305 | "rustix", 306 | "windows-sys 0.48.0", 307 | ] 308 | 309 | [[package]] 310 | name = "funty" 311 | version = "2.0.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 314 | 315 | [[package]] 316 | name = "gen_ops" 317 | version = "0.3.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "e7c56cad8ee78109d547e40bf4ad78968a25157e7963d799d79921655629825a" 320 | 321 | [[package]] 322 | name = "getrandom" 323 | version = "0.2.10" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 326 | dependencies = [ 327 | "cfg-if", 328 | "js-sys", 329 | "libc", 330 | "wasi 0.11.0+wasi-snapshot-preview1", 331 | "wasm-bindgen", 332 | ] 333 | 334 | [[package]] 335 | name = "hashbrown" 336 | version = "0.12.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 339 | dependencies = [ 340 | "ahash", 341 | ] 342 | 343 | [[package]] 344 | name = "hashbrown" 345 | version = "0.14.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 348 | 349 | [[package]] 350 | name = "heck" 351 | version = "0.4.1" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 354 | 355 | [[package]] 356 | name = "hermit-abi" 357 | version = "0.3.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 360 | 361 | [[package]] 362 | name = "i1" 363 | version = "0.1.0" 364 | dependencies = [ 365 | "base64ct", 366 | "clap", 367 | "index_list", 368 | "indexmap", 369 | "memmap2", 370 | "metro", 371 | "range-set-blaze", 372 | "reedline-repl-rs", 373 | "rkyv", 374 | "roaring", 375 | "tinyvec", 376 | "uuid", 377 | ] 378 | 379 | [[package]] 380 | name = "iana-time-zone" 381 | version = "0.1.57" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" 384 | dependencies = [ 385 | "android_system_properties", 386 | "core-foundation-sys", 387 | "iana-time-zone-haiku", 388 | "js-sys", 389 | "wasm-bindgen", 390 | "windows", 391 | ] 392 | 393 | [[package]] 394 | name = "iana-time-zone-haiku" 395 | version = "0.1.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 398 | dependencies = [ 399 | "cc", 400 | ] 401 | 402 | [[package]] 403 | name = "index_list" 404 | version = "0.2.7" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "5a9d968042a4902e08810946fc7cd5851eb75e80301342305af755ca06cb82ce" 407 | 408 | [[package]] 409 | name = "indexmap" 410 | version = "2.0.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 413 | dependencies = [ 414 | "equivalent", 415 | "hashbrown 0.14.0", 416 | ] 417 | 418 | [[package]] 419 | name = "io-lifetimes" 420 | version = "1.0.11" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 423 | dependencies = [ 424 | "hermit-abi", 425 | "libc", 426 | "windows-sys 0.48.0", 427 | ] 428 | 429 | [[package]] 430 | name = "is-terminal" 431 | version = "0.4.7" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 434 | dependencies = [ 435 | "hermit-abi", 436 | "io-lifetimes", 437 | "rustix", 438 | "windows-sys 0.48.0", 439 | ] 440 | 441 | [[package]] 442 | name = "itertools" 443 | version = "0.10.5" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 446 | dependencies = [ 447 | "either", 448 | ] 449 | 450 | [[package]] 451 | name = "js-sys" 452 | version = "0.3.64" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 455 | dependencies = [ 456 | "wasm-bindgen", 457 | ] 458 | 459 | [[package]] 460 | name = "libc" 461 | version = "0.2.147" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 464 | 465 | [[package]] 466 | name = "linux-raw-sys" 467 | version = "0.3.8" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 470 | 471 | [[package]] 472 | name = "lock_api" 473 | version = "0.4.10" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 476 | dependencies = [ 477 | "autocfg", 478 | "scopeguard", 479 | ] 480 | 481 | [[package]] 482 | name = "log" 483 | version = "0.4.19" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 486 | 487 | [[package]] 488 | name = "memchr" 489 | version = "2.5.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 492 | 493 | [[package]] 494 | name = "memmap2" 495 | version = "0.7.1" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" 498 | dependencies = [ 499 | "libc", 500 | ] 501 | 502 | [[package]] 503 | name = "metro" 504 | version = "0.1.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "c4cbcb9bda3f68fa711725b57b35b53a68c4207299b294ac29167fcd3b4e62e8" 507 | 508 | [[package]] 509 | name = "mio" 510 | version = "0.8.8" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 513 | dependencies = [ 514 | "libc", 515 | "log", 516 | "wasi 0.11.0+wasi-snapshot-preview1", 517 | "windows-sys 0.48.0", 518 | ] 519 | 520 | [[package]] 521 | name = "nu-ansi-term" 522 | version = "0.47.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "1df031e117bca634c262e9bd3173776844b6c17a90b3741c9163663b4385af76" 525 | dependencies = [ 526 | "windows-sys 0.45.0", 527 | ] 528 | 529 | [[package]] 530 | name = "num-integer" 531 | version = "0.1.45" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 534 | dependencies = [ 535 | "autocfg", 536 | "num-traits", 537 | ] 538 | 539 | [[package]] 540 | name = "num-traits" 541 | version = "0.2.15" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 544 | dependencies = [ 545 | "autocfg", 546 | ] 547 | 548 | [[package]] 549 | name = "once_cell" 550 | version = "1.18.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 553 | 554 | [[package]] 555 | name = "parking_lot" 556 | version = "0.12.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 559 | dependencies = [ 560 | "lock_api", 561 | "parking_lot_core", 562 | ] 563 | 564 | [[package]] 565 | name = "parking_lot_core" 566 | version = "0.9.8" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 569 | dependencies = [ 570 | "cfg-if", 571 | "libc", 572 | "redox_syscall", 573 | "smallvec", 574 | "windows-targets 0.48.0", 575 | ] 576 | 577 | [[package]] 578 | name = "ppv-lite86" 579 | version = "0.2.17" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 582 | 583 | [[package]] 584 | name = "proc-macro2" 585 | version = "1.0.63" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 588 | dependencies = [ 589 | "unicode-ident", 590 | ] 591 | 592 | [[package]] 593 | name = "ptr_meta" 594 | version = "0.1.4" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 597 | dependencies = [ 598 | "ptr_meta_derive", 599 | ] 600 | 601 | [[package]] 602 | name = "ptr_meta_derive" 603 | version = "0.1.4" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 606 | dependencies = [ 607 | "proc-macro2", 608 | "quote", 609 | "syn 1.0.109", 610 | ] 611 | 612 | [[package]] 613 | name = "quote" 614 | version = "1.0.28" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 617 | dependencies = [ 618 | "proc-macro2", 619 | ] 620 | 621 | [[package]] 622 | name = "radium" 623 | version = "0.7.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 626 | 627 | [[package]] 628 | name = "rand" 629 | version = "0.8.5" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 632 | dependencies = [ 633 | "libc", 634 | "rand_chacha", 635 | "rand_core", 636 | ] 637 | 638 | [[package]] 639 | name = "rand_chacha" 640 | version = "0.3.1" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 643 | dependencies = [ 644 | "ppv-lite86", 645 | "rand_core", 646 | ] 647 | 648 | [[package]] 649 | name = "rand_core" 650 | version = "0.6.4" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 653 | dependencies = [ 654 | "getrandom", 655 | ] 656 | 657 | [[package]] 658 | name = "range-set-blaze" 659 | version = "0.1.6" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "85443a0bf00e4fca6fc4500501c8673d3255c46d5760e8b648308c3d1b9d2203" 662 | dependencies = [ 663 | "gen_ops", 664 | "itertools", 665 | "num-integer", 666 | "num-traits", 667 | "rand", 668 | "thiserror", 669 | ] 670 | 671 | [[package]] 672 | name = "redox_syscall" 673 | version = "0.3.5" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 676 | dependencies = [ 677 | "bitflags", 678 | ] 679 | 680 | [[package]] 681 | name = "reedline" 682 | version = "0.19.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "31d763be5efcabcd27d6e804d126661215aa0a2d898c13c0aa1c953c6652fcec" 685 | dependencies = [ 686 | "chrono", 687 | "crossterm", 688 | "fd-lock", 689 | "itertools", 690 | "nu-ansi-term", 691 | "serde", 692 | "strip-ansi-escapes", 693 | "strum", 694 | "strum_macros", 695 | "thiserror", 696 | "unicode-segmentation", 697 | "unicode-width", 698 | ] 699 | 700 | [[package]] 701 | name = "reedline-repl-rs" 702 | version = "1.0.6" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "15773627b14cb5e3a774a8dd61fc4f611faa47a6d20071c1ff25b30526fde04d" 705 | dependencies = [ 706 | "clap", 707 | "crossterm", 708 | "nu-ansi-term", 709 | "reedline", 710 | "regex", 711 | "winapi-util", 712 | "yansi", 713 | ] 714 | 715 | [[package]] 716 | name = "regex" 717 | version = "1.8.4" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 720 | dependencies = [ 721 | "aho-corasick", 722 | "memchr", 723 | "regex-syntax", 724 | ] 725 | 726 | [[package]] 727 | name = "regex-syntax" 728 | version = "0.7.2" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 731 | 732 | [[package]] 733 | name = "rend" 734 | version = "0.4.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" 737 | dependencies = [ 738 | "bytecheck", 739 | ] 740 | 741 | [[package]] 742 | name = "retain_mut" 743 | version = "0.1.7" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" 746 | 747 | [[package]] 748 | name = "rkyv" 749 | version = "0.7.42" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" 752 | dependencies = [ 753 | "bitvec", 754 | "bytecheck", 755 | "hashbrown 0.12.3", 756 | "ptr_meta", 757 | "rend", 758 | "rkyv_derive", 759 | "seahash", 760 | "tinyvec", 761 | "uuid", 762 | ] 763 | 764 | [[package]] 765 | name = "rkyv_derive" 766 | version = "0.7.42" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" 769 | dependencies = [ 770 | "proc-macro2", 771 | "quote", 772 | "syn 1.0.109", 773 | ] 774 | 775 | [[package]] 776 | name = "roaring" 777 | version = "0.10.1" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "ef0fb5e826a8bde011ecae6a8539dd333884335c57ff0f003fbe27c25bbe8f71" 780 | dependencies = [ 781 | "bytemuck", 782 | "byteorder", 783 | "retain_mut", 784 | ] 785 | 786 | [[package]] 787 | name = "rustix" 788 | version = "0.37.20" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" 791 | dependencies = [ 792 | "bitflags", 793 | "errno", 794 | "io-lifetimes", 795 | "libc", 796 | "linux-raw-sys", 797 | "windows-sys 0.48.0", 798 | ] 799 | 800 | [[package]] 801 | name = "rustversion" 802 | version = "1.0.12" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" 805 | 806 | [[package]] 807 | name = "scopeguard" 808 | version = "1.1.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 811 | 812 | [[package]] 813 | name = "seahash" 814 | version = "4.1.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 817 | 818 | [[package]] 819 | name = "serde" 820 | version = "1.0.164" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 823 | dependencies = [ 824 | "serde_derive", 825 | ] 826 | 827 | [[package]] 828 | name = "serde_derive" 829 | version = "1.0.164" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" 832 | dependencies = [ 833 | "proc-macro2", 834 | "quote", 835 | "syn 2.0.22", 836 | ] 837 | 838 | [[package]] 839 | name = "signal-hook" 840 | version = "0.3.15" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" 843 | dependencies = [ 844 | "libc", 845 | "signal-hook-registry", 846 | ] 847 | 848 | [[package]] 849 | name = "signal-hook-mio" 850 | version = "0.2.3" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 853 | dependencies = [ 854 | "libc", 855 | "mio", 856 | "signal-hook", 857 | ] 858 | 859 | [[package]] 860 | name = "signal-hook-registry" 861 | version = "1.4.1" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 864 | dependencies = [ 865 | "libc", 866 | ] 867 | 868 | [[package]] 869 | name = "simdutf8" 870 | version = "0.1.4" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" 873 | 874 | [[package]] 875 | name = "smallvec" 876 | version = "1.10.0" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 879 | 880 | [[package]] 881 | name = "strip-ansi-escapes" 882 | version = "0.1.1" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" 885 | dependencies = [ 886 | "vte", 887 | ] 888 | 889 | [[package]] 890 | name = "strsim" 891 | version = "0.10.0" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 894 | 895 | [[package]] 896 | name = "strum" 897 | version = "0.24.1" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 900 | 901 | [[package]] 902 | name = "strum_macros" 903 | version = "0.24.3" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 906 | dependencies = [ 907 | "heck", 908 | "proc-macro2", 909 | "quote", 910 | "rustversion", 911 | "syn 1.0.109", 912 | ] 913 | 914 | [[package]] 915 | name = "syn" 916 | version = "1.0.109" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 919 | dependencies = [ 920 | "proc-macro2", 921 | "quote", 922 | "unicode-ident", 923 | ] 924 | 925 | [[package]] 926 | name = "syn" 927 | version = "2.0.22" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" 930 | dependencies = [ 931 | "proc-macro2", 932 | "quote", 933 | "unicode-ident", 934 | ] 935 | 936 | [[package]] 937 | name = "tap" 938 | version = "1.0.1" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 941 | 942 | [[package]] 943 | name = "thiserror" 944 | version = "1.0.40" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 947 | dependencies = [ 948 | "thiserror-impl", 949 | ] 950 | 951 | [[package]] 952 | name = "thiserror-impl" 953 | version = "1.0.40" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 956 | dependencies = [ 957 | "proc-macro2", 958 | "quote", 959 | "syn 2.0.22", 960 | ] 961 | 962 | [[package]] 963 | name = "time" 964 | version = "0.1.45" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 967 | dependencies = [ 968 | "libc", 969 | "wasi 0.10.0+wasi-snapshot-preview1", 970 | "winapi", 971 | ] 972 | 973 | [[package]] 974 | name = "tinyvec" 975 | version = "1.6.0" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 978 | dependencies = [ 979 | "tinyvec_macros", 980 | ] 981 | 982 | [[package]] 983 | name = "tinyvec_macros" 984 | version = "0.1.1" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 987 | 988 | [[package]] 989 | name = "unicode-ident" 990 | version = "1.0.9" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 993 | 994 | [[package]] 995 | name = "unicode-segmentation" 996 | version = "1.10.1" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 999 | 1000 | [[package]] 1001 | name = "unicode-width" 1002 | version = "0.1.10" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1005 | 1006 | [[package]] 1007 | name = "utf8parse" 1008 | version = "0.2.1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1011 | 1012 | [[package]] 1013 | name = "uuid" 1014 | version = "1.3.4" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" 1017 | dependencies = [ 1018 | "atomic", 1019 | "getrandom", 1020 | "rand", 1021 | "uuid-macro-internal", 1022 | "wasm-bindgen", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "uuid-macro-internal" 1027 | version = "1.3.4" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "7da8500be15217da76379f13cfb1a9e351ccc2b0959c7bc8ea64ac4302ba4de4" 1030 | dependencies = [ 1031 | "proc-macro2", 1032 | "quote", 1033 | "syn 2.0.22", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "version_check" 1038 | version = "0.9.4" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1041 | 1042 | [[package]] 1043 | name = "vte" 1044 | version = "0.10.1" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" 1047 | dependencies = [ 1048 | "arrayvec", 1049 | "utf8parse", 1050 | "vte_generate_state_changes", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "vte_generate_state_changes" 1055 | version = "0.1.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" 1058 | dependencies = [ 1059 | "proc-macro2", 1060 | "quote", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "wasi" 1065 | version = "0.10.0+wasi-snapshot-preview1" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1068 | 1069 | [[package]] 1070 | name = "wasi" 1071 | version = "0.11.0+wasi-snapshot-preview1" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1074 | 1075 | [[package]] 1076 | name = "wasm-bindgen" 1077 | version = "0.2.87" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1080 | dependencies = [ 1081 | "cfg-if", 1082 | "wasm-bindgen-macro", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "wasm-bindgen-backend" 1087 | version = "0.2.87" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1090 | dependencies = [ 1091 | "bumpalo", 1092 | "log", 1093 | "once_cell", 1094 | "proc-macro2", 1095 | "quote", 1096 | "syn 2.0.22", 1097 | "wasm-bindgen-shared", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "wasm-bindgen-macro" 1102 | version = "0.2.87" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 1105 | dependencies = [ 1106 | "quote", 1107 | "wasm-bindgen-macro-support", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "wasm-bindgen-macro-support" 1112 | version = "0.2.87" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 1115 | dependencies = [ 1116 | "proc-macro2", 1117 | "quote", 1118 | "syn 2.0.22", 1119 | "wasm-bindgen-backend", 1120 | "wasm-bindgen-shared", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "wasm-bindgen-shared" 1125 | version = "0.2.87" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 1128 | 1129 | [[package]] 1130 | name = "winapi" 1131 | version = "0.3.9" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1134 | dependencies = [ 1135 | "winapi-i686-pc-windows-gnu", 1136 | "winapi-x86_64-pc-windows-gnu", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "winapi-i686-pc-windows-gnu" 1141 | version = "0.4.0" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1144 | 1145 | [[package]] 1146 | name = "winapi-util" 1147 | version = "0.1.5" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1150 | dependencies = [ 1151 | "winapi", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "winapi-x86_64-pc-windows-gnu" 1156 | version = "0.4.0" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1159 | 1160 | [[package]] 1161 | name = "windows" 1162 | version = "0.48.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 1165 | dependencies = [ 1166 | "windows-targets 0.48.0", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "windows-sys" 1171 | version = "0.45.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1174 | dependencies = [ 1175 | "windows-targets 0.42.2", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "windows-sys" 1180 | version = "0.48.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1183 | dependencies = [ 1184 | "windows-targets 0.48.0", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "windows-targets" 1189 | version = "0.42.2" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1192 | dependencies = [ 1193 | "windows_aarch64_gnullvm 0.42.2", 1194 | "windows_aarch64_msvc 0.42.2", 1195 | "windows_i686_gnu 0.42.2", 1196 | "windows_i686_msvc 0.42.2", 1197 | "windows_x86_64_gnu 0.42.2", 1198 | "windows_x86_64_gnullvm 0.42.2", 1199 | "windows_x86_64_msvc 0.42.2", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "windows-targets" 1204 | version = "0.48.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1207 | dependencies = [ 1208 | "windows_aarch64_gnullvm 0.48.0", 1209 | "windows_aarch64_msvc 0.48.0", 1210 | "windows_i686_gnu 0.48.0", 1211 | "windows_i686_msvc 0.48.0", 1212 | "windows_x86_64_gnu 0.48.0", 1213 | "windows_x86_64_gnullvm 0.48.0", 1214 | "windows_x86_64_msvc 0.48.0", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "windows_aarch64_gnullvm" 1219 | version = "0.42.2" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1222 | 1223 | [[package]] 1224 | name = "windows_aarch64_gnullvm" 1225 | version = "0.48.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1228 | 1229 | [[package]] 1230 | name = "windows_aarch64_msvc" 1231 | version = "0.42.2" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1234 | 1235 | [[package]] 1236 | name = "windows_aarch64_msvc" 1237 | version = "0.48.0" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1240 | 1241 | [[package]] 1242 | name = "windows_i686_gnu" 1243 | version = "0.42.2" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1246 | 1247 | [[package]] 1248 | name = "windows_i686_gnu" 1249 | version = "0.48.0" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1252 | 1253 | [[package]] 1254 | name = "windows_i686_msvc" 1255 | version = "0.42.2" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1258 | 1259 | [[package]] 1260 | name = "windows_i686_msvc" 1261 | version = "0.48.0" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1264 | 1265 | [[package]] 1266 | name = "windows_x86_64_gnu" 1267 | version = "0.42.2" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1270 | 1271 | [[package]] 1272 | name = "windows_x86_64_gnu" 1273 | version = "0.48.0" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1276 | 1277 | [[package]] 1278 | name = "windows_x86_64_gnullvm" 1279 | version = "0.42.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1282 | 1283 | [[package]] 1284 | name = "windows_x86_64_gnullvm" 1285 | version = "0.48.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1288 | 1289 | [[package]] 1290 | name = "windows_x86_64_msvc" 1291 | version = "0.42.2" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1294 | 1295 | [[package]] 1296 | name = "windows_x86_64_msvc" 1297 | version = "0.48.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1300 | 1301 | [[package]] 1302 | name = "wyz" 1303 | version = "0.5.1" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 1306 | dependencies = [ 1307 | "tap", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "yansi" 1312 | version = "0.5.1" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 1315 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "i1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | reedline-repl-rs = "1.0.6" 8 | memmap2 = "0.7.1" 9 | indexmap = "2.0.0" 10 | range-set-blaze = "0.1.6" 11 | metro = "0.1.1" 12 | index_list = "0.2.7" 13 | roaring = "0.10.1" 14 | 15 | [dependencies.base64ct] 16 | version = "1.6.0" 17 | features = [ "alloc" ] 18 | 19 | [dependencies.clap] 20 | version = "4.3.8" 21 | features = [ "string" ] 22 | 23 | [dependencies.rkyv] 24 | version = "0.7.42" 25 | features = [ 26 | "alloc", 27 | "archive_be", 28 | # "bitvec", 29 | # "size_64", 30 | "tinyvec", 31 | "uuid", 32 | "validation", 33 | ] 34 | 35 | [dependencies.tinyvec] 36 | version = "1.6.0" 37 | features = [ 38 | "alloc", 39 | ] 40 | 41 | [dependencies.uuid] 42 | version = "1.3.4" 43 | features = [ 44 | "v7", 45 | "js", 46 | "std", 47 | "fast-rng", 48 | "macro-diagnostics" 49 | ] 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `chit`: (early experiments toward) a version-control system for structured data 2 | 3 | **please note, very little is actually implemented here. this is not useful yet. the on-disk format is far from stabilized.** 4 | 5 | ### Usage 6 | 7 | ``` 8 | cargo run --release 9 | ``` 10 | ![demo](./demo.png) 11 | 12 | All commits are serialized to disk (in a folder called patches/) and then read back from disk on startup. 13 | 14 | Other commands include `heads`, `commits`, and `count` (counts the number of elements in the current set). Actually assigning elements to sorts is only partially implemented WIP. There is a `merge` command but this is also only partially implemented. 15 | 16 | ## Motivations 17 | 18 | 1. **The [Open Agency Architecture](https://www.alignmentforum.org/posts/jRf4WENQnhssCb6mJ/davidad-s-bold-plan-for-alignment-an-in-depth-explanation#Fine_grained_Scheme) calls for compositional version-control systems** for causal models as well as specifications. These need to be organized so that both humans and AIs can both navigate and propose patches easily. The models I have in mind will be fundamentally graphical in nature—more like wiring diagrams or pasting diagrams than programs. The standard version-control paradigm of maintaining a repository of files in a syntax language does not facilitate discovery and manipulation for either AIs (which would do better with graph attention) or humans (which would do better with visual representations, at least at some scales). 19 | 2. **I've wanted something like this for ages for personal information management**. Roam, Notion, and Airtable barely scratch the itch. They lack adequate version-control features (and, relatedly, offline-first functionality), querying tools, schema evolution, and computational efficiency. And there are new directions I want to explore: bargaining solutions for multi-party meeting scheduling; RL policies for project management in [SDCPN](https://pure.tudelft.nl/ws/portalfiles/portal/139657077/ESREL2021_Final_revised_full_paper_50.pdf) models of R&D roadmaps and business processes; [language model cascades](https://arxiv.org/pdf/2207.10342.pdf) that are both version-controlled as structured data and that operate on other structured data (not just reading your Notion documents and suggesting the next sentence!). 20 | 3. **Who knows, maybe the next SQL?** [Codd's relational model](https://en.wikipedia.org/wiki/Relational_model) has been the dominant paradigm for data management for over 50 years. Many have tried to supersede it; all have failed, generally for one or more of a few reasons: (a) computational inefficiency and inability to catch up with decades of optimizations and heuristics, (b) falling short of SQL's level of rigor or expressiveness in schema design, or (c) inability to maintain consistency under concurrency. 21 | 22 | ## Inspirations 23 | 24 | 1. [A Relational Model of Data for Large Shared Data Banks](https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf), Edgar Codd, 1970. The foundation of most database systems of the past 50 years. If you're trying to do something new and foundational in databases, you have to at least be familiar with this. It's a really good paper! Honorable mention to [The Third Manifesto](https://www.dcs.warwick.ac.uk/~hugh/TTM/DTATRM.pdf) which is in a similar spirit, updated in 2006 and elaborated with the benefit of decades of practical experience, and with a commendable attempt to integrate type theory to bring some rational design to the then-fashionable concept of "inheritance." 25 | 2. [Categorical Data Structures for Technical Computing](https://compositionality-journal.org/papers/compositionality-4-5/pdf/), Evan Patterson, Owen Lynch, James Fairbanks, 2021. This is my reference for the concept of ACSets, a highly flexible but extremely formal notion of database instances and schemas. 26 | 3. [Data-Parallel Algorithms for String Diagrams](https://arxiv.org/pdf/2305.01041.pdf), Paul Wilson and Fabio Zanasi, 2023. This paper explores highly efficient representations for ACSets that are suitable for GPU acceleration, and also demonstrates how string diagrams (one of the main types of structured data I want to manage) are naturally represented using ACSets. 27 | 4. [Better Together: Unifying Datalog and Equality Saturation](https://arxiv.org/pdf/2304.04332.pdf), Yihong Zhang, Yisu Remy Wang, Oliver Flatt, David Cao, Philip Zucker (aka @sandmouth and "Hey There Buddo!"), Eli Rosenthal, Zachary Tatlock, Max Willsey, 2023. Great! This is actually _two_ fewer unifications I have to work out the details of (or to convince you makes sense!); although the categorical semantics is not very well developed, they have incorporated the lattice-variable framework from Flix and Ascent (which I trace back to Kuper's LVars). They are also actively working on an [implementation](https://github.com/egraphs-good/egglog) (in Rust, at that!) which we can take some inspiration from. 28 | 5. [Algebraic Semantics of Datalog with Equality](https://arxiv.org/pdf/2302.03167.pdf), Martin Bidlingmaier, 2023. This provides a lot of the categorical semantics I was hoping for in the above reference. It also explains, in very rigorous detail, the inductive definitions of PHL terms, PHL atoms, PHL formulae, and PHL sequents. 29 | 6. [Free Join: Unifying Worst-Case Optimal and Traditional Joins](https://arxiv.org/pdf/2301.10841.pdf), Yisu Remy Wang, Max Willsey, Dan Suciu, 2023. One of the reasons I think there might be an opportunity for a paradigm shift in databases is recent sudden progress on worst-case optimal join algorithms from a computational-complexity perspective. This paper provides some missing pieces that make those theoretically appealing algorithms also quite competitive with SQL join algorithms in practice. 30 | 7. [A Categorical Theory of Patches](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=68f9718ce423df0c9f5cb094ce47fc93c776c9f6), Samuel Mimram and Cinzia Di Giusto, 2013. Probably the earliest paper proposing an analogy of patches as morphisms, with merges as pushouts. 31 | 8. [On lax limits in ∞-categories](https://arxiv.org/pdf/2006.10851.pdf), John Berman, 2020. This isn't a joke; I don't recommend trying to read it, but this paper is what finally gave me an intuition I felt somewhat comfortable with for Grothendieck constructions (they're [oplax colimits](https://ncatlab.org/nlab/show/Grothendieck+construction#AsALaxColimit)), and this intuition was critical for my idea about how to version-control CSet schemas and instances jointly (namely, by taking the Grothendieck construction of a domain fibration). Similarly, but less essentially, I glanced through [Internal enriched categories](https://arxiv.org/pdf/2006.07997.pdf) by Enrico Ghiorzi and a lot while thinking about this project. 32 | 9. [Higher Dimensional Categories](https://www.amazon.co.uk/Higher-Dimensional-Categories-Double-Multiple/dp/9811205108), Marco Grandis, 2019. An excellent book on double and multiple categories (my preferred shape for higher-dimensional categories). This directly provided the notion of update between CSets that I plan to use. 33 | 10. [1ML: Core and Modules United](https://people.mpi-sws.org/~rossberg/papers/Rossberg%20-%201ML%20--%20Core%20and%20modules%20united.pdf), Andreas Rossberg, 2015. Somewhat tangential, but probably describes "the correct way" to handle *namespaces*, which is an important and oft-neglected aspect of any software framework that can be used to manage large things. 34 | 11. [Making CRDTs Byzantine Fault Tolerant](https://martin.kleppmann.com/papers/bft-crdt-papoc22.pdf), Martin Kleppmann, 2022. This explains where and why cryptographic hashes are needed to ensure consistency in a Byzantine setting. An alternative approach is to cryptographically sign updates and use trust. I hope to support both approaches, but right now have only implemented 128-bit UUIDs. Some of Kleppmann's other work, such as [Thinking in Events](https://martin.kleppmann.com/papers/debs21-keynote.pdf), is also tangentially inspirational. 35 | 12. [Typed Image-Based Programming With Structure Editing](https://arxiv.org/pdf/2110.08993.pdf), Jonathan Edwards and Tomas Petricek, 2021. Cites the Categorical Theory of Patches and pursues the idea that version-controlling schemas in a structured way alongisde the data that is based on them makes data migration much less challenging. 36 | 37 | ## Key ideas 38 | 39 | These are mostly notes for myself if and when I should come back to this project. 40 | 41 | ### Sorts 42 | 43 | In ACSets, an object of the non-attribute part of the schema (S0) is a sort. S0 itself is the meta-sort of sorts, but does not contain itself or other meta-sorts (such as the meta-sort of attributes). A schema induces a mapping of meta-sorts to sets; an instance additionally induces a mapping of sorts to sets. 44 | 45 | An object of the attribute part of the schema (S1) is a (primitive) type. 46 | 47 | In the type theory of `chit`, it should be possible to form record types whose fields have types drawn from a mixture of S0 and S1. In this sense both sorts and primitve types are **types**. 48 | 49 | In particular, objects of S0 can be thought of as inductive or algebraic data types, generally constituted of a large collection of nullary and anonymous constructors (the unique IDs of elements of that sort). But when we declare functions that map into a sort, we begin to open the possibility of constructing algebraic terms, which may or may not reduce to any particular nullary constructor. Think of it this way: in SQL, PresentKing("France") would return NULL, but in `chit`, PresentKing("France") would return PresentKing("France"). It simply doesn't normalize any further. (Although it should be possible to define Maybe types, and assert that PresentKing("France")=None, this would represent "missing but inapplicable" as distinct from simply "missing". This distinction is one that Codd himself once tried to make but it was shouted down in SQL committee.) 50 | 51 | ### IDs 52 | 53 | There are multiple kinds of IDs that are useful: 54 | 0. Patch-Local ID: like a sort-local ID, but even more local, to the elements which are introduced in a given patch. 55 | 1. Sort-Local ID: this is the kind which the Julia implementation of ACSets uses almost exclusively. It is a natural number ranging from 0 to to the total number of elements of a given sort at a given version(/instance). These numbers necessarily will overlap between versions, and between different sorts within the same version, but may also vary in unpredictable ways. Thus they are local to a specific sort at a specific verison. 56 | 2. Version-Local ID: this is a natural number ranging from 0 to the total number of elements in a version (including the sorts themselves, as elements of S0, etc.). 57 | 3. Locally Universal ID: this is a natural number ranging from 0 to the total number of UUIDs that has been seen by this installation of the program. 58 | 4. Universally Unique ID (UUID): this is a 128-bit number incorporating a timestamp and some randomness, which is universally unique unless someone has been playing tricks or unless you got astronomically unlucky. 59 | 5. Cryptographic-Hash ID (CHID): per Kleppmann, this is a concatenation of a hash (256 bits or more) of a patch followed by the Patch-Local ID of an element which was introduced in that patch. 60 | 61 | #### Namings 62 | 63 | There is a meta-sort called a "context" whose elements are "namings": associations of a particular UUID or CHID with a particular name (a list of strings) within a version. Patches may delete and add namings (modification is given by deletion followed by addition). 64 | 65 | For practical purposes, on the theoretical side, wherever the binary monoidal product appears as a primitive, we should consider replacing it with the formation of a record type. And instead of associativity and swaps, we should think of the structural maps as substitutions-in-context. 66 | 67 | But assertions in the database actually only refer to IDs, not to names, so namings are purely a user interface (input/output for humans and large language models). Language models may also have their own kind of ``namings'' that refer to vector embeddings instead of lists of strings, in the spirit of [Quasilinguistic Neural Representations](https://www.alignmentforum.org/posts/FKE6cAzQxEK4QH9fC/qnr-prospects-are-important-for-ai-alignment-research). 68 | 69 | It is also an important design desideratum that multiple names can refer to the same entity. There are many reasons for this, ranging from namespace exports to filesystem hardlinks to the simple fact that equalities between entities can always be asserted at the union-find level anyway. 70 | 71 | ### Custom attribute types 72 | 73 | It should be possible to implement custom attributes as Rust types that implement the [rkyv](https://docs.rs/rkyv/latest/rkyv/) traits, and then register them with the UUID of an element of S1 meta-sort. Perhaps ultimately this should take place via loading WebAssembly modules, but a first implementation would simply perform the registration in a macro and require a downstream fork in order to maintain custom attribute types. 74 | 75 | ### Assertions of logical judgments 76 | 77 | All data items in `chit` are in some sense denoting the assertion of some logical judgment. For example, a sort Person denotes the assertion of the judgment |- Person : S0. A person Bob denotes the assertion of the judgment |- AymDPMKBd2C2tMPV8KErnQ : Person, and perhaps the judgment |- Name(AymDPMKBd2C2tMPV8KErnQ) = "Bob" (given a previous assertion of |- Name : Person -> String). Ultimately, `chit` will also be able to represent assertions of sequents with premises, known in Datalog as "rules", which can provide the basis of many things from logic programming to reproducible dataflow computing. Congruence closure of terms relative to deducible equality judgments can be done efficiently with worst-case optimal joins, cf. `egglog`. 78 | 79 | #### Partially ordered types 80 | 81 | `egglog` brings in the notion of types with partial orders. Following the general vibe of freely adjoining term constructors, we could perhaps simply say that all types have a Join operation and a Bottom constant, along with their universal properties as declared sequents: 82 | ``` 83 | T : Type, x : T |- Bottom <= x 84 | T : Type, x : T, y : T |- x <= Join(x,y) 85 | T : Type, x : T, y : T |- y <= Join(x,y) 86 | T : Type, x : T, y : T, z : T, x <= z, y <= z |- Join(x,y) <= z 87 | ``` 88 | If we want to actually define some of these terms (instead of leaving them as freely generated uninterpreted terms), we could do so by asserting their equalities to ground values. If uninterpreted Join terms appear in a database instance without a ground normal form, then the instance is a "conflicted state" analogous to a git repository in the midst of a merge conflict. Committing such states is forbidden, but it is useful to be able to work with them within the formalism. 89 | 90 | The way that `egglog` performs merges, it is almost as though typical assertions are of the form 91 | ``` 92 | |- v <= f(x) 93 | ``` 94 | rather than 95 | ``` 96 | |- v = f(x) 97 | ``` 98 | 99 | This suggests thinking about [Directed Type Theory](https://jacobneu.github.io/research/abstracts/HoTT-UF-2022.pdf) in which "equalities" are replaced by (or defined in terms of) directed "paths". 100 | 101 | ### Elements with parameters 102 | 103 | It should be possible to declare a function **from** a primitive type (or a record of primitive types) **to** an uninterpreted sort. If the primitive type is infinite, this notionally breaks the interpretation of instances as always finite. But it enables the construction of infinite diagrams such as in [Monoidal Streams for Dataflow Programming](https://arxiv.org/pdf/2202.02061.pdf). And since primitive types have combinatorial representations, term diagrams that invoke functions on primitive types do too, so this doesn't break the desirable computational properties of having finite instances. (Of course, there are some pathological infinite structures, for example, so-called "real numbers", that still cannot be represented this way, but that is fine; we use floating-point numbers or rationals in practice anyway.) 104 | 105 | ### Succinct data structures 106 | 107 | In these experiments I've made use of the [RoaringTreemap](https://docs.rs/roaring/latest/roaring/treemap/struct.RoaringTreemap.html), based on Daniel Lemire's [Roaring Bitmaps](https://lemire.me/en/publication/arxiv14026407/), widely used in database indices, to represent sets of `u64`s (the various kinds of ID short of level 4 are all `usize`s, which I assume fits in `u64`). Other data structures filling this role, such as Partitioned Elias-Fano and [la_vector](https://github.com/gvinciguerra/la_vector), should be considered and evaluated in the fullness of time. Also, [RangeSetBlaze](https://github.com/CarlKCarlK/range-set-blaze) could probably be easily extended to support `rank` and `select` queries, and then could fill this role as well (perhaps best for the `version_universe` index). 108 | 109 | ### Patches as vertical natural transformations 110 | 111 | If we consider CSets as double functors from horizontal double categories into the double category FinRel (which has functions as horizontal morphisms and relations as vertical morphisms), then there is a very natural construction of what it means to be a "patch" between a C0Set and a C1Set: a functor from C0 to C1, equipped with a vertical natural transformation that fills the slice triangle. What this cashes out to, with UUIDs in play, turns out to be a partial function between every sort in the first instance and its (unique) corresponding sort in the second instance. (It's really a relation, but the squares with the UUID attribute can only commute if no elements on the left have multiple related elements on the right.) Add the principle that a previously deleted UUID may never be added again, and you get the convenient lemma that elements with the same UUID are always related, and the only way for elements to be related without sharing the same UUID is if there is a "merge" of one UUID into the other recorded in some patch. (Perfect for a union-find data structure.) Patches are thus recorded, at the sort level (`UuidSetPatch`), as deletions followed by merges followed by additions (exploiting Lack's [spider theorem](https://graphicallinearalgebra.net/tag/spider-theorem/)). 112 | 113 | Some open theoretical questions to work out: 114 | 1. With ACSets (as opposed to mere CSets), how does this construction work? 115 | 2. (perhaps related; if not, perhaps unfruitful) Can this be interpreted formally as the Grothendieck construction of the functor assigning to each finite category C its category of CSets? Does this then generalize to ACSets? 116 | 117 | ### Submodules 118 | 119 | As mentioned earlier, namespaces are often neglected. I hope to get this right, but I'm not sure I have the answer yet. My best idea is that an "import" - which brings in certain named entities from a completely separate repository or branch, under a certain renaming policy - should be a primitive operation, like additions and deletions. This particular operation should be annotated with a "fully qualified branch name": a hostname, account name, repo name, and branch name. And the interface should make it easy to try to "rebase" such operations by fetching the latest authenaticated version of that branch (the equivalent of updating submodules). 120 | 121 | ### Version diagrams 122 | 123 | The total state of a `chit` installation supervenes upon a **diagram** of versions, i.e. a (monoidal) functor from some (free symmetric monoidal) category into the category of versions and patches. The monoidal product in the category of versions and patches is taken to be categorical coproduct (corresponding to union of underlying UuidSets), so a "commit" (which can have multiple source versions) must denote a patch (which can only have one source version) whose source is the union of the commit's source versions. Similarly, a root commit with no source versions denotes a patch from the empty coproduct (an empty instance of an empty schema). 124 | 125 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidad/chit/ad8ac2e3f9466d263d18ce93fa60bfdf3e605cd5/demo.png -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum Context { 6 | Node(HashMap), 7 | Leaf(Luid), 8 | } 9 | 10 | impl Context { 11 | pub fn get(&self, mut path: impl Iterator) -> Option { 12 | match self { 13 | Context::Leaf(luid) => match path.next() { 14 | None => Some(*luid), 15 | Some(_) => None, 16 | }, 17 | Context::Node(map) => { 18 | let map = map; 19 | let key = path.next()?; 20 | let key = key.to_string(); 21 | let context = map.get(&key)?; 22 | context.get(path) 23 | } 24 | } 25 | } 26 | } 27 | 28 | impl Default for Context { 29 | fn default() -> Self { 30 | Context::Node(HashMap::new()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | use base64ct::{Base64UrlUnpadded, Encoding}; 2 | 3 | // Uuid = Universally Unique ID (128 bits; globally unique) 4 | pub use uuid::Uuid; 5 | 6 | // Local ID type (just an unsigned integer) 7 | pub type Lid = usize; 8 | 9 | // Luid = Locally Universal ID: local to an entire installation 10 | pub type Luid = Lid; 11 | 12 | // Vlid = Version-Local ID: local to a single version 13 | pub type Vlid = Lid; 14 | 15 | // Slid = Set-Local ID: local to a single finite set within a single version 16 | pub type Slid = Lid; 17 | 18 | pub trait AsBase64Url { 19 | fn as_base64url(&self) -> String; 20 | } 21 | 22 | impl AsBase64Url for Uuid { 23 | fn as_base64url(&self) -> String { 24 | Base64UrlUnpadded::encode_string(self.as_bytes()) 25 | } 26 | } 27 | 28 | pub trait FromBase64Url { 29 | fn from_base64url(s: &str) -> Result 30 | where 31 | Self: Sized; 32 | } 33 | 34 | impl FromBase64Url for Uuid { 35 | fn from_base64url(s: &str) -> Result { 36 | let bytes = Base64UrlUnpadded::decode_vec(s) 37 | .map_err(|e| format!("Error decoding UUID in parsing base64url: {}", e))?; 38 | Uuid::from_slice(bytes.as_slice()).map_err(|e| format!("Error decoding UUID format: {}", e)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | mod context; 4 | pub use context::Context; 5 | mod id; 6 | pub use id::{AsBase64Url, FromBase64Url, Luid, Slid, Uuid, Vlid}; 7 | mod patch; 8 | pub use patch::UuidSetPatch; 9 | mod state; 10 | pub use state::TotalState; 11 | mod version; 12 | pub use version::Version; 13 | 14 | impl TotalState { 15 | pub fn new() -> Self { 16 | let mut state = Self::default(); 17 | state.load_all_patches(); 18 | state 19 | } 20 | 21 | pub fn get_patch_dir() -> PathBuf { 22 | let mut patch_dir = PathBuf::new(); 23 | patch_dir.push("patches/"); 24 | patch_dir 25 | } 26 | 27 | pub fn commits(&self) -> impl Iterator { 28 | self 29 | .commits 30 | .keys() 31 | .map(|luid| self.universe.get_index(*luid).unwrap()) 32 | } 33 | 34 | pub fn checkout(&mut self, commit: &Uuid) -> Option<()> { 35 | let commit_luid = self.universe.get_index_of(commit)?; 36 | self.checkout_luid(commit_luid) 37 | } 38 | 39 | pub fn checkout_luid(&mut self, commit: Luid) -> Option<()> { 40 | if !self.working_patch.is_empty() { 41 | eprintln!("Error: checkout while working patch is not empty. Commit before checking out."); 42 | } 43 | self.working_patch.clear(); 44 | self 45 | .working_patch 46 | .source_commits 47 | .push(*self.universe.get_index(commit).unwrap()); 48 | let version = self.version_cache.get(&commit)?; 49 | self.working_state = version 50 | .version_universe 51 | .iter() 52 | .map(|x| x as usize) 53 | .collect(); 54 | Some(()) 55 | } 56 | 57 | pub fn heads(&self) -> impl Iterator { 58 | self 59 | .heads 60 | .iter() 61 | .map(|luid| self.universe.get_index(*luid).unwrap()) 62 | } 63 | 64 | pub fn add(&mut self) -> Uuid { 65 | let uuid = Uuid::now_v7(); 66 | self.working_state.insert(self.universe.insert_full(uuid).0); 67 | self.working_patch.universe_patch.additions.insert(uuid); 68 | uuid 69 | } 70 | 71 | pub fn count(&self) -> usize { 72 | self.working_state.len() 73 | } 74 | 75 | pub fn list(&self) -> impl Iterator { 76 | self 77 | .working_state 78 | .iter() 79 | .map(move |luid| self.universe.get_index(*luid).unwrap()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use i1::*; 2 | 3 | use reedline_repl_rs::clap::{self, builder::TypedValueParser, Arg, Command}; 4 | use reedline_repl_rs::{Error, Repl}; 5 | use std::sync::{Arc, RwLock}; 6 | use uuid::Uuid; 7 | 8 | #[derive(Clone)] 9 | struct CommitUuidParser { 10 | state: Arc>, 11 | } 12 | 13 | impl From<&Arc>> for CommitUuidParser { 14 | fn from(state: &Arc>) -> Self { 15 | Self { 16 | state: state.clone(), 17 | } 18 | } 19 | } 20 | 21 | impl TypedValueParser for CommitUuidParser { 22 | type Value = Uuid; 23 | fn parse_ref( 24 | &self, 25 | cmd: &Command, 26 | arg: Option<&Arg>, 27 | value: &std::ffi::OsStr, 28 | ) -> Result { 29 | let inner = reedline_repl_rs::clap::builder::StringValueParser::default().parse( 30 | cmd, 31 | arg, 32 | value.into(), 33 | )?; 34 | let uuid = Uuid::from_base64url(&inner).map_err(|_| { 35 | clap::Error::raw( 36 | clap::error::ErrorKind::InvalidValue, 37 | format!("Invalid UUID: {}", inner), 38 | ) 39 | })?; 40 | let state = self.state.read().unwrap(); 41 | if state 42 | .universe 43 | .get_index_of(&uuid) 44 | .map(|luid| state.commits.contains_key(&luid)) 45 | .unwrap_or(false) 46 | { 47 | Ok(uuid) 48 | } else { 49 | Err(clap::Error::raw( 50 | clap::error::ErrorKind::InvalidValue, 51 | format!("Unknown commit: {}", inner), 52 | )) 53 | } 54 | } 55 | fn possible_values(&self) -> Option + '_>> { 56 | let state = self.state.read().unwrap(); 57 | let commits: Vec = state.commits().map(|id| id.as_base64url()).collect(); 58 | Some(Box::new( 59 | commits.into_iter().map(clap::builder::PossibleValue::new), 60 | )) 61 | } 62 | } 63 | 64 | fn main() { 65 | let state = Arc::new(RwLock::new(TotalState::new())); 66 | let mut repl: Repl<_, Error> = Repl::new(state.clone()) 67 | .with_name("uuid_set") 68 | .with_partial_completions(true) 69 | .with_command(Command::new("add"), |_, state| { 70 | let mut state = state.write().unwrap(); 71 | let new_uuid = state.add(); 72 | Ok(Some(format!( 73 | "Created new entity {} in working set", 74 | new_uuid.as_base64url() 75 | ))) 76 | }) 77 | .with_command( 78 | Command::new("checkout").arg( 79 | Arg::new("uuid") 80 | .required(true) 81 | .index(1) 82 | .value_parser(CommitUuidParser::from(&state)), 83 | ), 84 | |matches, state| { 85 | let mut state = state.write().unwrap(); 86 | let uuid = matches.get_one::("uuid").unwrap(); 87 | state.checkout(uuid); 88 | Ok(Some(format!("Checked out commit {}", uuid.as_base64url()))) 89 | }, 90 | ) 91 | .with_command(Command::new("commit"), |_, state| { 92 | let mut state = state.write().unwrap(); 93 | let (patch_id, patch) = state.commit(); 94 | Ok(Some(format!( 95 | "Saved new patch {} from [{}] to {}", 96 | patch_id.as_base64url(), 97 | patch 98 | .source_commits 99 | .iter() 100 | .map(|id| id.as_base64url()) 101 | .collect::>() 102 | .join(", "), 103 | patch.target_commit.as_base64url(), 104 | ))) 105 | }) 106 | .with_command(Command::new("graph"), |_, state| { 107 | let state = state.read().unwrap(); 108 | let graph = state.graph(); 109 | Ok(Some( 110 | metro::to_string( 111 | graph 112 | .iter() 113 | .map(|e| e.into()) 114 | .collect::>() 115 | .as_slice(), 116 | ) 117 | .unwrap() 118 | .trim_end_matches('\n') 119 | .to_string(), 120 | )) 121 | }) 122 | .with_command(Command::new("load"), |_, state| { 123 | let mut state = state.write().unwrap(); 124 | state.load_all_patches(); 125 | Ok(None) 126 | }) 127 | .with_command( 128 | Command::new("merge").arg( 129 | Arg::new("uuid") 130 | .required(true) 131 | .index(1) 132 | .value_parser(CommitUuidParser::from(&state)), 133 | ), 134 | |matches, state| { 135 | let mut state = state.write().unwrap(); 136 | let uuid = matches.get_one::("uuid").unwrap(); 137 | state 138 | .merge(uuid) 139 | .map(|_| Some(format!("Merged {} into working set", uuid.as_base64url()))) 140 | .or_else(|e| Ok(Some(e.to_string()))) 141 | }, 142 | ) 143 | .with_command(Command::new("count"), |_, state| { 144 | let state = state.read().unwrap(); 145 | Ok(Some(format!("Entities: {:?}", state.count()))) 146 | }) 147 | .with_command(Command::new("commits"), |_, state| { 148 | let state = state.read().unwrap(); 149 | Ok(Some( 150 | state 151 | .commits() 152 | .map(|uuid| format!("* {}", uuid.as_base64url())) 153 | .collect::>() 154 | .join("\n"), 155 | )) 156 | }) 157 | .with_command(Command::new("heads"), |_, state| { 158 | let state = state.read().unwrap(); 159 | Ok(Some( 160 | state 161 | .heads() 162 | .map(|uuid| format!("* {}", uuid.as_base64url())) 163 | .collect::>() 164 | .join("\n"), 165 | )) 166 | }) 167 | .with_command(Command::new("list"), |_, state| { 168 | let state = state.read().unwrap(); 169 | Ok(Some( 170 | state 171 | .list() 172 | .map(|uuid| format!("* {}", uuid.as_base64url())) 173 | .collect::>() 174 | .join("\n"), 175 | )) 176 | }); 177 | let _ = repl.run(); 178 | } 179 | -------------------------------------------------------------------------------- /src/patch.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use rkyv::{Archive, Deserialize, Serialize}; 3 | use std::collections::{BTreeSet, HashMap}; 4 | use tinyvec::TinyVec; 5 | 6 | #[derive(Default, Archive, Deserialize, Serialize)] 7 | #[archive(check_bytes)] 8 | pub struct UuidSetPatch { 9 | pub deletions: BTreeSet, 10 | pub merges: HashMap, 11 | pub additions: BTreeSet, 12 | } 13 | 14 | impl UuidSetPatch { 15 | pub fn clear(&mut self) { 16 | self.deletions.clear(); 17 | self.merges.clear(); 18 | self.additions.clear(); 19 | } 20 | pub fn is_empty(&self) -> bool { 21 | self.deletions.is_empty() && self.merges.is_empty() && self.additions.is_empty() 22 | } 23 | } 24 | 25 | pub type UniversePatch = UuidSetPatch; 26 | 27 | #[derive(Archive, Deserialize, Serialize)] 28 | #[archive(check_bytes)] 29 | pub enum AdditionKind { 30 | NewSort, 31 | NewEntity(Uuid), 32 | } 33 | 34 | pub type AdditionKinds = Vec; 35 | 36 | #[derive(Default, Archive, Deserialize, Serialize)] 37 | #[archive(check_bytes)] 38 | pub struct ContextPatch { 39 | pub deletions: BTreeSet>, 40 | pub additions: HashMap, Uuid>, 41 | } 42 | 43 | #[derive(Default, Archive, Deserialize, Serialize)] 44 | #[archive(check_bytes)] 45 | pub struct Patch { 46 | pub target_commit: Uuid, 47 | pub source_commits: TinyVec<[Uuid; 2]>, 48 | pub universe_patch: UniversePatch, 49 | pub addition_kinds: AdditionKinds, 50 | pub context_patch: ContextPatch, 51 | } 52 | 53 | impl Patch { 54 | pub fn clear(&mut self) { 55 | self.target_commit = Uuid::nil(); 56 | self.source_commits.clear(); 57 | self.universe_patch.clear(); 58 | } 59 | pub fn is_empty(&self) -> bool { 60 | self.universe_patch.is_empty() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/state/commit.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::patch::*; 3 | use crate::state::*; 4 | use rkyv::ser::{ 5 | serializers::{AllocScratch, CompositeSerializer, FallbackScratch, HeapScratch, WriteSerializer}, 6 | Serializer, 7 | }; 8 | use std::{ 9 | cell::RefCell, 10 | fs::{self, File}, 11 | thread_local, 12 | }; 13 | 14 | thread_local! { 15 | static RKYV_SCRATCH : RefCell, AllocScratch>> = RefCell::new(FallbackScratch::new(HeapScratch::new(), AllocScratch::new())); 16 | } 17 | 18 | impl TotalState { 19 | pub fn commit(&mut self) -> (Uuid, &Patch) { 20 | // make a new UUID for the patch 21 | let new_patch_id = Uuid::now_v7(); 22 | // make a new UUID for the commit 23 | let new_commit_id = Uuid::now_v7(); 24 | // set the target_commit_id of the patch to the new_commit_id 25 | self.working_patch.target_commit = new_commit_id; 26 | 27 | // convert the patch UUID to a filename-friendly string 28 | let new_patch_id_str = new_patch_id.as_base64url(); 29 | 30 | // open the file for writing 31 | let patch_dir = Self::get_patch_dir(); 32 | fs::create_dir_all(&patch_dir).unwrap(); 33 | let mut file = File::create(patch_dir.join("patch_".to_string() + &new_patch_id_str)).unwrap(); 34 | 35 | // Serialize the patch to the file using rkyv::ser::serializers::WriteSerializer 36 | RKYV_SCRATCH.with(|scratch| { 37 | let scratch_inner = scratch.replace(FallbackScratch::new( 38 | HeapScratch::new(), 39 | AllocScratch::new(), 40 | )); 41 | let mut serializer: CompositeSerializer, FallbackScratch<_, _>, _> = 42 | CompositeSerializer::new( 43 | WriteSerializer::new(&mut file), 44 | scratch_inner, 45 | rkyv::Infallible, 46 | ); 47 | 48 | serializer.serialize_value(&self.working_patch).unwrap(); 49 | 50 | serializer.into_components().1 // return borrowed scratch space 51 | }); 52 | 53 | let written_patch = std::mem::take(&mut self.working_patch); 54 | self.working_patch.clear(); 55 | let new_patch_luid = self.index_patch(new_patch_id, written_patch); 56 | process_patch( 57 | &mut self.universe, 58 | &mut self.version_cache, 59 | &mut self.commits, 60 | &mut self.heads, 61 | &self.patches, 62 | new_patch_luid, 63 | ); 64 | self.working_patch.source_commits.push(new_commit_id); 65 | self.working_patch.target_commit = Default::default(); 66 | 67 | (new_patch_id, self.patches.get(&new_patch_luid).unwrap()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/state/graph.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::TotalState; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Clone, Debug)] 6 | pub enum GraphEvent { 7 | StartTrack(usize), 8 | StopTrack(usize), 9 | Station(usize, String), 10 | SplitTrack(usize, usize), 11 | JoinTrack(usize, usize), 12 | NoEvent, 13 | } 14 | use GraphEvent::*; 15 | 16 | impl<'a> From<&'a GraphEvent> for metro::Event<'a> { 17 | fn from(event: &'a GraphEvent) -> Self { 18 | match event { 19 | GraphEvent::StartTrack(track_id) => metro::Event::StartTrack(*track_id), 20 | GraphEvent::StopTrack(track_id) => metro::Event::StopTrack(*track_id), 21 | GraphEvent::Station(track_id, station_name) => { 22 | metro::Event::Station(*track_id, station_name.as_str()) 23 | } 24 | GraphEvent::SplitTrack(track_id, new_track_id) => { 25 | metro::Event::SplitTrack(*track_id, *new_track_id) 26 | } 27 | GraphEvent::JoinTrack(track_id, new_track_id) => { 28 | metro::Event::JoinTrack(*track_id, *new_track_id) 29 | } 30 | GraphEvent::NoEvent => metro::Event::NoEvent, 31 | } 32 | } 33 | } 34 | 35 | impl TotalState { 36 | pub fn graph(&self) -> Vec { 37 | let mut graph = Vec::with_capacity(self.commits.len()); 38 | let mut tracks: HashMap = HashMap::new(); 39 | let mut n_tracks_total: usize = 0; 40 | let mut track: usize; 41 | for (commit_luid, reachable_by) in self.commits.iter().rev() { 42 | let reached_by = reachable_by.get(0).unwrap(); 43 | if let Some(&existing_track) = tracks.get(commit_luid) { 44 | track = existing_track; 45 | } else { 46 | track = n_tracks_total; 47 | n_tracks_total += 1; 48 | tracks.insert(*commit_luid, track); 49 | graph.push(NoEvent); 50 | graph.push(StartTrack(track)); 51 | } 52 | graph.push(Station( 53 | track, 54 | format!( 55 | "{} <- {}", 56 | self 57 | .universe 58 | .get_index(*commit_luid) 59 | .unwrap() 60 | .as_base64url(), 61 | self 62 | .universe 63 | .get_index(reached_by.1) 64 | .unwrap() 65 | .as_base64url() 66 | ), 67 | )); 68 | if reached_by.0.is_empty() { 69 | graph.push(NoEvent); 70 | graph.push(StopTrack(track)); 71 | graph.push(NoEvent); 72 | } else { 73 | for &parent in reached_by.0.iter().skip(1) { 74 | let parent_track = n_tracks_total; 75 | n_tracks_total += 1; 76 | if let Some(existing_track) = tracks.insert(parent, parent_track) { 77 | graph.push(SplitTrack(track, parent_track)); 78 | graph.push(JoinTrack(parent_track, existing_track)); 79 | tracks.insert(parent, existing_track); 80 | } else { 81 | graph.push(SplitTrack(track, parent_track)); 82 | } 83 | } 84 | let primary_parent = reached_by.0[0]; 85 | if let Some(&existing_track) = tracks.get(&primary_parent) { 86 | graph.push(JoinTrack(track, existing_track)); 87 | } else { 88 | tracks.insert(primary_parent, track); 89 | } 90 | } 91 | } 92 | graph 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/state/id_lookups.rs: -------------------------------------------------------------------------------- 1 | use crate::state::total_state::*; 2 | use crate::version::*; 3 | 4 | impl TotalState { 5 | pub fn luid_to_uuid(&self, luid: Luid) -> Option { 6 | self.universe.get_index(luid) 7 | } 8 | pub fn uuid_to_luid(&self, uuid: &Uuid) -> Option { 9 | self.universe.get_index_of(uuid) 10 | } 11 | } 12 | 13 | impl Version { 14 | pub fn vlid_to_luid(&self, vlid: Vlid) -> Option { 15 | self.version_universe.select(vlid) 16 | } 17 | pub fn luid_to_vlid(&self, luid: Luid) -> Option { 18 | match self.version_universe.contains(luid) { 19 | false => None, 20 | true => Some(self.version_universe.rank(luid)-1), 21 | } 22 | } 23 | pub fn slids0_to_vlid(&self, slid: Slid) -> Option { 24 | self.s0.select(slid) 25 | } 26 | pub fn vlid_to_slids0(&self, vlid: Vlid) -> Option { 27 | match self.s0.contains(vlid) { 28 | false => None, 29 | true => Some(self.s0.rank(vlid)-1), 30 | } 31 | } 32 | pub fn slids0_of_vlid(&self, vlid: Vlid) -> Option { 33 | for slids0 in 0..self.s0.len() { 34 | if self.s0i[slids0].contains(vlid) { 35 | return Some(slids0); 36 | } 37 | } 38 | } 39 | pub fn slid_to_vlid(&self, slids0: Slid, slid: Slid) -> Option { 40 | self.s0i[slids0 as usize].select(slid) 41 | } 42 | pub fn vlid_to_slid(&self, slids0: Slid, vlid: Vlid) -> Option { 43 | match self.s0i[slids0 as usize].contains(vlid) { 44 | false => None, 45 | true => Some(self.s0i[slids0 as usize].rank(vlid)-1), 46 | } 47 | } 48 | pub fn vlid_to_slids0_and_slid(&self, vlid: Vlid) -> Option<(Slid, Slid)> { 49 | let slids0 = self.vlid_to_slids0(vlid)?; 50 | let slid = self.vlid_to_slid(slids0, vlid)?; 51 | Some((slids0, slid)) 52 | } 53 | pub fn luid_to_slids0_and_slid(&self, luid: Luid) -> Option<(Slid, Slid)> { 54 | let vlid = self.luid_to_vlid(luid)?; 55 | self.vlid_to_slids0_and_slid(vlid) 56 | } 57 | pub fn uuid_to_slids0_and_slid(&self, uuid: &Uuid) -> Option<(Slid, Slid)> { 58 | let luid = self.uuid_to_luid(uuid)?; 59 | self.luid_to_slids0_and_slid(luid) 60 | } 61 | pub fn luid_to_slids0(&self, luid: Luid) -> Option { 62 | let vlid = self.luid_to_vlid(luid)?; 63 | self.vlid_to_slids0(vlid) 64 | } 65 | pub fn uuid_to_slids0(&self, uuid: &Uuid) -> Option { 66 | let luid = self.uuid_to_luid(uuid)?; 67 | self.luid_to_slids0(luid) 68 | } 69 | } -------------------------------------------------------------------------------- /src/state/load_patch.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::patch::*; 3 | use crate::state::*; 4 | use memmap2::Mmap; 5 | use rkyv::{check_archived_root, Deserialize}; 6 | use std::collections::BTreeSet; 7 | use std::fs::{self, File}; 8 | use tinyvec::TinyVec; 9 | 10 | impl TotalState { 11 | pub fn load_all_patches(&mut self) { 12 | let patch_dir = Self::get_patch_dir(); 13 | fs::create_dir_all(&patch_dir).unwrap(); 14 | let patch_files = std::fs::read_dir(patch_dir) 15 | .unwrap() 16 | .map(|entry| entry.unwrap().path()) 17 | .collect::>(); 18 | let len = patch_files.len(); 19 | for (i, path) in patch_files.iter().enumerate() { 20 | let f = File::open(path).unwrap(); 21 | let mmap = unsafe { Mmap::map(&f).unwrap() }; 22 | let patch = check_archived_root::(mmap.as_ref()).unwrap(); 23 | let patch_uuid_str = path 24 | .file_name() 25 | .unwrap() 26 | .to_str() 27 | .unwrap() 28 | .strip_prefix("patch_") 29 | .unwrap(); 30 | eprintln!("Loading patch {}/{}: {:?}", i + 1, len, patch_uuid_str); 31 | let patch_uuid = Uuid::from_base64url(patch_uuid_str).unwrap(); 32 | // (note: this deserialization may perform unnecessary copies, but is memory-safe) 33 | self.index_patch( 34 | patch_uuid, 35 | patch.deserialize(&mut rkyv::Infallible).unwrap(), 36 | ); 37 | } 38 | let patch_luids: Vec<(Luid, Luid)> = self 39 | .patches 40 | .iter() 41 | .map(|(k, v)| (*k, self.universe.get_index_of(&v.target_commit).unwrap())) 42 | .collect(); // TODO: this is a temporary hack to avoid borrowing self 43 | for (patch_luid, target_commit_luid) in patch_luids { 44 | if !self.version_cache.contains_key(&target_commit_luid) { 45 | process_patch( 46 | &mut self.universe, 47 | &mut self.version_cache, 48 | &mut self.commits, 49 | &mut self.heads, 50 | &self.patches, 51 | patch_luid, 52 | ); 53 | } 54 | } 55 | if let Some(&head) = self.heads.last() { 56 | self.checkout_luid(head); 57 | } 58 | } 59 | 60 | pub(in crate::state) fn index_patch(&mut self, patch_uuid: Uuid, patch: Patch) -> Luid { 61 | // add patch to universe 62 | let patch_luid = self.universe.insert_full(patch_uuid).0; 63 | // add patch to patches 64 | self.patches.insert(patch_luid, patch); 65 | let patch_ref = self.patches.get(&patch_luid).unwrap(); 66 | // add patch contents to universe 67 | patch_ref.universe_patch.additions.iter().for_each(|uuid| { 68 | self.universe.insert(*uuid); 69 | }); 70 | // add target commit to universe 71 | let target_commit_luid = self.universe.insert_full(patch_ref.target_commit).0; 72 | // add patch to commits 73 | self.commits.insert( 74 | target_commit_luid, 75 | TinyVec::from([( 76 | patch_ref 77 | .source_commits 78 | .iter() 79 | .map(|uuid| self.universe.insert_full(*uuid).0) 80 | .collect(), 81 | patch_luid, 82 | )]), 83 | ); 84 | patch_luid 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/state/merge.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::TotalState; 3 | use std::collections::{BTreeMap, BTreeSet}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub enum MergeError { 7 | WorkingPatchNotEmpty, 8 | CommitNotFound, 9 | DetachedHead, 10 | NoCommonAncestor, 11 | } 12 | 13 | impl std::fmt::Display for MergeError { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | match self { 16 | MergeError::WorkingPatchNotEmpty => write!(f, "Working patch is not empty"), 17 | MergeError::CommitNotFound => write!(f, "Commit not found"), 18 | MergeError::DetachedHead => write!(f, "Detached head"), 19 | MergeError::NoCommonAncestor => write!(f, "No common ancestor"), 20 | } 21 | } 22 | } 23 | 24 | impl TotalState { 25 | pub fn lca(&self, commit0: &Uuid, commit1: &Uuid) -> Option { 26 | // Finding a least common ancestor is a very similar problem to rendering the commit graph, but 27 | // (a) we need to start from just these two commits, and only consider the commits that are 28 | // reachable from one of them, and (b) we need to stop at the first moment (after introducing 29 | // these two commits as two initial tracks) when there is only one track. 30 | let mut tracks: BTreeMap = BTreeMap::new(); 31 | let mut n_tracks_total = 0; 32 | let mut track: usize; 33 | let mut queue: BTreeSet = BTreeSet::new(); 34 | let mut lca: Option = None; 35 | // Begin by inserting the two leaf commits to the queue. 36 | queue.insert(*commit0); 37 | queue.insert(*commit1); 38 | let commit0_luid = self.universe.get_index_of(commit0).unwrap(); 39 | let commit1_luid = self.universe.get_index_of(commit1).unwrap(); 40 | tracks.insert(commit0_luid, 0); 41 | tracks.insert(commit1_luid, 1); 42 | while let Some(uuid) = queue.pop_last() { 43 | let luid = self.universe.get_index_of(&uuid).unwrap(); 44 | let reached_by = self.commits.get(&luid).unwrap(); 45 | if let Some(existing_track) = tracks.remove(&luid) { 46 | track = existing_track; 47 | } else { 48 | track = n_tracks_total; 49 | n_tracks_total += 1; 50 | } 51 | if tracks.is_empty() { 52 | lca = Some(luid); 53 | break; 54 | } 55 | // TODO: handle distinct incoming morphisms 56 | let parents = reached_by.iter().flat_map(|x| x.0.iter()); 57 | if let Some(&primary_parent) = parents.clone().next() { 58 | tracks.remove(&luid); 59 | if tracks.get(&primary_parent).is_none() { 60 | tracks.insert(primary_parent, track); 61 | } 62 | for &parent_luid in parents { 63 | let &parent_uuid = self.universe.get_index(parent_luid).unwrap(); 64 | queue.insert(parent_uuid); 65 | } 66 | } 67 | } 68 | eprintln!( 69 | "LCA: {:?}", 70 | lca.and_then(|lca| self.universe.get_index(lca).map(|x| x.as_base64url())) 71 | ); 72 | lca 73 | } 74 | 75 | pub fn merge(&mut self, commit: &Uuid) -> Result<(), MergeError> { 76 | let commit_luid = self 77 | .universe 78 | .get_index_of(commit) 79 | .ok_or(MergeError::CommitNotFound)?; 80 | self.merge_luid(commit_luid) 81 | } 82 | 83 | fn merge_luid(&mut self, other_commit_luid: Luid) -> Result<(), MergeError> { 84 | if !self.working_patch.is_empty() { 85 | return Err(MergeError::WorkingPatchNotEmpty); 86 | } 87 | /* 88 | let other_version = self.version_cache.get(&commit) 89 | .ok_or(()).map_err(|_| { 90 | let patch_spec = self.commits.get(&commit) 91 | .and_then(|x| x.get(0)) 92 | .ok_or(MergeError::CommitNotFound)?; 93 | let patch_luid = patch_spec.1; 94 | process_patch(&mut self.universe, &mut self.version_cache, &mut self.commits, &mut self.heads, &self.patches, patch_luid); 95 | self.version_cache.get(&commit) 96 | .ok_or(MergeError::CommitNotFound)? 97 | })?; 98 | */ 99 | let this_commit_uuid = *self 100 | .working_patch 101 | .source_commits 102 | .get(0) 103 | .ok_or(MergeError::DetachedHead)?; 104 | let _this_commit_luid = self.universe.get_index_of(&this_commit_uuid).unwrap(); 105 | let other_commit_uuid = *self.universe.get_index(other_commit_luid).unwrap(); 106 | let _lca = self 107 | .lca(&this_commit_uuid, &other_commit_uuid) 108 | .ok_or(MergeError::NoCommonAncestor)?; 109 | 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod total_state; 2 | pub use total_state::*; 3 | mod commit; 4 | pub use commit::*; 5 | mod graph; 6 | pub use graph::*; 7 | mod load_patch; 8 | pub use load_patch::*; 9 | mod merge; 10 | pub use merge::*; 11 | mod process_patch; 12 | pub use process_patch::process_patch; 13 | 14 | // TODO: 15 | // * implement merges 16 | // * implement deletions 17 | // * implement conflict checks 18 | // * implement namings: each naming associates a fully-qualified name to a Uuid and also has a Uuid of its own, and an optional message. 19 | // * implement authenticated states: a SHA3 hash of a naming concatenated with a canonicalized working state 20 | // * Add a "revert" command that reverts the working state to the last committed state 21 | // * Add a "revert" command that reverts the working state to a specific commit 22 | // * Add a "merge" command that merges the working state with a specific commit 23 | -------------------------------------------------------------------------------- /src/state/process_patch.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::patch::*; 3 | use crate::state::*; 4 | use crate::version::*; 5 | use roaring::RoaringTreemap; 6 | use std::ops::*; 7 | 8 | pub fn process_patch( 9 | universe: &mut Universe, 10 | version_cache: &mut VersionCache, 11 | commits: &mut Commits, 12 | heads: &mut Heads, 13 | patches: &Patches, 14 | patch_luid: Luid, 15 | ) { 16 | let patch = patches.get(&patch_luid).unwrap(); 17 | let mut version_universe: RoaringTreemap = RoaringTreemap::new(); 18 | for source_commit in patch.source_commits.iter() { 19 | let source_commit_luid = universe.get_index_of(source_commit).unwrap(); 20 | let mut source_version = version_cache.get(&source_commit_luid); 21 | if source_version.is_none() { 22 | eprintln!( 23 | "Info: patch {:?} depends on {:?} which has not been processed yet. Processing it now.", 24 | universe.get_index(patch_luid).unwrap().as_base64url(), 25 | universe 26 | .get_index(source_commit_luid) 27 | .unwrap() 28 | .as_base64url() 29 | ); 30 | process_patch( 31 | universe, 32 | version_cache, 33 | commits, 34 | heads, 35 | patches, 36 | commits.get(&source_commit_luid).unwrap().get(0).unwrap().1, 37 | ); 38 | source_version = Some(version_cache.get(&source_commit_luid).unwrap()); 39 | } 40 | heads.remove(&source_commit_luid); 41 | version_universe.bitor_assign(&source_version.unwrap().version_universe); 42 | } 43 | { 44 | // Handle universe patch 45 | let universe_patch = &patch.universe_patch; 46 | universe_patch.deletions.iter().for_each(|uuid| { 47 | version_universe.remove(universe.get_index_of(uuid).unwrap() as u64); 48 | }); 49 | universe_patch 50 | .merges 51 | .iter() 52 | .for_each(|(uuid, merged_into)| { 53 | if uuid != merged_into { 54 | version_universe.remove(universe.get_index_of(uuid).unwrap() as u64); 55 | } 56 | }); 57 | version_universe.extend( 58 | universe_patch 59 | .additions 60 | .iter() 61 | .map(|uuid| universe.get_index_of(uuid).unwrap() as u64), 62 | ); 63 | } 64 | { 65 | // TODO: Handle addition kinds 66 | let addition_kinds = &patch.addition_kinds; 67 | for (_i, kind) in addition_kinds.iter().enumerate() { 68 | match kind { 69 | AdditionKind::NewSort => {} 70 | AdditionKind::NewEntity(_sort_uuid) => {} 71 | } 72 | } 73 | } 74 | { // TODO: Handle context patch 75 | } 76 | let target_commit_luid = universe.insert_full(patch.target_commit).0; 77 | heads.insert(target_commit_luid); 78 | version_cache.insert( 79 | target_commit_luid, 80 | Version { 81 | version_universe: version_universe.into_iter().collect(), 82 | s0: Default::default(), 83 | s0i: Default::default(), 84 | ctx: Default::default(), 85 | }, 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /src/state/total_state.rs: -------------------------------------------------------------------------------- 1 | use crate::id::*; 2 | use crate::patch::*; 3 | use crate::version::*; 4 | use indexmap::IndexSet; 5 | use std::collections::{BTreeMap, BTreeSet, HashMap}; 6 | use tinyvec::TinyVec; 7 | 8 | pub type Universe = IndexSet; 9 | // target commit id, source commit id(s), patch id 10 | pub type Commits = BTreeMap, Luid); 1]>>; 11 | pub type Patches = BTreeMap; 12 | pub type Heads = BTreeSet; 13 | pub type VersionCache = HashMap; // TODO: consider alternative data structures 14 | pub(crate) type WorkingPatch = Patch; 15 | pub(crate) type WorkingState = IndexSet; 16 | 17 | #[derive(Default)] 18 | pub struct TotalState { 19 | pub universe: Universe, 20 | pub commits: Commits, 21 | pub patches: Patches, 22 | pub heads: Heads, 23 | pub version_cache: VersionCache, 24 | pub(crate) working_patch: WorkingPatch, 25 | pub(crate) working_state: WorkingState, 26 | } 27 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use roaring::RoaringTreemap; 3 | 4 | pub struct Version { 5 | pub version_universe: RoaringTreemap, // of Luid 6 | pub s0: RoaringTreemap, // of Vlid 7 | pub s0i: Vec, // Slid(s0) -> Vlid 8 | pub ctx: Context, 9 | } 10 | --------------------------------------------------------------------------------