Skip to content
Draft
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
57 changes: 57 additions & 0 deletions gix-object/src/traits/_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,28 @@ where
(*self).write_buf(object, from)
}

fn write_buf_with_known_id(
&self,
id: ObjectId,
object: Kind,
from: &[u8],
) -> Result<ObjectId, crate::write::Error> {
(*self).write_buf_with_known_id(id, object, from)
}

fn write_stream(&self, kind: Kind, size: u64, from: &mut dyn Read) -> Result<ObjectId, crate::write::Error> {
(*self).write_stream(kind, size, from)
}

fn write_stream_with_known_id(
&self,
id: ObjectId,
kind: Kind,
size: u64,
from: &mut dyn Read,
) -> Result<ObjectId, crate::write::Error> {
(*self).write_stream_with_known_id(id, kind, size, from)
}
}

impl<T> crate::Write for Arc<T>
Expand All @@ -33,9 +52,28 @@ where
self.deref().write_buf(object, from)
}

fn write_buf_with_known_id(
&self,
id: ObjectId,
object: Kind,
from: &[u8],
) -> Result<ObjectId, crate::write::Error> {
self.deref().write_buf_with_known_id(id, object, from)
}

fn write_stream(&self, kind: Kind, size: u64, from: &mut dyn Read) -> Result<ObjectId, crate::write::Error> {
self.deref().write_stream(kind, size, from)
}

fn write_stream_with_known_id(
&self,
id: ObjectId,
kind: Kind,
size: u64,
from: &mut dyn Read,
) -> Result<ObjectId, crate::write::Error> {
self.deref().write_stream_with_known_id(id, kind, size, from)
}
}

impl<T> crate::Write for Rc<T>
Expand All @@ -50,9 +88,28 @@ where
self.deref().write_buf(object, from)
}

fn write_buf_with_known_id(
&self,
id: ObjectId,
object: Kind,
from: &[u8],
) -> Result<ObjectId, crate::write::Error> {
self.deref().write_buf_with_known_id(id, object, from)
}

fn write_stream(&self, kind: Kind, size: u64, from: &mut dyn Read) -> Result<ObjectId, crate::write::Error> {
self.deref().write_stream(kind, size, from)
}

fn write_stream_with_known_id(
&self,
id: ObjectId,
kind: Kind,
size: u64,
from: &mut dyn Read,
) -> Result<ObjectId, crate::write::Error> {
self.deref().write_stream_with_known_id(id, kind, size, from)
}
}

impl<T> WriteTo for &T
Expand Down
27 changes: 27 additions & 0 deletions gix-object/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ pub trait Write {
fn write_buf(&self, object: crate::Kind, mut from: &[u8]) -> Result<gix_hash::ObjectId, crate::write::Error> {
self.write_stream(object, from.len() as u64, &mut from)
}
/// As [`write_buf`](Write::write_buf), but the object id has already been computed by the caller.
///
/// Implementations may trust the given `id` and avoid computing it again. Callers must make sure `id` matches
/// the provided `object` and `from` bytes.
fn write_buf_with_known_id(
&self,
id: gix_hash::ObjectId,
object: crate::Kind,
from: &[u8],
) -> Result<gix_hash::ObjectId, crate::write::Error> {
let _ = id;
self.write_buf(object, from)
Comment on lines +28 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should this mirror how write_buf calls write_stream? i.e.:

Suggested change
let _ = id;
self.write_buf(object, from)
self.write_stream_with_known_id(id, object, from.len() as u64, &mut from)

It looks like places like cache.rs omit write_buf, so this kind of call might let them omit write_buf_with_known_id similarly (though looking at the loose/write.rs changes which do provide write_buf, perhaps cache.rs should actually be forwarding write_buf?)

}
/// As [`write`](Write::write), but takes an input stream.
/// This is commonly used for writing blobs directly without reading them to memory first.
fn write_stream(
Expand All @@ -23,6 +36,20 @@ pub trait Write {
size: u64,
from: &mut dyn io::Read,
) -> Result<gix_hash::ObjectId, crate::write::Error>;
/// As [`write_stream`](Write::write_stream), but the object id has already been computed by the caller.
///
/// Implementations may trust the given `id` and avoid computing it again. Callers must make sure `id` matches
/// the provided `kind`, `size` and stream contents.
fn write_stream_with_known_id(
&self,
id: gix_hash::ObjectId,
kind: crate::Kind,
size: u64,
from: &mut dyn io::Read,
) -> Result<gix_hash::ObjectId, crate::write::Error> {
let _ = id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it right to be discarding the id here (versus not providing a default implementation)? Maybe this is appropriate for backwards compatibility; if that's the intent, maybe worth a comment?

Suggested change
let _ = id;
// Provide a default implementation which discards the id.
let _ = id;

self.write_stream(kind, size, from)
}
}

/// Writing of objects to a `Write` implementation
Expand Down
19 changes: 19 additions & 0 deletions gix-odb/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,25 @@ mod impls {
) -> Result<ObjectId, gix_object::write::Error> {
self.inner.write_stream(kind, size, from)
}

fn write_buf_with_known_id(
&self,
id: ObjectId,
kind: Kind,
from: &[u8],
) -> Result<ObjectId, gix_object::write::Error> {
self.inner.write_buf_with_known_id(id, kind, from)
}

fn write_stream_with_known_id(
&self,
id: ObjectId,
kind: Kind,
size: u64,
from: &mut dyn Read,
) -> Result<ObjectId, gix_object::write::Error> {
self.inner.write_stream_with_known_id(id, kind, size, from)
}
}

impl<S> gix_object::Find for Cache<S>
Expand Down
32 changes: 32 additions & 0 deletions gix-odb/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,38 @@ where
map.borrow_mut().insert(id, (kind, buf));
Ok(id)
}

fn write_buf_with_known_id(
&self,
id: gix_hash::ObjectId,
kind: gix_object::Kind,
from: &[u8],
) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
let Some(map) = self.memory.as_ref() else {
return self.inner.write_buf_with_known_id(id, kind, from);
};

map.borrow_mut().insert(id, (kind, from.to_owned()));
Ok(id)
}

fn write_stream_with_known_id(
&self,
id: gix_hash::ObjectId,
kind: gix_object::Kind,
size: u64,
from: &mut dyn std::io::Read,
) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
let Some(map) = self.memory.as_ref() else {
return self.inner.write_stream_with_known_id(id, kind, size, from);
};

let mut buf = Vec::new();
from.read_to_end(&mut buf)?;

map.borrow_mut().insert(id, (kind, buf));
Ok(id)
}
}

impl<T> Deref for Proxy<T> {
Expand Down
43 changes: 43 additions & 0 deletions gix-odb/src/store_impls/dynamic/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,47 @@ where
}
})
}

fn write_buf_with_known_id(
&self,
id: ObjectId,
kind: Kind,
from: &[u8],
) -> Result<ObjectId, gix_object::write::Error> {
let mut snapshot = self.snapshot.borrow_mut();
Ok(match snapshot.loose_dbs.first() {
Some(ldb) => ldb.write_buf_with_known_id(id, kind, from)?,
None => {
let new_snapshot = self
.store
.load_one_index(self.refresh, snapshot.marker)
.map_err(Box::new)?
.expect("there is always at least one ODB, and this code runs only once for initialization");
*snapshot = new_snapshot;
snapshot.loose_dbs[0].write_buf_with_known_id(id, kind, from)?
}
})
}

fn write_stream_with_known_id(
&self,
id: ObjectId,
kind: Kind,
size: u64,
from: &mut dyn Read,
) -> Result<ObjectId, gix_object::write::Error> {
let mut snapshot = self.snapshot.borrow_mut();
Ok(match snapshot.loose_dbs.first() {
Some(ldb) => ldb.write_stream_with_known_id(id, kind, size, from)?,
None => {
let new_snapshot = self
.store
.load_one_index(self.refresh, snapshot.marker)
.map_err(Box::new)?
.expect("there is always at least one ODB, and this code runs only once for initialization");
*snapshot = new_snapshot;
snapshot.loose_dbs[0].write_stream_with_known_id(id, kind, size, from)?
}
})
}
}
67 changes: 62 additions & 5 deletions gix-odb/src/store_impls/loose/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ impl gix_object::Write for Store {
Ok(self.finalize_object(to)?)
}

fn write_buf_with_known_id(
&self,
id: gix_hash::ObjectId,
kind: gix_object::Kind,
from: &[u8],
) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
let mut to = self.compressed_tempfile().map_err(Box::new)?;
to.write_all(&gix_object::encode::loose_header(kind, from.len() as u64))
.map_err(|err| Error::Io {
source: err.into(),
message: "write header to tempfile in",
path: self.path.to_owned(),
})?;

to.write_all(from).map_err(|err| Error::Io {
source: err.into(),
message: "stream all data into tempfile in",
path: self.path.to_owned(),
})?;
to.flush()?;
Ok(self.finalize_object_at(id.as_ref(), to)?)
}

/// Write the given stream in `from` to disk with at least one syscall.
///
/// This will cost at least 4 IO operations.
Expand Down Expand Up @@ -91,6 +114,32 @@ impl gix_object::Write for Store {
to.flush().map_err(Box::new)?;
Ok(self.finalize_object(to)?)
}

fn write_stream_with_known_id(
&self,
id: gix_hash::ObjectId,
kind: gix_object::Kind,
size: u64,
mut from: &mut dyn io::Read,
) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
let mut to = self.compressed_tempfile().map_err(Box::new)?;
to.write_all(&gix_object::encode::loose_header(kind, size))
.map_err(|err| Error::Io {
source: err.into(),
message: "write header to tempfile in",
path: self.path.to_owned(),
})?;

io::copy(&mut from, &mut to)
.map_err(|err| Error::Io {
source: err.into(),
message: "stream all data into tempfile in",
path: self.path.to_owned(),
})
.map_err(Box::new)?;
to.flush().map_err(Box::new)?;
Ok(self.finalize_object_at(id.as_ref(), to)?)
}
}

type CompressedTempfile = deflate::Write<NamedTempFile>;
Expand All @@ -107,6 +156,10 @@ impl Store {

impl Store {
fn dest(&self) -> Result<gix_hash::io::Write<CompressedTempfile>, Error> {
Ok(gix_hash::io::Write::new(self.compressed_tempfile()?, self.object_hash))
}

fn compressed_tempfile(&self) -> Result<CompressedTempfile, Error> {
#[cfg_attr(not(unix), allow(unused_mut))]
let mut builder = tempfile::Builder::new();
#[cfg(unix)]
Expand All @@ -115,14 +168,13 @@ impl Store {
let perms = std::fs::Permissions::from_mode(0o444);
builder.permissions(perms);
}
Ok(gix_hash::io::Write::new(
deflate::Write::new(builder.tempfile_in(&self.path).map_err(|err| Error::Io {
Ok(deflate::Write::new(builder.tempfile_in(&self.path).map_err(|err| {
Error::Io {
source: err.into(),
message: "create named temp file in",
path: self.path.to_owned(),
})?),
self.object_hash,
))
}
})?))
}

fn finalize_object(
Expand All @@ -134,6 +186,11 @@ impl Store {
message: "hash tempfile in",
path: self.path.to_owned(),
})?;
self.finalize_object_at(&id, file)
}

fn finalize_object_at(&self, id: &gix_hash::oid, file: CompressedTempfile) -> Result<gix_hash::ObjectId, Error> {
let id = id.to_owned();
let object_path = loose::hash_path(&id, self.path.clone());
let object_dir = object_path
.parent()
Expand Down
13 changes: 13 additions & 0 deletions gix-odb/tests/odb/store/loose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ mod write {
db.try_find(&oid, &mut buf2)?.expect("id present").decode()?,
obj.decode()?
);
let actual = db.write_buf_with_known_id(oid, obj.kind, obj.data)?;
assert_eq!(actual, oid);
assert_eq!(
db.try_find(&oid, &mut buf2)?.expect("id present").decode()?,
obj.decode()?
);
let mut from = obj.data;
let actual = db.write_stream_with_known_id(oid, obj.kind, obj.data.len() as u64, &mut from)?;
assert_eq!(actual, oid);
assert_eq!(
db.try_find(&oid, &mut buf2)?.expect("id present").decode()?,
obj.decode()?
);
}
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion gix/src/repository/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl gix_object::Write for crate::Repository {
if self.objects.exists(&oid) {
return Ok(oid);
}
self.objects.write_buf(object, from)
self.objects.write_buf_with_known_id(oid, object, from)
}

fn write_stream(
Expand Down
Loading
Loading