From afaae25fb783d32ca1a2c3a6b39c0604a6437c77 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Wed, 06 Jul 2022 01:04:51 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 ++ Cargo.lock | 1249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 24 ++++++++++++++++++++++++ LICENSE | 14 ++++++++++++++ README.md | 4 ++++ src/git.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ statics/style.css | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ templates/base.html | 26 ++++++++++++++++++++++++++ templates/index.html | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layers/logger.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layers/mod.rs | 19 +++++++++++++++++++ src/methods/index.rs | 21 +++++++++++++++++++++ src/methods/mod.rs | 2 ++ src/methods/repo.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ templates/repo/about.html | 6 ++++++ templates/repo/base.html | 14 ++++++++++++++ templates/repo/commit.html | 38 ++++++++++++++++++++++++++++++++++++++ templates/repo/diff.html | 6 ++++++ templates/repo/log.html | 6 ++++++ templates/repo/refs.html | 6 ++++++ templates/repo/stats.html | 6 ++++++ templates/repo/summary.html | 6 ++++++ templates/repo/tree.html | 6 ++++++ 24 files changed, 2072 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d81f12e 100644 --- /dev/null +++ a/.gitignore @@ -1,0 +1,2 @@ +/target +/.idea diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3c0b44e 100644 --- /dev/null +++ a/Cargo.lock @@ -1,0 +1,1249 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "askama" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", +] + +[[package]] +name = "askama_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +dependencies = [ + "askama_shared", + "proc-macro2", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_shared" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" +dependencies = [ + "askama_escape", + "humansize", + "mime", + "mime_guess", + "nom", + "num-traits", + "percent-encoding", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2cc6e8e8c993cb61a005fab8c1e5093a29199b7253b05a6883999312935c1ff" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4d047478b986f14a13edad31a009e2e05cb241f9805d0d75e4cba4e129ad4d" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "git2" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libgit2-sys" +version = "0.13.4+1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "path-clean" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rgit" +version = "0.1.0" +dependencies = [ + "askama", + "axum", + "clap", + "futures", + "git2", + "humantime", + "owning_ref", + "path-clean", + "time", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3a8dfe2 100644 --- /dev/null +++ a/Cargo.toml @@ -1,0 +1,24 @@ +[package] +name = "rgit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +askama = "0.11" +axum = "0.5" +clap = { version = "3.2", features = ["cargo"] } +futures = "0.3" +git2 = "0.14" +humantime = "2.1" +path-clean = "0.1" +owning_ref = "0.4" +time = "0.3" +tokio = { version = "1.19", features = ["full"] } +tower = "0.4" +tower-service = "0.3" +tower-layer = "0.3" +tracing = "0.1" +tracing-subscriber = "0.3" +uuid = { version = "1.1", features = ["v4"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8c3bdb1 100644 --- /dev/null +++ a/LICENSE @@ -1,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..801462c 100644 --- /dev/null +++ a/README.md @@ -1,0 +1,4 @@ +# rgit + +A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2 and +Askama. No more CGI, how exciting. diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..d644fdf 100644 --- /dev/null +++ a/src/git.rs @@ -1,0 +1,136 @@ +use std::{borrow::Cow, collections::BTreeMap, fmt::Display, path::Path, time::Duration}; + +use git2::{Repository, Signature}; +use owning_ref::OwningHandle; +use time::OffsetDateTime; + +pub type RepositoryMetadataList = BTreeMap, Vec>; + +#[derive(Debug)] +pub struct RepositoryMetadata { + pub name: String, + pub description: Option>, + pub owner: Option, + pub last_modified: Duration, +} + +pub struct CommitUser<'a>(Signature<'a>); + +impl CommitUser<'_> { + pub fn name(&self) -> &str { + self.0.name().unwrap() + } + + pub fn email(&self) -> &str { + self.0.email().unwrap() + } + + pub fn time(&self) -> String { + OffsetDateTime::from_unix_timestamp(self.0.when().seconds()) + .unwrap() + .to_string() + } +} + +pub struct Commit(OwningHandle, Box>>); + +impl Commit { + pub fn author(&self) -> CommitUser<'_> { + CommitUser(self.0.author()) + } + + pub fn committer(&self) -> CommitUser<'_> { + CommitUser(self.0.committer()) + } + + pub fn oid(&self) -> impl Display { + self.0.id() + } + + pub fn tree(&self) -> impl Display { + self.0.tree_id() + } + + pub fn parents(&self) -> impl Iterator { + self.0.parent_ids() + } + + pub fn summary(&self) -> &str { + self.0.summary().unwrap() + } + + pub fn body(&self) -> &str { + self.0.message().unwrap() + } +} + +pub fn get_latest_commit(path: &Path) -> Commit { + let repo = Repository::open_bare(path).unwrap(); + + let commit = OwningHandle::new_with_fn(Box::new(repo), |v| { + let head = unsafe { (*v).head().unwrap() }; + Box::new(head.peel_to_commit().unwrap()) + }); + + // TODO: we can cache this + Commit(commit) +} + +pub fn fetch_repository_metadata() -> RepositoryMetadataList { + let start = Path::new("../test-git").canonicalize().unwrap(); + + let mut repos: RepositoryMetadataList = RepositoryMetadataList::new(); + fetch_repository_metadata_impl(&start, &start, &mut repos); + repos +} + +fn fetch_repository_metadata_impl( + start: &Path, + current: &Path, + repos: &mut RepositoryMetadataList, +) { + let dirs = std::fs::read_dir(current) + .unwrap() + .map(|v| v.unwrap().path()) + .filter(|path| path.is_dir()); + + for dir in dirs { + let repository = match Repository::open_bare(&dir) { + Ok(v) => v, + Err(_e) => { + fetch_repository_metadata_impl(start, &dir, repos); + continue; + } + }; + + let repo_path = Some( + current + .strip_prefix(start) + .unwrap() + .to_string_lossy() + .into_owned(), + ) + .filter(|v| !v.is_empty()); + let repos = repos.entry(repo_path).or_default(); + + let description = std::fs::read_to_string(dir.join("description")) + .map(Cow::Owned) + .ok(); + let last_modified = std::fs::metadata(&dir).unwrap().modified().unwrap(); + let owner = repository.config().unwrap().get_string("gitweb.owner").ok(); + + repos.push(RepositoryMetadata { + name: dir + .components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .into_owned(), + description, + owner, + last_modified: (OffsetDateTime::now_utc() - OffsetDateTime::from(last_modified)) + .unsigned_abs(), + }); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fd2a40e 100644 --- /dev/null +++ a/src/main.rs @@ -1,0 +1,48 @@ +#![deny(clippy::pedantic)] + +use axum::{ + body::Body, handler::Handler, http::HeaderValue, response::Response, routing::get, Router, +}; +use tower_layer::layer_fn; + +use crate::{ + git::{fetch_repository_metadata, get_latest_commit}, + layers::logger::LoggingMiddleware, +}; + +mod git; +mod layers; +mod methods; + +const CRATE_VERSION: &str = clap::crate_version!(); + +#[tokio::main] +async fn main() { + let subscriber = tracing_subscriber::fmt(); + #[cfg(debug_assertions)] + let subscriber = subscriber.pretty(); + subscriber.init(); + + let app = Router::new() + .route("/", get(methods::index::handle)) + .route( + "/style.css", + get(static_css(include_bytes!("../statics/style.css"))), + ) + .fallback(methods::repo::service.into_service()) + .layer(layer_fn(LoggingMiddleware)); + + axum::Server::bind(&"127.0.0.1:3333".parse().unwrap()) + .serve(app.into_make_service_with_connect_info::()) + .await + .unwrap(); +} + +fn static_css(content: &'static [u8]) -> impl Handler<()> { + move || async move { + let mut resp = Response::new(Body::from(content)); + resp.headers_mut() + .insert("Content-Type", HeaderValue::from_static("text/css")); + resp + } +} diff --git a/statics/style.css b/statics/style.css new file mode 100644 index 0000000..57cb54c 100644 --- /dev/null +++ a/statics/style.css @@ -1,0 +1,70 @@ +body { + font-family: sans-serif; + font-size: 10pt; +} + +main { + padding: 2rem; + margin: 0; + border-bottom: solid 3px #ccc; +} + +header { + border-bottom: solid 1px #ccc; +} + +footer { + margin-top: 0.5em; + text-align: center; + font-size: 80%; + color: #ccc; +} + +a { + text-decoration: none; + color: blue; +} + +a:hover { + text-decoration: underline; +} + +table { border-collapse: collapse; } +table.repositories { width: 100%; } +table.repositories a { color: black; } +table.repositories a:hover { color: #00f; } +table th { text-align: left; } +.repo-section { font-style: italic; color: #888; } + +table.repositories tbody tr:nth-child(odd) { + background: #f7f7f7; +} + +table tbody tr.has-parent td:first-of-type { + padding-left: 1rem; +} + +table pre { margin: 0; } + +table.commit-info td, table.commit-info th { padding: 0.1em 1em 0.1em 0.1em; } + +nav { + margin-top: 2rem; + border-bottom: solid 3px #ccc; +} + +nav a { + padding: 2px 0.75em; + color: #777; + font-size: 110%; + text-decoration: none; +} + +nav a:hover { + text-decoration: underline; +} + +nav a.active { + color: #000; + background-color: #ccc; +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..7f9c081 100644 --- /dev/null +++ a/templates/base.html @@ -1,0 +1,26 @@ + + + + + {% block title %}rgit{% endblock %} + {% block head %}{% endblock %} + + + + + +
+

{% block header %}Git repository browser{% endblock %}

+
+ +{% block nav %}{% endblock %} + +
+ {% block content %}{% endblock %} +
+ +
+ generated by rgit v{{ crate::CRATE_VERSION }} at {{ time::OffsetDateTime::now_utc().to_string() }} +
+ +diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..b0fb288 100644 --- /dev/null +++ a/templates/index.html @@ -1,0 +1,55 @@ +{% extends "base.html" %} + +{% block title %}Index{% endblock %} + +{% block content %} + + + + + + + + + + + + {% for (path, repositories) in repositories %} + {% if let Some(path) = path %} + + {% endif %} + + {%for repository in repositories %} + + + + + + + {% endfor %} + {% endfor %} + +
NameDescriptionOwnerIdle
{{ path }}
+ + {{ repository.name }} + + + + {% if let Some(description) = repository.description %} + {{ description }} + {% else %} + Unnamed repository; edit this file 'description' to name the repository. + {% endif %} + + + + {% if let Some(owner) = repository.owner %} + {{ owner }} + {% endif %} + + + + {{ humantime::format_duration(repository.last_modified.clone()) }} + +
+{% endblock %}diff --git a/src/layers/logger.rs b/src/layers/logger.rs new file mode 100644 index 0000000..33c87b9 100644 --- /dev/null +++ a/src/layers/logger.rs @@ -1,0 +1,134 @@ +//! Logs each and every request out in a format similar to that of Apache's logs. + +use std::{ + fmt::Debug, + net::SocketAddr, + task::{Context, Poll}, + time::Instant, +}; + +use axum::{ + extract, + http::{HeaderValue, Method, Request, Response}, +}; +use futures::future::{Future, FutureExt, Join, Map, Ready}; +use tower_service::Service; +use tracing::{error, info, instrument::Instrumented, Instrument, Span}; +use uuid::Uuid; + +use super::UnwrapInfallible; + +pub trait GenericError: std::error::Error + Debug + Send + Sync {} + +#[derive(Clone)] +pub struct LoggingMiddleware(pub S); + +impl Service> for LoggingMiddleware +where + S: Service, Response = Response, Error = std::convert::Infallible> + + Clone + + Send + + 'static, + S::Future: Send + 'static, + S::Response: Default + Debug, + ReqBody: Send + Debug + 'static, + ResBody: Default + Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = Map< + Join, Ready>, + fn((::Output, PendingLogMessage)) -> ::Output, + >; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let request_id = Uuid::new_v4(); + let span = tracing::info_span!("web", "request_id" = request_id.to_string().as_str()); + + let log_message = PendingLogMessage { + span: span.clone(), + request_id, + ip: req + .extensions() + .get::>() + .map_or_else(|| "0.0.0.0:0".parse().unwrap(), |v| v.0), + method: req.method().clone(), + uri: req.uri().path().to_string(), + start: Instant::now(), + user_agent: req.headers().get(axum::http::header::USER_AGENT).cloned(), + }; + + // this is infallible because of the type of S::Error + futures::future::join( + self.0.call(req).instrument(span), + futures::future::ready(log_message), + ) + .map(|(response, pending_log_message)| { + let mut response = response.unwrap_infallible(); + pending_log_message.log(&response); + response.headers_mut().insert( + "X-Request-ID", + HeaderValue::try_from(pending_log_message.request_id.to_string()).unwrap(), + ); + Ok(response) + }) + } +} + +pub struct PendingLogMessage { + span: Span, + request_id: Uuid, + ip: SocketAddr, + method: Method, + uri: String, + start: Instant, + user_agent: Option, +} + +impl PendingLogMessage { + pub fn log(&self, response: &Response) { + let _enter = self.span.enter(); + + if response.status().is_server_error() { + error!( + "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"", + ip = self.ip, + method = self.method, + uri = self.uri, + status = response.status().as_u16(), + duration = self.start.elapsed(), + user_agent = self + .user_agent + .as_ref() + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown"), + error = match response.extensions().get::>() { + Some(e) => Err(e), + None => Ok(()), + } + ); + } else { + info!( + "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"", + ip = self.ip, + method = self.method, + uri = self.uri, + status = response.status().as_u16(), + duration = self.start.elapsed(), + user_agent = self + .user_agent + .as_ref() + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown"), + error = match response.extensions().get::>() { + Some(e) => Err(e), + None => Ok(()), + } + ); + } + } +} diff --git a/src/layers/mod.rs b/src/layers/mod.rs new file mode 100644 index 0000000..e92435f 100644 --- /dev/null +++ a/src/layers/mod.rs @@ -1,0 +1,19 @@ +use std::convert::Infallible; + +pub mod logger; + +pub trait UnwrapInfallible { + fn unwrap_infallible(self) -> T; +} + +impl UnwrapInfallible for Result { + fn unwrap_infallible(self) -> T { + self.unwrap() + } +} + +impl UnwrapInfallible for Result { + fn unwrap_infallible(self) -> T { + self.unwrap() + } +} diff --git a/src/methods/index.rs b/src/methods/index.rs new file mode 100644 index 0000000..9ca2503 100644 --- /dev/null +++ a/src/methods/index.rs @@ -1,0 +1,21 @@ +use askama::Template; +use axum::response::Html; + +use crate::{fetch_repository_metadata, git::RepositoryMetadataList}; + +#[allow(clippy::unused_async)] +pub async fn handle() -> Html { + #[derive(Template)] + #[template(path = "index.html")] + pub struct View { + pub repositories: RepositoryMetadataList, + } + + Html( + View { + repositories: fetch_repository_metadata(), + } + .render() + .unwrap(), + ) +} diff --git a/src/methods/mod.rs b/src/methods/mod.rs new file mode 100644 index 0000000..5592c5f 100644 --- /dev/null +++ a/src/methods/mod.rs @@ -1,0 +1,2 @@ +pub mod index; +pub mod repo; diff --git a/src/methods/repo.rs b/src/methods/repo.rs new file mode 100644 index 0000000..99e4b43 100644 --- /dev/null +++ a/src/methods/repo.rs @@ -1,0 +1,174 @@ +use std::{ + ops::Deref, + path::{Path, PathBuf}, +}; + +use askama::Template; +use axum::{ + handler::Handler, + http::Request, + response::{Html, IntoResponse, Response}, + Extension, +}; +use path_clean::PathClean; +use tower::{util::BoxCloneService, Service}; + +use crate::{get_latest_commit, git::Commit, layers::UnwrapInfallible}; + +#[derive(Clone)] +pub struct Repository(pub PathBuf); + +impl Deref for Repository { + type Target = Path; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct RepositoryPath(pub PathBuf); + +impl Deref for RepositoryPath { + type Target = Path; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub async fn service(mut request: Request) -> Response { + let mut uri_parts: Vec<&str> = request + .uri() + .path() + .trim_start_matches('/') + .trim_end_matches('/') + .split('/') + .collect(); + + let mut service = match uri_parts.pop() { + Some("about") => BoxCloneService::new(handle_about.into_service()), + Some("refs") => BoxCloneService::new(handle_refs.into_service()), + Some("log") => BoxCloneService::new(handle_log.into_service()), + Some("tree") => BoxCloneService::new(handle_tree.into_service()), + Some("commit") => BoxCloneService::new(handle_commit.into_service()), + Some("diff") => BoxCloneService::new(handle_diff.into_service()), + Some("stats") => BoxCloneService::new(handle_stats.into_service()), + Some(v) => { + uri_parts.push(v); + BoxCloneService::new(handle_summary.into_service()) + } + None => panic!("not found"), + }; + + let uri = uri_parts.into_iter().collect::().clean(); + let path = Path::new("../test-git").canonicalize().unwrap().join(&uri); + + request.extensions_mut().insert(Repository(uri)); + request.extensions_mut().insert(RepositoryPath(path)); + + service + .call(request) + .await + .unwrap_infallible() + .into_response() +} + +#[allow(clippy::unused_async)] +pub async fn handle_summary(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/summary.html")] + pub struct View { + repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_log(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/log.html")] + pub struct View { + repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_refs(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/refs.html")] + pub struct View { + repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_about(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/about.html")] + pub struct View { + repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_commit( + Extension(repo): Extension, + Extension(RepositoryPath(repository_path)): Extension, +) -> Html { + #[derive(Template)] + #[template(path = "repo/commit.html")] + pub struct View { + pub repo: Repository, + pub commit: Commit, + } + + Html( + View { + repo, + commit: get_latest_commit(&repository_path), + } + .render() + .unwrap(), + ) +} + +#[allow(clippy::unused_async)] +pub async fn handle_tree(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/tree.html")] + pub struct View { + pub repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_diff(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/diff.html")] + pub struct View { + pub repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} + +#[allow(clippy::unused_async)] +pub async fn handle_stats(Extension(repo): Extension) -> Html { + #[derive(Template)] + #[template(path = "repo/stats.html")] + pub struct View { + pub repo: Repository, + } + + Html(View { repo }.render().unwrap()) +} diff --git a/templates/repo/about.html b/templates/repo/about.html new file mode 100644 index 0000000..f4d3acc 100644 --- /dev/null +++ a/templates/repo/about.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block about_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/base.html b/templates/repo/base.html new file mode 100644 index 0000000..24e1590 100644 --- /dev/null +++ a/templates/repo/base.html @@ -1,0 +1,14 @@ +{% extends "../base.html" %} + +{% block nav %} + +{% endblock %} diff --git a/templates/repo/commit.html b/templates/repo/commit.html new file mode 100644 index 0000000..f4e13b7 100644 --- /dev/null +++ a/templates/repo/commit.html @@ -1,0 +1,38 @@ +{% extends "repo/base.html" %} + +{% block commit_nav_class %}active{% endblock %} + +{% block content %} + + + + + + + + + + + + + + + + + + + + + {% for parent in commit.parents() %} + + + + + {% endfor %} + +
author{{ commit.author().name() }} <{{ commit.author().email() }}>{{ commit.author().time() }}
committer{{ commit.committer().name() }} <{{ commit.committer().email() }}>{{ commit.committer().time() }}
commit
{{ commit.oid() }}
tree
{{ commit.tree() }}
parent
{{ parent }}
+ +

{{ commit.summary() }}

+
{{ commit.body() }}
+ +{% endblock %}diff --git a/templates/repo/diff.html b/templates/repo/diff.html new file mode 100644 index 0000000..aa0b72d 100644 --- /dev/null +++ a/templates/repo/diff.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block diff_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/log.html b/templates/repo/log.html new file mode 100644 index 0000000..a50ecab 100644 --- /dev/null +++ a/templates/repo/log.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block log_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/refs.html b/templates/repo/refs.html new file mode 100644 index 0000000..efdfb77 100644 --- /dev/null +++ a/templates/repo/refs.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block refs_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/stats.html b/templates/repo/stats.html new file mode 100644 index 0000000..e917c8f 100644 --- /dev/null +++ a/templates/repo/stats.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block stats_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/summary.html b/templates/repo/summary.html new file mode 100644 index 0000000..d72f5be 100644 --- /dev/null +++ a/templates/repo/summary.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block summary_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}diff --git a/templates/repo/tree.html b/templates/repo/tree.html new file mode 100644 index 0000000..1fac543 100644 --- /dev/null +++ a/templates/repo/tree.html @@ -1,0 +1,6 @@ +{% extends "repo/base.html" %} + +{% block tree_nav_class %}active{% endblock %} + +{% block content %} +{% endblock %}-- rgit 0.1.3