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/git.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/git.rs (limited to 'src/git.rs') diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..f837711 --- /dev/null +++ b/src/git.rs @@ -0,0 +1,111 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use std::process::{ + self, + Command, +}; + +use anyhow::{ + anyhow, + ensure, + Context, +}; +use once_cell::sync::Lazy; +use sha2::{ + Digest, + Sha256, +}; + +mod commit; +pub use commit::{ + commit_signed, + verify_commit_signature, +}; + +pub mod config; + +pub mod refs; +pub use refs::{ + ReferenceNames, + Refname, +}; +pub mod repo; +pub use repo::add_alternates; +pub mod serde; + +pub static EMPTY_TREE: Lazy = + Lazy::new(|| git2::Oid::from_str("4b825dc642cb6eb9a060e54bf8d69288fbee4904").unwrap()); + +pub type Result = core::result::Result; + +pub fn empty_tree(repo: &git2::Repository) -> Result { + repo.find_tree(*EMPTY_TREE) +} + +pub fn if_not_found_none(r: Result) -> Result> { + if_not_found_then(r.map(Some), || Ok(None)) +} + +pub fn if_not_found_then(r: Result, f: F) -> Result +where + F: FnOnce() -> Result, +{ + r.or_else(|e| match e.code() { + git2::ErrorCode::NotFound => f(), + _ => Err(e), + }) +} + +pub fn blob_hash(data: &[u8]) -> Result { + // very minimally faster than going through libgit2. not sure yet if that's + // worth the dependency. + #[cfg(feature = "sha1dc")] + { + use sha1collisiondetection::Sha1CD; + + let mut hasher = Sha1CD::default(); + hasher.update("blob "); + hasher.update(data.len().to_string().as_bytes()); + hasher.update(b"\0"); + hasher.update(data); + let hash = hasher.finalize_cd().expect("sha1 collision detected"); + git2::Oid::from_bytes(&hash) + } + #[cfg(not(feature = "sha1dc"))] + git2::Oid::hash_object(git2::ObjectType::Blob, data) +} + +pub fn blob_hash_sha2(data: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update("blob "); + hasher.update(data.len().to_string().as_bytes()); + hasher.update(b"\0"); + hasher.update(data); + hasher.finalize().into() +} + +/// Look up `key` from config and run the value as a command +pub fn config_command(cfg: &git2::Config, key: &str) -> crate::Result> { + if_not_found_none(cfg.get_string(key))? + .map(|cmd| { + let process::Output { status, stdout, .. } = { + let invalid = || anyhow!("'{cmd}' is not a valid command"); + let lex = shlex::split(&cmd).ok_or_else(invalid)?; + let (bin, args) = lex.split_first().ok_or_else(invalid)?; + Command::new(bin) + .args(args) + .stderr(process::Stdio::inherit()) + .output()? + }; + ensure!(status.success(), "'{cmd}' failed"); + const NL: u8 = b'\n'; + let line1 = stdout + .into_iter() + .take_while(|b| b != &NL) + .collect::>(); + ensure!(!line1.is_empty(), "no output from '{cmd}'"); + String::from_utf8(line1).with_context(|| format!("invalid output from '{cmd}'")) + }) + .transpose() +} -- cgit v1.2.3