// Back - An extremely simple git issue tracking system. Inspired by tvix's // panettone // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: AGPL-3.0-or-later // // This file is part of Back. // // You should have received a copy of the License along with this program. // If not, see . use std::{ fs, path::{Path, PathBuf}, }; use gix::ThreadSafeRepository; use serde::Deserialize; use url::Url; use crate::{ error::{self, Error}, git_bug::dag::is_git_bug, }; #[derive(Deserialize)] pub struct BackConfig { /// The url to the source code of back. This is needed, because back is licensed under the /// AGPL. pub source_code_repository_url: Url, /// The root url this instance of back is hosted on. /// For example: /// `issues.foss-syndicate.org` pub root_url: Url, project_list: PathBuf, /// The path that is the common parent of all the repositories. pub scan_path: PathBuf, } pub struct BackRepositories { repositories: Vec, } impl BackRepositories { pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { self.into_iter() } } impl<'a> IntoIterator for &'a BackRepositories { type Item = <&'a Vec as IntoIterator>::Item; type IntoIter = <&'a Vec as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.repositories.iter() } } impl BackRepositories { /// Try to get the repository at path `path`. /// If no repository was registered/found at `path`, returns an error. pub fn get(&self, path: &Path) -> Result<&BackRepository, error::Error> { self.repositories .iter() .find(|p| p.repo_path == path) .ok_or(error::Error::RepoFind { repository_path: path.to_owned(), }) } } pub struct BackRepository { repo_path: PathBuf, } impl BackRepository { pub fn open(&self, scan_path: &Path) -> Result { let path = { let base = scan_path.join(&self.repo_path); if base.is_dir() { base } else { PathBuf::from(base.display().to_string() + ".git") } }; let repo = ThreadSafeRepository::open(path).map_err(|err| Error::RepoOpen { repository_path: self.repo_path.to_owned(), error: Box::new(err), })?; if is_git_bug(&repo.to_thread_local())? { Ok(repo) } else { Err(error::Error::NotGitBug { path: self.repo_path.clone(), }) } } pub fn path(&self) -> &Path { &self.repo_path } } impl BackConfig { pub fn repositories(&self) -> error::Result { let repositories = fs::read_to_string(&self.project_list) .map_err(|err| error::Error::ProjectListRead { error: err, file: self.project_list.to_owned(), })? .lines() .try_fold(vec![], |mut acc, path| { acc.push(BackRepository { repo_path: PathBuf::from(path), }); Ok::<_, error::Error>(acc) })?; Ok(BackRepositories { repositories }) } pub fn from_config_file(path: &Path) -> error::Result { let value = fs::read_to_string(path).map_err(|err| Error::ConfigRead { file: path.to_owned(), error: err, })?; serde_json::from_str(&value).map_err(|err| Error::ConfigParse { file: path.to_owned(), error: err, }) } }