From d2f423521ec76406944ad83098ec33afe20c692b Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Mon, 9 Jan 2023 13:18:33 +0100 Subject: This is it Squashed commit of all the exploration history. Development starts here. Signed-off-by: Kim Altintop --- src/metadata/git.rs | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/metadata/git.rs (limited to 'src/metadata/git.rs') diff --git a/src/metadata/git.rs b/src/metadata/git.rs new file mode 100644 index 0000000..1dde3da --- /dev/null +++ b/src/metadata/git.rs @@ -0,0 +1,232 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use std::{ + borrow::Cow, + io, +}; + +use anyhow::anyhow; + +use super::{ + drop, + identity, + Alternates, + ContentHash, + Drop, + Identity, + IdentityId, + KeySet, + Metadata, + Mirrors, + Signed, +}; +use crate::{ + cmd, + git::if_not_found_none, + json, +}; + +pub const META_FILE_ALTERNATES: &str = "alternates.json"; +pub const META_FILE_DROP: &str = "drop.json"; +pub const META_FILE_ID: &str = "id.json"; +pub const META_FILE_MIRRORS: &str = "mirrors.json"; + +pub mod error { + use thiserror::Error; + + #[derive(Debug, Error)] + #[error("unexpected metadata type")] + pub struct TypeMismatch; + + #[derive(Debug, Error)] + #[error("{file} not found in tree")] + pub struct FileNotFound { + pub file: &'static str, + } +} + +pub struct GitMeta { + pub hash: ContentHash, + pub signed: Signed, +} + +pub type GitIdentity = GitMeta; +pub type GitDrop = GitMeta; +pub type GitMirrors = GitMeta; +pub type GitAlternates = GitMeta; + +impl GitMeta { + pub fn verified<'a, F, G>( + self, + find_prev: F, + find_signer: G, + ) -> Result + where + F: FnMut(&ContentHash) -> io::Result>, + G: FnMut(&IdentityId) -> io::Result>, + { + self.signed.verified(find_prev, find_signer) + } +} + +impl GitMeta { + pub fn verified(self, find_prev: F) -> Result + where + F: FnMut(&ContentHash) -> io::Result>, + { + self.signed.verified(find_prev) + } +} + +pub struct FromSearchPath<'a, T> { + /// The repository (from the search path) the object was found in + pub repo: &'a git2::Repository, + pub meta: GitMeta, +} + +pub trait FromGit: Sized + Clone +where + for<'a> Cow<'a, Self>: TryFrom>, +{ + const METADATA_JSON: &'static str; + + fn from_blob(blob: &git2::Blob) -> crate::Result> { + let hash = ContentHash::from(blob); + let signed = json::from_blob::>(blob)? + .fmap(Cow::::try_from) + .transpose() + .map_err(|_| error::TypeMismatch)? + .fmap(Cow::into_owned); + + Ok(GitMeta { hash, signed }) + } + + fn from_tip>( + repo: &git2::Repository, + refname: R, + ) -> crate::Result> { + Self::from_reference(repo, &repo.find_reference(refname.as_ref())?) + } + + fn from_reference( + repo: &git2::Repository, + reference: &git2::Reference, + ) -> crate::Result> { + Self::from_commit(repo, &reference.peel_to_commit()?) + } + + fn from_commit(repo: &git2::Repository, commit: &git2::Commit) -> crate::Result> { + Self::from_tree(repo, &commit.tree()?) + } + + fn from_tree(repo: &git2::Repository, tree: &git2::Tree) -> crate::Result> { + let entry = tree + .get_name(Self::METADATA_JSON) + .ok_or(error::FileNotFound { + file: Self::METADATA_JSON, + })?; + let blob = entry.to_object(repo)?.peel_to_blob()?; + + Self::from_blob(&blob) + } + + fn from_content_hash( + repo: &git2::Repository, + hash: &ContentHash, + ) -> crate::Result> { + let blob = repo.find_blob(hash.into())?; + Self::from_blob(&blob) + } + + fn from_search_path>( + search_path: &[git2::Repository], + refname: R, + ) -> crate::Result> { + let (repo, reference) = find_ref_in_path(search_path, refname.as_ref())? + .ok_or_else(|| anyhow!("{} not found in search path", refname.as_ref()))?; + Self::from_reference(repo, &reference).map(|meta| FromSearchPath { repo, meta }) + } +} + +impl FromGit for Identity { + const METADATA_JSON: &'static str = META_FILE_ID; +} + +impl FromGit for Drop { + const METADATA_JSON: &'static str = META_FILE_DROP; +} + +impl FromGit for Mirrors { + const METADATA_JSON: &'static str = META_FILE_MIRRORS; +} + +impl FromGit for Alternates { + const METADATA_JSON: &'static str = META_FILE_ALTERNATES; +} + +pub fn find_parent( + repo: &git2::Repository, +) -> impl Fn(&ContentHash) -> io::Result> + '_ +where + T: FromGit, + for<'a> Cow<'a, T>: TryFrom>, +{ + |hash| { + T::from_content_hash(repo, hash) + .map_err(as_io) + .map(|meta| meta.signed) + } +} + +pub fn find_parent_in_tree<'a, T>( + repo: &'a git2::Repository, + tree: &'a git2::Tree<'a>, +) -> impl Fn(&ContentHash) -> io::Result> + 'a +where + T: FromGit, + for<'b> Cow<'b, T>: TryFrom>, +{ + fn go( + repo: &git2::Repository, + tree: &git2::Tree, + hash: &ContentHash, + ) -> crate::Result> + where + T: FromGit, + for<'b> Cow<'b, T>: TryFrom>, + { + let oid = git2::Oid::from(hash); + let blob = tree + .get_id(oid) + .ok_or_else(|| anyhow!("parent {} not found in tree {}", oid, tree.id()))? + .to_object(repo)? + .into_blob() + .map_err(|_| anyhow!("parent {} is not a file", oid))?; + + T::from_blob(&blob).map(|meta| meta.signed) + } + + move |hash| go(repo, tree, hash).map_err(as_io) +} + +pub fn find_ref_in_path<'a>( + search_path: &'a [git2::Repository], + name: &str, +) -> cmd::Result)>> { + for repo in search_path { + let have_ref = if_not_found_none(repo.resolve_reference_from_short_name(name))?; + if let Some(r) = have_ref { + return Ok(Some((repo, r))); + } + } + + Ok(None) +} + +fn as_io(e: E) -> io::Error +where + E: Into>, +{ + io::Error::new(io::ErrorKind::Other, e) +} -- cgit v1.2.3