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 @@
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+}
git-object/src/mutable/convert.rs+101−0
@@ -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+}
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+
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;
git-object/src/mutable/object.rs+141−0
@@ -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+}
git-object/src/mutable/ser.rs+71−0
@@ -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+}
git-object/src/mutable/signature.rs+55−0
@@ -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+}
git-object/src/mutable/tag.rs+76−0
@@ -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;
git-object/src/mutable/tag/tests.rs+27−0
@@ -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+}
git-object/src/mutable/tree.rs+76−0
@@ -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+}