summary history branches tags files
commit:6bdc73f807eb85051b45ca81be4211adb9b54537
author:Trevor Bentley
committer:Trevor Bentley
date:Thu Jan 19 21:03:07 2023 +0100
parents:d83f682aa2bce903d1916222d5077526e2def27f
cargo fmt
diff --git a/src/generate.rs b/src/generate.rs
line changes: +454/-164
index 273c164..28c8664
--- a/src/generate.rs
+++ b/src/generate.rs
@@ -25,12 +25,14 @@ use crate::{
     git::{dir_listing, parse_repo, GitFile, GitRepo, GitsyMetadata},
     loud, louder, loudest, normal, normal_noln,
     settings::{GitsyCli, GitsyRepoDescriptions, GitsySettings, GitsySettingsRepo},
-    template::{DirFilter, FileFilter, HexFilter, MaskFilter, OctFilter, Pagination, TsDateFn, TsTimestampFn, UrlStringFilter},
+    template::{
+        DirFilter, FileFilter, HexFilter, MaskFilter, OctFilter, Pagination, TsDateFn, TsTimestampFn, UrlStringFilter,
+    },
     util::{GitsyError, GitsyErrorKind, VERBOSITY},
 };
+use chrono::{DateTime, Local};
 use git2::{Error, Repository};
 use rayon::prelude::*;
-use chrono::{DateTime, Local};
 use std::cmp;
 use std::fs::File;
 use std::io::Write;
@@ -102,8 +104,7 @@ impl GitsyGenerator {
             _ => Context::new(),
         };
         if let Some(extra) = &self.settings.extra {
-            ctx
-                .try_insert("extra", extra)
+            ctx.try_insert("extra", extra)
                 .expect("Failed to add extra settings to template engine.");
         }
         if let Some(site_name) = &self.settings.site_name {
@@ -117,7 +118,13 @@ impl GitsyGenerator {
         }
         ctx.insert("site_dir", &self.settings.outputs.output_dir());
         if self.settings.outputs.global_assets.is_some() {
-            ctx.insert("site_assets", &self.settings.outputs.to_relative(&self.settings.outputs.global_assets::<GitFile>(None, None)));
+            ctx.insert(
+                "site_assets",
+                &self
+                    .settings
+                    .outputs
+                    .to_relative(&self.settings.outputs.global_assets::<GitFile>(None, None)),
+            );
         }
         ctx.insert("site_generated_ts", &self.generated_dt.timestamp());
         ctx.insert("site_generated_offset", &self.generated_dt.offset().local_minus_utc());
@@ -128,8 +135,13 @@ impl GitsyGenerator {
         let repo_path = match &repo_desc.path {
             url if url.starts_with("https://") || url.to_str().unwrap_or_default().contains("@") => {
                 if self.settings.outputs.cloned_repos.is_none() {
-                    return Err(GitsyError::kind(GitsyErrorKind::Settings,
-                                                Some(&format!("ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.", name))));
+                    return Err(GitsyError::kind(
+                        GitsyErrorKind::Settings,
+                        Some(&format!(
+                            "ERROR: Found remote repo [{}], but `cloned_repos` directory not configured.",
+                            name
+                        )),
+                    ));
                 };
                 let clone_path: PathBuf = [self.settings.outputs.cloned_repos.as_deref().unwrap(), name]
                     .iter()
@@ -201,8 +213,10 @@ impl GitsyGenerator {
                             name,
                             dir.display()
                         );
-                        return Err(GitsyError::kind(GitsyErrorKind::Settings,
-                                                    Some(&format!("ERROR: Local repository not found: {}", name))));
+                        return Err(GitsyError::kind(
+                            GitsyErrorKind::Settings,
+                            Some(&format!("ERROR: Local repository not found: {}", name)),
+                        ));
                     }
                 }
                 dir.to_string_lossy().to_string()
@@ -282,8 +296,11 @@ impl GitsyGenerator {
 
     fn write_rendered<P: AsRef<Path>>(&self, path: &P, rendered: &str) -> usize {
         let path: &Path = path.as_ref();
-        assert!(self.settings.outputs.assert_valid(&path),
-                "ERROR: attempted to write invalid path: {}", path.display());
+        assert!(
+            self.settings.outputs.assert_valid(&path),
+            "ERROR: attempted to write invalid path: {}",
+            path.display()
+        );
         // Write the file to disk
         let mut file = File::create(path).expect(&format!("Unable to write to output path: {}", path.display()));
         file.write(rendered.as_bytes())
@@ -312,8 +329,14 @@ impl GitsyGenerator {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut global_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.repo_list::<GitRepo>(None, None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             match tera.render(templ_path, &ctx) {
                 Ok(rendered) => {
                     global_bytes += self.write_rendered(&out_path, &rendered);
@@ -330,8 +353,14 @@ impl GitsyGenerator {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut global_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.error::<GitRepo>(None, None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             match tera.render(templ_path, &ctx) {
                 Ok(rendered) => {
                     global_bytes += self.write_rendered(&out_path, &rendered);
@@ -344,13 +373,25 @@ impl GitsyGenerator {
         Ok(global_bytes)
     }
 
-    pub fn gen_summary(&self, ctx: &Context, atomic_bytes: &AtomicUsize,
-                       parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_summary(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.summary::<GitRepo>(Some(parsed_repo), None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             match tera.render(templ_path, &ctx) {
                 Ok(rendered) => {
                     let bytes = self.write_rendered(&out_path, &rendered);
@@ -361,54 +402,93 @@ impl GitsyGenerator {
                     _ => error!("ERROR: {:?}", x),
                 },
             }
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                        return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_history(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_history(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let repo_bytes = AtomicUsize::new(0);
         for (templ_path, out_path) in self.settings.outputs.history::<GitRepo>(Some(parsed_repo), None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             let pages = parsed_repo.history.chunks(self.settings.paginate_history());
             let page_count = pages.len();
-            parsed_repo.history.par_chunks(self.settings.paginate_history()).enumerate().try_for_each(|(idx, page)| {
-                let mut paged_ctx = ctx.clone();
-                let pagination = Pagination::new(
-                    idx + 1,
-                    page_count,
-                    &out_path,
-                );
-                paged_ctx.insert("page", &pagination.with_relative_paths());
-                paged_ctx.insert("history", &page);
-                let rendered = tera.render(templ_path, &paged_ctx)?;
-                let bytes = self.write_rendered(&pagination.cur_page, &rendered);
-                repo_bytes.fetch_add(bytes, Ordering::SeqCst);
-                atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
-                paged_ctx.remove("page");
-                paged_ctx.remove("history");
-                size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                                   return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
-                Ok::<(), GitsyError>(())
-            })?;
+            parsed_repo
+                .history
+                .par_chunks(self.settings.paginate_history())
+                .enumerate()
+                .try_for_each(|(idx, page)| {
+                    let mut paged_ctx = ctx.clone();
+                    let pagination = Pagination::new(idx + 1, page_count, &out_path);
+                    paged_ctx.insert("page", &pagination.with_relative_paths());
+                    paged_ctx.insert("history", &page);
+                    let rendered = tera.render(templ_path, &paged_ctx)?;
+                    let bytes = self.write_rendered(&pagination.cur_page, &rendered);
+                    repo_bytes.fetch_add(bytes, Ordering::SeqCst);
+                    atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
+                    paged_ctx.remove("page");
+                    paged_ctx.remove("history");
+                    size_check_atomic!(
+                        repo_desc,
+                        atomic_bytes,
+                        self.total_bytes,
+                        return Err(GitsyError::kind(
+                            GitsyErrorKind::Settings,
+                            Some("ERROR: size limit exceeded")
+                        ))
+                    );
+                    Ok::<(), GitsyError>(())
+                })?;
         }
         Ok(repo_bytes.load(Ordering::SeqCst))
     }
 
-    pub fn gen_commit(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_commit(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let mut ctx = ctx.clone();
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for (_id, commit) in &parsed_repo.commits {
-            ctx
-                .try_insert("commit", &commit)
+            ctx.try_insert("commit", &commit)
                 .expect("Failed to add commit to template engine.");
             for (templ_path, out_path) in self.settings.outputs.commit(Some(parsed_repo), Some(commit)) {
-                let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-                let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+                let templ_path = templ_path.to_str().expect(&format!(
+                    "ERROR: a summary template path is invalid: {}",
+                    templ_path.display()
+                ));
+                let out_path = out_path.to_str().expect(&format!(
+                    "ERROR: a summary output path is invalid: {}",
+                    out_path.display()
+                ));
                 match tera.render(templ_path, &ctx) {
                     Ok(rendered) => {
                         let bytes = self.write_rendered(&out_path, &rendered);
@@ -421,28 +501,44 @@ impl GitsyGenerator {
                 }
             }
             ctx.remove("commit");
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_branches(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_branches(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.branches::<GitRepo>(Some(parsed_repo), None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             let mut paged_ctx = ctx.clone();
             paged_ctx.remove("branches");
             let pages = parsed_repo.branches.chunks(self.settings.paginate_branches());
             let page_count = pages.len();
             for (idx, page) in pages.enumerate() {
-                let pagination = Pagination::new(
-                    idx + 1,
-                    page_count,
-                    &out_path,
-                );
+                let pagination = Pagination::new(idx + 1, page_count, &out_path);
                 paged_ctx.insert("page", &pagination.with_relative_paths());
                 paged_ctx.insert("branches", &page);
                 match tera.render(templ_path, &paged_ctx) {
@@ -458,25 +554,44 @@ impl GitsyGenerator {
                 paged_ctx.remove("page");
                 paged_ctx.remove("branches");
             }
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_branch(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_branch(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let mut ctx = ctx.clone();
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for branch in &parsed_repo.branches {
             ctx.insert("branch", branch);
             for (templ_path, out_path) in self.settings.outputs.branch(Some(parsed_repo), Some(branch)) {
-                let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-                let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+                let templ_path = templ_path.to_str().expect(&format!(
+                    "ERROR: a summary template path is invalid: {}",
+                    templ_path.display()
+                ));
+                let out_path = out_path.to_str().expect(&format!(
+                    "ERROR: a summary output path is invalid: {}",
+                    out_path.display()
+                ));
                 match tera.render(templ_path, &ctx) {
                     Ok(rendered) => {
-                        let bytes = self
-                            .write_rendered(&out_path, &rendered);
+                        let bytes = self.write_rendered(&out_path, &rendered);
                         repo_bytes += bytes;
                         atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
                     }
@@ -486,25 +601,44 @@ impl GitsyGenerator {
                 }
             }
             ctx.remove("branch");
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_tags(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_tags(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.tags::<GitRepo>(Some(parsed_repo), None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             let mut paged_ctx = ctx.clone();
             paged_ctx.remove("tags");
             let pages = parsed_repo.tags.chunks(self.settings.paginate_tags());
             let page_count = pages.len();
             for (idx, page) in pages.enumerate() {
-                let pagination =
-                    Pagination::new(idx + 1, page_count, &out_path);
+                let pagination = Pagination::new(idx + 1, page_count, &out_path);
                 paged_ctx.insert("page", &pagination.with_relative_paths());
                 paged_ctx.insert("tags", &page);
                 match tera.render(templ_path, &paged_ctx) {
@@ -519,14 +653,28 @@ impl GitsyGenerator {
                 }
                 paged_ctx.remove("page");
                 paged_ctx.remove("tags");
-                size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                                   return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+                size_check_atomic!(
+                    repo_desc,
+                    atomic_bytes,
+                    self.total_bytes,
+                    return Err(GitsyError::kind(
+                        GitsyErrorKind::Settings,
+                        Some("ERROR: size limit exceeded")
+                    ))
+                );
             }
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_tag(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_tag(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let mut ctx = ctx.clone();
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
@@ -538,12 +686,17 @@ impl GitsyGenerator {
                 }
             }
             for (templ_path, out_path) in self.settings.outputs.tag(Some(parsed_repo), Some(tag)) {
-                let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-                let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+                let templ_path = templ_path.to_str().expect(&format!(
+                    "ERROR: a summary template path is invalid: {}",
+                    templ_path.display()
+                ));
+                let out_path = out_path.to_str().expect(&format!(
+                    "ERROR: a summary output path is invalid: {}",
+                    out_path.display()
+                ));
                 match tera.render(templ_path, &ctx) {
                     Ok(rendered) => {
-                        let bytes =
-                            self.write_rendered(&out_path, &rendered);
+                        let bytes = self.write_rendered(&out_path, &rendered);
                         repo_bytes += bytes;
                         atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
                     }
@@ -554,25 +707,44 @@ impl GitsyGenerator {
             }
             ctx.remove("tag");
             ctx.remove("commit");
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_files(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, _repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_files(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        _repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let mut ctx = ctx.clone();
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for (templ_path, out_path) in self.settings.outputs.files::<GitRepo>(Some(parsed_repo), None) {
-            let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-            let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+            let templ_path = templ_path.to_str().expect(&format!(
+                "ERROR: a summary template path is invalid: {}",
+                templ_path.display()
+            ));
+            let out_path = out_path.to_str().expect(&format!(
+                "ERROR: a summary output path is invalid: {}",
+                out_path.display()
+            ));
             ctx.insert("root_files", &parsed_repo.root_files);
             ctx.insert("all_files", &parsed_repo.all_files);
             match tera.render(templ_path, &ctx) {
                 Ok(rendered) => {
-                    let bytes =
-                        self.write_rendered(&out_path, &rendered);
+                    let bytes = self.write_rendered(&out_path, &rendered);
                     repo_bytes += bytes;
                     atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
                 }
@@ -580,13 +752,27 @@ impl GitsyGenerator {
                     _ => error!("ERROR: {:?}", x),
                 },
             }
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    pub fn gen_file(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_file(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
 
@@ -604,8 +790,10 @@ impl GitsyGenerator {
                 .expect("Invalid syntax highlighting theme specified.");
             let css: String = css_for_theme_with_class_style(theme, syntect::html::ClassStyle::Spaced)
                 .expect("Invalid syntax highlighting theme specified.");
-            let bytes =
-                self.write_rendered(&self.settings.outputs.syntax_css::<GitFile>(Some(&parsed_repo), None), css.as_str());
+            let bytes = self.write_rendered(
+                &self.settings.outputs.syntax_css::<GitFile>(Some(&parsed_repo), None),
+                css.as_str(),
+            );
             repo_bytes += bytes;
             atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
         }
@@ -614,7 +802,10 @@ impl GitsyGenerator {
         // done first because syntax highlighting is very slow.
         let files: Vec<&GitFile> = parsed_repo.all_files.iter().filter(|x| x.kind == "file").collect();
         let atomic_repo_bytes: AtomicUsize = AtomicUsize::new(repo_bytes);
-        let repo_path = repo.path().to_str().expect("ERROR: unable to determine path to local repository");
+        let repo_path = repo
+            .path()
+            .to_str()
+            .expect("ERROR: unable to determine path to local repository");
         let _ = files
             .par_iter()
             .fold(
@@ -626,21 +817,32 @@ impl GitsyGenerator {
 
                     let mut local_bytes = 0;
                     let cur_repo_bytes = atomic_repo_bytes.load(Ordering::SeqCst);
-                    size_check!(repo_desc, cur_repo_bytes, self.total_bytes.load(Ordering::SeqCst), return None);
+                    size_check!(
+                        repo_desc,
+                        cur_repo_bytes,
+                        self.total_bytes.load(Ordering::SeqCst),
+                        return None
+                    );
                     let file = match file.size < repo_desc.limit_file_size.unwrap_or(usize::MAX) {
-                        true => GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc)
-                            .expect("Failed to parse file."),
+                        true => {
+                            GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc).expect("Failed to parse file.")
+                        }
                         false => (*file).clone(),
                     };
-                    ctx
-                        .try_insert("file", &file)
+                    ctx.try_insert("file", &file)
                         .expect("Failed to add file to template engine.");
                     for (templ_path, out_path) in self.settings.outputs.file(Some(parsed_repo), Some(&file)) {
-                        let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-                        let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+                        let templ_path = templ_path.to_str().expect(&format!(
+                            "ERROR: a summary template path is invalid: {}",
+                            templ_path.display()
+                        ));
+                        let out_path = out_path.to_str().expect(&format!(
+                            "ERROR: a summary output path is invalid: {}",
+                            out_path.display()
+                        ));
                         match tera.render(templ_path, &ctx) {
                             Ok(rendered) => {
-                                local_bytes = self.write_rendered(&out_path, &rendered,);
+                                local_bytes = self.write_rendered(&out_path, &rendered);
                                 atomic_repo_bytes.fetch_add(local_bytes, Ordering::SeqCst);
                                 atomic_bytes.fetch_add(local_bytes, Ordering::SeqCst);
                             }
@@ -659,28 +861,46 @@ impl GitsyGenerator {
             .while_some() // allow short-circuiting if size limit is reached
             .sum::<usize>();
         repo_bytes = atomic_repo_bytes.load(Ordering::SeqCst);
-        size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                           return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+        size_check_atomic!(
+            repo_desc,
+            atomic_bytes,
+            self.total_bytes,
+            return Err(GitsyError::kind(
+                GitsyErrorKind::Settings,
+                Some("ERROR: size limit exceeded")
+            ))
+        );
         Ok(repo_bytes)
     }
 
-    pub fn gen_dir(&self, ctx: &Context, atomic_bytes: &AtomicUsize, parsed_repo: &GitRepo, repo_desc: &GitsySettingsRepo, repo: &Repository) -> Result<usize, GitsyError> {
+    pub fn gen_dir(
+        &self,
+        ctx: &Context,
+        atomic_bytes: &AtomicUsize,
+        parsed_repo: &GitRepo,
+        repo_desc: &GitsySettingsRepo,
+        repo: &Repository,
+    ) -> Result<usize, GitsyError> {
         let mut ctx = ctx.clone();
         let tera = self.tera.as_ref().expect("ERROR: generate called without a context!?");
         let mut repo_bytes = 0;
         for dir in parsed_repo.all_files.iter().filter(|x| x.kind == "dir") {
             let listing = dir_listing(&repo, &dir).expect("Failed to parse file.");
             ctx.insert("dir", dir);
-            ctx
-                .try_insert("files", &listing)
+            ctx.try_insert("files", &listing)
                 .expect("Failed to add dir to template engine.");
             for (templ_path, out_path) in self.settings.outputs.dir(Some(parsed_repo), Some(dir)) {
-                let templ_path = templ_path.to_str().expect(&format!("ERROR: a summary template path is invalid: {}", templ_path.display()));
-                let out_path = out_path.to_str().expect(&format!("ERROR: a summary output path is invalid: {}", out_path.display()));
+                let templ_path = templ_path.to_str().expect(&format!(
+                    "ERROR: a summary template path is invalid: {}",
+                    templ_path.display()
+                ));
+                let out_path = out_path.to_str().expect(&format!(
+                    "ERROR: a summary output path is invalid: {}",
+                    out_path.display()
+                ));
                 match tera.render(templ_path, &ctx) {
                     Ok(rendered) => {
-                        let bytes =
-                            self.write_rendered(&out_path, &rendered);
+                        let bytes = self.write_rendered(&out_path, &rendered);
                         repo_bytes += bytes;
                         atomic_bytes.fetch_add(bytes, Ordering::SeqCst);
                     }
@@ -691,13 +911,25 @@ impl GitsyGenerator {
             }
             ctx.remove("files");
             ctx.remove("dir");
-            size_check_atomic!(repo_desc, atomic_bytes, self.total_bytes,
-                               return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+            size_check_atomic!(
+                repo_desc,
+                atomic_bytes,
+                self.total_bytes,
+                return Err(GitsyError::kind(
+                    GitsyErrorKind::Settings,
+                    Some("ERROR: size limit exceeded")
+                ))
+            );
         }
         Ok(repo_bytes)
     }
 
-    fn copy_assets(&self, repo_desc: Option<&GitsySettingsRepo>, parsed_repo: Option<&GitRepo>, repo: Option<&Repository>) -> Result<usize, GitsyError> {
+    fn copy_assets(
+        &self,
+        repo_desc: Option<&GitsySettingsRepo>,
+        parsed_repo: Option<&GitRepo>,
+        repo: Option<&Repository>,
+    ) -> Result<usize, GitsyError> {
         let mut bytes = 0;
         match repo_desc {
             Some(repo_desc) => {
@@ -725,7 +957,7 @@ impl GitsyGenerator {
                         loud!(" - copied asset: {}", src_file.display());
                     }
                 }
-            },
+            }
             _ => {
                 if self.settings.asset_files.is_some() {
                     let target_dir = self.settings.outputs.global_assets::<GitFile>(None, None);
@@ -745,12 +977,16 @@ impl GitsyGenerator {
                         loud!(" - copied asset: {}", src_file.display());
                     }
                 }
-            },
+            }
         }
         Ok(bytes)
     }
 
-    pub fn generate_repo(&self, repo_desc: &GitsySettingsRepo, pad_name_len: usize) -> Result<(GitRepo, usize), GitsyError> {
+    pub fn generate_repo(
+        &self,
+        repo_desc: &GitsySettingsRepo,
+        pad_name_len: usize,
+    ) -> Result<(GitRepo, usize), GitsyError> {
         loudest!("Repo settings:\n{:#?}", &repo_desc);
         let start_repo = Instant::now();
 
@@ -779,40 +1015,51 @@ impl GitsyGenerator {
             for readme in readmes {
                 if let Some(file) = parsed_repo.root_files.iter().filter(|x| &x.name == readme).next() {
                     louder!(" - found readme file: {}", file.name);
-                    let _ = GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc)
-                        .expect("Failed to parse file.");
+                    let _ =
+                        GitsyGenerator::fill_file_contents(&repo, &file, &repo_desc).expect("Failed to parse file.");
                     local_ctx.insert("readme", &file);
                     break;
                 }
             }
         };
 
-        let fns = &[GitsyGenerator::gen_summary,
-                    GitsyGenerator::gen_branches,
-                    GitsyGenerator::gen_branch,
-                    GitsyGenerator::gen_tags,
-                    GitsyGenerator::gen_tag,
-                    GitsyGenerator::gen_history,
-                    GitsyGenerator::gen_commit,
-                    GitsyGenerator::gen_file,
-                    GitsyGenerator::gen_dir,
-                    GitsyGenerator::gen_files,
+        let fns = &[
+            GitsyGenerator::gen_summary,
+            GitsyGenerator::gen_branches,
+            GitsyGenerator::gen_branch,
+            GitsyGenerator::gen_tags,
+            GitsyGenerator::gen_tag,
+            GitsyGenerator::gen_history,
+            GitsyGenerator::gen_commit,
+            GitsyGenerator::gen_file,
+            GitsyGenerator::gen_dir,
+            GitsyGenerator::gen_files,
         ];
 
-        let repo_bytes: usize = fns.par_iter().try_fold(
-            || 0,
-            |acc, x| {
-                let repo = Repository::open(&repo_path).expect("Unable to find git repository.");
-                let bytes = x(&self, &local_ctx, &atomic_bytes, &parsed_repo, repo_desc, &repo)?;
-                // remove these bytes from the current repo bytes and move them to the total bytes.
-                atomic_bytes.fetch_sub(bytes, Ordering::SeqCst);
-                self.total_bytes.fetch_add(bytes, Ordering::SeqCst);
-                Ok::<usize, GitsyError>(acc + bytes)
-            })
+        let repo_bytes: usize = fns
+            .par_iter()
+            .try_fold(
+                || 0,
+                |acc, x| {
+                    let repo = Repository::open(&repo_path).expect("Unable to find git repository.");
+                    let bytes = x(&self, &local_ctx, &atomic_bytes, &parsed_repo, repo_desc, &repo)?;
+                    // remove these bytes from the current repo bytes and move them to the total bytes.
+                    atomic_bytes.fetch_sub(bytes, Ordering::SeqCst);
+                    self.total_bytes.fetch_add(bytes, Ordering::SeqCst);
+                    Ok::<usize, GitsyError>(acc + bytes)
+                },
+            )
             .try_reduce(|| 0, |acc, x| Ok(acc + x))?;
 
-        size_check!(repo_desc, 0, self.total_bytes.load(Ordering::SeqCst),
-                    return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: size limit exceeded"))));
+        size_check!(
+            repo_desc,
+            0,
+            self.total_bytes.load(Ordering::SeqCst),
+            return Err(GitsyError::kind(
+                GitsyErrorKind::Settings,
+                Some("ERROR: size limit exceeded")
+            ))
+        );
 
         self.copy_assets(Some(&repo_desc), Some(&parsed_repo), Some(&repo))?;
 
@@ -878,45 +1125,81 @@ impl GitsyGenerator {
                 let mut tb = 0;
                 for repo_desc in &repo_vec {
                     let (minimized_repo, repo_bytes) = self.generate_repo(repo_desc, longest_repo_name)?;
-                    size_check!(repo_desc, 0, tb,
-                                return Err(GitsyError::kind(GitsyErrorKind::Settings, Some("ERROR: site size limit exceeded"))));
+                    size_check!(
+                        repo_desc,
+                        0,
+                        tb,
+                        return Err(GitsyError::kind(
+                            GitsyErrorKind::Settings,
+                            Some("ERROR: site size limit exceeded")
+                        ))
+                    );
                     shared_repos.lock().unwrap().push(minimized_repo);
                     tb += repo_bytes;
                 }
                 tb
-            },
+            }
             n if n == 0 => {
-                let total_bytes: usize = repo_vec.par_iter().try_fold(|| 0, |acc, repo_desc| {
-                    let (minimized_repo, repo_bytes) = self.generate_repo(repo_desc, longest_repo_name)?;
-                    size_check!(repo_desc, 0, acc + repo_bytes, return Err(GitsyError::kind(GitsyErrorKind::Unknown,
-                                                                                            Some("ERROR: site size limit exceeded"))));
-                    shared_repos.lock().unwrap().push(minimized_repo);
-                    Ok::<usize, GitsyError>(repo_bytes)
-                })
+                let total_bytes: usize = repo_vec
+                    .par_iter()
+                    .try_fold(
+                        || 0,
+                        |acc, repo_desc| {
+                            let (minimized_repo, repo_bytes) = self.generate_repo(repo_desc, longest_repo_name)?;
+                            size_check!(
+                                repo_desc,
+                                0,
+                                acc + repo_bytes,
+                                return Err(GitsyError::kind(
+                                    GitsyErrorKind::Unknown,
+                                    Some("ERROR: site size limit exceeded")
+                                ))
+                            );
+                            shared_repos.lock().unwrap().push(minimized_repo);
+                            Ok::<usize, GitsyError>(repo_bytes)
+                        },
+                    )
                     .try_reduce(|| 0, |acc, x| Ok(acc + x))?;
                 total_bytes
-            },
+            }
             n => {
-                let pool = rayon::ThreadPoolBuilder::new()
-                    .num_threads(n)
-                    .build().unwrap();
+                let pool = rayon::ThreadPoolBuilder::new().num_threads(n).build().unwrap();
 
                 let total_bytes = pool.install(|| {
-                        let total_bytes: usize = repo_vec.par_iter().try_fold(|| 0, |acc, repo_desc| {
-                            let (minimized_repo, repo_bytes) = self.generate_repo(repo_desc, longest_repo_name)?;
-                            size_check!(repo_desc, 0, acc + repo_bytes, return Err(GitsyError::kind(GitsyErrorKind::Unknown,
-                                                                                                    Some("ERROR: site size limit exceeded"))));
-                            shared_repos.lock().unwrap().push(minimized_repo);
-                            Ok::<usize, GitsyError>(repo_bytes)
-                        })
+                    let total_bytes: usize = repo_vec
+                        .par_iter()
+                        .try_fold(
+                            || 0,
+                            |acc, repo_desc| {
+                                let (minimized_repo, repo_bytes) = self.generate_repo(repo_desc, longest_repo_name)?;
+                                size_check!(
+                                    repo_desc,
+                                    0,
+                                    acc + repo_bytes,
+                                    return Err(GitsyError::kind(
+                                        GitsyErrorKind::Unknown,
+                                        Some("ERROR: site size limit exceeded")
+                                    ))
+                                );
+                                shared_repos.lock().unwrap().push(minimized_repo);
+                                Ok::<usize, GitsyError>(repo_bytes)
+                            },
+                        )
                         .try_reduce(|| 0, |acc, x| Ok(acc + x))?;
                     Ok::<usize, GitsyError>(total_bytes)
                 })?;
                 total_bytes
             }
         };
-        size_check!(self.settings, 0, total_bytes, return Err(GitsyError::kind(GitsyErrorKind::Unknown,
-                                                                               Some("ERROR: site size limit exceeded"))));
+        size_check!(
+            self.settings,
+            0,
+            total_bytes,
+            return Err(GitsyError::kind(
+                GitsyErrorKind::Unknown,
+                Some("ERROR: site size limit exceeded")
+            ))
+        );
 
         let repos = shared_repos;
 
@@ -936,8 +1219,15 @@ impl GitsyGenerator {
         self.copy_assets(None, None, None)?;
 
         total_bytes += global_bytes;
-        size_check!(self.settings, 0, total_bytes, return Err(GitsyError::kind(GitsyErrorKind::Unknown,
-                                                                               Some("ERROR: site size limit exceeded"))));
+        size_check!(
+            self.settings,
+            0,
+            total_bytes,
+            return Err(GitsyError::kind(
+                GitsyErrorKind::Unknown,
+                Some("ERROR: site size limit exceeded")
+            ))
+        );
         normal!(
             "done in {:.2}s ({} bytes)",
             start_global.elapsed().as_secs_f32(),

diff --git a/src/git.rs b/src/git.rs
line changes: +126/-85
index 3c84bcb..c470abe
--- a/src/git.rs
+++ b/src/git.rs
@@ -21,16 +21,16 @@
  * along with Itsy-Gitsy.  If not, see <http://www.gnu.org/licenses/>.
  */
 use crate::settings::GitsySettingsRepo;
-use crate::util::{sanitize_path_component, SafePathVar, urlify_path};
+use crate::util::{sanitize_path_component, urlify_path, SafePathVar};
 use crate::{error, loud, louder, loudest};
 use git2::{DiffOptions, Error, Repository};
 use rayon::prelude::*;
 use serde::{Deserialize, Serialize};
-use std::sync::atomic::AtomicUsize;
 use std::cell::RefCell;
 use std::collections::BTreeMap;
 use std::path::{Path, PathBuf};
 use std::rc::Rc;
+use std::sync::atomic::AtomicUsize;
 use std::sync::atomic::Ordering;
 
 fn first_line(msg: &[u8]) -> String {
@@ -63,8 +63,10 @@ impl GitRepo {
         let new_history: Vec<GitObject> = self.history.iter().cloned().take(max_entries).collect();
         for entry in &new_history {
             if self.commits.contains_key(&entry.full_hash) {
-                new_commits.insert(entry.full_hash.clone(),
-                                   self.commits.get(&entry.full_hash).unwrap().clone());
+                new_commits.insert(
+                    entry.full_hash.clone(),
+                    self.commits.get(&entry.full_hash).unwrap().clone(),
+                );
             }
         }
         let all_files: Vec<GitFile> = self.all_files.iter().cloned().take(max_entries).collect();
@@ -99,8 +101,11 @@ impl SafePathVar for GitRepo {
             let cmp = cmp.as_os_str().to_string_lossy().replace("%REPO%", &safe_name);
             dst.push(cmp);
         }
-        assert!(src.components().count() == dst.components().count(),
-                "ERROR: path substitution accidentally created a new folder in: {}", src.display());
+        assert!(
+            src.components().count() == dst.components().count(),
+            "ERROR: path substitution accidentally created a new folder in: {}",
+            src.display()
+        );
         dst
     }
 }
@@ -144,17 +149,24 @@ impl SafePathVar for GitObject {
         let src: &Path = path.as_ref();
         let mut dst = PathBuf::new();
         let safe_full_hash = sanitize_path_component(&self.full_hash);
-        let safe_ref = self.ref_name.as_deref()
+        let safe_ref = self
+            .ref_name
+            .as_deref()
             .map(|v| sanitize_path_component(&urlify_path(v)))
             .unwrap_or("%REF%".to_string());
         for cmp in src.components() {
-            let cmp = cmp.as_os_str().to_string_lossy()
+            let cmp = cmp
+                .as_os_str()
+                .to_string_lossy()
                 .replace("%ID%", &safe_full_hash)
                 .replace("%REF%", &safe_ref);
             dst.push(cmp);
         }
-        assert!(src.components().count() == dst.components().count(),
-                "ERROR: path substitution accidentally created a new folder in: {}", src.display());
+        assert!(
+            src.components().count() == dst.components().count(),
+            "ERROR: path substitution accidentally created a new folder in: {}",
+            src.display()
+        );
         dst
     }
 }
@@ -189,14 +201,19 @@ impl SafePathVar for GitFile {
         let safe_name = sanitize_path_component(&self.name);
         let safe_path = sanitize_path_component(&urlify_path(&self.path));
         for cmp in src.components() {
-            let cmp = cmp.as_os_str().to_string_lossy()
+            let cmp = cmp
+                .as_os_str()
+                .to_string_lossy()
                 .replace("%ID%", &safe_id)
                 .replace("%NAME%", &safe_name)
                 .replace("%PATH%", &safe_path);
             dst.push(cmp);
         }
-        assert!(src.components().count() == dst.components().count(),
-                "ERROR: path substitution accidentally created a new folder in: {}", src.display());
+        assert!(
+            src.components().count() == dst.components().count(),
+            "ERROR: path substitution accidentally created a new folder in: {}",
+            src.display()
+        );
         dst
     }
 }
@@ -295,21 +312,38 @@ fn walk_file_tree(
 
 pub fn dir_listing(repo: &Repository, file: &GitFile) -> Result<Vec<GitFile>, Error> {
     let mut files: Vec<GitFile> = vec![];
-    walk_file_tree(&repo, &file.id, &mut files, 0, usize::MAX, false, &(file.path.clone() + "/"))?;
+    walk_file_tree(
+        &repo,
+        &file.id,
+        &mut files,
+        0,
+        usize::MAX,
+        false,
+        &(file.path.clone() + "/"),
+    )?;
     Ok(files)
 }
 
-pub fn parse_revwalk(repo: &Repository, mut revwalk: git2::Revwalk, references: &BTreeMap<String, Vec<String>>, settings: &GitsySettingsRepo) -> Result<Vec<GitObject>, Error> {
+pub fn parse_revwalk(
+    repo: &Repository,
+    mut revwalk: git2::Revwalk,
+    references: &BTreeMap<String, Vec<String>>,
+    settings: &GitsySettingsRepo,
+) -> Result<Vec<GitObject>, Error> {
     let mut history: Vec<GitObject> = vec![];
 
     for (idx, oid) in revwalk.by_ref().enumerate() {
         let oid = oid?;
-        if  idx >= settings.limit_history.unwrap_or(usize::MAX) {
+        if idx >= settings.limit_history.unwrap_or(usize::MAX) {
             break;
         }
         let parsed = parse_commit(idx, settings, repo, &oid.to_string(), &references)?;
-        loudest!("   + [{}] {} {}", idx, parsed.full_hash,
-                 parsed.summary.as_deref().unwrap_or_default());
+        loudest!(
+            "   + [{}] {} {}",
+            idx,
+            parsed.full_hash,
+            parsed.summary.as_deref().unwrap_or_default()
+        );
         history.push(parsed);
     }
     Ok(history)
@@ -372,8 +406,7 @@ pub fn parse_repo(
     // Let's arbitrarily say it's not worth parallelizing unless we
     // can give all cores at least 1k commits to parse.  This could
     // certainly use some configurability...
-    let thread_jobs = match rayon::current_num_threads() > 1 &&
-        commit_count > 1000 * rayon::current_num_threads() {
+    let thread_jobs = match rayon::current_num_threads() > 1 && commit_count > 1000 * rayon::current_num_threads() {
         // Divide a chunk up into even smaller units, so each core
         // runs about 10.  This makes it more efficient to detect when
         // the commit limit is reached and short-circuit.
@@ -388,58 +421,66 @@ pub fn parse_repo(
     // might not be evenly distributed.
     let chunk_size = ((commit_count as f64) / (thread_jobs as f64)).ceil() as usize;
     if thread_jobs > 1 {
-        loud!(" - splitting {} commits across {} threads of approximate size {}", commit_count, thread_jobs, chunk_size);
+        loud!(
+            " - splitting {} commits across {} threads of approximate size {}",
+            commit_count,
+            thread_jobs,
+            chunk_size
+        );
     }
 
     let repo_path = repo.path();
 
     let thread_jobs: Vec<usize> = (0..thread_jobs).rev().collect(); // note the subtle rev() to do this in the right order
     let atomic_commits = AtomicUsize::new(0);
-    let mut history: Vec<_> = thread_jobs.par_iter().try_fold(|| Vec::<_>::new(), |mut acc, thread| {
-        if atomic_commits.load(Ordering::SeqCst) > settings.limit_history.unwrap_or(usize::MAX) {
-            // TODO: should convert all error paths in this function
-            // to GitsyErrors, and differentiate between real failures
-            // and soft limits.  For now, they're all stop processing,
-            // but don't raise any errors.  Here, we take advantage of
-            // that.
-            return Err(git2::Error::from_str("history limit reached"));
-        }
-        let repo = Repository::open(repo_path)?;
-        let mut revwalk = repo.revwalk()?;
-        // TODO: TOPOLOGICAL might be better, but it's also ungodly slow
-        // on large repos.  Maybe this should be configurable.
-        //
-        //revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?;
-        revwalk.set_sorting(git2::Sort::NONE)?;
-        let start_commit = match (chunk_size * thread) + 1 > commit_count {
-            true => 1,
-            false => commit_count - 1 - (chunk_size * thread),
-        };
-        let end_commit = match chunk_size > start_commit {
-            true => "".into(),
-            false => format!("~{}", start_commit - chunk_size),
-        };
-        let range = format!("{}~{}..{}{}",
-                            branch_name, start_commit,
-                            branch_name, end_commit);
-        loud!(" - Parse range: {} on thread {}", range, thread);
-        match *thread == 0 {
-            true => {
-                // The last chunk gets a single ref instead of a
-                // range, because ranges can't seem to represent the
-                // very first commit in a repository...
-                let end_commit = format!("{}{}", branch_name, end_commit);
-                let branch_obj = repo.revparse_single(&end_commit).unwrap();
-                revwalk.push(branch_obj.id())?
+    let mut history: Vec<_> = thread_jobs
+        .par_iter()
+        .try_fold(
+            || Vec::<_>::new(),
+            |mut acc, thread| {
+                if atomic_commits.load(Ordering::SeqCst) > settings.limit_history.unwrap_or(usize::MAX) {
+                    // TODO: should convert all error paths in this function
+                    // to GitsyErrors, and differentiate between real failures
+                    // and soft limits.  For now, they're all stop processing,
+                    // but don't raise any errors.  Here, we take advantage of
+                    // that.
+                    return Err(git2::Error::from_str("history limit reached"));
+                }
+                let repo = Repository::open(repo_path)?;
+                let mut revwalk = repo.revwalk()?;
+                // TODO: TOPOLOGICAL might be better, but it's also ungodly slow
+                // on large repos.  Maybe this should be configurable.
+                //
+                //revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?;
+                revwalk.set_sorting(git2::Sort::NONE)?;
+                let start_commit = match (chunk_size * thread) + 1 > commit_count {
+                    true => 1,
+                    false => commit_count - 1 - (chunk_size * thread),
+                };
+                let end_commit = match chunk_size > start_commit {
+                    true => "".into(),
+                    false => format!("~{}", start_commit - chunk_size),
+                };
+                let range = format!("{}~{}..{}{}", branch_name, start_commit, branch_name, end_commit);
+                loud!(" - Parse range: {} on thread {}", range, thread);
+                match *thread == 0 {
+                    true => {
+                        // The last chunk gets a single ref instead of a
+                        // range, because ranges can't seem to represent the
+                        // very first commit in a repository...
+                        let end_commit = format!("{}{}", branch_name, end_commit);
+                        let branch_obj = repo.revparse_single(&end_commit).unwrap();
+                        revwalk.push(branch_obj.id())?
+                    }
+                    false => revwalk.push_range(&range)?,
+                }
+                let res = parse_revwalk(&repo, revwalk, &references, &settings)?;
+                louder!(" - Parsed {} on thread {}", res.len(), thread);
+                atomic_commits.fetch_add(res.len(), Ordering::SeqCst);
+                acc.extend(res);
+                Ok(acc)
             },
-            false => revwalk.push_range(&range)?,
-        }
-        let res = parse_revwalk(&repo, revwalk, &references, &settings)?;
-        louder!(" - Parsed {} on thread {}", res.len(), thread);
-        atomic_commits.fetch_add(res.len(), Ordering::SeqCst);
-        acc.extend(res);
-        Ok(acc)
-    })
+        )
         .map(|x: Result<Vec<GitObject>, Error>| x.ok())
         .while_some()
         .flatten_iter() // concatenate all of the vecs in series
@@ -581,9 +622,13 @@ pub fn parse_repo(
     })
 }
 
-pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
-                    repo: &Repository, refr: &str,
-                    references: &BTreeMap<String, Vec<String>>) -> Result<GitObject, Error> {
+pub fn parse_commit(
+    idx: usize,
+    settings: &GitsySettingsRepo,
+    repo: &Repository,
+    refr: &str,
+    references: &BTreeMap<String, Vec<String>>,
+) -> Result<GitObject, Error> {
     let obj = repo.revparse_single(refr)?;
     let commit = repo.find_commit(obj.id())?;
 
@@ -610,9 +655,7 @@ pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
     };
 
     let (stats, commit_diff) = match idx < settings.limit_diffs.unwrap_or(usize::MAX) {
-        false => {
-            (None, None)
-        },
+        false => (None, None),
         true => {
             let b = commit.tree()?;
             let mut diffopts = DiffOptions::new();
@@ -620,17 +663,13 @@ pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
             let diff = repo.diff_tree_to_tree(a.as_ref(), Some(&b), Some(&mut diffopts))?;
             let stats = diff.stats()?;
             let commit_diff: Option<GitDiffCommit> = match idx < settings.limit_diffs.unwrap_or(usize::MAX) {
-                true => {
-                    Some(GitDiffCommit {
-                        file_count: stats.files_changed(),
-                        additions: stats.insertions(),
-                        deletions: stats.deletions(),
-                        ..Default::default()
-                    })
-                },
-                false => {
-                    None
-                }
+                true => Some(GitDiffCommit {
+                    file_count: stats.files_changed(),
+                    additions: stats.insertions(),
+                    deletions: stats.deletions(),
+                    ..Default::default()
+                }),
+                false => None,
             };
             let stats = GitStats {
                 files: stats.files_changed(),
@@ -681,7 +720,8 @@ pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
                         None, // TODO: handle binary files?
                         Some(&mut |_file, hunk| {
                             let mut files = files.borrow_mut();
-                            let file_diff: &mut GitDiffFile = files.last_mut().expect("Diff hunk not associated with a file!");
+                            let file_diff: &mut GitDiffFile =
+                                files.last_mut().expect("Diff hunk not associated with a file!");
                             let mut hunk_diff: GitDiffHunk = Default::default();
                             hunk_diff.context = String::from_utf8_lossy(hunk.header()).to_string();
                             file_diff.hunks.push(hunk_diff);
@@ -689,7 +729,8 @@ pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
                         }),
                         Some(&mut |_file, _hunk, line| {
                             let mut files = files.borrow_mut();
-                            let file_diff: &mut GitDiffFile = files.last_mut().expect("Diff hunk not associated with a file!");
+                            let file_diff: &mut GitDiffFile =
+                                files.last_mut().expect("Diff hunk not associated with a file!");
                             let hunk_diff: &mut GitDiffHunk = file_diff
                                 .hunks
                                 .last_mut()
@@ -727,7 +768,7 @@ pub fn parse_commit(idx: usize, settings: &GitsySettingsRepo,
                 }
             };
             (Some(stats), commit_diff)
-        },
+        }
     };
 
     let tree = obj.peel_to_tree()?;

diff --git a/src/settings.rs b/src/settings.rs
line changes: +37/-26
index e867a20..dfe7381
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -20,14 +20,14 @@
  * You should have received a copy of the GNU General Public License
  * along with Itsy-Gitsy.  If not, see <http://www.gnu.org/licenses/>.
  */
-use crate::{louder, error};
 use crate::git::GitRepo;
 use crate::util::SafePathVar;
+use crate::{error, louder};
 use clap::Parser;
 use git2::Repository;
 use serde::Deserialize;
 use std::collections::{BTreeMap, HashMap, HashSet};
-use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all, read_to_string};
+use std::fs::{create_dir, create_dir_all, read_dir, read_to_string, remove_dir_all};
 use std::hash::{Hash, Hasher};
 use std::path::{Path, PathBuf};
 use std::sync::atomic::Ordering;
@@ -140,11 +140,17 @@ pub enum GitsyTemplateType {
     error,
 }
 
-pub fn substitute_path_vars<P,S>(path: &P, repo: Option<&GitRepo>, obj: Option<&S>) -> PathBuf
-where P: AsRef<Path>,
-      S: SafePathVar {
+pub fn substitute_path_vars<P, S>(path: &P, repo: Option<&GitRepo>, obj: Option<&S>) -> PathBuf
+where
+    P: AsRef<Path>,
+    S: SafePathVar,
+{
     let p: PathBuf = path.as_ref().to_path_buf();
-    assert!(p.is_relative(), "ERROR: path must be relative, not absolute: {}", p.display());
+    assert!(
+        p.is_relative(),
+        "ERROR: path must be relative, not absolute: {}",
+        p.display()
+    );
     let p: PathBuf = repo.map(|r| r.safe_substitute(&p)).unwrap_or(p);
     let p: PathBuf = obj.map(|o| o.safe_substitute(&p)).unwrap_or(p);
     p
@@ -164,42 +170,45 @@ macro_rules! template_fn {
             let new_path = substitute_path_vars(&tmpl_path, repo, obj);
             self.canonicalize_and_create(&new_path, $is_dir)
         }
-    }
+    };
 }
 
 macro_rules! templates_fn {
     ($var:ident, $is_dir:expr) => {
         pub fn $var<S: SafePathVar>(&self, repo: Option<&GitRepo>, obj: Option<&S>) -> Vec<(PathBuf, PathBuf)> {
             match &self.templates {
-                Some(template) => {
-                    template.iter()
-                        .filter(|x| x.kind == GitsyTemplateType::$var)
-                        .map(|x| {
-                            let tmpl_path = PathBuf::from(&x.output);
-                            let new_path = substitute_path_vars(&tmpl_path, repo, obj);
-                            (PathBuf::from(&x.template),
-                             self.canonicalize_and_create(&new_path, $is_dir))
-                        }).collect()
-                },
+                Some(template) => template
+                    .iter()
+                    .filter(|x| x.kind == GitsyTemplateType::$var)
+                    .map(|x| {
+                        let tmpl_path = PathBuf::from(&x.output);
+                        let new_path = substitute_path_vars(&tmpl_path, repo, obj);
+                        (
+                            PathBuf::from(&x.template),
+                            self.canonicalize_and_create(&new_path, $is_dir),
+                        )
+                    })
+                    .collect(),
                 None => {
-                    vec!()
-                },
+                    vec![]
+                }
             }
         }
-    }
+    };
 }
 
 impl SafePathVar for GitsySettingsOutputs {
     fn safe_substitute(&self, path: &impl AsRef<Path>) -> PathBuf {
         let src: &Path = path.as_ref();
         let mut dst = PathBuf::new();
-        let root = self.template_root.to_str()
-            .expect(&format!("ERROR: couldn't parse template root: {}", self.template_root.display()));
+        let root = self.template_root.to_str().expect(&format!(
+            "ERROR: couldn't parse template root: {}",
+            self.template_root.display()
+        ));
         for cmp in src.components() {
             // NOTE: this variable is not sanitized, since it's
             // allowed to create new directory structure.
-            let cmp = cmp.as_os_str().to_string_lossy()
-                .replace("%TEMPLATE%", &root);
+            let cmp = cmp.as_os_str().to_string_lossy().replace("%TEMPLATE%", &root);
             dst.push(cmp);
         }
         dst
@@ -545,9 +554,11 @@ impl GitsySettings {
         if cli.repos.len() > 0 {
             repo_descriptions.clear();
             for dir in &cli.repos {
-                let name: String = dir.file_name()
+                let name: String = dir
+                    .file_name()
                     .expect(&format!("Invalid repository path: {}", dir.display()))
-                    .to_string_lossy().to_string();
+                    .to_string_lossy()
+                    .to_string();
                 let clone_url = match settings.clone_url.as_ref() {
                     Some(url) => Some(url.replace("%REPO%", &name)),
                     _ => None,

diff --git a/src/template.rs b/src/template.rs
line changes: +8/-10
index 146333d..6a8ba8a
--- a/src/template.rs
+++ b/src/template.rs
@@ -102,16 +102,13 @@ impl Filter for MaskFilter {
                 .expect("ERROR: Tera mask filter called without `mask` parameter.")
                 .clone(),
         )
-            .expect("ERROR: Tera `mask` parameter is not valid.");
+        .expect("ERROR: Tera `mask` parameter is not valid.");
         let mask: u64 = match mask.starts_with("0x") {
             true => {
                 let hexstr = mask.strip_prefix("0x").unwrap();
-                u64::from_str_radix(hexstr, 16)
-                    .expect("ERROR: Tera `mask` parameter is invalid hex.")
-            },
-            false => {
-                str::parse::<u64>(&mask).expect("ERROR: Tera `mask` parameter is not valid.")
-            },
+                u64::from_str_radix(hexstr, 16).expect("ERROR: Tera `mask` parameter is invalid hex.")
+            }
+            false => str::parse::<u64>(&mask).expect("ERROR: Tera `mask` parameter is not valid."),
         };
         Ok(to_value(v & mask).unwrap())
     }
@@ -191,9 +188,10 @@ pub struct Pagination {
 }
 impl Pagination {
     pub fn new<P: AsRef<Path>>(cur: usize, total: usize, url_template: &P) -> Self {
-        let url_template = url_template.as_ref().to_str()
-            .expect(&format!("ERROR: attempted to paginate unparseable path: {}",
-                             url_template.as_ref().display()));
+        let url_template = url_template.as_ref().to_str().expect(&format!(
+            "ERROR: attempted to paginate unparseable path: {}",
+            url_template.as_ref().display()
+        ));
         let digits = total.to_string().len().max(2);
         let next = match cur + 1 <= total {
             true => Some(cur + 1),

diff --git a/src/util.rs b/src/util.rs
line changes: +1/-4
index f533db0..0b08000
--- a/src/util.rs
+++ b/src/util.rs
@@ -160,13 +160,10 @@ pub fn sanitize_path_component(var: &str) -> String {
 }
 
 pub fn urlify_path(path: &str) -> String {
-    let path = path
-        .replace("/", "+")
-        .replace("\\", "+");
+    let path = path.replace("/", "+").replace("\\", "+");
     path.trim().to_string()
 }
 
-
 pub trait SafePathVar {
     fn safe_substitute(&self, path: &impl AsRef<Path>) -> PathBuf;
 }