Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions git-actor/src/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,25 @@ mod write {
pub fn write_to(&self, out: impl io::Write) -> io::Result<()> {
self.to_ref().write_to(out)
}
/// Computes the number of bytes necessary to serialize this signature
pub fn size(&self) -> usize {
self.to_ref().size()
}
}

impl<'a> SignatureRef<'a> {
/// Serialize this instance to `out` in the git serialization format for actors.
pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
out.write_all(validated_token(self.name)?)?;
out.write_all(SPACE)?;
out.write_all(&b"<"[..])?;
out.write_all(b"<")?;
out.write_all(validated_token(self.email)?)?;
out.write_all(&b"> "[..])?;
self.time.write_to(out)?;
Ok(())
out.write_all(b"> ")?;
self.time.write_to(out)
}
/// Computes the number of bytes necessary to serialize this signature
pub fn size(&self) -> usize {
self.name.len() + self.email.len() + self.time.size() + 4
}
}

Expand Down
35 changes: 31 additions & 4 deletions git-actor/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ impl Time {
pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
itoa::write(&mut out, self.time)?;
out.write_all(SPACE)?;
out.write_all(&[match self.sign {
Sign::Plus => b'+',
Sign::Minus => b'-',
}])?;
out.write_all(match self.sign {
Sign::Plus => b"+",
Sign::Minus => b"-",
})?;

const ZERO: &[u8; 1] = b"0";

Expand All @@ -40,4 +40,31 @@ impl Time {
}
itoa::write(&mut out, minutes).map(|_| ())
}
/// Computes the number of bytes necessary to render this time
pub fn size(&self) -> usize {
// TODO: this is not year 2038 safe...
(if self.time >= 1_000_000_000 {
10
} else if self.time >= 100_000_000 {
9
} else if self.time >= 10_000_000 {
8
} else if self.time >= 1_000_000 {
7
} else if self.time >= 100_000 {
6
} else if self.time >= 10_000 {
5
} else if self.time >= 1_000 {
4
} else if self.time >= 100 {
3
} else if self.time >= 10 {
2
} else {
1
}) + 2
+ 2
+ 2
}
}
4 changes: 3 additions & 1 deletion git-object/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ git-hash = { version ="^0.8.0", path = "../git-hash" }
git-validate = { version ="^0.5.3", path = "../git-validate" }
git-actor = { version ="^0.6.0", path = "../git-actor" }

btoi = "0.4.2"
itoa = "0.4.6"
quick-error = "2.0.0"
hex = "0.4.2"
bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] }
nom = { version = "7", default-features = false, features = ["std"]}
smallvec = "1.4.0"
smallvec = { version = "1.4.0", features = ["write"] }
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}

[dev-dependencies]
Expand Down
8 changes: 8 additions & 0 deletions git-object/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ impl<'a> crate::WriteTo for BlobRef<'a> {
out.write_all(self.data)
}

fn size(&self) -> usize {
self.data.len()
}

fn kind(&self) -> Kind {
Kind::Blob
}
Expand All @@ -19,6 +23,10 @@ impl crate::WriteTo for Blob {
self.to_ref().write_to(out)
}

fn size(&self) -> usize {
self.to_ref().size()
}

fn kind(&self) -> Kind {
Kind::Blob
}
Expand Down
65 changes: 65 additions & 0 deletions git-object/src/commit/write.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bstr::ByteSlice;
use std::io;

use crate::{encode, encode::NL, Commit, CommitRef, Kind};
Expand All @@ -21,6 +22,38 @@ impl crate::WriteTo for Commit {
out.write_all(&self.message)
}

fn size(&self) -> usize {
let hashsize = self.tree.kind().len_in_hex();
b"tree".len()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[rustfmt::skip] would help to have more specialized formatting without cargo fmt exploding it into newlines.

However, I'd really love to see what the number literals are there for, to help maintenance and be a learning experience at the same time as it's basically a description of the serialized format then :).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, the numeric constants are really just the literal characters (spaces and newlines mostly) inserted by the trusted_* functions so e.g. trusted_header_id(key, value) is key <space> hex <newline> hence key.len() + 1 + len_in_hex + 1.

But I think I'll update this to add e.g. #[inline] encode::trusted_header_id_size(key, value) -> usize that way the non-trivial size implementations of Commit and Tag should be much easier to relate to the write_to implementation. I waffled a bit on doing it before pushing, but after your comments and the mess rustfmt makes of it it seems like a cleaner solution than rustfmt::skip.

+ 1
+ hashsize
+ 1
+ self.parents.iter().count() * (b"parent".len() + 1 + hashsize + 1)
+ b"author".len()
+ 1
+ self.author.size()
+ 1
+ b"committer".len()
+ 1
+ self.committer.size()
+ 1
+ self
.encoding
.as_ref()
.map(|e| b"encoding".len() + 1 + e.len() + 1)
.unwrap_or(0)
+ self
.extra_headers
.iter()
.map(|(name, value)| {
// each header *value* is preceded by a space and followed by a newline
name.len() + value.split_str("\n").map(|s| s.len() + 2).sum::<usize>()
})
.sum::<usize>()
+ 1
+ self.message.len()
}

fn kind(&self) -> Kind {
Kind::Commit
}
Expand All @@ -45,6 +78,38 @@ impl<'a> crate::WriteTo for CommitRef<'a> {
out.write_all(self.message)
}

fn size(&self) -> usize {
let hashsize = self.tree().kind().len_in_hex();
b"tree".len()
+ 1
+ hashsize
+ 1
+ self.parents.iter().count() * (b"parent".len() + 1 + hashsize + 1)
+ b"author".len()
+ 1
+ self.author.size()
+ 1
+ b"committer".len()
+ 1
+ self.committer.size()
+ 1
+ self
.encoding
.as_ref()
.map(|e| b"encoding".len() + 1 + e.len() + 1)
.unwrap_or(0)
+ self
.extra_headers
.iter()
.map(|(name, value)| {
// each header *value* is preceded by a space and followed by a newline
name.len() + value.split_str("\n").map(|s| s.len() + 2).sum::<usize>()
})
.sum::<usize>()
+ 1
+ self.message.len()
}

fn kind(&self) -> Kind {
Kind::Commit
}
Expand Down
32 changes: 24 additions & 8 deletions git-object/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io;
//! Encoding utilities
use std::io::{self, Write};

use bstr::{BString, ByteSlice};
use quick_error::quick_error;
Expand All @@ -15,13 +16,28 @@ quick_error! {
}
}

macro_rules! check {
($e: expr) => {
$e.expect("Writing to a Vec should never fail.")
};
}
/// Generates a loose header buffer
pub fn loose_header(kind: crate::Kind, size: usize) -> smallvec::SmallVec<[u8; 28]> {
let mut v = smallvec::SmallVec::new();
check!(v.write_all(kind.as_bytes()));
check!(v.write_all(SPACE));
check!(itoa::write(&mut v, size));
check!(v.write_all(b"\0"));
v
}

impl From<Error> for io::Error {
fn from(other: Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, other)
}
}

pub fn header_field_multi_line(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> {
pub(crate) fn header_field_multi_line(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> {
let mut lines = value.as_bstr().split_str(b"\n");
trusted_header_field(name, lines.next().ok_or(Error::EmptyValue)?, &mut out)?;
for line in lines {
Expand All @@ -32,14 +48,14 @@ pub fn header_field_multi_line(name: &[u8], value: &[u8], mut out: impl io::Writ
Ok(())
}

pub fn trusted_header_field(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> {
pub(crate) fn trusted_header_field(name: &[u8], value: &[u8], mut out: impl io::Write) -> io::Result<()> {
out.write_all(name)?;
out.write_all(SPACE)?;
out.write_all(value)?;
out.write_all(NL)
}

pub fn trusted_header_signature(
pub(crate) fn trusted_header_signature(
name: &[u8],
value: &git_actor::SignatureRef<'_>,
mut out: impl io::Write,
Expand All @@ -50,14 +66,14 @@ pub fn trusted_header_signature(
out.write_all(NL)
}

pub fn trusted_header_id(name: &[u8], value: &git_hash::ObjectId, mut out: impl io::Write) -> io::Result<()> {
pub(crate) fn trusted_header_id(name: &[u8], value: &git_hash::ObjectId, mut out: impl io::Write) -> io::Result<()> {
out.write_all(name)?;
out.write_all(&SPACE[..])?;
out.write_all(SPACE)?;
value.write_hex_to(&mut out)?;
out.write_all(&NL[..])
out.write_all(NL)
}

pub fn header_field(name: &[u8], value: &[u8], out: impl io::Write) -> io::Result<()> {
pub(crate) fn header_field(name: &[u8], value: &[u8], out: impl io::Write) -> io::Result<()> {
if value.is_empty() {
return Err(Error::EmptyValue.into());
}
Expand Down
Loading