From 6bdc73f807eb85051b45ca81be4211adb9b54537 Mon Sep 17 00:00:00 2001 From: Trevor Bentley Date: Thu, 19 Jan 2023 21:03:07 +0100 Subject: [PATCH] cargo fmt --- src/generate.rs | 622 +++++++++++++++++++++++++++++++++++------------- src/git.rs | 211 +++++++++------- src/settings.rs | 63 +++-- src/template.rs | 18 +- src/util.rs | 5 +- 5 files changed, 628 insertions(+), 291 deletions(-) diff --git a/src/generate.rs b/src/generate.rs index 273c164..28c8664 100644 --- 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::(None, None))); + ctx.insert( + "site_assets", + &self + .settings + .outputs + .to_relative(&self.settings.outputs.global_assets::(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>(&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::(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::(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 { + pub fn gen_summary( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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::(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 { + pub fn gen_history( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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::(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 { + pub fn gen_commit( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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 { + pub fn gen_branches( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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::(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 { + pub fn gen_branch( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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 { + pub fn gen_tags( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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::(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 { + pub fn gen_tag( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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 { + pub fn gen_files( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + _repo: &Repository, + ) -> Result { 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::(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 { + pub fn gen_file( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + repo: &Repository, + ) -> Result { 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::(Some(&parsed_repo), None), css.as_str()); + let bytes = self.write_rendered( + &self.settings.outputs.syntax_css::(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::(); 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 { + pub fn gen_dir( + &self, + ctx: &Context, + atomic_bytes: &AtomicUsize, + parsed_repo: &GitRepo, + repo_desc: &GitsySettingsRepo, + repo: &Repository, + ) -> Result { 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 { + fn copy_assets( + &self, + repo_desc: Option<&GitsySettingsRepo>, + parsed_repo: Option<&GitRepo>, + repo: Option<&Repository>, + ) -> Result { 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::(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::(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::(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::(repo_bytes) - }) - .try_reduce(|| 0, |acc, x| Ok(acc + x))?; - total_bytes - }, - n => { - 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 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")))); + 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::(repo_bytes) - }) + }, + ) + .try_reduce(|| 0, |acc, x| Ok(acc + x))?; + total_bytes + } + n => { + 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::(repo_bytes) + }, + ) .try_reduce(|| 0, |acc, x| Ok(acc + x))?; Ok::(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 index 3c84bcb..c470abe 100644 --- a/src/git.rs +++ b/src/git.rs @@ -21,16 +21,16 @@ * along with Itsy-Gitsy. If not, see . */ 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 = 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 = 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, Error> { let mut files: Vec = 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>, settings: &GitsySettingsRepo) -> Result, Error> { +pub fn parse_revwalk( + repo: &Repository, + mut revwalk: git2::Revwalk, + references: &BTreeMap>, + settings: &GitsySettingsRepo, +) -> Result, Error> { let mut history: Vec = 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 = (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, 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>) -> Result { +pub fn parse_commit( + idx: usize, + settings: &GitsySettingsRepo, + repo: &Repository, + refr: &str, + references: &BTreeMap>, +) -> Result { 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 = 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 index e867a20..dfe7381 100644 --- 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 . */ -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(path: &P, repo: Option<&GitRepo>, obj: Option<&S>) -> PathBuf -where P: AsRef, - S: SafePathVar { +pub fn substitute_path_vars(path: &P, repo: Option<&GitRepo>, obj: Option<&S>) -> PathBuf +where + P: AsRef, + 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(&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) -> 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 index 146333d..6a8ba8a 100644 --- 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::(&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::(&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>(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 index f533db0..0b08000 100644 --- 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) -> PathBuf; }