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"
|
name = "frontend"
|
||||||
path = "src/frontend.rs"
|
path = "src/frontend.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "backend"
|
||||||
|
path = "src/backend.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git2 = "0.20.2"
|
git2 = "0.20.2"
|
||||||
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
||||||
#serde_derive = { version = "1.0.219", default-features = false }
|
|
||||||
#inkjet = "0.11.1"
|
#inkjet = "0.11.1"
|
||||||
#markdown = "1.0.0"
|
#markdown = "1.0.0"
|
||||||
tera = { version = "1.20.0", default-features = false, features = ["builtins"] }
|
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.
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{
|
use std::path::PathBuf;
|
||||||
env::current_dir,
|
|
||||||
fs::metadata,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use git2::{ Commit, Repository, Sort };
|
use git2::Repository;
|
||||||
use serde::Serialize;
|
|
||||||
use tera::{ Context, Tera };
|
use tera::{ Context, Tera };
|
||||||
|
|
||||||
trait ToContext {
|
trait ToContext {
|
||||||
fn to_context(self) -> Result<Context, git2::Error>;
|
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 {
|
impl ToContext for Repository {
|
||||||
fn to_context(self) -> Result<Context, git2::Error> {
|
fn to_context(self) -> Result<Context, git2::Error> {
|
||||||
let repo = self.commondir();
|
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> {
|
fn render_path(path: PathBuf) -> tera::Result<String> {
|
||||||
let tera = Tera::new("./assets/templates/repo/*")?;
|
let tera = Tera::new("./assets/templates/repo/*")?;
|
||||||
let repo = Repository::discover(path).unwrap();
|
let repo = Repository::discover(path).unwrap();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user