// This file is part of radicle-surf // // // Copyright (C) 2019-2020 The Radicle Team // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 or // later as published by the Free Software Foundation. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! A model of a general VCS. The components consist of a [`History`] //! and a [`Vcs`] trait. // use crate::file_system::directory::Directory; use nonempty::NonEmpty; pub mod git; /// A non-empty bag of artifacts which are used to /// derive a [`crate::file_system::Directory`] view. Examples of artifacts /// would be commits in Git or patches in Pijul. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct History(pub NonEmpty); impl History { /// Create a new `History` consisting of one artifact. pub fn new(a: A) -> Self { History(NonEmpty::new(a)) } /// Push an artifact to the end of the `History`. pub fn push(&mut self, a: A) { self.0.push(a) } /// Iterator over the artifacts. pub fn iter(&self) -> impl Iterator { self.0.iter() } /// Get the firest artifact in the `History`. pub fn first(&self) -> &A { self.0.first() } /// Get the length of `History` (aka the artefacts count) pub fn len(&self) -> usize { self.0.len() } /// Check if `History` is empty pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Given that the `History` is topological order from most /// recent artifact to least recent, `find_suffix` gets returns /// the history up until the point of the given artifact. /// /// This operation may fail if the artifact does not exist in /// the given `History`. pub fn find_suffix(&self, artifact: &A) -> Option where A: Clone + PartialEq, { let new_history: Option> = NonEmpty::from_slice( &self .iter() .cloned() .skip_while(|current| *current != *artifact) .collect::>(), ); new_history.map(History) } /// Apply a function from `A` to `B` over the `History` pub fn map(self, f: F) -> History where F: FnMut(A) -> B, { History(self.0.map(f)) } /// Find an artifact in the `History`. /// /// The function provided should return `Some` if the item is the desired /// output and `None` otherwise. pub fn find(&self, f: F) -> Option where F: Fn(&A) -> Option, { self.iter().find_map(f) } /// Find an atrifact in the given `History` using the artifacts ID. /// /// This operation may fail if the artifact does not exist in the history. pub fn find_in_history(&self, identifier: &Identifier, id_of: F) -> Option where A: Clone, F: Fn(&A) -> Identifier, Identifier: PartialEq, { self.iter() .find(|artifact| { let current_id = id_of(artifact); *identifier == current_id }) .cloned() } /// Find all occurences of an artifact in a bag of `History`s. pub fn find_in_histories( histories: Vec, identifier: &Identifier, id_of: F, ) -> Vec where A: Clone, F: Fn(&A) -> Identifier + Copy, Identifier: PartialEq, { histories .into_iter() .filter(|history| history.find_in_history(identifier, id_of).is_some()) .collect() } } pub(crate) trait GetVcs where Self: Sized, { /// The way to identify a Repository. type RepoId; /// Find a Repository fn get_repo(identifier: Self::RepoId) -> Result; } /// The `VCS` trait encapsulates the minimal amount of information for /// interacting with some notion of `History` from a given /// Version-Control-System. pub trait Vcs { /// The way to identify a History. type HistoryId; /// The way to identify an artefact. type ArtefactId; /// Find a History in a Repo given a way to identify it fn get_history(&self, identifier: Self::HistoryId) -> Result, Error>; /// Find all histories in a Repo fn get_histories(&self) -> Result>, Error>; /// Identify artefacts of a Repository fn get_identifier(artefact: &A) -> Self::ArtefactId; }