Skip to content

Commit 958edef

Browse files
committed
feat: add InMemoryPassThrough implementation.
An implementation of `Header`, `Write` and `Find`, that can optionally write everything to an in-memory store, and if enabled, also read objects back from there. That way it can present a consistent view to objects from two locations.
1 parent b279957 commit 958edef

File tree

6 files changed

+235
-0
lines changed

6 files changed

+235
-0
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-odb/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde = ["dep:serde", "gix-hash/serde", "gix-object/serde", "gix-pack/serde"]
2121

2222
[dependencies]
2323
gix-features = { version = "^0.38.2", path = "../gix-features", features = ["rustsha1", "walkdir", "zlib", "crc32"] }
24+
gix-hashtable = { version = "^0.5.2", path = "../gix-hashtable" }
2425
gix-hash = { version = "^0.14.2", path = "../gix-hash" }
2526
gix-date = { version = "^0.9.0", path = "../gix-date" }
2627
gix-path = { version = "^0.10.10", path = "../gix-path" }

gix-odb/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ pub fn sink(object_hash: gix_hash::Kind) -> Sink {
6666
}
6767
}
6868

69+
///
70+
pub mod memory;
71+
6972
mod sink;
7073

7174
///

gix-odb/src/memory.rs

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use crate::find::Header;
2+
use gix_object::Data;
3+
use std::cell::RefCell;
4+
use std::ops::{Deref, DerefMut};
5+
6+
/// An object database to read from any implementation but write to memory.
7+
/// Previously written objects can be returned from memory upon query which
8+
/// makes the view of objects consistent, but it's impact temporary unless
9+
/// [`memory objects`](Proxy::memory) are persisted in a separate step.
10+
///
11+
/// It's possible to turn off the memory by removing it from the instance.
12+
pub struct Proxy<T> {
13+
/// The actual odb implementation
14+
inner: T,
15+
/// The kind of hash to produce when writing new objects.
16+
object_hash: gix_hash::Kind,
17+
/// The storage for in-memory objects.
18+
/// If `None`, the proxy will always read from and write-through to `inner`.
19+
pub memory: Option<RefCell<Storage>>,
20+
}
21+
22+
/// Lifecycle
23+
impl<T> Proxy<T> {
24+
/// Create a new instance using `odb` as actual object provider, with an empty in-memory store for
25+
/// objects that are to be written.
26+
/// Use `object_hash` to determine the kind of hash to produce when writing new objects.
27+
pub fn new(odb: T, object_hash: gix_hash::Kind) -> Proxy<T> {
28+
Proxy {
29+
inner: odb,
30+
object_hash,
31+
memory: Some(Default::default()),
32+
}
33+
}
34+
}
35+
36+
impl<T> gix_object::Find for Proxy<T>
37+
where
38+
T: gix_object::Find,
39+
{
40+
fn try_find<'a>(
41+
&self,
42+
id: &gix_hash::oid,
43+
buffer: &'a mut Vec<u8>,
44+
) -> Result<Option<Data<'a>>, gix_object::find::Error> {
45+
if let Some(map) = self.memory.as_ref() {
46+
let map = map.borrow();
47+
if let Some((kind, data)) = map.get(id) {
48+
buffer.clear();
49+
buffer.extend_from_slice(data);
50+
return Ok(Some(Data {
51+
kind: *kind,
52+
data: &*buffer,
53+
}));
54+
}
55+
}
56+
self.inner.try_find(id, buffer)
57+
}
58+
}
59+
60+
impl<T> crate::Header for Proxy<T>
61+
where
62+
T: crate::Header,
63+
{
64+
fn try_header(&self, id: &gix_hash::oid) -> Result<Option<Header>, gix_object::find::Error> {
65+
if let Some(map) = self.memory.as_ref() {
66+
let map = map.borrow();
67+
if let Some((kind, data)) = map.get(id) {
68+
return Ok(Some(Header::Loose {
69+
kind: *kind,
70+
size: data.len() as u64,
71+
}));
72+
}
73+
}
74+
self.inner.try_header(id)
75+
}
76+
}
77+
78+
impl<T> crate::Write for Proxy<T>
79+
where
80+
T: crate::Write,
81+
{
82+
fn write_stream(
83+
&self,
84+
kind: gix_object::Kind,
85+
size: u64,
86+
from: &mut dyn std::io::Read,
87+
) -> Result<gix_hash::ObjectId, crate::write::Error> {
88+
let Some(map) = self.memory.as_ref() else {
89+
return self.inner.write_stream(kind, size, from);
90+
};
91+
92+
let mut buf = Vec::new();
93+
from.read_to_end(&mut buf)?;
94+
95+
let id = gix_object::compute_hash(self.object_hash, kind, &buf);
96+
map.borrow_mut().insert(id, (kind, buf));
97+
Ok(id)
98+
}
99+
}
100+
101+
impl<T> Deref for Proxy<T> {
102+
type Target = T;
103+
104+
fn deref(&self) -> &Self::Target {
105+
&self.inner
106+
}
107+
}
108+
109+
impl<T> DerefMut for Proxy<T> {
110+
fn deref_mut(&mut self) -> &mut Self::Target {
111+
&mut self.inner
112+
}
113+
}
114+
115+
/// A mapping between an object id and all data corresponding to an object, acting like a `HashMap<ObjectID, (Kind, Data)>`.
116+
#[derive(Default, Debug, Clone, Eq, PartialEq)]
117+
pub struct Storage(gix_hashtable::HashMap<gix_hash::ObjectId, (gix_object::Kind, Vec<u8>)>);
118+
119+
impl Deref for Storage {
120+
type Target = gix_hashtable::HashMap<gix_hash::ObjectId, (gix_object::Kind, Vec<u8>)>;
121+
122+
fn deref(&self) -> &Self::Target {
123+
&self.0
124+
}
125+
}
126+
127+
impl DerefMut for Storage {
128+
fn deref_mut(&mut self) -> &mut Self::Target {
129+
&mut self.0
130+
}
131+
}

gix-odb/tests/odb/memory.rs

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use crate::odb::hex_to_id;
2+
use gix_object::{tree, FindExt};
3+
use gix_odb::{Header, HeaderExt, Write};
4+
use gix_testtools::tempfile::TempDir;
5+
6+
#[test]
7+
fn without_memory() -> crate::Result {
8+
let (mut odb, _tmp) = db_rw()?;
9+
let mut buf = Vec::new();
10+
let mem = odb.memory.take().expect("it starts out with memory set").into_inner();
11+
assert_eq!(mem.len(), 0, "no object is stored initially");
12+
let existing = hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c");
13+
let tree = odb.find_tree(&existing, &mut buf).expect("present and valid");
14+
assert_eq!(tree.entries.len(), 1);
15+
odb.header(existing).expect("header can be found just the same");
16+
17+
let mut tree = tree.to_owned();
18+
tree.entries.push(tree::Entry {
19+
mode: tree::EntryKind::Blob.into(),
20+
filename: "z-for-sorting_another-file-with-same-content".into(),
21+
oid: existing,
22+
});
23+
let new_tree_id = odb.write(&tree)?;
24+
assert_eq!(new_tree_id, hex_to_id("249b0b4106a5e9e7875e446a26468e22ec47a05c"));
25+
let actual = odb.header(new_tree_id).expect("header of new objects can be found");
26+
assert_eq!(actual.kind(), gix_object::Kind::Tree);
27+
assert_eq!(actual.size(), 104);
28+
29+
let new_tree = odb
30+
.find_tree(&new_tree_id, &mut buf)
31+
.expect("new tree is also available as object")
32+
.to_owned();
33+
assert_eq!(new_tree, tree);
34+
35+
Ok(())
36+
}
37+
38+
#[test]
39+
fn with_memory() -> crate::Result {
40+
let mut odb = db()?;
41+
assert_eq!(
42+
(*odb).iter()?.count(),
43+
6,
44+
"let's be sure we didn't accidentally write anything"
45+
);
46+
let mut buf = Vec::new();
47+
let existing = hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c");
48+
let tree = odb.find_tree(&existing, &mut buf).expect("present and valid");
49+
assert_eq!(tree.entries.len(), 1);
50+
odb.header(existing).expect("header can be found just the same");
51+
assert_eq!(
52+
odb.memory.as_ref().unwrap().borrow().len(),
53+
0,
54+
"nothing is stored when fetching objects - it's not an object cache"
55+
);
56+
57+
let mut tree = tree.to_owned();
58+
tree.entries.push(tree::Entry {
59+
mode: tree::EntryKind::Blob.into(),
60+
filename: "z-for-sorting_another-file-with-same-content".into(),
61+
oid: existing,
62+
});
63+
let new_tree_id = odb.write(&tree)?;
64+
assert_eq!(new_tree_id, hex_to_id("249b0b4106a5e9e7875e446a26468e22ec47a05c"));
65+
let actual = odb.header(new_tree_id).expect("header of new objects can be found");
66+
assert_eq!(actual.kind(), gix_object::Kind::Tree);
67+
assert_eq!(actual.size(), 104);
68+
69+
let new_tree = odb
70+
.find_tree(&new_tree_id, &mut buf)
71+
.expect("new tree is also available as object")
72+
.to_owned();
73+
assert_eq!(new_tree, tree);
74+
75+
let mem = odb.memory.take().expect("memory is still available").into_inner();
76+
assert_eq!(mem.len(), 1, "one new object was just written");
77+
78+
assert_eq!(
79+
odb.try_header(&new_tree_id)?,
80+
None,
81+
"without memory, the object can't be found anymore"
82+
);
83+
84+
Ok(())
85+
}
86+
87+
fn db() -> crate::Result<gix_odb::memory::Proxy<gix_odb::Handle>> {
88+
let odb = gix_odb::at(
89+
gix_testtools::scripted_fixture_read_only_standalone("repo_with_loose_objects.sh")?.join(".git/objects"),
90+
)?;
91+
Ok(gix_odb::memory::Proxy::new(odb, gix_hash::Kind::Sha1))
92+
}
93+
94+
fn db_rw() -> crate::Result<(gix_odb::memory::Proxy<gix_odb::Handle>, TempDir)> {
95+
let tmp = gix_testtools::scripted_fixture_writable_standalone("repo_with_loose_objects.sh")?;
96+
let odb = gix_odb::at(tmp.path().join(".git/objects"))?;
97+
Ok((gix_odb::memory::Proxy::new(odb, gix_hash::Kind::Sha1), tmp))
98+
}

gix-odb/tests/odb/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ fn db_small_packs() -> gix_odb::Handle {
1818
pub mod alternate;
1919
pub mod find;
2020
pub mod header;
21+
pub mod memory;
2122
pub mod regression;
2223
pub mod sink;
2324
pub mod store;

0 commit comments

Comments
 (0)