diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-05-12 12:39:10 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-05-12 12:39:10 +0200 |
commit | 1e4dff1995833538f436b381bc0450a7c0080bad (patch) | |
tree | 2dc620ac9ea683cbee412b8d5818b3992462677c | |
download | back-1e4dff1995833538f436b381bc0450a7c0080bad.zip |
chore: Initial commit
Based on the version that was previously in `vhack.eu/nixos-server/pkgs/by-name/ba/back`.
-rw-r--r-- | .editorconfig | 27 | ||||
-rw-r--r-- | .envrc | 22 | ||||
-rw-r--r-- | .gitignore | 17 | ||||
-rw-r--r-- | .reuse/templates/default.jinja2 | 30 | ||||
-rw-r--r-- | Cargo.lock | 3044 | ||||
-rw-r--r-- | Cargo.toml | 104 | ||||
-rw-r--r-- | README.md | 82 | ||||
-rw-r--r-- | assets/style.css | 383 | ||||
-rw-r--r-- | cog.toml | 36 | ||||
-rw-r--r-- | contrib/config.json | 6 | ||||
-rw-r--r-- | contrib/config.json.license | 11 | ||||
-rw-r--r-- | flake.lock | 139 | ||||
-rw-r--r-- | flake.lock.license | 10 | ||||
-rw-r--r-- | flake.nix | 136 | ||||
-rw-r--r-- | nix/module.nix | 102 | ||||
-rw-r--r-- | nix/package.nix | 38 | ||||
-rw-r--r-- | rustfmt.toml | 79 | ||||
-rwxr-xr-x | scripts/optimize_images.sh | 136 | ||||
-rw-r--r-- | src/cli.rs | 25 | ||||
-rw-r--r-- | src/config/mod.rs | 137 | ||||
-rw-r--r-- | src/error/mod.rs | 135 | ||||
-rw-r--r-- | src/main.rs | 54 | ||||
-rw-r--r-- | src/web/generate/mod.rs | 236 | ||||
-rw-r--r-- | src/web/mod.rs | 138 | ||||
-rw-r--r-- | src/web/responses.rs | 61 | ||||
-rw-r--r-- | templates/issue.html | 74 | ||||
-rw-r--r-- | templates/issues.html | 77 | ||||
-rw-r--r-- | templates/repos.html | 62 | ||||
-rw-r--r-- | tests/base.nix | 216 | ||||
-rw-r--r-- | treefmt.nix | 88 | ||||
-rwxr-xr-x | update.sh | 17 |
31 files changed, 5722 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74297ea --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{nix}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..bd488c8 --- /dev/null +++ b/.envrc @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use flake || use nix +watch_file flake.nix + +PATH_add ./scripts + +if on_git_branch; then + echo && git status --short --branch +fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..702b917 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +# build +/target +/result + +# dev env +.direnv diff --git a/.reuse/templates/default.jinja2 b/.reuse/templates/default.jinja2 new file mode 100644 index 0000000..81706dc --- /dev/null +++ b/.reuse/templates/default.jinja2 @@ -0,0 +1,30 @@ +{# +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. +#} + +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +{% for copyright_line in copyright_lines %} +{{ copyright_line }} +{% endfor %} +{% for contributor_line in contributor_lines %} +SPDX-FileContributor: {{ contributor_line }} +{% endfor %} +{% for expression in spdx_expressions %} +SPDX-License-Identifier: {{ expression }} +{% endfor %} + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..61a5656 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3044 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atom_syndication" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f68d23e2cb4fd958c705b91a6b4c80ceeaf27a9e11651272a8389d5ce1a4a3" +dependencies = [ + "chrono", + "derive_builder", + "diligent-date-parser", + "never", + "quick-xml", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "back" +version = "0.1.0" +dependencies = [ + "bytes", + "chrono", + "clap", + "gix", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "markdown", + "rinja", + "rss", + "serde", + "serde_json", + "sha2", + "stderrlog", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" + +[[package]] +name = "cc" +version = "1.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "diligent-date-parser" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ede7d79366f419921e2e2f67889c12125726692a313bffb474bd5f37a581e9" +dependencies = [ + "chrono", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gix" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8" +dependencies = [ + "gix-actor", + "gix-archive", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-dir", + "gix-discover", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-mailmap", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-status", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-state", + "gix-worktree-stream", + "once_cell", + "parking_lot", + "regex", + "signal-hook", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-actor" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b300e6e4f31f3f6bd2de5e2b0caab192ced00dc0fcd0f7cc56e28c575c8e1ff" +dependencies = [ + "bstr", + "gix-date", + "gix-utils", + "itoa", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-archive" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e59cc5867c40065122324265d99416ca0ddeb51d007870868b5f835423fb6c" +dependencies = [ + "bstr", + "gix-date", + "gix-object", + "gix-worktree-stream", + "jiff", + "thiserror", +] + +[[package]] +name = "gix-attributes" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e26b3ac280ddb25bb6980d34f4a82ee326f78bf2c6d4ea45eef2d940048b8e" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-command" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f47f3fb4ba33644061e8e0e1030ef2a937d42dc969553118c320a205a9fb28" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951" +dependencies = [ + "bstr", + "gix-chunk", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", + "winnow", +] + +[[package]] +name = "gix-config-value" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439d62e241dae2dffd55bfeeabe551275cf9d9f084c5ebc6b48bad49d03285b7" +dependencies = [ + "bitflags", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1c7307e36026b6088e5b12014ffe6d4f509c911ee453e22a7be4003a159c9b" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139d1d52b21741e3f0c72b0fc65e1ff34d4eaceb100ef529d182725d2e09b8cb" +dependencies = [ + "bstr", + "itoa", + "jiff", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-diff" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252" +dependencies = [ + "bstr", + "gix-attributes", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "imara-diff", + "thiserror", +] + +[[package]] +name = "gix-dir" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e6e2dc5b8917142d0ffe272209d1671e45b771e433f90186bc71c016792e87" +dependencies = [ + "bstr", + "gix-discover", + "gix-fs", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-trace", + "gix-utils", + "gix-worktree", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" +dependencies = [ + "bytes", + "bytesize", + "crc32fast", + "crossbeam-channel", + "flate2", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "parking_lot", + "prodash", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-filter" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90c21f0d61778f518bbb7c431b00247bf4534b2153c3e85bcf383876c55ca6c" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline-blocking", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-glob" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2926b03666e83b8d01c10cf06e5733521aacbd2d97179a4c9b1fdddabb9e937d" +dependencies = [ + "bitflags", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +dependencies = [ + "gix-hash", + "hashbrown 0.14.5", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" +dependencies = [ + "bitflags", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.14.5", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-mailmap" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e7c52eb13d84ad26030d07a2c2975ba639dd1400a7996e6966c5aef617ed829" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "thiserror", +] + +[[package]] +name = "gix-negotiate" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.49.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-odb" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450" +dependencies = [ + "clru", + "gix-chunk", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "memmap2", + "smallvec", + "thiserror", + "uluru", +] + +[[package]] +name = "gix-packetline" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddc034bc67c848e4ef7596ab5528cd8fd439d310858dbe1ce8b324f25deb91c" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-packetline-blocking" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44880f028ba46d6cf37a66d27a300310c6b51b8ed0e44918f93df061168e2f3" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567f65fec4ef10dfab97ae71f26a27fd4d7fe7b8e3f90c8a58551c41ff3fb65b" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-pathspec" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" +dependencies = [ + "bitflags", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + +[[package]] +name = "gix-prompt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d024a3fe3993bbc17733396d2cefb169c7a9d14b5b71dafb7f96e3962b7c3128" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-quote" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd" +dependencies = [ + "bstr", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-refspec" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" +dependencies = [ + "bitflags", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" +dependencies = [ + "bitflags", + "gix-path", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "gix-shallow" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror", +] + +[[package]] +name = "gix-status" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072099c2415cfa5397df7d47eacbcb6016d2cd17e0d674c74965e6ad1b17289f" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-worktree", + "portable-atomic", + "thiserror", +] + +[[package]] +name = "gix-submodule" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "signal-hook", + "signal-hook-registry", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" + +[[package]] +name = "gix-transport" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" +dependencies = [ + "bstr", + "gix-features", + "gix-path", + "percent-encoding", + "thiserror", + "url", +] + +[[package]] +name = "gix-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" +dependencies = [ + "bstr", + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "gix-worktree" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" +dependencies = [ + "bstr", + "gix-attributes", + "gix-features", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] + +[[package]] +name = "gix-worktree-state" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81e31496d034dbdac87535b0b9d4659dbbeabaae1045a0dce7c69b5d16ea7d6" +dependencies = [ + "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", + "thiserror", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7731f9e7ffc45f1f79d6601a37169be0697d4e2bd015495b4f53dffd80b42e" +dependencies = [ + "gix-attributes", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-traverse", + "parking_lot", + "thiserror", +] + +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "human_format" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3b1f728c459d27b12448862017b96ad4767b1ec2ec5e6434e99f1577f085b8" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "imara-diff" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" +dependencies = [ + "hashbrown 0.15.3", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", +] + +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "never" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prodash" +version = "29.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +dependencies = [ + "bytesize", + "human_format", + "log", + "parking_lot", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "encoding_rs", + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rinja" +version = "0.4.0+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67047e6d76ddf18132ef41c8a3465dceb48cf05edd3eb38189456ebe83875ea8" + +[[package]] +name = "rss" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2107738f003660f0a91f56fd3e3bd3ab5d918b2ddaf1e1ec2136fb1c46f71bf" +dependencies = [ + "atom_syndication", + "derive_builder", + "never", + "quick-xml", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stderrlog" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c910772f992ab17d32d6760e167d2353f4130ed50e796752689556af07dc6b" +dependencies = [ + "chrono", + "is-terminal", + "log", + "termcolor", + "thread_local", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uluru" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] +name = "unicode-id" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a898572 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,104 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +[package] +name = "back" +description = "An extremely simple git bug visualization system. Inspired by TVL's panettone." +version = "0.1.0" +edition = "2021" +license = "AGPL-3.0-or-later" +homepage = "" +repository = "https://git.foss-syndicate.org/vhack.eu/git_bug/back" +# TODO +# categories = [""] +# keywords = ["", ""] + + +[dependencies] +bytes = "1.10.1" +chrono = "0.4.41" +clap = { version = "4.5.38", features = ["derive"] } +gix = "0.72.1" +http = "1.3.1" +http-body-util = "0.1.3" +hyper = { version = "1.6.0", features = ["http1", "http2", "server"] } +hyper-util = { version = "0.1.11", features = ["tokio"] } +log = "0.4.27" +markdown = "1.0.0" +rinja = "0.4.0" +rss = "2.0.12" +serde = "1.0.219" +serde_json = "1.0.140" +sha2 = "0.10.9" +stderrlog = "0.6.0" +thiserror = "2.0.12" +tokio = { version = "1.45.0", features = ["macros", "net", "rt-multi-thread"] } +url = { version = "2.5.4", features = ["serde"] } + +[profile.release] +lto = true +codegen-units = 1 +panic = "abort" +split-debuginfo = "off" + +[lints.rust] +# rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html +warnings = "warn" +future_incompatible = { level = "warn", priority = -1 } +let_underscore = { level = "warn", priority = -1 } +nonstandard_style = { level = "warn", priority = -1 } +rust_2018_compatibility = { level = "warn", priority = -1 } +rust_2018_idioms = { level = "warn", priority = -1 } +rust_2021_compatibility = { level = "warn", priority = -1 } +unused = { level = "warn", priority = -1 } + +# rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html +# missing_docs = "warn" +macro_use_extern_crate = "warn" +meta_variable_misuse = "warn" +missing_abi = "warn" +missing_copy_implementations = "warn" +missing_debug_implementations = "warn" +non_ascii_idents = "warn" +noop_method_call = "warn" +single_use_lifetimes = "warn" +trivial_casts = "warn" +trivial_numeric_casts = "warn" +unreachable_pub = "warn" +unsafe_op_in_unsafe_fn = "warn" +unused_crate_dependencies = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" +unused_qualifications = "warn" +variant_size_differences = "warn" + +[lints.rustdoc] +# rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html +broken_intra_doc_links = "warn" +private_intra_doc_links = "warn" +missing_crate_level_docs = "warn" +private_doc_tests = "warn" +invalid_codeblock_attributes = "warn" +invalid_rust_codeblocks = "warn" +bare_urls = "warn" + +[lints.clippy] +# clippy allowed by default +dbg_macro = "warn" + +# clippy categories https://doc.rust-lang.org/clippy/ +all = { level = "warn", priority = -1 } +correctness = { level = "warn", priority = -1 } +suspicious = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } +complexity = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } diff --git a/README.md b/README.md new file mode 100644 index 0000000..afb70a1 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +<!-- +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. +--> + +# Back + +> An extremely simple git issue tracking system. Inspired by tvix's panettone. + +## Usage + +`back` is modelled after `cgit`, only for `git-bug` initialized repositories. +The server is than started at `http://127.0.0.1:8000` and provides access to the +issues (bugs) tracked via `git-bug`, via multiple routes: + +### `/` + +The default index is a list of all repositories that have `git-bug` data in +them. + +### `<repo_path>/issues/<state>` + +This path displays all issues in `<state>` (i.e., open or closed) for the +repository at `<repo_path>`. + +### `<repo_path>/issue/<issue_id>` + +Displays the actual issue with `id` `<issue_id>`. Beware, that the `<isuse_id>` +is sourced from the actual git object associated with the issue create commit. +As such, it is not the same ID, as displayed by the `git-bug` CLI. + +### `<repo_path>/issues/feed` + +An RSS feed usable to subscribe to. This includes all issues and all comments of +issues. + +## Configuration file + +The config file is passed to `back` via the first command line argument. It is +written in JSON. An example configuration file is available at +[`./contrib/config.json`](./contrib/config.json). + +Following keys are required: + +### `source_code_repository_url` + +The URL to the source code of this instance of `back`. + +### `root_url` + +The root URL this instance of `back` is hosted at. For example: +`https://issues.foss-syndicate.org`. This is required by the RSS feed to +generate links to the various issues/comments. + +### `scan_path` + +The path under which to search for the repositories as specified by the +`projects.list` file. This is semantically the same as `cgit`'s `scan-path`. + +### `project_list` + +The path to the file specifying the repositories to search. A repository path +per line. This is semantically the same as `cgit`'s `project-list`. + +### Note + +`back` needs write access to the repository, because of internal `gix` and `git` +object reasons. + +## Licensing + +This project complies with the REUSE v3.3 specification. This means that every +file clearly states its copyright. diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..e7ce376 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,383 @@ +/* + * Back - An extremely simple git bug visualization system. Inspired by TVL's + * panettone. + * + * Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> + * Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Back. + * + * You should have received a copy of the License along with this program. + * If not, see <https://www.gnu.org/licenses/agpl.txt>. + */ + +/* +* This has been taken from the tvix depot from panettone. +* Fetched via `suckit https://b.tvlfyi`. +* It was originally licensed under the MIT license. +*/ + +input[type="text"], +input[type="password"], +textarea { + width: 100%; + padding: 0.5rem; + outline: none; + border-top: none; + border-left: none; + border-right: none; + border-bottom: 1px solid var(--gray); + margin-bottom: 1rem; +} + +textarea { + resize: vertical; +} + +input[type="submit"] { + -webkit-appearance: none; + border: none; + cursor: pointer; + font-size: 1rem; +} + +input[type="submit"] { + background-color: var(--success); + padding: 0.5rem; + text-decoration: none; + -moz-transition: box-shadow 0.15s ease-in-out; + -o-transition: box-shadow 0.15s ease-in-out; + -webkit-transition: box-shadow 0.15s ease-in-out; + -ms-transition: box-shadow 0.15s ease-in-out; + transition: box-shadow 0.15s ease-in-out; +} + +input[type="submit"]:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); +} + +input[type="submit"]:active, +input[type="submit"]:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + outline: none; + border: none; +} + +.form-link input[type="submit"] { + background-color: initial; + color: inherit; + padding: 0; + text-decoration: underline; +} + +.form-link input[type="submit"]:hover, +.form-link input[type="submit"]:active, +.form-link input[type="submit"]:focus { + -moz-box-shadow: 0 0 0 0; + -o-box-shadow: 0 0 0 0; + -webkit-box-shadow: 0 0 0 0; + -ms-box-shadow: 0 0 0 0; + box-shadow: 0 0 0 0; +} + +.form-group { + margin-top: 1rem; +} + +label.checkbox { + cursor: pointer; +} + +.issue-list { + list-style-type: none; + padding-left: 0; +} + +.issue-list .issue-subject { + font-weight: bold; +} + +.issue-list li { + padding-bottom: 1rem; +} + +.issue-list li + li { + border-top: 1px solid var(--gray); +} + +.issue-list a { + text-decoration: none; + display: block; +} + +.issue-list a:hover { + outline: none; +} + +.issue-list a:hover .issue-subject { + color: var(--primary); +} + +.comment-count { + color: var(--gray); +} + +.issue-links { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; +} + +.issue-search input[type="search"] { + padding: 0.5rem; + background-image: url("static/search.png"); + background-position: 10px 10px; + background-repeat: no-repeat; + background-size: 1rem; + padding-left: 2rem; + border: 1px solid var(--gray); +} + +.issue-info { + display: flex; + justify-content: space-between; + align-items: center; +} + +.issue-info .edit-issue { + background-color: var(--success); + padding: 0.5rem; + text-decoration: none; + -moz-transition: box-shadow 0.15s ease-in-out; + -o-transition: box-shadow 0.15s ease-in-out; + -webkit-transition: box-shadow 0.15s ease-in-out; + -ms-transition: box-shadow 0.15s ease-in-out; + transition: box-shadow 0.15s ease-in-out; +} + +.issue-info .edit-issue:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); +} + +.issue-info .edit-issue:active, +.issue-info .edit-issue:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + outline: none; + border: none; +} + +.issue-info .created-by-at { + flex: 1; +} + +.issue-info .edit-issue { + background-color: var(--light) -gray; + flex: 0; + margin-right: 0.5rem; +} + +.issue-info .close-issue { + background-color: var(--failure); +} + +.issue-history { + list-style: none; + border-top: 1px solid var(--gray); + padding-top: 1rem; + padding-left: 2rem; +} + +.issue-history .comment-info { + color: var(--gray); + margin: 0; + padding-top: 1rem; +} + +.issue-history .comment-info a { + text-decoration: none; +} + +.issue-history .comment-info a:hover { + text-decoration: underline; +} + +.issue-history .comment, +.issue-history .event { + padding-top: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--gray); +} + +.issue-history .comment p, +.issue-history .event p { + margin: 0; +} + +.issue-history .comment:target, +.issue-history .event:target { + border-color: var(--primary); + border-bottom-width: 3px; +} + +.issue-history .event { + color: var(--gray); +} + +blockquote { + border-left: 5px solid var(--light) -gray; + padding-left: 1rem; + margin-left: 0rem; +} + +pre { + overflow-x: auto; +} + +body { + font-family: sans-serif; + color: var(--text); + background: var(--bg); + --text: rgb(24, 24, 24); + --bg: white; + --gray: #8d8d8d; + --primary: rgb(106, 154, 255); + --primary-light: rgb(150, 166, 200); + --success: rgb(168, 249, 166); + --failure: rgb(247, 167, 167); + --light-gray: #eee; +} + +@media (prefers-color-scheme: dark) { + body { + --text: rgb(240, 240, 240); + --bg: black; + --gray: #8d8d8d; + --primary: rgb(106, 154, 255); + --primary-light: rgb(150, 166, 200); + --success: rgb(14, 130, 11); + --failure: rgb(124, 14, 14); + --light-gray: #222; + } +} + +a { + color: inherit; +} + +.content { + max-width: 800px; + margin: 0 auto; +} + +header { + display: flex; + align-items: center; + border-bottom: 1px solid var(--text); + margin-bottom: 1rem; +} + +header h1 { + padding: 0; + flex: 1; +} + +header .issue-number { + color: var(--gray); + font-size: 1.5rem; +} + +nav { + display: flex; + color: var(--gray); + justify-content: space-between; +} + +nav .nav-group { + display: flex; +} + +nav .nav-group > * { + margin-left: 0.5rem; +} + +footer { + border-top: 1px solid var(--gray); + padding-top: 1rem; + margin-top: 1rem; + color: var(--gray); +} + +.new-issue { + background-color: var(--success); + padding: 0.5rem; + text-decoration: none; + -moz-transition: box-shadow 0.15s ease-in-out; + -o-transition: box-shadow 0.15s ease-in-out; + -webkit-transition: box-shadow 0.15s ease-in-out; + -ms-transition: box-shadow 0.15s ease-in-out; + transition: box-shadow 0.15s ease-in-out; +} + +.new-issue:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); +} + +.new-issue:active, +.new-issue:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + outline: none; + border: none; +} + +.alert { + padding: 0.5rem; + margin-bottom: 1rem; + background-color: var(--failure); +} + +.login-form { + max-width: 300px; + margin: 0 auto; +} + +.created-by-at { + color: var(--gray); +} + +.sr-only { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} diff --git a/cog.toml b/cog.toml new file mode 100644 index 0000000..f4f71b7 --- /dev/null +++ b/cog.toml @@ -0,0 +1,36 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +tag_prefix = "v" +branch_whitelist = ["main", "prime"] +ignore_merge_commits = false + +pre_bump_hooks = [ + "reuse lint", # Check licensing status. + "nix flake check", # Verify the project builds. + "cargo set-version {{version}}", # Bump version in Cargo.toml. + "nix fmt", # Format. +] +post_bump_hooks = [ + "git push", + "cargo publish", + "git push origin v{{version}}", # push the new tag to origin +] + +[bump_profiles] + +[changelog] +path = "NEWS.md" +template = "remote" +remote = "git.foss-syndicate.org" +repository = "git_bug/back" +owner = "vhack.eu" +authors = [{ signature = "Benedikt Peetz", username = "vhack.eu" }] diff --git a/contrib/config.json b/contrib/config.json new file mode 100644 index 0000000..a1d70d0 --- /dev/null +++ b/contrib/config.json @@ -0,0 +1,6 @@ +{ + "source_code_repository_url": "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back", + "root_url": "https://issues.foss-syndicate.org", + "scan_path": "/path/to/the/scan/path", + "project_list": "/path/to/the/projects.list" +} diff --git a/contrib/config.json.license b/contrib/config.json.license new file mode 100644 index 0000000..6cb8d56 --- /dev/null +++ b/contrib/config.json.license @@ -0,0 +1,11 @@ +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..78982af --- /dev/null +++ b/flake.lock @@ -0,0 +1,139 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1746291859, + "narHash": "sha256-DdWJLA+D5tcmrRSg5Y7tp/qWaD05ATI4Z7h22gd1h7Q=", + "owner": "ipetkov", + "repo": "crane", + "rev": "dfd9a8dfd09db9aad544c4d3b6c47b12562544a5", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1746576598, + "narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "systems": "systems", + "treefmt-nix": "treefmt-nix" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747017456, + "narHash": "sha256-C/U12fcO+HEF071b5mK65lt4XtAIZyJSSJAg9hdlvTk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "5b07506ae89b025b14de91f697eba23b48654c52", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1680978846, + "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=", + "owner": "nix-systems", + "repo": "x86_64-linux", + "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "x86_64-linux", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1746989248, + "narHash": "sha256-uoQ21EWsAhyskNo8QxrTVZGjG/dV4x5NM1oSgrmNDJY=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "708ec80ca82e2bbafa93402ccb66a35ff87900c5", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.lock.license b/flake.lock.license new file mode 100644 index 0000000..deb57aa --- /dev/null +++ b/flake.lock.license @@ -0,0 +1,10 @@ +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7389429 --- /dev/null +++ b/flake.nix @@ -0,0 +1,136 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + description = "An extremely simple git bug visualization system. Inspired by TVL's panettone."; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + + crane = { + url = "github:ipetkov/crane"; + inputs = {}; + }; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + + # inputs for following + systems = { + url = "github:nix-systems/x86_64-linux"; # only evaluate for this system + }; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + inputs = { + systems.follows = "systems"; + }; + }; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + treefmt-nix, + crane, + rust-overlay, + ... + }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [(import rust-overlay)]; + }; + + nightly = true; + rust_minimal = + if nightly + then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal) + else pkgs.rust-bin.stable.latest.minimal; + rust_default = + if nightly + then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default) + else pkgs.rust-bin.stable.latest.default; + + cargo_toml = craneLib.cleanCargoToml {cargoToml = ./Cargo.toml;}; + pname = cargo_toml.package.name; + + craneLib = (crane.mkLib pkgs).overrideToolchain rust_minimal; + craneBuild = craneLib.buildPackage { + src = craneLib.cleanCargoSource ./.; + + doCheck = true; + }; + + manual = pkgs.stdenv.mkDerivation { + name = "${pname}-manual"; + inherit (cargo_toml.package) version; + + src = ./docs; + nativeBuildInputs = with pkgs; [pandoc]; + + buildPhase = '' + mkdir --parents $out/docs; + + pandoc "./${pname}.1.md" -s -t man > $out/docs/${pname}.1 + ''; + + installPhase = '' + install -D $out/docs/${pname}.1 $out/share/man/man1/${pname}; + ''; + }; + + treefmtEval = import ./treefmt.nix { + inherit treefmt-nix pkgs; + rustfmt = rust_default; + }; + in { + packages.default = pkgs.symlinkJoin { + inherit (cargo_toml.package) name; + + paths = [manual craneBuild]; + }; + + checks = { + inherit craneBuild; + formatting = treefmtEval.config.build.check self; + }; + + formatter = treefmtEval.config.build.wrapper; + + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + cocogitto + git-bug + + rust_default + cargo-edit + + reuse + ]; + }; + }); +} +# vim: ts=2 + diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..4a22467 --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,102 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + config, + lib, + vhackPackages, + pkgs, + ... +}: let + cfg = config.vhack.back; +in { + options.vhack.back = { + enable = lib.mkEnableOption "Back issue tracker (inspired by tvix's panettone)"; + + domain = lib.mkOption { + type = lib.types.str; + description = "The domain to host this `back` instance on."; + }; + + settings = { + scan_path = lib.mkOption { + type = lib.types.path; + description = "The path to the directory under which all the repositories reside"; + }; + project_list = lib.mkOption { + type = lib.types.path; + description = "The path to the `projects.list` file."; + }; + + source_code_repository_url = lib.mkOption { + description = "The url to the source code of this instance of back"; + default = "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back"; + type = lib.types.str; + }; + + root_url = lib.mkOption { + type = lib.types.str; + description = "The url to this instance of back."; + default = "https://${cfg.domain}"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services."back" = { + description = "Back issue tracking system."; + requires = ["network-online.target"]; + after = ["network-online.target"]; + wantedBy = ["default.target"]; + + serviceConfig = { + ExecStart = "${lib.getExe vhackPackages.back} ${(pkgs.formats.json {}).generate "config.json" cfg.settings}"; + + # Ensure that the service can read the repository + # FIXME(@bpeetz): This has the implied assumption, that all the exposed git + # repositories are readable for the git group. This should not be necessary. <2024-12-23> + User = "git"; + Group = "git"; + + DynamicUser = true; + Restart = "always"; + + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"]; + }; + }; + services.nginx.virtualHosts."${cfg.domain}" = { + locations."/".proxyPass = "http://127.0.0.1:8000"; + + enableACME = true; + forceSSL = true; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..ef00195 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,38 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + rustPlatform, + lib, +}: +rustPlatform.buildRustPackage { + pname = "back"; + version = "1.0.0"; + + src = lib.cleanSourceWith { + src = lib.cleanSource ./.; + filter = name: type: + (type == "directory") + || (builtins.elem (builtins.baseNameOf name) ["Cargo.toml" "Cargo.lock" "style.css"]) + || (lib.strings.hasSuffix ".rs" (builtins.baseNameOf name)) + || (lib.strings.hasSuffix ".html" (builtins.baseNameOf name)); + }; + + doCheck = true; + + cargoLock = { + lockFile = ./Cargo.lock; + }; + + meta = { + mainProgram = "back"; + }; +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..5c3f6ba --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,79 @@ +max_width = 100 +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +indent_style = "Block" +use_small_heuristics = "Default" +fn_call_width = 60 +attr_fn_like_width = 70 +struct_lit_width = 18 +struct_variant_width = 35 +array_width = 60 +chain_width = 60 +single_line_if_else_max_width = 50 +single_line_let_else_max_width = 50 +wrap_comments = false +format_code_in_doc_comments = true +doc_comment_code_block_width = 100 +comment_width = 100 +normalize_comments = false +normalize_doc_attributes = true +format_strings = true +format_macro_matchers = true +format_macro_bodies = true +skip_macro_invocations = [] +hex_literal_case = "Upper" +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +reorder_imports = true +reorder_modules = true +reorder_impl_items = true +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +short_array_element_width_threshold = 10 +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_params_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2024" +style_edition = "2024" +inline_attribute_width = 0 +format_generated_files = true +generated_marker_line_search_limit = 5 +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = true +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.8.0" +unstable_features = true +disable_all_formatting = false +skip_children = false +show_parse_errors = true +error_on_line_overflow = true +error_on_unformatted = true +ignore = [] +emit_mode = "Files" +make_backup = false diff --git a/scripts/optimize_images.sh b/scripts/optimize_images.sh new file mode 100755 index 0000000..2d923ac --- /dev/null +++ b/scripts/optimize_images.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env nix +#! nix shell nixpkgs#optipng nixpkgs#jpegoptim nixpkgs#nodePackages.svgo nixpkgs#dash --command dash + +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +# shellcheck shell=dash + +# source: https://github.com/stride-tasks/stride/blob/148d513297c8ae66d79fc287769adfe5e711c93c/scripts/optimize-images + +PROJECT_DIR="$(git rev-parse --show-toplevel)" + +cd "$PROJECT_DIR" || { + echo "No '$PROJECT_DIR' ?!" + exit 1 +} + +PNG_OPITMIZE_COMMAND='optipng --quiet -o7 -preserve' +JPG_OPITMIZE_COMMAND='jpegoptim --quiet --strip-all -m76' +SVG_OPITMIZE_COMMAND='svgo --multipass --quiet --input' + +# The extension is the part after the first dot in the filename: +# +# For example: +# file.png => png +# file.tar.gz => tar.gz +find_files_by_extension() { + wanted_extension="$1" + tmp="$(mktemp)" + + git ls-files --cached --modified --other --exclude-standard --deduplicate | while IFS= read -r file; do + file_basename="$(basename "$file")" + extension="${file_basename#*.}" + if [ "$extension" = "$wanted_extension" ]; then + echo "$file" + else + : + # echo "'$file' with extension: '$extension' does not match filter: '$wanted_extension'" 1>&2 + fi + done >"$tmp" + + echo "$tmp" +} + +size_of() { + du -b "$1" | cut -f1 +} + +bytes_human() { + number="$1" + + numfmt --to=iec-i --suffix=B --format="%9.2f" "$number" +} + +# https://stackoverflow.com/questions/44695878/how-to-calculate-percentage-in-shell-script +# Native POSIX solution using string manipulation (assumes integer inputs). +percent() { + DP="$1" + SDC="$2" + + # Special case when DP is zero. + [ "$DP" = "0" ] && echo "0.00" && return + + # # e.g. round down e.g. round up + # # DP=1 SDC=3 DP=2 SDC=3 + Percent=$((DP * 100000 / SDC + 5)) # Percent=33338 Percent=66671 + Whole=${Percent%???} # Whole=33 Whole=66 + Percent=${Percent#"$Whole"} # Percent=338 Percent=671 + Percent=$Whole.${Percent%?} # Percent=33.33 Percent=66.67 + echo "$Percent" +} + +TOTAL=0 +TOTAL_SAVED=0 + +optimize_files() { + FILTER="$1" + PROGRAM="$2" + + printf "%s" "Processing $FILTER files:" + + FILES="$(find_files_by_extension "$FILTER")" + COUNT=$(wc -l <"$FILES") + + if [ "$COUNT" -eq 0 ]; then + echo " no files found!" + return + fi + + echo "" + + I=1 + + TOTAL_INNER=0 + TOTAL_SAVED_INNER=0 + + while IFS= read -r f; do + printf "%s/${COUNT} $f ... " "$I" + + SIZE="$(size_of "$f")" + + $PROGRAM "$f" + + NEW_SIZE="$(size_of "$f")" + DIFF=$((SIZE - NEW_SIZE)) + + echo "saved: $(bytes_human "$DIFF") ($(percent $DIFF "$SIZE")%)" + + TOTAL_INNER=$((TOTAL_INNER + SIZE)) + TOTAL_SAVED_INNER=$((TOTAL_SAVED_INNER + DIFF)) + + I=$((I + 1)) + done <"$FILES" + rm "$FILES" + + echo "Total saved for $FILTER: $(bytes_human "$TOTAL_SAVED_INNER") ($(percent $TOTAL_SAVED_INNER $TOTAL_INNER)%)" + echo "" + + TOTAL=$((TOTAL + TOTAL_INNER)) + TOTAL_SAVED=$((TOTAL_SAVED + TOTAL_SAVED_INNER)) +} + +optimize_files 'png' "$PNG_OPITMIZE_COMMAND" +optimize_files 'jpg' "$JPG_OPITMIZE_COMMAND" +optimize_files 'jpeg' "$JPG_OPITMIZE_COMMAND" +optimize_files 'svg' "$SVG_OPITMIZE_COMMAND" + +echo "Total saved: $(bytes_human "$TOTAL_SAVED") ($(percent $TOTAL_SAVED $TOTAL)%)" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..eeeed2b --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,25 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +#[allow(clippy::module_name_repetitions)] +/// An extremely simple git issue tracking system. +/// Inspired by tvix's panettone +pub struct Cli { + /// The path to the configuration file. The file should be written in JSON. + pub config_file: PathBuf, +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..6c90fce --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,137 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use gix::ThreadSafeRepository; +use serde::Deserialize; +use url::Url; + +use crate::{ + error::{self, Error}, + git_bug::dag::is_git_bug, +}; + +#[derive(Deserialize)] +pub struct BackConfig { + /// The url to the source code of back. This is needed, because back is licensed under the + /// AGPL. + pub source_code_repository_url: Url, + + /// The root url this instance of back is hosted on. + /// For example: + /// `issues.foss-syndicate.org` + pub root_url: Url, + + project_list: PathBuf, + + /// The path that is the common parent of all the repositories. + pub scan_path: PathBuf, +} + +pub struct BackRepositories { + repositories: Vec<BackRepository>, +} + +impl BackRepositories { + pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { + self.into_iter() + } +} + +impl<'a> IntoIterator for &'a BackRepositories { + type IntoIter = <&'a Vec<BackRepository> as IntoIterator>::IntoIter; + type Item = <&'a Vec<BackRepository> as IntoIterator>::Item; + + fn into_iter(self) -> Self::IntoIter { + self.repositories.iter() + } +} + +impl BackRepositories { + /// Try to get the repository at path `path`. + /// If no repository was registered/found at `path`, returns an error. + pub fn get(&self, path: &Path) -> Result<&BackRepository, error::Error> { + self.repositories + .iter() + .find(|p| p.repo_path == path) + .ok_or(error::Error::RepoFind { + repository_path: path.to_owned(), + }) + } +} + +pub struct BackRepository { + repo_path: PathBuf, +} + +impl BackRepository { + pub fn open(&self, scan_path: &Path) -> Result<ThreadSafeRepository, error::Error> { + let path = { + let base = scan_path.join(&self.repo_path); + if base.is_dir() { + base + } else { + PathBuf::from(base.display().to_string() + ".git") + } + }; + let repo = ThreadSafeRepository::open(path).map_err(|err| Error::RepoOpen { + repository_path: self.repo_path.to_owned(), + error: Box::new(err), + })?; + if is_git_bug(&repo.to_thread_local())? { + Ok(repo) + } else { + Err(error::Error::NotGitBug { + path: self.repo_path.clone(), + }) + } + } + + pub fn path(&self) -> &Path { + &self.repo_path + } +} + +impl BackConfig { + pub fn repositories(&self) -> error::Result<BackRepositories> { + let repositories = fs::read_to_string(&self.project_list) + .map_err(|err| error::Error::ProjectListRead { + error: err, + file: self.project_list.to_owned(), + })? + .lines() + .try_fold(vec![], |mut acc, path| { + acc.push(BackRepository { + repo_path: PathBuf::from(path), + }); + + Ok::<_, error::Error>(acc) + })?; + Ok(BackRepositories { repositories }) + } + + pub fn from_config_file(path: &Path) -> error::Result<Self> { + let value = fs::read_to_string(path).map_err(|err| Error::ConfigRead { + file: path.to_owned(), + error: err, + })?; + + serde_json::from_str(&value).map_err(|err| Error::ConfigParse { + file: path.to_owned(), + error: err, + }) + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..026cc58 --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,135 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{fmt::Display, io, net::SocketAddr, path::PathBuf}; + +use gix::hash::Prefix; +use thiserror::Error; + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Error, Debug)] +pub enum Error { + ConfigParse { + file: PathBuf, + error: serde_json::Error, + }, + + ProjectListRead { + file: PathBuf, + error: io::Error, + }, + ConfigRead { + file: PathBuf, + error: io::Error, + }, + NotGitBug { + path: PathBuf, + }, + RepoOpen { + repository_path: PathBuf, + error: Box<gix::open::Error>, + }, + RepoFind { + repository_path: PathBuf, + }, + RepoRefsIter(#[from] gix::refs::packed::buffer::open::Error), + RepoRefsPrefixed { + error: io::Error, + }, + + TcpBind { + addr: SocketAddr, + err: io::Error, + }, + TcpAccept { + err: io::Error, + }, + + IssuesPrefixMissing { + prefix: Prefix, + }, + IssuesPrefixParse(#[from] gix::hash::prefix::from_hex::Error), +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ConfigParse { file, error } => { + write!( + f, + "while trying to parse the config file ({}): {error}", + file.display() + ) + } + Error::ProjectListRead { file, error } => { + write!( + f, + "while trying to read the project.list file ({}): {error}", + file.display() + ) + } + Error::ConfigRead { file, error } => { + write!( + f, + "while trying to read the config file ({}): {error}", + file.display() + ) + } + Error::RepoOpen { + repository_path, + error, + } => { + write!( + f, + "while trying to open the repository ({}): {error}", + repository_path.display() + ) + } + Error::NotGitBug { path } => { + write!( + f, + "Repository ('{}') has no initialized git-bug data", + path.display() + ) + } + Error::RepoFind { repository_path } => { + write!( + f, + "failed to find the repository at path: '{}'", + repository_path.display() + ) + } + Error::RepoRefsIter(error) => { + write!(f, "while iteration over the refs in a repository: {error}",) + } + Error::RepoRefsPrefixed { error, .. } => { + write!(f, "while prefixing the refs with a path: {error}") + } + Error::IssuesPrefixMissing { prefix } => { + write!( + f, + "There is no 'issue' associated with the prefix: {prefix}" + ) + } + Error::IssuesPrefixParse(error) => { + write!(f, "The given prefix can not be parsed as prefix: {error}") + } + Error::TcpBind { addr, err } => { + write!(f, "while trying to open tcp {addr} for listening: {err}.") + } + Error::TcpAccept { err } => { + write!(f, "while trying to accept a tcp connection: {err}.") + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..49ffe5c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,54 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{process, sync::Arc}; + +use clap::Parser; + +use crate::config::BackConfig; + +mod cli; +pub mod config; +mod error; +pub mod git_bug; +mod web; + +fn main() -> Result<(), String> { + if let Err(err) = server_main() { + eprintln!("Error {err}"); + process::exit(1); + } else { + Ok(()) + } +} + +#[tokio::main] +async fn server_main() -> Result<(), error::Error> { + let args = cli::Cli::parse(); + + stderrlog::new() + .module(module_path!()) + .modules(["hyper", "http"]) + .quiet(false) + .show_module_names(false) + .color(stderrlog::ColorChoice::Auto) + .verbosity(2) + .timestamp(stderrlog::Timestamp::Off) + .init() + .expect("Let's just hope that this does not panic"); + + let config = BackConfig::from_config_file(&args.config_file)?; + + web::main(Arc::new(config)).await?; + + Ok(()) +} diff --git a/src/web/generate/mod.rs b/src/web/generate/mod.rs new file mode 100644 index 0000000..06bab17 --- /dev/null +++ b/src/web/generate/mod.rs @@ -0,0 +1,236 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{fs, path::Path}; + +use gix::hash::Prefix; +use log::info; +use rinja::Template; +use url::Url; + +use crate::{ + config::BackConfig, + error, + git_bug::{ + dag::issues_from_repository, + issue::{CollapsedIssue, Status}, + }, +}; + +#[derive(Template)] +#[template(path = "./issues.html")] +struct IssuesTemplate { + wanted_status: Status, + counter_status: Status, + issues: Vec<CollapsedIssue>, + + /// The path to the repository + repo_path: String, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +pub fn issues( + config: &BackConfig, + wanted_status: Status, + counter_status: Status, + repo_path: &Path, +) -> error::Result<String> { + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)?; + + let mut issue_list = issues_from_repository(&repository.to_thread_local())? + .into_iter() + .map(|issue| issue.collapse()) + .filter(|issue| issue.status == wanted_status) + .collect::<Vec<CollapsedIssue>>(); + + // Sort by date descending. + // SAFETY: + // The time stamp is only used for sorting, so a malicious attacker could only affect the issue + // sorting. + issue_list.sort_by_key(|issue| unsafe { issue.timestamp.to_unsafe() }); + issue_list.reverse(); + + Ok(IssuesTemplate { + wanted_status, + counter_status, + source_code_repository_url: config.source_code_repository_url.clone(), + issues: issue_list, + repo_path: repo_path.display().to_string(), + } + .render() + .expect("This should always work")) +} + +use crate::git_bug::format::HtmlString; +#[derive(Template)] +#[template(path = "./issue.html")] +struct IssueTemplate { + issue: CollapsedIssue, + + /// The path to the repository + repo_path: String, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +pub fn issue(config: &BackConfig, repo_path: &Path, prefix: Prefix) -> error::Result<String> { + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)? + .to_thread_local(); + + let maybe_issue = issues_from_repository(&repository)? + .into_iter() + .map(|val| val.collapse()) + .find(|issue| issue.id.to_string().starts_with(&prefix.to_string())); + + match maybe_issue { + Some(issue) => Ok(IssueTemplate { + issue, + repo_path: repo_path.display().to_string(), + source_code_repository_url: config.source_code_repository_url.clone(), + } + .render() + .expect("This should always work")), + None => Err(error::Error::IssuesPrefixMissing { prefix }), + } +} + +#[derive(Template)] +#[template(path = "./repos.html")] +struct ReposTemplate { + repos: Vec<RepoValue>, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +struct RepoValue { + description: String, + owner: String, + path: String, +} +pub fn repos(config: &BackConfig) -> error::Result<String> { + let repos: Vec<RepoValue> = config + .repositories()? + .iter() + .filter_map(|raw_repo| match raw_repo.open(&config.scan_path) { + Ok(repo) => { + let repo = repo.to_thread_local(); + let git_config = repo.config_snapshot(); + + let path = raw_repo.path().to_string_lossy().to_string(); + + let owner = git_config + .string("cgit.owner") + .map(|v| v.to_string()) + .unwrap_or("<No owner>".to_owned()); + + let description = fs::read_to_string(repo.git_dir().join("description")) + .unwrap_or("<No description>".to_owned()); + + Some(RepoValue { + description, + owner, + path, + }) + } + Err(err) => { + info!( + "Repo '{}' could not be opened: '{err}'", + raw_repo.path().display() + ); + None + } + }) + .collect(); + + Ok(ReposTemplate { + repos, + source_code_repository_url: config.source_code_repository_url.clone(), + } + .render() + .expect("this should work")) +} + +pub fn feed(config: &BackConfig, repo_path: &Path) -> error::Result<String> { + use rss::{ChannelBuilder, Item, ItemBuilder}; + + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)? + .to_thread_local(); + + let issues: Vec<CollapsedIssue> = issues_from_repository(&repository)? + .into_iter() + .map(|issue| issue.collapse()) + .collect(); + + // Collect all Items as rss items + let mut items: Vec<Item> = issues + .iter() + .map(|issue| { + ItemBuilder::default() + .title(issue.title.to_string()) + .author(issue.author.to_string()) + .description(issue.message.to_string()) + .pub_date(issue.timestamp.to_string()) + .link(format!( + "/{}/{}/issue/{}", + repo_path.display(), + &config.root_url, + issue.id + )) + .build() + }) + .collect(); + + // Append all comments after converting them to rss items + items.extend( + issues + .iter() + .filter(|issue| !issue.comments.is_empty()) + .flat_map(|issue| { + issue + .comments + .iter() + .map(|comment| { + ItemBuilder::default() + .title(issue.title.to_string()) + .author(comment.author.to_string()) + .description(comment.message.to_string()) + .pub_date(comment.timestamp.to_string()) + .link(format!( + "/{}/{}/issue/{}", + repo_path.display(), + &config.root_url, + issue.id + )) + .build() + }) + .collect::<Vec<Item>>() + }) + .collect::<Vec<Item>>(), + ); + + let channel = ChannelBuilder::default() + .title("Issues") + .link(config.root_url.to_string()) + .description(format!("The rss feed for issues on {}.", &config.root_url)) + .items(items) + .build(); + Ok(channel.to_string()) +} diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 0000000..8e2e9b0 --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,138 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc}; + +use bytes::Bytes; +use http_body_util::combinators::BoxBody; +use hyper::{Method, Request, Response, StatusCode, server::conn::http1, service::service_fn}; +use hyper_util::rt::TokioIo; +use log::{error, info}; +use responses::{html_response, html_response_status, html_response_status_content_type}; +use tokio::net::TcpListener; + +use crate::{config::BackConfig, error, git_bug::issue::Status}; + +mod generate; +mod responses; + +async fn match_uri( + config: Arc<BackConfig>, + req: Request<hyper::body::Incoming>, +) -> Result<Response<BoxBody<Bytes, Infallible>>, hyper::Error> { + if req.method() != Method::GET { + return Ok(html_response_status( + "Only get requests are supported", + StatusCode::NOT_ACCEPTABLE, + )); + } + + let output = || -> Result<Response<BoxBody<Bytes, Infallible>>, error::Error> { + match req.uri().path().trim_end_matches("/") { + "" => Ok(html_response(generate::repos(&config)?)), + + "/style.css" => Ok(responses::html_response_status_content_type( + include_str!("../../assets/style.css"), + StatusCode::OK, + "text/css", + )), + + path if path.ends_with("/issues/open") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/open") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let issues = generate::issues(&config, Status::Open, Status::Closed, &repo_path)?; + Ok(html_response(issues)) + } + path if path.ends_with("/issues/closed") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/closed") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let issues = generate::issues(&config, Status::Closed, Status::Open, &repo_path)?; + Ok(html_response(issues)) + } + path if path.ends_with("/issues/feed") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/feed") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let feed = generate::feed(&config, &repo_path)?; + Ok(html_response_status_content_type( + feed, + StatusCode::OK, + "text/xml", + )) + } + + path if path.contains("/issue/") => { + let (repo_path, prefix) = { + let split: Vec<&str> = path.split("/issue/").collect(); + + let prefix = + gix::hash::Prefix::from_hex(split[1]).map_err(error::Error::from)?; + + let repo_path = + PathBuf::from(split[0].strip_prefix("/").expect("This prefix exists")); + + (repo_path, prefix) + }; + Ok(html_response(generate::issue(&config, &repo_path, prefix)?)) + } + + other => Ok(responses::html_response_status_content_type( + format!("'{}' not found", other), + StatusCode::NOT_FOUND, + "text/plain", + )), + } + }; + match output() { + Ok(response) => Ok(response), + Err(err) => Ok(err.into_response()), + } +} + +pub async fn main(config: Arc<BackConfig>) -> Result<(), error::Error> { + let addr: SocketAddr = ([127, 0, 0, 1], 8000).into(); + + let listener = TcpListener::bind(addr) + .await + .map_err(|err| error::Error::TcpBind { addr, err })?; + info!("Listening on http://{}", addr); + loop { + let (stream, _) = listener + .accept() + .await + .map_err(|err| error::Error::TcpAccept { err })?; + let io = TokioIo::new(stream); + + let local_config = Arc::clone(&config); + + let service = service_fn(move |req| match_uri(Arc::clone(&local_config), req)); + + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, service).await { + error!("Error serving connection: {:?}", err); + } + }); + } +} diff --git a/src/web/responses.rs b/src/web/responses.rs new file mode 100644 index 0000000..bcdcc0a --- /dev/null +++ b/src/web/responses.rs @@ -0,0 +1,61 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::convert::Infallible; + +use bytes::Bytes; +use http::{Response, StatusCode, Version}; +use http_body_util::{BodyExt, Full, combinators::BoxBody}; + +use crate::{error, git_bug::format::HtmlString}; + +pub(super) fn html_response<T: Into<Bytes>>(html_text: T) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status(html_text, StatusCode::OK) +} + +pub(super) fn html_response_status<T: Into<Bytes>>( + html_text: T, + status: StatusCode, +) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status_content_type(html_text, status, "text/html") +} + +pub(super) fn html_response_status_content_type<T: Into<Bytes>>( + html_text: T, + status: StatusCode, + content_type: &str, +) -> Response<BoxBody<Bytes, Infallible>> { + Response::builder() + .status(status) + .version(Version::HTTP_2) + .header("Content-Type", format!("{}; charset=utf-8", content_type)) + .header("x-content-type-options", "nosniff") + .header("x-frame-options", "SAMEORIGIN") + .body(full(html_text)) + .expect("This will always build") +} + +fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Infallible> { + Full::new(chunk.into()).boxed() +} + +// FIXME: Not all errors should return `INTERNAL_SERVER_ERROR`. <2025-03-08> +impl error::Error { + pub fn into_response(self) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status( + format!( + "<h1> Internal server error. </h1> <pre>Error: {}</pre>", + HtmlString::from(self.to_string()) + ), + StatusCode::INTERNAL_SERVER_ERROR, + ) + } +} diff --git a/templates/issue.html b/templates/issue.html new file mode 100644 index 0000000..c7e4efc --- /dev/null +++ b/templates/issue.html @@ -0,0 +1,74 @@ +<!-- +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. +--> + +<!doctype html> +<html lang="en"> + <head> + <title>{{ HtmlString::from(issue.title.clone()) }} | Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" + /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" + /> + </head> + <body> + <div class="content"> + <nav> + <a href="/{{repo_path}}/issues/open">Open Issues</a> + <a href="/{{repo_path}}/issues/closed">Closed Issues</a> + </nav> + <header> + <h1>{{issue.title|safe}}</h1> + <div class="issue-number">{{issue.id}}</div> + </header> + <main> + <div class="issue-info"> + <span class="created-by-at" + >Opened by + <span class="user-name">{{issue.author.name|safe}}</span> + <span class="user-email"><{{issue.author.email|safe}}></span> + at <span class="timestamp">{{issue.timestamp}}</span></span + > + </div> + {{issue.message|safe}} {% if !issue.comments.is_empty() %} + <ol class="issue-history"> + {% for comment in issue.comments %} + <li + class="comment" + id="{{comment.id}}" + > + {{comment.message|safe}} + <p class="comment-info"> + <span class="user-name" + >{{comment.author.name|safe}} at {{comment.timestamp}}</span + > + </p> + </li> + {% endfor %} + </ol> + {% endif %} + </main> + <footer> + <nav> + <a href="/{{repo_path}}/issues/open">Open Issues</a> + <a href="{{source_code_repository_url}}">Source code</a> + <a href="/{{repo_path}}/issues/closed">Closed Issues</a> + </nav> + </footer> + </div> + </body> +</html> diff --git a/templates/issues.html b/templates/issues.html new file mode 100644 index 0000000..564827c --- /dev/null +++ b/templates/issues.html @@ -0,0 +1,77 @@ +<!-- +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. +--> + +<!doctype html> +<html lang="en"> + <head> + <title>Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" + /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" + /> + </head> + <body> + <div class="content"> + <header> + <h1>{{wanted_status}} Issues</h1> + </header> + <main> + <div class="issue-links"> + <a href="/{{repo_path}}/issues/{{counter_status|lowercase}}/" + >View {{counter_status}} issues</a + > + <a href="{{source_code_repository_url}}">Source code</a> + <!-- + <form class="issue-search" method="get"> + <input name="search" title="Issue search query" type="search"> + <input class="sr-only" type="submit" value="Search Issues"> + </form> + --> + </div> + <ol class="issue-list"> + {% for issue in issues -%} + <li> + <a href="/{{repo_path}}/issue/{{issue.id}}"> + <p> + <span class="issue-subject">{{issue.title|safe}}</span> + </p> + <span class="issue-number">{{issue.id}}</span> + <span class="created-by-at" + >Opened by {{ " " }} + <span class="user-name">{{issue.author.name|safe}}</span> + {{ " " }} + <span class="user-email" + ><{{issue.author.email|safe}}></span + > + {{ "at" }} + <span class="timestamp">{{issue.timestamp}}</span> + </span> + {% if !issue.comments.is_empty() +%} + <span class="comment-count"> + - {{issue.comments.len()}} + comment{{issue.comments.len()|pluralize}}</span + > + {%+ endif %} + </a> + </li> + {%- endfor %} + </ol> + </main> + </div> + </body> +</html> diff --git a/templates/repos.html b/templates/repos.html new file mode 100644 index 0000000..8aa71c4 --- /dev/null +++ b/templates/repos.html @@ -0,0 +1,62 @@ +<!-- +Back - An extremely simple git bug visualization system. Inspired by TVL's +panettone. + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: AGPL-3.0-or-later + +This file is part of Back. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/agpl.txt>. +--> + +<!doctype html> +<html lang="en"> + <head> + <title>Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" + /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" + /> + </head> + <body> + <div class="content"> + <header> + <h1>Repositories</h1> + </header> + <main> + <div class="issue-links"> + <a href="{{source_code_repository_url}}">Source code</a> + <!-- + <form class="issue-search" method="get"> + <input name="search" title="Issue search query" type="search"> + <input class="sr-only" type="submit" value="Search Issues"> + </form> + --> + </div> + <ol class="issue-list"> + {% for repo in repos -%} + <li> + <a href="/{{repo.path}}/issues/open"> + <p> + <span class="issue-subject">{{repo.path}}</span> + </p> + <span class="created-by-at"> + <span class="timestamp">{{repo.description}}</span> + {{ "-" }} + <span class="user-name">{{repo.owner}}</span> + </span> + </a> + </li> + {%- endfor %} + </ol> + </main> + </div> + </body> +</html> diff --git a/tests/base.nix b/tests/base.nix new file mode 100644 index 0000000..5aebe6a --- /dev/null +++ b/tests/base.nix @@ -0,0 +1,216 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + nixos-lib, + pkgsUnstable, + nixpkgs-unstable, + vhackPackages, + pkgs, + extraModules, + nixLib, + ... +}: let + domain = "server"; + + sshKeys = + import ../../gi/git-server/ssh_keys.nix {inherit pkgs;}; + + gitoliteAdminConfSnippet = pkgs.writeText "gitolite-admin-conf-snippet" '' + repo CREATOR/[a-zA-Z0-9].* + C = @all + RW+ = CREATOR + RW = WRITERS + R = READERS + option user-configs = cgit\.owner cgit\.desc cgit\.section cgit\.homepage + ''; +in + nixos-lib.runTest { + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs + + name = "back"; + + node = { + specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + + # Use the nixpkgs as constructed by the `nixpkgs.*` options + pkgs = null; + }; + + nodes = { + server = {config, ...}: { + environment.systemPackages = [pkgs.git]; + + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + persist.enable = true; + openssh.enable = true; + nginx = { + enable = true; + selfsign = true; + }; + git-server = { + enable = true; + domain = "git.${domain}"; + gitolite.adminPubkey = sshKeys.admin.pub; + }; + back = { + enable = true; + domain = "issues.${domain}"; + + settings = { + scan_path = "${config.services.gitolite.dataDir}/repositories"; + project_list = "${config.services.gitolite.dataDir}/projects.list"; + }; + }; + }; + }; + + client = {nodes, ...}: { + environment.systemPackages = [pkgs.git pkgs.curl pkgs.git-bug pkgs.gawk]; + programs.ssh.extraConfig = '' + Host * + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + # there's nobody around that can input password + PreferredAuthentications publickey + ''; + users.users.alice = {isNormalUser = true;}; + networking.hosts = { + "${nodes.server.networking.primaryIPAddress}" = [ + "git.${domain}" + "issues.${domain}" + "${domain}" + ]; + }; + }; + }; + + testScript = {nodes, ...}: + /* + python + */ + '' + start_all() + + with subtest("can setup ssh keys on client"): + client.succeed( + "mkdir -p ~root/.ssh", + "cp ${sshKeys.admin.priv} ~root/.ssh/id_ed25519", + "chmod 600 ~root/.ssh/id_ed25519", + ) + client.succeed( + "sudo -u alice mkdir -p ~alice/.ssh", + "sudo -u alice cp ${sshKeys.alice.priv} ~alice/.ssh/id_ed25519", + "sudo -u alice chmod 600 ~alice/.ssh/id_ed25519", + ) + + with subtest("gitolite server starts"): + server.wait_for_unit("gitolite-init.service") + server.wait_for_unit("sshd.service") + client.succeed("ssh -n git@git.${domain} info") + + + with subtest("admin can clone and configure gitolite-admin.git"): + client.succeed("${pkgs.writeShellScript "setup-gitolite-admin.git" '' + set -xe + + git clone git@git.${domain}:gitolite-admin.git + git config --global user.name 'System Administrator' + git config --global user.email root\@domain.example + + cp ${sshKeys.alice.pub} gitolite-admin/keydir/alice.pub + + (cd gitolite-admin && git switch -c master && git branch -D main) + + (cd gitolite-admin && git add . && git commit -m 'Add keys for alice' && git push -u origin master) + cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf + (cd gitolite-admin && git add . && git commit -m 'Add support for wild repos' && git push) + (cd gitolite-admin && git push -d origin main) + ''}") + + with subtest("alice can create a repo"): + client.succeed("sudo -u alice ${pkgs.writeShellScript "alice-create-repo" '' + set -xe + + mkdir --parents ./alice/repo1 && cd alice/repo1; + + git init --initial-branch main + echo "# Alice's Repo" > README.md + git add README.md + git -c user.name=Alice -c user.email=alice@domain.example commit -m 'Add readme' + + git remote add origin git@git.${domain}:alice/repo1.git + git push --set-upstream origin main + ''}") + + with subtest("can setup git-bug issues in alice/repo1"): + client.succeed("sudo -u alice ${pkgs.writeShellScript "setup-git-repo" '' + set -ex + + cd alice/repo1 + + git bug user create --avatar "" --email "alice@server.org" --name "alice" --non-interactive + + git bug add \ + --title "Some bug title" \ + --message "A long description of the bug. Probably has some code segments, maybe even *markdown* mark_up_ or other things" \ + --non-interactive + + git bug add \ + --title "Second bug title" \ + --message "" \ + --non-interactive + + git bug add \ + --title "Third bug title" \ + --message "" \ + --non-interactive + + git bug select "$(git bug ls --format plain | awk '{print $1}' | head -n 1)" + + git bug comment add --message "Some comment message" --non-interactive + git bug comment add --message "Second comment message" --non-interactive + git bug label add "Test" + + # TODO: This should use `git bug push`, but their ssh implementation is just + # too special to work in a VM test <2025-03-08> + git push origin +refs/bugs/* + git push origin +refs/identities/* + + ssh git@${domain} -- config alice/repo1 --add cgit.owner Alice + ssh git@${domain} -- perms alice/repo1 + READERS @all + ''}") + + with subtest("back server starts"): + server.wait_for_unit("back.service") + + with subtest("client can access the server"): + client.succeed("${pkgs.writeShellScript "curl-back" '' + set -xe + + curl --insecure --fail --show-error "https://issues.${domain}/alice/repo1.git/issues/open" --output /root/issues.html + grep -- 'Second bug title' /root/issues.html + + curl --insecure --fail --show-error "https://issues.${domain}/" --output /root/repos.html + grep -- 'repo' /root/repos.html + grep -- "<No description>" /root/repos.html + grep -- '<span class="user-name">Alice</span>' /root/repos.html + ''} >&2") + + client.copy_from_vm("/root/issues.html", ""); + client.copy_from_vm("/root/repos.html", ""); + ''; + } diff --git a/treefmt.nix b/treefmt.nix new file mode 100644 index 0000000..ebe4157 --- /dev/null +++ b/treefmt.nix @@ -0,0 +1,88 @@ +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + treefmt-nix, + pkgs, + rustfmt, +}: +treefmt-nix.lib.evalModule pkgs ( + {pkgs, ...}: { + # Used to find the project root + projectRootFile = "flake.nix"; + + programs = { + alejandra.enable = true; + rustfmt = { + enable = true; + package = rustfmt; + edition = "2024"; + }; + clang-format.enable = true; + mdformat.enable = true; + shfmt = { + enable = true; + indent_size = 4; + }; + shellcheck.enable = true; + prettier = { + enable = true; + settings = { + arrowParens = "always"; + bracketSameLine = false; + bracketSpacing = true; + editorconfig = true; + embeddedLanguageFormatting = "auto"; + endOfLine = "lf"; + # experimentalTernaries = false; + htmlWhitespaceSensitivity = "css"; + insertPragma = false; + jsxSingleQuote = true; + printWidth = 80; + proseWrap = "always"; + quoteProps = "consistent"; + requirePragma = false; + semi = true; + singleAttributePerLine = true; + singleQuote = false; + trailingComma = "all"; + useTabs = false; + vueIndentScriptAndStyle = false; + + tabWidth = 2; + }; + }; + stylua.enable = true; + ruff = { + enable = true; + format = true; + }; + taplo.enable = true; + }; + + settings = { + global.excludes = [ + "CHANGELOG.md" + "NEWS.md" + ]; + formatter = { + clang-format = { + options = ["--style" "GNU"]; + }; + rustfmt = { + options = ["--config-path" "${./rustfmt.toml}"]; + }; + shfmt = { + includes = ["*.bash"]; + }; + }; + }; + } +) diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..d3304c2 --- /dev/null +++ b/update.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +# Back - An extremely simple git bug visualization system. Inspired by TVL's +# panettone. +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Back. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +nix flake update + +[ "$1" = "upgrade" ] && cargo upgrade --incompatible +cargo update |