// 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, }; 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, /// A list of the repositories known to back. /// This list is constructed from the `scan_path` and the `project_list` file. pub repositories: BackRepositories, /// The root url this instance of back is hosted on. /// For example: /// `issues.foss-syndicate.org` pub root: Url, } pub struct BackRepositories { repositories: Vec, /// The path that is the common parent of all the repositories. scan_path: PathBuf, } 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 fn scan_path(&self) -> &Path { &self.scan_path } } pub struct BackRepository { repo_path: PathBuf, } impl BackRepository { pub fn open(&self, scan_path: &Path) -> Result { let repo = ThreadSafeRepository::open(scan_path.join(&self.repo_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 } } #[derive(Deserialize)] struct RawBackConfig { source_code_repository_url: Url, root_url: Url, project_list: PathBuf, scan_path: PathBuf, } impl BackConfig { 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, })?; let raw: RawBackConfig = serde_json::from_str(&value).map_err(|err| Error::ConfigParse { file: path.to_owned(), error: err, })?; Self::try_from(raw) } } impl TryFrom for BackConfig { type Error = error::Error; fn try_from(value: RawBackConfig) -> Result { let repositories = fs::read_to_string(&value.project_list) .map_err(|err| error::Error::ProjectListRead { error: err, file: value.project_list, })? .lines() .try_fold(vec![], |mut acc, path| { acc.push(BackRepository { repo_path: PathBuf::from(path), }); Ok::<_, Self::Error>(acc) })?; Ok(Self { source_code_repository_url: value.source_code_repository_url, repositories: BackRepositories { repositories, scan_path: value.scan_path, }, root: value.root_url, }) } }