History

rename git-object::(owned->mutable)|(borrowed|immutable) #(67)

This categorizes them semantically without using the overloaded names of owning and borrowing, which mean different things in Rust.

Sebastian Thiel committed 5 years ago

Commit 91ee5581 parent cd7356034 files · +149 −141Browse files

Showing the changes to git-object/src/mutable (9 of 34 files). Show all

Unified · Split

9 files
  1. git-object/src/mutable/commit.rs +60 −0
  2. git-object/src/mutable/convert.rs +101 −0
  3. git-object/src/mutable/mod.rs +49 −0
  4. git-object/src/mutable/object.rs +141 −0
  5. git-object/src/mutable/ser.rs +71 −0
  6. git-object/src/mutable/signature.rs +55 −0
  7. git-object/src/mutable/tag.rs +76 −0
  8. git-object/src/mutable/tag/tests.rs +27 −0
  9. git-object/src/mutable/tree.rs +76 −0
git-object/src/mutable/commit.rs+60−0
@@ -0,0 +1,60 @@
1use crate::{
2 commit,
3 mutable::{self, ser, NL},
4};
5use bstr::{BStr, BString, ByteSlice};
6use smallvec::SmallVec;
7use 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))]
12pub 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
33impl 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}
git-object/src/mutable/convert.rs+101−0
@@ -0,0 +1,101 @@
1use crate::{immutable, mutable};
2
3impl 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
14impl 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
35impl 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
64impl<'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
72impl 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
81impl 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
92impl<'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}
git-object/src/mutable/mod.rs+49−0
@@ -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
7pub(crate) const NL: &[u8; 1] = b"\n";
8pub(crate) const SPACE: &[u8; 1] = b" ";
9
10mod convert;
11mod ser;
12
13mod tag;
14pub use tag::Tag;
15
16///
17pub mod tree;
18pub use tree::Tree;
19
20mod commit;
21pub use commit::Commit;
22
23mod 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}
41pub use blob::Blob;
42
43///
44pub mod signature;
45#[doc(inline)]
46pub use signature::Signature;
47
48mod object;
49pub use object::Object;
git-object/src/mutable/object.rs+141−0
@@ -0,0 +1,141 @@
1use crate::mutable;
2use 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)]
8pub 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.
16impl 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
57impl 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
70mod 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}
git-object/src/mutable/ser.rs+71−0
@@ -0,0 +1,71 @@
1use crate::mutable::{self, NL, SPACE};
2use bstr::{BString, ByteSlice};
3use quick_error::quick_error;
4use std::io;
5
6quick_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
18impl From<Error> for io::Error {
19 fn from(other: Error) -> io::Error {
20 io::Error::new(io::ErrorKind::Other, other)
21 }
22}
23
24pub 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
42pub 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
49pub 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
56pub 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
63pub 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}
git-object/src/mutable/signature.rs+55−0
@@ -0,0 +1,55 @@
1use crate::{mutable::SPACE, Time};
2use bstr::{BStr, BString, ByteSlice};
3use quick_error::quick_error;
4use 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))]
11pub 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
20quick_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
31impl From<Error> for io::Error {
32 fn from(err: Error) -> Self {
33 io::Error::new(io::ErrorKind::Other, err)
34 }
35}
36
37impl 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
50fn 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}
git-object/src/mutable/tag.rs+76−0
@@ -0,0 +1,76 @@
1use crate::mutable::{self, ser, NL};
2use bstr::{BStr, BString};
3use quick_error::quick_error;
4use std::io;
5
6quick_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
21impl 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))]
30pub 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
45impl 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
67fn 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)]
76mod tests;
git-object/src/mutable/tag/tests.rs+27−0
@@ -0,0 +1,27 @@
1mod 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}
git-object/src/mutable/tree.rs+76−0
@@ -0,0 +1,76 @@
1use crate::{mutable::SPACE, tree::Mode};
2use bstr::{BString, ByteSlice};
3use quick_error::quick_error;
4use std::io;
5
6quick_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
17impl 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))]
26pub 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))]
34pub 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
44impl 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
59impl 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}