This categorizes them semantically without using the overloaded names of owning and borrowing, which mean different things in Rust.
Showing the changes to git-object/src/mutable (9 of 34 files). Show all
Unified · Split
| @@ -0,0 +1,60 @@ | |||
| 1 | use crate::{ | ||
| 2 | commit, | ||
| 3 | mutable::{self, ser, NL}, | ||
| 4 | }; | ||
| 5 | use bstr::{BStr, BString, ByteSlice}; | ||
| 6 | use smallvec::SmallVec; | ||
| 7 | use std::io; | ||
| 8 | |||
| 9 | /// A mutable git commit, representing an annotated state of a working tree along with a reference to its historical commits. | ||
| 10 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 11 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 12 | pub struct Commit { | ||
| 13 | /// The hash of recorded working tree state. | ||
| 14 | pub tree: git_hash::ObjectId, | ||
| 15 | /// Hash of each parent commit. Empty for the first commit in repository. | ||
| 16 | pub parents: SmallVec<[git_hash::ObjectId; 1]>, | ||
| 17 | /// Who wrote this commit. | ||
| 18 | pub author: mutable::Signature, | ||
| 19 | /// Who committed this commit. | ||
| 20 | /// | ||
| 21 | /// This may be different from the `author` in case the author couldn't write to the repository themselves and | ||
| 22 | /// is commonly encountered with contributed commits. | ||
| 23 | pub committer: mutable::Signature, | ||
| 24 | /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). | ||
| 25 | pub encoding: Option<BString>, | ||
| 26 | /// The commit message documenting the change. | ||
| 27 | pub message: BString, | ||
| 28 | /// Extra header fields, in order of them being encountered, made accessible with the iterator returned | ||
| 29 | /// by [`extra_headers()`][Commit::extra_headers()]. | ||
| 30 | pub extra_headers: Vec<(BString, BString)>, | ||
| 31 | } | ||
| 32 | |||
| 33 | impl Commit { | ||
| 34 | /// Returns a convenient iterator over all extra headers. | ||
| 35 | pub fn extra_headers(&self) -> commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> { | ||
| 36 | commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr()))) | ||
| 37 | } | ||
| 38 | /// Serializes this instance to `out` in the git serialization format. | ||
| 39 | pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { | ||
| 40 | ser::trusted_header_id(b"tree", &self.tree, &mut out)?; | ||
| 41 | for parent in &self.parents { | ||
| 42 | ser::trusted_header_id(b"parent", parent, &mut out)?; | ||
| 43 | } | ||
| 44 | ser::trusted_header_signature(b"author", &self.author, &mut out)?; | ||
| 45 | ser::trusted_header_signature(b"committer", &self.committer, &mut out)?; | ||
| 46 | if let Some(encoding) = self.encoding.as_ref() { | ||
| 47 | ser::header_field(b"encoding", encoding, &mut out)?; | ||
| 48 | } | ||
| 49 | for (name, value) in &self.extra_headers { | ||
| 50 | let has_newline = value.find_byte(b'\n').is_some(); | ||
| 51 | if has_newline { | ||
| 52 | ser::header_field_multi_line(name, value, &mut out)?; | ||
| 53 | } else { | ||
| 54 | ser::trusted_header_field(name, value, &mut out)?; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | out.write_all(NL)?; | ||
| 58 | out.write_all(&self.message) | ||
| 59 | } | ||
| 60 | } | ||
| @@ -0,0 +1,101 @@ | |||
| 1 | use crate::{immutable, mutable}; | ||
| 2 | |||
| 3 | impl From<immutable::Signature<'_>> for mutable::Signature { | ||
| 4 | fn from(other: immutable::Signature<'_>) -> mutable::Signature { | ||
| 5 | let immutable::Signature { name, email, time } = other; | ||
| 6 | mutable::Signature { | ||
| 7 | name: name.to_owned(), | ||
| 8 | email: email.to_owned(), | ||
| 9 | time, | ||
| 10 | } | ||
| 11 | } | ||
| 12 | } | ||
| 13 | |||
| 14 | impl From<immutable::Tag<'_>> for mutable::Tag { | ||
| 15 | fn from(other: immutable::Tag<'_>) -> mutable::Tag { | ||
| 16 | let immutable::Tag { | ||
| 17 | target, | ||
| 18 | name, | ||
| 19 | target_kind, | ||
| 20 | message, | ||
| 21 | signature, | ||
| 22 | pgp_signature, | ||
| 23 | } = other; | ||
| 24 | mutable::Tag { | ||
| 25 | target: git_hash::ObjectId::from_hex(&target).expect("40 bytes hex sha1"), | ||
| 26 | name: name.to_owned(), | ||
| 27 | target_kind, | ||
| 28 | message: message.to_owned(), | ||
| 29 | signature: signature.map(Into::into), | ||
| 30 | pgp_signature: pgp_signature.map(ToOwned::to_owned), | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | impl From<immutable::Commit<'_>> for mutable::Commit { | ||
| 36 | fn from(other: immutable::Commit<'_>) -> mutable::Commit { | ||
| 37 | let immutable::Commit { | ||
| 38 | tree, | ||
| 39 | parents, | ||
| 40 | author, | ||
| 41 | committer, | ||
| 42 | encoding, | ||
| 43 | message, | ||
| 44 | extra_headers, | ||
| 45 | } = other; | ||
| 46 | mutable::Commit { | ||
| 47 | tree: git_hash::ObjectId::from_hex(&tree).expect("40 bytes hex sha1"), | ||
| 48 | parents: parents | ||
| 49 | .iter() | ||
| 50 | .map(|parent| git_hash::ObjectId::from_hex(parent).expect("40 bytes hex sha1")) | ||
| 51 | .collect(), | ||
| 52 | author: author.into(), | ||
| 53 | committer: committer.into(), | ||
| 54 | encoding: encoding.map(ToOwned::to_owned), | ||
| 55 | message: message.to_owned(), | ||
| 56 | extra_headers: extra_headers | ||
| 57 | .into_iter() | ||
| 58 | .map(|(k, v)| (k.into(), v.into_owned())) | ||
| 59 | .collect(), | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | impl<'a> From<immutable::Blob<'a>> for mutable::Blob { | ||
| 65 | fn from(v: immutable::Blob<'a>) -> Self { | ||
| 66 | mutable::Blob { | ||
| 67 | data: v.data.to_owned(), | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | impl From<immutable::Tree<'_>> for mutable::Tree { | ||
| 73 | fn from(other: immutable::Tree<'_>) -> mutable::Tree { | ||
| 74 | let immutable::Tree { entries } = other; | ||
| 75 | mutable::Tree { | ||
| 76 | entries: entries.into_iter().map(Into::into).collect(), | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | impl From<immutable::tree::Entry<'_>> for mutable::tree::Entry { | ||
| 82 | fn from(other: immutable::tree::Entry<'_>) -> mutable::tree::Entry { | ||
| 83 | let immutable::tree::Entry { mode, filename, oid } = other; | ||
| 84 | mutable::tree::Entry { | ||
| 85 | mode, | ||
| 86 | filename: filename.to_owned(), | ||
| 87 | oid: oid.into(), | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | impl<'a> From<immutable::Object<'a>> for mutable::Object { | ||
| 93 | fn from(v: immutable::Object<'_>) -> Self { | ||
| 94 | match v { | ||
| 95 | immutable::Object::Tree(v) => mutable::Object::Tree(v.into()), | ||
| 96 | immutable::Object::Blob(v) => mutable::Object::Blob(v.into()), | ||
| 97 | immutable::Object::Commit(v) => mutable::Object::Commit(v.into()), | ||
| 98 | immutable::Object::Tag(v) => mutable::Object::Tag(v.into()), | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | //! Mutable objects with each field being separately allocated and changeable. | ||
| 2 | //! | ||
| 3 | //! Mutable objects are Commits, Trees, Blobs and Tags that can be changed and serialized. | ||
| 4 | //! They either created using object [construction][Object] or by [deserializing existing objects][crate::immutable::Object::from_bytes()] | ||
| 5 | //! and converting these [into mutable copies][crate::immutable::Object::into_mutable()] for adjustments. | ||
| 6 | |||
| 7 | pub(crate) const NL: &[u8; 1] = b"\n"; | ||
| 8 | pub(crate) const SPACE: &[u8; 1] = b" "; | ||
| 9 | |||
| 10 | mod convert; | ||
| 11 | mod ser; | ||
| 12 | |||
| 13 | mod tag; | ||
| 14 | pub use tag::Tag; | ||
| 15 | |||
| 16 | /// | ||
| 17 | pub mod tree; | ||
| 18 | pub use tree::Tree; | ||
| 19 | |||
| 20 | mod commit; | ||
| 21 | pub use commit::Commit; | ||
| 22 | |||
| 23 | mod blob { | ||
| 24 | use std::io; | ||
| 25 | |||
| 26 | /// A mutable chunk of any [`data`][Blob::data]. | ||
| 27 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 28 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 29 | pub struct Blob { | ||
| 30 | /// The data itself. | ||
| 31 | pub data: Vec<u8>, | ||
| 32 | } | ||
| 33 | |||
| 34 | impl Blob { | ||
| 35 | /// Write the blobs data to `out` verbatim. | ||
| 36 | pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { | ||
| 37 | out.write_all(&self.data) | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | pub use blob::Blob; | ||
| 42 | |||
| 43 | /// | ||
| 44 | pub mod signature; | ||
| 45 | #[doc(inline)] | ||
| 46 | pub use signature::Signature; | ||
| 47 | |||
| 48 | mod object; | ||
| 49 | pub use object::Object; | ||
| @@ -0,0 +1,141 @@ | |||
| 1 | use crate::mutable; | ||
| 2 | use std::io; | ||
| 3 | |||
| 4 | /// A mutable object representing [`Trees`][mutable::Tree], [`Blobs`][mutable::Blob], [`Commits`][mutable::Commit] or [`Tags`][mutable::Tag]. | ||
| 5 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 6 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 7 | #[allow(clippy::large_enum_variant, missing_docs)] | ||
| 8 | pub enum Object { | ||
| 9 | Tree(mutable::Tree), | ||
| 10 | Blob(mutable::Blob), | ||
| 11 | Commit(mutable::Commit), | ||
| 12 | Tag(mutable::Tag), | ||
| 13 | } | ||
| 14 | |||
| 15 | /// Convenient extraction of typed object. | ||
| 16 | impl Object { | ||
| 17 | /// Returns a [`Blob`][mutable::Blob] if it is one. | ||
| 18 | pub fn as_blob(&self) -> Option<&mutable::Blob> { | ||
| 19 | match self { | ||
| 20 | Object::Blob(v) => Some(v), | ||
| 21 | _ => None, | ||
| 22 | } | ||
| 23 | } | ||
| 24 | /// Returns a [`Commit`][mutable::Commit] if it is one. | ||
| 25 | pub fn as_commit(&self) -> Option<&mutable::Commit> { | ||
| 26 | match self { | ||
| 27 | Object::Commit(v) => Some(v), | ||
| 28 | _ => None, | ||
| 29 | } | ||
| 30 | } | ||
| 31 | /// Returns a [`Tree`][mutable::Tree] if it is one. | ||
| 32 | pub fn as_tree(&self) -> Option<&mutable::Tree> { | ||
| 33 | match self { | ||
| 34 | Object::Tree(v) => Some(v), | ||
| 35 | _ => None, | ||
| 36 | } | ||
| 37 | } | ||
| 38 | /// Returns a [`Tag`][mutable::Tag] if it is one. | ||
| 39 | pub fn as_tag(&self) -> Option<&mutable::Tag> { | ||
| 40 | match self { | ||
| 41 | Object::Tag(v) => Some(v), | ||
| 42 | _ => None, | ||
| 43 | } | ||
| 44 | } | ||
| 45 | /// Returns the kind of object stored in this instance. | ||
| 46 | pub fn kind(&self) -> crate::Kind { | ||
| 47 | match self { | ||
| 48 | Object::Tree(_) => crate::Kind::Tree, | ||
| 49 | Object::Blob(_) => crate::Kind::Blob, | ||
| 50 | Object::Commit(_) => crate::Kind::Commit, | ||
| 51 | Object::Tag(_) => crate::Kind::Tag, | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Serialization | ||
| 57 | impl Object { | ||
| 58 | /// Write the contained object to `out` in the git serialization format. | ||
| 59 | pub fn write_to(&self, out: impl io::Write) -> io::Result<()> { | ||
| 60 | use Object::*; | ||
| 61 | match self { | ||
| 62 | Tree(v) => v.write_to(out), | ||
| 63 | Blob(v) => v.write_to(out), | ||
| 64 | Commit(v) => v.write_to(out), | ||
| 65 | Tag(v) => v.write_to(out), | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | mod convert { | ||
| 71 | use crate::mutable::{Blob, Commit, Object, Tag, Tree}; | ||
| 72 | use std::convert::TryFrom; | ||
| 73 | |||
| 74 | impl From<Tag> for Object { | ||
| 75 | fn from(v: Tag) -> Self { | ||
| 76 | Object::Tag(v) | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | impl From<Commit> for Object { | ||
| 81 | fn from(v: Commit) -> Self { | ||
| 82 | Object::Commit(v) | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | impl From<Tree> for Object { | ||
| 87 | fn from(v: Tree) -> Self { | ||
| 88 | Object::Tree(v) | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | impl From<Blob> for Object { | ||
| 93 | fn from(v: Blob) -> Self { | ||
| 94 | Object::Blob(v) | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | impl TryFrom<Object> for Tag { | ||
| 99 | type Error = Object; | ||
| 100 | |||
| 101 | fn try_from(value: Object) -> Result<Self, Self::Error> { | ||
| 102 | Ok(match value { | ||
| 103 | Object::Tag(v) => v, | ||
| 104 | _ => return Err(value), | ||
| 105 | }) | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | impl TryFrom<Object> for Commit { | ||
| 110 | type Error = Object; | ||
| 111 | |||
| 112 | fn try_from(value: Object) -> Result<Self, Self::Error> { | ||
| 113 | Ok(match value { | ||
| 114 | Object::Commit(v) => v, | ||
| 115 | _ => return Err(value), | ||
| 116 | }) | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | impl TryFrom<Object> for Tree { | ||
| 121 | type Error = Object; | ||
| 122 | |||
| 123 | fn try_from(value: Object) -> Result<Self, Self::Error> { | ||
| 124 | Ok(match value { | ||
| 125 | Object::Tree(v) => v, | ||
| 126 | _ => return Err(value), | ||
| 127 | }) | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | impl TryFrom<Object> for Blob { | ||
| 132 | type Error = Object; | ||
| 133 | |||
| 134 | fn try_from(value: Object) -> Result<Self, Self::Error> { | ||
| 135 | Ok(match value { | ||
| 136 | Object::Blob(v) => v, | ||
| 137 | _ => return Err(value), | ||
| 138 | }) | ||
| 139 | } | ||
| 140 | } | ||
| 141 | } | ||
| @@ -0,0 +1,71 @@ | |||
| 1 | use crate::mutable::{self, NL, SPACE}; | ||
| 2 | use bstr::{BString, ByteSlice}; | ||
| 3 | use quick_error::quick_error; | ||
| 4 | use std::io; | ||
| 5 | |||
| 6 | quick_error! { | ||
| 7 | #[derive(Debug)] | ||
| 8 | enum Error { | ||
| 9 | NewlineInHeaderValue(value: BString) { | ||
| 10 | display("Newlines are not allowed in header values: {:?}", value) | ||
| 11 | } | ||
| 12 | EmptyValue { | ||
| 13 | display("Header values must not be empty") | ||
| 14 | } | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | impl From<Error> for io::Error { | ||
| 19 | fn from(other: Error) -> io::Error { | ||
| 20 | io::Error::new(io::ErrorKind::Other, other) | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | pub fn header_field_multi_line(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> { | ||
| 25 | let mut lines = value.as_bstr().lines(); | ||
| 26 | trusted_header_field(name, lines.next().expect("non-empty value"), &mut out)?; | ||
| 27 | for line in lines { | ||
| 28 | out.write_all(SPACE)?; | ||
| 29 | out.write_all(line)?; | ||
| 30 | out.write_all(NL)?; | ||
| 31 | } | ||
| 32 | // If the last line ended on a newline, we have to re-add it by force | ||
| 33 | if let Some(b) = value.last() { | ||
| 34 | if *b == b'\n' { | ||
| 35 | out.write_all(SPACE)?; | ||
| 36 | out.write_all(NL)?; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | Ok(()) | ||
| 40 | } | ||
| 41 | |||
| 42 | pub fn trusted_header_field(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> { | ||
| 43 | out.write_all(name)?; | ||
| 44 | out.write_all(SPACE)?; | ||
| 45 | out.write_all(value)?; | ||
| 46 | out.write_all(NL) | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn trusted_header_signature(name: &[u8], value: &mutable::Signature, mut out: impl io::Write) -> io::Result<()> { | ||
| 50 | out.write_all(name)?; | ||
| 51 | out.write_all(SPACE)?; | ||
| 52 | value.write_to(&mut out)?; | ||
| 53 | out.write_all(NL) | ||
| 54 | } | ||
| 55 | |||
| 56 | pub fn trusted_header_id(name: &[u8], value: &git_hash::ObjectId, mut out: impl io::Write) -> io::Result<()> { | ||
| 57 | out.write_all(name)?; | ||
| 58 | out.write_all(&SPACE[..])?; | ||
| 59 | value.write_hex_to(&mut out)?; | ||
| 60 | out.write_all(&NL[..]) | ||
| 61 | } | ||
| 62 | |||
| 63 | pub fn header_field(name: &[u8], value: &[u8], out: impl io::Write) -> io::Result<()> { | ||
| 64 | if value.is_empty() { | ||
| 65 | return Err(Error::EmptyValue.into()); | ||
| 66 | } | ||
| 67 | if value.find(NL).is_some() { | ||
| 68 | return Err(Error::NewlineInHeaderValue(value.into()).into()); | ||
| 69 | } | ||
| 70 | trusted_header_field(name, value, out) | ||
| 71 | } | ||
| @@ -0,0 +1,55 @@ | |||
| 1 | use crate::{mutable::SPACE, Time}; | ||
| 2 | use bstr::{BStr, BString, ByteSlice}; | ||
| 3 | use quick_error::quick_error; | ||
| 4 | use std::io; | ||
| 5 | |||
| 6 | /// A mutable signature is created by an actor at a certain time. | ||
| 7 | /// | ||
| 8 | /// Note that this is not a cryptographical signature. | ||
| 9 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 10 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 11 | pub struct Signature { | ||
| 12 | /// The actors name. | ||
| 13 | pub name: BString, | ||
| 14 | /// The actor's email. | ||
| 15 | pub email: BString, | ||
| 16 | /// The time stamp at which the signature is performed. | ||
| 17 | pub time: Time, | ||
| 18 | } | ||
| 19 | |||
| 20 | quick_error! { | ||
| 21 | /// The Error produced in by [`Signature::write_to()`]. | ||
| 22 | #[derive(Debug)] | ||
| 23 | #[allow(missing_docs)] | ||
| 24 | pub enum Error { | ||
| 25 | IllegalCharacter { | ||
| 26 | display("Signature name or email must not contain '<', '>' or \\n") | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | impl From<Error> for io::Error { | ||
| 32 | fn from(err: Error) -> Self { | ||
| 33 | io::Error::new(io::ErrorKind::Other, err) | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | impl Signature { | ||
| 38 | /// Serialize this instance to `out` in the git serialization format for actors. | ||
| 39 | pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { | ||
| 40 | out.write_all(validated_token(self.name.as_bstr())?)?; | ||
| 41 | out.write_all(SPACE)?; | ||
| 42 | out.write_all(&b"<"[..])?; | ||
| 43 | out.write_all(validated_token(self.email.as_bstr())?)?; | ||
| 44 | out.write_all(&b"> "[..])?; | ||
| 45 | self.time.write_to(out)?; | ||
| 46 | Ok(()) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | fn validated_token(name: &BStr) -> Result<&BStr, Error> { | ||
| 51 | if name.find_byteset(b"<>\n").is_some() { | ||
| 52 | return Err(Error::IllegalCharacter); | ||
| 53 | } | ||
| 54 | Ok(name) | ||
| 55 | } | ||
| @@ -0,0 +1,76 @@ | |||
| 1 | use crate::mutable::{self, ser, NL}; | ||
| 2 | use bstr::{BStr, BString}; | ||
| 3 | use quick_error::quick_error; | ||
| 4 | use std::io; | ||
| 5 | |||
| 6 | quick_error! { | ||
| 7 | /// An Error used in [`Tag::write_to()`]. | ||
| 8 | #[derive(Debug)] | ||
| 9 | pub enum Error { | ||
| 10 | StartsWithDash { | ||
| 11 | display("Tags must not start with a dash: '-'") | ||
| 12 | } | ||
| 13 | InvalidRefName(err: git_ref::validated::NameError) { | ||
| 14 | display("The tag name was no valid reference name") | ||
| 15 | from() | ||
| 16 | source(err) | ||
| 17 | } | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | impl From<Error> for io::Error { | ||
| 22 | fn from(err: Error) -> Self { | ||
| 23 | io::Error::new(io::ErrorKind::Other, err) | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | /// A mutable git tag. | ||
| 28 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 29 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 30 | pub struct Tag { | ||
| 31 | /// The hash this tag is pointing to. | ||
| 32 | pub target: git_hash::ObjectId, | ||
| 33 | /// The kind of object this tag is pointing to. | ||
| 34 | pub target_kind: crate::Kind, | ||
| 35 | /// The name of the tag, e.g. "v1.0". | ||
| 36 | pub name: BString, | ||
| 37 | /// The message describing the tag. | ||
| 38 | pub message: BString, | ||
| 39 | /// The tags author. | ||
| 40 | pub signature: Option<mutable::Signature>, | ||
| 41 | /// A pgp signature over all bytes of the encoded tag, excluding the pgp signature itself. | ||
| 42 | pub pgp_signature: Option<BString>, | ||
| 43 | } | ||
| 44 | |||
| 45 | impl Tag { | ||
| 46 | /// Writes the encoded tag to `out`. | ||
| 47 | pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { | ||
| 48 | ser::trusted_header_id(b"object", &self.target, &mut out)?; | ||
| 49 | ser::trusted_header_field(b"type", self.target_kind.to_bytes(), &mut out)?; | ||
| 50 | ser::header_field(b"tag", validated_name(self.name.as_ref())?, &mut out)?; | ||
| 51 | if let Some(tagger) = &self.signature { | ||
| 52 | ser::trusted_header_signature(b"tagger", tagger, &mut out)?; | ||
| 53 | } | ||
| 54 | |||
| 55 | if !self.message.is_empty() { | ||
| 56 | out.write_all(NL)?; | ||
| 57 | out.write_all(&self.message)?; | ||
| 58 | } | ||
| 59 | if let Some(ref message) = self.pgp_signature { | ||
| 60 | out.write_all(NL)?; | ||
| 61 | out.write_all(&message)?; | ||
| 62 | } | ||
| 63 | Ok(()) | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | fn validated_name(name: &BStr) -> Result<&BStr, Error> { | ||
| 68 | git_ref::validated::name(name)?; | ||
| 69 | if name[0] == b'-' { | ||
| 70 | return Err(Error::StartsWithDash); | ||
| 71 | } | ||
| 72 | Ok(name) | ||
| 73 | } | ||
| 74 | |||
| 75 | #[cfg(test)] | ||
| 76 | mod tests; | ||
| @@ -0,0 +1,27 @@ | |||
| 1 | mod validated_name { | ||
| 2 | mod invalid { | ||
| 3 | use super::super::super::*; | ||
| 4 | use bstr::ByteSlice; | ||
| 5 | |||
| 6 | #[test] | ||
| 7 | fn only_dash() { | ||
| 8 | assert!(validated_name(b"-".as_bstr()).is_err()) | ||
| 9 | } | ||
| 10 | #[test] | ||
| 11 | fn leading_dash() { | ||
| 12 | assert!(validated_name(b"-hello".as_bstr()).is_err()) | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | mod valid { | ||
| 17 | use super::super::super::*; | ||
| 18 | use bstr::ByteSlice; | ||
| 19 | |||
| 20 | #[test] | ||
| 21 | fn version() { | ||
| 22 | for version in &["v1.0.0", "0.2.1", "0-alpha1"] { | ||
| 23 | assert!(validated_name(version.as_bytes().as_bstr()).is_ok()) | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| @@ -0,0 +1,76 @@ | |||
| 1 | use crate::{mutable::SPACE, tree::Mode}; | ||
| 2 | use bstr::{BString, ByteSlice}; | ||
| 3 | use quick_error::quick_error; | ||
| 4 | use std::io; | ||
| 5 | |||
| 6 | quick_error! { | ||
| 7 | /// The Error used in [`Tree::write_to()`]. | ||
| 8 | #[derive(Debug)] | ||
| 9 | #[allow(missing_docs)] | ||
| 10 | pub enum Error { | ||
| 11 | NewlineInFilename(name: BString) { | ||
| 12 | display("Newlines are invalid in file paths: {:?}", name) | ||
| 13 | } | ||
| 14 | } | ||
| 15 | } | ||
| 16 | |||
| 17 | impl From<Error> for io::Error { | ||
| 18 | fn from(err: Error) -> Self { | ||
| 19 | io::Error::new(io::ErrorKind::Other, err) | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | /// A mutable Tree, containing other trees, blobs or commits. | ||
| 24 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 25 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 26 | pub struct Tree { | ||
| 27 | /// The directories and files contained in this tree. | ||
| 28 | pub entries: Vec<Entry>, | ||
| 29 | } | ||
| 30 | |||
| 31 | /// An entry in a [`Tree`], similar to an entry in a directory. | ||
| 32 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 33 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 34 | pub struct Entry { | ||
| 35 | /// The kind of object to which `oid` is pointing to. | ||
| 36 | pub mode: Mode, | ||
| 37 | /// The name of the file in the parent tree. | ||
| 38 | pub filename: BString, | ||
| 39 | /// The id of the object representing the entry. | ||
| 40 | pub oid: git_hash::ObjectId, | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Serialization | ||
| 44 | impl Mode { | ||
| 45 | /// Return the representation as used in the git internal format. | ||
| 46 | pub fn as_bytes(&self) -> &'static [u8] { | ||
| 47 | use Mode::*; | ||
| 48 | match self { | ||
| 49 | Tree => b"40000", | ||
| 50 | Blob => b"100644", | ||
| 51 | BlobExecutable => b"100755", | ||
| 52 | Link => b"120000", | ||
| 53 | Commit => b"160000", | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Serialization | ||
| 59 | impl Tree { | ||
| 60 | /// Serialize this tree to `out` in the git internal format. | ||
| 61 | pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { | ||
| 62 | for Entry { mode, filename, oid } in &self.entries { | ||
| 63 | out.write_all(mode.as_bytes())?; | ||
| 64 | out.write_all(SPACE)?; | ||
| 65 | |||
| 66 | if filename.find_byte(b'\n').is_some() { | ||
| 67 | return Err(Error::NewlineInFilename(filename.to_owned()).into()); | ||
| 68 | } | ||
| 69 | out.write_all(&filename)?; | ||
| 70 | out.write_all(&[b'\0'])?; | ||
| 71 | |||
| 72 | out.write_all(oid.as_bytes())?; | ||
| 73 | } | ||
| 74 | Ok(()) | ||
| 75 | } | ||
| 76 | } | ||