🏡 index : ~doyle/rgit.git

author Jordan Doyle <jordan@doyle.la> 2022-07-06 22:43:53.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-07-06 22:43:53.0 +01:00:00
commit
2a9edba83a90889a5003203a7b63d9a239719a16 [patch]
tree
02ffa06ee565921e7891f6ac670695ef7f0744e0
parent
543067ecf94c9d52ef3ebdec1d2ffe8785c43c6b
download
2a9edba83a90889a5003203a7b63d9a239719a16.tar.gz

Implement diff on commit view with syntax highlighting



Diff

 Cargo.lock                 | 402 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml                 |   2 ++
 src/git.rs                 | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main.rs                |  17 ++++++++++++++++-
 statics/style.css          |  19 +++++++++++++++++++
 src/methods/repo.rs        |   6 ++++--
 templates/repo/commit.html |   8 ++++++++
 7 files changed, 557 insertions(+), 9 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 1303f79..c750fb4 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -1,8 +1,32 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
 "memchr",
]

[[package]]
name = "ansi_colours"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32678233b67f9056b0c144b39d46dc3218637e8d84ad6038ded339e08b19620d"
dependencies = [
 "rgb",
]

[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -173,13 +197,65 @@
 "http",
 "http-body",
 "mime",
]

[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"

[[package]]
name = "bat"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd1212d80800b3d7614b3725e0b2ee3b45b2b7484805d54b5660c8fa6f706305"
dependencies = [
 "ansi_colours",
 "ansi_term",
 "bincode",
 "bytesize",
 "clircle",
 "console",
 "content_inspector",
 "encoding",
 "flate2",
 "globset",
 "once_cell",
 "path_abs",
 "regex",
 "semver",
 "serde",
 "serde_yaml",
 "syntect",
 "thiserror",
 "unicode-width",
 "walkdir",
]

[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
 "serde",
]

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
 "memchr",
]

[[package]]
name = "bumpalo"
@@ -192,6 +268,12 @@
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"

[[package]]
name = "bytemuck"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a"

[[package]]
name = "bytes"
@@ -200,6 +282,12 @@
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"

[[package]]
name = "bytesize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"

[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -293,6 +381,18 @@
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
 "os_str_bytes",
]

[[package]]
name = "clircle"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e68bbd985a63de680ab4d1ad77b6306611a8f961b282c8b5ab513e6de934e396"
dependencies = [
 "cfg-if 1.0.0",
 "libc",
 "serde",
 "winapi",
]

[[package]]
@@ -311,9 +411,42 @@
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
 "cache-padded",
]

[[package]]
name = "console"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
dependencies = [
 "encode_unicode",
 "libc",
 "once_cell",
 "regex",
 "terminal_size",
 "unicode-width",
 "winapi",
]

[[package]]
name = "content_inspector"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
dependencies = [
 "memchr",
]

[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
 "cfg-if 1.0.0",
]

[[package]]
name = "crossbeam-channel"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -357,9 +490,79 @@
dependencies = [
 "cfg-if 1.0.0",
 "once_cell",
]

[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"

[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
 "encoding-index-japanese",
 "encoding-index-korean",
 "encoding-index-simpchinese",
 "encoding-index-singlebyte",
 "encoding-index-tradchinese",
]

[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
 "encoding_index_tests",
]

[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
 "encoding_index_tests",
]

[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
 "encoding_index_tests",
]

[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
 "encoding_index_tests",
]

[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
 "encoding_index_tests",
]

[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"

[[package]]
name = "error-chain"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -381,6 +584,16 @@
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
 "instant",
]

[[package]]
name = "flate2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
 "crc32fast",
 "miniz_oxide",
]

[[package]]
@@ -540,6 +753,19 @@
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"

[[package]]
name = "globset"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [
 "aho-corasick",
 "bstr",
 "fnv",
 "log",
 "regex",
]

[[package]]
name = "hashbrown"
@@ -751,7 +977,22 @@
 "libc",
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "line-wrap"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
dependencies = [
 "safemem",
]

[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"

[[package]]
name = "lock_api"
@@ -841,6 +1082,15 @@
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"

[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
 "adler",
]

[[package]]
name = "mio"
@@ -932,6 +1182,28 @@
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"

[[package]]
name = "onig"
version = "6.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1eb3502504c9c8b06634b38bfdda86a9a8cef6277f3dec4d8b17c115110dd2a3"
dependencies = [
 "bitflags",
 "lazy_static",
 "libc",
 "onig_sys",
]

[[package]]
name = "onig_sys"
version = "69.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf3fbc9b931b6c9af85d219c7943c274a6ad26cff7488a2210215edd5f49bf8"
dependencies = [
 "cc",
 "pkg-config",
]

[[package]]
name = "openssl-probe"
@@ -992,6 +1264,15 @@
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"

[[package]]
name = "path_abs"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3"
dependencies = [
 "std_prelude",
]

[[package]]
name = "percent-encoding"
@@ -1074,6 +1355,20 @@
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"

[[package]]
name = "plist"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225"
dependencies = [
 "base64",
 "indexmap",
 "line-wrap",
 "serde",
 "time 0.3.11",
 "xml-rs",
]

[[package]]
name = "polling"
@@ -1264,7 +1559,24 @@
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
 "bitflags",
]

[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"

[[package]]
name = "remove_dir_all"
@@ -1273,6 +1585,15 @@
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
 "winapi",
]

[[package]]
name = "rgb"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b221de559e4a29df3b957eec92bc0de6bc8eaf6ca9cfed43e5e1d67ff65a34"
dependencies = [
 "bytemuck",
]

[[package]]
@@ -1282,6 +1603,7 @@
 "arc-swap",
 "askama",
 "axum",
 "bat",
 "clap",
 "futures",
 "git2",
@@ -1291,6 +1613,7 @@
 "moka",
 "path-clean",
 "serde",
 "syntect",
 "time 0.3.11",
 "timeago",
 "tokio",
@@ -1307,6 +1630,12 @@
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"

[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"

[[package]]
name = "same-file"
@@ -1380,8 +1709,20 @@
dependencies = [
 "form_urlencoded",
 "itoa",
 "ryu",
 "serde",
]

[[package]]
name = "serde_yaml"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
dependencies = [
 "indexmap",
 "ryu",
 "serde",
 "yaml-rust",
]

[[package]]
@@ -1444,6 +1785,12 @@
 "libc",
 "winapi",
]

[[package]]
name = "std_prelude"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe"

[[package]]
name = "strsim"
@@ -1467,6 +1814,29 @@
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"

[[package]]
name = "syntect"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8"
dependencies = [
 "bincode",
 "bitflags",
 "flate2",
 "fnv",
 "lazy_static",
 "once_cell",
 "onig",
 "plist",
 "regex-syntax",
 "serde",
 "serde_derive",
 "serde_json",
 "thiserror",
 "walkdir",
 "yaml-rust",
]

[[package]]
name = "tagptr"
@@ -1495,6 +1865,16 @@
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
 "winapi-util",
]

[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
 "libc",
 "winapi",
]

[[package]]
@@ -1548,6 +1928,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
dependencies = [
 "itoa",
 "libc",
 "num_threads",
]
@@ -1764,6 +2145,12 @@
dependencies = [
 "tinyvec",
]

[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"

[[package]]
name = "url"
@@ -1989,3 +2376,18 @@
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"

[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"

[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
 "linked-hash-map",
]
diff --git a/Cargo.toml b/Cargo.toml
index acd49b9..2577af8 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -9,6 +9,7 @@
askama = "0.11"
arc-swap = "1.5"
axum = "0.5"
bat = { version = "0.21", default-features = false, features = ["build-assets"] }
clap = { version = "3.2", features = ["cargo"] }
futures = "0.3"
git2 = "0.14"
@@ -18,6 +19,7 @@
moka = { version = "0.9", features = ["future"] }
path-clean = "0.1"
serde = { version = "1.0", features = ["derive"] }
syntect = "5"
time = "0.3"
timeago = "0.3"
tokio = { version = "1.19", features = ["full"] }
diff --git a/src/git.rs b/src/git.rs
index 74fcf82..27a6315 100644
--- a/src/git.rs
+++ a/src/git.rs
@@ -7,8 +7,12 @@
};

use arc_swap::ArcSwapOption;
use git2::{ObjectType, Oid, Repository, Signature};
use git2::{
    DiffFormat, DiffLineType, DiffOptions, DiffStatsFormat, ObjectType, Oid, Repository, Signature,
};
use moka::future::Cache;
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
use syntect::parsing::SyntaxSet;
use time::OffsetDateTime;

pub type RepositoryMetadataList = BTreeMap<Option<String>, Vec<RepositoryMetadata>>;
@@ -42,7 +46,12 @@
}

impl Git {
    pub async fn get_commit<'a>(&'a self, repo: PathBuf, commit: &str) -> Arc<Commit> {
    pub async fn get_commit<'a>(
        &'a self,
        repo: PathBuf,
        commit: &str,
        syntax_set: Arc<SyntaxSet>,
    ) -> Arc<Commit> {
        let commit = Oid::from_str(commit).unwrap();

        self.commits
@@ -50,8 +59,14 @@
                tokio::task::spawn_blocking(move || {
                    let repo = Repository::open_bare(repo).unwrap();
                    let commit = repo.find_commit(commit).unwrap();
                    let (diff_output, diff_stats) =
                        fetch_diff_and_stats(&repo, &commit, &syntax_set);

                    let mut commit = Commit::from(commit);
                    commit.diff_stats = diff_stats;
                    commit.diff = diff_output;

                    Arc::new(Commit::from(commit))
                    Arc::new(commit)
                })
                .await
                .unwrap()
@@ -153,13 +168,17 @@
            .await
    }

    pub async fn get_latest_commit(&self, repo: PathBuf) -> Commit {
    pub async fn get_latest_commit(&self, repo: PathBuf, syntax_set: Arc<SyntaxSet>) -> Commit {
        tokio::task::spawn_blocking(move || {
            let repo = Repository::open_bare(repo).unwrap();
            let head = repo.head().unwrap();
            let commit = head.peel_to_commit().unwrap();

            Commit::from(commit)
            let (diff_output, diff_stats) = fetch_diff_and_stats(&repo, &commit, &syntax_set);

            let mut commit = Commit::from(commit);
            commit.diff_stats = diff_stats;
            commit.diff = diff_output;
            commit
        })
        .await
        .unwrap()
@@ -316,6 +335,8 @@
    parents: Vec<String>,
    summary: String,
    body: String,
    pub diff_stats: String,
    pub diff: String,
}

impl From<git2::Commit<'_>> for Commit {
@@ -328,6 +349,8 @@
            parents: commit.parent_ids().map(|v| v.to_string()).collect(),
            summary: commit.summary().unwrap().to_string(),
            body: commit.body().map(ToString::to_string).unwrap_or_default(),
            diff_stats: String::with_capacity(0),
            diff: String::with_capacity(0),
        }
    }
}
@@ -360,6 +383,83 @@
    pub fn body(&self) -> &str {
        &self.body
    }
}

fn fetch_diff_and_stats(
    repo: &git2::Repository,
    commit: &git2::Commit<'_>,
    syntax_set: &SyntaxSet,
) -> (String, String) {
    let current_tree = commit.tree().unwrap();
    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(&current_tree),
            Some(&mut diff_opts),
        )
        .unwrap();
    let diff_stats = diff
        .stats()
        .unwrap()
        .to_buf(DiffStatsFormat::FULL, 80)
        .unwrap()
        .as_str()
        .unwrap()
        .to_string();
    let diff_output = format_diff(&diff, &syntax_set);

    (diff_output, diff_stats)
}

fn format_diff(diff: &git2::Diff<'_>, syntax_set: &SyntaxSet) -> String {
    let mut diff_output = String::new();

    diff.print(DiffFormat::Patch, |delta, _diff_hunk, diff_line| {
        let (class, prefix, should_highlight_as_source) = match diff_line.origin_value() {
            DiffLineType::Addition => (Some("add-line"), "+", true),
            DiffLineType::Deletion => (Some("remove-line"), "-", true),
            DiffLineType::Context => (None, " ", true),
            DiffLineType::AddEOFNL => (Some("remove-line"), "", false),
            DiffLineType::DeleteEOFNL => (Some("add-line"), "", false),
            DiffLineType::FileHeader => (Some("file-header"), "", false),
            _ => (None, "", false),
        };

        let line = std::str::from_utf8(diff_line.content()).unwrap();

        let extension = if should_highlight_as_source {
            let path = delta.new_file().path().unwrap();
            path.extension()
                .or(path.file_name())
                .unwrap()
                .to_string_lossy()
        } else {
            Cow::Borrowed("patch")
        };
        let syntax = syntax_set
            .find_syntax_by_extension(&extension)
            .unwrap_or(syntax_set.find_syntax_plain_text());
        let mut html_generator =
            ClassedHTMLGenerator::new_with_class_style(syntax, &syntax_set, ClassStyle::Spaced);
        html_generator
            .parse_html_for_line_which_includes_newline(line)
            .unwrap();
        if let Some(class) = class {
            diff_output.push_str(&format!("<span class=\"diff-{class}\">"));
        }
        diff_output.push_str(prefix);
        diff_output.push_str(&html_generator.finalize());
        if class.is_some() {
            diff_output.push_str("</span>");
        }

        true
    })
    .unwrap();

    diff_output
}

fn fetch_repository_metadata_impl(
diff --git a/src/main.rs b/src/main.rs
index 5fc349e..a6a4711 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -1,9 +1,12 @@
#![deny(clippy::pedantic)]

use axum::{
    body::Body, handler::Handler, http::HeaderValue, response::Response, routing::get, Extension,
    Router,
};
use bat::assets::HighlightingAssets;
use std::sync::Arc;
use syntect::html::ClassStyle;
use tower_layer::layer_fn;

use crate::{git::Git, layers::logger::LoggingMiddleware};
@@ -21,15 +24,27 @@
    let subscriber = subscriber.pretty();
    subscriber.init();

    let bat_assets = HighlightingAssets::from_binary();
    let syntax_set = bat_assets.get_syntax_set().unwrap().clone();
    let theme = bat_assets.get_theme("GitHub");
    let css = Box::leak(
        syntect::html::css_for_theme_with_class_style(theme, ClassStyle::Spaced)
            .unwrap()
            .into_boxed_str()
            .into_boxed_bytes(),
    );

    let app = Router::new()
        .route("/", get(methods::index::handle))
        .route(
            "/style.css",
            get(static_css(include_bytes!("../statics/style.css"))),
        )
        .route("/highlight.css", get(static_css(css)))
        .fallback(methods::repo::service.into_service())
        .layer(layer_fn(LoggingMiddleware))
        .layer(Extension(Git::default()));
        .layer(Extension(Git::default()))
        .layer(Extension(Arc::new(syntax_set)));

    axum::Server::bind(&"127.0.0.1:3333".parse().unwrap())
        .serve(app.into_make_service_with_connect_info::<std::net::SocketAddr>())
diff --git a/statics/style.css b/statics/style.css
index 51cae2b..4cf9841 100644
--- a/statics/style.css
+++ a/statics/style.css
@@ -91,3 +91,22 @@
    font-size: 1.5em;
    font-weight: bold;
}


.diff-file-header {
    font-weight: bold;
}

.diff-file-header > span > span {
    font-weight: normal;
}

.diff-add-line {
    background: #e6ffec;
    display: block;
}

.diff-remove-line {
    background: #ffebe9;
    display: block;
}
diff --git a/src/methods/repo.rs b/src/methods/repo.rs
index a1d583c..a04fd75 100644
--- a/src/methods/repo.rs
+++ a/src/methods/repo.rs
@@ -14,6 +14,7 @@
};
use path_clean::PathClean;
use serde::Deserialize;
use syntect::parsing::SyntaxSet;
use tower::{util::BoxCloneService, Service};

use super::filters;
@@ -205,6 +206,7 @@
    Extension(repo): Extension<Repository>,
    Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
    Extension(git): Extension<Git>,
    Extension(syntax_set): Extension<Arc<SyntaxSet>>,
    Query(query): Query<CommitQuery>,
) -> Html<String> {
    #[derive(Template)]
@@ -218,9 +220,9 @@
        View {
            repo,
            commit: if let Some(commit) = query.id {
                git.get_commit(repository_path, &commit).await
                git.get_commit(repository_path, &commit, syntax_set).await
            } else {
                Arc::new(git.get_latest_commit(repository_path).await)
                Arc::new(git.get_latest_commit(repository_path, syntax_set).await)
            },
        }
        .render()
diff --git a/templates/repo/commit.html b/templates/repo/commit.html
index 70eeee2..647a32e 100644
--- a/templates/repo/commit.html
+++ a/templates/repo/commit.html
@@ -1,5 +1,9 @@
{% extends "repo/base.html" %}

{% block head %}
<link rel="stylesheet" type="text/css" href="/highlight.css" />
{% endblock %}

{% block commit_nav_class %}active{% endblock %}

{% block content %}
@@ -34,4 +38,8 @@

<h2>{{ commit.summary() }}</h2>
<pre>{{ commit.body() }}</pre>

<h3>Diff</h3>
<pre>{{ commit.diff_stats|safe }}
{{ commit.diff|safe }}</pre>
{% endblock %}