cargo fmt

This commit is contained in:
Trevor Bentley
2023-01-19 21:03:49 +01:00
parent d83f682aa2
commit 6bdc73f807
5 changed files with 628 additions and 291 deletions
+456 -166
View File
@@ -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)
})
.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::<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 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)
},
)
.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(),
+126 -85
View File
@@ -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()?;
+37 -26
View File
@@ -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,
+8 -10
View File
@@ -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),
+1 -4
View File
@@ -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;
}