partially moves backend repo logic out of render code
This commit is contained in:
		
							parent
							
								
									4a5199b08f
								
							
						
					
					
						commit
						8236925fee
					
				@ -7,10 +7,13 @@ edition = "2024"
 | 
			
		||||
name = "frontend"
 | 
			
		||||
path = "src/frontend.rs"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "backend"
 | 
			
		||||
path = "src/backend.rs"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
git2 = "0.20.2"
 | 
			
		||||
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
 | 
			
		||||
#serde_derive = { version = "1.0.219", default-features = false }
 | 
			
		||||
#inkjet = "0.11.1"
 | 
			
		||||
#markdown = "1.0.0"
 | 
			
		||||
tera = { version = "1.20.0", default-features = false, features = ["builtins"] }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										190
									
								
								src/backend.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/backend.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2025 Emma Tebibyte <emma@tebibyte.media>
 | 
			
		||||
 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of Mintee.
 | 
			
		||||
 *
 | 
			
		||||
 * Mintee is free software: you can redistribute it and/or modify it under the
 | 
			
		||||
 * terms of the GNU Affero General Public License as published by the Free
 | 
			
		||||
 * Software Foundation, either version 3 of the License, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 * 
 | 
			
		||||
 * Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
			
		||||
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
			
		||||
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 | 
			
		||||
 * details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with Mintee. If not, see https://www.gnu.org/licenses/.
 | 
			
		||||
 *
 | 
			
		||||
 * This file incorporates work covered by the following copyright and
 | 
			
		||||
 * permission notice:
 | 
			
		||||
 *
 | 
			
		||||
 *     MIT License
 | 
			
		||||
 * 
 | 
			
		||||
 *     Copyright (c) 2021 Sergey "Shnatsel" Davidoff <shnatsel@gmail.com>
 | 
			
		||||
 * 
 | 
			
		||||
 *     Permission is hereby granted, free of charge, to any person obtaining a
 | 
			
		||||
 *     copy of this software and associated documentation files (the 
 | 
			
		||||
 *     "Software"), to deal in the Software without restriction, including 
 | 
			
		||||
 *     without limitation the rights to use, copy, modify, merge, publish,
 | 
			
		||||
 *     distribute, sublicense, and/or sell copies of the Software, and to
 | 
			
		||||
 *     permit persons to whom the Software is furnished to do so, subject to
 | 
			
		||||
 *     the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 *     The above copyright notice and this permission notice shall be included
 | 
			
		||||
 *     in all copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 | 
			
		||||
 *     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | 
			
		||||
 *     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | 
			
		||||
 *     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 | 
			
		||||
 *     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 | 
			
		||||
 *     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 | 
			
		||||
 *     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
	error::Error,
 | 
			
		||||
	path::{ PathBuf, Path },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use git2::{ Commit, ErrorClass, ErrorCode, Repository, Sort };
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
pub struct GitCommit {
 | 
			
		||||
	hash: String,
 | 
			
		||||
	message: String,
 | 
			
		||||
	short_hash: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<Commit<'_>> for GitCommit {
 | 
			
		||||
	type Error = git2::Error;
 | 
			
		||||
 | 
			
		||||
	fn try_from(commit: Commit) -> Result<Self, Self::Error> {
 | 
			
		||||
		let hash = commit.id().to_string();
 | 
			
		||||
		let short_hash = commit.as_object().short_id()?.as_str().ok_or(
 | 
			
		||||
			Self::Error::new(ErrorCode::Invalid, ErrorClass::Object, "Short ID is not valid UTF-8")
 | 
			
		||||
		)?.to_owned();
 | 
			
		||||
		let message = commit.message().ok_or(
 | 
			
		||||
			Self::Error::new(ErrorCode::NotFound, ErrorClass::None, "No commit message")
 | 
			
		||||
		)?.to_owned();
 | 
			
		||||
		Ok(GitCommit { hash, message, short_hash })
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct GitEntry {
 | 
			
		||||
	committer: String,
 | 
			
		||||
	last_commit: String,
 | 
			
		||||
	last_commit_short: String,
 | 
			
		||||
	last_commit_time: i64,
 | 
			
		||||
	path: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GitEntry {
 | 
			
		||||
	fn new(commit: &Commit, path: String) -> Result<Self, git2::Error> {
 | 
			
		||||
		let commit_id = commit.id();
 | 
			
		||||
 | 
			
		||||
		Ok(GitEntry {
 | 
			
		||||
			committer: commit
 | 
			
		||||
				.committer()
 | 
			
		||||
				.name()
 | 
			
		||||
				.unwrap_or("")
 | 
			
		||||
				.to_owned(),
 | 
			
		||||
			last_commit: commit_id.to_string(),
 | 
			
		||||
			last_commit_short: commit.as_object().short_id()?.as_str().unwrap_or("").to_owned(),
 | 
			
		||||
			last_commit_time: commit.time().seconds(),
 | 
			
		||||
			path,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
pub struct GitRepo {
 | 
			
		||||
	entries: Vec<GitEntry>,
 | 
			
		||||
	last_commit: GitCommit,
 | 
			
		||||
	name: String,
 | 
			
		||||
	owner: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GitRepo {
 | 
			
		||||
	fn open(path: PathBuf) -> Result<Self, Box<dyn Error>> {
 | 
			
		||||
		let repo = Repository::open(path.clone())?;
 | 
			
		||||
		let entries = Self::get_entries(&repo)?;
 | 
			
		||||
 | 
			
		||||
		let full_path = path.clone().as_path().canonicalize()?;
 | 
			
		||||
 | 
			
		||||
		let relative_path = full_path.strip_prefix("/var/mintee/repos")?;
 | 
			
		||||
 | 
			
		||||
		let owner = String::from_utf8(
 | 
			
		||||
			relative_path
 | 
			
		||||
			.parent()
 | 
			
		||||
			.unwrap()
 | 
			
		||||
			.to_path_buf()
 | 
			
		||||
			.as_mut_os_string()
 | 
			
		||||
			.as_encoded_bytes()
 | 
			
		||||
			.to_vec()
 | 
			
		||||
		)?;
 | 
			
		||||
 | 
			
		||||
		let name = String::from_utf8(
 | 
			
		||||
			relative_path
 | 
			
		||||
			.file_name()
 | 
			
		||||
			.unwrap()
 | 
			
		||||
			.to_owned()
 | 
			
		||||
			.as_encoded_bytes()
 | 
			
		||||
			.to_vec()
 | 
			
		||||
		)?;
 | 
			
		||||
 | 
			
		||||
		let last_commit = GitCommit::try_from(repo.head()?.peel_to_commit()?)?;
 | 
			
		||||
 | 
			
		||||
		Ok(GitRepo { entries, last_commit, name, owner })
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn get_remote_repo() { // for AP repos
 | 
			
		||||
		todo!();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn get_entries(repo: &Repository) -> Result<Vec<GitEntry>, Box<dyn Error>> {
 | 
			
		||||
		let mut entries = Vec::new();
 | 
			
		||||
		let mut revwalk = repo.revwalk()?;
 | 
			
		||||
 | 
			
		||||
		revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
 | 
			
		||||
		revwalk.push_head()?;
 | 
			
		||||
 | 
			
		||||
		for commit_id in revwalk {
 | 
			
		||||
			let commit_id = commit_id?;
 | 
			
		||||
			let commit = repo.find_commit(commit_id)?;
 | 
			
		||||
			if commit.parent_count() <= 1 {
 | 
			
		||||
				let tree = commit.tree()?;
 | 
			
		||||
				let prev_tree = match commit.parent_count() {
 | 
			
		||||
					1 => Some(commit.parent(0)?.tree()?),
 | 
			
		||||
					0 => None,
 | 
			
		||||
					_ => unreachable!(),
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				let diff = repo.diff_tree_to_tree(prev_tree.as_ref(), Some(&tree), None)?;
 | 
			
		||||
				for delta in diff.deltas() {
 | 
			
		||||
					if let Some(file_path) = delta.new_file().path() {
 | 
			
		||||
						let p = String::from_utf8(file_path
 | 
			
		||||
							.to_path_buf()
 | 
			
		||||
							.as_mut_os_string()
 | 
			
		||||
							.as_encoded_bytes()
 | 
			
		||||
							.to_vec()
 | 
			
		||||
						)?;
 | 
			
		||||
						entries.push(GitEntry::new(&commit, p)?);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		Ok(entries)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
	GitRepo::open(Path::new("/var/mintee/repos/silt/pingus").to_path_buf())?;
 | 
			
		||||
 | 
			
		||||
	Ok(())
 | 
			
		||||
}
 | 
			
		||||
@ -44,30 +44,15 @@
 | 
			
		||||
 *     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
	env::current_dir,
 | 
			
		||||
	fs::metadata,
 | 
			
		||||
	path::PathBuf,
 | 
			
		||||
};
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
use git2::{ Commit, Repository, Sort };
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use git2::Repository;
 | 
			
		||||
use tera::{ Context, Tera };
 | 
			
		||||
 | 
			
		||||
trait ToContext {
 | 
			
		||||
	fn to_context(self) -> Result<Context, git2::Error>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct Entry {
 | 
			
		||||
	class: String,
 | 
			
		||||
	committer: String,
 | 
			
		||||
	last_commit: String,
 | 
			
		||||
	last_commit_short: String,
 | 
			
		||||
	last_commit_time: i64,
 | 
			
		||||
	path: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToContext for Repository {
 | 
			
		||||
	fn to_context(self) -> Result<Context, git2::Error> {
 | 
			
		||||
		let repo = self.commondir();
 | 
			
		||||
@ -99,62 +84,6 @@ impl ToContext for Repository {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Entry {
 | 
			
		||||
	fn new(commit: &Commit, path: String) -> Result<Self, git2::Error> {
 | 
			
		||||
		let commit_id = commit.id();
 | 
			
		||||
		let ft = metadata(&path).unwrap().file_type();
 | 
			
		||||
		let class: String;
 | 
			
		||||
 | 
			
		||||
		if ft.is_dir() {
 | 
			
		||||
			class = "directory".to_owned();
 | 
			
		||||
		} else {
 | 
			
		||||
			class = "file".to_owned();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(Entry {
 | 
			
		||||
			class,
 | 
			
		||||
			committer: commit
 | 
			
		||||
				.committer()
 | 
			
		||||
				.name()
 | 
			
		||||
				.unwrap_or("")
 | 
			
		||||
				.to_owned(),
 | 
			
		||||
			last_commit: commit_id.to_string(),
 | 
			
		||||
			last_commit_short: commit.as_object().short_id()?.as_str().unwrap_or("").to_owned(),
 | 
			
		||||
			last_commit_time: commit.time().seconds(),
 | 
			
		||||
			path,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_entries(repo: &Repository) -> Result<Vec<Entry>, git2::Error> {
 | 
			
		||||
	let mut entries = Vec::new();
 | 
			
		||||
	let mut revwalk = repo.revwalk()?;
 | 
			
		||||
 | 
			
		||||
	revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
 | 
			
		||||
	revwalk.push_head()?;
 | 
			
		||||
 | 
			
		||||
	for commit_id in revwalk {
 | 
			
		||||
		let commit_id = commit_id?;
 | 
			
		||||
		let commit = repo.find_commit(commit_id)?;
 | 
			
		||||
		if commit.parent_count() <= 1 {
 | 
			
		||||
			let tree = commit.tree()?;
 | 
			
		||||
			let prev_tree = match commit.parent_count() {
 | 
			
		||||
				1 => Some(commit.parent(0)?.tree()?),
 | 
			
		||||
				0 => None,
 | 
			
		||||
				_ => unreachable!(),
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			let diff = repo.diff_tree_to_tree(prev_tree.as_ref(), Some(&tree), None)?;
 | 
			
		||||
			for delta in diff.deltas() {
 | 
			
		||||
				if let Some(file_path) = delta.new_file().path() {
 | 
			
		||||
					entries.push(Entry::new(&commit, file_path.to_str().unwrap_or("Invalid UTF-8").to_owned())?);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Ok(entries)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn render_path(path: PathBuf) -> tera::Result<String> {
 | 
			
		||||
	let tera = Tera::new("./assets/templates/repo/*")?;
 | 
			
		||||
	let repo = Repository::discover(path).unwrap();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user