Move from libgit2 to gitoxide
Diff
Cargo.lock | 1270 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Cargo.toml | 2 +-
README.md | 6 +++---
flake.nix | 3 +--
src/git.rs | 875 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
src/main.rs | 1 +
src/unified_diff_builder.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
doc/man/rgit.1.md | 2 +-
src/database/indexer.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
src/methods/filters.rs | 4 ++--
src/database/schema/commit.rs | 56 ++++++++++++++++++++++++++++++--------------------------
src/database/schema/tag.rs | 10 +++++-----
src/methods/repo/commit.rs | 4 ++--
src/methods/repo/diff.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++------------
14 files changed, 2064 insertions(+), 488 deletions(-)
@@ -18,6 +18,19 @@
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -25,6 +38,12 @@
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "ansi_colours"
@@ -95,6 +114,12 @@
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 = "askama"
@@ -435,6 +460,7 @@
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"regex-automata 0.4.6",
"serde",
]
@@ -568,6 +594,12 @@
"serde",
"winapi",
]
[[package]]
name = "clru"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59"
[[package]]
name = "colorchoice"
@@ -775,6 +807,20 @@
"darling_core",
"quote",
"syn 1.0.109",
]
[[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]]
@@ -842,6 +888,12 @@
dependencies = [
"const-random",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
@@ -927,6 +979,12 @@
"bit-set",
"regex",
]
[[package]]
name = "faster-hex"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
[[package]]
name = "fastrand"
@@ -981,125 +1039,913 @@
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[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.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gix"
version = "0.66.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9048b8d1ae2104f045cb37e5c450fc49d5d8af22609386bfc739c11ba88995eb"
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-ref",
"gix-refspec",
"gix-revision",
"gix-revwalk",
"gix-sec",
"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.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc19e312cd45c4a66cd003f909163dc2f8e1623e30a0c0c6df3776e89b308665"
dependencies = [
"bstr",
"gix-date",
"gix-utils",
"itoa",
"thiserror",
"winnow",
]
[[package]]
name = "gix-archive"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9147c08a55c1398b755539e2cdd63ff690ffe4a2e5e5e0780ee6ef2b49b0a60a"
dependencies = [
"bstr",
"gix-date",
"gix-object",
"gix-worktree-stream",
"jiff",
"thiserror",
]
[[package]]
name = "gix-attributes"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebccbf25aa4a973dd352564a9000af69edca90623e8a16dad9cbc03713131311"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-quote",
"gix-trace",
"kstring",
"smallvec",
"thiserror",
"unicode-bom",
]
[[package]]
name = "gix-bitmap"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae"
dependencies = [
"thiserror",
]
[[package]]
name = "gix-chunk"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52"
dependencies = [
"thiserror",
]
[[package]]
name = "gix-command"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff2e692b36bbcf09286c70803006ca3fd56551a311de450be317a0ab8ea92e7"
dependencies = [
"bstr",
"gix-path",
"gix-trace",
"shell-words",
]
[[package]]
name = "gix-commitgraph"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133b06f67f565836ec0c473e2116a60fb74f80b6435e21d88013ac0e3c60fc78"
dependencies = [
"bstr",
"gix-chunk",
"gix-features",
"gix-hash",
"memmap2",
"thiserror",
]
[[package]]
name = "gix-config"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78e797487e6ca3552491de1131b4f72202f282fb33f198b1c34406d765b42bb0"
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.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c"
dependencies = [
"bitflags 2.5.0",
"bstr",
"gix-path",
"libc",
"thiserror",
]
[[package]]
name = "gix-credentials"
version = "0.24.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce391d305968782f1ae301c4a3d42c5701df7ff1d8bc03740300f6fd12bce78"
dependencies = [
"bstr",
"gix-command",
"gix-config-value",
"gix-path",
"gix-prompt",
"gix-sec",
"gix-trace",
"gix-url",
"thiserror",
]
[[package]]
name = "gix-date"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c84b7af01e68daf7a6bb8bb909c1ff5edb3ce4326f1f43063a5a96d3c3c8a5"
dependencies = [
"bstr",
"itoa",
"jiff",
"thiserror",
]
[[package]]
name = "gix-diff"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92c9afd80fff00f8b38b1c1928442feb4cd6d2232a6ed806b6b193151a3d336c"
dependencies = [
"bstr",
"gix-command",
"gix-filter",
"gix-fs",
"gix-hash",
"gix-object",
"gix-path",
"gix-tempfile",
"gix-trace",
"gix-worktree",
"imara-diff",
"thiserror",
]
[[package]]
name = "gix-dir"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed3a9076661359a1c5a27c12ad6c3ebe2dd96b8b3c0af6488ab7c128b7bdd98"
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.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0577366b9567376bc26e815fd74451ebd0e6218814e242f8e5b7072c58d956d2"
dependencies = [
"bstr",
"dunce",
"gix-fs",
"gix-hash",
"gix-path",
"gix-ref",
"gix-sec",
"thiserror",
]
[[package]]
name = "gix-features"
version = "0.38.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69"
dependencies = [
"bytes",
"bytesize",
"crc32fast",
"crossbeam-channel",
"flate2",
"gix-hash",
"gix-trace",
"gix-utils",
"libc",
"once_cell",
"parking_lot",
"prodash",
"sha1_smol",
"thiserror",
"walkdir",
]
[[package]]
name = "gix-filter"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4121790ae140066e5b953becc72e7496278138d19239be2e63b5067b0843119e"
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.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575"
dependencies = [
"fastrand",
"gix-features",
"gix-utils",
]
[[package]]
name = "gix-glob"
version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111"
dependencies = [
"bitflags 2.5.0",
"bstr",
"gix-features",
"gix-path",
]
[[package]]
name = "gix-hash"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e"
dependencies = [
"faster-hex",
"thiserror",
]
[[package]]
name = "gix-hashtable"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242"
dependencies = [
"gix-hash",
"hashbrown 0.14.5",
"parking_lot",
]
[[package]]
name = "gix-ignore"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e447cd96598460f5906a0f6c75e950a39f98c2705fc755ad2f2020c9e937fab7"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-trace",
"unicode-bom",
]
[[package]]
name = "gix-index"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cd4203244444017682176e65fd0180be9298e58ed90bd4a8489a357795ed22d"
dependencies = [
"bitflags 2.5.0",
"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 = "14.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d"
dependencies = [
"gix-tempfile",
"gix-utils",
"thiserror",
]
[[package]]
name = "gix-mailmap"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d522c8ec2501e1a5b2b4cb54e83cb5d9a52471c9d23b3a1e8dadaf063752f7"
dependencies = [
"bstr",
"gix-actor",
"gix-date",
"thiserror",
]
[[package]]
name = "gix-negotiate"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4063bf329a191a9e24b6f948a17ccf6698c0380297f5e169cee4f1d2ab9475b"
dependencies = [
"bitflags 2.5.0",
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-object",
"gix-revwalk",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-object"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f5b801834f1de7640731820c2df6ba88d95480dc4ab166a5882f8ff12b88efa"
dependencies = [
"bstr",
"gix-actor",
"gix-date",
"gix-features",
"gix-hash",
"gix-utils",
"gix-validate",
"itoa",
"smallvec",
"thiserror",
"winnow",
]
[[package]]
name = "gix-odb"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3158068701c17df54f0ab2adda527f5a6aca38fd5fd80ceb7e3c0a2717ec747"
dependencies = [
"arc-swap",
"gix-date",
"gix-features",
"gix-fs",
"gix-hash",
"gix-object",
"gix-pack",
"gix-path",
"gix-quote",
"parking_lot",
"tempfile",
"thiserror",
]
[[package]]
name = "gix-pack"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3223aa342eee21e1e0e403cad8ae9caf9edca55ef84c347738d10681676fd954"
dependencies = [
"clru",
"gix-chunk",
"gix-features",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-path",
"memmap2",
"smallvec",
"thiserror",
"uluru",
]
[[package]]
name = "gix-packetline-blocking"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9802304baa798dd6f5ff8008a2b6516d54b74a69ca2d3a2b9e2d6c3b5556b40"
dependencies = [
"bstr",
"faster-hex",
"gix-trace",
"thiserror",
]
[[package]]
name = "gix-path"
version = "0.10.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af"
dependencies = [
"bstr",
"gix-trace",
"home",
"once_cell",
"thiserror",
]
[[package]]
name = "gix-pathspec"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d23bf239532b4414d0e63b8ab3a65481881f7237ed9647bb10c1e3cc54c5ceb"
dependencies = [
"bitflags 2.5.0",
"bstr",
"gix-attributes",
"gix-config-value",
"gix-glob",
"gix-path",
"thiserror",
]
[[package]]
name = "gix-prompt"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fde865cdb46b30d8dad1293385d9bcf998d3a39cbf41bee67d0dab026fe6b1"
dependencies = [
"gix-command",
"gix-config-value",
"parking_lot",
"rustix",
"thiserror",
]
[[package]]
name = "gix-quote"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff"
dependencies = [
"bstr",
"gix-utils",
"thiserror",
]
[[package]]
name = "gix-ref"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae0d8406ebf9aaa91f55a57f053c5a1ad1a39f60fdf0303142b7be7ea44311e5"
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.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebb005f82341ba67615ffdd9f7742c87787544441c88090878393d0682869ca6"
dependencies = [
"bstr",
"gix-hash",
"gix-revision",
"gix-validate",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-revision"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4621b219ac0cdb9256883030c3d56a6c64a6deaa829a92da73b9a576825e1e"
dependencies = [
"bstr",
"gix-date",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-revwalk",
"gix-trace",
"thiserror",
]
[[package]]
name = "gix-revwalk"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41e72544b93084ee682ef3d5b31b1ba4d8fa27a017482900e5e044d5b1b3984"
dependencies = [
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-hashtable",
"gix-object",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-sec"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f"
dependencies = [
"bitflags 2.5.0",
"gix-path",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
name = "gix-status"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "f70d35ba639f0c16a6e4cca81aa374a05f07b23fa36ee8beb72c100d98b4ffea"
dependencies = [
"futures-core",
"futures-sink",
"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 = "futures-core"
version = "0.3.30"
name = "gix-submodule"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "529d0af78cc2f372b3218f15eb1e3d1635a21c8937c12e2dd0b6fc80c2ca874b"
dependencies = [
"bstr",
"gix-config",
"gix-path",
"gix-pathspec",
"gix-refspec",
"gix-url",
"thiserror",
]
[[package]]
name = "futures-executor"
version = "0.3.30"
name = "gix-tempfile"
version = "14.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
"dashmap",
"gix-fs",
"libc",
"once_cell",
"parking_lot",
"signal-hook",
"signal-hook-registry",
"tempfile",
]
[[package]]
name = "futures-io"
version = "0.3.30"
name = "gix-trace"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b"
[[package]]
name = "futures-macro"
version = "0.3.30"
name = "gix-traverse"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "030da39af94e4df35472e9318228f36530989327906f38e27807df305fccb780"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
"bitflags 2.5.0",
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-revwalk",
"smallvec",
"thiserror",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
name = "gix-url"
version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "fd280c5e84fb22e128ed2a053a0daeacb6379469be6a85e3d518a0636e160c89"
dependencies = [
"bstr",
"gix-features",
"gix-path",
"home",
"thiserror",
"url",
]
[[package]]
name = "futures-util"
version = "0.3.30"
name = "gix-utils"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
"bstr",
"fastrand",
"unicode-normalization",
]
[[package]]
name = "generic-array"
version = "0.14.7"
name = "gix-validate"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "81f2badbb64e57b404593ee26b752c26991910fd0d81fe6f9a71c1a8309b6c86"
dependencies = [
"typenum",
"version_check",
"bstr",
"thiserror",
]
[[package]]
name = "getrandom"
version = "0.2.15"
name = "gix-worktree"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "c312ad76a3f2ba8e865b360d5cb3aa04660971d16dec6dd0ce717938d903149a"
dependencies = [
"cfg-if",
"libc",
"wasi",
"bstr",
"gix-attributes",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
"gix-validate",
]
[[package]]
name = "gimli"
version = "0.28.1"
name = "gix-worktree-state"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
checksum = "7b05c4b313fa702c0bacd5068dd3e01671da73b938fade97676859fee286de43"
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 = "git2"
version = "0.18.3"
name = "gix-worktree-stream"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
checksum = "68e81b87c1a3ece22a54b682d6fdc37fbb3977132da972cafe5ec07175fddbca"
dependencies = [
"bitflags 2.5.0",
"libc",
"libgit2-sys",
"log",
"openssl-probe",
"openssl-sys",
"url",
"gix-attributes",
"gix-features",
"gix-filter",
"gix-fs",
"gix-hash",
"gix-object",
"gix-path",
"gix-traverse",
"parking_lot",
"thiserror",
]
[[package]]
@@ -1151,6 +1997,10 @@
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hdrhistogram"
@@ -1265,6 +2115,12 @@
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 = "humansize"
@@ -1366,6 +2222,16 @@
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "imara-diff"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9da1a252bd44cd341657203722352efc9bc0c847d06ea6d2dc1cd1135e0a01"
dependencies = [
"ahash",
"hashbrown 0.14.5",
]
[[package]]
@@ -1386,6 +2252,16 @@
dependencies = [
"equivalent",
"hashbrown 0.14.5",
]
[[package]]
name = "io-close"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
dependencies = [
"libc",
"winapi",
]
[[package]]
@@ -1408,6 +2284,31 @@
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jiff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a45489186a6123c128fdf6016183fcfab7113e1820eb813127e036e287233fb"
dependencies = [
"jiff-tzdb-platform",
"windows-sys 0.52.0",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653"
[[package]]
name = "jiff-tzdb-platform"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329"
dependencies = [
"jiff-tzdb",
]
[[package]]
name = "jobserver"
@@ -1425,6 +2326,15 @@
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"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]]
@@ -1444,20 +2354,6 @@
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "libgit2-sys"
version = "0.16.2+1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
dependencies = [
"cc",
"libc",
"libssh2-sys",
"libz-sys",
"openssl-sys",
"pkg-config",
]
[[package]]
name = "libloading"
@@ -1466,7 +2362,7 @@
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
"windows-targets 0.48.5",
]
[[package]]
@@ -1485,22 +2381,8 @@
"bzip2-sys",
"cc",
"glob",
"libc",
"libz-sys",
]
[[package]]
name = "libssh2-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
]
[[package]]
@@ -1510,7 +2392,6 @@
checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
@@ -1575,6 +2456,15 @@
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
[[package]]
name = "mime"
@@ -1758,27 +2648,9 @@
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[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.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
@@ -1823,7 +2695,7 @@
"libc",
"redox_syscall 0.5.1",
"smallvec",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -1898,6 +2770,12 @@
"serde",
"time",
]
[[package]]
name = "portable-atomic"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce"
[[package]]
name = "powerfmt"
@@ -1918,6 +2796,16 @@
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prodash"
version = "28.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
dependencies = [
"bytesize",
"human_format",
]
[[package]]
@@ -2111,7 +2999,7 @@
"console-subscriber",
"flate2",
"futures",
"git2",
"gix",
"hex",
"httparse",
"humantime",
@@ -2315,6 +3203,12 @@
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "sha2"
@@ -2347,6 +3241,16 @@
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
@@ -2399,6 +3303,12 @@
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "std_prelude"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2499,6 +3409,19 @@
"filetime",
"libc",
"xattr",
]
[[package]]
name = "tempfile"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
@@ -2829,6 +3752,15 @@
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "uluru"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da"
dependencies = [
"arrayvec",
]
[[package]]
name = "unicase"
@@ -2844,6 +3776,12 @@
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-bom"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-ident"
@@ -2886,9 +3824,9 @@
[[package]]
name = "url"
version = "2.5.0"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
@@ -3062,8 +4000,17 @@
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3083,18 +4030,18 @@
[[package]]
name = "windows-targets"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@@ -3105,9 +4052,9 @@
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@@ -3117,9 +4064,9 @@
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@@ -3129,15 +4076,15 @@
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@@ -3147,9 +4094,9 @@
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@@ -3159,9 +4106,9 @@
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -3171,9 +4118,9 @@
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@@ -3183,9 +4130,18 @@
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]
[[package]]
name = "xattr"
@@ -3235,6 +4191,26 @@
"quote",
"syn 2.0.63",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
@@ -20,7 +20,7 @@
comrak = "0.21.0"
clap = { version = "4.4.10", features = ["cargo", "derive"] }
futures = "0.3"
git2 = "0.18.0"
gix = "0.66"
hex = "0.4"
humantime = "2.1"
itertools = "0.12"
@@ -1,10 +1,10 @@
# rgit
## Introduction
[See it in action!](https://git.inept.dev/)
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama and RocksDB.
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, gitoxide, Askama and RocksDB.
Includes a dark mode for late night committing.
@@ -38,13 +38,13 @@
[RocksDB][] is used to store all metadata about a repository, including commits, branches, and tags. Metadata is reindexed, and the reindex interval is configurable (default: every 5 minutes), resulting in up to 97% faster load times for large repositories.
- **On-Demand Loading**
Files, trees, and diffs are loaded using [git2][] directly upon request. A small in-memory cache is included for rendered READMEs and diffs, enhancing performance.
Files, trees, and diffs are loaded using [gitoxide][] directly upon request. A small in-memory cache is included for rendered READMEs and diffs, enhancing performance.
- **Dark Mode Support**
Enjoy a dark mode for late-night committing, providing a visually comfortable experience during extended coding sessions.
[RocksDB]: https://github.com/facebook/rocksdb
[git2]: https://github.com/rust-lang/git2-rs
[gitoxide]: https://github.com/Byron/gitoxide
## Getting Started
@@ -15,10 +15,9 @@
defaultPackage = naersk-lib.buildPackage {
root = ./.;
nativeBuildInputs = with pkgs; [ pkg-config clang ];
buildInputs = with pkgs; [ openssl zlib libssh2 libgit2 ];
buildInputs = with pkgs; [ ];
LIBCLANG_PATH = "${pkgs.clang.cc.lib}/lib";
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/lib";
LIBSSH2_SYS_USE_PKG_CONFIG = "true";
};
devShell = with pkgs; mkShell {
buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ];
@@ -1,37 +1,49 @@
use std::{
borrow::Cow,
ffi::OsStr,
fmt,
fmt::Write,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use anyhow::{anyhow, Context, Result};
use axum::response::IntoResponse;
use bytes::buf::Writer;
use bytes::{BufMut, Bytes, BytesMut};
use comrak::{ComrakPlugins, Options};
use git2::{
DiffFormat, DiffLineType, DiffOptions, DiffStatsFormat, Email, EmailCreateOptions, ObjectType,
Oid, Signature, TreeWalkResult,
use flate2::write::GzEncoder;
use gix::{
actor::SignatureRef,
bstr::{BStr, BString, ByteSlice, ByteVec},
diff::blob::{platform::prepare_diff::Operation, Sink},
object::Kind,
objs::tree::EntryRef,
prelude::TreeEntryRefExt,
traverse::tree::visit::Action,
ObjectId,
};
use moka::future::Cache;
use parking_lot::Mutex;
use std::{
borrow::Cow,
collections::{BTreeMap, VecDeque},
ffi::OsStr,
fmt::{self, Arguments, Write},
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
time::Duration,
};
use syntect::{
parsing::SyntaxSet,
parsing::{BasicScopeStackOp, ParseState, Scope, ScopeStack, SCOPE_REPO},
util::LinesWithEndings,
};
use time::OffsetDateTime;
use tar::Builder;
use time::{OffsetDateTime, UtcOffset};
use tracing::{error, instrument, warn};
use crate::syntax_highlight::ComrakSyntectAdapter;
use crate::{
syntax_highlight::ComrakSyntectAdapter,
unified_diff_builder::{Callback, UnifiedDiffBuilder},
};
type ReadmeCacheKey = (PathBuf, Option<Arc<str>>);
pub struct Git {
commits: Cache<Oid, Arc<Commit>>,
commits: Cache<(ObjectId, bool), Arc<Commit>>,
readme_cache: Cache<ReadmeCacheKey, Option<(ReadmeFormat, Arc<str>)>>,
syntax_set: SyntaxSet,
}
@@ -60,9 +72,9 @@
repo_path: PathBuf,
branch: Option<Arc<str>>,
) -> Result<Arc<OpenRepository>> {
let repo = tokio::task::spawn_blocking({
let mut repo = tokio::task::spawn_blocking({
let repo_path = repo_path.clone();
move || git2::Repository::open(repo_path)
move || gix::open(repo_path)
})
.await
.context("Failed to join Tokio task")?
@@ -71,6 +83,8 @@
anyhow!("Failed to open repository")
})?;
repo.object_cache_size(10 * 1024 * 1024);
Ok(Arc::new(OpenRepository {
git: self,
cache_key: repo_path,
@@ -83,7 +97,7 @@
pub struct OpenRepository {
git: Arc<Git>,
cache_key: PathBuf,
repo: Mutex<git2::Repository>,
repo: Mutex<gix::Repository>,
branch: Option<Arc<str>>,
}
@@ -95,7 +109,7 @@
formatted: bool,
) -> Result<PathDestination> {
let tree_id = tree_id
.map(Oid::from_str)
.map(ObjectId::from_str)
.transpose()
.context("Failed to parse tree hash")?;
@@ -106,87 +120,88 @@
repo.find_tree(tree_id)
.context("Couldn't find tree with given id")?
} else if let Some(branch) = &self.branch {
let reference = repo.resolve_reference_from_short_name(branch)?;
reference
repo.find_reference(branch.as_ref())?
.peel_to_tree()
.context("Couldn't find tree for reference")?
} else {
let head = repo.head().context("Failed to find HEAD")?;
head.peel_to_tree()
.context("Couldn't find tree from HEAD")?
repo.find_reference("HEAD")
.context("Failed to find HEAD")?
.peel_to_tree()
.context("Couldn't find HEAD for reference")?
};
if let Some(path) = path.as_ref() {
let item = tree.get_path(path).context("Path doesn't exist in tree")?;
let object = item
.to_object(&repo)
.context("Path in tree isn't an object")?;
if let Some(blob) = object.as_blob() {
let name = String::from_utf8_lossy(item.name_bytes());
let path = path.clone().join(&*name);
let extension = path
.extension()
.or_else(|| path.file_name())
.map_or_else(|| Cow::Borrowed(""), OsStr::to_string_lossy);
let content = match (formatted, blob.is_binary()) {
(true, true) => Content::Binary(vec![]),
(true, false) => Content::Text(
format_file(
&String::from_utf8_lossy(blob.content()),
let item = tree
.peel_to_entry_by_path(path)?
.context("Path doesn't exist in tree")?;
let object = item.object().context("Path in tree isn't an object")?;
match object.kind {
Kind::Blob => {
let path = path.join(item.filename().to_path_lossy());
let mut blob = object.into_blob();
let size = blob.data.len();
let extension = path
.extension()
.or_else(|| path.file_name())
.map_or_else(|| Cow::Borrowed(""), OsStr::to_string_lossy);
let content = match (formatted, String::from_utf8(blob.take_data())) {
(true, Err(_)) => Content::Binary(vec![]),
(true, Ok(data)) => Content::Text(Cow::Owned(format_file(
&data,
&extension,
&self.git.syntax_set,
)?
.into(),
),
(false, true) => Content::Binary(blob.content().to_vec()),
(false, false) => Content::Text(
String::from_utf8_lossy(blob.content()).to_string().into(),
),
};
)?)),
(false, Err(e)) => Content::Binary(e.into_bytes()),
(false, Ok(data)) => Content::Text(Cow::Owned(data)),
};
return Ok(PathDestination::File(FileWithContent {
metadata: File {
mode: item.filemode(),
size: blob.size(),
path,
name: name.into_owned(),
},
content,
}));
} else if let Ok(new_tree) = object.into_tree() {
tree = new_tree;
} else {
anyhow::bail!("Given path not tree nor blob... what is it?!");
return Ok(PathDestination::File(FileWithContent {
metadata: File {
mode: item.mode().0,
size,
path: path.clone(),
name: item.filename().to_string(),
},
content,
}));
}
Kind::Tree => {
tree = object.into_tree();
}
_ => anyhow::bail!("bad object of type {:?}", object.kind),
}
}
let mut tree_items = Vec::new();
for item in &tree {
for item in tree.iter() {
let item = item?;
let object = item
.to_object(&repo)
.object()
.context("Expected item in tree to be object but it wasn't")?;
let name = String::from_utf8_lossy(item.name_bytes()).into_owned();
let path = path.clone().unwrap_or_default().join(&name);
if let Some(blob) = object.as_blob() {
tree_items.push(TreeItem::File(File {
mode: item.filemode(),
size: blob.size(),
let path = path
.clone()
.unwrap_or_default()
.join(item.filename().to_path_lossy());
tree_items.push(match object.kind {
Kind::Blob => TreeItem::File(File {
mode: item.mode().0,
size: object.into_blob().data.len(),
path,
name,
}));
} else if let Some(_tree) = object.as_tree() {
tree_items.push(TreeItem::Tree(Tree {
mode: item.filemode(),
name: item.filename().to_string(),
}),
Kind::Tree => TreeItem::Tree(Tree {
mode: item.mode().0,
path,
name,
}));
}
name: item.filename().to_string(),
}),
_ => continue,
});
}
Ok(PathDestination::Tree(tree_items))
@@ -206,21 +221,23 @@
.context("Given tag does not exist in repository")?
.peel_to_tag()
.context("Couldn't get to a tag from the given reference")?;
let tag_target = tag.target().context("Couldn't find tagged object")?;
let tagged_object = match tag_target.kind() {
Some(ObjectType::Commit) => Some(TaggedObject::Commit(tag_target.id().to_string())),
Some(ObjectType::Tree) => Some(TaggedObject::Tree(tag_target.id().to_string())),
None | Some(_) => None,
let tag_target = tag
.target_id()
.context("Couldn't find tagged object")?
.object()?;
let tagged_object = match tag_target.kind {
Kind::Commit => Some(TaggedObject::Commit(tag_target.id.to_string())),
Kind::Tree => Some(TaggedObject::Tree(tag_target.id.to_string())),
_ => None,
};
let tag_info = tag.decode()?;
Ok(DetailedTag {
name: tag_name,
tagger: tag.tagger().map(TryInto::try_into).transpose()?,
message: tag
.message_bytes()
.map_or_else(|| Cow::Borrowed(""), String::from_utf8_lossy)
.into_owned(),
tagger: tag_info.tagger.map(TryInto::try_into).transpose()?,
message: tag_info.message.to_string(),
tagged_object,
})
})
@@ -241,33 +258,34 @@
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let head = if let Some(reference) = &self.branch {
repo.resolve_reference_from_short_name(reference)?
let mut head = if let Some(reference) = &self.branch {
repo.find_reference(reference.as_ref())?
} else {
repo.head().context("Couldn't find HEAD of repository")?
repo.find_reference("HEAD")
.context("Couldn't find HEAD of repository")?
};
let commit = head.peel_to_commit().context(
"Couldn't find the commit that the HEAD of the repository refers to",
)?;
let tree = commit
let mut tree = commit
.tree()
.context("Couldn't get the tree that the HEAD refers to")?;
for name in README_FILES {
let Some(tree_entry) = tree.get_name(name) else {
let Some(tree_entry) = tree.peel_to_entry_by_path(name)? else {
continue;
};
let Some(blob) = tree_entry
.to_object(&repo)
.object()
.ok()
.and_then(|v| v.into_blob().ok())
.and_then(|v| v.try_into_blob().ok())
else {
continue;
};
let Ok(content) = std::str::from_utf8(blob.content()) else {
let Ok(content) = std::str::from_utf8(&blob.data) else {
continue;
};
@@ -291,33 +309,33 @@
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let head = repo.head().context("Couldn't find HEAD of repository")?;
Ok(head.shorthand().map(ToString::to_string))
Ok(head.referent_name().map(|v| v.shorten().to_string()))
})
.await
.context("Failed to join Tokio task")?
}
#[instrument(skip(self))]
pub async fn latest_commit(self: Arc<Self>) -> Result<Commit> {
pub async fn latest_commit(self: Arc<Self>, highlighted: bool) -> Result<Commit> {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let head = if let Some(reference) = &self.branch {
repo.resolve_reference_from_short_name(reference)?
let mut head = if let Some(reference) = &self.branch {
repo.find_reference(reference.as_ref())?
} else {
repo.head().context("Couldn't find HEAD of repository")?
repo.find_reference("HEAD")
.context("Couldn't find HEAD of repository")?
};
let commit = head
.peel_to_commit()
.context("Couldn't find commit HEAD of repository refers to")?;
let (diff_plain, diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set)?;
let (diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, highlighted.then_some(&self.git.syntax_set))?;
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
commit.diff_plain = diff_plain;
Ok(commit)
})
.await
@@ -331,28 +349,20 @@
cont: tokio::sync::oneshot::Sender<()>,
commit: Option<&str>,
) -> Result<(), anyhow::Error> {
const BUFFER_CAP: usize = 512 * 1024;
let commit = commit
.map(Oid::from_str)
.map(ObjectId::from_str)
.transpose()
.context("failed to build oid")?;
tokio::task::spawn_blocking(move || {
let buffer = BytesMut::with_capacity(BUFFER_CAP + 1024);
let flate = flate2::write::GzEncoder::new(buffer.writer(), flate2::Compression::fast());
let mut archive = tar::Builder::new(flate);
let repo = self.repo.lock();
let tree = if let Some(commit) = commit {
repo.find_commit(commit)?.tree()?
} else if let Some(reference) = &self.branch {
repo.resolve_reference_from_short_name(reference)?
.peel_to_tree()?
repo.find_reference(reference.as_ref())?.peel_to_tree()?
} else {
repo.head()
repo.find_reference("HEAD")
.context("Couldn't find HEAD of repository")?
.peel_to_tree()?
};
@@ -361,42 +371,24 @@
if cont.send(()).is_err() {
return Err(anyhow!("requester gone"));
}
let mut callback = |root: &str, entry: &git2::TreeEntry| -> TreeWalkResult {
if let Ok(blob) = entry.to_object(&repo).unwrap().peel_to_blob() {
let path =
Path::new(root).join(String::from_utf8_lossy(entry.name_bytes()).as_ref());
let mut header = tar::Header::new_gnu();
if let Err(error) = header.set_path(&path) {
warn!(%error, "Attempted to write invalid path to archive");
return TreeWalkResult::Skip;
}
header.set_size(blob.size() as u64);
#[allow(clippy::cast_sign_loss)]
header.set_mode(entry.filemode() as u32);
header.set_cksum();
if let Err(error) = archive.append(&header, blob.content()) {
error!(%error, "Failed to write blob to archive");
return TreeWalkResult::Abort;
}
}
if archive.get_ref().get_ref().get_ref().len() >= BUFFER_CAP {
let b = archive.get_mut().get_mut().get_mut().split().freeze();
if let Err(error) = res.blocking_send(Ok(b)) {
error!(%error, "Failed to send buffer to client");
return TreeWalkResult::Abort;
}
}
TreeWalkResult::Ok
let buffer = BytesMut::with_capacity(BUFFER_CAP + 1024);
let mut visitor = ArchivalVisitor {
repository: &repo,
res,
archive: Builder::new(GzEncoder::new(buffer.writer(), flate2::Compression::fast())),
path_deque: VecDeque::new(),
path: BString::default(),
};
tree.walk(git2::TreeWalkMode::PreOrder, &mut callback)?;
tree.traverse().breadthfirst(&mut visitor)?;
res.blocking_send(Ok(archive.into_inner()?.finish()?.into_inner().freeze()))?;
visitor.res.blocking_send(Ok(visitor
.archive
.into_inner()?
.finish()?
.into_inner()
.freeze()))?;
Ok::<_, anyhow::Error>(())
})
@@ -406,26 +398,33 @@
}
#[instrument(skip(self))]
pub async fn commit(self: Arc<Self>, commit: &str) -> Result<Arc<Commit>, Arc<anyhow::Error>> {
let commit = Oid::from_str(commit)
pub async fn commit(
self: Arc<Self>,
commit: &str,
highlighted: bool,
) -> Result<Arc<Commit>, Arc<anyhow::Error>> {
let commit = ObjectId::from_str(commit)
.map_err(anyhow::Error::from)
.map_err(Arc::new)?;
let git = self.git.clone();
git.commits
.try_get_with(commit, async move {
.try_get_with((commit, highlighted), async move {
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let commit = repo.find_commit(commit)?;
let (diff_plain, diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set)?;
let (diff_output, diff_stats) = fetch_diff_and_stats(
&repo,
&commit,
highlighted.then_some(&self.git.syntax_set),
)?;
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
commit.diff_plain = diff_plain;
Ok(Arc::new(commit))
})
@@ -433,6 +432,98 @@
.context("Failed to join Tokio task")?
})
.await
}
}
const BUFFER_CAP: usize = 512 * 1024;
pub struct ArchivalVisitor<'a> {
repository: &'a gix::Repository,
res: tokio::sync::mpsc::Sender<Result<Bytes, anyhow::Error>>,
archive: Builder<GzEncoder<Writer<BytesMut>>>,
path_deque: VecDeque<BString>,
path: BString,
}
impl<'a> ArchivalVisitor<'a> {
fn pop_element(&mut self) {
if let Some(pos) = self.path.rfind_byte(b'/') {
self.path.resize(pos, 0);
} else {
self.path.clear();
}
}
fn push_element(&mut self, name: &BStr) {
if !self.path.is_empty() {
self.path.push(b'/');
}
self.path.push_str(name);
}
}
impl<'a> gix::traverse::tree::Visit for ArchivalVisitor<'a> {
fn pop_front_tracked_path_and_set_current(&mut self) {
self.path = self
.path_deque
.pop_front()
.expect("every call is matched with push_tracked_path_component");
}
fn push_back_tracked_path_component(&mut self, component: &BStr) {
self.push_element(component);
self.path_deque.push_back(self.path.clone());
}
fn push_path_component(&mut self, component: &BStr) {
self.push_element(component);
}
fn pop_path_component(&mut self) {
self.pop_element();
}
fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> Action {
Action::Continue
}
fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action {
let entry = entry.attach(self.repository);
let Ok(object) = entry.object() else {
return Action::Continue;
};
if object.kind != Kind::Blob {
return Action::Continue;
}
let blob = object.into_blob();
let mut header = tar::Header::new_gnu();
if let Err(error) = header.set_path(self.path.to_path_lossy()) {
warn!(%error, "Attempted to write invalid path to archive");
return Action::Continue;
}
header.set_size(blob.data.len() as u64);
#[allow(clippy::cast_sign_loss)]
header.set_mode(entry.mode().0.into());
header.set_cksum();
if let Err(error) = self.archive.append(&header, blob.data.as_slice()) {
warn!(%error, "Failed to append to archive");
return Action::Cancel;
}
if self.archive.get_ref().get_ref().get_ref().len() >= BUFFER_CAP {
let b = self.archive.get_mut().get_mut().get_mut().split().freeze();
if self.res.blocking_send(Ok(b)).is_err() {
return Action::Cancel;
}
}
Action::Continue
}
}
@@ -473,20 +564,21 @@
#[derive(Debug)]
pub struct Tree {
pub mode: i32,
pub mode: u16,
pub name: String,
pub path: PathBuf,
}
#[derive(Debug)]
pub struct File {
pub mode: i32,
pub mode: u16,
pub size: usize,
pub name: String,
pub path: PathBuf,
}
#[derive(Debug)]
#[allow(unused)]
pub struct FileWithContent {
pub metadata: File,
pub content: Content,
@@ -544,14 +636,15 @@
time: OffsetDateTime,
}
impl TryFrom<Signature<'_>> for CommitUser {
impl TryFrom<SignatureRef<'_>> for CommitUser {
type Error = anyhow::Error;
fn try_from(v: Signature<'_>) -> Result<Self> {
fn try_from(v: SignatureRef<'_>) -> Result<Self> {
Ok(CommitUser {
name: String::from_utf8_lossy(v.name_bytes()).into_owned(),
email: String::from_utf8_lossy(v.email_bytes()).into_owned(),
time: OffsetDateTime::from_unix_timestamp(v.when().seconds())?,
name: v.name.to_string(),
email: v.email.to_string(),
time: OffsetDateTime::from_unix_timestamp(v.time.seconds)?
.to_offset(UtcOffset::from_whole_seconds(v.time.offset)?),
})
}
}
@@ -581,30 +674,24 @@
body: String,
pub diff_stats: String,
pub diff: String,
pub diff_plain: Bytes,
}
impl TryFrom<git2::Commit<'_>> for Commit {
impl TryFrom<gix::Commit<'_>> for Commit {
type Error = anyhow::Error;
fn try_from(commit: git2::Commit<'_>) -> Result<Self> {
fn try_from(commit: gix::Commit<'_>) -> Result<Self> {
let message = commit.message()?;
Ok(Commit {
author: CommitUser::try_from(commit.author())?,
committer: CommitUser::try_from(commit.committer())?,
author: CommitUser::try_from(commit.author()?)?,
committer: CommitUser::try_from(commit.committer()?)?,
oid: commit.id().to_string(),
tree: commit.tree_id().to_string(),
tree: commit.tree_id()?.to_string(),
parents: commit.parent_ids().map(|v| v.to_string()).collect(),
summary: commit
.summary_bytes()
.map_or_else(|| Cow::Borrowed(""), String::from_utf8_lossy)
.into_owned(),
body: commit
.body_bytes()
.map_or_else(|| Cow::Borrowed(""), String::from_utf8_lossy)
.into_owned(),
summary: message.summary().to_string(),
body: message.body.map_or_else(String::new, ToString::to_string),
diff_stats: String::with_capacity(0),
diff: String::with_capacity(0),
diff_plain: Bytes::new(),
})
}
}
@@ -641,42 +728,134 @@
#[instrument(skip(repo, commit, syntax_set))]
fn fetch_diff_and_stats(
repo: &git2::Repository,
commit: &git2::Commit<'_>,
syntax_set: &SyntaxSet,
) -> Result<(Bytes, String, String)> {
repo: &gix::Repository,
commit: &gix::Commit<'_>,
syntax_set: Option<&SyntaxSet>,
) -> Result<(String, String)> {
const WIDTH: usize = 80;
let current_tree = commit.tree().context("Couldn't get tree for the commit")?;
let parent_tree = commit.parents().next().and_then(|v| v.tree().ok());
let mut diff_opts = DiffOptions::new();
let diff = repo.diff_tree_to_tree(
parent_tree.as_ref(),
Some(¤t_tree),
Some(&mut diff_opts),
let parent_tree = commit
.ancestors()
.first_parent_only()
.all()?
.nth(1)
.transpose()?
.map(|v| v.object())
.transpose()?
.map(|v| v.tree())
.transpose()?
.unwrap_or_else(|| repo.empty_tree());
let mut diffs = BTreeMap::<_, FileDiff>::new();
let mut diff_output = String::new();
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
let mut changes = parent_tree.changes()?;
changes.track_path().track_rewrites(None);
changes.for_each_to_obtain_tree_with_cache(
¤t_tree,
&mut repo.diff_resource_cache_for_tree_diff()?,
|change| {
if let Some(syntax_set) = syntax_set {
DiffBuilder {
output: &mut diff_output,
resource_cache: &mut resource_cache,
diffs: &mut diffs,
formatter: SyntaxHighlightedDiffFormatter::new(
change.location.to_path().unwrap(),
syntax_set,
),
}
.handle(change)
} else {
DiffBuilder {
output: &mut diff_output,
resource_cache: &mut resource_cache,
diffs: &mut diffs,
formatter: PlainDiffFormatter,
}
.handle(change)
}
},
)?;
let (max_file_name_length, max_change_length, files_changed, insertions, deletions) =
diffs.iter().fold(
(0, 0, 0, 0, 0),
|(max_file_name_length, max_change_length, files_changed, insertions, deletions),
(f, stats)| {
(
max_file_name_length.max(f.len()),
max_change_length
.max(((stats.insertions + stats.deletions).ilog10() + 1) as usize),
files_changed + 1,
insertions + stats.insertions,
deletions + stats.deletions,
)
},
);
let mut diff_stats = String::new();
let total_changes = insertions + deletions;
for (file, diff) in &diffs {
let local_changes = diff.insertions + diff.deletions;
let width = WIDTH.min(local_changes);
let addition_width = (width * diff.insertions) / total_changes;
let deletion_width = (width * diff.deletions) / total_changes;
let remaining_width = width - (addition_width + deletion_width);
let adjusted_addition_width = addition_width + remaining_width.min(diff.insertions);
let adjusted_deletion_width =
deletion_width + (remaining_width - remaining_width.min(diff.insertions));
let plus_str = "+".repeat(adjusted_addition_width);
let minus_str = "-".repeat(adjusted_deletion_width);
writeln!(diff_stats, " {file:max_file_name_length$} | {local_changes:max_change_length$} {plus_str}{minus_str}").unwrap();
}
for (i, (singular_desc, plural_desc, amount)) in [
("file changed", "files changed", files_changed),
("insertion(+)", "insertions(+)", insertions),
("deletion(-)", "deletions(-)", deletions),
]
.into_iter()
.enumerate()
{
if amount == 0 {
continue;
}
let prefix = if i == 0 { "" } else { "," };
let mut diff_plain = BytesMut::new();
let email = Email::from_diff(
&diff,
1,
1,
&commit.id(),
commit.summary().unwrap_or(""),
commit.body().unwrap_or(""),
&commit.author(),
&mut EmailCreateOptions::default(),
)
.context("Couldn't build diff for commit")?;
diff_plain.extend_from_slice(email.as_slice());
let diff_stats = diff
.stats()?
.to_buf(DiffStatsFormat::FULL, 80)?
.as_str()
.unwrap_or("")
.to_string();
let diff_output = format_diff(&diff, syntax_set)?;
Ok((diff_plain.freeze(), diff_output, diff_stats))
let desc = if amount == 1 {
singular_desc
} else {
plural_desc
};
write!(diff_stats, "{prefix} {amount} {desc}")?;
}
writeln!(diff_stats)?;
Ok((diff_output, diff_stats))
}
#[derive(Default, Debug)]
struct FileDiff {
insertions: usize,
deletions: usize,
}
fn format_file(content: &str, extension: &str, syntax_set: &SyntaxSet) -> Result<String> {
@@ -834,48 +1013,256 @@
}
}
#[instrument(skip(diff, syntax_set))]
fn format_diff(diff: &git2::Diff<'_>, syntax_set: &SyntaxSet) -> Result<String> {
let mut diff_output = String::new();
trait DiffFormatter {
fn file_header(&self, output: &mut String, data: fmt::Arguments<'_>);
fn binary(
&self,
output: &mut String,
left: &str,
right: &str,
left_content: &[u8],
right_content: &[u8],
);
}
struct DiffBuilder<'a, F> {
output: &'a mut String,
resource_cache: &'a mut gix::diff::blob::Platform,
diffs: &'a mut BTreeMap<String, FileDiff>,
formatter: F,
}
impl<'a, F: DiffFormatter + Callback> DiffBuilder<'a, F> {
fn handle(
&mut self,
change: gix::object::tree::diff::Change<'_, '_, '_>,
) -> Result<gix::object::tree::diff::Action> {
if !change.event.entry_mode().is_blob_or_symlink() {
return Ok(gix::object::tree::diff::Action::Continue);
}
let diff = self.diffs.entry(change.location.to_string()).or_default();
let change = change.diff(self.resource_cache)?;
let prep = change.resource_cache.prepare_diff()?;
self.formatter.file_header(
self.output,
format_args!(
"diff --git a/{} b/{}",
prep.old.rela_path, prep.new.rela_path
),
);
if prep.old.id.is_null() {
self.formatter.file_header(
self.output,
format_args!("new file mode {}", prep.new.mode.as_octal_str()),
);
} else if prep.new.id.is_null() {
self.formatter.file_header(
self.output,
format_args!("deleted file mode {}", prep.old.mode.as_octal_str()),
);
} else if prep.new.mode != prep.old.mode {
self.formatter.file_header(
self.output,
format_args!("old mode {}", prep.old.mode.as_octal_str()),
);
self.formatter.file_header(
self.output,
format_args!("new mode {}", prep.new.mode.as_octal_str()),
);
}
diff.print(DiffFormat::Patch, |delta, _diff_hunk, diff_line| {
let (class, should_highlight_as_source) = match diff_line.origin_value() {
DiffLineType::Addition => (Some("add-line"), true),
DiffLineType::Deletion => (Some("remove-line"), true),
DiffLineType::Context => (Some("context"), true),
DiffLineType::AddEOFNL => (Some("remove-line"), false),
DiffLineType::DeleteEOFNL => (Some("add-line"), false),
DiffLineType::FileHeader => (Some("file-header"), false),
_ => (None, false),
let (index_suffix_sep, index_suffix) = if prep.old.mode == prep.new.mode {
(" ", prep.new.mode.as_octal_str())
} else {
("", BStr::new(&[]))
};
let line = String::from_utf8_lossy(diff_line.content());
let old_path = if prep.old.id.is_null() {
Cow::Borrowed("/dev/null")
} else {
Cow::Owned(format!("a/{}", prep.old.rela_path))
};
let extension = if should_highlight_as_source {
if let Some(path) = delta.new_file().path() {
path.extension()
.or_else(|| path.file_name())
.map_or_else(|| Cow::Borrowed(""), OsStr::to_string_lossy)
} else {
Cow::Borrowed("")
}
let new_path = if prep.new.id.is_null() {
Cow::Borrowed("/dev/null")
} else {
Cow::Borrowed("patch")
Cow::Owned(format!("a/{}", prep.new.rela_path))
};
if let Some(class) = class {
let _ = write!(diff_output, r#"<span class="diff-{class}">"#);
match prep.operation {
Operation::InternalDiff { algorithm } => {
self.formatter.file_header(
self.output,
format_args!(
"index {}..{}{index_suffix_sep}{index_suffix}",
prep.old.id.to_hex_with_len(7),
prep.new.id.to_hex_with_len(7)
),
);
self.formatter
.file_header(self.output, format_args!("--- {old_path}"));
self.formatter
.file_header(self.output, format_args!("+++ {new_path}"));
let old_source = gix::diff::blob::sources::lines_with_terminator(
std::str::from_utf8(prep.old.data.as_slice().unwrap_or_default())?,
);
let new_source = gix::diff::blob::sources::lines_with_terminator(
std::str::from_utf8(prep.new.data.as_slice().unwrap_or_default())?,
);
let input = gix::diff::blob::intern::InternedInput::new(old_source, new_source);
let output = gix::diff::blob::diff(
algorithm,
&input,
UnifiedDiffBuilder::with_writer(&input, &mut *self.output, &mut self.formatter)
.with_counter(),
);
diff.deletions += output.removals as usize;
diff.insertions += output.insertions as usize;
}
Operation::ExternalCommand { .. } => {}
Operation::SourceOrDestinationIsBinary => {
self.formatter.file_header(
self.output,
format_args!(
"index {}..{}{index_suffix_sep}{index_suffix}",
prep.old.id, prep.new.id,
),
);
self.formatter.binary(
self.output,
old_path.as_ref(),
new_path.as_ref(),
prep.old.data.as_slice().unwrap_or_default(),
prep.new.data.as_slice().unwrap_or_default(),
);
}
}
self.resource_cache.clear_resource_cache_keep_allocation();
Ok(gix::object::tree::diff::Action::Continue)
}
}
struct PlainDiffFormatter;
impl DiffFormatter for PlainDiffFormatter {
fn file_header(&self, output: &mut String, data: fmt::Arguments<'_>) {
writeln!(output, "{data}").unwrap();
}
fn binary(
&self,
output: &mut String,
left: &str,
right: &str,
_left_content: &[u8],
_right_content: &[u8],
) {
writeln!(output, "Binary files {left} and {right} differ").unwrap();
}
}
impl Callback for PlainDiffFormatter {
fn addition(&mut self, data: &str, dst: &mut String) {
write!(dst, "+{data}").unwrap();
}
fn remove(&mut self, data: &str, dst: &mut String) {
write!(dst, "-{data}").unwrap();
}
fn context(&mut self, data: &str, dst: &mut String) {
write!(dst, " {data}").unwrap();
}
}
struct SyntaxHighlightedDiffFormatter<'a> {
syntax_set: &'a SyntaxSet,
extension: Cow<'a, str>,
}
let _res = format_file_inner(&mut diff_output, &line, &extension, syntax_set, false);
impl<'a> SyntaxHighlightedDiffFormatter<'a> {
fn new(path: &'a Path, syntax_set: &'a SyntaxSet) -> Self {
let extension = path
.extension()
.or_else(|| path.file_name())
.map_or_else(|| Cow::Borrowed(""), OsStr::to_string_lossy);
if class.is_some() {
diff_output.push_str("</span>");
Self {
syntax_set,
extension,
}
}
fn write(&self, output: &mut String, class: &str, data: &str) {
write!(output, r#"<span class="diff-{class}">"#).unwrap();
format_file_inner(
output,
data,
self.extension.as_ref(),
self.syntax_set,
false,
)
.unwrap();
write!(output, r#"</span>"#).unwrap();
}
}
impl<'a> DiffFormatter for SyntaxHighlightedDiffFormatter<'a> {
fn file_header(&self, output: &mut String, data: Arguments<'_>) {
write!(output, r#"<span class="diff-file-header">"#).unwrap();
format_file_inner(output, &data.to_string(), "patch", self.syntax_set, false).unwrap();
writeln!(output, r#"</span>"#).unwrap();
}
fn binary(
&self,
output: &mut String,
left: &str,
right: &str,
_left_content: &[u8],
_right_content: &[u8],
) {
format_file_inner(
output,
&format!("Binary files {left} and {right} differ"),
"patch",
self.syntax_set,
false,
)
.unwrap();
}
}
impl<'a> Callback for SyntaxHighlightedDiffFormatter<'a> {
fn addition(&mut self, data: &str, dst: &mut String) {
self.write(dst, "add-line", data);
}
true
})
.context("Failed to prepare diff")?;
fn remove(&mut self, data: &str, dst: &mut String) {
self.write(dst, "remote-line", data);
}
Ok(diff_output)
fn context(&mut self, data: &str, dst: &mut String) {
self.write(dst, "context", data);
}
}
@@ -52,6 +52,7 @@
mod layers;
mod methods;
mod syntax_highlight;
mod unified_diff_builder;
const CRATE_VERSION: &str = clap::crate_version!();
@@ -1,0 +1,143 @@
use std::fmt::Write;
use std::ops::Range;
use gix::diff::blob::intern::{InternedInput, Interner, Token};
use gix::diff::blob::Sink;
pub(crate) trait Callback {
fn addition(&mut self, data: &str, dst: &mut String);
fn remove(&mut self, data: &str, dst: &mut String);
fn context(&mut self, data: &str, dst: &mut String);
}
impl<C: Callback> Callback for &mut C {
fn addition(&mut self, data: &str, dst: &mut String) {
(*self).addition(data, dst);
}
fn remove(&mut self, data: &str, dst: &mut String) {
(*self).remove(data, dst);
}
fn context(&mut self, data: &str, dst: &mut String) {
(*self).context(data, dst);
}
}
pub struct UnifiedDiffBuilder<'a, C, W>
where
C: Callback,
W: Write,
{
before: &'a [Token],
after: &'a [Token],
interner: &'a Interner<&'a str>,
pos: u32,
before_hunk_start: u32,
after_hunk_start: u32,
before_hunk_len: u32,
after_hunk_len: u32,
callback: C,
buffer: String,
dst: W,
}
impl<'a, C, W> UnifiedDiffBuilder<'a, C, W>
where
C: Callback,
W: Write,
{
pub fn with_writer(input: &'a InternedInput<&'a str>, writer: W, callback: C) -> Self {
Self {
before_hunk_start: 0,
after_hunk_start: 0,
before_hunk_len: 0,
after_hunk_len: 0,
buffer: String::with_capacity(8),
dst: writer,
interner: &input.interner,
before: &input.before,
after: &input.after,
callback,
pos: 0,
}
}
fn flush(&mut self) {
if self.before_hunk_len == 0 && self.after_hunk_len == 0 {
return;
}
let end = (self.pos + 3).min(u32::try_from(self.before.len()).unwrap_or(u32::MAX));
self.update_pos(end, end);
writeln!(
&mut self.dst,
"@@ -{},{} +{},{} @@",
self.before_hunk_start + 1,
self.before_hunk_len,
self.after_hunk_start + 1,
self.after_hunk_len,
)
.unwrap();
write!(&mut self.dst, "{}", &self.buffer).unwrap();
self.buffer.clear();
self.before_hunk_len = 0;
self.after_hunk_len = 0;
}
fn update_pos(&mut self, print_to: u32, move_to: u32) {
for token in &self.before[self.pos as usize..print_to as usize] {
self.callback
.context(self.interner[*token], &mut self.buffer);
}
let len = print_to - self.pos;
self.pos = move_to;
self.before_hunk_len += len;
self.after_hunk_len += len;
}
}
impl<C, W> Sink for UnifiedDiffBuilder<'_, C, W>
where
C: Callback,
W: Write,
{
type Out = W;
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
if before.start - self.pos > 6 {
self.flush();
self.pos = before.start - 3;
self.before_hunk_start = self.pos;
self.after_hunk_start = after.start - 3;
}
self.update_pos(before.start, before.end);
self.before_hunk_len += before.end - before.start;
self.after_hunk_len += after.end - after.start;
for token in &self.before[before.start as usize..before.end as usize] {
self.callback
.remove(self.interner[*token], &mut self.buffer);
}
for token in &self.after[after.start as usize..after.end as usize] {
self.callback
.addition(self.interner[*token], &mut self.buffer);
}
}
fn finish(mut self) -> Self::Out {
self.flush();
self.dst
}
}
@@ -15,7 +15,7 @@
DESCRIPTION
===========
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama, and RocksDB.
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, gitoxide, Askama, and RocksDB.
_bind_address_
@@ -8,11 +8,13 @@
};
use anyhow::Context;
use git2::{ErrorCode, Reference, Sort};
use gix::bstr::ByteSlice;
use gix::refs::Category;
use gix::Reference;
use ini::Ini;
use itertools::Itertools;
use rocksdb::WriteBatch;
use time::OffsetDateTime;
use time::{OffsetDateTime, UtcOffset};
use tracing::{error, info, info_span, instrument, warn};
use crate::database::schema::{
@@ -69,7 +71,7 @@
let repository_path = scan_path.join(relative);
let git_repository = match git2::Repository::open(repository_path.clone()) {
let mut git_repository = match gix::open(repository_path.clone()) {
Ok(v) => v,
Err(error) => {
warn!(%error, "Failed to open repository {} to update metadata, skipping", relative.display());
@@ -77,6 +79,8 @@
}
};
git_repository.object_cache_size(10 * 1024 * 1024);
let res = Repository {
id,
name,
@@ -97,21 +101,25 @@
}
}
fn find_default_branch(repo: &git2::Repository) -> Result<Option<String>, git2::Error> {
Ok(repo.head()?.name().map(ToString::to_string))
fn find_default_branch(repo: &gix::Repository) -> Result<Option<String>, anyhow::Error> {
Ok(Some(repo.head()?.name().as_bstr().to_string()))
}
fn find_last_committed_time(repo: &git2::Repository) -> Result<OffsetDateTime, git2::Error> {
fn find_last_committed_time(repo: &gix::Repository) -> Result<OffsetDateTime, anyhow::Error> {
let mut timestamp = OffsetDateTime::UNIX_EPOCH;
for reference in repo.references()? {
let Ok(commit) = reference?.peel_to_commit() else {
for reference in repo.references()?.all()? {
let Ok(commit) = reference.unwrap().peel_to_commit() else {
continue;
};
let committed_time = commit.committer().when().seconds();
let committed_time = OffsetDateTime::from_unix_timestamp(committed_time)
let committer = commit.committer()?;
let mut committed_time = OffsetDateTime::from_unix_timestamp(committer.time.seconds)
.unwrap_or(OffsetDateTime::UNIX_EPOCH);
if let Ok(offset) = UtcOffset::from_whole_seconds(committer.time.offset) {
committed_time = committed_time.to_offset(offset);
}
if committed_time > timestamp {
timestamp = committed_time;
@@ -138,6 +146,14 @@
};
let references = match git_repository.references() {
Ok(v) => v,
Err(error) => {
error!(%error, "Failed to read references for {relative_path}");
continue;
}
};
let references = match references.all() {
Ok(v) => v,
Err(error) => {
error!(%error, "Failed to read references for {relative_path}");
@@ -147,26 +163,34 @@
let mut valid_references = Vec::new();
for reference in references.filter_map(Result::ok) {
let reference_name = String::from_utf8_lossy(reference.name_bytes());
if !reference_name.starts_with("refs/heads/")
&& !reference_name.starts_with("refs/tags/")
{
for reference in references {
let mut reference = match reference {
Ok(v) => v,
Err(error) => {
error!(%error, "Failed to read reference for {relative_path}");
continue;
}
};
let reference_name = reference.name();
if !matches!(
reference_name.category(),
Some(Category::Tag | Category::LocalBranch)
) {
continue;
}
valid_references.push(reference_name.to_string());
valid_references.push(reference_name.as_bstr().to_string());
if let Err(error) = branch_index_update(
&reference,
&reference_name,
&mut reference,
&relative_path,
db_repository.get(),
db.clone(),
&git_repository,
false,
) {
error!(%error, "Failed to update reflog for {relative_path}@{reference_name}");
error!(%error, "Failed to update reflog for {relative_path}@{:?}", valid_references.last());
}
}
@@ -178,17 +202,16 @@
#[instrument(skip(reference, db_repository, db, git_repository))]
fn branch_index_update(
reference: &Reference<'_>,
reference_name: &str,
reference: &mut Reference<'_>,
relative_path: &str,
db_repository: &Repository<'_>,
db: Arc<rocksdb::DB>,
git_repository: &git2::Repository,
git_repository: &gix::Repository,
force_reindex: bool,
) -> Result<(), anyhow::Error> {
info!("Refreshing indexes");
let commit_tree = db_repository.commit_tree(db.clone(), reference_name);
let commit_tree = db_repository.commit_tree(db.clone(), reference.name().as_bstr().to_str()?);
if force_reindex {
commit_tree.drop_commits()?;
@@ -207,9 +230,13 @@
None
};
let mut revwalk = git_repository.revwalk()?;
revwalk.set_sorting(Sort::REVERSE)?;
revwalk.push_ref(reference_name)?;
let revwalk = git_repository
.rev_walk([commit.id().detach()])
.all()?
.collect::<Vec<_>>()
.into_iter()
.rev();
let tree_len = commit_tree.len()?;
let mut seen = false;
@@ -221,7 +248,7 @@
let rev = rev?;
if let (false, Some(latest_indexed)) = (seen, &latest_indexed) {
if rev.as_bytes() == &*latest_indexed.get().hash {
if rev.id.as_bytes() == &*latest_indexed.get().hash {
seen = true;
}
@@ -234,11 +261,11 @@
info!("{} commits ingested", i + 1);
}
let commit = git_repository.find_commit(rev)?;
let author = commit.author();
let committer = commit.committer();
let commit = rev.object()?;
let author = commit.author()?;
let committer = commit.committer()?;
Commit::new(&commit, &author, &committer).insert(
Commit::new(&commit, author, committer)?.insert(
&commit_tree,
tree_len + i,
&mut batch,
@@ -255,7 +282,6 @@
return branch_index_update(
reference,
reference_name,
relative_path,
db_repository,
db,
@@ -299,16 +325,17 @@
relative_path: &str,
db_repository: &Repository<'_>,
db: Arc<rocksdb::DB>,
git_repository: &git2::Repository,
git_repository: &gix::Repository,
) -> Result<(), anyhow::Error> {
let tag_tree = db_repository.tag_tree(db);
let git_tags: HashSet<_> = git_repository
.references()
.context("Failed to scan indexes on git repository")?
.all()?
.filter_map(Result::ok)
.filter(|v| v.name_bytes().starts_with(b"refs/tags/"))
.map(|v| String::from_utf8_lossy(v.name_bytes()).into_owned())
.filter(|v| v.name().category() == Some(Category::Tag))
.map(|v| v.name().as_bstr().to_string())
.collect();
let indexed_tags: HashSet<String> = tag_tree.list()?.into_iter().collect();
@@ -329,17 +356,17 @@
#[instrument(skip(git_repository, tag_tree))]
fn tag_index_update(
tag_name: &str,
git_repository: &git2::Repository,
git_repository: &gix::Repository,
tag_tree: &TagTree,
) -> Result<(), anyhow::Error> {
let reference = git_repository
let mut reference = git_repository
.find_reference(tag_name)
.context("Failed to read newly discovered tag")?;
if let Ok(tag) = reference.peel_to_tag() {
info!("Inserting newly discovered tag to index");
Tag::new(tag.tagger().as_ref()).insert(tag_tree, tag_name)?;
Tag::new(tag.tagger()?)?.insert(tag_tree, tag_name)?;
}
Ok(())
@@ -359,10 +386,13 @@
relative_path: P,
db_repository: &Repository<'_>,
db: &rocksdb::DB,
) -> Option<git2::Repository> {
match git2::Repository::open(scan_path.join(relative_path.as_ref())) {
Ok(v) => Some(v),
Err(e) if e.code() == ErrorCode::NotFound => {
) -> Option<gix::Repository> {
match gix::open(scan_path.join(relative_path.as_ref())) {
Ok(mut v) => {
v.object_cache_size(10 * 1024 * 1024);
Some(v)
}
Err(gix::open::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {
warn!("Repository gone from disk, removing from db");
if let Err(error) = db_repository.delete(db, relative_path) {
@@ -17,8 +17,8 @@
.convert((time::OffsetDateTime::now_utc() - *s.borrow()).unsigned_abs()))
}
pub fn file_perms(s: &i32) -> Result<String, askama::Error> {
Ok(unix_mode::to_string(s.unsigned_abs()))
pub fn file_perms(s: &u16) -> Result<String, askama::Error> {
Ok(unix_mode::to_string(u32::from(*s)))
}
pub fn hex(s: &[u8]) -> Result<String, askama::Error> {
@@ -1,10 +1,12 @@
use std::{borrow::Cow, ops::Deref, sync::Arc};
use anyhow::Context;
use git2::{Oid, Signature};
use gix::actor::SignatureRef;
use gix::bstr::ByteSlice;
use gix::ObjectId;
use rocksdb::{IteratorMode, ReadOptions, WriteBatch};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use time::OffsetDateTime;
use time::{OffsetDateTime, UtcOffset};
use tracing::debug;
use yoke::{Yoke, Yokeable};
@@ -27,21 +29,21 @@
impl<'a> Commit<'a> {
pub fn new(
commit: &'a git2::Commit<'_>,
author: &'a git2::Signature<'_>,
committer: &'a git2::Signature<'_>,
) -> Self {
Self {
summary: commit
.summary_bytes()
.map_or(Cow::Borrowed(""), String::from_utf8_lossy),
message: commit
.body_bytes()
.map_or(Cow::Borrowed(""), String::from_utf8_lossy),
committer: committer.into(),
author: author.into(),
hash: CommitHash::Oid(commit.id()),
}
commit: &gix::Commit<'_>,
author: SignatureRef<'a>,
committer: SignatureRef<'a>,
) -> Result<Self, anyhow::Error> {
let message = commit.message()?;
Ok(Self {
summary: message.summary().to_string().into(),
message: message
.body
.map_or(Cow::Borrowed(""), |v| v.to_string().into()),
committer: committer.try_into()?,
author: author.try_into()?,
hash: CommitHash::Oid(commit.id().detach()),
})
}
pub fn insert(&self, tree: &CommitTree, id: u64, tx: &mut WriteBatch) -> anyhow::Result<()> {
@@ -51,7 +53,7 @@
#[derive(Debug)]
pub enum CommitHash<'a> {
Oid(Oid),
Oid(ObjectId),
Bytes(&'a [u8]),
}
@@ -97,14 +99,16 @@
pub time: OffsetDateTime,
}
impl<'a> From<&'a git2::Signature<'_>> for Author<'a> {
fn from(author: &'a Signature<'_>) -> Self {
Self {
name: String::from_utf8_lossy(author.name_bytes()),
email: String::from_utf8_lossy(author.email_bytes()),
time: OffsetDateTime::from_unix_timestamp(author.when().seconds()).unwrap(),
}
impl<'a> TryFrom<SignatureRef<'a>> for Author<'a> {
type Error = anyhow::Error;
fn try_from(author: SignatureRef<'a>) -> Result<Self, anyhow::Error> {
Ok(Self {
name: author.name.to_str_lossy(),
email: author.email.to_str_lossy(),
time: OffsetDateTime::from_unix_timestamp(author.time.seconds)?
.to_offset(UtcOffset::from_whole_seconds(author.time.offset)?),
})
}
}
@@ -1,7 +1,7 @@
use std::{collections::HashSet, sync::Arc};
use anyhow::Context;
use git2::Signature;
use gix::actor::SignatureRef;
use serde::{Deserialize, Serialize};
use yoke::{Yoke, Yokeable};
@@ -16,10 +16,10 @@
}
impl<'a> Tag<'a> {
pub fn new(tagger: Option<&'a Signature<'_>>) -> Self {
Self {
tagger: tagger.map(Into::into),
}
pub fn new(tagger: Option<SignatureRef<'a>>) -> Result<Self, anyhow::Error> {
Ok(Self {
tagger: tagger.map(TryFrom::try_from).transpose()?,
})
}
pub fn insert(&self, batch: &TagTree, name: &str) -> Result<(), anyhow::Error> {
@@ -54,9 +54,9 @@
};
let commit = if let Some(commit) = query.id.as_deref() {
open_repo.commit(commit).await?
open_repo.commit(commit, true).await?
} else {
Arc::new(open_repo.latest_commit().await?)
Arc::new(open_repo.latest_commit(true).await?)
};
Ok(into_response(View {
@@ -1,13 +1,5 @@
use std::sync::Arc;
use askama::Template;
use axum::{
extract::Query,
http::HeaderValue,
response::{IntoResponse, Response},
Extension,
};
use crate::{
git::Commit,
http, into_response,
@@ -16,7 +8,18 @@
repo::{commit::UriQuery, Repository, RepositoryPath, Result},
},
Git,
};
use askama::Template;
use axum::{
extract::Query,
http::HeaderValue,
response::{IntoResponse, Response},
Extension,
};
use bytes::{BufMut, BytesMut};
use clap::crate_version;
use std::fmt::Write;
use time::format_description::well_known::Rfc2822;
#[derive(Template)]
#[template(path = "repo/diff.html")]
@@ -34,9 +37,9 @@
) -> Result<impl IntoResponse> {
let open_repo = git.repo(repository_path, query.branch.clone()).await?;
let commit = if let Some(commit) = query.id {
open_repo.commit(&commit).await?
open_repo.commit(&commit, true).await?
} else {
Arc::new(open_repo.latest_commit().await?)
Arc::new(open_repo.latest_commit(true).await?)
};
Ok(into_response(View {
@@ -53,15 +56,48 @@
) -> Result<Response> {
let open_repo = git.repo(repository_path, query.branch).await?;
let commit = if let Some(commit) = query.id {
open_repo.commit(&commit).await?
open_repo.commit(&commit, false).await?
} else {
Arc::new(open_repo.latest_commit().await?)
Arc::new(open_repo.latest_commit(false).await?)
};
let headers = [(
http::header::CONTENT_TYPE,
HeaderValue::from_static("text/plain"),
)];
let mut data = BytesMut::new();
writeln!(data, "From {} Mon Sep 17 00:00:00 2001", commit.oid()).unwrap();
writeln!(
data,
"From: {} <{}>",
commit.author().name(),
commit.author().email()
)
.unwrap();
write!(data, "Date: ").unwrap();
let mut writer = data.writer();
commit
.author()
.time()
.format_into(&mut writer, &Rfc2822)
.unwrap();
let mut data = writer.into_inner();
writeln!(data).unwrap();
writeln!(data, "Subject: [PATCH] {}\n", commit.summary()).unwrap();
write!(data, "{}", commit.body()).unwrap();
writeln!(data, "---").unwrap();
data.extend_from_slice(commit.diff_stats.as_bytes());
data.extend_from_slice(b"\n");
data.extend_from_slice(commit.diff.as_bytes());
writeln!(data, "--\nrgit {}", crate_version!()).unwrap();
Ok((headers, commit.diff_plain.clone()).into_response())
Ok((headers, data.freeze()).into_response())
}