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/patches.rs | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/patches.rs (limited to 'src/patches.rs') diff --git a/src/patches.rs b/src/patches.rs new file mode 100644 index 0000000..8623e4e --- /dev/null +++ b/src/patches.rs @@ -0,0 +1,212 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use core::{ + fmt, + ops::Deref, +}; +use std::{ + io::BufRead, + str::FromStr, +}; + +use anyhow::{ + anyhow, + bail, +}; + +use hex::FromHex; +use once_cell::sync::Lazy; +use sha2::{ + digest::{ + generic_array::GenericArray, + typenum::U32, + }, + Digest, + Sha256, +}; + +use crate::{ + git::Refname, + iter::IteratorExt, +}; + +mod traits; +pub use traits::{ + to_blob, + to_tree, + Seen, +}; +use traits::{ + write_sharded, + Blob, +}; + +mod bundle; +pub use bundle::Bundle; + +mod error; +pub use error::FromTree; + +pub mod iter; +pub mod notes; + +pub mod record; +pub use record::{ + Record, + Signature, +}; + +mod state; +pub use state::{ + merge_notes, + unbundle, + unbundled_ref, + DropHead, +}; + +mod submit; +pub use submit::{ + AcceptArgs, + AcceptOptions, + Submission, + ALLOWED_REFS, + GLOB_HEADS, + GLOB_IT_BUNDLES, + GLOB_IT_IDS, + GLOB_IT_TOPICS, + GLOB_NOTES, + GLOB_TAGS, +}; + +pub const MAX_LEN_BUNDLE: usize = 5_000_000; + +pub const HTTP_HEADER_SIGNATURE: &str = "X-it-Signature"; + +pub const REF_HEADS_PATCHES: &str = "refs/heads/patches"; + +pub const REF_IT_BRANCHES: &str = "refs/it/branches"; +pub const REF_IT_BUNDLES: &str = "refs/it/bundles"; +pub const REF_IT_PATCHES: &str = "refs/it/patches"; +pub const REF_IT_SEEN: &str = "refs/it/seen"; +pub const REF_IT_TOPICS: &str = "refs/it/topics"; + +pub const BLOB_HEADS: &str = "heads"; +pub const BLOB_META: &str = "record.json"; + +pub static TOPIC_MERGES: Lazy = Lazy::new(|| Topic::hashed("merges")); +pub static TOPIC_SNAPSHOTS: Lazy = Lazy::new(|| Topic::hashed("snapshots")); + +#[derive(Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Topic(#[serde(with = "hex::serde")] [u8; 32]); + +impl Topic { + const TRAILER_PREFIX: &str = "Re:"; + + pub fn hashed>(v: T) -> Self { + Self(Sha256::digest(v).into()) + } + + pub fn from_commit(commit: &git2::Commit) -> crate::Result> { + commit + .message_raw_bytes() + .lines() + .try_find_map(|line| -> crate::Result> { + let val = line? + .strip_prefix(Self::TRAILER_PREFIX) + .map(|v| Self::from_hex(v.trim())) + .transpose()?; + Ok(val) + }) + } + + pub fn as_trailer(&self) -> String { + format!("{} {}", Self::TRAILER_PREFIX, self) + } + + pub fn from_refname(name: &str) -> crate::Result { + let last = name + .split('/') + .next_back() + .ok_or_else(|| anyhow!("invalid topic ref {name}"))?; + Ok(Self::from_hex(last)?) + } + + pub fn as_refname(&self) -> Refname { + let name = format!("{}/{}", REF_IT_TOPICS, self); + Refname::try_from(name).unwrap() + } +} + +impl FromHex for Topic { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + <[u8; 32]>::from_hex(hex).map(Self) + } +} + +impl FromStr for Topic { + type Err = ::Error; + + fn from_str(s: &str) -> Result { + Self::from_hex(s) + } +} + +impl fmt::Display for Topic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + +impl fmt::Debug for Topic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + +impl From> for Topic { + fn from(a: GenericArray) -> Self { + Self(a.into()) + } +} + +/// Maps a [`Refname`] to the [`REF_IT_BRANCHES`] namespace +/// +/// The [`Refname`] must be a branch, ie. start with 'refs/heads/'. +pub struct TrackingBranch(String); + +impl TrackingBranch { + pub fn master() -> Self { + Self([REF_IT_BRANCHES, "master"].join("/")) + } + + pub fn main() -> Self { + Self([REF_IT_BRANCHES, "main"].join("/")) + } + + pub fn into_refname(self) -> Refname { + Refname::try_from(self.0).unwrap() + } +} + +impl Deref for TrackingBranch { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom<&Refname> for TrackingBranch { + type Error = crate::Error; + + fn try_from(r: &Refname) -> Result { + match r.strip_prefix("refs/heads/") { + None => bail!("not a branch: {r}"), + Some("patches") => bail!("reserved name: {r}"), + Some(suf) => Ok(Self([REF_IT_BRANCHES, suf].join("/"))), + } + } +} -- cgit v1.2.3